* fix(#2486): runtime-branch the settings worktrees question + W020 health diagnostic On non-Claude runtimes /gsd:settings offered "Yes (Recommended)" for worktree isolation and persisted workflow.use_worktrees: true — the exact value the execution workflows fail closed on (#1521 guards). Branch the question on the same stamped config-get runtime read the guards use: Claude keeps the unchanged question; non-Claude offers only "No (Recommended)" / "Leave unchanged", never persists true, and warns when the config carries an inherited explicit true. /gsd:health gains W020, surfacing such a config with the guards' own predicate before execution-time failure. Docs state the runtime-conditional default. Fixes #2486 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * chore(#2486): add changeset for PR #2531 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(#2486): reassign the health worktrees check W020 -> W024 (verify.cts namespace collision) The workflow-level check collided with the live W020 (git-worktree-list health) emitted by cmdValidateHealth in src/verify.cts — invisible from health.md's error_codes table, which stops at W019 and under-represents the real namespace (W010-W017, W020-W023 all live). W024 verified free. Adds a regression test pinning the chosen code against src/verify.cts so a future assignment cannot silently collide, a table note naming the namespace owner, and the changeset body reworded to house style. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(#2486): pre-select the recommended repair in the broken-inheritance case Review round 2: at settings.md:142 the pre-selection rule left "Leave unchanged" as the default when the config carried an explicit non-false use_worktrees — the exact broken state the adjacent notice warns about, so accepting the default kept a config that fails closed at execution time. "Leave unchanged" is now the default only when the key is absent (nothing to repair); explicit false AND explicit non-false both pre-select "No (Recommended)", aligning the default, the label, and the notice. Pinned by two source-contract assertions in the #2486 regression block. Goldens (settings.md hash x19) + size baseline regenerated. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(#2486): gate the worktrees question on dispatch.isolation, not the runtime name Review round 2: #2584 Phase 3 replaced the runtime-name test with a declared `dispatch.isolation` capability, invalidating this PR's premise. cursor declares harness-worktree and codex/opencode/kimi/ kimi-code declare orchestrator-worktree, so a `RUNTIME != claude` gate blocked a supported configuration on five runtimes and false-warned in health. - settings.md + health.md read `query dispatch-isolation` and branch on `ISOLATION = none`; the runtime-name read is gone from both, and the capability read needs no per-runtime stamping (it fail-closes unknown/ undocumented internally) - all "Claude Code-only primitive" prose rewritten, including the two gates the shell-syntax check missed (config-key list, JSON schema comment) - W024 reconciled across health.md + CONFIGURATION.md + planning-config.md (docs still said W020, which collides with a verify.cts code) - health.md error-codes table fixed: the namespace note no longer sits between rows orphaning I001 - the asymmetry note for the two workflows #2584 has not migrated (quick.md, diagnose-issues.md) is enforced by a set-equality test with a self-check table, so it cannot go stale in either direction Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * docs(#2486): restore the Executor isolation section clobbered by #2661 `46ba02ac` (feat(#2630), the current next tip) reverted docs/CONFIGURATION.md to a pre-#2584 state: it restored the old "Non-Claude note" wording on the workflow.use_worktrees row and deleted the whole "Executor isolation per runtime" section. The change is unrelated to that PR's phase-estimation feature and looks like a stale-copy edit. This PR's use_worktrees row links to #executor-isolation-per-runtime, so the deletion leaves a dangling anchor. Restored byte-for-byte froma40ee8a5(the text #2584 Phase 3 originally shipped). No link/anchor checker exists in scripts/ or tests/, so CI would not have caught the dead link. The reverted row wording is outside this PR's scope and is still wrong on next; reported upstream as #2668. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * test(#2486): acknowledge the emitted size growth for settings.md and health.md The #2723 differential attribution check (epic #2719 Phase 3) landed on next after this branch opened and gates emitted-file growth behind a committed acknowledgment. Both grown files are attributable to source this PR changes; the ack names them and says why, per ADR-2719 §3. Verified load-bearing: removing tests/emitted-drift-ack.json reproduces the same failure; restoring it passes 59/59. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(#2486): reword the W024 remediation so it names no bare gsd-tools call The W024 warning told the user to run `gsd-tools query config-set`, a command-position bare invocation that fails with "command not found" on a shim-only install (#2751) — the diagnostic sent the user into a second, more confusing error than the one it reported. The remediation now names only `/gsd:settings` and the config key itself, both of which work on every install layout, and the #2751 command-position gate goes green. Fixes #2486 * fix(#2486): drop the Known-asymmetry note and its guard test — #2728 migrates both workflows Review Major 2: once #2728 lands, the note describes a gap that no longer exists and instructs maintainers not to do the thing that was just done — with the set-equality guard test pinning the stale prose green. This PR now depends on #2728 (declared in the PR body), so the note and its guard go. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * test(#2486): make the #2728 merge order enforceable instead of advisory settings.md recommends and persists `workflow.use_worktrees: true` for every runtime whose declared dispatch.isolation is not `none` — cursor (harness-worktree) and codex/opencode/kimi/kimi-code (orchestrator-worktree). quick.md and diagnose-issues.md consume that value at dispatch time and, while they still gate on the runtime NAME, FATAL for all five. Merged first, this PR reintroduces #2486's own shape for the exact runtimes it exists to help — on the path it now labels "Recommended". The dependency was stated only as prose in the PR body. A merge-order note is not a gate: an automated batch merge never reads it. This adds the check that makes the ordering structural — red while either sibling is still name-gated, green the moment #2728 lands. The predicate matches a RUNTIME-vs-"claude" comparison, not the legitimate runtime-identity read, and accepts either the inline canonical dispatch-isolation read or a reference to dispatch-isolation-gate.md, which is the shape #2728 gives quick.md. Verified both directions against real sources: red against the current workflows, green against #2728's. This also closes W024's coverage window. W024 fires only when ISOLATION is `none`, so it is structurally blind to these five runtimes — /gsd:health would report healthy right up until quick.md FATALs. W024 cannot see the hazard, so the hazard is prevented by making the unsafe ordering unmergeable rather than by warning after the fact. Separately, the W-code namespace-collision test now declares its source read instead of passing lint silently: verify.cts emits its codes as inline string literals across ~25 addIssue() calls and exports no enumerable registry, so there is nothing to require() and assert against. The gap, and what it costs, are stated in the test. * docs(#2486): use the hyphen command form for /gsd-health in CONFIGURATION.md docs/ is never passed through the install-time slash-form converters, so the colon form names a command no runtime registers. Clears the sole lint-docs-command-form violation attributable to this PR. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(#2486): resolve isolation via new side-effect-free inspect-dispatch-isolation query; scope the settings change to isolation-none runtimes Review round 4: - B1: /gsd:health and /gsd:settings no longer call the recording dispatch-isolation query — on current next it persists the resolved decision to the executor-isolation sentinel as an unconditional #3045 side effect, letting a read-only diagnostic hard-block executor dispatch for the sentinel's lifetime across sessions. Both surfaces now use inspect-dispatch-isolation, a new read-only verb sharing the exact resolution implementation (extracted resolveDispatchIsolationDecision) with zero writes. Behavioral tests pin: no sentinel write, per-runtime parity with the recording verb, recording knobs ignored, --json shape. - B2: the #2728 merge-order interlock test is deleted — a repo test cannot sequence merges; it only made this PR unmergeable on its own schedule. The settings behavior change is scoped entirely to the ISOLATION=none branch, which needs nothing from #2728; the != none path is base behavior unchanged. - M1: the W024-vs-verify.cts namespace test (an admitted source-grep) is deleted per RULESET.TESTS.delete-bad-tests, without a standing exemption; the namespace claim lives as guidance in health.md. - M2: remaining allow-test-rule exemptions re-derived into documented categories (source-text-is-the-product, integration-test-input) with issue refs per ADR-456. - Minor: settings.md's current-value read drops the stampable --default/fallback so key-absence stays distinguishable from an explicit false on non-Claude emits (the pre-selection rule depends on the tri-state); docs now present inspect-dispatch-isolation as the inspection command and name dispatch-isolation as the recording resolver. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(#2486): put the install-marker rung in the canonical runtime resolver Round-7 review (independent cross-AI pass over the whole PR). BLOCKER — the gate did not fire in the default case. `resolveRuntime` stopped at GSD_RUNTIME > config.runtime > 'claude', and `config-new-project` writes NO `runtime` key — so on a real non-Claude install every consumer believed it was on Claude: isolation reported `harness-worktree`, /gsd:settings still offered "Yes (Recommended)" and W024 stayed silent. That is #2486's own symptom, surviving the fix meant to remove it. Verified end-to-end on a real `--qwen` install with a runtime-neutral config and GSD_RUNTIME unset: pre-fix `harness-worktree`, fixed `none`. The rung lives in `resolveRuntime` (src/runtime-slash.cts), the ONE canonical resolver, not in the isolation call site. A per-consumer fix forks precedence: `inspect-dispatch-isolation` would answer `cursor` while `dispatch-should-flatten` and `resolve-dispatch-type` still answered `claude` for the same install. All three now agree. `readInstallRuntimeMarker`, its cache and its test seams MOVED from model-resolver.cts to runtime-slash.cts, with model-resolver re-exporting the seams — one marker read and one cache, not two that drift. Deliberately NOT `resolveActiveRuntime`/`loadConfig`: an intermediate revision of this fix routed through `loadConfig`, which normalizes and rewrites legacy keys back to disk. That gave `inspect-dispatch-isolation` — the verb whose entire purpose is being side-effect-free — a write side effect, which is the defect the verb exists to avoid. `resolveRuntime` reads .planning/config.json directly. Re-verified: the inspect query against a real install creates no `.gsd/`. Tests, both of which were too weak in the first attempt and are now fail-first proven: - The W024 behavioral stub returned success-with-empty-output for an absent key. Real `config-get` EXITS NON-ZERO, and the `|| echo "true"` fallback only triggers on failure — so reintroducing the fallback would have passed. The stub now returns 1 for the absent case. - The marker regression asserted on the exported helper, so reverting gsd-tools.cjs to a marker-blind resolver still passed. It now drives a real `--qwen` install through the shipped `inspect-dispatch-isolation` query. (GSD_TEST_MODE must be cleared for that child, or install.js no-ops while still exiting 0 — a green test over an install that wrote nothing.) Spawned through `installSpawnEnv()` so ambient GSD_HOME cannot leak in. Also: the three PR-added `try/finally` test bodies converted to `t.after()` per CONTRIBUTING; CONTEXT.md's `worktree create` UNCONSUMED claim corrected (Phase 3 calls it in executor-isolation-dispatch.md); docs/CONFIGURATION.md now states the current non-Claude `use_worktrees` default rather than describing capability-scoped stamping that lands with #2652; resolver precedence comments updated to name the marker rung. Refs #2486 Refs #2668 * fix(#2486): split the marker rung out; make the use_worktrees doc row order-independent Round-8 review. trek-e's Blocker was procedural — commit 10fba7b8 moved the per-install .gsd-runtime marker rung into the canonical resolver, a large blast-radius change that arrived undisclosed and unreviewed. They offered two remedies; taking the second: SPLIT IT OUT. src/runtime-slash.cts and src/model-resolver.cts are reverted to their next state and the marker regression test is removed. What remains is what this PR was filed for: the settings.md / health.md / gsd-tools.cjs isolation query, W024, and the #2668 docs restoration. COST, stated plainly rather than buried: without that rung this PR's gate resolves 'claude' on a non-Claude install whose project config carries no `runtime` key — which is every config config-new-project writes. On those installs W024 stays quiet and /gsd-settings still offers Worktrees. The gate is correct whenever the runtime IS resolvable (GSD_RUNTIME set, or an explicit config.runtime). That is why `Fixes #2486` is already downgraded to `Refs` — #2486 must not close until the residual lands. A KNOWN LIMITATION comment at the resolver call site names #2395 so this does not read as an oversight. The rung itself belongs to #2395, which reports this exact defect and was closed by #2446 — a PR that touched only bin/install.js and fixtures and never runtime-slash.cts, so it persisted the identity into ~/.gsd/defaults.json, a tier resolveRuntime does not read. Evidence and a reopen request are posted there. That same tier is #2566's B1. DOCS — the reviewer flagged that this row and #2728's are order-dependent: whichever merges second falsifies the other. Removed the dependency instead of picking an order. The "Current default … until #2652" paragraph is now a plain troubleshooting note, true before and after #2728 lands. CONFLICT — one hunk in CONTEXT.md: next added the #2596 scope-conformance interface to the same Worktree-Safety paragraph where this PR corrected the `worktree create` UNCONSUMED claim. Resolved keeping both. Verified: lint:ci green. Full suite clean apart from the pre-existing #1160 installed-runtime capability surface. (emitted-attribution also failed until the fork's stale next was fast-forwarded — it defaults to origin/next, which was 131 commits behind; 175/175 against the current base.) Refs #2486 Refs #2668 * fix(#2486): address round-9 review — distinguish "cannot resolve" from "declares none", reject recording-only args on the read verb Codex review of the whole PR, five findings, all verified against source first. Major 3 (the one real defect). Both surfaces read isolation as `ISOLATION=$(… || echo "none")`, so a resolver failure became indistinguishable from a genuine `dispatch.isolation: none` declaration — and W024's text then asserts the latter, telling a user their runtime declares no executor-isolation primitive when GSD simply could not find out. health.md and settings.md now capture the raw value and track ISOLATION_RESOLVED, the same shape references/dispatch-isolation-gate.md already uses (#2652 review). W024 gained a second message for the unresolved case; settings still fails closed there — it must never persist a `true` it cannot justify — but reports what happened rather than a verdict it never reached. Major 4. `dispatch-isolation` applies --force-isolation AFTER the shared resolver returns; inspection accepted the flag and silently ignored it, so the same argv yielded 'none' from one verb and the declared capability from the other. inspect-dispatch-isolation now rejects --force-isolation/--phase/--plan as usage errors. Verified by mutation: disabling the guard reds exactly the three new rejection tests. Major 1. settings.md claimed this flow and the execution guards "always reach the same verdict". False while quick.md still gates on the runtime name: an orchestrator-worktree host can be offered a `true` that /gsd:quick rejects as fatal, W024 silent because isolation is not none. Scoped the claim to the capability gate, named #2728 as the conversion, and added the same caveat to the CONFIGURATION.md use_worktrees row. Not a behavior change — the `!= none` branch is untouched base behavior. Major 2. "side-effect-free" overstated it: every gsd-tools invocation runs the shared bootstrap, and getActiveWorkstream unlinks a stale workstream pointer. That is pre-existing, verb-independent and cannot block a dispatch; writing the sentinel can. Renamed the claim to "sentinel-free" everywhere and documented precisely what is and is not asserted. Minor 5. The JSON parity test asserted against a handwritten key list and never invoked the recording verb, so the two could diverge and stay green. It now runs `dispatch-isolation --json` in a separate project dir and deep-compares, with a control asserting that verb DID write a sentinel. Added the orchestrator exec branch (--cwd-target) the registry parity test never covered. runtime-converters.test.cjs pinned the old `|| echo "none"` line as canonical — that literal was the Major 3 defect. Repinned as two invariants (the raw read is present; the collapsing fallback is absent) plus an ISOLATION_RESOLVED requirement, so reformatting does not fail the test but a semantic regression does. settings.md sits at 40777 bytes against the 40960 DEFAULT hard cap. The round-9 prose was compressed to fit rather than raising the cap. Validated: lint:ci clean; full suite green except the two failures that reproduce identically on pristine next @33fca50d(#1160 _resolveManifest, and the #3053 quick_id tests, which compare a local-time expectation against a TZ=UTC child and so only pass on a UTC host). * fix(#2486): second review pass — drop a dangling reference, correct the whole unresolved branch, pin the branch behaviorally Codex re-review of the full PR after the first round-9 pass. It confirmed Majors 1, 2 and 4 and Minor 5 fixed, and found seven more. All verified against source. Minor 5 was mine and the worst of them: settings.md and health.md pointed at `gsd-core/references/dispatch-isolation-gate.md`, which exists only on the #2728 branch — not in this PR and not on the merge-base. Merging this alone would have shipped three dangling canonical-source references. Removed; the surrounding text now stands on its own. Major 2. The unresolved branch corrected only the two option descriptions. The pre-selection rationale still claimed an absent key already resolves to false on this runtime, and the explicit-true notice still stated the runtime declares no primitive — both capability verdicts that were never reached. The substitution rule now covers every place in that branch that asserts one. Major 3. The repinned invariants could not catch the mutation they existed to catch: flipping the shipped block's ISOLATION_RESOLVED=true to false left all three green, since they only assert the raw read is present, the token appears, and the collapsing fallback is gone. Added a behavioral test that drives the shipped W024 block twice — resolver answering vs resolver exiting non-zero — and asserts the two emit different text, that the unresolved branch says "could not resolve", and that it does NOT claim the runtime has no primitive. Verified: the true->false mutation now reds it. Major 1. The verb fail-closes an unknown or undeclared runtime to `none` and exits 0, so ISOLATION_RESOLVED=true means "the query answered", not "the runtime published a declaration" — the shell cannot see an internal fallback. Exposing provenance is an API change and out of scope here, so W024's resolved-case text is instead written to be true of every path that reaches it ("no usable executor-isolation primitive — declared or fail-closed from an unknown value"), and both workflows state the limit of the signal explicitly. Minor 4. The rejection tests regex-matched human prose, so swapping ERROR_REASON.USAGE for UNKNOWN would have kept them green while breaking machine consumers. They now pass --json-errors and assert reason === 'usage' on the parsed envelope. Minor 6. CONTEXT.md still described the verb as side-effect-free. Now sentinel-free, consistent with the router comment and the other two surfaces. Minor 7. 183 bytes of headroom under the 40960 hard cap was called unacceptable, and it was — a routine 184-byte edit would have failed CI. Compressed this PR's own settings.md prose (the #2486 block was carrying ~7.1KB of rationale) to 40499 bytes, 458 free. Two phrases other tests pin verbatim were restored after the first compression pass reworded them. Validated: lint:ci clean; full suite green except the two failures that reproduce identically on pristine next @33fca50d. * fix(#2486): third review pass — placeholder table replaces the fragile substitution rule Codex round 3 blocked the push on two Majors. Both verified and real. Major 2 was the substantive one and my error. The round-2 fix told the model to find and replace the string "this runtime declares no executor-isolation primitive (dispatch.isolation: none)" — text that does not appear anywhere in the branch. The first option description has no parenthetical, the second asserts "absent, it already resolves to false on this runtime" (not covered at all), and the pre-selection rationale and notice carried three more unsupported assertions. A model following the instruction literally would have found no match and changed nothing. Replaced with three named placeholders — {FINDING}, {CONSEQUENCE}, {ABSENCE} — and a two-column table giving each one's resolved and unresolved wording. Every assertion in the branch now flows from the table, so none of them can outrun what was actually established, and there is no string-matching to get wrong. It is also shorter than the prose it replaced. Major 1: the resolver catches thrown errors and returns none successfully, a path the round-2 wording ("declared, or unknown/undeclared") did not cover. Both surfaces now say "declared as none, or fail-closed because the capability could not be determined", which is true of the thrown path too, and both state that an internal resolution error is among the things the verb fail-closes. Still not provenance — the verb cannot distinguish these for the caller — but no longer a claim the code contradicts. Minor 3: the behavioral test asserted only that the two branches differ and that the resolved one omits "could not resolve", so arbitrary resolved text stayed green. Now pins what it must positively say: the capability finding, the fail-closed consequence, the offending key, and (both branches) the repair command. Minor 4: two stale artifacts the round-2 sweep missed — a test comment still citing the #2728-only references/dispatch-isolation-gate.md, and the emitted drift ack still describing inspection as side-effect-free. Also aligned the two remaining router comments to sentinel-free. settings.md 40420 bytes, 540 free under the cap. Validated: lint:ci clean; full suite green except the two failures that reproduce identically on pristine next @33fca50d. * fix(#2486): fourth review pass — stop recommending a repair whose effect depends on the emit Codex round 4, one Major and three Minors. The Major is a real hole and worth the round. W024 advised "remove the key from .planning/config.json so the runtime default (false) applies". That default is not false everywhere: execute-phase.md:102, quick.md:155 and diagnose-issues.md:62 all read the key as `|| echo "true"`, and only `_stampNonClaudeRuntimeDefaults` rewrites them to `--default false` on a non-Claude emit. So on an un-stamped emit the advised repair leaves use_worktrees resolving to TRUE against isolation none — exactly the state W024 exists to flag. Reachable in the round-3 gap: a caught internal resolver error returns none with rc=0, so ISOLATION_RESOLVED is true and the confident branch fires on a host whose emit was never stamped. Fixed by removing the dependence rather than the symptom: both surfaces now say to set workflow.use_worktrees false explicitly, and say why deleting the key is not equivalent. The {ABSENCE} placeholder no longer claims absence resolves to false unconditionally — it is scoped to an emit that stamped that default. Minor: the notice and W024 hardcoded .planning/config.json, which is the wrong file in an active workstream (settings itself resolves $GSD_CONFIG_PATH correctly). Both now say "the project config", naming the workstream case. Minor: the new repair pin required the literal /gsd:settings, a form documented as no longer routable. Relaxed to accept the canonical slash and $-prefixed forms so a correct rewording cannot fail the test. Nit: two test descriptions still said side-effect-free. Not fixed, deliberately: the verb still cannot tell a caught resolver error from a declared none, so ISOLATION_RESOLVED remains a signal about the CALL, not the declaration. Both workflows now state that limit outright. Exposing provenance is a change to the query contract that belongs with the runtime-resolution work in #2395, not here. Validated: lint:ci clean; full suite green except the two failures that reproduce identically on pristine next @33fca50d. The only change after that suite run was removing a redundant regex escape flagged by eslint; that test was re-run individually and eslint is clean. * fix(#2486): fifth review pass — stop recommending "leave it absent" as the safe default Codex round 5, one Major: the round-4 fix corrected the {ABSENCE} wording but left the pre-selection rule built on the assumption it had just falsified. An absent key still pre-selected "Leave unchanged" on the rationale that there was nothing to repair. Absence resolves to false only where the emit stamped that default; on an un-stamped emit it reads as true, so accepting the recommended default could preserve the exact isolation-none-plus-true state this branch exists to prevent. The isolation-none branch now pre-selects "No (Recommended)" in every case, including an absent key. Writing an explicit false is correct under either emit; "Leave unchanged" stays available for the deliberate shared-config case but is never the default. The test that pinned the old rule pinned a falsified assumption, so it now pins the new one and asserts the old sentence is gone. Also tightened the round-4 repair regex, which had been relaxed far enough to accept `$gsd:settings` and `/gsd:settings-bogus`. It now matches only the canonical `/gsd-settings`, `/gsd:settings` and `$gsd-settings` forms. settings.md 40685 bytes, 275 free. Validated: lint:ci clean; full suite green except the two failures that reproduce identically on pristine next @33fca50d. * fix(#2486): make the inspection exemption block-scoped, and drop the last pre-#2728 claim Codex review of the conflict resolution: one Major, two Minors, all real. Major. The inspection-surface exemption I added to #2728's "every dispatch-site degrade block re-records" guard was FILE-wide. Codex probed it by adding an unrecorded executor block to health.md: the exemption assertions still passed, the whole file was skipped, and the offender went unreported. That is the same hole the hand-listed revision of this guard had — a promise of "every dispatch site" with a silent carve-out. Now scoped per block. A block earns the exemption only by resolving through `inspect-dispatch-isolation` AND carrying no dispatch primitive (`Agent(`, `harnessFlag`/`HARNESS_FLAG`, `isolation="worktree"`, or the recording verb). The file-level assertions remain as a precondition on top. Verified with Codex's own probe: the appended dispatch block is now reported at health.md:285, while the legitimate inspection block still passes. Minor. settings.md still carried the caveat saying `quick.md` gates on the runtime name and "#2728 converts that surface". #2728 has landed. Same obsolete premise already removed from docs/CONFIGURATION.md in the merge commit; this was the copy I missed. Removing it also returns 413 bytes, so settings.md now has 688 free under the 40960 cap rather than 275. Minor. Four scratch-directory prefixes still read `w024` after the renumber. Non-functional, but the whole point of the rename was that W024 now means someone else's warning. Validated: lint:ci clean; full suite green except the two failures that reproduce identically on pristine next. * fix(#2486): name the diagnostics' state INSPECTED_ISOLATION and delete the exemption Codex probed the per-block exemption and it was still escapable: DISPATCH_PRIMITIVE matched only `Agent(`, two harness-flag spellings, `isolation="worktree"` and the recording verb, so `Task(`, `spawn_agent(`, a `codex exec` process dispatch, and — unfixably by any regex — the repo's normal split shape (isolation resolved in one bash fence, dispatch in a later one) all still qualified as exempt. Took Codex's suggested fix, which is better than the one it replaces. The diagnostics now name their state `INSPECTED_ISOLATION` / `INSPECTED_RESOLVED` / `_INSPECTED_RAW` instead of reusing the dispatch-site names. #2728's guard scans for a literal `ISOLATION=none`, so health.md and settings.md fall outside it BY CONSTRUCTION and the exemption is deleted outright — no carve-out to escape, and a diagnostic that ever writes a real `ISOLATION=none` is caught like any other site. The name is also just more accurate: an inspection result is not a dispatch decision. Pinned so the reasoning cannot be lost: the #2486 suite now asserts neither diagnostic assigns the bare `ISOLATION` name, with the rationale in the comment. Verified by mutation — renaming back makes #2728's guard flag health.md:107 AND fails the new naming pin. Net effect on the guard's coverage is positive: before this PR it scanned two fewer files by exemption; now it scans everything. settings.md 40352 bytes, 608 free. Validated: lint:ci clean; full suite green except the two failures that reproduce identically on pristine next. --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
30 KiB
<planning_config>
Configuration options for .planning/ directory behavior.
<config_schema>
"planning": {
"commit_docs": true,
"search_gitignored": false
},
"git": {
"branching_strategy": "none",
"base_branch": null,
"phase_branch_template": "gsd/phase-{phase}-{slug}",
"milestone_branch_template": "gsd/{milestone}-{slug}",
"quick_branch_template": null
},
"manager": {
"flags": {
"discuss": "",
"plan": "",
"execute": ""
}
}
| Option | Default | Description |
|---|---|---|
commit_docs |
true |
Whether to commit planning artifacts to git |
search_gitignored |
false |
Add --no-ignore to broad rg searches |
git.branching_strategy |
"none" |
Git branching approach: "none", "phase", or "milestone" |
git.base_branch |
null (auto-detect) |
Target branch for PRs and merges (e.g. "master", "develop"). When null, auto-detects from git symbolic-ref refs/remotes/origin/HEAD, falling back to "main". |
git.create_tag |
true |
Create git tags on milestone completion |
git.phase_branch_template |
"gsd/phase-{phase}-{slug}" |
Branch template for phase strategy |
git.milestone_branch_template |
"gsd/{milestone}-{slug}" |
Branch template for milestone strategy |
git.quick_branch_template |
null |
Optional branch template for quick-task runs |
workflow.use_worktrees |
true |
Whether executor agents run in isolated git worktrees. Set to false to disable worktrees — agents execute sequentially on the main working tree instead. Recommended for solo developers or when worktree merges cause issues. Note: if your branch is ahead of origin/HEAD (a diverged milestone or feature branch), GSD auto-degrades to sequential and prints a warning; set worktree.baseRef:"head" in .claude/settings.local.json to restore parallel execution. See the branch-divergence note below. |
workflow.subagent_timeout |
300000 |
Timeout in milliseconds for parallel subagent tasks (e.g. codebase mapping). Increase for large codebases or slower models. Default: 300000 (5 minutes). |
workflow.test_command |
null |
Custom shell command run as the regression/test gate by verify-phase, execute-phase, audit-fix, and post-merge-gate. When unset, GSD auto-detects (Makefile / package.json / Cargo.toml / go.mod / pyproject.toml). Example: npm test. |
workflow.build_command |
null |
Custom shell command run as the build gate by the post-merge gate. When unset, the build step is skipped/auto-detected. Example: npm run build. |
workflow.inline_plan_threshold |
2 |
Plans with this many tasks or fewer execute inline (Pattern C) instead of spawning a subagent. Avoids ~14K token spawn overhead for small plans. Set to 0 to always spawn subagents. |
manager.flags.discuss |
"" |
Flags passed to /gsd:discuss-phase when dispatched from manager (e.g. "--auto --analyze") |
manager.flags.plan |
"" |
Flags passed to plan workflow when dispatched from manager |
manager.flags.execute |
"" |
Flags passed to execute workflow when dispatched from manager |
response_language |
null |
Language for user-facing questions and prompts across all phases/subagents (e.g. "Portuguese", "Japanese", "Spanish"). When set, all spawned agents include a directive to respond in this language. |
| </config_schema> |
<commit_docs_behavior>
When commit_docs: true (default):
- Planning files committed normally
- SUMMARY.md, STATE.md, ROADMAP.md tracked in git
- Full history of planning decisions preserved
When commit_docs: false:
- Skip all
git add/git commitfor.planning/files - User must add
.planning/to.gitignore - Useful for: OSS contributions, client projects, keeping planning private
Using gsd-tools query (preferred):
# Commit with automatic commit_docs + gitignore checks:
gsd-tools query commit "docs: update state" --files .planning/STATE.md
# Load config via state load (returns JSON):
INIT=$(gsd-tools query state.load)
if [[ "$INIT" == @file:* ]]; then INIT=$(cat "${INIT#@file:}"); fi
# commit_docs is available in the JSON output
# Or use init commands which include commit_docs:
INIT=$(gsd-tools query init.execute-phase "1")
if [[ "$INIT" == @file:* ]]; then INIT=$(cat "${INIT#@file:}"); fi
# commit_docs is included in all init command outputs
Auto-detection: If .planning/ is gitignored, commit_docs is automatically false regardless of config.json. This prevents git errors when users have .planning/ in .gitignore.
Commit via CLI (handles checks automatically):
gsd-tools query commit "docs: update state" --files .planning/STATE.md
The CLI checks commit_docs config and gitignore status internally — no manual conditionals needed.
</commit_docs_behavior>
<search_behavior>
When search_gitignored: false (default):
- Standard rg behavior (respects .gitignore)
- Direct path searches work:
rg "pattern" .planning/finds files - Broad searches skip gitignored:
rg "pattern"skips.planning/
When search_gitignored: true:
- Add
--no-ignoreto broad rg searches that should include.planning/ - Only needed when searching entire repo and expecting
.planning/matches
Note: Most GSD operations use direct file reads or explicit paths, which work regardless of gitignore status.
</search_behavior>
<setup_uncommitted_mode>
To use uncommitted mode:
-
Set config:
"planning": { "commit_docs": false, "search_gitignored": true } -
Add to .gitignore:
.planning/ -
Existing tracked files: If
.planning/was previously tracked:git rm -r --cached .planning/ git commit -m "chore: stop tracking planning docs" -
Branch merges: When using
branching_strategy: phaseormilestone, thecomplete-milestoneworkflow automatically strips.planning/files from staging before merge commits whencommit_docs: false.
</setup_uncommitted_mode>
<branching_strategy_behavior>
Branching Strategies:
| Strategy | When branch created | Branch scope | Merge point |
|---|---|---|---|
none |
Never | N/A | N/A |
phase |
At execute-phase start |
Single phase | User merges after phase |
milestone |
At first execute-phase of milestone |
Entire milestone | At complete-milestone |
When git.branching_strategy: "none" (default):
- All work commits to current branch
- Standard GSD behavior
When git.branching_strategy: "phase":
execute-phasecreates/switches to a branch before execution- Branch name from
phase_branch_template(e.g.,gsd/phase-03-authentication) - All plan commits go to that branch
- User merges branches manually after phase completion
complete-milestoneoffers to merge all phase branches
When git.branching_strategy: "milestone":
- First
execute-phaseof milestone creates the milestone branch - Branch name from
milestone_branch_template(e.g.,gsd/v1.0-mvp) - All phases in milestone commit to same branch
complete-milestoneoffers to merge milestone branch to main
Template variables:
| Variable | Available in | Description |
|---|---|---|
{phase} |
phase_branch_template | Zero-padded phase number (e.g., "03") |
{slug} |
Both | Lowercase, hyphenated name |
{milestone} |
milestone_branch_template | Milestone version (e.g., "v1.0") |
Checking the config:
Use init execute-phase which returns all config as JSON:
INIT=$(gsd-tools query init.execute-phase "1")
if [[ "$INIT" == @file:* ]]; then INIT=$(cat "${INIT#@file:}"); fi
# JSON output includes: branching_strategy, phase_branch_template, milestone_branch_template
Or use state load for the config values:
INIT=$(gsd-tools query state.load)
if [[ "$INIT" == @file:* ]]; then INIT=$(cat "${INIT#@file:}"); fi
# Parse branching_strategy, phase_branch_template, milestone_branch_template from JSON
Branch creation:
# For phase strategy
if [ "$BRANCHING_STRATEGY" = "phase" ]; then
PHASE_SLUG=$(echo "$PHASE_NAME" | tr '[:upper:]' '[:lower:]' | sed 's/[^a-z0-9]/-/g' | sed 's/--*/-/g' | sed 's/^-//;s/-$//')
BRANCH_NAME=$(echo "$PHASE_BRANCH_TEMPLATE" | sed "s/{phase}/$PADDED_PHASE/g" | sed "s/{slug}/$PHASE_SLUG/g")
git checkout -b "$BRANCH_NAME" 2>/dev/null || git checkout "$BRANCH_NAME"
fi
# For milestone strategy
if [ "$BRANCHING_STRATEGY" = "milestone" ]; then
MILESTONE_SLUG=$(echo "$MILESTONE_NAME" | tr '[:upper:]' '[:lower:]' | sed 's/[^a-z0-9]/-/g' | sed 's/--*/-/g' | sed 's/^-//;s/-$//')
BRANCH_NAME=$(echo "$MILESTONE_BRANCH_TEMPLATE" | sed "s/{milestone}/$MILESTONE_VERSION/g" | sed "s/{slug}/$MILESTONE_SLUG/g")
git checkout -b "$BRANCH_NAME" 2>/dev/null || git checkout "$BRANCH_NAME"
fi
Merge options at complete-milestone:
| Option | Git command | Result |
|---|---|---|
| Squash merge (recommended) | git merge --squash |
Single clean commit per branch |
| Merge with history | git merge --no-ff |
Preserves all individual commits |
| Delete without merging | git branch -D |
Discard branch work |
| Keep branches | (none) | Manual handling later |
Squash merge is recommended — keeps main branch history clean while preserving the full development history in the branch (until deleted).
Use cases:
| Strategy | Best for |
|---|---|
none |
Solo development, simple projects |
phase |
Code review per phase, granular rollback, team collaboration |
milestone |
Release branches, staging environments, PR per version |
</branching_strategy_behavior>
<complete_field_reference>
Complete Field Reference
Generated from CONFIG_DEFAULTS (configuration.cjs) and VALID_CONFIG_KEYS (config-schema.cjs).
Core Fields
| Key | Type | Default | Allowed Values | Description |
|---|---|---|---|---|
model_profile |
string | "balanced" |
"quality", "balanced", "budget", "adaptive", "inherit" |
Model selection preset for subagents |
mode |
string | "interactive" |
"interactive", "yolo" |
Operation mode: "interactive" shows gates and confirmations; "yolo" runs autonomously without prompts |
granularity |
string | (none) | "coarse", "standard", "fine" |
Planning depth for phase plans (migrated from deprecated depth) |
commit_docs |
boolean | true |
true, false |
Commit .planning/ artifacts to git (auto-false if .planning/ is gitignored) |
search_gitignored |
boolean | false |
true, false |
Include gitignored paths in broad rg searches via --no-ignore |
phase_naming |
string | "sequential" |
"sequential", "custom" |
Phase numbering: auto-increment or arbitrary string IDs |
project_code |
string|null | null |
Any short string | Prefix for phase dirs (e.g., "CK" produces CK-01-foundation) |
response_language |
string|null | null |
Any language name | Language for user-facing prompts (e.g., "Portuguese", "Japanese") |
context_window |
number | 200000 |
200000, 1000000 |
Context window size; set 1000000 for 1M-context models |
resolve_model_ids |
boolean|string | false |
false, true, "omit" |
Map model aliases to full Claude IDs; "omit" returns empty string |
context |
string|null | null |
"dev", "research", "review" |
Execution context profile that adjusts agent behavior: "dev" for development tasks, "research" for investigation/exploration, "review" for code review workflows |
review.models.<cli> |
string|null | null |
Any model ID string | Per-CLI model override for /gsd:review (e.g., review.models.gemini). Falls back to CLI default when null. |
Workflow Fields
Set via workflow.* namespace in config.json (e.g., "workflow": { "research": true }).
| Key | Type | Default | Allowed Values | Description |
|---|---|---|---|---|
workflow.research |
boolean | true |
true, false |
Run research agent before planning |
workflow.plan_check |
boolean | true |
true, false |
Run plan-checker agent to validate plans. Alias: plan_checker is the flat-key form used in CONFIG_DEFAULTS; workflow.plan_check is the canonical namespaced form. |
workflow.verifier |
boolean | true |
true, false |
Run verifier agent after execution |
workflow.nyquist_validation |
boolean | true |
true, false |
Enable Nyquist-inspired validation gates |
workflow.auto_prune_state |
boolean | false |
true, false |
Automatically prune old STATE.md entries on phase completion (keeps 3 most recent phases) |
workflow.auto_advance |
boolean | false |
true, false |
Auto-advance to next phase after completion |
workflow.node_repair |
boolean | true |
true, false |
Attempt automatic repair of failed plan nodes |
workflow.node_repair_budget |
number | 2 |
Any positive integer | Max repair retries per failed node |
workflow.smart_zone_tokens |
number | 100000 |
Any positive integer | Smart-zone token budget for phase-effort estimation (#2630, ADR-2629). A phase whose estimate exceeds this is flagged with a split recommendation — advisory only, never a block. A policy default, not a benchmark constant: degradation begins before the advertised context window is full, but the effective ceiling is model- and task-dependent, so the calibration loop corrects it per project. Alias: smart_zone_tokens is the flat-key form used in CONFIG_DEFAULTS; workflow.smart_zone_tokens is the canonical namespaced form. |
workflow.ai_integration_phase |
boolean | true |
true, false |
Run /gsd:ai-integration-phase before planning AI system phases |
workflow.api_coverage_gate |
boolean | true |
true, false |
Require an explicit API-coverage decision (full-by-default, opt-out-not-opt-in) before a phase that integrates an external API/SDK/service can seal. At plan:pre prompts a COVERAGE.md matrix; at verify:pre a blocking gate fails the seal unless the matrix exists with every non-integrated capability an explicit, reasoned opt-out (#1562) |
workflow.ui_phase |
boolean | true |
true, false |
Generate UI-SPEC.md for frontend phases |
workflow.ui_safety_gate |
boolean | true |
true, false |
Require safety gate approval for UI changes |
workflow.text_mode |
boolean | false |
true, false |
Use plain-text numbered lists instead of AskUserQuestion menus |
workflow.research_before_questions |
boolean | false |
true, false |
Run research before interactive questions in discuss phase |
workflow.discuss_mode |
string | "discuss" |
"discuss", "assumptions" |
Default mode for discuss-phase: "discuss" runs interactive questioning; "assumptions" analyzes codebase and surfaces assumptions instead |
workflow.skip_discuss |
boolean | false |
true, false |
Skip discuss phase entirely |
workflow.use_worktrees |
boolean | true |
true, false |
Run executor agents in isolated git worktrees |
workflow.subagent_timeout |
number | 300000 |
Any positive integer (ms) | Timeout for parallel subagent tasks (default: 5 minutes) |
workflow.test_command |
string|null | null |
Any shell command | Regression/test gate command run by verify-phase, execute-phase, audit-fix, and post-merge-gate. Unset → GSD auto-detects (Makefile / package.json / Cargo.toml / go.mod / pyproject.toml). |
workflow.build_command |
string|null | null |
Any shell command | Build gate command run by the post-merge gate. Unset → build step auto-detected/skipped. |
workflow.mvp_mode |
boolean | false |
true, false |
Persist the MVP-mode flag in config so every phase defaults to MVP framing without requiring --mvp on the CLI. Resolved via the chain: --mvp CLI flag → ROADMAP.md **Mode:** mvp field → this config value → false. When true, the planner, executor, verifier, and discovery surfaces (progress, stats, graphify) all treat the phase as an MVP vertical slice (UI → API → DB) of one user-visible capability. |
workflow.context_guard_mode |
string | "warn" |
"auto", "warn", "off" |
Context exhaustion guard mode for execute-phase. Before each wave, the orchestrator self-assesses context pressure using degradation signals from context-budget.md. "warn" (default): emit a warning and recommend /gsd:pause-work when POOR tier is detected. "auto": automatically invoke /gsd:pause-work before the next wave when POOR tier is detected. "off": disable the guard. The guard is heuristic — no programmatic context-% API exists. |
workflow.plan_chunked |
boolean | false |
true, false |
Enable chunked planning mode. When true, the plan-phase orchestrator splits the single long-lived planner Task into a short outline Task followed by N short per-plan Tasks (~3–5 min each). Each plan is committed individually for crash resilience. Particularly useful on Windows where long-lived Tasks may hang on stdio. Also activated by the --chunked flag. |
workflow.specless_probe_fallback |
boolean | true |
true, false |
Gate the SPEC-less probe fallback in plan-phase. When true (default), a phase that did not supply a ## Edge Coverage / ## Prohibitions SPEC section (header absent or present-but-empty) runs the existing probe protocol — the deterministic edge-probe.cjs for edges and an in-planner LLM recall pass for prohibitions — and authors the resulting predicates into PLAN.md must_haves (section-level precedence: a SPEC-supplied section is never re-run or overwritten). When false, the fallback is skipped but the skip is recorded: plan-phase emits a visible "probe fallback disabled" marker, never a silent skip. |
workflow.code_review_command |
string|null | null |
Any shell command | External code-review command integrated into /gsd:ship. The diff is piped to the command via stdin; the command must output JSON with a verdict field ("APPROVED" or "REVISE"). Non-zero exit or "REVISE" verdict blocks the ship workflow. When unset, the built-in review flow runs. Example: my-review-tool --review. |
workflow.inline_plan_threshold |
number | 2 |
0–10 |
Plans with ≤N tasks execute inline instead of spawning a subagent |
workflow.code_review |
boolean | true |
true, false |
Enable built-in code review step in the ship workflow |
workflow.code_review_depth |
string | "standard" |
"light", "standard", "deep" |
Depth level for code review analysis in the ship workflow |
workflow._auto_chain_active |
boolean | false |
true, false |
Internal: tracks whether autonomous chaining is active |
workflow.security_enforcement |
boolean | true |
true, false |
Enable threat-model-anchored security verification via /gsd:secure-phase. When false, security checks are skipped entirely |
workflow.security_asvs_level |
number | 1 |
1, 2, 3 |
OWASP ASVS verification level. Level 1 = opportunistic, Level 2 = standard, Level 3 = comprehensive. Scales both planner threat-disposition rigor (which threats must be mitigated vs. accepted) and auditor verification depth (grep-level → boundary-placement check → full data-flow trace). See gsd-core/references/security-asvs-levels.md. |
workflow.security_block_on |
string | "high" |
"critical", "high", "medium", "low", "none" |
Minimum threat severity that blocks phase advancement. The auditor counts only open threats at or above this severity toward the blocking gate (SECURITY.md threats_open); none disables severity blocking. |
workflow.post_planning_gaps |
boolean | true |
true, false |
Post-planning gap report (#2493). After plans are generated, scans REQUIREMENTS.md and CONTEXT.md <decisions> against all PLAN.md files and emits a unified Source | Item | Status table. Non-blocking. Set to false to skip Step 13e of plan-phase. Alias: post_planning_gaps is the flat-key form used in CONFIG_DEFAULTS; workflow.post_planning_gaps is the canonical namespaced form. |
Ship Fields
Set via ship.* namespace in config.json. These fields affect /gsd:ship PRD-style pull request body composition only.
| Key | Type | Default | Allowed Values | Description |
|---|---|---|---|---|
ship.pr_body_sections |
array | [] |
Array of section objects | Append-only project-specific PR body sections. Each entry has heading, optional enabled, and one or more of source, template, or fallback. Disabled entries remain in onboarding config but do not render. Core sections remain required and cannot be removed or replaced. |
Git Fields
Set via git.* namespace (e.g., "git": { "branching_strategy": "phase" }).
| Key | Type | Default | Allowed Values | Description |
|---|---|---|---|---|
git.branching_strategy |
string | "none" |
"none", "phase", "milestone" |
Git branching approach for phase/milestone isolation |
git.base_branch |
string|null | null (auto-detect) |
Any branch name | Target branch for PRs and merges; auto-detects from origin/HEAD when null |
git.create_tag |
boolean | true |
true, false |
Create git tags on milestone completion |
git.phase_branch_template |
string | "gsd/phase-{phase}-{slug}" |
Template with {phase}, {slug} |
Branch naming template for phase strategy |
git.milestone_branch_template |
string | "gsd/{milestone}-{slug}" |
Template with {milestone}, {slug} |
Branch naming template for milestone strategy |
git.quick_branch_template |
string|null | null |
Template with {slug} |
Optional branch template for quick-task runs |
Search & API Fields
These toggle external search integrations. Auto-detected at project creation when API keys are present.
| Key | Type | Default | Allowed Values | Description |
|---|---|---|---|---|
brave_search |
boolean | false |
true, false |
Enable Brave web search for research agent (requires BRAVE_API_KEY) |
firecrawl |
boolean | false |
true, false |
Enable Firecrawl page scraping (requires FIRECRAWL_API_KEY) |
exa_search |
boolean | false |
true, false |
Enable Exa semantic search (requires EXA_API_KEY) |
Features Fields
Set via features.* namespace (e.g., "features": { "thinking_partner": true }).
| Key | Type | Default | Allowed Values | Description |
|---|---|---|---|---|
features.thinking_partner |
boolean | false |
true, false |
Enable conditional extended thinking at workflow decision points (used by discuss-phase and plan-phase for architectural tradeoff analysis) |
features.global_learnings |
boolean | false |
true, false |
Enable injection of global learnings from ~/.gsd/knowledge/ into agent prompts |
Hook Fields
Set via hooks.* namespace (e.g., "hooks": { "context_warnings": true }).
| Key | Type | Default | Allowed Values | Description |
|---|---|---|---|---|
hooks.context_warnings |
boolean | true |
true, false |
Show warnings when context budget is exceeded |
Learnings Fields
Set via learnings.* namespace (e.g., "learnings": { "max_inject": 5 }). Used together with features.global_learnings.
| Key | Type | Default | Allowed Values | Description |
|---|---|---|---|---|
learnings.max_inject |
number | 10 |
Any positive integer | Maximum number of global learning entries to inject into agent prompts per session |
Intel Fields
Set via intel.* namespace (e.g., "intel": { "enabled": true }). Controls the queryable codebase intelligence system consumed by /gsd:map-codebase --query.
| Key | Type | Default | Allowed Values | Description |
|---|---|---|---|---|
intel.enabled |
boolean | false |
true, false |
Enable queryable codebase intelligence system. When true, /gsd:map-codebase --query builds and queries a JSON index in .planning/intel/. |
Manager Fields
Set via manager.* namespace (e.g., "manager": { "flags": { "discuss": "--auto" } }).
| Key | Type | Default | Allowed Values | Description |
|---|---|---|---|---|
manager.flags.discuss |
string | "" |
Any CLI flags string | Flags passed to /gsd:discuss-phase from manager (e.g., "--auto --analyze") |
manager.flags.plan |
string | "" |
Any CLI flags string | Flags passed to plan workflow from manager |
manager.flags.execute |
string | "" |
Any CLI flags string | Flags passed to execute workflow from manager |
Advanced Fields
| Key | Type | Default | Allowed Values | Description |
|---|---|---|---|---|
parallelization |
boolean|object | true |
true, false, { "enabled": true } |
Enable parallel wave execution; object form allows additional sub-keys |
model_overrides |
object|null | null |
{ "<agent-type>": "<model-id>" } |
Override model selection per agent type |
agent_skills |
object | {} |
{ "<agent-type>": "<skill-set>" } |
Assign skill sets to specific agent types |
sub_repos |
array | [] |
Array of relative path strings | Child directories with independent .git repos (auto-detected) |
Planning Fields
These can be set at top level or nested under planning.* (e.g., "planning": { "commit_docs": false }). Both forms are equivalent; top-level takes precedence if both exist.
| Key | Type | Default | Allowed Values | Description |
|---|---|---|---|---|
planning.commit_docs |
boolean | true |
true, false |
Alias for top-level commit_docs |
planning.search_gitignored |
boolean | false |
true, false |
Alias for top-level search_gitignored |
Field Interactions
Several config fields affect each other or trigger special behavior:
-
commit_docsauto-detection -- When no explicit value is set in config.json and.planning/is in.gitignore,commit_docsautomatically resolves tofalse. An explicittrueorfalsein config always overrides auto-detection. -
branching_strategycontrols branch templates -- Thephase_branch_templateandmilestone_branch_templatefields are only used whenbranching_strategyis set to"phase"or"milestone"respectively. Whenbranching_strategyis"none", all template fields are ignored. -
context_windowthreshold triggers -- Whencontext_window >= 500000, workflows enable adaptive context enrichment: full-body reads of prior phase SUMMARYs, cross-phase context injection in plan-phase, and deeper read depth for anti-pattern references. Below 500000, only frontmatter and summaries are read. -
parallelizationpolymorphism -- Accepts both a simple boolean and an object with anenabledfield.loadConfig()normalizes either form to a boolean.{ "enabled": true }is equivalent totrue. -
Search API keys and flags --
brave_search,firecrawl, andexa_searchare auto-set totrueduring project creation if the corresponding API key is detected (environment variable or~/.gsd/<name>_api_keyfile). Setting them totruewithout the API key has no effect. -
planning.*and top-level equivalence --planning.commit_docsandcommit_docsare equivalent;planning.search_gitignoredandsearch_gitignoredare equivalent. If both are set, the top-level value takes precedence. -
depthtogranularitymigration -- The deprecateddepthkey (quick/standard/comprehensive) is automatically migrated togranularity(coarse/standard/fine) on config load and persisted back to disk. -
sub_reposauto-sync -- On every config load, GSD scans for child directories with.gitand updates thesub_reposarray if the filesystem has changed. LegacymultiRepo: trueis automatically migrated to a detectedsub_reposarray. -
workflow.use_worktreesand branch divergence -- Whenuse_worktreesistrue(default), executor worktrees are forked fromorigin/HEAD-- by the host's own harness ondispatch.isolation: harness-worktreeruntimes (Claude Code, Cursor), or by GSD itself onorchestrator-worktreeruntimes (Codex, OpenCode, Kimi, Kimi Code). The divergence behavior below is identical either way, because the fork base is a property of the repository rather than of whoever creates the worktree. If your current branch has commits thatorigin/HEADdoes not (for example an unmerged milestone or feature branch), GSD automatically degrades to sequential execution for that run and prints a one-line⚠ Worktree base mismatchwarning. To restore parallel execution permanently, setworktree.baseRef:"head"in.claude/settings.local.json(runnode gsd-tools.cjs worktree set-baseref). This makes the harness fork worktrees from the live HEAD instead oforigin/HEAD. Both fresh installs and upgrades of GSD Core set this automatically (no-clobber) whenuse_worktreesis enabled; you can also run the command manually at any time. Settingworkflow.use_worktrees: falseis the alternative if worktrees are not needed at all. On a runtime whose declareddispatch.isolationisnone, an explicittrueis a config the execution workflows fail closed on;/gsd:healthreports it as warningW025and/gsd:settingsoffers to repair it (#2486).
Example Configurations
Minimal -- Solo Developer
{
"model_profile": "balanced",
"commit_docs": true,
"workflow": {
"research": true,
"plan_check": true,
"verifier": true,
"use_worktrees": false
}
}
Team Project with Branching
{
"model_profile": "quality",
"commit_docs": true,
"project_code": "APP",
"git": {
"branching_strategy": "phase",
"base_branch": "develop",
"phase_branch_template": "gsd/phase-{phase}-{slug}"
},
"workflow": {
"research": true,
"plan_check": true,
"verifier": true,
"nyquist_validation": true,
"use_worktrees": true,
"discuss_mode": "discuss"
},
"manager": {
"flags": {
"discuss": "",
"plan": "",
"execute": ""
}
},
"response_language": "English"
}
Large Codebase -- 1M Context with Extended Timeouts
{
"model_profile": "quality",
"context_window": 1000000,
"commit_docs": true,
"project_code": "MEGA",
"phase_naming": "sequential",
"git": {
"branching_strategy": "milestone",
"milestone_branch_template": "gsd/{milestone}-{slug}"
},
"workflow": {
"research": true,
"plan_check": true,
"verifier": true,
"nyquist_validation": true,
"subagent_timeout": 600000,
"use_worktrees": true,
"node_repair": true,
"node_repair_budget": 3,
"auto_advance": true
},
"brave_search": true,
"hooks": {
"context_warnings": true
}
}
</complete_field_reference>
</planning_config>