* chore(#2992): widen the when= grammar and key the section manifest per workflow Epic #1671 Phase 6.1. Two blockers stopped the fragment model reaching any file beyond execute-phase.md: the when= vocabulary was frozen at 4 atoms (3 execute-phase-specific), and the section manifest was single-workflow by construction with 'execute-phase' hardcoded into buildSectionManifestField. - widen WHEN_VOCABULARY 4 -> 14 via a coordinated ADR-1671 amendment; the grammar stays CLOSED (one atom, no operators, negation or nesting) and WHEN_PREDICATES stays a hand-written literal map, never deriving a predicate from its atom string - InvocationFacts gains flags: ReadonlySet<string> plus three computed state booleans; add the missing reverse vocabulary/predicate parity guard - key the manifest artifact per workflow; a stale flat {sections:[...]} artifact now fails shape validation instead of being misattributed - wire the field into six init entry points and parse the flags each needs An atom ships only with both a real consuming section and a fact the init seam actually computes. Six surveyed atoms are withheld because their workflows have no dedicated init entry point; an atom without a computed fact evaluates false forever and silently disables its own section. Fixes a defect found while wiring: parseNamedArgs always materializes a boolean flag key, so folding its false into the absent sentinel is required or every flag reads as present and gating is silently always-on. Also resolves ADR-1671:194 by measurement: --mvp stays unmarkable, because its interleaved sites are always-run flag resolution and a ~340 byte block that already delegates lazily. Refs #2992 * fix(#2992): treat any falsy option value as an absent flag and reject unsafe manifest read paths Findings from two orthogonal reviews (Claude /code-review + an isolated adversarial pass); both independently reproduced the first one. - MAJOR: the flags-builder treated only `undefined` as absent, but parseNamedArgs yields `null` for an absent value-flag and `false` for an absent boolean-flag, so `--granularity` read as present on every plan-phase invocation. Fixed at the root: a flag is present iff its option value is truthy. The six per-handler `|| undefined` folds are now redundant and removed, which also closes the duplicate-translation and missed-onboard-handler findings. - MAJOR: state:needs-codebase-map had zero coverage. Added unit, property and real-CLI integration tests. - MINOR: reject absolute, UNC/drive and `..`-traversing `read` paths in the manifest, degrading the whole load to null like every other shape violation. Verified: `/etc/passwd` previously reached section_manifest.read. - MINOR: corrected a stale "4 to 20" doc comment; the vocabulary is 14. Refs #2992 * test(#2992): update the generator suite for the per-workflow manifest shape The remote matrix went red with 5 unique failures, identical on linux-node22 and linux-node24, all in tests/gen-section-manifest.test.cjs. Re-keying the artifact to {workflows:{...}} left this suite asserting the old flat {sections:[...]} shape; nothing else in the tree still does. - three tests read manifest.sections.length, now undefined; retargeted at workflows.<name> with their original intent preserved (a fenced or loop-host marker still asserts NO section is produced, not merely a changed count) - the stale-manifest test wrote its fixture in the OLD shape, so it tripped shape validation and stopped exercising staleness at all. Its fixture is now valid-but-mismatched so FAIL_STALE is genuinely reached again. - added the coverage that exposed: a pre-6.1 flat artifact must report FAIL_MANIFEST_MALFORMED_SHAPE. That is the real upgrade path for an installed tree and nothing covered it. Refs #2992 * chore(#2992): backfill changeset pr number to 3013 --------- Co-authored-by: sim <sim@local>
This commit is contained in:
@@ -24,12 +24,12 @@ import { parseNamedArgs } from './command-arg-projection.cjs';
|
||||
// ─── Types ────────────────────────────────────────────────────────────────────
|
||||
|
||||
interface InitModule {
|
||||
cmdInitExecutePhase(cwd: string, phase: string | undefined, raw: boolean, opts: Record<string, string | boolean | null>): void;
|
||||
cmdInitPlanPhase(cwd: string, phase: string | undefined, raw: boolean, opts: Record<string, string | boolean | null>): void;
|
||||
cmdInitNewProject(cwd: string, raw: boolean): void;
|
||||
cmdInitNewMilestone(cwd: string, raw: boolean): void;
|
||||
cmdInitExecutePhase(cwd: string, phase: string | undefined, raw: boolean, opts: Record<string, string | boolean | null | undefined>): void;
|
||||
cmdInitPlanPhase(cwd: string, phase: string | undefined, raw: boolean, opts: Record<string, string | boolean | null | undefined>): void;
|
||||
cmdInitNewProject(cwd: string, raw: boolean, options?: Record<string, string | boolean | null | undefined>): void;
|
||||
cmdInitNewMilestone(cwd: string, raw: boolean, options?: Record<string, string | boolean | null | undefined>): void;
|
||||
cmdInitOnboard(cwd: string, raw: boolean, opts?: Record<string, string | boolean | null>): void;
|
||||
cmdInitQuick(cwd: string, name: string, raw: boolean): void;
|
||||
cmdInitQuick(cwd: string, name: string, raw: boolean, options?: Record<string, string | boolean | null | undefined>): void;
|
||||
cmdInitIngestDocs(cwd: string, raw: boolean): void;
|
||||
cmdInitResume(cwd: string, raw: boolean): void;
|
||||
cmdInitVerifyWork(cwd: string, phase: string | undefined, raw: boolean): void;
|
||||
@@ -37,7 +37,7 @@ interface InitModule {
|
||||
cmdInitTodos(cwd: string, phase: string | undefined, raw: boolean): void;
|
||||
cmdInitMilestoneOp(cwd: string, raw: boolean): void;
|
||||
cmdInitMapCodebase(cwd: string, raw: boolean): void;
|
||||
cmdInitProgress(cwd: string, raw: boolean): void;
|
||||
cmdInitProgress(cwd: string, raw: boolean, options?: Record<string, string | boolean | null | undefined>): void;
|
||||
cmdInitManager(cwd: string, raw: boolean): void;
|
||||
cmdInitNewWorkspace(cwd: string, raw: boolean): void;
|
||||
cmdInitListWorkspaces(cwd: string, raw: boolean): void;
|
||||
@@ -62,25 +62,50 @@ function routeInitCommand({ init, args, cwd, raw, error }: RouteInitCommandOptio
|
||||
error,
|
||||
unknownMessage: (_subcommand: string, available: string[]) => `Unknown init workflow: ${_subcommand}\nAvailable: ${available.join(', ')}`,
|
||||
handlers: {
|
||||
// #2932/#2992: `parseNamedArgs` never yields `undefined` for an absent
|
||||
// flag (value-flags default to `null`, booleanFlags default to `false`);
|
||||
// `buildSectionManifestField`'s flags-Set builder (src/init.cts) is the
|
||||
// single source of truth for flag ABSENCE and gates on value truthiness,
|
||||
// so `namedArgs` is passed through here uncoerced.
|
||||
'execute-phase': () => {
|
||||
// #2932: 'wave' is boolean/token-presence (parseNamedArgs's booleanFlags
|
||||
// semantics already match the design's token-presence rule: `--wave` alone,
|
||||
// `--wave 0`, and duplicate `--wave 1 --wave 2` all resolve to `true`;
|
||||
// near-miss tokens `--waves`/`--wave-filter` never match the exact `--wave` token).
|
||||
const namedArgs = parseNamedArgs(args, [], ['validate', 'tdd', 'wave']);
|
||||
init.cmdInitExecutePhase(cwd, args[2], raw, { validate: namedArgs['validate'], tdd: namedArgs['tdd'], wave: namedArgs['wave'] });
|
||||
init.cmdInitExecutePhase(cwd, args[2], raw, {
|
||||
validate: namedArgs['validate'],
|
||||
tdd: namedArgs['tdd'],
|
||||
wave: namedArgs['wave'],
|
||||
});
|
||||
},
|
||||
'plan-phase': () => {
|
||||
const namedArgs = parseNamedArgs(args, ['granularity'], ['validate', 'tdd']);
|
||||
init.cmdInitPlanPhase(cwd, args[2], raw, { validate: namedArgs['validate'], tdd: namedArgs['tdd'], granularity: namedArgs['granularity'] });
|
||||
init.cmdInitPlanPhase(cwd, args[2], raw, {
|
||||
validate: namedArgs['validate'],
|
||||
tdd: namedArgs['tdd'],
|
||||
granularity: namedArgs['granularity'],
|
||||
});
|
||||
},
|
||||
'new-project': () => {
|
||||
const namedArgs = parseNamedArgs(args, [], ['auto']);
|
||||
init.cmdInitNewProject(cwd, raw, { auto: namedArgs['auto'] });
|
||||
},
|
||||
'new-milestone': () => {
|
||||
const namedArgs = parseNamedArgs(args, [], ['reset-phase-numbers']);
|
||||
init.cmdInitNewMilestone(cwd, raw, {
|
||||
'reset-phase-numbers': namedArgs['reset-phase-numbers'],
|
||||
});
|
||||
},
|
||||
'new-project': () => init.cmdInitNewProject(cwd, raw),
|
||||
'new-milestone': () => init.cmdInitNewMilestone(cwd, raw),
|
||||
onboard: () => {
|
||||
const namedArgs = parseNamedArgs(args, [], ['fast', 'text']);
|
||||
init.cmdInitOnboard(cwd, raw, { fast: namedArgs['fast'], text: namedArgs['text'] });
|
||||
},
|
||||
quick: () => init.cmdInitQuick(cwd, args.slice(2).join(' '), raw),
|
||||
quick: () => {
|
||||
const namedArgs = parseNamedArgs(args, [], ['discuss', 'research', 'validate', 'full']);
|
||||
init.cmdInitQuick(cwd, args.slice(2).join(' '), raw, {
|
||||
discuss: namedArgs['discuss'],
|
||||
research: namedArgs['research'],
|
||||
validate: namedArgs['validate'],
|
||||
full: namedArgs['full'],
|
||||
});
|
||||
},
|
||||
'ingest-docs': () => init.cmdInitIngestDocs(cwd, raw),
|
||||
resume: () => init.cmdInitResume(cwd, raw),
|
||||
'verify-work': () => init.cmdInitVerifyWork(cwd, args[2], raw),
|
||||
@@ -88,7 +113,10 @@ function routeInitCommand({ init, args, cwd, raw, error }: RouteInitCommandOptio
|
||||
todos: () => init.cmdInitTodos(cwd, args[2], raw),
|
||||
'milestone-op': () => init.cmdInitMilestoneOp(cwd, raw),
|
||||
'map-codebase': () => init.cmdInitMapCodebase(cwd, raw),
|
||||
progress: () => init.cmdInitProgress(cwd, raw),
|
||||
progress: () => {
|
||||
const namedArgs = parseNamedArgs(args, [], ['forensic']);
|
||||
init.cmdInitProgress(cwd, raw, { forensic: namedArgs['forensic'] });
|
||||
},
|
||||
// Keep manager on CJS for now so runtime-specific command rendering
|
||||
// (e.g. $gsd-* for codex) stays consistent with runtime-slash helpers.
|
||||
manager: () => init.cmdInitManager(cwd, raw),
|
||||
|
||||
Reference in New Issue
Block a user