@opengsd/gsd-core 1.18.0 → 1.20.0

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 (308) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +2 -1
  3. package/.opencode/plugins/gsd-core.js +2 -3
  4. package/agents/gsd-code-fixer.compact.md +7 -7
  5. package/agents/gsd-code-fixer.md +13 -7
  6. package/agents/gsd-code-reviewer.compact.md +49 -8
  7. package/agents/gsd-code-reviewer.md +53 -14
  8. package/agents/gsd-executor.md +8 -4
  9. package/agents/gsd-verifier.md +1 -1
  10. package/bin/install.js +422 -103
  11. package/commands/gsd/update.md +5 -2
  12. package/gsd-core/bin/gsd-tools.cjs +179 -30
  13. package/gsd-core/bin/lib/agent-install-check.cjs +29 -3
  14. package/gsd-core/bin/lib/args-terminator.cjs +114 -0
  15. package/gsd-core/bin/lib/broken-windows.cjs +59 -1
  16. package/gsd-core/bin/lib/capability-lifecycle.cjs +1 -12
  17. package/gsd-core/bin/lib/capability-lock.cjs +13 -2
  18. package/gsd-core/bin/lib/capability-registry.cjs +376 -103
  19. package/gsd-core/bin/lib/capability-state.cjs +14 -8
  20. package/gsd-core/bin/lib/capability-validator.cjs +32 -0
  21. package/gsd-core/bin/lib/commands.cjs +27 -17
  22. package/gsd-core/bin/lib/config-loader.cjs +13 -3
  23. package/gsd-core/bin/lib/config.cjs +256 -58
  24. package/gsd-core/bin/lib/core-utils.cjs +45 -0
  25. package/gsd-core/bin/lib/decision-coverage-support.cjs +74 -0
  26. package/gsd-core/bin/lib/frontmatter.cjs +21 -3
  27. package/gsd-core/bin/lib/gate-api-coverage-verify-pre.cjs +2 -2
  28. package/gsd-core/bin/lib/gate-context-drift.cjs +9 -4
  29. package/gsd-core/bin/lib/gate-decision-coverage-plan.cjs +6 -1
  30. package/gsd-core/bin/lib/gate-decision-coverage-verify.cjs +6 -1
  31. package/gsd-core/bin/lib/gate-evaluation-scope.cjs +134 -13
  32. package/gsd-core/bin/lib/gate-phase-context.cjs +7 -8
  33. package/gsd-core/bin/lib/gate-schema-drift.cjs +1 -1
  34. package/gsd-core/bin/lib/gate-tdd-review-checkpoint.cjs +63 -22
  35. package/gsd-core/bin/lib/gate-ui-safety.cjs +23 -5
  36. package/gsd-core/bin/lib/git-info-exclude.cjs +52 -0
  37. package/gsd-core/bin/lib/health-diagnostic-rules/phase-structure.cjs +31 -12
  38. package/gsd-core/bin/lib/health-diagnostic-rules/roadmap-disk-consistency.cjs +9 -7
  39. package/gsd-core/bin/lib/health-diagnostic-rules/state-consistency.cjs +3 -3
  40. package/gsd-core/bin/lib/health-diagnostic-types.cjs +4 -0
  41. package/gsd-core/bin/lib/health-diagnostic.cjs +52 -1
  42. package/gsd-core/bin/lib/host-integration.cjs +13 -6
  43. package/gsd-core/bin/lib/init.cjs +245 -68
  44. package/gsd-core/bin/lib/install-engine.cjs +246 -32
  45. package/gsd-core/bin/lib/install-profiles.cjs +9 -4
  46. package/gsd-core/bin/lib/installer-migration-report.cjs +2 -0
  47. package/gsd-core/bin/lib/io.cjs +6 -0
  48. package/gsd-core/bin/lib/markdown-line-diff.cjs +147 -0
  49. package/gsd-core/bin/lib/markdown-sectionizer.cjs +147 -1
  50. package/gsd-core/bin/lib/markdown-table.cjs +23 -0
  51. package/gsd-core/bin/lib/mcp-server.cjs +8 -5
  52. package/gsd-core/bin/lib/milestone-lock.cjs +8 -1
  53. package/gsd-core/bin/lib/milestone-release-map.cjs +244 -0
  54. package/gsd-core/bin/lib/milestone-release-tags.cjs +440 -0
  55. package/gsd-core/bin/lib/milestone.cjs +61 -51
  56. package/gsd-core/bin/lib/model-catalog.cjs +8 -0
  57. package/gsd-core/bin/lib/normalize-test-command.cjs +67 -0
  58. package/gsd-core/bin/lib/phase-entry-grammar.cjs +119 -0
  59. package/gsd-core/bin/lib/phase-id-display.cjs +2 -0
  60. package/gsd-core/bin/lib/phase-id.cjs +629 -38
  61. package/gsd-core/bin/lib/phase-lifecycle.cjs +12 -44
  62. package/gsd-core/bin/lib/phase-locator.cjs +328 -19
  63. package/gsd-core/bin/lib/phase.cjs +3066 -653
  64. package/gsd-core/bin/lib/planning-document.cjs +26 -5
  65. package/gsd-core/bin/lib/planning-inspect.cjs +27 -35
  66. package/gsd-core/bin/lib/planning-snapshot.cjs +15 -8
  67. package/gsd-core/bin/lib/planning-workspace.cjs +21 -3
  68. package/gsd-core/bin/lib/pristine-baseline.cjs +11 -3
  69. package/gsd-core/bin/lib/read-only-git-env.cjs +79 -0
  70. package/gsd-core/bin/lib/refactor-trigger-command-router.cjs +2 -2
  71. package/gsd-core/bin/lib/review-lane-descriptor.cjs +57 -2
  72. package/gsd-core/bin/lib/review-lane-invocation.cjs +125 -18
  73. package/gsd-core/bin/lib/review-lane-runner.cjs +33 -4
  74. package/gsd-core/bin/lib/reviewer-step-dispatch.cjs +11 -9
  75. package/gsd-core/bin/lib/roadmap-parser.cjs +735 -137
  76. package/gsd-core/bin/lib/roadmap-progress.cjs +136 -0
  77. package/gsd-core/bin/lib/roadmap-upgrade.cjs +47 -7
  78. package/gsd-core/bin/lib/roadmap.cjs +383 -123
  79. package/gsd-core/bin/lib/runtime-artifact-conversion.cjs +381 -14
  80. package/gsd-core/bin/lib/runtime-artifact-layout.cjs +8 -4
  81. package/gsd-core/bin/lib/runtime-hooks-surface.cjs +60 -16
  82. package/gsd-core/bin/lib/shell-command-projection.cjs +151 -19
  83. package/gsd-core/bin/lib/smart-entry.cjs +35 -11
  84. package/gsd-core/bin/lib/state-contract.cjs +39 -15
  85. package/gsd-core/bin/lib/state-document.cjs +344 -27
  86. package/gsd-core/bin/lib/state-transition.cjs +156 -32
  87. package/gsd-core/bin/lib/state.cjs +527 -186
  88. package/gsd-core/bin/lib/surface.cjs +9 -0
  89. package/gsd-core/bin/lib/task-command-router.cjs +5 -25
  90. package/gsd-core/bin/lib/task-content-resolution.cjs +46 -1
  91. package/gsd-core/bin/lib/template.cjs +2 -2
  92. package/gsd-core/bin/lib/validate.cjs +3 -2
  93. package/gsd-core/bin/lib/vendor/re2js.cjs +863 -577
  94. package/gsd-core/bin/lib/vendor/re2js.d.cts +284 -219
  95. package/gsd-core/bin/lib/verification.cjs +96 -9
  96. package/gsd-core/bin/lib/verify.cjs +4 -2
  97. package/gsd-core/bin/lib/workstream-inventory.cjs +26 -20
  98. package/gsd-core/bin/lib/worktree-safety.cjs +22 -1
  99. package/gsd-core/bin/shared/config-defaults.manifest.json +1 -0
  100. package/gsd-core/bin/shared/config-schema.manifest.json +1 -0
  101. package/gsd-core/bin/verify-reapply-patches.cjs +411 -10
  102. package/gsd-core/references/cicd-review-patterns.md +45 -0
  103. package/gsd-core/references/git-integration.md +11 -0
  104. package/gsd-core/references/loop-hook-dispatch.md +13 -6
  105. package/gsd-core/references/necessity-review.md +48 -0
  106. package/gsd-core/references/planning-config.md +2 -0
  107. package/gsd-core/references/tdd.md +2 -2
  108. package/gsd-core/templates/DEBUG.md +16 -0
  109. package/gsd-core/templates/config.json +2 -1
  110. package/gsd-core/templates/verification-report.md +1 -1
  111. package/gsd-core/workflows/audit-fix.md +2 -1
  112. package/gsd-core/workflows/code-review/steps/structural-pre-pass.md +10 -1
  113. package/gsd-core/workflows/code-review-fix.md +46 -5
  114. package/gsd-core/workflows/code-review.md +64 -7
  115. package/gsd-core/workflows/complete-milestone/steps/project-md-evolution-review.md +145 -0
  116. package/gsd-core/workflows/complete-milestone/steps/release-map.md +80 -0
  117. package/gsd-core/workflows/complete-milestone.md +37 -150
  118. package/gsd-core/workflows/diagnose-issues.md +29 -5
  119. package/gsd-core/workflows/discuss-phase/modes/power.md +1 -1
  120. package/gsd-core/workflows/discuss-phase-assumptions.md +33 -2
  121. package/gsd-core/workflows/discuss-phase-power.md +19 -2
  122. package/gsd-core/workflows/discuss-phase.md +6 -10
  123. package/gsd-core/workflows/execute-phase/steps/per-plan-worktree-gate.md +13 -7
  124. package/gsd-core/workflows/execute-phase/steps/post-merge-gate.md +7 -2
  125. package/gsd-core/workflows/execute-phase/steps/regression-gate-run.md +2 -1
  126. package/gsd-core/workflows/execute-phase/steps/verify-phase-goal.md +2 -2
  127. package/gsd-core/workflows/execute-phase/steps/wave-post-gate-hooks.md +11 -2
  128. package/gsd-core/workflows/execute-phase.md +10 -6
  129. package/gsd-core/workflows/execute-plan.md +1 -1
  130. package/gsd-core/workflows/health.md +2 -1
  131. package/gsd-core/workflows/import.md +1 -1
  132. package/gsd-core/workflows/plan-phase/steps/chunked-planning-mode.md +2 -2
  133. package/gsd-core/workflows/plan-phase/steps/stall-detection-helpers.md +13 -9
  134. package/gsd-core/workflows/plan-phase.md +14 -14
  135. package/gsd-core/workflows/pr-branch.md +2 -0
  136. package/gsd-core/workflows/quick/steps/plan-checker-loop.md +1 -1
  137. package/gsd-core/workflows/quick/steps/worktree-pre-dispatch-commit.md +8 -1
  138. package/gsd-core/workflows/quick-batch/steps/plan-checker-loop.md +2 -1
  139. package/gsd-core/workflows/quick.md +1 -1
  140. package/gsd-core/workflows/reapply-patches.md +104 -0
  141. package/gsd-core/workflows/review.md +70 -154
  142. package/gsd-core/workflows/section-manifest.json +5 -0
  143. package/gsd-core/workflows/secure-phase.md +1 -1
  144. package/gsd-core/workflows/settings-advanced.md +57 -9
  145. package/gsd-core/workflows/settings.md +1 -0
  146. package/gsd-core/workflows/ship.md +15 -4
  147. package/gsd-core/workflows/spec-phase.md +3 -6
  148. package/gsd-core/workflows/verify-work.md +4 -3
  149. package/hooks/dist/gsd-agent-isolation-guard.js +11 -1
  150. package/hooks/dist/gsd-cursor-subagent-start.js +6 -2
  151. package/hooks/dist/gsd-graphify-update.sh +6 -0
  152. package/hooks/dist/gsd-path-export.js +95 -0
  153. package/hooks/dist/gsd-secret-read-guard.js +172 -39
  154. package/hooks/dist/gsd-statusline.js +37 -3
  155. package/hooks/dist/gsd-windsurf-pre-write.js +29 -2
  156. package/hooks/dist/gsd-workflow-guard.js +3 -2
  157. package/hooks/dist/gsd-worktree-path-guard.js +29 -2
  158. package/hooks/dist/lib/git-probe.js +16 -0
  159. package/hooks/dist/lib/isolation-sentinel.js +44 -0
  160. package/hooks/dist/managed-hooks-registry.cjs +1 -0
  161. package/hooks/gsd-agent-isolation-guard.js +11 -1
  162. package/hooks/gsd-cursor-subagent-start.js +6 -2
  163. package/hooks/gsd-graphify-update.sh +6 -0
  164. package/hooks/gsd-path-export.js +95 -0
  165. package/hooks/gsd-secret-read-guard.js +172 -39
  166. package/hooks/gsd-statusline.js +37 -3
  167. package/hooks/gsd-windsurf-pre-write.js +29 -2
  168. package/hooks/gsd-workflow-guard.js +3 -2
  169. package/hooks/gsd-worktree-path-guard.js +29 -2
  170. package/hooks/hooks.json +1 -0
  171. package/hooks/lib/git-probe.js +16 -0
  172. package/hooks/lib/isolation-sentinel.js +44 -0
  173. package/hooks/managed-hooks-registry.cjs +1 -0
  174. package/package.json +15 -8
  175. package/pi/gsd.cjs +1047 -10
  176. package/pi/package-agents/gsd-advisor-researcher.md +113 -0
  177. package/pi/package-agents/gsd-ai-researcher.md +111 -0
  178. package/pi/package-agents/gsd-assumptions-analyzer.md +110 -0
  179. package/pi/package-agents/gsd-code-fixer.md +774 -0
  180. package/pi/package-agents/gsd-code-reviewer.md +441 -0
  181. package/pi/package-agents/gsd-codebase-mapper.md +850 -0
  182. package/pi/package-agents/gsd-debug-session-manager.md +416 -0
  183. package/pi/package-agents/gsd-debugger.md +1274 -0
  184. package/pi/package-agents/gsd-doc-classifier.md +269 -0
  185. package/pi/package-agents/gsd-doc-synthesizer.md +259 -0
  186. package/pi/package-agents/gsd-doc-verifier.md +212 -0
  187. package/pi/package-agents/gsd-doc-writer.md +626 -0
  188. package/pi/package-agents/gsd-dom-verifier.md +164 -0
  189. package/pi/package-agents/gsd-domain-researcher.md +144 -0
  190. package/pi/package-agents/gsd-eval-auditor.md +185 -0
  191. package/pi/package-agents/gsd-eval-planner.md +149 -0
  192. package/pi/package-agents/gsd-executor.md +898 -0
  193. package/pi/package-agents/gsd-framework-selector.md +159 -0
  194. package/pi/package-agents/gsd-integration-checker.md +476 -0
  195. package/pi/package-agents/gsd-intel-updater.md +338 -0
  196. package/pi/package-agents/gsd-mempalace-curator.md +49 -0
  197. package/pi/package-agents/gsd-nyquist-auditor.md +200 -0
  198. package/pi/package-agents/gsd-pattern-mapper.md +340 -0
  199. package/pi/package-agents/gsd-phase-researcher.md +897 -0
  200. package/pi/package-agents/gsd-plan-checker.md +1077 -0
  201. package/pi/package-agents/gsd-planner.md +1003 -0
  202. package/pi/package-agents/gsd-project-researcher.md +611 -0
  203. package/pi/package-agents/gsd-research-synthesizer.md +259 -0
  204. package/pi/package-agents/gsd-roadmapper.md +779 -0
  205. package/pi/package-agents/gsd-security-auditor.md +171 -0
  206. package/pi/package-agents/gsd-ui-auditor.md +581 -0
  207. package/pi/package-agents/gsd-ui-checker.md +417 -0
  208. package/pi/package-agents/gsd-ui-researcher.md +469 -0
  209. package/pi/package-agents/gsd-user-profiler.md +174 -0
  210. package/pi/package-agents/gsd-verifier.md +963 -0
  211. package/pi/package-agents/manifest.json +39 -0
  212. package/pi/package-skills/gsd-add-tests/SKILL.md +39 -0
  213. package/pi/package-skills/gsd-ai-integration-phase/SKILL.md +36 -0
  214. package/pi/package-skills/gsd-audit-fix/SKILL.md +34 -0
  215. package/pi/package-skills/gsd-audit-milestone/SKILL.md +40 -0
  216. package/pi/package-skills/gsd-audit-uat/SKILL.md +26 -0
  217. package/pi/package-skills/gsd-autonomous/SKILL.md +52 -0
  218. package/pi/package-skills/gsd-capture/SKILL.md +68 -0
  219. package/pi/package-skills/gsd-cleanup/SKILL.md +25 -0
  220. package/pi/package-skills/gsd-code-review/SKILL.md +71 -0
  221. package/pi/package-skills/gsd-complete-milestone/SKILL.md +147 -0
  222. package/pi/package-skills/gsd-config/SKILL.md +60 -0
  223. package/pi/package-skills/gsd-debug/SKILL.md +64 -0
  224. package/pi/package-skills/gsd-discuss-phase/SKILL.md +74 -0
  225. package/pi/package-skills/gsd-docs-update/SKILL.md +50 -0
  226. package/pi/package-skills/gsd-eval-review/SKILL.md +35 -0
  227. package/pi/package-skills/gsd-execute-phase/SKILL.md +62 -0
  228. package/pi/package-skills/gsd-explore/SKILL.md +26 -0
  229. package/pi/package-skills/gsd-extract-learnings/SKILL.md +25 -0
  230. package/pi/package-skills/gsd-fast/SKILL.md +33 -0
  231. package/pi/package-skills/gsd-forensics/SKILL.md +59 -0
  232. package/pi/package-skills/gsd-graphify/SKILL.md +210 -0
  233. package/pi/package-skills/gsd-health/SKILL.md +36 -0
  234. package/pi/package-skills/gsd-help/SKILL.md +37 -0
  235. package/pi/package-skills/gsd-import/SKILL.md +46 -0
  236. package/pi/package-skills/gsd-inbox/SKILL.md +42 -0
  237. package/pi/package-skills/gsd-ingest-docs/SKILL.md +43 -0
  238. package/pi/package-skills/gsd-manager/SKILL.md +46 -0
  239. package/pi/package-skills/gsd-map-codebase/SKILL.md +85 -0
  240. package/pi/package-skills/gsd-mempalace-capture/SKILL.md +111 -0
  241. package/pi/package-skills/gsd-mempalace-recall/SKILL.md +111 -0
  242. package/pi/package-skills/gsd-milestone-summary/SKILL.md +54 -0
  243. package/pi/package-skills/gsd-mvp-phase/SKILL.md +45 -0
  244. package/pi/package-skills/gsd-new-milestone/SKILL.md +49 -0
  245. package/pi/package-skills/gsd-new-project/SKILL.md +49 -0
  246. package/pi/package-skills/gsd-next/SKILL.md +34 -0
  247. package/pi/package-skills/gsd-ns-context/SKILL.md +31 -0
  248. package/pi/package-skills/gsd-ns-ideate/SKILL.md +30 -0
  249. package/pi/package-skills/gsd-ns-manage/SKILL.md +42 -0
  250. package/pi/package-skills/gsd-ns-project/SKILL.md +34 -0
  251. package/pi/package-skills/gsd-ns-review/SKILL.md +35 -0
  252. package/pi/package-skills/gsd-ns-workflow/SKILL.md +44 -0
  253. package/pi/package-skills/gsd-onboard/SKILL.md +46 -0
  254. package/pi/package-skills/gsd-pause-work/SKILL.md +48 -0
  255. package/pi/package-skills/gsd-phase/SKILL.md +61 -0
  256. package/pi/package-skills/gsd-plan-phase/SKILL.md +62 -0
  257. package/pi/package-skills/gsd-plan-review-convergence/SKILL.md +63 -0
  258. package/pi/package-skills/gsd-pr-branch/SKILL.md +31 -0
  259. package/pi/package-skills/gsd-profile-user/SKILL.md +48 -0
  260. package/pi/package-skills/gsd-progress/SKILL.md +51 -0
  261. package/pi/package-skills/gsd-quick/SKILL.md +179 -0
  262. package/pi/package-skills/gsd-quick-batch/SKILL.md +114 -0
  263. package/pi/package-skills/gsd-resume-work/SKILL.md +31 -0
  264. package/pi/package-skills/gsd-review/SKILL.md +50 -0
  265. package/pi/package-skills/gsd-review-backlog/SKILL.md +65 -0
  266. package/pi/package-skills/gsd-secure-phase/SKILL.md +37 -0
  267. package/pi/package-skills/gsd-settings/SKILL.md +30 -0
  268. package/pi/package-skills/gsd-ship/SKILL.md +27 -0
  269. package/pi/package-skills/gsd-sketch/SKILL.md +56 -0
  270. package/pi/package-skills/gsd-spec-phase/SKILL.md +63 -0
  271. package/pi/package-skills/gsd-spike/SKILL.md +53 -0
  272. package/pi/package-skills/gsd-stats/SKILL.md +22 -0
  273. package/pi/package-skills/gsd-surface/SKILL.md +177 -0
  274. package/pi/package-skills/gsd-thread/SKILL.md +29 -0
  275. package/pi/package-skills/gsd-ui-phase/SKILL.md +35 -0
  276. package/pi/package-skills/gsd-ui-review/SKILL.md +35 -0
  277. package/pi/package-skills/gsd-ultraplan-phase/SKILL.md +38 -0
  278. package/pi/package-skills/gsd-undo/SKILL.md +38 -0
  279. package/pi/package-skills/gsd-update/SKILL.md +54 -0
  280. package/pi/package-skills/gsd-validate-phase/SKILL.md +37 -0
  281. package/pi/package-skills/gsd-verify-work/SKILL.md +41 -0
  282. package/pi/package-skills/gsd-workspace/SKILL.md +57 -0
  283. package/pi/package-skills/gsd-workstreams/SKILL.md +73 -0
  284. package/scripts/build-hooks.js +4 -0
  285. package/scripts/ci-next-health.cjs +246 -23
  286. package/scripts/command-contract-helpers.cjs +1 -1
  287. package/scripts/docs-guard-registry.cjs +22 -7
  288. package/scripts/gen-features.cjs +14 -2
  289. package/scripts/gen-health-docs.cjs +13 -1
  290. package/scripts/gen-pi-package-skills.cjs +217 -0
  291. package/scripts/lib/drift-scan.cjs +27 -0
  292. package/scripts/lib/macos-conformance-tier.generated.cjs +17 -1
  293. package/scripts/lib/platform-conformance-tier.generated.cjs +37 -1
  294. package/scripts/lint-docs-guard-registration.exempt-baseline.cjs +3 -3
  295. package/scripts/lint-phase-enumeration-drift.cjs +8 -4
  296. package/scripts/lint-retired-runtime-name.cjs +5 -0
  297. package/scripts/lint-roadmap-progress-drift.cjs +421 -0
  298. package/scripts/mutation-matrix.cjs +23 -1
  299. package/scripts/npm-audit-baseline.cjs +41 -7
  300. package/scripts/reviewed-or-maintainer-policy.cjs +181 -0
  301. package/scripts/select-docs-guards.cjs +1 -1
  302. package/scripts/self-test-report-policy.cjs +350 -0
  303. package/scripts/sync-next-version.cjs +38 -6
  304. package/skills/gsd-update/SKILL.md +5 -2
  305. package/vscode/package.json +1 -1
  306. package/scripts/diagnose-validate-commit-in-tier.cjs +0 -270
  307. package/scripts/diagnose-validate-commit-phases.cjs +0 -348
  308. package/scripts/xtrace-phase-timing.cjs +0 -205
@@ -27,20 +27,11 @@ command -v cursor-agent >/dev/null 2>&1 && echo "cursor:available" || echo "curs
27
27
  command -v agy >/dev/null 2>&1 && echo "antigravity:available" || echo "antigravity:missing"
28
28
  command -v kimi >/dev/null 2>&1 && echo "kimi-code:available" || echo "kimi-code:missing"
29
29
 
30
- # Check local model servers (OpenAI-compatible HTTP API — no CLI binary required)
31
- OLLAMA_HOST=$(gsd_run query config-get review.ollama_host --raw 2>/dev/null || echo "")
32
- if [ -z "$OLLAMA_HOST" ] || [ "$OLLAMA_HOST" = "null" ]; then OLLAMA_HOST="http://localhost:11434"; fi
33
- curl -s --max-time 2 "${OLLAMA_HOST}/v1/models" >/dev/null 2>&1 && echo "ollama:available" || echo "ollama:missing"
30
+ # Check local model servers (OpenAI-compatible HTTP API — no CLI binary required);
31
+ # hosts and defaults come from each lane's descriptor
32
+ gsd_run review-lane availability || { echo "WARNING: review-lane availability failed or is unsupported by this gsd-tools; treating local lanes as missing" >&2; printf 'ollama:missing\nlm_studio:missing\nllama_cpp:missing\n'; }
34
33
 
35
- LM_STUDIO_HOST=$(gsd_run query config-get review.lm_studio_host --raw 2>/dev/null || echo "")
36
- if [ -z "$LM_STUDIO_HOST" ] || [ "$LM_STUDIO_HOST" = "null" ]; then LM_STUDIO_HOST="http://localhost:1234"; fi
37
- curl -s --max-time 2 "${LM_STUDIO_HOST}/v1/models" >/dev/null 2>&1 && echo "lm_studio:available" || echo "lm_studio:missing"
38
-
39
- LLAMA_CPP_HOST=$(gsd_run query config-get review.llama_cpp_host --raw 2>/dev/null || echo "")
40
- if [ -z "$LLAMA_CPP_HOST" ] || [ "$LLAMA_CPP_HOST" = "null" ]; then LLAMA_CPP_HOST="http://localhost:8080"; fi
41
- curl -s --max-time 2 "${LLAMA_CPP_HOST}/v1/models" >/dev/null 2>&1 && echo "llama_cpp:available" || echo "llama_cpp:missing"
42
-
43
- # jq prerequisite (#2589). The config/model/budget lookups in this workflow no
34
+ # jq prerequisite (#2589). The config/model lookups in this workflow no
44
35
  # longer need jq — they use the native --raw/--pick flags. But the lanes listed
45
36
  # under "jq-dependent reviewer lanes" below parse structured JSON that gsd-tools
46
37
  # does not emit (OpenAI-compatible /v1/chat/completions responses, opencode's
@@ -254,7 +245,7 @@ Output your review in markdown format.
254
245
 
255
246
  Write to a temp file: `{run_dir}/gsd-review-prompt.md`
256
247
 
257
- Also write individual section files so the budget tool can re-trim per reviewer:
248
+ Append the plan coverage manifest to the prompt:
258
249
 
259
250
  ```bash
260
251
  # #2962: zsh aborts the block on an unmatched for-list glob (nomatch); bash passes it through. nullglob both.
@@ -262,38 +253,21 @@ shopt -s nullglob 2>/dev/null; setopt NULL_GLOB 2>/dev/null
262
253
 
263
254
  RUN_DIR="{run_dir}" # from gather_context
264
255
 
265
- # Write individual section files for per-reviewer budget trimming
266
- # These are always written so reviewers with a budget can invoke prompt-budget
267
- cp "$INSTRUCTIONS_BLOCK_FILE" "${RUN_DIR}/gsd-review-instructions.md"
268
- cp "$ROADMAP_SECTION_FILE" "${RUN_DIR}/gsd-review-roadmap.md"
269
-
270
- # Plan files: copy each PLAN.md to a predictable path named after its source
271
- # plan id (#3959: a bare padded index discards provenance — the budget tool's
272
- # per-plan `### <file>` header then renders a run-dir artifact name no reviewer
273
- # or consensus step can resolve. The plan id keeps the gsd-review-plan-*.md glob
274
- # prepare_trimmed_prompt_for_reviewer consumes.)
275
- for PLAN_FILE in "${PHASE_DIR}"/*-PLAN.md; do
276
- PLAN_BASENAME=$(basename "$PLAN_FILE")
277
- PLAN_ID="${PLAN_BASENAME%-PLAN.md}"
278
- cp "$PLAN_FILE" "${RUN_DIR}/gsd-review-plan-${PLAN_ID}.md"
279
- done
280
-
281
256
  # #3301: plan coverage manifest — tell reviewers exactly which plan ids exist and
282
257
  # how many there are, so a review that silently covers 6 of 7 plans is no longer
283
258
  # indistinguishable from one that covers all 7. The id is the plan file's own
284
259
  # basename with the `-PLAN.md` suffix stripped (e.g. `12.6-01-PLAN.md` ->
285
260
  # `12.6-01`) — NOT the `plan:` frontmatter key, which holds only the bare
286
261
  # in-phase sequence number ("01") and can never reconstruct the phase-qualified
287
- # id reviewers need to cite. The filename is guaranteed present for every copied
288
- # plan, so no plan is ever dropped from the manifest for lacking a key.
262
+ # id reviewers need to cite. The filename is guaranteed present for every plan,
263
+ # so no plan is ever dropped from the manifest for lacking a key.
289
264
  # Count and bullets are both derived directly from the glob loop below, never
290
265
  # from re-splitting an accumulated string: bash word-splits an unquoted `$VAR`
291
266
  # on IFS by default, but zsh does not (no `setopt SH_WORD_SPLIT` here), so a
292
267
  # prior `PLAN_IDS="$PLAN_IDS $id"` + `for x in $PLAN_IDS` round-trip silently
293
268
  # collapsed every id onto one iteration under zsh whenever there were 2+ plans
294
- # (gsd-core#4099). Direct glob iteration (`for f in "${PHASE_DIR}"/*-PLAN.md`,
295
- # same pattern as the copy loop above) is identical under both shells, so this
296
- # never needs word-splitting at all.
269
+ # (gsd-core#4099). Direct glob iteration (`for f in "${PHASE_DIR}"/*-PLAN.md`)
270
+ # is identical under both shells, so this never needs word-splitting at all.
297
271
  PLAN_COUNT=0
298
272
  PLAN_ID_BULLETS=""
299
273
  for PLAN_FILE in "${PHASE_DIR}"/*-PLAN.md; do
@@ -304,10 +278,8 @@ for PLAN_FILE in "${PHASE_DIR}"/*-PLAN.md; do
304
278
  "
305
279
  done
306
280
 
307
- # Named to avoid BOTH existing RUN_DIR globs: `gsd-review-*.md` (reviewer
308
- # reports, invoke_reviewers) and `gsd-review-plan-*.md` (the plan copies just
309
- # above) — a manifest matching either would be picked up as a report or as a
310
- # plan to review.
281
+ # Dot-prefixed so the `gsd-review-*.md` glob over RUN_DIR (reviewer reports,
282
+ # invoke_reviewers) can never pick it up as a report.
311
283
  {
312
284
  echo ""
313
285
  echo "## Plan Coverage Manifest"
@@ -318,33 +290,11 @@ done
318
290
  printf '%s' "$PLAN_ID_BULLETS"
319
291
  } > "${RUN_DIR}/.plans-manifest.md"
320
292
 
321
- # Optional section files (only if content was included in the combined prompt)
322
- if [ -f ".planning/PROJECT.md" ]; then
323
- cp .planning/PROJECT.md "${RUN_DIR}/gsd-review-project.md"
324
- fi
325
- _CTX=( "${PHASE_DIR}"/*-CONTEXT.md )
326
- if [ ${#_CTX[@]} -gt 0 ]; then
327
- cat "${_CTX[@]}" > "${RUN_DIR}/gsd-review-context.md"
328
- fi
329
- _RESEARCH=( "${PHASE_DIR}"/*-RESEARCH.md )
330
- if [ ${#_RESEARCH[@]} -gt 0 ]; then
331
- cat "${_RESEARCH[@]}" > "${RUN_DIR}/gsd-review-research.md"
332
- fi
333
- if [ -f ".planning/REQUIREMENTS.md" ]; then
334
- cp .planning/REQUIREMENTS.md "${RUN_DIR}/gsd-review-requirements.md"
335
- fi
336
-
337
- # #3301: append the manifest to BOTH files reviewers actually read — the
338
- # per-lane budget-trimmed instructions file (descriptor lanes get
339
- # `--instructions-file`) and the full combined prompt (combined-prompt lanes
340
- # read the whole file). The `instructions` fragment is in prompt-budget's
341
- # `minimumFor` floor set and is never trimmed, so this survives per-lane
342
- # budget trimming intact.
343
- cat "${RUN_DIR}/.plans-manifest.md" >> "${RUN_DIR}/gsd-review-instructions.md"
293
+ # #3301: append the manifest to the one prompt file every lane reads.
344
294
  cat "${RUN_DIR}/.plans-manifest.md" >> "${RUN_DIR}/gsd-review-prompt.md"
345
295
  ```
346
296
 
347
- Note: `INSTRUCTIONS_BLOCK_FILE`, `ROADMAP_SECTION_FILE`, and `PHASE_DIR` come from prompt assembly; `RUN_DIR` is the run-scoped dir from `gather_context` (#2358) re-assigned from `{run_dir}` above. Copy the temp files written during prompt assembly to these section paths (or write each section here if the prompt was built inline).
297
+ Note: `PHASE_DIR` comes from prompt assembly; `RUN_DIR` is the run-scoped dir from `gather_context` (#2358) re-assigned from `{run_dir}` above. Every lane receives `gsd-review-prompt.md` whole — it is never trimmed per lane (#4559). A lane whose prompt exceeds its cap is refused in `invoke_reviewers`, not shortened.
348
298
  </step>
349
299
 
350
300
  <step name="invoke_reviewers">
@@ -417,35 +367,6 @@ REPO_ROOT="$(git rev-parse --show-toplevel 2>/dev/null || pwd)"
417
367
  # user's intent; here it would fire exactly the requests the default exists to prevent.
418
368
  PARALLEL_LANES=$(gsd_run query config-get review.parallel_lanes --raw 2>/dev/null || echo "false")
419
369
 
420
- # Shared budget-trim helper. Was defined inside the Ollama leg; it is lane-agnostic, so it is
421
- # hoisted here now that any lane may declare a promptBudgetKey. Returns non-zero when the budget
422
- # is too small for the minimum review set (prompt-budget exit 2 / 11).
423
- prepare_trimmed_prompt_for_reviewer() {
424
- REVIEWER_KEY="$1"; REVIEWER_BUDGET="$2"; OUTPUT_PROMPT="$3"; OUTPUT_META="$4"
425
-
426
- PLAN_FILE_ARGS=""
427
- for p in "$RUN_DIR"/gsd-review-plan-*.md; do
428
- [ -f "$p" ] && PLAN_FILE_ARGS="$PLAN_FILE_ARGS --plan-file $p"
429
- done
430
- PROJECT_ARG=""
431
- [ -f "$RUN_DIR/gsd-review-project.md" ] && PROJECT_ARG="--project-file $RUN_DIR/gsd-review-project.md"
432
- CONTEXT_ARG=""
433
- [ -f "$RUN_DIR/gsd-review-context.md" ] && CONTEXT_ARG="--context-file $RUN_DIR/gsd-review-context.md"
434
- RESEARCH_ARG=""
435
- [ -f "$RUN_DIR/gsd-review-research.md" ] && RESEARCH_ARG="--research-file $RUN_DIR/gsd-review-research.md"
436
- REQUIREMENTS_ARG=""
437
- [ -f "$RUN_DIR/gsd-review-requirements.md" ] && REQUIREMENTS_ARG="--requirements-file $RUN_DIR/gsd-review-requirements.md"
438
-
439
- gsd_run query prompt-budget \
440
- --budget "$REVIEWER_BUDGET" \
441
- --instructions-file "$RUN_DIR/gsd-review-instructions.md" \
442
- --roadmap-file "$RUN_DIR/gsd-review-roadmap.md" \
443
- $PLAN_FILE_ARGS $PROJECT_ARG $CONTEXT_ARG $RESEARCH_ARG $REQUIREMENTS_ARG \
444
- --output-prompt "$OUTPUT_PROMPT" \
445
- --output-metadata "$OUTPUT_META"
446
- return $?
447
- }
448
-
449
370
  gsd_run query review-lane plan \
450
371
  --selected "$SELECTED_REVIEWERS" --run-dir "$RUN_DIR" --repo-root "$REPO_ROOT" --json \
451
372
  > "$RUN_DIR/gsd-review-lanes.json"
@@ -462,36 +383,18 @@ run_review_lane() {
462
383
  # `local` is hygiene, not a live fix: each `&`-dispatched call already forks its own subshell, so
463
384
  # concurrent lanes cannot share these today. Scoped anyway so the isolation is a property of this
464
385
  # function rather than of the dispatch mechanism happening to fork.
465
- local SLUG LANE_BUDGET PROMPT_ARG TRIMMED
386
+ local SLUG
466
387
  SLUG="$1"
467
- # Per-lane prompt budget. The lane declares its own `promptBudgetKey`; `plan` resolved it,
468
- # applying #2797's sentinel rule (-1 = unset → fall back to the global budget; 0 legitimately
469
- # means "do not trim this lane"). Trimming itself stays in prompt-budget, which owns it.
470
- LANE_BUDGET=$(gsd_run query review-lane plan --selected "$SLUG" --run-dir "$RUN_DIR" \
471
- --repo-root "$REPO_ROOT" --json 2>/dev/null \
472
- | sed -n 's/.*"promptBudget": *\([0-9-]*\).*/\1/p' | head -1)
473
- PROMPT_ARG=""
474
- if [ -n "$LANE_BUDGET" ] && [ "$LANE_BUDGET" != "null" ] && [ "$LANE_BUDGET" -gt 0 ] 2>/dev/null; then
475
- TRIMMED="$RUN_DIR/gsd-review-prompt-$SLUG.md"
476
- if prepare_trimmed_prompt_for_reviewer "$SLUG" "$LANE_BUDGET" "$TRIMMED" \
477
- "$RUN_DIR/gsd-review-prompt-$SLUG.metadata.json"; then
478
- PROMPT_ARG="--prompt-file $TRIMMED"
479
- else
480
- # A budget too small for the minimum review set drops the lane just as silently as an empty
481
- # response used to (#2605), so leave the skip visible in the review output, not only on stderr.
482
- echo "$SLUG review skipped: prompt budget (${LANE_BUDGET} tokens) too small for the minimum review set." \
483
- > "$RUN_DIR/gsd-review-$SLUG.md"
484
- # Was `continue` when this was a loop body. Inside a function that keyword is not the loop
485
- # control it looks like — `return 0` is what skips this lane and leaves it with no result line.
486
- return 0
487
- fi
488
- fi
388
+ # #4559: no per-lane trimming. Every lane receives the whole prompt; `review-lane invoke` measures it
389
+ # against the lane's cap (its own `promptBudgetKey`, the global `review.max_prompt_tokens`, or the
390
+ # default derived from its declared context window) and REFUSES a lane that is over, writing a
391
+ # `review did not run: prompt exceeds` stub. A trimmed prompt reviewed something other than the plans.
489
392
 
490
393
  # One invocation, whatever the lane's transport, prompt channel, output channel or handler.
491
394
  # `--explicit` marks a lane the user NAMED: ADR-2782 D4 — not finding a lane nobody asked for is
492
395
  # normal, failing to run one somebody asked for is an error.
493
396
  gsd_run query review-lane invoke --slug "$SLUG" \
494
- --run-dir "$RUN_DIR" --repo-root "$REPO_ROOT" $PROMPT_ARG $EXPLICIT_FLAG --json \
397
+ --run-dir "$RUN_DIR" --repo-root "$REPO_ROOT" $EXPLICIT_FLAG --json \
495
398
  > "$RUN_DIR/gsd-review-lane-result-$SLUG.json"
496
399
  }
497
400
 
@@ -536,13 +439,17 @@ wait
536
439
 
537
440
  # Aggregate in SELECTED_REVIEWERS order, NOT completion order, so the JSONL a concurrent run
538
441
  # produces is byte-identical to the one a sequential run produces. This is post-join and therefore
539
- # single-threaded, so `>>` here is safe. A lane that was budget-skipped, or that never started,
540
- # leaves no result file and correctly contributes no line.
442
+ # single-threaded, so `>>` here is safe. A lane that never started leaves no result file, and a lane
443
+ # REFUSED for its prompt cap (#4559: reason `prompt_exceeds_context`) is deliberately left out of the
444
+ # aggregate — it did not run, so it must not count as a lane result or feed models:/model_sources:.
445
+ # Its own result file stays in RUN_DIR for the `refused_reviewers` frontmatter.
541
446
  # Rewrapped through unquoted command substitution (gsd-core#4109) — see the
542
447
  # dispatch loop above for why a bare `$DISPATCH_SLUGS` collapses under zsh.
543
448
  for SLUG in $(printf '%s' "$DISPATCH_SLUGS"); do
544
449
  LANE_RESULT="$RUN_DIR/gsd-review-lane-result-$SLUG.json"
545
- if [ -f "$LANE_RESULT" ]; then
450
+ # The refusal is the PARSED `reason` field — a failed lane whose `detail` merely mentions the
451
+ # string is a real lane result and must stay in the aggregate.
452
+ if [ -f "$LANE_RESULT" ] && ! node -e 'try{process.exit(JSON.parse(require("fs").readFileSync(process.argv[1],"utf8")).reason==="prompt_exceeds_context"?0:1)}catch{process.exit(1)}' "$LANE_RESULT"; then
546
453
  cat "$LANE_RESULT" >> "$RUN_DIR/gsd-review-lane-results.jsonl"
547
454
  fi
548
455
  done
@@ -555,7 +462,9 @@ review: it keeps its "failed or returned empty output" header (#2494/#2605/#2794
555
462
 
556
463
  A lane that will not run reports a typed reason rather than an empty file — `missing_binary`,
557
464
  `probe_failed`, `probe_timeout`, `missing_required_binary`, `host_unreachable`,
558
- `egress_host_changed`, `unknown_handler`, `budget_too_small`. **`egress_host_changed` means the lane
465
+ `egress_host_changed`, `unknown_handler`, `prompt_exceeds_context` (the prompt is over
466
+ the lane's cap — refused, never trimmed; its review file carries a `review did not run: prompt exceeds` stub).
467
+ **`egress_host_changed` means the lane
559
468
  was consented to send plans to one destination and `.planning/config.json` now names another; it is
560
469
  blocked, not silently redirected** (ADR-2782 D5).
561
470
 
@@ -572,7 +481,7 @@ Display progress:
572
481
  **#3352 (ADR-3473 §8.5): no artifact from failed inputs.** Before rendering anything, gate on
573
482
  whether any lane actually produced a result — "every lane failed" is exactly "the aggregate
574
483
  JSONL has zero lines" (§`invoke_reviewers`'s aggregation loop already builds this file as a
575
- byproduct; a lane that never started or was budget-skipped contributes no line either way).
484
+ byproduct; a lane that never started or was refused for its prompt cap contributes no line either way).
576
485
 
577
486
  ```bash
578
487
  RUN_DIR="{run_dir}"
@@ -584,7 +493,7 @@ TOTAL_LANE_FAILURE="false"
584
493
  ALL_LANES_SKIPPED="false"
585
494
  if [ "${LANE_LINES:-0}" -eq 0 ]; then
586
495
  # Zero lines means every dispatched lane left no result JSON — either every one
587
- # was budget-skipped (N5: a skip is not a failure) or every one actually failed
496
+ # was refused for its prompt cap (N5: a refusal is not a failure) or every one actually failed
588
497
  # to run. Re-derive the dispatched-slug set the same way invoke_reviewers did
589
498
  # (SELECTED_REVIEWERS is a shell block boundary — recompute, do not assume the
590
499
  # earlier step's local DISPATCH_SLUGS variable survived into this block).
@@ -595,10 +504,10 @@ if [ "${LANE_LINES:-0}" -eq 0 ]; then
595
504
  esac
596
505
  DISPATCH_SLUGS="$DISPATCH_SLUGS $SLUG"
597
506
  done
598
- # Distinguish by whether every dispatched slug's stub markdown says "skipped":
599
- # a skip stub always does (see run_review_lane's budget branch, which writes
600
- # this exact text before returning without ever invoking the lane); a real
601
- # failure stub does not. If a slug has no stub at all, it is not a skip.
507
+ # Distinguish by whether every dispatched slug's stub markdown carries the refusal marker:
508
+ # `review-lane invoke` writes `review did not run: prompt exceeds ...` (#4559) before returning
509
+ # without ever probing or spawning the lane; a real failure stub does not. If a slug has no
510
+ # stub at all, it is not a refusal.
602
511
  DISPATCHED_COUNT=0
603
512
  SKIPPED_COUNT=0
604
513
  # Rewrapped through unquoted command substitution (gsd-core#4109): a bare
@@ -607,7 +516,7 @@ if [ "${LANE_LINES:-0}" -eq 0 ]; then
607
516
  for SLUG in $(printf '%s' "$DISPATCH_SLUGS"); do
608
517
  DISPATCHED_COUNT=$((DISPATCHED_COUNT + 1))
609
518
  STUB="$RUN_DIR/gsd-review-$SLUG.md"
610
- if [ -f "$STUB" ] && grep -q "review skipped: prompt budget" "$STUB" 2>/dev/null; then
519
+ if [ -f "$STUB" ] && grep -q "review did not run: prompt exceeds" "$STUB" 2>/dev/null; then
611
520
  SKIPPED_COUNT=$((SKIPPED_COUNT + 1))
612
521
  fi
613
522
  done
@@ -620,15 +529,16 @@ fi
620
529
  ```
621
530
 
622
531
  - **If `ALL_LANES_SKIPPED=true`:** do NOT write `REVIEWS.md` and do NOT run the commit below —
623
- there is nothing to review. Report to the user that every selected lane was budget-skipped
624
- (not a failure) and stop; do not proceed to `present_results`' summary claiming a review ran.
532
+ there is nothing to review. Report to the user that every selected lane refused its prompt cap
533
+ (not a failure; name the cap and the `review.context_window_tokens.<slug>` /
534
+ `review.max_prompt_tokens_per_reviewer.<slug>` keys that raise it) and stop; do not proceed to `present_results`' summary claiming a review ran.
625
535
  - **If `TOTAL_LANE_FAILURE=true`:** do NOT write `REVIEWS.md` and do NOT run the commit below.
626
536
  Report the total lane failure to the user (name the lanes that were dispatched and point at
627
537
  their `.err`/stub files preserved under `.review-diagnostics/` by `present_results`) and stop.
628
538
  - **Otherwise** (at least one lane produced a result — R1, unchanged): proceed exactly as below.
629
539
 
630
540
  **#3301: plan coverage check.** For each dispatched lane that produced a *real* review (not a
631
- stub, not budget-skipped, not empty), check whether its output mentions every plan id from
541
+ stub, not refused for its prompt cap, not empty), check whether its output mentions every plan id from
632
542
  `.plans-manifest.md` — the same manifest `build_prompt` gave the reviewer, so the expected-id list
633
543
  here can never diverge from what the reviewer was actually told. This is diagnostic only: it never
634
544
  blocks the workflow, never fails a lane, and never changes the `TOTAL_LANE_FAILURE`/
@@ -665,17 +575,29 @@ done
665
575
  # Rewrapped through unquoted command substitution (gsd-core#4109): a bare
666
576
  # `$DISPATCH_SLUGS` word-splits under bash but not zsh, collapsing every
667
577
  # slug onto one iteration there whenever 2+ reviewers were selected.
578
+ # gsd-resolve-lib begin pattern.cjs (#5640): load helpers from the installed runtime, not the cwd
579
+ _gsd_lib_probe="pattern.cjs"
580
+ _gsd_rt="${RUNTIME_DIR:-$(git rev-parse --show-toplevel 2>/dev/null || pwd)}"
581
+ _gsd_gr="$(command -v gsd_run 2>/dev/null || true)"
582
+ case "$_gsd_gr" in /*) ;; *) _gsd_gr="" ;; esac # a gsd_run shell function prints its bare name: not a path
583
+ [ -n "$_gsd_gr" ] && _gsd_gr="$(dirname "$(node -p "require('fs').realpathSync(process.argv[1])" "$_gsd_gr" 2>/dev/null || echo "$_gsd_gr")")/lib"
584
+ GSD_LIB_DIR=""
585
+ _gsd_pick() { for _gsd_d; do if [ -n "$_gsd_d" ] && [ -f "$_gsd_d/$_gsd_lib_probe" ]; then GSD_LIB_DIR="$_gsd_d"; return 0; fi; done; return 1; }
586
+ _gsd_pick "${GSD_TOOLS:+${GSD_TOOLS%/*}/lib}" "$_gsd_rt/gsd-core/bin/lib" "$_gsd_rt/.claude/gsd-core/bin/lib" "$_gsd_rt/.codex/gsd-core/bin/lib" "$_gsd_gr" "${CLAUDE_CONFIG_DIR:-$HOME/.claude}/gsd-core/bin/lib" "${CODEX_HOME:-$HOME/.codex}/gsd-core/bin/lib" || true
587
+ if [ -z "$GSD_LIB_DIR" ]; then echo "ERROR: pattern.cjs not found in the GSD runtime. Looked in: ${GSD_TOOLS:+${GSD_TOOLS%/*}/lib, }${_gsd_rt}/gsd-core/bin/lib (and its .claude and .codex forms), ${_gsd_gr:-next to gsd_run (not on PATH)}, ${CLAUDE_CONFIG_DIR:-$HOME/.claude}/gsd-core/bin/lib, ${CODEX_HOME:-$HOME/.codex}/gsd-core/bin/lib. Reinstall: npx -y @opengsd/gsd-core@latest --claude --local" >&2; exit 1; fi
588
+ export GSD_LIB_DIR
589
+ # gsd-resolve-lib end
668
590
  for SLUG in $(printf '%s' "$DISPATCH_SLUGS"); do
669
591
  [ "$SLUG" = "coderabbit" ] && continue
670
592
  REVIEW_FILE="$RUN_DIR/gsd-review-$SLUG.md"
671
593
  [ -f "$REVIEW_FILE" ] || continue
672
594
  [ -s "$REVIEW_FILE" ] || continue
673
- grep -q "review skipped: prompt budget" "$REVIEW_FILE" 2>/dev/null && continue
595
+ grep -q "review did not run: prompt exceeds" "$REVIEW_FILE" 2>/dev/null && continue
674
596
  grep -q "failed or returned empty output" "$REVIEW_FILE" 2>/dev/null && continue
675
597
 
676
598
  node -e '
677
599
  const fs = require("fs");
678
- const { escapeRegex } = require("./gsd-core/bin/lib/pattern.cjs");
600
+ const { escapeRegex } = require(require("path").join(process.env.GSD_LIB_DIR, "pattern.cjs"));
679
601
  const manifest = fs.readFileSync(process.argv[1], "utf8");
680
602
  const review = fs.readFileSync(process.argv[2], "utf8");
681
603
  const ids = manifest.split("\n")
@@ -693,7 +615,7 @@ done
693
615
 
694
616
  Each `${RUN_DIR}/.plan-coverage-<slug>.json` carries `{complete, missing_ids, total}` for one
695
617
  graded lane. Collect these into a `plan_coverage` frontmatter block — **only** when at least one
696
- graded lane has `complete: false` (mirrors the existing `trimmed_reviewers` precedent: present
618
+ graded lane has `complete: false` (mirrors the `refused_reviewers` precedent: present
697
619
  only when there is something to report):
698
620
 
699
621
  ```yaml
@@ -711,7 +633,7 @@ heading and before the end of the first exact `<!-- gsd:plan-revision-conflicts:
711
633
  as `{preserved_plan_revision_conflict_entries}`. Ignore identical headings or delimiters in reviewer
712
634
  output: reviewers do not own blocking state. Restore the captured bytes at the explicit slot below.
713
635
 
714
- After all reviewers complete, collect trim metadata files written during the run. For each reviewer that was trimmed (i.e. a `.metadata.json` file exists and `hardFailed` or `omitted` is non-empty, or `projectMdShrunk` is true, or `planTruncationPct > 0`), include a `trimmed_reviewers` block in the frontmatter. Omit the key entirely if no reviewer was trimmed.
636
+ After all reviewers complete, collect the lanes that were refused for their prompt cap (#4559): each dispatched lane whose `${RUN_DIR}/gsd-review-lane-result-<slug>.json` has `"reason": "prompt_exceeds_context"`. For each, include a `refused_reviewers` entry in the frontmatter with `estimated_tokens`, `cap` and `cap_source` copied from that file's `refusal` object (`estimatedTokens`, `cap`, `capSource`). Omit the key entirely if no lane was refused. A refused lane's section in the body renders its `review did not run: prompt exceeds` stub, never an empty section.
715
637
 
716
638
  **Reviewer instances (#1517, optional):** when instances ran, frontmatter records their
717
639
  names, each gets its own `## <Adapter> Review (<instance>)` section, and ≥2 same-cli
@@ -743,16 +665,11 @@ models: # resolved model per reviewer; `unknown` when not reco
743
665
  model_sources: # how each value above was determined
744
666
  codex: "banner"
745
667
  antigravity: "unknown"
746
- trimmed_reviewers: # only present if at least one reviewer was trimmed
668
+ refused_reviewers: # only present if at least one lane was refused for its prompt cap (#4559)
747
669
  ollama:
748
- budget: 6000
749
- effective_budget: 5400
750
- estimated_tokens: 5380
751
- omitted: [context, research]
752
- project_md_shrunk: true
753
- plan_truncation_pct: 22
754
- hard_failed: false
755
- note_injected: true
670
+ estimated_tokens: 61000
671
+ cap: 6000
672
+ cap_source: lane # lane | global | context-window
756
673
  plan_coverage: # only present if at least one graded lane is incomplete (#3301)
757
674
  ollama:
758
675
  total: 7
@@ -816,7 +733,7 @@ present as complete. Report instead:
816
733
  ```
817
734
  ### GSD ► REVIEW FAILED
818
735
 
819
- Phase {N}: every selected reviewer lane {failed to produce a result|was budget-skipped} — no
736
+ Phase {N}: every selected reviewer lane {failed to produce a result|refused its prompt cap} — no
820
737
  REVIEWS.md was written.
821
738
 
822
739
  {If the preserve+cleanup block below reports success: "Diagnostics preserved:
@@ -849,11 +766,9 @@ step names only `{padded_phase}-REVIEWS.md` explicitly, never a directory glob,
849
766
  `.review-diagnostics/` is never swept into it.
850
767
 
851
768
  **#4097: preserve lane OUTPUT, never the run's own input copies.** `RUN_DIR` holds not only
852
- lane outputs — prompt assembly (the `gather_context`/section-copy step above) also writes the
853
- run's assembled INPUTS there under the same `gsd-review-` prefix: the combined prompt, the
854
- instructions/roadmap sections, a copy of every plan under review, the project/context/research/
855
- requirements sections, and the per-lane trimmed prompts. Those are byte-identical duplicates of
856
- files already committed under `.planning/`; sweeping them into `.review-diagnostics/` buries
769
+ lane outputs — prompt assembly also writes the run's assembled INPUT there under the same
770
+ `gsd-review-` prefix: the combined prompt (`gsd-review-prompt.md`). It duplicates files already
771
+ committed under `.planning/`; sweeping them into `.review-diagnostics/` buries
857
772
  the actual evidence under plan duplicates and grows the phase directory on every run. The
858
773
  exclusion list below is CLOSED and owned here: this workflow itself writes every input
859
774
  basename at prompt-assembly time, so a future input file CANNOT silently join the evidence
@@ -879,8 +794,10 @@ DIAG_DIR="{phase_dir}/.review-diagnostics"
879
794
  # the closed input set this workflow itself writes — direct glob iteration with a `case`
880
795
  # filter, no string accumulator, identical under bash and zsh (#4099/#4109), and the
881
796
  # `nullglob` set at the top of this fence keeps an empty RUN_DIR an empty array (#2962).
882
- # `gsd-review-prompt*` deliberately covers BOTH the combined prompt (`gsd-review-prompt.md`)
883
- # and the per-lane trimmed prompts (`gsd-review-prompt-<slug>.md`).
797
+ # `gsd-review-prompt*` is the one input file this workflow now writes (#4559 removed the
798
+ # per-lane trimmed prompts and the section copies). The section-copy names stay excluded:
799
+ # a run dir reused from an earlier GSD version can still hold them, and they are inputs,
800
+ # not lane evidence.
884
801
  _DIAG_MD=()
885
802
  for f in "$RUN_DIR"/gsd-review-*.md; do
886
803
  case "$(basename "$f")" in
@@ -905,9 +822,8 @@ if [ ${#_DIAG_MD[@]} -gt 0 ] || [ ${#_DIAG_ERR[@]} -gt 0 ]; then
905
822
  # unique per run BY CONSTRUCTION — uniqueness never depends on the clock. The
906
823
  # UTC stamp is only a sort key in front of it, and is omitted entirely if `date`
907
824
  # fails. Lane basenames are unchanged INSIDE the subdirectory, so evidence still
908
- # correlates back to its lane, and nothing inside $RUN_DIR is renamed (both
909
- # `prepare_trimmed_prompt_for_reviewer` and the lane invocation resolver depend
910
- # on those exact basenames).
825
+ # correlates back to its lane, and nothing inside $RUN_DIR is renamed (the
826
+ # lane invocation resolver depends on those exact basenames).
911
827
  _DIAG_STAMP="$(date -u +%Y%m%dT%H%M%SZ 2>/dev/null || true)"
912
828
  _DIAG_RUN_DIR="$DIAG_DIR/${_DIAG_STAMP:+${_DIAG_STAMP}-}$(basename "$RUN_DIR")"
913
829
  # `mkdir -p` still creates $DIAG_DIR itself, so a plain FILE sitting at
@@ -40,6 +40,11 @@
40
40
  }
41
41
  ],
42
42
  "complete-milestone": [
43
+ {
44
+ "id": "project-md-evolution-review",
45
+ "when": "state:flat-mode",
46
+ "read": "gsd-core/workflows/complete-milestone/steps/project-md-evolution-review.md"
47
+ },
43
48
  {
44
49
  "id": "git-tag",
45
50
  "when": "state:git-create-tag",
@@ -181,7 +181,7 @@ gsd_run query commit "docs(phase-${PHASE}): add/update security threat verificat
181
181
  GSD > PHASE {N} THREAT-SECURE
182
182
  threats_open: 0 — no blocking threats remain (threats_open: 0).
183
183
  ▶ /gsd:validate-phase {N} validate test coverage
184
- ▶ /gsd:verify-work {N} run UAT
184
+ ▶ /gsd:verify-work {N} run UAT; once UAT and verification pass it marks the phase complete (ROADMAP + STATE); if it refuses on stale verification, re-run /gsd:execute-phase {N} then verify again
185
185
  ```
186
186
 
187
187
  Display `/clear` reminder.
@@ -1,8 +1,7 @@
1
1
  Apply response_language to all user-facing prose — narration between tool calls, status updates, progress notes, and findings included; preserve code, paths, and identifiers.
2
2
 
3
3
  <purpose>
4
- Interactive configuration of GSD power-user knobs — plan bounce, planner stall detection, node
5
- repair, subagent timeouts, inline plan threshold, cross-AI execution, base branch, branch templates, response language,
4
+ Interactive configuration of GSD power-user knobs — plan bounce, planner stall detection and timing, node repair, subagent timeouts, inline plan threshold, cross-AI execution, base branch, branch templates, response language,
6
5
  context window, gitignored search, graphify build timeout, runtime model tier overrides, and
7
6
  model policy configuration (provider + budget → canonical tier mapping, or manual model ID
8
7
  assignment per cost tier).
@@ -50,6 +49,8 @@ shown in parentheses:
50
49
 
51
50
  Planning Tuning:
52
51
  - `planner.stall_detection_enabled` (default: `true`)
52
+ - `planner.stall_threshold_minutes` (default: `10`)
53
+ - `planner.stall_detect_interval_minutes` (default: `5`)
53
54
  - `workflow.plan_bounce` (default: `false`)
54
55
  - `workflow.plan_bounce_passes` (default: `2`)
55
56
  - `workflow.plan_bounce_script` (default: `null`)
@@ -106,10 +107,10 @@ in `$ARGUMENTS` OR `text_mode` is true in config. When `TEXT_MODE=true`, replace
106
107
  choice number or free-text value.
107
108
 
108
109
  **Numeric-input validation.** For any numeric field (`*_passes`, `*_budget`, `*_timeout`,
109
- `*_threshold`, `context_window`, `graphify.build_timeout`), if the user types a value that
110
+ `*_threshold`, `*_minutes`, `context_window`, `graphify.build_timeout`), if the user types a value that
110
111
  is not a non-negative integer, the workflow MUST reject it, state which value was invalid,
111
112
  and re-prompt that single field. The minimum accepted value is field-specific and is stated
112
- in each field's prompt below — `workflow.plan_bounce_passes` and `workflow.max_discuss_passes`
113
+ in each field's prompt below — the `*_passes` fields and the `planner.stall_*_minutes` fields
113
114
  require `>= 1`; all other numeric fields accept `>= 0`. An empty input means "keep current"
114
115
  — the existing value is retained. Non-numeric input is never silently coerced.
115
116
 
@@ -127,7 +128,9 @@ stored verbatim as a string.
127
128
 
128
129
  ### Section 1 — Planning Tuning
129
130
 
130
- Before offering the disabled choice, display this warning verbatim:
131
+ Asked as four calls, 1a–1d (`AskUserQuestion` takes at most 4 questions per call).
132
+
133
+ **1a — Planner watchdog.** Before offering the disabled choice, display this warning verbatim:
131
134
 
132
135
  `Warning: disabling planner stall detection gives up bounded automatic recovery if the runtime loses an agent completion handoff. Planning still waits through the runtime-native completion mechanism, but you may need to interrupt and use the filesystem fallback.`
133
136
 
@@ -141,7 +144,40 @@ AskUserQuestion([
141
144
  { label: "Yes (default: true)", description: "Poll for completion markers and plan-file activity, with bounded recovery." },
142
145
  { label: "No (false)", description: "Await runtime-native completion without polling. Gives up bounded automatic recovery if completion handoff is lost." }
143
146
  ]
147
+ }
148
+ ])
149
+ ```
150
+
151
+ **1b — Stall timing.** Ask only when `planner.stall_detection_enabled` is effectively `true` after 1a (anything but boolean `false`); otherwise skip 1b.
152
+ `planner.stall_threshold_minutes`: idle time before plan-phase offers recovery; raise it for slow models. `planner.stall_detect_interval_minutes`: check cadence. Both integer `>= 1`.
153
+
154
+ ```text
155
+ AskUserQuestion([
156
+ {
157
+ question: "Planner stall threshold (minutes)? (current: <value or 10>)",
158
+ header: "Stall Threshold",
159
+ multiSelect: false,
160
+ options: [
161
+ { label: "Keep current", description: "Leave the threshold unchanged." },
162
+ { label: "Enter minutes", description: "Integer >= 1. Default: 10." }
163
+ ]
144
164
  },
165
+ {
166
+ question: "Planner stall check interval (minutes)? (current: <value or 5>)",
167
+ header: "Stall Interval",
168
+ multiSelect: false,
169
+ options: [
170
+ { label: "Keep current", description: "Leave the interval unchanged." },
171
+ { label: "Enter minutes", description: "Integer >= 1. Default: 5." }
172
+ ]
173
+ }
174
+ ])
175
+ ```
176
+
177
+ **1c — Plan bounce.**
178
+
179
+ ```text
180
+ AskUserQuestion([
145
181
  {
146
182
  question: "Run external plan-bounce validator against generated PLAN.md? (current: <value or false>)",
147
183
  header: "Plan Bounce",
@@ -169,7 +205,14 @@ AskUserQuestion([
169
205
  { label: "Clear (null)", description: "Unset the script path." },
170
206
  { label: "Enter path", description: "Type an absolute or repo-relative path. Receives PLAN.md path as first argument." }
171
207
  ]
172
- },
208
+ }
209
+ ])
210
+ ```
211
+
212
+ **1d — Timeouts.**
213
+
214
+ ```text
215
+ AskUserQuestion([
173
216
  {
174
217
  question: "Subagent timeout (milliseconds)? (current: <value or 300000>)",
175
218
  header: "Subagent Timeout",
@@ -180,12 +223,12 @@ AskUserQuestion([
180
223
  ]
181
224
  },
182
225
  {
183
- question: "Inline plan threshold — tasks allowed inline before splitting to PLAN.md? (current: <value or 3>)",
226
+ question: "Inline plan threshold — plans with this many tasks or fewer execute inline instead of spawning a subagent? (current: <value or 2>)",
184
227
  header: "Inline Plan Threshold",
185
228
  multiSelect: false,
186
229
  options: [
187
230
  { label: "Keep current", description: "Leave threshold unchanged." },
188
- { label: "Enter number", description: "Integer count. Non-numeric rejected. Default: 3" }
231
+ { label: "Enter number", description: "Integer >= 0. Non-numeric rejected. Default: 2. Set 0 to always spawn a subagent." }
189
232
  ]
190
233
  }
191
234
  ])
@@ -535,7 +578,9 @@ anything not listed in Sections 1–8 MUST survive the update):
535
578
  ...existing_config,
536
579
  "planner": {
537
580
  ...existing_planner,
538
- "stall_detection_enabled": <new|existing>
581
+ "stall_detection_enabled": <new|existing>,
582
+ "stall_threshold_minutes": <new|existing>,
583
+ "stall_detect_interval_minutes": <new|existing>
539
584
  },
540
585
  "workflow": {
541
586
  ...existing_workflow,
@@ -772,6 +817,8 @@ Display:
772
817
  | Setting | Value |
773
818
  |--------------------------------------------|-------|
774
819
  | planner.stall_detection_enabled | {true/false} |
820
+ | planner.stall_threshold_minutes | {minutes} |
821
+ | planner.stall_detect_interval_minutes | {minutes} |
775
822
  | workflow.plan_bounce | {on/off} |
776
823
  | workflow.plan_bounce_passes | {n} |
777
824
  | workflow.plan_bounce_script | {path/null} |
@@ -826,6 +873,7 @@ UI/AI phase gates), use /gsd:settings.
826
873
  - [ ] Eight sections rendered (Planning, Execution, Discussion, Cross-AI, Git, Runtime/Output, Runtime Model Tiers, Model Policy)
827
874
  - [ ] Every field pre-selected to its current value (or documented default if absent)
828
875
  - [ ] Disabling planner stall detection shows the bounded-recovery warning before writing `false`
876
+ - [ ] Stall threshold/interval asked only while stall detection is on; no call exceeds 4 questions
829
877
  - [ ] Numeric inputs validated — non-numeric rejected and re-prompted
830
878
  - [ ] Branch-template inputs validated — non-default must contain a placeholder
831
879
  - [ ] Null-allowed fields accept an empty input as a clear
@@ -59,6 +59,7 @@ Parse current values (default to `true` if not present):
59
59
  - `graphify.auto_update` — opt-in: auto-rebuild graph after main HEAD advances (#3347) (default: `false`)
60
60
  - `model_profile` — which model each agent uses (default: `balanced`)
61
61
  - `git.branching_strategy` — branching approach (default: `"none"`)
62
+ - `git.repo_tagging` — milestone tagging mode: `"single"` (one `v[X.Y]` tag, default) or `"per_repo"` (record a release map and tag each `planning.sub_repos` repository separately; set via `gsd_run query config-set git.repo_tagging per_repo`, #3628)
62
63
  - `workflow.use_worktrees` — whether parallel executor agents run in worktree isolation (honored when the runtime declares a `dispatch.isolation` primitive — `harness-worktree` or `orchestrator-worktree`; runtimes declaring `none` default it to `false` and fail closed on an explicit `true` — #1521, #2486, #2584)
63
64
  - `workflow.compact_content` — load token-minimized instruction variants where they exist, trading rarely-needed elaboration for a smaller always-loaded instruction window (default: false if absent; #4139)
64
65
  - `model_policy.provider` — provider slug for model policy (default: `null`; known values: anthropic, openai, google, qwen; set via /gsd:config --advanced)