refactor(gates): the autonomy gate is the --gates flag parser
lib/util/autonomy-gate.mjs keeps only gatesValue + parseGatesArgs and the
CLI ({"ok":true,"gates":true|false}; exact usage error on a bad value;
the old --state/--event arguments are a usage error). The four commands
run 'autonomy-gate.mjs --gates [value]' and read gates. Prose (README,
operations, architecture, sdlc-playbook-gap) and the S24, gates-flag
and [#1] tests follow (R9).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
651be3468b
commit
308b149e70
12 changed files with 165 additions and 355 deletions
|
|
@ -1,18 +1,19 @@
|
|||
// tests/lib/autonomy-gate.test.mjs
|
||||
// Cover the autonomy-gate state machine (lib/util/autonomy-gate.mjs):
|
||||
// every legal transition + every invalid-transition error + idempotent
|
||||
// re-entry to `completed` + CLI-shim JSON-on-stdout contract.
|
||||
// Cover the --gates flag parser (lib/util/autonomy-gate.mjs): the exported
|
||||
// parser function, the CLI's JSON-on-stdout contract, and the one CLI line
|
||||
// each of the four commands runs.
|
||||
|
||||
import { test } from 'node:test';
|
||||
import { strict as assert } from 'node:assert';
|
||||
import { execFileSync, spawnSync } from 'node:child_process';
|
||||
import { readFileSync } from 'node:fs';
|
||||
import { dirname, join } from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import { transition, isTerminal, STATES, EVENTS } from '../../lib/util/autonomy-gate.mjs';
|
||||
import { fileURLToPath, pathToFileURL } from 'node:url';
|
||||
|
||||
const HERE = dirname(fileURLToPath(import.meta.url));
|
||||
const SHIM = join(HERE, '..', '..', 'lib', 'util', 'autonomy-gate.mjs');
|
||||
const ROOT = join(HERE, '..', '..');
|
||||
const SHIM = join(ROOT, 'lib', 'util', 'autonomy-gate.mjs');
|
||||
const GATES_USAGE = 'usage: --gates takes true, false, closed or open';
|
||||
|
||||
function runShim(args) {
|
||||
try {
|
||||
|
|
@ -26,200 +27,99 @@ function runShim(args) {
|
|||
}
|
||||
}
|
||||
|
||||
test('idle + start + gates=true → gates_on', () => {
|
||||
const r = transition(STATES.IDLE, EVENTS.START, { gates: true });
|
||||
assert.equal(r.ok, true);
|
||||
assert.equal(r.next_state, STATES.GATES_ON);
|
||||
});
|
||||
|
||||
test('idle + start + gates=false → auto_running', () => {
|
||||
const r = transition(STATES.IDLE, EVENTS.START, { gates: false });
|
||||
assert.equal(r.ok, true);
|
||||
assert.equal(r.next_state, STATES.AUTO_RUNNING);
|
||||
});
|
||||
|
||||
test('idle + start + gates omitted defaults to auto_running', () => {
|
||||
const r = transition(STATES.IDLE, EVENTS.START);
|
||||
assert.equal(r.ok, true);
|
||||
assert.equal(r.next_state, STATES.AUTO_RUNNING);
|
||||
});
|
||||
|
||||
test('gates_on + phase_boundary → paused_for_gate', () => {
|
||||
const r = transition(STATES.GATES_ON, EVENTS.PHASE_BOUNDARY);
|
||||
assert.equal(r.ok, true);
|
||||
assert.equal(r.next_state, STATES.PAUSED_FOR_GATE);
|
||||
});
|
||||
|
||||
test('gates_on + finish → completed', () => {
|
||||
const r = transition(STATES.GATES_ON, EVENTS.FINISH);
|
||||
assert.equal(r.ok, true);
|
||||
assert.equal(r.next_state, STATES.COMPLETED);
|
||||
});
|
||||
|
||||
test('auto_running + phase_boundary → auto_running (no pause)', () => {
|
||||
const r = transition(STATES.AUTO_RUNNING, EVENTS.PHASE_BOUNDARY);
|
||||
assert.equal(r.ok, true);
|
||||
assert.equal(r.next_state, STATES.AUTO_RUNNING);
|
||||
});
|
||||
|
||||
test('auto_running + finish → completed', () => {
|
||||
const r = transition(STATES.AUTO_RUNNING, EVENTS.FINISH);
|
||||
assert.equal(r.ok, true);
|
||||
assert.equal(r.next_state, STATES.COMPLETED);
|
||||
});
|
||||
|
||||
test('paused_for_gate + resume → gates_on', () => {
|
||||
const r = transition(STATES.PAUSED_FOR_GATE, EVENTS.RESUME);
|
||||
assert.equal(r.ok, true);
|
||||
assert.equal(r.next_state, STATES.GATES_ON);
|
||||
});
|
||||
|
||||
test('paused_for_gate + finish → completed', () => {
|
||||
const r = transition(STATES.PAUSED_FOR_GATE, EVENTS.FINISH);
|
||||
assert.equal(r.ok, true);
|
||||
assert.equal(r.next_state, STATES.COMPLETED);
|
||||
});
|
||||
|
||||
test('completed + any event → completed (idempotent re-entry)', () => {
|
||||
for (const ev of Object.values(EVENTS)) {
|
||||
const r = transition(STATES.COMPLETED, ev);
|
||||
assert.equal(r.ok, true, `event ${ev} should be tolerated from completed`);
|
||||
assert.equal(r.next_state, STATES.COMPLETED, `event ${ev} broke idempotency`);
|
||||
}
|
||||
});
|
||||
|
||||
test('idle + non-start event → invalid transition error', () => {
|
||||
const r = transition(STATES.IDLE, EVENTS.PHASE_BOUNDARY);
|
||||
assert.equal(r.ok, false);
|
||||
assert.match(r.error, /invalid transition.*idle/);
|
||||
});
|
||||
|
||||
test('gates_on + resume → invalid (resume is only valid from paused_for_gate)', () => {
|
||||
const r = transition(STATES.GATES_ON, EVENTS.RESUME);
|
||||
assert.equal(r.ok, false);
|
||||
});
|
||||
|
||||
test('auto_running + resume → invalid (auto-mode never pauses)', () => {
|
||||
const r = transition(STATES.AUTO_RUNNING, EVENTS.RESUME);
|
||||
assert.equal(r.ok, false);
|
||||
});
|
||||
|
||||
test('unknown state rejected with descriptive error', () => {
|
||||
const r = transition('zombie', EVENTS.START);
|
||||
assert.equal(r.ok, false);
|
||||
assert.match(r.error, /unknown state/);
|
||||
});
|
||||
|
||||
test('unknown event rejected with descriptive error', () => {
|
||||
const r = transition(STATES.IDLE, 'snooze');
|
||||
assert.equal(r.ok, false);
|
||||
assert.match(r.error, /unknown event/);
|
||||
});
|
||||
|
||||
test('isTerminal — only completed is terminal', () => {
|
||||
assert.equal(isTerminal(STATES.COMPLETED), true);
|
||||
for (const s of [STATES.IDLE, STATES.GATES_ON, STATES.AUTO_RUNNING, STATES.PAUSED_FOR_GATE]) {
|
||||
assert.equal(isTerminal(s), false, `${s} should not be terminal`);
|
||||
}
|
||||
});
|
||||
|
||||
test('CLI shim returns valid JSON on success (exit 0)', () => {
|
||||
const r = runShim(['--state', 'idle', '--event', 'start', '--gates', 'true']);
|
||||
assert.equal(r.code, 0);
|
||||
const parsed = JSON.parse(r.out.trim());
|
||||
assert.equal(parsed.ok, true);
|
||||
assert.equal(parsed.next_state, 'gates_on');
|
||||
});
|
||||
|
||||
test('CLI shim returns JSON error on invalid transition (exit 1)', () => {
|
||||
const r = runShim(['--state', 'idle', '--event', 'phase_boundary']);
|
||||
assert.equal(r.code, 1);
|
||||
const parsed = JSON.parse(r.out.trim());
|
||||
assert.equal(parsed.ok, false);
|
||||
assert.match(parsed.error, /invalid transition/);
|
||||
});
|
||||
|
||||
test('CLI shim missing required args returns usage error (exit 1)', () => {
|
||||
const r = runShim(['--state', 'idle']);
|
||||
assert.equal(r.code, 1);
|
||||
const parsed = JSON.parse(r.out.trim());
|
||||
assert.equal(parsed.ok, false);
|
||||
assert.match(parsed.error, /usage:/);
|
||||
});
|
||||
|
||||
// ---- --gates value rule (fix round 4.5a, #1) ---------------------------------
|
||||
// The shim is tested only through its CLI; the exit code is asserted before
|
||||
// any JSON.parse, so a crash or a usage error never reads as a wrong value.
|
||||
|
||||
function shimState(args) {
|
||||
// The exit code is asserted before any JSON.parse, so a crash or a usage
|
||||
// error never reads as a wrong value.
|
||||
function shimGates(args) {
|
||||
const r = runShim(args);
|
||||
assert.equal(r.code, 0, `expected exit 0 for ${JSON.stringify(args)}, got ${r.code}: ${r.out}`);
|
||||
const parsed = JSON.parse(r.out.trim());
|
||||
assert.equal(parsed.ok, true);
|
||||
return parsed.next_state;
|
||||
assert.deepEqual(parsed, { ok: true, gates: parsed.gates });
|
||||
assert.equal(typeof parsed.gates, 'boolean', `gates must be a boolean: ${r.out}`);
|
||||
return parsed.gates;
|
||||
}
|
||||
|
||||
test('[#1] bare --gates as the last argument → gates_on', () => {
|
||||
assert.equal(shimState(['--state', 'idle', '--event', 'start', '--gates']), 'gates_on');
|
||||
});
|
||||
|
||||
test('[#1] bare --gates before other flags swallows none of them → exit 0, gates_on', () => {
|
||||
assert.equal(shimState(['--gates', '--state', 'idle', '--event', 'start']), 'gates_on');
|
||||
});
|
||||
|
||||
test('[#1] --gates closed → gates_on', () => {
|
||||
assert.equal(shimState(['--state', 'idle', '--event', 'start', '--gates', 'closed']), 'gates_on');
|
||||
});
|
||||
|
||||
for (const bad of ['TRUE', 'yes', 'adaptive']) {
|
||||
test(`[#1] --gates ${bad} → usage error (exit 1)`, () => {
|
||||
const r = runShim(['--state', 'idle', '--event', 'start', '--gates', bad]);
|
||||
assert.equal(r.code, 1, `expected exit 1 for --gates ${bad}, got ${r.code}: ${r.out}`);
|
||||
const parsed = JSON.parse(r.out.trim());
|
||||
assert.equal(parsed.ok, false);
|
||||
assert.match(parsed.error, /usage/);
|
||||
});
|
||||
function shimUsage(args) {
|
||||
const r = runShim(args);
|
||||
assert.equal(r.code, 1, `expected exit 1 for ${JSON.stringify(args)}, got ${r.code}: ${r.out}`);
|
||||
const parsed = JSON.parse(r.out.trim());
|
||||
assert.equal(parsed.ok, false);
|
||||
assert.equal(Object.keys(parsed).sort().join(','), 'error,ok');
|
||||
return parsed.error;
|
||||
}
|
||||
|
||||
test('--gates true → gates_on', () => {
|
||||
assert.equal(shimState(['--state', 'idle', '--event', 'start', '--gates', 'true']), 'gates_on');
|
||||
// ---- the exported parser ----------------------------------------------------
|
||||
|
||||
test('gatesValue maps true|closed to on, false|open to off, anything else to undefined', async () => {
|
||||
const mod = await import(pathToFileURL(SHIM).href);
|
||||
assert.equal(typeof mod.gatesValue, 'function', 'autonomy-gate.mjs must export gatesValue');
|
||||
for (const v of ['true', 'closed']) assert.equal(mod.gatesValue(v), true, v);
|
||||
for (const v of ['false', 'open']) assert.equal(mod.gatesValue(v), false, v);
|
||||
for (const v of ['TRUE', 'yes', 'adaptive', '', undefined]) assert.equal(mod.gatesValue(v), undefined, String(v));
|
||||
});
|
||||
|
||||
test('--gates false → auto_running', () => {
|
||||
assert.equal(shimState(['--state', 'idle', '--event', 'start', '--gates', 'false']), 'auto_running');
|
||||
});
|
||||
|
||||
test('--gates open → auto_running', () => {
|
||||
assert.equal(shimState(['--state', 'idle', '--event', 'start', '--gates', 'open']), 'auto_running');
|
||||
});
|
||||
|
||||
// The `=` form reaches the shim whole as one token; it is read as `--gates <value>`.
|
||||
test('[#1] sibling: --gates=<value> is read as --gates <value>; an unknown or empty value is the usage error', () => {
|
||||
assert.equal(shimState(['--state', 'idle', '--event', 'start', '--gates=true']), 'gates_on');
|
||||
assert.equal(shimState(['--state', 'idle', '--event', 'start', '--gates=closed']), 'gates_on');
|
||||
assert.equal(shimState(['--state', 'idle', '--event', 'start', '--gates=false']), 'auto_running');
|
||||
assert.equal(shimState(['--state', 'idle', '--event', 'start', '--gates=open']), 'auto_running');
|
||||
for (const bad of ['--gates=maybe', '--gates=']) {
|
||||
const r = runShim(['--state', 'idle', '--event', 'start', bad]);
|
||||
assert.equal(r.code, 1, `expected exit 1 for ${bad}, got ${r.code}: ${r.out}`);
|
||||
const parsed = JSON.parse(r.out.trim());
|
||||
assert.equal(parsed.ok, false);
|
||||
assert.match(parsed.error, /usage: --gates takes true, false, closed or open/);
|
||||
test('the module is the flag parser only: no state machine is exported', async () => {
|
||||
const mod = await import(pathToFileURL(SHIM).href);
|
||||
for (const gone of ['STATES', 'EVENTS', 'transition', 'isTerminal']) {
|
||||
assert.equal(mod[gone], undefined, `autonomy-gate.mjs still exports ${gone}`);
|
||||
}
|
||||
});
|
||||
|
||||
// ---- the four commands' --gates rule and shim line ----------------------------
|
||||
// Each of the four commands 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.
|
||||
// ---- the CLI ----------------------------------------------------------------
|
||||
|
||||
test('CLI: bare --gates → {"ok":true,"gates":true}, exit 0', () => {
|
||||
assert.equal(shimGates(['--gates']), true);
|
||||
});
|
||||
|
||||
test('CLI: no flag → {"ok":true,"gates":false}, exit 0', () => {
|
||||
assert.equal(shimGates([]), false);
|
||||
});
|
||||
|
||||
test('CLI: --gates true and --gates closed → gates true', () => {
|
||||
assert.equal(shimGates(['--gates', 'true']), true);
|
||||
assert.equal(shimGates(['--gates', 'closed']), true);
|
||||
});
|
||||
|
||||
test('CLI: --gates false and --gates open → gates false', () => {
|
||||
assert.equal(shimGates(['--gates', 'false']), false);
|
||||
assert.equal(shimGates(['--gates', 'open']), false);
|
||||
});
|
||||
|
||||
test('CLI: the one-token --gates=<value> form is read as --gates <value>', () => {
|
||||
assert.equal(shimGates(['--gates=true']), true);
|
||||
assert.equal(shimGates(['--gates=closed']), true);
|
||||
assert.equal(shimGates(['--gates=false']), false);
|
||||
assert.equal(shimGates(['--gates=open']), false);
|
||||
});
|
||||
|
||||
for (const bad of [['--gates', 'TRUE'], ['--gates', 'yes'], ['--gates', 'adaptive'], ['--gates=maybe'], ['--gates=']]) {
|
||||
test(`CLI: ${bad.join(' ')} → the exact usage error, exit 1`, () => {
|
||||
assert.equal(shimUsage(bad), GATES_USAGE);
|
||||
});
|
||||
}
|
||||
|
||||
for (const old of [
|
||||
['--state', 'idle', '--event', 'start', '--gates', 'true'],
|
||||
['--state', 'idle', '--event', 'start'],
|
||||
['--gates', '--state', 'idle'],
|
||||
['--event', 'start'],
|
||||
]) {
|
||||
test(`CLI: the old state-machine arguments are a usage error (${old.join(' ')}), exit 1`, () => {
|
||||
assert.match(shimUsage(old), /^usage:/);
|
||||
});
|
||||
}
|
||||
|
||||
// ---- the four commands' --gates rule and CLI line ----------------------------
|
||||
// Each of the four commands holds one rule: the next token is the flag's value
|
||||
// only when it is true, false, closed or open; the CLI maps it, run once; a
|
||||
// broken CLI falls back to the same mapping in prose and the run goes on.
|
||||
|
||||
const ROOT = join(HERE, '..', '..');
|
||||
const GATES_COMMANDS = ['trekbrief', 'trekresearch', 'trekplan', 'trekexecute'];
|
||||
const GATES_LINE = 'node ${CLAUDE_PLUGIN_ROOT}/lib/util/autonomy-gate.mjs --gates [value]';
|
||||
|
||||
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.',
|
||||
'`gates: true` sets `gates_mode = true`, `false` sets it false.',
|
||||
'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.',
|
||||
];
|
||||
|
||||
|
|
@ -228,44 +128,46 @@ const commandText = (cmd) => readFileSync(join(ROOT, 'commands', `${cmd}.md`), '
|
|||
/** Whitespace (line breaks too) collapsed to one space. */
|
||||
const flat = (s) => s.replace(/\s+/g, ' ').trim();
|
||||
|
||||
/** The command's one shim line, `[value]` filled in with `value` (left out when ''). */
|
||||
function gatesShim(cmd, value) {
|
||||
/** The command's one CLI line, `[value]` filled in with `value` (left out when ''). */
|
||||
function gatesLine(cmd, value) {
|
||||
const hits = commandText(cmd).split('\n')
|
||||
.filter((l) => /^\s*node \$\{CLAUDE_PLUGIN_ROOT\}\/lib\/util\/autonomy-gate\.mjs\b/.test(l) && l.includes('--event start'));
|
||||
assert.equal(hits.length, 1, `${cmd}: expected exactly one autonomy-gate.mjs --event start line; found ${hits.length}`);
|
||||
.filter((l) => /^\s*node \$\{CLAUDE_PLUGIN_ROOT\}\/lib\/util\/autonomy-gate\.mjs\b/.test(l));
|
||||
assert.equal(hits.length, 1, `${cmd}: expected exactly one autonomy-gate.mjs line; found ${hits.length}`);
|
||||
const l = hits[0].trim();
|
||||
assert.match(l, / --state idle --event start --gates \[value\]$/, `${cmd}: the shim line must end "--gates [value]"`);
|
||||
assert.equal(l, GATES_LINE, `${cmd}: the line must read "${GATES_LINE}"`);
|
||||
return l.replace(' [value]', value ? ` ${value}` : '');
|
||||
}
|
||||
|
||||
const shimCache = new Map();
|
||||
const lineCache = new Map();
|
||||
|
||||
/** Run a shim line from the prose with the plugin root filled in; its `next_state`. Each distinct line runs once. */
|
||||
function shimNext(cmdLine) {
|
||||
if (!shimCache.has(cmdLine)) {
|
||||
/** Run a CLI line from the prose with the plugin root filled in; its `gates`. Each distinct line runs once. */
|
||||
function lineGates(cmdLine) {
|
||||
if (!lineCache.has(cmdLine)) {
|
||||
const r = spawnSync('bash', ['-c', cmdLine.split('${CLAUDE_PLUGIN_ROOT}').join(ROOT)], { cwd: ROOT, encoding: 'utf8' });
|
||||
assert.equal(r.status, 0, `shim exited ${r.status}: ${r.stdout}${r.stderr}`);
|
||||
shimCache.set(cmdLine, JSON.parse(r.stdout).next_state);
|
||||
assert.equal(r.status, 0, `CLI exited ${r.status}: ${r.stdout}${r.stderr}`);
|
||||
const parsed = JSON.parse(r.stdout);
|
||||
assert.equal(parsed.ok, true);
|
||||
lineCache.set(cmdLine, parsed.gates);
|
||||
}
|
||||
return shimCache.get(cmdLine);
|
||||
return lineCache.get(cmdLine);
|
||||
}
|
||||
|
||||
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`, () => {
|
||||
test(`[#1] commands/${cmd}.md holds the shared --gates rule and a CLI line that maps closed to gates true`, () => {
|
||||
const text = commandText(cmd);
|
||||
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');
|
||||
assert.doesNotMatch(text, /--state \w+ --event \w+/, `${cmd}: the state-machine line is still there`);
|
||||
assert.doesNotMatch(f, /next_state|auto_running|gates_on/, `${cmd}: still reads a state of the old state machine`);
|
||||
assert.equal(lineGates(gatesLine(cmd, 'closed')), true);
|
||||
});
|
||||
}
|
||||
|
||||
// `/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('every command\'s shim line maps a bare --gates to gates_on and open to auto_running', () => {
|
||||
// --gates (the next token is not a value), so the line runs bare.
|
||||
test('every command\'s CLI line maps a bare --gates to true and open to false', () => {
|
||||
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`);
|
||||
assert.equal(lineGates(gatesLine(cmd, '')), true, `${cmd}: bare --gates`);
|
||||
assert.equal(lineGates(gatesLine(cmd, 'open')), false, `${cmd}: --gates open`);
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -25,7 +25,6 @@ import { join, dirname } from 'node:path';
|
|||
import { fileURLToPath } from 'node:url';
|
||||
import { parseDocument } from '../../lib/util/frontmatter.mjs';
|
||||
import { resolveProfile, loadProfile } from '../../lib/profiles/resolver.mjs';
|
||||
import { STATES } from '../../lib/util/autonomy-gate.mjs';
|
||||
import { TREKRESEARCH_ALLOWED } from '../../lib/exporters/field-allowlist.mjs';
|
||||
|
||||
const HERE = dirname(fileURLToPath(import.meta.url));
|
||||
|
|
@ -395,11 +394,11 @@ 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|closed|open] → gates_on|auto_running.
|
||||
// State machine: idle → gates_on|auto_running → paused_for_gate → completed.
|
||||
// autonomy-gate.mjs is the --gates flag parser: --gates [true|false|closed|open]
|
||||
// → {"ok":true,"gates":true|false}. It has no state machine (R9).
|
||||
// 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
|
||||
// state machine `idle → approved → executing → merge-pending → main-merged`, and
|
||||
// later named the removed one (gates_on, auto_running, paused_for_gate); (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}` —
|
||||
// there is no strictness policy and no `adaptive` value: the flag is boolean, and
|
||||
|
|
@ -409,21 +408,19 @@ test('operations.md + command-modes.md describe the real autonomy-gate, not a fa
|
|||
const ops = read('docs/operations.md');
|
||||
const modes = read('docs/command-modes.md');
|
||||
|
||||
// F1 — fabricated state names (merge-pending/main-merged are unique to the false claim)
|
||||
for (const bad of ['merge-pending', 'main-merged']) {
|
||||
// F1 — state names: fabricated (merge-pending/main-merged) or removed with the state machine (R9)
|
||||
for (const bad of ['merge-pending', 'main-merged', 'gates_on', 'auto_running', 'paused_for_gate']) {
|
||||
assert.ok(
|
||||
!ops.includes(bad),
|
||||
`docs/operations.md: fabricated autonomy state "${bad}" — autonomy-gate.mjs has no such state; fix the prose, not this test`,
|
||||
);
|
||||
}
|
||||
|
||||
// F1 — the real transient states (derived from autonomy-gate.mjs STATES) must be documented
|
||||
for (const real of [STATES.GATES_ON, STATES.AUTO_RUNNING, STATES.PAUSED_FOR_GATE]) {
|
||||
assert.ok(
|
||||
ops.includes(real),
|
||||
`docs/operations.md must name the real autonomy-gate state "${real}" (from autonomy-gate.mjs STATES)`,
|
||||
);
|
||||
}
|
||||
// F1 — the module is documented as what it is: the flag parser
|
||||
assert.ok(
|
||||
ops.includes('`lib/util/autonomy-gate.mjs` is the `--gates` flag parser'),
|
||||
'docs/operations.md must describe lib/util/autonomy-gate.mjs as the --gates flag parser',
|
||||
);
|
||||
|
||||
// F2 — event-emit does NOT record each autonomy-gate transition
|
||||
assert.ok(
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
// tests/lib/gates-flag-coverage.test.mjs
|
||||
// Step 11 (plan-v2) — pin that all four pipeline commands document the
|
||||
// --gates autonomy-control flag and consume the autonomy-gate state
|
||||
// machine via the lib/util/autonomy-gate.mjs CLI shim.
|
||||
// --gates autonomy-control flag and map its value through the
|
||||
// lib/util/autonomy-gate.mjs flag parser.
|
||||
|
||||
import { test } from 'node:test';
|
||||
import { strict as assert } from 'node:assert';
|
||||
|
|
@ -34,7 +34,7 @@ for (const cmdPath of COMMANDS) {
|
|||
const text = read(cmdPath);
|
||||
assert.ok(
|
||||
text.includes('autonomy-gate.mjs'),
|
||||
`${cmdPath} should reference lib/util/autonomy-gate.mjs as the state-machine implementation`,
|
||||
`${cmdPath} should reference lib/util/autonomy-gate.mjs, the --gates flag parser`,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
|
@ -43,6 +43,6 @@ test('commands/trekexecute.md mentions MAIN_MERGE_GATE', () => {
|
|||
const text = read('commands/trekexecute.md');
|
||||
assert.ok(
|
||||
text.includes('MAIN_MERGE_GATE'),
|
||||
'commands/trekexecute.md should name MAIN_MERGE_GATE — the only boundary that always pauses regardless of --gates',
|
||||
'commands/trekexecute.md should name MAIN_MERGE_GATE — the lock on the review verdict before the merge into main; the guard is what asks before merge and push, whatever --gates says',
|
||||
);
|
||||
});
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue