feat(ultraplan-local): add autonomy-gate state machine + manifest schema extensions for skip_commit_check + memory_write

Step 4 of plan-v2 (ultra-pipeline-speedup).

lib/util/autonomy-gate.mjs (NEW)
  5-state machine {idle, gates_on, auto_running, paused_for_gate, completed}
  honoring the --gates flag intent. Re-entry to completed is idempotent.
  Includes CLI shim:
    node lib/util/autonomy-gate.mjs --state X --event Y [--gates true|false]
  → JSON: { ok, next_state | error }, exit 0 on success / 1 on invalid.

lib/parsers/manifest-yaml.mjs (EXTENDED)
  OPTIONAL_KEYS list adds skip_commit_check and memory_write — both boolean,
  default false when absent, MANIFEST_OPTIONAL_TYPE when non-boolean.
  Existing REQUIRED_KEYS contract untouched; existing 9 manifest tests
  still pass.

Tests: 19 (autonomy-gate) + 8 (manifest-schema-extensions) = 27 new.

[skip-docs]
This commit is contained in:
Kjell Tore Guttormsen 2026-05-04 06:28:47 +02:00
commit 645f01625b
6 changed files with 602 additions and 2 deletions

View file

@ -20,6 +20,19 @@ const REQUIRED_KEYS = [
'must_contain',
];
// Optional manifest keys (plan-v2 Step 4). Absence == false.
// `skip_commit_check`: opt out of the per-step commit assertion (e.g. memory-only steps).
// `memory_write` : marks a step that writes to ~/.claude/projects/.../memory/
// so the executor can route it through the memory truth gate.
const OPTIONAL_KEYS = [
'skip_commit_check',
'memory_write',
];
const OPTIONAL_BOOLEAN_KEYS = new Set(OPTIONAL_KEYS);
export { OPTIONAL_KEYS };
/**
* Extract the first fenced YAML block whose first non-blank line begins with
* `manifest:`.
@ -84,6 +97,19 @@ export function parseManifest(stepBody) {
errors.push(issue('MANIFEST_COUNT_TYPE', 'min_file_count must be a number'));
}
for (const k of OPTIONAL_BOOLEAN_KEYS) {
if (k in parsed) {
if (typeof parsed[k] !== 'boolean') {
errors.push(issue(
'MANIFEST_OPTIONAL_TYPE',
`${k} must be boolean if present (got ${typeof parsed[k]})`,
));
}
} else {
parsed[k] = false; // default: absence == false
}
}
return { valid: errors.length === 0, errors, warnings, parsed };
}