@opengsd/gsd-core 1.8.0 → 1.9.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (177) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/.opencode/plugins/gsd-core.js +31 -1
  4. package/agents/gsd-code-fixer.md +107 -34
  5. package/agents/gsd-codebase-mapper.md +1 -1
  6. package/agents/gsd-debug-session-manager.md +36 -0
  7. package/agents/gsd-executor.md +20 -7
  8. package/agents/gsd-intel-updater.md +3 -3
  9. package/agents/gsd-phase-researcher.md +4 -2
  10. package/agents/gsd-plan-checker.md +20 -0
  11. package/agents/gsd-planner.md +15 -23
  12. package/agents/gsd-project-researcher.md +2 -2
  13. package/agents/gsd-ui-auditor.md +0 -40
  14. package/bin/install.js +236 -107
  15. package/commands/gsd/plan-review-convergence.md +5 -1
  16. package/gsd-core/bin/gsd-tools.cjs +882 -4
  17. package/gsd-core/bin/lib/api-coverage.cjs +22 -8
  18. package/gsd-core/bin/lib/audit.cjs +8 -8
  19. package/gsd-core/bin/lib/capability-consent.cjs +40 -1
  20. package/gsd-core/bin/lib/capability-lifecycle.cjs +58 -0
  21. package/gsd-core/bin/lib/capability-loader.cjs +23 -1
  22. package/gsd-core/bin/lib/capability-registry.cjs +1353 -132
  23. package/gsd-core/bin/lib/capability-trust.cjs +468 -33
  24. package/gsd-core/bin/lib/capability-validator.cjs +882 -6
  25. package/gsd-core/bin/lib/check-command-router.cjs +12 -2
  26. package/gsd-core/bin/lib/cjs-command-router-adapter.cjs +15 -0
  27. package/gsd-core/bin/lib/claude-orchestration-command-router.cjs +102 -12
  28. package/gsd-core/bin/lib/claude-orchestration.cjs +125 -22
  29. package/gsd-core/bin/lib/commands.cjs +246 -18
  30. package/gsd-core/bin/lib/config-loader.cjs +200 -28
  31. package/gsd-core/bin/lib/config.cjs +90 -5
  32. package/gsd-core/bin/lib/estimate-cli.cjs +336 -0
  33. package/gsd-core/bin/lib/frontmatter.cjs +125 -15
  34. package/gsd-core/bin/lib/host-integration.cjs +215 -8
  35. package/gsd-core/bin/lib/init.cjs +44 -19
  36. package/gsd-core/bin/lib/install-engine.cjs +1 -0
  37. package/gsd-core/bin/lib/milestone.cjs +36 -9
  38. package/gsd-core/bin/lib/model-catalog.cjs +51 -1
  39. package/gsd-core/bin/lib/observability/logger.cjs +7 -2
  40. package/gsd-core/bin/lib/phase-command-router.cjs +10 -1
  41. package/gsd-core/bin/lib/phase-estimation.cjs +398 -0
  42. package/gsd-core/bin/lib/phase-id.cjs +278 -5
  43. package/gsd-core/bin/lib/phase.cjs +61 -6
  44. package/gsd-core/bin/lib/plan-drift-guard.cjs +1 -1
  45. package/gsd-core/bin/lib/plan-scan.cjs +1 -1
  46. package/gsd-core/bin/lib/planning-workspace.cjs +9 -2
  47. package/gsd-core/bin/lib/profile-output.cjs +34 -8
  48. package/gsd-core/bin/lib/project-root.cjs +48 -0
  49. package/gsd-core/bin/lib/review-lane-descriptor.cjs +927 -0
  50. package/gsd-core/bin/lib/review-lane-invocation.cjs +348 -0
  51. package/gsd-core/bin/lib/review-lane-runner.cjs +594 -0
  52. package/gsd-core/bin/lib/review-reviewer-selection.cjs +114 -32
  53. package/gsd-core/bin/lib/roadmap-parser.cjs +54 -6
  54. package/gsd-core/bin/lib/roadmap.cjs +10 -4
  55. package/gsd-core/bin/lib/runtime-artifact-conversion.cjs +31 -4
  56. package/gsd-core/bin/lib/runtime-artifact-layout.cjs +1 -1
  57. package/gsd-core/bin/lib/runtime-hooks-surface.cjs +140 -0
  58. package/gsd-core/bin/lib/runtime-name-policy.cjs +15 -2
  59. package/gsd-core/bin/lib/smart-entry.cjs +1 -1
  60. package/gsd-core/bin/lib/state-document.cjs +164 -20
  61. package/gsd-core/bin/lib/state-transition.cjs +28 -10
  62. package/gsd-core/bin/lib/state.cjs +141 -21
  63. package/gsd-core/bin/lib/uat-predicate.cjs +6 -4
  64. package/gsd-core/bin/lib/uat.cjs +9 -7
  65. package/gsd-core/bin/lib/ui-consideration-probe.cjs +2 -2
  66. package/gsd-core/bin/lib/unusable-input.cjs +216 -0
  67. package/gsd-core/bin/lib/validate.cjs +32 -0
  68. package/gsd-core/bin/lib/verification.cjs +51 -14
  69. package/gsd-core/bin/lib/verify.cjs +146 -22
  70. package/gsd-core/bin/lib/worktree-safety.cjs +360 -15
  71. package/gsd-core/bin/shared/config-defaults.manifest.json +1 -0
  72. package/gsd-core/bin/shared/config-schema.manifest.json +1 -13
  73. package/gsd-core/bin/shared/model-catalog.json +5 -0
  74. package/gsd-core/bin/shared/runtime-aliases.manifest.json +5 -0
  75. package/gsd-core/references/context-budget.md +40 -0
  76. package/gsd-core/references/gate-prompts.md +6 -3
  77. package/gsd-core/references/model-profile-resolution.md +64 -13
  78. package/gsd-core/references/offer-next.md +88 -0
  79. package/gsd-core/references/planning-config.md +2 -1
  80. package/gsd-core/references/reviewer-instances.md +28 -21
  81. package/gsd-core/references/runtime-aware-dispatch.md +42 -0
  82. package/gsd-core/references/ui-consideration-probe.md +2 -2
  83. package/gsd-core/references/worktree-branch-check.md +4 -4
  84. package/gsd-core/templates/summary-minimal.md +4 -0
  85. package/gsd-core/templates/summary-standard.md +4 -0
  86. package/gsd-core/templates/summary.md +7 -0
  87. package/gsd-core/workflows/ai-integration-phase.md +4 -4
  88. package/gsd-core/workflows/audit-fix.md +4 -0
  89. package/gsd-core/workflows/audit-milestone.md +8 -0
  90. package/gsd-core/workflows/autonomous.md +19 -15
  91. package/gsd-core/workflows/check-todos.md +2 -2
  92. package/gsd-core/workflows/code-review-fix.md +14 -6
  93. package/gsd-core/workflows/code-review.md +93 -21
  94. package/gsd-core/workflows/debug.md +10 -2
  95. package/gsd-core/workflows/diagnose-issues.md +4 -0
  96. package/gsd-core/workflows/discuss-phase/modes/advisor.md +2 -4
  97. package/gsd-core/workflows/discuss-phase/modes/auto.md +0 -6
  98. package/gsd-core/workflows/discuss-phase-assumptions.md +15 -9
  99. package/gsd-core/workflows/discuss-phase.md +2 -2
  100. package/gsd-core/workflows/docs-update.md +8 -0
  101. package/gsd-core/workflows/eval-review.md +1 -1
  102. package/gsd-core/workflows/execute-phase/steps/codebase-drift-gate.md +4 -0
  103. package/gsd-core/workflows/execute-phase/steps/executor-isolation-dispatch.md +160 -0
  104. package/gsd-core/workflows/execute-phase.md +85 -115
  105. package/gsd-core/workflows/execute-plan.md +5 -4
  106. package/gsd-core/workflows/explore.md +4 -0
  107. package/gsd-core/workflows/extract-learnings.md +21 -0
  108. package/gsd-core/workflows/help/modes/full.md +3 -3
  109. package/gsd-core/workflows/import.md +4 -1
  110. package/gsd-core/workflows/ingest-docs.md +4 -0
  111. package/gsd-core/workflows/map-codebase.md +13 -6
  112. package/gsd-core/workflows/new-milestone.md +10 -2
  113. package/gsd-core/workflows/new-project.md +11 -4
  114. package/gsd-core/workflows/next.md +5 -2
  115. package/gsd-core/workflows/plan-phase.md +42 -46
  116. package/gsd-core/workflows/plan-review-convergence.md +18 -14
  117. package/gsd-core/workflows/progress.md +1 -1
  118. package/gsd-core/workflows/quick.md +14 -3
  119. package/gsd-core/workflows/review.md +146 -575
  120. package/gsd-core/workflows/scan.md +9 -1
  121. package/gsd-core/workflows/secure-phase.md +10 -2
  122. package/gsd-core/workflows/ship.md +41 -11
  123. package/gsd-core/workflows/smart-entry.md +1 -1
  124. package/gsd-core/workflows/ui-phase.md +8 -1
  125. package/gsd-core/workflows/ui-review.md +8 -1
  126. package/gsd-core/workflows/update.md +104 -5
  127. package/gsd-core/workflows/validate-phase.md +10 -2
  128. package/gsd-core/workflows/verify-work.md +8 -1
  129. package/hooks/dist/gsd-cursor-session-start.js +6 -2
  130. package/hooks/dist/gsd-cursor-stop.js +6 -2
  131. package/hooks/dist/gsd-cursor-subagent-start.js +6 -2
  132. package/hooks/dist/gsd-graphify-update.sh +9 -0
  133. package/hooks/dist/gsd-phase-boundary.sh +14 -2
  134. package/hooks/dist/gsd-prompt-guard.js +101 -2
  135. package/hooks/dist/gsd-read-guard.js +100 -2
  136. package/hooks/dist/gsd-read-injection-scanner.js +109 -2
  137. package/hooks/dist/gsd-statusline.js +9 -6
  138. package/hooks/dist/gsd-workflow-guard.js +110 -6
  139. package/hooks/dist/gsd-worktree-path-guard.js +132 -8
  140. package/hooks/dist/lib/cursor-workspace.js +74 -0
  141. package/hooks/gsd-cursor-session-start.js +6 -2
  142. package/hooks/gsd-cursor-stop.js +6 -2
  143. package/hooks/gsd-cursor-subagent-start.js +6 -2
  144. package/hooks/gsd-graphify-update.sh +9 -0
  145. package/hooks/gsd-phase-boundary.sh +14 -2
  146. package/hooks/gsd-prompt-guard.js +101 -2
  147. package/hooks/gsd-read-guard.js +100 -2
  148. package/hooks/gsd-read-injection-scanner.js +109 -2
  149. package/hooks/gsd-statusline.js +9 -6
  150. package/hooks/gsd-workflow-guard.js +110 -6
  151. package/hooks/gsd-worktree-path-guard.js +132 -8
  152. package/hooks/lib/cursor-workspace.js +74 -0
  153. package/package.json +7 -7
  154. package/pi/gsd.cjs +26 -1
  155. package/scripts/check-coverage-gate.cjs +51 -0
  156. package/scripts/check-glossary-refs.cjs +24 -0
  157. package/scripts/ci-test-scope.cjs +67 -17
  158. package/scripts/gen-adr-index.cjs +6 -4
  159. package/scripts/gen-capability-matrix.cjs +26 -2
  160. package/scripts/gen-capability-registry.cjs +132 -34
  161. package/scripts/gen-emitted-baseline.cjs +145 -0
  162. package/scripts/gen-registry.cjs +39 -15
  163. package/scripts/lint-compiled-artifact-sync.cjs +146 -0
  164. package/scripts/lint-emitted-drift-ack.cjs +149 -0
  165. package/scripts/lint-fix-has-regression-test.cjs +131 -0
  166. package/scripts/lint-resolution-provenance.cjs +9 -0
  167. package/scripts/mutation-matrix.cjs +4 -0
  168. package/scripts/prompt-injection-scan.sh +6 -0
  169. package/scripts/registry-schema.cjs +372 -94
  170. package/scripts/release-notes/conventional-title.cjs +19 -1
  171. package/scripts/release-notes/format-github-release-notes.cjs +7 -3
  172. package/scripts/validate-registry.cjs +10 -6
  173. package/scripts/workflow-size.cjs +16 -8
  174. package/skills/gsd-plan-review-convergence/SKILL.md +5 -1
  175. package/vscode/package.json +1 -1
  176. package/scripts/gen-golden-install-parity-zcode.cjs +0 -77
  177. package/scripts/update-size-baseline.cjs +0 -68
@@ -1,38 +1,89 @@
1
1
  # Model Profile Resolution
2
2
 
3
- Resolve model profile once at the start of orchestration, then use it for all Task spawns.
3
+ Resolve each agent's model through `gsd-tools`, then pass it to the `Agent()` spawn — or
4
+ omit the parameter entirely when nothing resolved.
4
5
 
5
6
  ## Resolution Pattern
6
7
 
8
+ Prefer the field your workflow's own `init.*` payload already emits (every orchestrator
9
+ init that spawns subagents carries one — `mapper_model`, `planner_model`,
10
+ `executor_model`, `doc_writer_model`, …). Declare it in the workflow's parse line so the
11
+ binding is stated, not implied:
12
+
13
+ ```
14
+ Parse JSON for: `mapper_model`, …
15
+ ```
16
+
17
+ When the agent type is only known at runtime — a capability hook naming its own agent, for
18
+ example — resolve it directly instead:
19
+
7
20
  ```bash
8
- MODEL_PROFILE=$(cat .planning/config.json 2>/dev/null | grep -o '"model_profile"[[:space:]]*:[[:space:]]*"[^"]*"' | grep -o '"[^"]*"$' | tr -d '"' || echo "balanced")
21
+ AGENT_MODEL=$(gsd_run query resolve-model "<agent-type>" --raw)
9
22
  ```
10
23
 
11
- Default: `balanced` if not set or config missing.
24
+ Both surfaces return the same thing: a model string for the active runtime, or the **empty
25
+ string** when nothing resolved.
12
26
 
13
27
  ## Lookup Table
14
28
 
15
29
  @~/.claude/gsd-core/references/model-profiles.md
16
30
 
17
- Look up the agent in the table for the resolved profile. Pass the model parameter to Task calls:
31
+ ## Passing the model to a spawn
18
32
 
19
33
  ```
20
- Task(
34
+ Agent(
21
35
  prompt="...",
22
36
  subagent_type="gsd-planner",
23
- model="{resolved_model}" # "inherit", "sonnet", or "haiku"
37
+ model="{planner_model}"
38
+ )
39
+ ```
40
+
41
+ Substitute the field **this workflow bound** — never a generic placeholder name.
42
+
43
+ **#2517 — omit, do not emit an empty model.** When the resolved value is `"inherit"` or
44
+ empty, **omit the `model=` parameter entirely**:
45
+
46
+ ```
47
+ Agent(
48
+ prompt="...",
49
+ subagent_type="gsd-planner"
24
50
  )
25
51
  ```
26
52
 
27
- **Note:** Opus-tier agents resolve to `"inherit"` (not `"opus"`). This causes the agent to use the parent session's model, avoiding conflicts with organization policies that may block specific opus versions.
53
+ No model parameter is passed at all — `planner_model` resolved to `"inherit"` or empty, and
54
+ omitting it inherits the orchestrator's model. Passing either value through as an argument
55
+ instead 404s on non-Claude runtimes.
56
+
57
+ This is not cosmetic, and both values really occur. `model_profile: "inherit"` — and any
58
+ opus-tier agent — resolves to the literal string `"inherit"`. `resolve_model_ids: "omit"`
59
+ resolves to the **empty string** whenever the project sets it explicitly or the active
60
+ runtime has no native tier aliases; an agent type absent from the profile table takes that
61
+ same empty-string path, because it has no tier for the earlier steps to resolve. Emitting
62
+ either value verbatim fails the spawn on every runtime without native tier aliases.
63
+
64
+ **#2684 — substitute a field your workflow actually bound.** `model="{…}"` must name a key
65
+ your own `init.*` payload emits, a shell variable you assigned, or a field on your declared
66
+ parse line. A placeholder that resolves to nothing does not fail loudly — the orchestrator
67
+ silently invents a value, which is the invisible partial application ADR-1411 prohibits.
68
+ `tests/model-omit-when-inherit-guard.test.cjs` enforces both rules.
69
+
70
+ ## Profile semantics
71
+
72
+ **Note:** Opus-tier agents resolve to `"inherit"` (not `"opus"`). This causes the agent to
73
+ use the parent session's model, avoiding conflicts with organization policies that may
74
+ block specific opus versions — and, per the rule above, means the `model=` parameter is
75
+ omitted rather than set.
28
76
 
29
- If `model_profile` is `"adaptive"`, agents resolve to role-based assignments (opus/sonnet/haiku based on agent type).
77
+ If `model_profile` is `"adaptive"`, agents resolve to role-based assignments (opus/sonnet/
78
+ haiku based on agent type).
30
79
 
31
- If `model_profile` is `"inherit"`, all agents resolve to `"inherit"` (useful for OpenCode `/model`).
80
+ If `model_profile` is `"inherit"`, all agents resolve to `"inherit"` (useful for OpenCode
81
+ `/model`).
32
82
 
33
83
  ## Usage
34
84
 
35
- 1. Resolve once at orchestration start
36
- 2. Store the profile value
37
- 3. Look up each agent's model from the table when spawning
38
- 4. Pass model parameter to each Task call (values: `"inherit"`, `"sonnet"`, `"haiku"`)
85
+ 1. Bind the model once — from the `init.*` payload field, or `query resolve-model` when the
86
+ agent type is runtime-determined
87
+ 2. Declare the bound field on the workflow's parse line
88
+ 3. Pass `model="{bound_field}"` on each `Agent()` spawn
89
+ 4. Omit `model=` entirely whenever the bound value is `"inherit"` or empty
@@ -0,0 +1,88 @@
1
+ <!--
2
+ offer-next.md — extracted from execute-phase.md step "offer_next" (#2537).
3
+ Eagerly @-referenced from execute-phase.md so runtime behavior is unchanged; the
4
+ extraction restores byte-budget headroom the frozen ceiling exists to provide.
5
+ -->
6
+
7
+
8
+ **Exception:** If `gaps_found`, the `verify_phase_goal` step already presents the gap-closure path (`/gsd:plan-phase {X} --gaps`). No additional routing needed — skip auto-advance.
9
+
10
+ **No-transition check (spawned by auto-advance chain):**
11
+
12
+ Parse `--no-transition` flag from $ARGUMENTS.
13
+
14
+ **If `--no-transition` flag present:**
15
+
16
+ Execute-phase was spawned by plan-phase's auto-advance. Do NOT run transition.md.
17
+ After verification passes and roadmap is updated, return completion status to parent:
18
+
19
+ ```
20
+ ## PHASE COMPLETE
21
+
22
+ Phase: ${PHASE_NUMBER} - ${PHASE_NAME}
23
+ Plans: ${completed_count}/${total_count}
24
+ Verification: {Passed | Gaps Found}
25
+
26
+ [Include aggregate_results output]
27
+ ```
28
+
29
+ STOP. Do not proceed to auto-advance or transition.
30
+
31
+ **If `--no-transition` flag is NOT present:**
32
+
33
+ **Auto-advance detection:**
34
+
35
+ 1. Parse `--auto` flag from $ARGUMENTS
36
+ 2. Read consolidated auto-mode (`active` = chain flag OR user preference; chain flag already synced in init step):
37
+ ```bash
38
+ AUTO_MODE=$(gsd_run query check auto-mode --pick active 2>/dev/null || echo "false")
39
+ ```
40
+
41
+ **If `--auto` flag present OR `AUTO_MODE` is true (AND verification passed with no gaps):**
42
+
43
+ ```
44
+ ╔══════════════════════════════════════════╗
45
+ ║ AUTO-ADVANCING → TRANSITION ║
46
+ ║ Phase {X} verified, continuing chain ║
47
+ ╚══════════════════════════════════════════╝
48
+ ```
49
+
50
+ Execute the transition workflow inline (do NOT use Agent — orchestrator context is ~10-15%, transition needs phase completion data already in context):
51
+
52
+ Read and follow `~/.claude/gsd-core/workflows/transition.md`, passing through the `--auto` flag so it propagates to the next phase invocation.
53
+
54
+ **If neither `--auto` nor `AUTO_MODE` is true:**
55
+
56
+ **STOP. Do not auto-advance. Do not execute transition. Do not plan next phase. Present options to the user and wait.**
57
+
58
+ **IMPORTANT: There is NO `/gsd-transition` command. Never suggest it. The transition workflow is internal only.**
59
+
60
+ Check whether CONTEXT.md already exists for the next phase:
61
+
62
+ ```bash
63
+ ls .planning/phases/*{next}*/{next}-CONTEXT.md 2>/dev/null || echo "no-context"
64
+ ```
65
+
66
+ If CONTEXT.md does **not** exist for the next phase, present:
67
+
68
+ ```
69
+ ## ✓ Phase {X}: {Name} Complete
70
+
71
+ /gsd:progress ${GSD_WS} — see updated roadmap
72
+ /gsd:discuss-phase {next} ${GSD_WS} — start here: discuss next phase before planning ← recommended
73
+ /gsd:plan-phase {next} ${GSD_WS} — plan next phase (skip discuss)
74
+ /gsd:execute-phase {next} ${GSD_WS} — execute next phase (skip discuss and plan)
75
+ ```
76
+
77
+ If CONTEXT.md **exists** for the next phase, present:
78
+
79
+ ```
80
+ ## ✓ Phase {X}: {Name} Complete
81
+
82
+ /gsd:progress ${GSD_WS} — see updated roadmap
83
+ /gsd:plan-phase {next} ${GSD_WS} — start here: plan next phase (CONTEXT.md already present) ← recommended
84
+ /gsd:discuss-phase {next} ${GSD_WS} — re-discuss next phase
85
+ /gsd:execute-phase {next} ${GSD_WS} — execute next phase (skip planning)
86
+ ```
87
+
88
+ Only suggest the commands listed above. Do not invent or hallucinate command names.
@@ -255,6 +255,7 @@ Set via `workflow.*` namespace in config.json (e.g., `"workflow": { "research":
255
255
  | `workflow.auto_advance` | boolean | `false` | `true`, `false` | Auto-advance to next phase after completion |
256
256
  | `workflow.node_repair` | boolean | `true` | `true`, `false` | Attempt automatic repair of failed plan nodes |
257
257
  | `workflow.node_repair_budget` | number | `2` | Any positive integer | Max repair retries per failed node |
258
+ | `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. |
258
259
  | `workflow.ai_integration_phase` | boolean | `true` | `true`, `false` | Run /gsd:ai-integration-phase before planning AI system phases |
259
260
  | `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) |
260
261
  | `workflow.ui_phase` | boolean | `true` | `true`, `false` | Generate UI-SPEC.md for frontend phases |
@@ -395,7 +396,7 @@ Several config fields affect each other or trigger special behavior:
395
396
 
396
397
  8. **`sub_repos` auto-sync** -- On every config load, GSD scans for child directories with `.git` and updates the `sub_repos` array if the filesystem has changed. Legacy `multiRepo: true` is automatically migrated to a detected `sub_repos` array.
397
398
 
398
- 9. **`workflow.use_worktrees` and branch divergence** -- When `use_worktrees` is `true` (default), executor worktrees are forked from `origin/HEAD` by the Claude Code harness. If your current branch has commits that `origin/HEAD` does 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 mismatch` warning. To restore parallel execution permanently, set `worktree.baseRef:"head"` in `.claude/settings.local.json` (run `node gsd-tools.cjs worktree set-baseref`). This makes the harness fork worktrees from the live HEAD instead of `origin/HEAD`. Both fresh installs and upgrades of GSD Core set this automatically (no-clobber) when `use_worktrees` is enabled; you can also run the command manually at any time. Setting `workflow.use_worktrees: false` is the alternative if worktrees are not needed at all.
399
+ 9. **`workflow.use_worktrees` and branch divergence** -- When `use_worktrees` is `true` (default), executor worktrees are forked from `origin/HEAD` -- by the host's own harness on `dispatch.isolation: harness-worktree` runtimes (Claude Code, Cursor), or by GSD itself on `orchestrator-worktree` runtimes (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 that `origin/HEAD` does 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 mismatch` warning. To restore parallel execution permanently, set `worktree.baseRef:"head"` in `.claude/settings.local.json` (run `node gsd-tools.cjs worktree set-baseref`). This makes the harness fork worktrees from the live HEAD instead of `origin/HEAD`. Both fresh installs and upgrades of GSD Core set this automatically (no-clobber) when `use_worktrees` is enabled; you can also run the command manually at any time. Setting `workflow.use_worktrees: false` is the alternative if worktrees are not needed at all.
399
400
 
400
401
  ---
401
402
 
@@ -58,32 +58,39 @@ cannot diverge (`DEFECT.GENERATIVE-FIX`; parity-locked in
58
58
 
59
59
  ## Invocation
60
60
 
61
- For each selected INSTANCE, invoke its base `cli` using the instance's own `model`/`agent` —
62
- NOT the global `review.models.<cli>`. Each instance writes to its OWN per-instance output file
63
- under the run-scoped `{run_dir}` (`RUN_DIR` from `gather_context`, #2358 — never a bare
64
- `{phase}`-keyed `/tmp` path) and runs as a distinct reviewer identity.
61
+ An instance resolves **through** a lane; it is not a lane itself (ADR-2782 D8). It takes no part in
62
+ the roster, the flag set, or lane uniqueness — which is why an instance heading
63
+ (`## OpenCode Review (opencode-deepseek)`) must never be read as a lane section.
65
64
 
66
- For an OpenCode-backed instance (the motivating adapter):
65
+ Since Phase 5b (#2799) `invoke_reviewers` iterates declared lanes rather than hand-authored per-CLI
66
+ blocks, so an instance is invoked through the same single seam as its base lane, with two
67
+ substitutions:
67
68
 
68
69
  ```bash
69
- # $INSTANCE_MODEL / $INSTANCE_AGENT come from the instance spec; $INSTANCE_NAME is the
70
- # reviewer identity (e.g. opencode-deepseek). --agent is OpenCode's native subagent flag;
71
- # omit it when the instance has no agent. {run_dir} is the run-scoped mktemp directory
72
- # created once in gather_context (#2358) — same directory every other reviewer block uses.
73
- if [ -n "$INSTANCE_AGENT" ] && [ "$INSTANCE_AGENT" != "null" ]; then
74
- cat {run_dir}/gsd-review-prompt.md | opencode run --model "$INSTANCE_MODEL" --agent "$INSTANCE_AGENT" - 2>/dev/null > {run_dir}/gsd-review-${INSTANCE_NAME}.md
75
- else
76
- cat {run_dir}/gsd-review-prompt.md | opencode run --model "$INSTANCE_MODEL" - 2>/dev/null > {run_dir}/gsd-review-${INSTANCE_NAME}.md
77
- fi
78
- if [ ! -s {run_dir}/gsd-review-${INSTANCE_NAME}.md ]; then
79
- echo "OpenCode review ($INSTANCE_NAME) failed or returned empty output." > {run_dir}/gsd-review-${INSTANCE_NAME}.md
80
- fi
70
+ # $INSTANCE_NAME is the reviewer identity (e.g. opencode-deepseek); $INSTANCE_MODEL / $INSTANCE_AGENT
71
+ # come from the instance spec. --run-dir is the run-scoped mktemp directory created once in
72
+ # gather_context (#2358) — the same directory every lane uses.
73
+ #
74
+ # The instance's OWN model replaces the lane's configured model, and the output lands under the
75
+ # INSTANCE name so two instances of one adapter never overwrite each other.
76
+ gsd_run query review-lane invoke \
77
+ --slug "$INSTANCE_CLI" \
78
+ --run-dir "$RUN_DIR" --repo-root "$REPO_ROOT" \
79
+ --model "$INSTANCE_MODEL" ${INSTANCE_AGENT:+--agent "$INSTANCE_AGENT"} \
80
+ --as "$INSTANCE_NAME"
81
81
  ```
82
82
 
83
- For an instance backed by a DIFFERENT cli, reuse that cli's invocation block with two
84
- substitutions: use the instance's `model` in place of the global `review.models.<cli>` value,
85
- and write to `{run_dir}/gsd-review-${INSTANCE_NAME}.md`. Only `opencode` honours an
86
- `agent` field in v1; ignore `agent` for other adapters.
83
+ `--as` is what makes the run write `{run_dir}/gsd-review-${INSTANCE_NAME}.md` instead of the lane's
84
+ own `{run_dir}/gsd-review-<slug>.md`.
85
+
86
+ Everything the lane declares — probe, prompt channel, output channel, timeout floor, empty-output
87
+ policy, handler — applies unchanged to an instance. That is the point of routing instances through
88
+ the lane rather than duplicating its invocation: a cross-cutting fix reaches instances for free,
89
+ where the previous per-adapter block had to be copied and kept in sync by hand.
90
+
91
+ Only `opencode` honours an `agent` field in v1; it is ignored by other adapters. `model` and `agent`
92
+ are opaque pass-through strings and are NEVER interpolated into a shell string — the runner spawns
93
+ with an argv array and `shell: false`.
87
94
 
88
95
  ---
89
96
 
@@ -0,0 +1,42 @@
1
+ # Runtime-Aware Subagent Dispatch (epic #2505 Phase 4 / #2508)
2
+
3
+ GSD workflows dispatch specialized subagents by role (planner, executor,
4
+ verifier, …). On **named-dispatch runtimes** (Claude Code, OpenCode, Cursor,
5
+ Cline, … — every runtime whose descriptor declares `hostIntegration.dispatch.namedDispatch: true`), the role name dispatches the named subagent directly.
6
+
7
+ On **built-in-only runtimes** (kimi-code — three built-in subagents only:
8
+ `coder`, `explore`, `plan`; no custom registration per
9
+ `moonshotai.github.io/kimi-code/en/customization/agents`), a GSD role name is
10
+ unknown and the dispatch must use the closest built-in.
11
+
12
+ ## Resolution
13
+
14
+ Before dispatching a subagent by role, resolve the type for the current runtime
15
+ via the `resolve-dispatch-type` query. Pass the requested role name; the query
16
+ returns the name unchanged on named-dispatch runtimes and maps to the closest
17
+ built-in (`coder`/`explore`/`plan`) on kimi-code. The `|| echo` fallback
18
+ preserves named-dispatch behavior on older GSD installs that lack the query.
19
+
20
+ The persona rides `${AGENT_SKILLS_<ROLE>}` (Phase 3 / #2510) regardless of the
21
+ resolved type — on non-Claude runtimes with no `agent_skills` config,
22
+ `gsd-tools query agent-skills <role>` returns the installed agent prompt as
23
+ the block. So a coder dispatch with the planner persona injected gives kimi-code
24
+ the planner's behavior in the coder built-in's process.
25
+
26
+ ## Suffix → built-in map
27
+
28
+ | Agent role suffix | Built-in | Rationale |
29
+ |---|---|---|
30
+ | `-planner`, `-roadmapper`, `-selector`, `-spec` | `plan` | Plans/designs; no file writes |
31
+ | `-researcher`, `-mapper`, `-checker`, `-verifier`, `-auditor`, `-analyzer`, `-synthesizer`, `-profiler`, `-curator`, `-classifier`, `-reviewer` | `explore` | Read-only investigation |
32
+ | everything else (`-executor`, `-fixer`, `-writer`, `-debugger`, …) | `coder` | General-purpose with full tool set |
33
+ | `general-purpose`, `general`, `default`, `sonnet`, `opus`, `haiku` | `coder` | Already-generic names |
34
+
35
+ ## Why not a hook?
36
+
37
+ Kimi Code's documented PreToolUse hook API
38
+ (`moonshotai.github.io/kimi-code/en/customization/hooks`) supports only
39
+ `permissionDecision: allow|deny` on blockable events — it cannot rewrite the
40
+ dispatch payload's role field in flight. A PreToolUse-remap hook (the epic's
41
+ original "Option B") is therefore infeasible; this per-dispatch resolution
42
+ (Option A) is the documented-API-correct path.
@@ -30,8 +30,8 @@ bloating this closed core.
30
30
  | id | name | applies to element kinds | consideration question |
31
31
  |----|------|--------------------------|------------------------|
32
32
  | empty | Empty / no data | form, list-collection, media | What is shown when there is no data — zero items, an unfilled form, or absent media? |
33
- | loading | Loading / in-flight | form, list-collection, media, nav | What is shown while data or content is still loading (skeleton, spinner, progressive reveal)? |
34
- | error | Error / failure | form, list-collection, media, nav | What is shown when the load or submit fails (message, retry affordance, partial fallback)? |
33
+ | loading | Loading / in-flight | form, list-collection, media, nav, interactive-control | What is shown while data or content is still loading (skeleton, spinner, progressive reveal)? |
34
+ | error | Error / failure | form, list-collection, media, nav, interactive-control | What is shown when the load or submit fails (message, retry affordance, partial fallback)? |
35
35
  | populated | Populated / happy path | list-collection, media | What does the normal populated (happy-path) state look like at a typical volume of content? |
36
36
  | partial | Partial / incomplete | form, list-collection | What is shown for partial or incomplete data — some fields or rows present, others missing? |
37
37
  | overflow | Overflow / truncation | list-collection, nav, static-content | What happens when content exceeds its container — scroll, clip, wrap, or truncate? |
@@ -17,7 +17,7 @@ did not create (#48).
17
17
  <worktree_branch_check>
18
18
  FIRST ACTION: HEAD assertion MUST run before anything else, and this block is
19
19
  VERIFY-ONLY. Worktrees spawned by Claude Code's `isolation="worktree"` use the
20
- `worktree-agent-<id>` namespace. The orchestrator owns this worktree's lifecycle;
20
+ `agent-<id>` namespace (previously `worktree-agent-<id>`; both are accepted). The orchestrator owns this worktree's lifecycle;
21
21
  a sub-agent MUST NOT hold state-correction primitives (hard-reset, update-ref,
22
22
  force-move, index-discard) on a worktree it did not create (#48, #2924). If ANY
23
23
  assertion below fails, HALT immediately — print the FATAL line, `exit 42`, and let
@@ -27,11 +27,11 @@ commit.
27
27
  HEAD_REF=$(git symbolic-ref --quiet HEAD || echo "DETACHED")
28
28
  ACTUAL_BRANCH=$(git rev-parse --abbrev-ref HEAD)
29
29
  if [ "$HEAD_REF" = "DETACHED" ] || echo "$ACTUAL_BRANCH" | grep -Eq '^(main|master|develop|trunk|release/.*)$'; then
30
- echo "FATAL: worktree HEAD on '$ACTUAL_BRANCH' (expected worktree-agent-*); refusing to commit or self-recover via 'git update-ref' (#2924)." >&2
30
+ echo "FATAL: worktree HEAD on '$ACTUAL_BRANCH' (expected agent-* or worktree-agent-*); refusing to commit or self-recover via 'git update-ref' (#2924)." >&2
31
31
  exit 42
32
32
  fi
33
- if ! echo "$ACTUAL_BRANCH" | grep -Eq '^worktree-agent-[A-Za-z0-9._/-]+$'; then
34
- echo "FATAL: worktree HEAD '$ACTUAL_BRANCH' is not in the worktree-agent-* namespace; refusing to commit (#2924)." >&2
33
+ if ! echo "$ACTUAL_BRANCH" | grep -Eq '^(worktree-)?agent-[A-Za-z0-9._/-]+$'; then
34
+ echo "FATAL: worktree HEAD '$ACTUAL_BRANCH' is not in the agent-* / worktree-agent-* namespace; refusing to commit (#2924)." >&2
35
35
  exit 42
36
36
  fi
37
37
  ACTUAL_BASE=$(git rev-parse HEAD)
@@ -6,6 +6,10 @@ tags: [searchable tech]
6
6
  provides:
7
7
  - [bullet list of what was built/delivered]
8
8
  affects: [list of phase names or keywords]
9
+ actuals:
10
+ tokens: [chars/4 over files actually changed]
11
+ tasks: [tasks completed]
12
+ commits: [commits made]
9
13
  tech-stack:
10
14
  added: [libraries/tools]
11
15
  patterns: [architectural/code patterns]
@@ -6,6 +6,10 @@ tags: [searchable tech]
6
6
  provides:
7
7
  - [bullet list of what was built/delivered]
8
8
  affects: [list of phase names or keywords]
9
+ actuals:
10
+ tokens: [chars/4 over files actually changed]
11
+ tasks: [tasks completed]
12
+ commits: [commits made]
9
13
  tech-stack:
10
14
  added: [libraries/tools]
11
15
  patterns: [architectural/code patterns]
@@ -21,6 +21,13 @@ provides:
21
21
  - [bullet list of what this phase built/delivered]
22
22
  affects: [list of phase names or keywords that will need this context]
23
23
 
24
+ # Actuals (#2632) — pairs with the plan's `estimate` to calibrate future estimates.
25
+ # Same estimateTokens scale (chars/4 over the realized diff), never a harness token count.
26
+ actuals:
27
+ tokens: [chars/4 over files actually changed]
28
+ tasks: [tasks completed]
29
+ commits: [commits made]
30
+
24
31
  # Tech tracking
25
32
  tech-stack:
26
33
  added: [libraries/tools added in this phase]
@@ -33,10 +33,10 @@ Parse JSON for: `phase_dir`, `phase_number`, `phase_name`, `phase_slug`, `padded
33
33
 
34
34
  Resolve agent models:
35
35
  ```bash
36
- SELECTOR_MODEL=$(gsd_run query resolve-model gsd-framework-selector 2>/dev/null | jq -r '.model' 2>/dev/null || true)
37
- RESEARCHER_MODEL=$(gsd_run query resolve-model gsd-ai-researcher 2>/dev/null | jq -r '.model' 2>/dev/null || true)
38
- DOMAIN_MODEL=$(gsd_run query resolve-model gsd-domain-researcher 2>/dev/null | jq -r '.model' 2>/dev/null || true)
39
- PLANNER_MODEL=$(gsd_run query resolve-model gsd-eval-planner 2>/dev/null | jq -r '.model' 2>/dev/null || true)
36
+ SELECTOR_MODEL=$(gsd_run query resolve-model gsd-framework-selector --pick model 2>/dev/null || true)
37
+ RESEARCHER_MODEL=$(gsd_run query resolve-model gsd-ai-researcher --pick model 2>/dev/null || true)
38
+ DOMAIN_MODEL=$(gsd_run query resolve-model gsd-domain-researcher --pick model 2>/dev/null || true)
39
+ PLANNER_MODEL=$(gsd_run query resolve-model gsd-eval-planner --pick model 2>/dev/null || true)
40
40
  ```
41
41
 
42
42
  Check config:
@@ -94,6 +94,10 @@ final output — do not proceed to fixing.
94
94
  <step name="fix-loop">
95
95
  For each **auto-fixable** finding (up to `--max`, ordered by severity desc):
96
96
 
97
+ <!-- #2508 runtime-aware-dispatch -->
98
+
99
+ > **Runtime-aware dispatch (#2508 Phase 4).** GSD workflows dispatch specialized subagents by role. Before dispatching on a built-in-only runtime (kimi-code — three built-ins only), resolve the role to a built-in via `gsd_run query resolve-dispatch-type --requested <role> --raw`. On named-dispatch runtimes (Claude/OpenCode/…) the role is returned unchanged; on kimi-code it maps to `coder`/`explore`/`plan` by role-suffix. The persona rides `${AGENT_SKILLS_<ROLE>}` (Phase 3) regardless. See @gsd-core/references/runtime-aware-dispatch.md.
100
+
97
101
  **a. Spawn executor agent** (runs in a subagent — no output until it returns, ~1–5 min; expected, not a freeze)**:**
98
102
  ```
99
103
  Agent(
@@ -69,6 +69,10 @@ Extract `MILESTONE_REQ_IDS` from REQUIREMENTS.md traceability table — all REQ-
69
69
 
70
70
  Print: "Spawning integration checker (runs in a subagent — no output until it returns, ~1–5 min; expected, not a freeze)"
71
71
 
72
+ <!-- #2517 model-omit-on-inherit -->
73
+
74
+ > **Model omission (#2517).** Omit the `model` parameter entirely when the value it would carry (`integration_checker_model`) is `"inherit"` or empty. An empty value 404s on runtimes without native tier aliases — the default on non-Claude runtimes. Omitting it inherits the orchestrator's model. See @gsd-core/references/model-profile-resolution.md.
75
+
72
76
  ```
73
77
  Agent(
74
78
  prompt="Check cross-phase integration and E2E flows.
@@ -82,6 +86,10 @@ Milestone Requirements:
82
86
 
83
87
  MUST map each integration finding to affected requirement IDs where applicable.
84
88
 
89
+ <!-- #2508 runtime-aware-dispatch -->
90
+
91
+ > **Runtime-aware dispatch (#2508 Phase 4).** GSD workflows dispatch specialized subagents by role. Before dispatching on a built-in-only runtime (kimi-code — three built-ins only), resolve the role to a built-in via `gsd_run query resolve-dispatch-type --requested <role> --raw`. On named-dispatch runtimes (Claude/OpenCode/…) the role is returned unchanged; on kimi-code it maps to `coder`/`explore`/`plan` by role-suffix. The persona rides `${AGENT_SKILLS_<ROLE>}` (Phase 3) regardless. See @gsd-core/references/runtime-aware-dispatch.md.
92
+
85
93
  Verify cross-phase wiring and E2E user flows.
86
94
  ${AGENT_SKILLS_CHECKER}",
87
95
  subagent_type="gsd-integration-checker",
@@ -44,19 +44,6 @@ PLAN_STRATEGY="local"
44
44
  if echo "$ARGUMENTS" | grep -qE '(^|[[:space:]])\-\-(converge|cross-ai)([[:space:]]|$)'; then
45
45
  PLAN_STRATEGY="converge"
46
46
  fi
47
-
48
- CONVERGENCE_ARGS=""
49
- for REVIEW_FLAG in --codex --gemini --claude --opencode --ollama --lm-studio --llama-cpp --all --text; do
50
- if echo "$ARGUMENTS" | grep -qE "(^|[[:space:]])${REVIEW_FLAG}([[:space:]]|$)"; then
51
- CONVERGENCE_ARGS="${CONVERGENCE_ARGS} ${REVIEW_FLAG}"
52
- fi
53
- done
54
-
55
- MAX_CYCLES_ARG=""
56
- if echo "$ARGUMENTS" | grep -qE '\-\-max-cycles\s+[0-9]+'; then
57
- MAX_CYCLES_ARG=$(echo "$ARGUMENTS" | grep -oE '\-\-max-cycles\s+[0-9]+' | awk '{print $2}')
58
- CONVERGENCE_ARGS="${CONVERGENCE_ARGS} --max-cycles ${MAX_CYCLES_ARG}"
59
- fi
60
47
  ```
61
48
 
62
49
  When `--only` is set, also set `FROM_PHASE` to the same value so existing filter logic applies.
@@ -76,6 +63,23 @@ if [[ "$INIT" == @file:* ]]; then INIT=$(cat "${INIT#@file:}"); fi
76
63
  If `PLAN_STRATEGY` is `converge`, fail fast unless the existing convergence feature gate is enabled:
77
64
 
78
65
  ```bash
66
+ # Lane flags derived from the declared roster (#2800/#2272); --all and --text are convergence
67
+ # controls, not reviewer lanes, so they stay literal.
68
+ # This block must stay AFTER the launcher preamble (above) because it calls `gsd_run` —
69
+ # do not move it back above the preamble in a future edit.
70
+ CONVERGENCE_ARGS=""
71
+ for REVIEW_FLAG in $(gsd_run review-lane flags) --all --text; do
72
+ if echo "$ARGUMENTS" | grep -qE "(^|[[:space:]])${REVIEW_FLAG}([[:space:]]|$)"; then
73
+ CONVERGENCE_ARGS="${CONVERGENCE_ARGS} ${REVIEW_FLAG}"
74
+ fi
75
+ done
76
+
77
+ MAX_CYCLES_ARG=""
78
+ if echo "$ARGUMENTS" | grep -qE '\-\-max-cycles\s+[0-9]+'; then
79
+ MAX_CYCLES_ARG=$(echo "$ARGUMENTS" | grep -oE '\-\-max-cycles\s+[0-9]+' | awk '{print $2}')
80
+ CONVERGENCE_ARGS="${CONVERGENCE_ARGS} --max-cycles ${MAX_CYCLES_ARG}"
81
+ fi
82
+
79
83
  if [ "$PLAN_STRATEGY" = "converge" ]; then
80
84
  CONVERGENCE_ENABLED=$(gsd_run query config-get workflow.plan_review_convergence 2>/dev/null || echo "false")
81
85
  if [ "$CONVERGENCE_ENABLED" != "true" ]; then
@@ -490,7 +494,7 @@ Skill(skill="gsd-code-review", args="${PHASE_NUM} --fix --auto")
490
494
  After execute, read canonical verification:
491
495
 
492
496
  ```bash
493
- VERIFY_STATUS=$(gsd_run query verification.status "${PHASE_DIR}" 2>/dev/null | jq -r '.status//empty')
497
+ VERIFY_STATUS=$(gsd_run query verification.status "${PHASE_DIR}" --pick status 2>/dev/null || true)
494
498
  ```
495
499
 
496
500
  If `PHASE_DIR` is absent, re-fetch `init.phase-op ${PHASE_NUM}` and parse `phase_dir`.
@@ -548,7 +552,7 @@ Skill(skill="gsd-execute-phase", args="${PHASE_NUM} --no-transition")
548
552
 
549
553
  Re-read verification status:
550
554
  ```bash
551
- VERIFY_STATUS=$(gsd_run query verification.status "${PHASE_DIR}" 2>/dev/null | jq -r '.status//empty')
555
+ VERIFY_STATUS=$(gsd_run query verification.status "${PHASE_DIR}" --pick status 2>/dev/null || true)
552
556
  ```
553
557
 
554
558
  If `passed` or `human_needed`: route normally.
@@ -156,7 +156,7 @@ Re-run `init todos` to get updated count, then update STATE.md "### Pending Todo
156
156
  </step>
157
157
 
158
158
  <step name="git_commit">
159
- If todo was moved to done/, commit the change:
159
+ If todo was moved to completed/, commit the change:
160
160
 
161
161
  ```bash
162
162
  git rm --cached .planning/todos/pending/[filename] 2>/dev/null || true
@@ -178,5 +178,5 @@ Confirm: "Committed: docs: start work on todo - [title]"
178
178
  - [ ] Appropriate actions offered
179
179
  - [ ] Selected action executed
180
180
  - [ ] STATE.md updated if todo count changed
181
- - [ ] Changes committed to git (if todo moved to done/)
181
+ - [ ] Changes committed to git (if todo moved to completed/)
182
182
  </success_criteria>
@@ -118,7 +118,7 @@ Parse REVIEW.md frontmatter to check status and extract context for --auto loop:
118
118
  REVIEW_STATUS=$(REVIEW_PATH="${REVIEW_PATH}" node -e "
119
119
  const fs = require('fs');
120
120
  const content = fs.readFileSync(process.env.REVIEW_PATH, 'utf-8');
121
- const match = content.match(/^---\n([\s\S]*?)\n---/);
121
+ const match = content.replace(/\r\n/g, '\n').match(/^---\n([\s\S]*?)\n---/);
122
122
  if (match && /status:\s*(\S+)/.test(match[1])) {
123
123
  console.log(match[1].match(/status:\s*(\S+)/)[1]);
124
124
  } else {
@@ -144,7 +144,7 @@ Extract review depth for --auto re-review:
144
144
  REVIEW_DEPTH=$(REVIEW_PATH="${REVIEW_PATH}" node -e "
145
145
  const fs = require('fs');
146
146
  const content = fs.readFileSync(process.env.REVIEW_PATH, 'utf-8');
147
- const match = content.match(/^---\n([\s\S]*?)\n---/);
147
+ const match = content.replace(/\r\n/g, '\n').match(/^---\n([\s\S]*?)\n---/);
148
148
  if (match && /depth:\s*(\S+)/.test(match[1])) {
149
149
  console.log(match[1].match(/depth:\s*(\S+)/)[1]);
150
150
  } else {
@@ -163,7 +163,7 @@ while IFS= read -r line; do
163
163
  done < <(REVIEW_PATH="${REVIEW_PATH}" node -e "
164
164
  const fs = require('fs');
165
165
  const content = fs.readFileSync(process.env.REVIEW_PATH, 'utf-8');
166
- const match = content.match(/^---\n([\s\S]*?)\n---/);
166
+ const match = content.replace(/\r\n/g, '\n').match(/^---\n([\s\S]*?)\n---/);
167
167
  if (match) {
168
168
  const fm = match[1];
169
169
  // Try YAML array format: files_reviewed_list: [file1, file2]
@@ -197,6 +197,14 @@ echo "Fix scope: ${FIX_SCOPE}"
197
197
 
198
198
  Use Agent() to spawn agent:
199
199
 
200
+ <!-- #2508 runtime-aware-dispatch -->
201
+
202
+ > **Runtime-aware dispatch (#2508 Phase 4).** GSD workflows dispatch specialized subagents by role. Before dispatching on a built-in-only runtime (kimi-code — three built-ins only), resolve the role to a built-in via `gsd_run query resolve-dispatch-type --requested <role> --raw`. On named-dispatch runtimes (Claude/OpenCode/…) the role is returned unchanged; on kimi-code it maps to `coder`/`explore`/`plan` by role-suffix. The persona rides `${AGENT_SKILLS_<ROLE>}` (Phase 3) regardless. See @gsd-core/references/runtime-aware-dispatch.md.
203
+
204
+ <!-- #2517 model-omit-on-inherit -->
205
+
206
+ > **Model omission (#2517).** Omit the `model` parameter entirely when the value it would carry (`FIXER_MODEL`, `REVIEWER_MODEL`) is `"inherit"` or empty. An empty value 404s on runtimes without native tier aliases — the default on non-Claude runtimes. Omitting it inherits the orchestrator's model. See @gsd-core/references/model-profile-resolution.md.
207
+
200
208
  ```text
201
209
  Agent(subagent_type="gsd-code-fixer", model="{FIXER_MODEL}", prompt="
202
210
  <files_to_read>
@@ -298,7 +306,7 @@ ${AGENT_SKILLS_REVIEWER}")
298
306
  NEW_STATUS=$(REVIEW_PATH="${REVIEW_PATH}" node -e "
299
307
  const fs = require('fs');
300
308
  const content = fs.readFileSync(process.env.REVIEW_PATH, 'utf-8');
301
- const match = content.match(/^---\n([\s\S]*?)\n---/);
309
+ const match = content.replace(/\r\n/g, '\n').match(/^---\n([\s\S]*?)\n---/);
302
310
  if (match && /status:\s*(\S+)/.test(match[1])) {
303
311
  console.log(match[1].match(/status:\s*(\S+)/)[1]);
304
312
  } else {
@@ -364,7 +372,7 @@ if [ -f "${FIX_REPORT_PATH}" ]; then
364
372
  HAS_STATUS=$(REVIEW_PATH="${REVIEW_PATH}" node -e "
365
373
  const fs = require('fs');
366
374
  const content = fs.readFileSync(process.env.FIX_REPORT_PATH, 'utf-8');
367
- const match = content.match(/^---\n([\s\S]*?)\n---/);
375
+ const match = content.replace(/\r\n/g, '\n').match(/^---\n([\s\S]*?)\n---/);
368
376
  if (match && /status:/.test(match[1])) { console.log('valid'); } else { console.log('invalid'); }
369
377
  " 2>/dev/null)
370
378
 
@@ -421,7 +429,7 @@ Extract frontmatter fields:
421
429
  FIX_FRONTMATTER=$(REVIEW_PATH="${REVIEW_PATH}" node -e "
422
430
  const fs = require('fs');
423
431
  const content = fs.readFileSync(process.env.FIX_REPORT_PATH, 'utf-8');
424
- const match = content.match(/^---\n([\s\S]*?)\n---/);
432
+ const match = content.replace(/\r\n/g, '\n').match(/^---\n([\s\S]*?)\n---/);
425
433
  if (match) process.stdout.write(match[1]);
426
434
  " 2>/dev/null)
427
435