feat(#3149): add dedicated init.debug entry point for /gsd:debug
/gsd:debug was one of the last workflows with no cmdInit* of its own: its Step 0 made three separate round-trips (state.load, resolve-model gsd-debugger, config-get workflow.tdd_mode) to assemble one context. Because no debug-scoped fact was computed at any entry point, ADR-1671 admission gate (2) could never be satisfied for debug — an applicability atom naming such a fact would evaluate FALSE forever and silently exclude its section. Adds cmdInitDebug (init.debug), registers it in the init router and the command-alias table, and collapses debug.md Step 0 to one call. Every field resolves through the same primitive the call it replaces used: loadConfig for commit_docs, withProjectRoot for response_language (#2402), planningPaths for debug_dir, resolveModelInternal for debugger_model, and the existing Boolean(workflow.tdd_mode) idiom for tdd_mode. PlanningPaths gains a debug field so state.load and init.debug share ONE debug-directory expression rather than two kept in sync by hand. state.load keeps emitting debug_dir: it is a shipped query surface with its own test anchor, so narrowing it would break unseen consumers for no gain. No WHEN_VOCABULARY atom and no gsd:section marker: gate (1), a consuming section of at least 400 bytes, belongs to the change that adds the section. Closes #3149 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -448,6 +448,14 @@ export const INIT_COMMAND_ALIASES: CommandAlias[] = [
|
||||
"subcommand": "transition",
|
||||
"mutation": false
|
||||
},
|
||||
{
|
||||
"canonical": "init.debug",
|
||||
"aliases": [
|
||||
"init debug"
|
||||
],
|
||||
"subcommand": "debug",
|
||||
"mutation": false
|
||||
},
|
||||
{
|
||||
"canonical": "init.new-workspace",
|
||||
"aliases": [
|
||||
|
||||
@@ -47,6 +47,7 @@ interface InitModule {
|
||||
cmdInitDocsUpdate(cwd: string, raw: boolean, options?: Record<string, string | boolean | null | undefined>): void;
|
||||
cmdInitUpdate(cwd: string, raw: boolean, options?: Record<string, string | boolean | null | undefined>): void;
|
||||
cmdInitTransition(cwd: string, raw: boolean, options?: Record<string, string | boolean | null | undefined>): void;
|
||||
cmdInitDebug(cwd: string, raw: boolean, options?: Record<string, string | boolean | null | undefined>): void;
|
||||
cmdInitNewWorkspace(cwd: string, raw: boolean): void;
|
||||
cmdInitListWorkspaces(cwd: string, raw: boolean): void;
|
||||
cmdInitRemoveWorkspace(cwd: string, name: string | undefined, raw: boolean): void;
|
||||
@@ -171,6 +172,10 @@ function routeInitCommand({ init, args, cwd, raw, error }: RouteInitCommandOptio
|
||||
init.cmdInitUpdate(cwd, raw, { next: namedArgs['next'], rc: namedArgs['rc'] });
|
||||
},
|
||||
transition: () => init.cmdInitTransition(cwd, raw, {}),
|
||||
debug: () => {
|
||||
const namedArgs = parseNamedArgs(args, [], ['diagnose']);
|
||||
init.cmdInitDebug(cwd, raw, { diagnose: namedArgs['diagnose'] });
|
||||
},
|
||||
'new-workspace': () => init.cmdInitNewWorkspace(cwd, raw),
|
||||
'list-workspaces': () => init.cmdInitListWorkspaces(cwd, raw),
|
||||
'remove-workspace': () => init.cmdInitRemoveWorkspace(cwd, args[2], raw),
|
||||
|
||||
63
src/init.cts
63
src/init.cts
@@ -2690,6 +2690,68 @@ function cmdInitTransition(cwd: string, raw: boolean, options: Record<string, un
|
||||
output(withProjectRoot(cwd, result), raw);
|
||||
}
|
||||
|
||||
/**
|
||||
* `debug.md`'s dedicated init entry point (#3149; prerequisite for #3128).
|
||||
* `debug.md` previously carried NO `gsd_run query init.*` call at all — it made
|
||||
* THREE separate round-trips instead: `state.load` (for `commit_docs`,
|
||||
* `config.response_language` and `debug_dir`), `resolve-model gsd-debugger
|
||||
* --pick model`, and `config-get workflow.tdd_mode --raw`. Because no
|
||||
* debug-scoped fact was computed at any entry point, a `when=` atom naming one
|
||||
* would have evaluated FALSE forever — ADR-1671's admission gate (2) and the
|
||||
* silent-exclusion bug it exists to prevent (`docs/adr/1671-…:122-131`).
|
||||
*
|
||||
* Every field is resolved through the SAME primitive the call it replaces used,
|
||||
* never a second hand-maintained copy (DEFECT.GENERATIVE-FIX):
|
||||
*
|
||||
* - `commit_docs` — `loadConfig`, the same loader `cmdStateLoad` calls.
|
||||
* - `response_language` — NOT read here: `withProjectRoot` already injects it
|
||||
* when configured (#2402), which is also the shape sibling init bundles use.
|
||||
* It is absent, not null, when unset.
|
||||
* - `debug_dir` — `planningPaths(cwd).debug`, the SAME expression `cmdStateLoad`
|
||||
* now uses; the `debug` field was added to `PlanningPaths` (#3149) so the
|
||||
* location has one source instead of two kept in sync by hand.
|
||||
* - `debugger_model` — `resolveModelInternal`, which IS what `query
|
||||
* resolve-model --pick model` returns (`cmdResolveModel`, src/commands.cts).
|
||||
* - `tdd_mode` — the `Boolean(wf['tdd_mode'])` idiom `cmdInitExecutePhase` and
|
||||
* `cmdInitPlanPhase` already use. `/gsd:debug` has no `--tdd` flag, so the
|
||||
* sibling handlers' `options['tdd'] ||` disjunct is deliberately omitted
|
||||
* rather than carried as a phantom.
|
||||
*
|
||||
* `state.load` is deliberately NOT narrowed — see the note beside its own
|
||||
* `debug_dir` field. This handler is purely additive alongside it.
|
||||
*
|
||||
* `diagnose` is the one flag `/gsd:debug` already documents. Exposing it as a
|
||||
* top-level fact follows `cmdInitUpdate`'s `next_channel` and
|
||||
* `cmdInitAutonomous`'s `plan_strategy_converge` precedent, and is what makes
|
||||
* the router's flag forwarding observable. No `when=` atom consumes it yet:
|
||||
* admission gate (1) — a consuming section of at least 400 bytes — is #3128's
|
||||
* to satisfy, and shipping the atom before its section is the same
|
||||
* silent-exclusion bug from the other direction.
|
||||
*/
|
||||
function cmdInitDebug(cwd: string, raw: boolean, options: Record<string, unknown> = {}): void {
|
||||
const config = loadConfig(cwd);
|
||||
const wf = (config.workflow ?? {}) as Record<string, unknown>;
|
||||
|
||||
const result: Record<string, unknown> = {
|
||||
commit_docs: config.commit_docs,
|
||||
// #2376: absolute — debug.md builds `debug_file_path` as
|
||||
// `{debug_dir}/{slug}.md` for its gsd-debug-session-manager spawns, whose
|
||||
// own cwd may differ from the orchestrator's.
|
||||
debug_dir: toPosixPath(planningPaths(cwd).debug),
|
||||
debugger_model: resolveModelInternal(cwd, 'gsd-debugger'),
|
||||
tdd_mode: Boolean(wf['tdd_mode']),
|
||||
diagnose: options['diagnose'] === true,
|
||||
};
|
||||
|
||||
// Additive, optional field — degrades to null while `debug` has no key in
|
||||
// `gsd-core/workflows/section-manifest.json` (it has no `gsd:section` markers
|
||||
// until #3128). null means "read everything", which is NOT the same as a
|
||||
// computed empty selection.
|
||||
result['section_manifest'] = buildSectionManifestField(cwd, null, options, 'debug', {});
|
||||
|
||||
output(withProjectRoot(cwd, result), raw);
|
||||
}
|
||||
|
||||
function cmdInitProgress(cwd: string, raw: boolean, options: Record<string, unknown> = {}): void {
|
||||
try {
|
||||
(pruneOrphanedWorktrees as (cwd: string) => void)(cwd);
|
||||
@@ -3708,6 +3770,7 @@ export = {
|
||||
cmdInitDocsUpdate,
|
||||
cmdInitUpdate,
|
||||
cmdInitTransition,
|
||||
cmdInitDebug,
|
||||
cmdInitNewWorkspace,
|
||||
cmdInitListWorkspaces,
|
||||
cmdInitRemoveWorkspace,
|
||||
|
||||
@@ -166,6 +166,7 @@ interface PlanningPaths {
|
||||
config: string;
|
||||
phases: string;
|
||||
requirements: string;
|
||||
debug: string;
|
||||
}
|
||||
|
||||
function planningPaths(cwd: string, ws?: string | null): PlanningPaths {
|
||||
@@ -178,6 +179,10 @@ function planningPaths(cwd: string, ws?: string | null): PlanningPaths {
|
||||
config: path.join(base, 'config.json'),
|
||||
phases: path.join(base, 'phases'),
|
||||
requirements: path.join(base, 'REQUIREMENTS.md'),
|
||||
// #3149: the debug-session directory. Single source for both `state.load`'s
|
||||
// `debug_dir` field and `init.debug`'s — previously each composed its own
|
||||
// `path.join(planning, 'debug')` (DEFECT.GENERATIVE-FIX).
|
||||
debug: path.join(base, 'debug'),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -351,10 +351,12 @@ function cmdStateLoad(cwd: string, raw: boolean): void {
|
||||
config_exists: configExists,
|
||||
// #2376: absolute (anchored on cwd), not orchestrator-cwd-relative — a
|
||||
// spawned subagent's own cwd may differ from the orchestrator's.
|
||||
// debug.md has no init.* call of its own; it reads this field from
|
||||
// `state load` to build debug_file_path for its gsd-debug-session-manager
|
||||
// spawns instead of hardcoding '.planning/debug/{slug}.md'.
|
||||
debug_dir: toPosixPath(path.join(planDir, 'debug')),
|
||||
// #3149: debug.md now has its own `init.debug` entry point and reads this
|
||||
// field from there, not from `state load`. This stays on the state.load
|
||||
// bundle regardless: it is a shipped query surface with its own test anchor
|
||||
// (tests/state.test.cjs), so narrowing it would break unseen consumers for
|
||||
// no gain (Hyrum's Law). Both emit the SAME `planningPaths(cwd).debug`.
|
||||
debug_dir: toPosixPath(planningPaths(cwd).debug),
|
||||
};
|
||||
|
||||
// For --raw, output a condensed key=value format
|
||||
|
||||
Reference in New Issue
Block a user