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:
sim
2026-08-07 09:30:27 -04:00
parent 0d93a5cfe5
commit 2bead6ca1d
11 changed files with 214 additions and 16 deletions

View File

@@ -0,0 +1,5 @@
---
type: Changed
pr: 0
---
**`/gsd:debug` now initializes in one round-trip instead of three** — the workflow previously made three separate `gsd-tools` calls to assemble its context (`state.load`, `resolve-model`, and `config-get workflow.tdd_mode`); it now makes a single `init.debug` call carrying the same resolved values. (#3149)

View File

@@ -159,6 +159,17 @@ own dedicated `cmdInit*` entry points (`cmdInitReview`,
(`cmdInitDocsUpdate`, `cmdInitUpdate`, `cmdInitTransition`) plus an extension
of the pre-existing `cmdInitNewMilestone`.
An entry point can also land **ahead of** the atom it will unblock. `#3149`
gives `debug` a dedicated `cmdInitDebug` (`init.debug`) with no vocabulary
change at all: `/gsd:debug` previously made three separate `gsd_run`
round-trips and had no `cmdInit*` of its own, so gate (2) could never be
satisfied for any debug-scoped fact. Shipping the entry point first satisfies
gate (2) on its own schedule and leaves gate (1) — a consuming section of at
least 400 bytes — to the change that actually adds the section. `debug` has no
`<!-- gsd:section -->` markers yet, so it contributes no key to
`section-manifest.json` and `init.debug`'s `section_manifest` field degrades to
`null` (read everything) until it does.
### Compound conditions are resolved in the fact, never the grammar
`state:chunked-mode` looks, at the section-body level, like it should be a

View File

@@ -17,24 +17,21 @@ Valid GSD subagent types (use exact names — do not fall back to 'general-purpo
```bash
_GSD_SHIM_NAME="gsd-tools.cjs"; _GSD_RUNTIME_ROOT="${RUNTIME_DIR:-$(git rev-parse --show-toplevel 2>/dev/null || pwd)}"; GSD_TOOLS="${_GSD_RUNTIME_ROOT}/gsd-core/bin/${_GSD_SHIM_NAME}"; if [ -f "$GSD_TOOLS" ]; then gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${_GSD_RUNTIME_ROOT}/.claude/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${_GSD_RUNTIME_ROOT}/.claude/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${_GSD_RUNTIME_ROOT}/.codex/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${_GSD_RUNTIME_ROOT}/.codex/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif command -v gsd-tools >/dev/null 2>&1; then GSD_TOOLS="$(command -v gsd-tools)"; gsd_run() { "$GSD_TOOLS" "$@"; }; elif [ -f "${CLAUDE_CONFIG_DIR:-$HOME/.claude}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CLAUDE_CONFIG_DIR:-$HOME/.claude}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${HERMES_HOME:-$HOME/.hermes}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${HERMES_HOME:-$HOME/.hermes}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${CURSOR_CONFIG_DIR:-$HOME/.cursor}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CURSOR_CONFIG_DIR:-$HOME/.cursor}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${CODEX_HOME:-$HOME/.codex}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CODEX_HOME:-$HOME/.codex}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${GEMINI_CONFIG_DIR:-$HOME/.gemini}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${GEMINI_CONFIG_DIR:-$HOME/.gemini}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${COPILOT_CONFIG_DIR:-$HOME/.copilot}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${COPILOT_CONFIG_DIR:-$HOME/.copilot}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${WINDSURF_CONFIG_DIR:-$HOME/.codeium/windsurf}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${WINDSURF_CONFIG_DIR:-$HOME/.codeium/windsurf}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${AUGMENT_CONFIG_DIR:-$HOME/.augment}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${AUGMENT_CONFIG_DIR:-$HOME/.augment}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${TRAE_CONFIG_DIR:-$HOME/.trae}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${TRAE_CONFIG_DIR:-$HOME/.trae}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${QWEN_CONFIG_DIR:-$HOME/.qwen}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${QWEN_CONFIG_DIR:-$HOME/.qwen}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${CODEBUDDY_CONFIG_DIR:-$HOME/.codebuddy}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CODEBUDDY_CONFIG_DIR:-$HOME/.codebuddy}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${CLINE_CONFIG_DIR:-$HOME/.cline}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CLINE_CONFIG_DIR:-$HOME/.cline}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${GROK_AGENTS_HOME:-$HOME/.agents}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${GROK_AGENTS_HOME:-$HOME/.agents}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${ANTIGRAVITY_CONFIG_DIR:-$HOME/.gemini/antigravity}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${ANTIGRAVITY_CONFIG_DIR:-$HOME/.gemini/antigravity}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${OPENCODE_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/opencode}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${OPENCODE_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/opencode}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${KILO_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/kilo}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${KILO_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/kilo}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; else echo "ERROR: gsd-tools.cjs not found at $GSD_TOOLS and gsd-tools is not on PATH. Run: npx -y @opengsd/gsd-core@latest --claude --local" >&2; exit 1; fi; if [ -n "${CLAUDE_ENV_FILE:-}" ] && [ -n "${GSD_TOOLS:-}" ]; then printf "export PATH='%s':\"\$PATH\"\n" "${GSD_TOOLS%/*}" >> "$CLAUDE_ENV_FILE" 2>/dev/null || true; fi
INIT=$(gsd_run query state.load)
INIT=$(gsd_run query init.debug)
if [[ "$INIT" == @file:* ]]; then INIT=$(cat "${INIT#@file:}"); fi
```
Extract `commit_docs` and `config.response_language` from init JSON. Extract `debug_dir` from init JSON — an absolute path anchored on `project_root` (#2376: `debug_file_path` values handed to the spawned `gsd-debug-session-manager` must resolve regardless of that subagent's own cwd, which may differ from the orchestrator's — build them as `{debug_dir}/{slug}.md`, never a bare `.planning/debug/...` literal).
One round-trip carries everything this workflow needs (#3149 — this call replaces the former `state.load` + `resolve-model` + `config-get` trio). Extract from init JSON:
- `commit_docs` — whether planning docs are committed.
- `response_language` — TOP-LEVEL field, present ONLY when configured. Absent means English; absence is not a degraded read.
- `debug_dir` — an absolute path anchored on `project_root` (#2376: `debug_file_path` values handed to the spawned `gsd-debug-session-manager` must resolve regardless of that subagent's own cwd, which may differ from the orchestrator's — build them as `{debug_dir}/{slug}.md`, never a bare `.planning/debug/...` literal).
- `debugger_model` — the resolved model for `gsd-debugger` spawns; used as `{debugger_model}` below and governed by the model-omission rule in step 2.
- `tdd_mode` — used as `{TDD_MODE}` in the session parameter blocks below.
- `section_manifest` — `null` today, because this workflow carries no `<!-- gsd:section -->` markers. **When it is `null`, read this workflow in full.** When it is present, read only the files named in its `read` array. `null` and an empty `included` array are NOT the same: `null` means "no manifest for this workflow", an empty `included` means "nothing applies".
**If `response_language` is set:** All user-facing questions, prompts, and explanations in this workflow MUST be presented in `{response_language}`. Technical terms, code, file paths, and subagent prompts stay in English — only user-facing output is translated.
Resolve debugger model:
```bash
debugger_model=$(gsd_run query resolve-model gsd-debugger --pick model 2>/dev/null || true)
```
Read TDD mode from config:
```bash
TDD_MODE=$(gsd_run query config-get workflow.tdd_mode --raw 2>/dev/null || echo "false")
```
## 1a. LIST subcommand
When SUBCMD=list:

View File

@@ -22,6 +22,15 @@
],
"issue": "622"
},
"init": {
"files": [
"init-debug-workflow-contract.test.cjs",
"init-debug.test.cjs",
"init-manager.test.cjs",
"init.test.cjs"
],
"issue": "3149"
},
"milestone": {
"files": [
"milestone-archive.test.cjs",

View File

@@ -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": [

View File

@@ -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),

View File

@@ -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,

View File

@@ -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'),
};
}

View File

@@ -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

View File

@@ -0,0 +1,6 @@
{
"version": 1,
"paths": {
"debug.md": "#3149 (prerequisite for #3128, ADR-1671 admission gate 2): debug.md gains a dedicated `init.debug` entry point (cmdInitDebug) and its Step 0 collapses THREE separate `gsd_run` round-trips into one. Removed: `gsd_run query state.load` (line 20, replaced in place), the `resolve-model gsd-debugger --pick model` block, and the `config-get workflow.tdd_mode --raw` block — 2 prose lead-ins and 2 fenced code blocks in total. Added: a 6-bullet extraction list documenting the bundle's fields (`commit_docs`, the now TOP-LEVEL `response_language`, `debug_dir`, `debugger_model`, `tdd_mode`, `section_manifest`) plus the `section_manifest: null` -> read-everything rule and the null-vs-empty-included distinction. Net SOURCE growth is +607 bytes (20,555 -> 21,162): the bullets that document one bundle cost more bytes than the two shell round-trips they replace, which is the intended trade — the round-trips cost three subprocess spawns at RUN time on every /gsd:debug invocation. No `<!-- gsd:section -->` marker is added and WHEN_VOCABULARY is unchanged at 29, so the composeWorkflow emission path is byte-identical in shape to before; only this file's own content moved. The `{TDD_MODE}` and `{debugger_model}` placeholders in the session-parameter blocks (lines ~137-145, ~226-234) are deliberately left byte-identical — they now resolve from the init bundle instead of shell variables, and rewording them would ripple into tests/fix-2257-debug-nonterminal-resume.test.cjs and tests/debug-session-manager-commit.test.cjs for no behavioral gain."
}
}

View File

@@ -0,0 +1,87 @@
// allow-test-rule: source-text-is-the-product (see #3149)
// gsd-core/workflows/debug.md is shipped prompt content: the text IS what the
// runtime loads, so its Step 0 contract can only be asserted against the text.
// The behavioral half of this change lives in tests/init-debug.test.cjs, which
// carries no exemption.
'use strict';
/**
* `debug.md` Step 0 contract after the `init.debug` consolidation (#3149).
*
* Matrix: `.gsd/phase/feat-3149-cmdinitdebug/50-test-matrix.md` group F.
*
* Guards the four ways this consolidation can silently regress:
* F1/F2 — a replaced round-trip creeping back, or the new one being lost.
* F3 — reading `config.response_language` (the nested state.load shape)
* instead of the flat top-level field withProjectRoot injects. This
* is the #2402 defect class: the workflow silently stays English.
* F4 — losing the `@file:` unwrap, which large init payloads still need.
* F5 — dropping the `section_manifest: null` -> read-everything rule,
* without which a null manifest reads as "read nothing".
*/
const { describe, test } = require('node:test');
const assert = require('node:assert/strict');
const fs = require('fs');
const path = require('path');
const WORKFLOW_PATH = path.join(__dirname, '..', 'gsd-core', 'workflows', 'debug.md');
const workflow = fs.readFileSync(WORKFLOW_PATH, 'utf-8');
/** Every `gsd_run query <name>` invocation in the workflow, in document order. */
function queryInvocations(text) {
return [...text.matchAll(/gsd_run query ([\w.-]+)/g)].map((m) => m[1]);
}
describe('debug.md Step 0 init contract (#3149, matrix §F)', () => {
test('calls init.debug exactly once (row F1)', () => {
const initDebugCalls = queryInvocations(workflow).filter((q) => q === 'init.debug');
assert.equal(initDebugCalls.length, 1, 'exactly one init.debug round-trip');
});
test('no longer makes the three replaced calls (row F2)', () => {
const queries = queryInvocations(workflow);
assert.equal(queries.includes('state.load'), false, 'state.load is replaced by init.debug');
assert.equal(
workflow.includes('resolve-model gsd-debugger'),
false,
'debugger_model now rides the init bundle'
);
assert.equal(
workflow.includes('config-get workflow.tdd_mode'),
false,
'tdd_mode now rides the init bundle'
);
});
test('reads the flat response_language, not the nested state.load shape (row F3)', () => {
assert.equal(
workflow.includes('config.response_language'),
false,
'withProjectRoot injects response_language at the TOP level; reading config.response_language ' +
'against an init bundle resolves undefined and silently drops translated output (#2402)'
);
assert.ok(
workflow.includes('`response_language`'),
'the field is still documented, just at its new location'
);
});
test('still unwraps an @file: payload (row F4)', () => {
assert.ok(
workflow.includes('@file:'),
'init payloads can spill to a file; dropping the unwrap leaves INIT holding a path, not JSON'
);
});
test('documents the null-manifest read-everything fallback (row F5)', () => {
assert.ok(workflow.includes('section_manifest'), 'the field is documented');
assert.ok(
/`null`[^\r\n]*read this workflow in full/i.test(workflow),
'a null section_manifest must be documented as "read everything" — without the rule, ' +
'a null manifest reads as an empty selection and the workflow reads nothing'
);
});
});