fix(gates): one --gates rule in every command, run through the shim, and no executor pause the executor lacks
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
e2fcf4060c
commit
af36578f12
11 changed files with 190 additions and 52 deletions
8
tests/fixtures/jsonl-schemas.md
vendored
8
tests/fixtures/jsonl-schemas.md
vendored
|
|
@ -19,10 +19,10 @@
|
|||
|
||||
| schema_id | fields | writer_path | line_ref | v4.1 additive | PII |
|
||||
|-----------|--------|-------------|----------|---------------|-----|
|
||||
| trekbrief-stats | ts, task, slug, mode, interview_turns, review_iterations, brief_quality, research_topics, auto_research, auto_result, intent_approved, project_dir, order_id, order_claimed_at, order_reason | commands/trekbrief.md (orchestrator-emit Phase 7; order_* added by lib/stats/stats-append.mjs) | trekbrief.md:1128-1152 | profile, phase_models, profile_source | none |
|
||||
| trekresearch-stats | ts, question, mode, scope, engine, slug, project_dir, brief_path, dimensions, dimensions_baseline, dimensions_baseline_preserved, effort, conv_turns, empty_turns, unique_sources, agents_local, agents_external, gemini_used, confidence, contradictions, open_questions | lib/stats/stats-append.mjs (trekresearch.md Stats tracking) | trekresearch.md:782-808 | profile, phase_models, parallel_agents, external_research_enabled, profile_source | none |
|
||||
| trekplan-stats | ts, task, mode, slug, brief_path, project_dir, codebase_size, codebase_files, agents_deployed, deep_dives, research_briefs_used, research_scout_used, critic_verdict, guardian_verdict, outcome | lib/stats/stats-append.mjs (trekplan.md Phase 12) | trekplan.md:1294-1322 | profile, phase_models, parallel_agents, profile_source | none |
|
||||
| trekexecute-stats (Phase 9 record) | ts, plan, plan_type, mode, result, steps_total, steps_passed, steps_failed, steps_skipped, failed_at_step | lib/stats/stats-append.mjs (trekexecute.md Phase 9) | trekexecute.md:2909-2931 | profile, phase_models, profile_source | none |
|
||||
| trekbrief-stats | ts, task, slug, mode, interview_turns, review_iterations, brief_quality, research_topics, auto_research, auto_result, intent_approved, project_dir, order_id, order_claimed_at, order_reason | commands/trekbrief.md (orchestrator-emit Phase 7; order_* added by lib/stats/stats-append.mjs) | trekbrief.md:1140-1164 | profile, phase_models, profile_source | none |
|
||||
| trekresearch-stats | ts, question, mode, scope, engine, slug, project_dir, brief_path, dimensions, dimensions_baseline, dimensions_baseline_preserved, effort, conv_turns, empty_turns, unique_sources, agents_local, agents_external, gemini_used, confidence, contradictions, open_questions | lib/stats/stats-append.mjs (trekresearch.md Stats tracking) | trekresearch.md:806-839 | profile, phase_models, parallel_agents, external_research_enabled, profile_source | none |
|
||||
| trekplan-stats | ts, task, mode, slug, brief_path, project_dir, codebase_size, codebase_files, agents_deployed, deep_dives, research_briefs_used, research_scout_used, critic_verdict, guardian_verdict, outcome | lib/stats/stats-append.mjs (trekplan.md Phase 12) | trekplan.md:1305-1333 | profile, phase_models, parallel_agents, profile_source | none |
|
||||
| trekexecute-stats (Phase 9 record) | ts, plan, plan_type, mode, result, steps_total, steps_passed, steps_failed, steps_skipped, failed_at_step | lib/stats/stats-append.mjs (trekexecute.md Phase 9) | trekexecute.md:2921-2943 | profile, phase_models, profile_source | none |
|
||||
| trekexecute-stats (autonomy events) | ts, event, known_event, payload | lib/stats/event-emit.mjs `emit()` | event-emit.mjs:78-99 | payload.profile, payload.phase_models, payload.profile_source | none |
|
||||
| trekexecute-stats (PostToolUse Bash) | ts, session_id, command_excerpt, duration_ms, success | hooks/scripts/post-bash-stats.mjs (Bash PostToolUse) | post-bash-stats.mjs:42-54 | none (hook is plugin-level, not profile-aware) | command_excerpt (CWE-212) |
|
||||
| trekreview-stats | ts, slug, verdict, counts (BLOCKER/MAJOR/MINOR/SUGGESTION), reviewed_files_count, mode, duration_ms | lib/stats/stats-append.mjs (trekreview.md Phase 8) | trekreview.md:636-686 | profile, phase_models, profile_source | none |
|
||||
|
|
|
|||
|
|
@ -395,15 +395,16 @@ test('operations.md custom-profile prose matches findProfilePath resolution orde
|
|||
|
||||
// --- S24 — anti-false-claim: operations.md + command-modes.md autonomy-gate truth-pass ---
|
||||
//
|
||||
// autonomy-gate.mjs is a BOOLEAN gate: --gates {true|false} → gates_on|auto_running.
|
||||
// autonomy-gate.mjs is a BOOLEAN gate: --gates [true|false|closed|open] → gates_on|auto_running.
|
||||
// State machine: idle → gates_on|auto_running → paused_for_gate → completed.
|
||||
// The old prose (operations.md:15 / command-modes.md gates rows) (1) fabricated a
|
||||
// state machine `idle → approved → executing → merge-pending → main-merged` — only
|
||||
// `idle` exists in autonomy-gate.mjs STATES; (2) claimed event-emit.mjs "records
|
||||
// each transition" — it emits 3 named lifecycle events and is decoupled from the
|
||||
// (pure, no-I/O) autonomy-gate; (3) advertised `--gates {open|closed|adaptive}` —
|
||||
// open/closed/adaptive is a DERIVED gates_mode policy in the command layer, not a
|
||||
// flag value (the CLI shim + all 4 command docs take boolean `--gates {true|false}`).
|
||||
// there is no strictness policy and no `adaptive` value: the flag is boolean, and
|
||||
// the CLI shim + all 4 command docs take `--gates [true|false|closed|open]`
|
||||
// (bare, true, closed = on; false, open = off; fix round 4.5a, #1).
|
||||
test('operations.md + command-modes.md describe the real autonomy-gate, not a fabricated one [S24]', () => {
|
||||
const ops = read('docs/operations.md');
|
||||
const modes = read('docs/command-modes.md');
|
||||
|
|
@ -430,13 +431,13 @@ test('operations.md + command-modes.md describe the real autonomy-gate, not a fa
|
|||
'docs/operations.md: event-emit records 3 named lifecycle events, not "each transition" of the (decoupled) autonomy-gate; fix the prose',
|
||||
);
|
||||
|
||||
// F3 — --gates is boolean; open/closed/adaptive is a derived gates_mode policy, not a flag enum
|
||||
// F3 — --gates is boolean; there is no open/closed/adaptive strictness enum
|
||||
const GATES_ENUM_FORMS = ['{open|closed|adaptive}', '{open\\|closed\\|adaptive}'];
|
||||
for (const [label, txt] of [['operations.md', ops], ['command-modes.md', modes]]) {
|
||||
for (const bad of GATES_ENUM_FORMS) {
|
||||
assert.ok(
|
||||
!txt.includes(bad),
|
||||
`docs/${label}: --gates flag enum "${bad}" is false — flag is boolean {true|false}; open/closed/adaptive is a derived gates_mode; fix the prose, not this test`,
|
||||
`docs/${label}: --gates flag enum "${bad}" is false — the flag is boolean: bare, true or closed = on, false or open = off, and there is no adaptive; fix the prose, not this test`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -523,3 +523,108 @@ test('keep: /trekbrief\'s auto path still requires an approved intent', () => {
|
|||
assert.equal(partAt('commands/trekbrief.md', '**Auto requires an approved intent.**', toBlank),
|
||||
'**Auto requires an approved intent.** If Phase 4h ended "Leave unapproved", the auto chain would halt at `/trekplan` anyway — print the manual-path output instead and stop.');
|
||||
});
|
||||
|
||||
// --- #1 ----------------------------------------------------------------------------
|
||||
// `--gates` meant one thing in the shim (a boolean), another in the docs (an
|
||||
// open/closed/adaptive strictness policy), and /trekexecute promised pauses it
|
||||
// never makes. Each of the four commands now holds one rule: the next token is
|
||||
// the flag's value only when it is true, false, closed or open; the shim maps
|
||||
// it, run once; a broken shim falls back to the same mapping in prose and the
|
||||
// run goes on. /trekexecute records gates_mode and pauses on nothing.
|
||||
|
||||
const GATES_COMMANDS = ['trekbrief', 'trekresearch', 'trekplan', 'trekexecute'];
|
||||
|
||||
const GATES_RULE = [
|
||||
'`--gates` takes the next token as its value only when it is `true`, `false`, `closed` or `open` (strip both); any other next token is not its value (strip only the flag).',
|
||||
'Run the shim once to map the value, bare when there is no value:',
|
||||
'`next_state: gates_on` sets `gates_mode = true`, `auto_running` sets it false, and nothing else reads the state.',
|
||||
'On exit 1 or no JSON (a broken shim; the four values cannot cause it), print the error in one line and map in prose instead: bare, `true`, `closed` → true; `false`, `open` → false. The run goes on either way.',
|
||||
];
|
||||
|
||||
/** The command's one shim line, `[value]` filled in with `value` (left out when ''). */
|
||||
function gatesShim(cmd, value) {
|
||||
const l = line(read(`commands/${cmd}.md`), 'util/autonomy-gate.mjs', '--event start');
|
||||
assert.match(l, / --state idle --event start --gates \[value\]$/, `${cmd}: the shim line must end "--gates [value]"`);
|
||||
return l.replace(' [value]', value ? ` ${value}` : '');
|
||||
}
|
||||
|
||||
/** Run a shim line from the prose; its `next_state`. */
|
||||
function shimNext(cmdLine) {
|
||||
const r = bash(cmdLine, ROOT);
|
||||
assert.equal(r.status, 0, `shim exited ${r.status}: ${r.stdout}${r.stderr}`);
|
||||
return JSON.parse(r.stdout).next_state;
|
||||
}
|
||||
|
||||
for (const cmd of GATES_COMMANDS) {
|
||||
test(`[#1] commands/${cmd}.md holds the shared --gates rule and a shim line that maps closed to gates_on`, () => {
|
||||
const text = read(`commands/${cmd}.md`);
|
||||
const f = flat(text);
|
||||
for (const s of GATES_RULE) assert.ok(f.includes(s), `${cmd}: missing the shared --gates sentence: ${s}`);
|
||||
assert.doesNotMatch(text, /--state X --event Y/, `${cmd}: the placeholder shim line is still there`);
|
||||
assert.doesNotMatch(f, /consumed by the autonomy-gate/, `${cmd}: still says the flag is consumed by the state machine`);
|
||||
assert.equal(shimNext(gatesShim(cmd, 'closed')), 'gates_on');
|
||||
});
|
||||
}
|
||||
|
||||
// Review focus: `/trekplan --gates --project d` and `/trekexecute --gates plan.md`
|
||||
// are a bare --gates (the next token is not a value), so the shim runs bare.
|
||||
test('keep: every command\'s shim line maps a bare --gates to gates_on and open to auto_running', () => {
|
||||
for (const cmd of GATES_COMMANDS) {
|
||||
assert.equal(shimNext(gatesShim(cmd, '')), 'gates_on', `${cmd}: bare --gates`);
|
||||
assert.equal(shimNext(gatesShim(cmd, 'open')), 'auto_running', `${cmd}: --gates open`);
|
||||
}
|
||||
});
|
||||
|
||||
test('[#1] sibling: commands/trekbrief.md promises no execute pause and no pause at every wave in the executor', () => {
|
||||
const f = flat(read('commands/trekbrief.md'));
|
||||
assert.doesNotMatch(f, /every wave in the executor/, 'trekbrief still promises a pause at every wave in the executor');
|
||||
assert.doesNotMatch(f, /\(research, plan, execute\)/, 'trekbrief still promises an execute pause');
|
||||
assert.match(f, /approval pauses at every phase boundary in the downstream pipeline \(research, plan\)\./);
|
||||
});
|
||||
|
||||
test('[#1] sibling: commands/trekexecute.md records gates_mode and pauses only at the merge into main', () => {
|
||||
const f = flat(read('commands/trekexecute.md'));
|
||||
assert.doesNotMatch(f, /consumed by the autonomy-gate state machine/);
|
||||
assert.match(f, /`\/trekexecute` records `gates_mode`; no phase of it pauses on it\. The one stop is the merge into main, which always asks \(Phase 8\)\./);
|
||||
});
|
||||
|
||||
test('[#1] sibling: docs/operations.md gives the four values and the bare form, and no executor strictness table', () => {
|
||||
const text = read('docs/operations.md');
|
||||
const sec = flat(section(text, '## Autonomy mode (`--gates`, v3.4.0)'));
|
||||
assert.doesNotMatch(text, /\{open\\?\|closed\\?\|adaptive\}/);
|
||||
assert.doesNotMatch(sec, /\| `gates_mode` \| Derived from/, 'the executor strictness table is still there');
|
||||
assert.doesNotMatch(sec, /\| `adaptive` \|/, 'the executor strictness table is still there');
|
||||
assert.doesNotMatch(sec, /how strict/);
|
||||
assert.doesNotMatch(sec, /gates_mode_flag_override/, 'still names an override record the executor does not write');
|
||||
assert.match(sec, /accept `--gates \[true\|false\|closed\|open\]`/);
|
||||
assert.match(sec, /A bare `--gates`, `--gates true` or `--gates closed` turns gating \*\*on\*\*/);
|
||||
for (const s of ['gates_on', 'auto_running', 'paused_for_gate']) assert.ok(sec.includes(s), `the state ${s} stays named`);
|
||||
});
|
||||
|
||||
test('[#1] sibling: docs/architecture.md does not call --gates open the executor\'s low effort', () => {
|
||||
const text = read('docs/architecture.md');
|
||||
assert.doesNotMatch(text, /--gates open/);
|
||||
assert.match(text, /\(`\/trekexecute` low effort = one session at a time\)/);
|
||||
});
|
||||
|
||||
test('[#1] sibling: docs/command-modes.md gives every --gates row the four values and no strictness policy', () => {
|
||||
const text = read('docs/command-modes.md');
|
||||
assert.doesNotMatch(text, /--gates \{true\\\|false\}/);
|
||||
assert.doesNotMatch(text, /\{open\\?\|closed\\?\|adaptive\}/);
|
||||
assert.doesNotMatch(text, /Policy \(`gates_mode`\)/, 'a row still points at a gates_mode strictness policy');
|
||||
const rows = text.split('\n').filter((l) => l.startsWith('| `--gates'));
|
||||
assert.equal(rows.length, 4, 'one --gates row per command');
|
||||
for (const r of rows) {
|
||||
assert.ok(r.startsWith('| `--gates [true\\|false\\|closed\\|open]` |'), `row does not give the four values: ${r}`);
|
||||
assert.match(r, /`## Autonomy mode` in `docs\/operations\.md`/);
|
||||
}
|
||||
});
|
||||
|
||||
test('[#1] sibling: README.md gives the four values, and no strictness table', () => {
|
||||
const text = read('README.md');
|
||||
const sec = flat(section(text, '### Autonomy mode (`--gates`, v3.4.0)'));
|
||||
assert.doesNotMatch(text, /\{open\|closed\|adaptive\}/);
|
||||
assert.doesNotMatch(sec, /\| Value \| Behavior \|/, 'the strictness table is still there');
|
||||
assert.doesNotMatch(sec, /`adaptive` \(default\)/);
|
||||
assert.match(sec, /accept `--gates \[true\|false\|closed\|open\]`/);
|
||||
});
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue