@opengsd/gsd-core 1.13.0 → 1.15.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 (441) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/README.ja-JP.md +3 -3
  4. package/README.ko-KR.md +3 -3
  5. package/README.pt-BR.md +3 -3
  6. package/README.zh-CN.md +3 -3
  7. package/agents/gsd-advisor-researcher.compact.md +85 -0
  8. package/agents/gsd-ai-researcher.compact.md +96 -0
  9. package/agents/gsd-assumptions-analyzer.compact.md +81 -0
  10. package/agents/gsd-code-fixer.compact.md +459 -0
  11. package/agents/gsd-code-fixer.md +9 -8
  12. package/agents/gsd-code-reviewer.compact.md +269 -0
  13. package/agents/gsd-code-reviewer.md +15 -3
  14. package/agents/gsd-codebase-mapper.compact.md +760 -0
  15. package/agents/gsd-debug-session-manager.compact.md +360 -0
  16. package/agents/gsd-debug-session-manager.md +17 -2
  17. package/agents/gsd-debugger.md +2 -2
  18. package/agents/gsd-doc-classifier.compact.md +192 -0
  19. package/agents/gsd-doc-synthesizer.compact.md +200 -0
  20. package/agents/gsd-doc-verifier.compact.md +143 -0
  21. package/agents/gsd-doc-writer.compact.md +440 -0
  22. package/agents/gsd-dom-verifier.compact.md +138 -0
  23. package/agents/gsd-domain-researcher.compact.md +141 -0
  24. package/agents/gsd-eval-auditor.compact.md +160 -0
  25. package/agents/gsd-eval-auditor.md +1 -1
  26. package/agents/gsd-eval-planner.compact.md +137 -0
  27. package/agents/gsd-executor.md +13 -8
  28. package/agents/gsd-framework-selector.compact.md +82 -0
  29. package/agents/gsd-integration-checker.compact.md +245 -0
  30. package/agents/gsd-intel-updater.compact.md +226 -0
  31. package/agents/gsd-intel-updater.md +1 -1
  32. package/agents/gsd-mempalace-curator.compact.md +45 -0
  33. package/agents/gsd-nyquist-auditor.compact.md +179 -0
  34. package/agents/gsd-pattern-mapper.compact.md +275 -0
  35. package/agents/gsd-phase-researcher.md +19 -11
  36. package/agents/gsd-plan-checker.md +8 -7
  37. package/agents/gsd-planner.md +12 -8
  38. package/agents/gsd-project-researcher.compact.md +587 -0
  39. package/agents/gsd-project-researcher.md +1 -1
  40. package/agents/gsd-research-synthesizer.compact.md +212 -0
  41. package/agents/gsd-research-synthesizer.md +1 -1
  42. package/agents/gsd-roadmapper.compact.md +454 -0
  43. package/agents/gsd-roadmapper.md +13 -0
  44. package/agents/gsd-security-auditor.compact.md +162 -0
  45. package/agents/gsd-ui-auditor.compact.md +404 -0
  46. package/agents/gsd-ui-auditor.md +155 -17
  47. package/agents/gsd-ui-checker.compact.md +277 -0
  48. package/agents/gsd-ui-researcher.compact.md +282 -0
  49. package/agents/gsd-ui-researcher.md +1 -1
  50. package/agents/gsd-user-profiler.compact.md +108 -0
  51. package/agents/gsd-verifier.md +10 -9
  52. package/bin/install.js +848 -163
  53. package/commands/gsd/autonomous.md +2 -2
  54. package/commands/gsd/capture.md +1 -1
  55. package/commands/gsd/cleanup.md +1 -0
  56. package/commands/gsd/code-review.md +2 -1
  57. package/commands/gsd/complete-milestone.md +1 -0
  58. package/commands/gsd/config.md +1 -0
  59. package/commands/gsd/debug.md +1 -0
  60. package/commands/gsd/graphify.md +1 -0
  61. package/commands/gsd/health.md +1 -0
  62. package/commands/gsd/mempalace-capture.md +8 -3
  63. package/commands/gsd/mempalace-recall.md +1 -0
  64. package/commands/gsd/new-milestone.md +1 -0
  65. package/commands/gsd/new-project.md +1 -0
  66. package/commands/gsd/next.md +1 -0
  67. package/commands/gsd/pause-work.md +1 -0
  68. package/commands/gsd/phase.md +1 -0
  69. package/commands/gsd/plan-review-convergence.md +6 -6
  70. package/commands/gsd/pr-branch.md +1 -0
  71. package/commands/gsd/progress.md +1 -1
  72. package/commands/gsd/quick-batch.md +1 -1
  73. package/commands/gsd/resume-work.md +1 -0
  74. package/commands/gsd/review-backlog.md +1 -0
  75. package/commands/gsd/review.md +2 -3
  76. package/commands/gsd/settings.md +2 -1
  77. package/commands/gsd/stats.md +1 -0
  78. package/commands/gsd/thread.md +1 -0
  79. package/commands/gsd/workspace.md +1 -0
  80. package/commands/gsd/workstreams.md +1 -0
  81. package/gsd-core/bin/check-latest-version.cjs +8 -3
  82. package/gsd-core/bin/gsd-tools.cjs +672 -146
  83. package/gsd-core/bin/lib/adr-parser.cjs +4 -2
  84. package/gsd-core/bin/lib/artifacts.cjs +2 -1
  85. package/gsd-core/bin/lib/audit.cjs +119 -34
  86. package/gsd-core/bin/lib/broken-windows.cjs +168 -49
  87. package/gsd-core/bin/lib/capability-lifecycle.cjs +10 -6
  88. package/gsd-core/bin/lib/capability-loader.cjs +135 -1
  89. package/gsd-core/bin/lib/capability-registry.cjs +96 -189
  90. package/gsd-core/bin/lib/capability-source.cjs +19 -2
  91. package/gsd-core/bin/lib/capability-validator.cjs +14 -2
  92. package/gsd-core/bin/lib/check-command-router.cjs +213 -49
  93. package/gsd-core/bin/lib/code-review-depth.cjs +2 -2
  94. package/gsd-core/bin/lib/codex-agent-toml.cjs +21 -25
  95. package/gsd-core/bin/lib/commands.cjs +823 -112
  96. package/gsd-core/bin/lib/config-loader.cjs +66 -4
  97. package/gsd-core/bin/lib/config.cjs +186 -45
  98. package/gsd-core/bin/lib/coverage.cjs +1 -1
  99. package/gsd-core/bin/lib/decisions.cjs +164 -45
  100. package/gsd-core/bin/lib/external-descriptor-trust.cjs +29 -14
  101. package/gsd-core/bin/lib/frontmatter.cjs +13 -0
  102. package/gsd-core/bin/lib/graphify.cjs +10 -2
  103. package/gsd-core/bin/lib/gsd2-import.cjs +1 -2
  104. package/gsd-core/bin/lib/health-diagnostic-rules/state-consistency.cjs +12 -1
  105. package/gsd-core/bin/lib/health-diagnostic-rules/worktree-health.cjs +1 -1
  106. package/gsd-core/bin/lib/host-runtime-detection.cjs +9 -0
  107. package/gsd-core/bin/lib/init.cjs +614 -86
  108. package/gsd-core/bin/lib/install-engine.cjs +29 -3
  109. package/gsd-core/bin/lib/install-profiles.cjs +14 -0
  110. package/gsd-core/bin/lib/installer-migrations.cjs +41 -5
  111. package/gsd-core/bin/lib/loop-resolver.cjs +50 -31
  112. package/gsd-core/bin/lib/mcp-catalog.cjs +2 -2
  113. package/gsd-core/bin/lib/milestone.cjs +37 -13
  114. package/gsd-core/bin/lib/model-resolver.cjs +253 -53
  115. package/gsd-core/bin/lib/phase-command-router.cjs +16 -2
  116. package/gsd-core/bin/lib/phase-id-card.cjs +32 -0
  117. package/gsd-core/bin/lib/phase-id-display.cjs +78 -0
  118. package/gsd-core/bin/lib/phase-id.cjs +268 -27
  119. package/gsd-core/bin/lib/phase-lifecycle.cjs +61 -0
  120. package/gsd-core/bin/lib/phase-locator.cjs +29 -10
  121. package/gsd-core/bin/lib/phase.cjs +393 -88
  122. package/gsd-core/bin/lib/plan-document.cjs +49 -1
  123. package/gsd-core/bin/lib/planning-document.cjs +459 -0
  124. package/gsd-core/bin/lib/planning-inspect.cjs +52 -19
  125. package/gsd-core/bin/lib/planning-snapshot.cjs +61 -12
  126. package/gsd-core/bin/lib/planning-workspace.cjs +57 -3
  127. package/gsd-core/bin/lib/pr-branch-patterns.cjs +57 -0
  128. package/gsd-core/bin/lib/pristine-baseline.cjs +182 -0
  129. package/gsd-core/bin/lib/probe-core.cjs +7 -1
  130. package/gsd-core/bin/lib/prohibition-enforcement.cjs +91 -4
  131. package/gsd-core/bin/lib/project-root.cjs +41 -2
  132. package/gsd-core/bin/lib/quick-batch.cjs +1 -1
  133. package/gsd-core/bin/lib/refactor-trigger-command-router.cjs +61 -2
  134. package/gsd-core/bin/lib/research-store.cjs +11 -12
  135. package/gsd-core/bin/lib/review-lane-descriptor.cjs +10 -30
  136. package/gsd-core/bin/lib/review-lane-invocation.cjs +23 -0
  137. package/gsd-core/bin/lib/review-reviewer-selection.cjs +2 -2
  138. package/gsd-core/bin/lib/reviewer-step-dispatch.cjs +337 -0
  139. package/gsd-core/bin/lib/roadmap-command-router.cjs +12 -4
  140. package/gsd-core/bin/lib/roadmap-parser.cjs +219 -18
  141. package/gsd-core/bin/lib/roadmap-upgrade.cjs +1539 -13
  142. package/gsd-core/bin/lib/roadmap.cjs +356 -42
  143. package/gsd-core/bin/lib/runtime-artifact-conversion.cjs +310 -41
  144. package/gsd-core/bin/lib/runtime-artifact-install-plan.cjs +15 -4
  145. package/gsd-core/bin/lib/runtime-artifact-layout.cjs +13 -5
  146. package/gsd-core/bin/lib/runtime-homes.cjs +4 -0
  147. package/gsd-core/bin/lib/runtime-hooks-surface.cjs +408 -37
  148. package/gsd-core/bin/lib/runtime-name-policy.cjs +111 -1
  149. package/gsd-core/bin/lib/security.cjs +126 -7
  150. package/gsd-core/bin/lib/shell-command-projection.cjs +10 -6
  151. package/gsd-core/bin/lib/state-document.cjs +130 -28
  152. package/gsd-core/bin/lib/state-md-schema.cjs +21 -14
  153. package/gsd-core/bin/lib/state-transition.cjs +181 -30
  154. package/gsd-core/bin/lib/state.cjs +265 -27
  155. package/gsd-core/bin/lib/surface.cjs +77 -3
  156. package/gsd-core/bin/lib/task-command-router.cjs +12 -6
  157. package/gsd-core/bin/lib/tdd-red-evidence.cjs +78 -5
  158. package/gsd-core/bin/lib/uat-predicate.cjs +47 -4
  159. package/gsd-core/bin/lib/uat.cjs +9 -1
  160. package/gsd-core/bin/lib/ui-consideration-probe.cjs +15 -2
  161. package/gsd-core/bin/lib/ui-frontend-evidence.cjs +100 -9
  162. package/gsd-core/bin/lib/undo-commit-selection.cjs +131 -0
  163. package/gsd-core/bin/lib/update-context.cjs +30 -24
  164. package/gsd-core/bin/lib/vendor/js-yaml.cjs +11 -3
  165. package/gsd-core/bin/lib/verification.cjs +315 -30
  166. package/gsd-core/bin/lib/verify-command-grounding.cjs +47 -3
  167. package/gsd-core/bin/lib/verify.cjs +320 -48
  168. package/gsd-core/bin/lib/workstream-inventory.cjs +1 -0
  169. package/gsd-core/bin/lib/worktree-base-ref.cjs +482 -73
  170. package/gsd-core/bin/lib/worktree-safety.cjs +797 -58
  171. package/gsd-core/bin/shared/config-defaults.manifest.json +4 -0
  172. package/gsd-core/bin/shared/config-schema.manifest.json +6 -0
  173. package/gsd-core/bin/verify-reapply-patches.cjs +439 -80
  174. package/gsd-core/references/checkpoints.md +5 -3
  175. package/gsd-core/references/compact-content-gate.md +66 -0
  176. package/gsd-core/references/edge-probe-fixtures/01-round-half-even/expected-coverage.json +28 -3
  177. package/gsd-core/references/edge-probe-fixtures/02-merge-intervals/expected-coverage.json +37 -4
  178. package/gsd-core/references/edge-probe-fixtures/03-truncate-graphemes/expected-coverage.json +28 -3
  179. package/gsd-core/references/edge-probe-fixtures/04-money-rounding/expected-coverage.json +28 -3
  180. package/gsd-core/references/edge-probe-fixtures/05-list-dedupe/expected-coverage.json +37 -4
  181. package/gsd-core/references/edge-probe-fixtures/06-resolved-mixed/expected-coverage.json +37 -4
  182. package/gsd-core/references/edge-probe.md +195 -21
  183. package/gsd-core/references/execute-phase-between-wave-reset.md +7 -6
  184. package/gsd-core/references/execute-phase-wave-guard.md +22 -11
  185. package/gsd-core/references/gsd-run-resolver.md +1 -1
  186. package/gsd-core/references/loop-hook-dispatch.md +18 -0
  187. package/gsd-core/references/model-profiles.md +13 -4
  188. package/gsd-core/references/phase-argument-parsing.md +9 -7
  189. package/gsd-core/references/phase-id-convention.md +28 -0
  190. package/gsd-core/references/planner-gap-closure.md +2 -0
  191. package/gsd-core/references/planner-load-graph-context.md +24 -13
  192. package/gsd-core/references/planner-verify-command-grounding.md +14 -0
  193. package/gsd-core/references/planning-config.md +14 -2
  194. package/gsd-core/references/tdd.md +30 -4
  195. package/gsd-core/references/thinking-models-planning.md +18 -2
  196. package/gsd-core/references/ui-consideration-probe.md +10 -5
  197. package/gsd-core/references/verification-patterns.md +17 -4
  198. package/gsd-core/references/verify-command-path-resolvability.md +10 -2
  199. package/gsd-core/references/worktree-path-safety.md +433 -2
  200. package/gsd-core/templates/README.md +7 -1
  201. package/gsd-core/templates/state.md +6 -3
  202. package/gsd-core/templates/summary.compact.md +212 -0
  203. package/gsd-core/templates/user-setup.compact.md +199 -0
  204. package/gsd-core/templates/user-setup.md +0 -9
  205. package/gsd-core/templates/verification-report.md +1 -1
  206. package/gsd-core/workflows/_runtime-launcher.snippet.sh +1 -1
  207. package/gsd-core/workflows/add-backlog.md +1 -1
  208. package/gsd-core/workflows/add-phase.md +1 -1
  209. package/gsd-core/workflows/add-tests.md +2 -2
  210. package/gsd-core/workflows/add-todo.md +6 -5
  211. package/gsd-core/workflows/ai-integration-phase.md +11 -3
  212. package/gsd-core/workflows/audit-fix.md +1 -1
  213. package/gsd-core/workflows/audit-milestone.md +1 -1
  214. package/gsd-core/workflows/audit-uat.md +1 -1
  215. package/gsd-core/workflows/autonomous/steps/converge-fail-fast.md +9 -18
  216. package/gsd-core/workflows/autonomous.md +29 -16
  217. package/gsd-core/workflows/check-todos.md +6 -4
  218. package/gsd-core/workflows/cleanup.md +5 -3
  219. package/gsd-core/workflows/code-review/steps/dispatch-fix.md +4 -3
  220. package/gsd-core/workflows/code-review/steps/structural-pre-pass.md +8 -1
  221. package/gsd-core/workflows/code-review-fix.md +108 -22
  222. package/gsd-core/workflows/code-review.md +216 -73
  223. package/gsd-core/workflows/complete-milestone/detail/elaboration.md +274 -0
  224. package/gsd-core/workflows/complete-milestone.md +41 -264
  225. package/gsd-core/workflows/debug.md +3 -3
  226. package/gsd-core/workflows/diagnose-issues.md +1 -1
  227. package/gsd-core/workflows/discuss-phase/modes/advisor.md +1 -1
  228. package/gsd-core/workflows/discuss-phase/modes/chain.md +1 -1
  229. package/gsd-core/workflows/discuss-phase-assumptions.md +1 -1
  230. package/gsd-core/workflows/discuss-phase.md +1 -1
  231. package/gsd-core/workflows/do.md +2 -2
  232. package/gsd-core/workflows/docs-update/detail/elaboration.md +179 -0
  233. package/gsd-core/workflows/docs-update.md +17 -158
  234. package/gsd-core/workflows/edit-phase.md +1 -1
  235. package/gsd-core/workflows/eval-review.md +10 -3
  236. package/gsd-core/workflows/execute-phase/detail/elaboration.md +124 -0
  237. package/gsd-core/workflows/execute-phase/steps/code-review-disposition.md +1017 -0
  238. package/gsd-core/workflows/execute-phase/steps/codebase-drift-gate.md +19 -4
  239. package/gsd-core/workflows/execute-phase/steps/completion-reconciliation.md +56 -0
  240. package/gsd-core/workflows/execute-phase/steps/executor-isolation-dispatch.md +43 -4
  241. package/gsd-core/workflows/execute-phase/steps/executor-progress-policy.md +43 -0
  242. package/gsd-core/workflows/execute-phase/steps/gap-closure-artifacts.md +1 -1
  243. package/gsd-core/workflows/execute-phase/steps/partial-wave.md +1 -1
  244. package/gsd-core/workflows/execute-phase/steps/per-plan-executor-routing.md +1 -1
  245. package/gsd-core/workflows/execute-phase/steps/per-plan-worktree-gate.md +1 -1
  246. package/gsd-core/workflows/execute-phase/steps/post-merge-gate.md +46 -9
  247. package/gsd-core/workflows/execute-phase/steps/protected-branch.md +1 -1
  248. package/gsd-core/workflows/execute-phase/steps/ready-wave-gate.md +37 -0
  249. package/gsd-core/workflows/execute-phase/steps/regression-gate-run.md +1 -1
  250. package/gsd-core/workflows/execute-phase/steps/sequential-root-pin.md +35 -0
  251. package/gsd-core/workflows/execute-phase/steps/stale-reverification.md +24 -0
  252. package/gsd-core/workflows/execute-phase/steps/tdd-applicability-resolution.md +1 -1
  253. package/gsd-core/workflows/execute-phase/steps/threat-id-gate.md +28 -0
  254. package/gsd-core/workflows/execute-phase/steps/wave-post-gate-hooks.md +1 -1
  255. package/gsd-core/workflows/execute-phase/steps/worktree-base-check.md +25 -0
  256. package/gsd-core/workflows/execute-phase.md +83 -172
  257. package/gsd-core/workflows/execute-plan.md +24 -10
  258. package/gsd-core/workflows/explore.md +3 -3
  259. package/gsd-core/workflows/extract-learnings.md +2 -1
  260. package/gsd-core/workflows/fast.md +1 -1
  261. package/gsd-core/workflows/forensics.md +1 -1
  262. package/gsd-core/workflows/graduation.md +1 -1
  263. package/gsd-core/workflows/health.md +2 -2
  264. package/gsd-core/workflows/help/modes/full.compact.md +398 -0
  265. package/gsd-core/workflows/help/modes/full.md +5 -5
  266. package/gsd-core/workflows/help/modes/topic.md +15 -5
  267. package/gsd-core/workflows/help.md +1 -1
  268. package/gsd-core/workflows/import.md +2 -2
  269. package/gsd-core/workflows/inbox.md +2 -2
  270. package/gsd-core/workflows/ingest-docs.md +3 -3
  271. package/gsd-core/workflows/insert-phase.md +1 -1
  272. package/gsd-core/workflows/list-seeds.md +1 -1
  273. package/gsd-core/workflows/list-workspaces.md +1 -1
  274. package/gsd-core/workflows/manager.md +2 -2
  275. package/gsd-core/workflows/map-codebase.md +52 -5
  276. package/gsd-core/workflows/milestone-summary.md +1 -1
  277. package/gsd-core/workflows/mvp-phase.md +1 -1
  278. package/gsd-core/workflows/new-milestone.md +56 -14
  279. package/gsd-core/workflows/new-project/detail/elaboration.md +216 -0
  280. package/gsd-core/workflows/new-project/steps/auto-mode-config.md +3 -3
  281. package/gsd-core/workflows/new-project/steps/codebase-map-offer.md +1 -1
  282. package/gsd-core/workflows/new-project.md +39 -209
  283. package/gsd-core/workflows/new-workspace.md +2 -2
  284. package/gsd-core/workflows/next.md +1 -1
  285. package/gsd-core/workflows/note.md +1 -1
  286. package/gsd-core/workflows/onboard.md +1 -1
  287. package/gsd-core/workflows/pause-work.md +1 -1
  288. package/gsd-core/workflows/plan-phase/detail/elaboration.md +209 -0
  289. package/gsd-core/workflows/plan-phase/steps/chunked-planning-mode.md +17 -5
  290. package/gsd-core/workflows/plan-phase/steps/prd-express-path.md +1 -1
  291. package/gsd-core/workflows/plan-phase/steps/stall-detection-helpers.md +23 -5
  292. package/gsd-core/workflows/plan-phase.md +45 -187
  293. package/gsd-core/workflows/plan-review-convergence.md +21 -5
  294. package/gsd-core/workflows/plant-seed.md +62 -20
  295. package/gsd-core/workflows/pr-branch.md +132 -20
  296. package/gsd-core/workflows/profile-user.md +2 -2
  297. package/gsd-core/workflows/progress.md +1 -1
  298. package/gsd-core/workflows/quick/steps/plan-checker-loop.md +25 -0
  299. package/gsd-core/workflows/quick/steps/quick-verification.md +1 -1
  300. package/gsd-core/workflows/quick/steps/worktree-pre-dispatch-commit.md +1 -1
  301. package/gsd-core/workflows/quick-batch/steps/batch-init.md +1 -1
  302. package/gsd-core/workflows/quick-batch/steps/completion.md +1 -1
  303. package/gsd-core/workflows/quick-batch/steps/merge-wave.md +1 -1
  304. package/gsd-core/workflows/quick-batch/steps/planner-wave.md +1 -1
  305. package/gsd-core/workflows/quick-batch/steps/research-phase.md +1 -1
  306. package/gsd-core/workflows/quick-batch/steps/resume-mode.md +1 -1
  307. package/gsd-core/workflows/quick-batch/steps/verification-wave.md +1 -1
  308. package/gsd-core/workflows/quick-batch/steps/worktree-dispatch.md +1 -1
  309. package/gsd-core/workflows/quick-batch.md +1 -1
  310. package/gsd-core/workflows/quick.md +29 -10
  311. package/gsd-core/workflows/reapply-patches.md +86 -6
  312. package/gsd-core/workflows/remove-phase.md +1 -1
  313. package/gsd-core/workflows/remove-workspace.md +2 -2
  314. package/gsd-core/workflows/resume-project.md +1 -1
  315. package/gsd-core/workflows/review.md +31 -16
  316. package/gsd-core/workflows/scan.md +1 -1
  317. package/gsd-core/workflows/secure-phase.md +3 -2
  318. package/gsd-core/workflows/settings-advanced.md +30 -10
  319. package/gsd-core/workflows/settings-integrations.md +2 -3
  320. package/gsd-core/workflows/settings.md +22 -9
  321. package/gsd-core/workflows/ship.md +3 -2
  322. package/gsd-core/workflows/sketch-wrap-up.md +1 -1
  323. package/gsd-core/workflows/sketch.md +1 -1
  324. package/gsd-core/workflows/smart-entry.md +2 -2
  325. package/gsd-core/workflows/spec-phase.md +15 -5
  326. package/gsd-core/workflows/spike-wrap-up.md +1 -1
  327. package/gsd-core/workflows/spike.md +1 -1
  328. package/gsd-core/workflows/stats.md +1 -1
  329. package/gsd-core/workflows/sync-skills.md +5 -5
  330. package/gsd-core/workflows/thread.md +1 -1
  331. package/gsd-core/workflows/transition.md +1 -1
  332. package/gsd-core/workflows/ui-phase.md +44 -8
  333. package/gsd-core/workflows/ui-review.md +18 -4
  334. package/gsd-core/workflows/ultraplan-phase.md +1 -1
  335. package/gsd-core/workflows/undo.md +339 -20
  336. package/gsd-core/workflows/update.md +14 -12
  337. package/gsd-core/workflows/validate-phase.md +3 -2
  338. package/gsd-core/workflows/verify-work/detail/elaboration.md +230 -0
  339. package/gsd-core/workflows/verify-work/steps/automated-ui-verification.md +1 -1
  340. package/gsd-core/workflows/verify-work/steps/mvp-uat-framing.md +1 -1
  341. package/gsd-core/workflows/verify-work.md +101 -196
  342. package/hooks/dist/gsd-agent-isolation-guard.js +66 -16
  343. package/hooks/dist/gsd-context-monitor.js +88 -15
  344. package/hooks/dist/gsd-cursor-subagent-start.js +34 -14
  345. package/hooks/dist/gsd-secret-read-guard.js +71 -19
  346. package/hooks/dist/gsd-statusline.js +81 -20
  347. package/hooks/dist/gsd-validate-commit.sh +97 -8
  348. package/hooks/dist/gsd-worktree-path-guard.js +25 -14
  349. package/hooks/dist/gsd-write-guard.js +46 -1
  350. package/hooks/dist/lib/dispatch-identity.js +187 -0
  351. package/hooks/dist/lib/filename-classification.js +64 -0
  352. package/hooks/dist/lib/isolation-deny-reason.js +53 -1
  353. package/hooks/dist/lib/isolation-sentinel.js +58 -19
  354. package/hooks/gsd-agent-isolation-guard.js +66 -16
  355. package/hooks/gsd-context-monitor.js +88 -15
  356. package/hooks/gsd-cursor-subagent-start.js +34 -14
  357. package/hooks/gsd-secret-read-guard.js +71 -19
  358. package/hooks/gsd-statusline.js +81 -20
  359. package/hooks/gsd-validate-commit.sh +97 -8
  360. package/hooks/gsd-worktree-path-guard.js +25 -14
  361. package/hooks/gsd-write-guard.js +46 -1
  362. package/hooks/lib/dispatch-identity.js +187 -0
  363. package/hooks/lib/filename-classification.js +64 -0
  364. package/hooks/lib/isolation-deny-reason.js +53 -1
  365. package/hooks/lib/isolation-sentinel.js +58 -19
  366. package/package.json +11 -6
  367. package/scripts/benchmark-compact-content-variants.cjs +298 -0
  368. package/scripts/benchmark-compact-content.cjs +368 -0
  369. package/scripts/build-hooks.js +15 -6
  370. package/scripts/check-contract-drift.cjs +131 -12
  371. package/scripts/check-env.cjs +36 -8
  372. package/scripts/check-glossary-refs.cjs +25 -21
  373. package/scripts/ci-next-health.cjs +271 -0
  374. package/scripts/ci-prepare-test-scope.cjs +7 -7
  375. package/scripts/ci-test-scope.cjs +126 -20
  376. package/scripts/ci-timeout-report.cjs +1 -1
  377. package/scripts/command-contract-helpers.cjs +3 -0
  378. package/scripts/diff-touches-shipped-paths.cjs +1 -1
  379. package/scripts/docs-guard-registry.cjs +35 -2
  380. package/scripts/gen-adr-index.cjs +8 -2
  381. package/scripts/gen-inventory-manifest.cjs +12 -0
  382. package/scripts/gen-loop-host-contract.cjs +69 -0
  383. package/scripts/gen-platform-conformance-tier.cjs +557 -0
  384. package/scripts/lib/drift-scan.cjs +1 -1
  385. package/scripts/lib/macos-conformance-tier.generated.cjs +224 -0
  386. package/scripts/lib/ndjson-reporter.cjs +3 -2
  387. package/scripts/lib/npm-version-check-diagnosis.cjs +59 -0
  388. package/scripts/lib/platform-conformance-tier.generated.cjs +287 -0
  389. package/scripts/lib/suite-detection.cjs +32 -0
  390. package/scripts/lint-allowed-tools-parity.cjs +221 -0
  391. package/scripts/lint-docs-guard-registration.exempt-baseline.cjs +47 -3
  392. package/scripts/lint-phase-arg-assignment.cjs +257 -0
  393. package/scripts/lint-phase-id-drift.cjs +623 -13
  394. package/scripts/lint-pr-branch-pattern-drift.cjs +148 -0
  395. package/scripts/lint-response-language-coverage.cjs +9 -3
  396. package/scripts/lint-retired-runtime-name.cjs +619 -0
  397. package/scripts/lint-source-test-name-collision.cjs +1 -1
  398. package/scripts/lint-state-write-path-drift.cjs +93 -0
  399. package/scripts/lint-test-file-count.allowlist.json +29 -9
  400. package/scripts/lint-vendored-deps.cjs +128 -17
  401. package/scripts/lint-workflow-shellcheck-baseline.json +100 -0
  402. package/scripts/prompt-injection-scan.sh +18 -0
  403. package/scripts/release-tarball-smoke.cjs +194 -1
  404. package/scripts/workflow-size.cjs +139 -0
  405. package/skills/gsd-autonomous/SKILL.md +2 -2
  406. package/skills/gsd-capture/SKILL.md +1 -1
  407. package/skills/gsd-cleanup/SKILL.md +1 -0
  408. package/skills/gsd-code-review/SKILL.md +2 -1
  409. package/skills/gsd-complete-milestone/SKILL.md +1 -0
  410. package/skills/gsd-config/SKILL.md +1 -0
  411. package/skills/gsd-debug/SKILL.md +1 -0
  412. package/skills/gsd-graphify/SKILL.md +1 -0
  413. package/skills/gsd-health/SKILL.md +1 -0
  414. package/skills/gsd-mempalace-capture/SKILL.md +8 -3
  415. package/skills/gsd-mempalace-recall/SKILL.md +1 -0
  416. package/skills/gsd-new-milestone/SKILL.md +1 -0
  417. package/skills/gsd-new-project/SKILL.md +1 -0
  418. package/skills/gsd-next/SKILL.md +1 -0
  419. package/skills/gsd-pause-work/SKILL.md +1 -0
  420. package/skills/gsd-phase/SKILL.md +1 -0
  421. package/skills/gsd-plan-review-convergence/SKILL.md +5 -5
  422. package/skills/gsd-pr-branch/SKILL.md +1 -0
  423. package/skills/gsd-progress/SKILL.md +1 -1
  424. package/skills/gsd-quick-batch/SKILL.md +1 -1
  425. package/skills/gsd-resume-work/SKILL.md +1 -0
  426. package/skills/gsd-review/SKILL.md +2 -3
  427. package/skills/gsd-review-backlog/SKILL.md +1 -0
  428. package/skills/gsd-settings/SKILL.md +2 -1
  429. package/skills/gsd-stats/SKILL.md +1 -0
  430. package/skills/gsd-thread/SKILL.md +1 -0
  431. package/skills/gsd-workspace/SKILL.md +1 -0
  432. package/skills/gsd-workstreams/SKILL.md +1 -0
  433. package/vscode/package.json +1 -1
  434. package/gsd-core/templates/claude-md.md +0 -145
  435. package/gsd-core/templates/codebase/concerns.md +0 -310
  436. package/gsd-core/templates/codebase/conventions.md +0 -307
  437. package/gsd-core/templates/codebase/integrations.md +0 -280
  438. package/gsd-core/templates/codebase/structure.md +0 -285
  439. package/gsd-core/templates/codebase/testing.md +0 -480
  440. package/gsd-core/templates/debug-subagent-prompt.md +0 -91
  441. package/gsd-core/templates/discovery.md +0 -146
@@ -28,6 +28,9 @@ const node_fs_1 = __importDefault(require("node:fs"));
28
28
  const node_os_1 = __importDefault(require("node:os"));
29
29
  const node_path_1 = __importDefault(require("node:path"));
30
30
  const shell_command_projection_cjs_1 = require("./shell-command-projection.cjs");
31
+ // #4717: runtime-identity fill — env rung + per-install marker rung.
32
+ const runtime_slash_cjs_1 = require("./runtime-slash.cjs");
33
+ const runtime_name_policy_cjs_1 = require("./runtime-name-policy.cjs");
31
34
  // eslint-disable-next-line @typescript-eslint/no-require-imports
32
35
  const planningWorkspace = require("./planning-workspace.cjs");
33
36
  const { planningDir, planningRoot } = planningWorkspace;
@@ -135,6 +138,7 @@ const CONFIG_DEFAULTS = {
135
138
  firecrawl: _getConfigDefault('firecrawl'),
136
139
  exa_search: _getConfigDefault('exa_search'),
137
140
  text_mode: _getNestedConfigDefault('workflow', 'text_mode'),
141
+ compact_content: _getNestedConfigDefault('workflow', 'compact_content'),
138
142
  sub_repos: _getNestedConfigDefault('planning', 'sub_repos'),
139
143
  pr_strict: _getNestedConfigDefault('planning', 'pr_strict'),
140
144
  resolve_model_ids: _getConfigDefault('resolve_model_ids'),
@@ -149,8 +153,21 @@ const CONFIG_DEFAULTS = {
149
153
  research_before_questions: _getNestedConfigDefault('workflow', 'research_before_questions'), // #3894
150
154
  smart_zone_tokens: _getNestedConfigDefault('workflow', 'smart_zone_tokens'),
151
155
  inline_plan_threshold: _getNestedConfigDefault('workflow', 'inline_plan_threshold'), // #3801
156
+ planner_stall_detection_enabled: _getNestedConfigDefault('planner', 'stall_detection_enabled'),
152
157
  max_prompt_tokens: _getNestedConfigDefault('review', 'max_prompt_tokens'),
153
158
  };
159
+ /**
160
+ * Resolve the planner watchdog policy from hand-edited configuration.
161
+ * Only a real JSON boolean may override the default; strings/numbers/null
162
+ * fail safe to the manifest-owned default-on behavior (#4570).
163
+ */
164
+ function resolvePlannerStallDetectionEnabled(value) {
165
+ if (typeof value === 'boolean')
166
+ return value;
167
+ // A missing or skewed manifest must never convert an absent override into
168
+ // permission to disable the watchdog. The documented contract is default-on.
169
+ return true;
170
+ }
154
171
  /**
155
172
  * Deep-merge two plain config objects. `overlay` wins on key conflict.
156
173
  * Explicit `null` in overlay overrides base (null means "unset this key").
@@ -600,7 +617,7 @@ function _warnUnusableConfig(fault) {
600
617
  * cannot dirty the working tree; the ~30 callers that omit it keep persisting, so
601
618
  * a legacy config is still migrated exactly once by ordinary use.
602
619
  */
603
- function loadConfigResolved(cwd, options = {}) {
620
+ function loadConfigResolvedInternal(cwd, options = {}) {
604
621
  // Opt-OUT, not opt-in: omitting the option must preserve the historical
605
622
  // write-back for every existing caller.
606
623
  const persist = options['persist'] !== false;
@@ -613,7 +630,7 @@ function loadConfigResolved(cwd, options = {}) {
613
630
  : (options['workstreamContext'] && Object.prototype.hasOwnProperty.call(options['workstreamContext'], 'ws'))
614
631
  ? options['workstreamContext']['ws']
615
632
  : (process.env['GSD_WORKSTREAM'] || null);
616
- const ws = typeof activeWorkstream === 'string' ? activeWorkstream : (activeWorkstream === null ? null : null);
633
+ const ws = typeof activeWorkstream === 'string' ? activeWorkstream.trim() || null : null;
617
634
  // wsRequested: true when caller explicitly requested a non-empty workstream.
618
635
  // Used for source labeling (Fix 4) and early absent-dir intercept (Fix 2).
619
636
  const wsRequested = ws != null && ws !== '';
@@ -857,6 +874,7 @@ function loadConfigResolved(cwd, options = {}) {
857
874
  phase_naming: get('phase_naming') ?? defaults.phase_naming,
858
875
  project_code: get('project_code') ?? defaults.project_code,
859
876
  subagent_timeout: get('subagent_timeout', { section: 'workflow', field: 'subagent_timeout' }) ?? defaults.subagent_timeout,
877
+ planner_stall_detection_enabled: resolvePlannerStallDetectionEnabled(getNested('planner', 'stall_detection_enabled')),
860
878
  model_overrides: (parsed['model_overrides']) || null,
861
879
  agent_tools: (parsed['agent_tools']) || null,
862
880
  models: (parsed['models']) || null,
@@ -958,7 +976,7 @@ function loadConfigResolved(cwd, options = {}) {
958
976
  // `workstream: null` still wins the `hasOwnProperty` check at the top of this
959
977
  // function, so spreading cannot let `workstreamContext` reintroduce a workstream.
960
978
  if (wsRequested && rootParsed) {
961
- const fb = loadConfigResolved(cwd, { ...options, workstream: null });
979
+ const fb = loadConfigResolvedInternal(cwd, { ...options, workstream: null });
962
980
  return fallback({ config: fb.config, source: 'root', degraded: true });
963
981
  }
964
982
  // Branch B, C, D, E
@@ -966,7 +984,7 @@ function loadConfigResolved(cwd, options = {}) {
966
984
  if (rootParsed) {
967
985
  // Branch B: workstream requested but ws config.json absent; root config present.
968
986
  // (Only reached when wsRequested is false — e.g. ws='' with .planning/workstreams//config.json)
969
- const fb = loadConfigResolved(cwd, { ...options, workstream: null });
987
+ const fb = loadConfigResolvedInternal(cwd, { ...options, workstream: null });
970
988
  return fallback({ config: fb.config, source: 'root', degraded: true });
971
989
  }
972
990
  // Branch C: .planning/ exists but no config.json and no root config — federated/builtin defaults
@@ -1015,6 +1033,7 @@ function loadConfigResolved(cwd, options = {}) {
1015
1033
  resolve_model_ids: (globalDefaults['resolve_model_ids']) ?? defaults.resolve_model_ids,
1016
1034
  context_window: (globalDefaults['context_window']) ?? defaults.context_window,
1017
1035
  subagent_timeout: (globalDefaults['subagent_timeout']) ?? defaults.subagent_timeout,
1036
+ planner_stall_detection_enabled: resolvePlannerStallDetectionEnabled(globalDefaults['planner']?.['stall_detection_enabled']),
1018
1037
  model_overrides: (globalDefaults['model_overrides']) || null,
1019
1038
  models: (globalDefaults['models']) || null,
1020
1039
  granularity: (globalDefaults['granularity']) !== undefined ? globalDefaults['granularity'] : null,
@@ -1053,6 +1072,48 @@ function loadConfigResolved(cwd, options = {}) {
1053
1072
  }
1054
1073
  }
1055
1074
  }
1075
+ /**
1076
+ * #4717 — fill an empty `runtime` from the environment, then the per-install
1077
+ * marker. writeNonClaudeDefaults stamps `runtime` into the SHARED
1078
+ * ~/.gsd/defaults.json with whichever non-Claude runtime installed first, so
1079
+ * direct readers of config.runtime saw another runtime's identity (or
1080
+ * nothing) on a multi-runtime machine. Copy-on-write: the builtin-defaults
1081
+ * branch returns a shared object, so never assign into it. An explicit
1082
+ * config.runtime is never overridden.
1083
+ */
1084
+ function fillRuntimeIdentity(resolved) {
1085
+ const cfg = resolved?.config;
1086
+ if (!cfg)
1087
+ return resolved;
1088
+ const runtime = (0, runtime_name_policy_cjs_1.resolveRuntimeNameFromCandidates)(process.env['GSD_RUNTIME'], (0, runtime_slash_cjs_1.readInstallRuntimeMarker)());
1089
+ // Only a runtime the name policy can canonicalize is an identity. Unknown
1090
+ // tokens pass THROUGH resolveRuntimeNameFromCandidates (future-runtime
1091
+ // tolerance) and must not be materialized into config.runtime, where ~30
1092
+ // consumers would read them — fail safe to no identity (#4717 review).
1093
+ const canonicalRuntime = runtime ? (0, runtime_name_policy_cjs_1.canonicalizeRuntimeName)(runtime) : null;
1094
+ if (!canonicalRuntime)
1095
+ return resolved;
1096
+ // Rung 1 — empty runtime: materialize THIS install's identity (env, then
1097
+ // the per-install marker). An explicit runtime is never overridden.
1098
+ if (!cfg['runtime']) {
1099
+ return { ...resolved, config: { ...cfg, runtime: canonicalRuntime } };
1100
+ }
1101
+ // Rung 2 (#4717 stamped-defaults leg): the global-defaults branch forwards
1102
+ // the SHARED ~/.gsd/defaults.json's runtime verbatim — whichever non-Claude
1103
+ // runtime installed FIRST stamped that machine-wide file, and every other
1104
+ // runtime's resolution inherited its identity (the issue's second failure
1105
+ // shape). When THIS install carries its own identity (GSD_RUNTIME or the
1106
+ // marker), the stamp — not the operator — is speaking: correct it. Project
1107
+ // and workstream configs are explicit operator intent and are never touched;
1108
+ // with no install identity of its own the stamped value stays (status quo).
1109
+ if (resolved.source === 'global-defaults') {
1110
+ return { ...resolved, config: { ...cfg, runtime: canonicalRuntime } };
1111
+ }
1112
+ return resolved;
1113
+ }
1114
+ function loadConfigResolved(cwd, options = {}) {
1115
+ return fillRuntimeIdentity(loadConfigResolvedInternal(cwd, options));
1116
+ }
1056
1117
  /**
1057
1118
  * loadConfig — backwards-compatible config loading, now a thin wrapper over loadConfigResolved.
1058
1119
  * Returns the config object only; for provenance metadata use loadConfigResolved.
@@ -1071,6 +1132,7 @@ module.exports = {
1071
1132
  _getNestedConfigDefault,
1072
1133
  _getConfigValue,
1073
1134
  _getConfigNested,
1135
+ resolvePlannerStallDetectionEnabled,
1074
1136
  _deepMergeConfig,
1075
1137
  _warnedUnknownConfigKeys,
1076
1138
  _warnedShadowedGlobalKeys,
@@ -20,11 +20,11 @@ const cliExitMod = require("./cli-exit.cjs");
20
20
  const { ExitError } = cliExitMod;
21
21
  // eslint-disable-next-line @typescript-eslint/no-require-imports
22
22
  const configLoader = require("./config-loader.cjs");
23
- const { CONFIG_DEFAULTS } = configLoader;
23
+ const { CONFIG_DEFAULTS, resolvePlannerStallDetectionEnabled } = configLoader;
24
24
  const shell_command_projection_cjs_1 = require("./shell-command-projection.cjs");
25
25
  // eslint-disable-next-line @typescript-eslint/no-require-imports
26
26
  const planningWorkspace = require("./planning-workspace.cjs");
27
- const { planningDir, planningRoot, withPlanningLock } = planningWorkspace;
27
+ const { planningDir, planningRoot, resolveEnvWorkstream, withPlanningLock } = planningWorkspace;
28
28
  // eslint-disable-next-line @typescript-eslint/no-require-imports
29
29
  const modelProfiles = require("./model-profiles.cjs");
30
30
  const { VALID_PROFILES, getAgentToModelMapForProfile, formatAgentToModelMapAsTable } = modelProfiles;
@@ -66,6 +66,14 @@ const SHIP_PR_BODY_TEMPLATE_TOKENS = new Set([
66
66
  'padded_phase',
67
67
  ]);
68
68
  const SHIP_PR_BODY_SOURCE_RE = /^(ROADMAP|PLAN|SUMMARY|VERIFICATION|STATE|REQUIREMENTS|CONTEXT)\.md\s+##\s+[^\r\n#][^\r\n]*$/;
69
+ // ADR-612 PR-5: configuration accepts every convention the runtime can read.
70
+ // Keep this distinct from roadmap-upgrade's supported target set: sequential
71
+ // is valid project configuration but is not a migration destination.
72
+ const VALID_PHASE_ID_CONVENTIONS = Object.freeze([
73
+ 'sequential',
74
+ 'milestone-prefixed',
75
+ 'bracket',
76
+ ]);
69
77
  /**
70
78
  * Schema-level defaults for well-known config keys.
71
79
  * When a key is absent from config.json and no --default flag was supplied,
@@ -75,12 +83,18 @@ const SCHEMA_DEFAULTS = {
75
83
  'context_window': 200000,
76
84
  'executor.stall_detect_interval_minutes': 5,
77
85
  'executor.stall_threshold_minutes': 10,
86
+ 'planner.stall_detection_enabled': CONFIG_DEFAULTS.planner_stall_detection_enabled,
78
87
  'planner.stall_detect_interval_minutes': 5,
79
88
  'planner.stall_threshold_minutes': 10,
80
89
  'git.create_tag': true,
81
90
  // #1689: per-plan agent_hint executor routing — default-on. A no-op for plans
82
91
  // without an agent_hint field, so existing dispatch is byte-identical.
83
92
  'workflow.agent_hint_routing': true,
93
+ // #4401: Compact Content mode gate — derived from the defaults manifest via
94
+ // CONFIG_DEFAULTS (added in config-loader.cts) so the manifest stays the
95
+ // single source of truth, matching workflow.smart_zone_tokens /
96
+ // planning.pr_strict / workflow.inline_plan_threshold below.
97
+ 'workflow.compact_content': CONFIG_DEFAULTS.compact_content,
84
98
  // Derived from the defaults manifest rather than restated, so the manifest
85
99
  // stays the single source of truth for the smart-zone budget (#2630).
86
100
  'workflow.smart_zone_tokens': CONFIG_DEFAULTS.smart_zone_tokens,
@@ -93,6 +107,27 @@ const SCHEMA_DEFAULTS = {
93
107
  // effective default existed only as the workflow's shell fallback and the
94
108
  // docs disagreed (settings-advanced said 3). Manifest stays the one owner.
95
109
  'workflow.inline_plan_threshold': CONFIG_DEFAULTS.inline_plan_threshold,
110
+ // #4285 review: an absent threshold resolved to "Key not found" while the
111
+ // hook silently used 35/25 — the query surface disagreeing with the reader.
112
+ //
113
+ // Restated here rather than derived: `CONFIG_DEFAULTS` is re-exported with a
114
+ // FLATTENED shape that drops the manifest's nested blocks, so
115
+ // `CONFIG_DEFAULTS.hooks` is undefined at runtime and the manifest cannot
116
+ // feed these two rows the way `workflow.smart_zone_tokens` above is fed.
117
+ //
118
+ // Not added to `buildNewProjectConfig` either, and that one is deliberate
119
+ // rather than incidental: it writes a `hooks` object into every NEW project's
120
+ // config.json, which would freeze today's fire-points as an explicit
121
+ // per-project override everywhere — the opposite of this PR's premise that an
122
+ // absent key tracks the shipped default. (The manifest alone would NOT have
123
+ // that effect; `buildNewProjectConfig` builds its own literal. Correcting an
124
+ // earlier version of this comment that ran the two together.)
125
+ //
126
+ // That leaves ONE copy of 35/25 outside the hook — these two rows — and
127
+ // `tests/config.test.cjs` pins them against the hook's exported
128
+ // WARNING_THRESHOLD/CRITICAL_THRESHOLD so the copies cannot drift.
129
+ 'hooks.context_warning_threshold': 35,
130
+ 'hooks.context_critical_threshold': 25,
96
131
  };
97
132
  /**
98
133
  * Resolve a schema-level default for an absent key (#2256). Checks the legacy
@@ -126,12 +161,15 @@ function resolveSchemaDefault(cwd, kp) {
126
161
  * Centralizing emission here means masking can't be missed at a call site.
127
162
  */
128
163
  function emitResolvedDefault(kp, value, raw) {
164
+ const resolvedValue = kp === 'planner.stall_detection_enabled'
165
+ ? resolvePlannerStallDetectionEnabled(value)
166
+ : value;
129
167
  if ((0, secrets_cjs_1.isSecretKey)(kp)) {
130
- const masked = (0, secrets_cjs_1.maskSecret)(value);
168
+ const masked = (0, secrets_cjs_1.maskSecret)(resolvedValue);
131
169
  output(masked, raw, masked);
132
170
  return;
133
171
  }
134
- output(value, raw, String(value));
172
+ output(resolvedValue, raw, String(resolvedValue));
135
173
  }
136
174
  // ─── Validation helpers ───────────────────────────────────────────────────────
137
175
  function validateKnownConfigKeyPath(keyPath) {
@@ -308,6 +346,7 @@ function buildNewProjectConfig(userChoices) {
308
346
  human_verify_mode: 'end-of-phase',
309
347
  context_guard_mode: 'warn',
310
348
  text_mode: false,
349
+ compact_content: false,
311
350
  research_before_questions: false,
312
351
  discuss_mode: 'discuss',
313
352
  skip_discuss: false,
@@ -515,7 +554,7 @@ function _setNestedValue(config, keyPath, parsedValue) {
515
554
  }
516
555
  /**
517
556
  * Deletes a value from the config object, allowing nested values via dot
518
- * notation (e.g., "review.models.gemini"). Mirrors `_setNestedValue`'s
557
+ * notation (e.g., "review.models.codex"). Mirrors `_setNestedValue`'s
519
558
  * prototype-pollution guard on every path segment (including intermediates).
520
559
  *
521
560
  * Unlike `_setNestedValue`, this NEVER creates missing intermediate objects —
@@ -564,19 +603,35 @@ function _unsetNestedValue(config, keyPath) {
564
603
  * Does not call `output()`, so can be used as one step in a command without triggering `exit(0)` in
565
604
  * the happy path. But note that `error()` will still `exit(1)` out of the process.
566
605
  */
606
+ /**
607
+ * Loads `.planning/config.json` as a plain object, or `{}` if the file does
608
+ * not exist. A parse failure calls `error()` (process-exiting) rather than
609
+ * throwing, matching every caller's existing behavior.
610
+ *
611
+ * Single source for this load+parse step — `setConfigValue`,
612
+ * `unsetConfigValue`, `setConfigValues`, `previewConfigValue`, and
613
+ * `previewUnsetConfigValue` all delegate here instead of each repeating the
614
+ * same try/catch (CLAUDE.md's "Generative Fix Divergence" known-defect
615
+ * pattern: independently-guessed copies of the same logic can silently
616
+ * drift apart).
617
+ */
618
+ function loadConfigJson(cwd) {
619
+ const configPath = node_path_1.default.join(planningDir(cwd), 'config.json');
620
+ let config = {};
621
+ try {
622
+ if (node_fs_1.default.existsSync(configPath)) {
623
+ config = JSON.parse(node_fs_1.default.readFileSync(configPath, 'utf-8'));
624
+ }
625
+ }
626
+ catch (err) {
627
+ error('Failed to read config.json: ' + err.message, ERROR_REASON.CONFIG_PARSE_FAILED);
628
+ }
629
+ return config;
630
+ }
567
631
  function unsetConfigValue(cwd, keyPath) {
568
632
  const configPath = node_path_1.default.join(planningDir(cwd), 'config.json');
569
633
  return withPlanningLock(cwd, () => {
570
- // Load existing config or start with empty object
571
- let config = {};
572
- try {
573
- if (node_fs_1.default.existsSync(configPath)) {
574
- config = JSON.parse(node_fs_1.default.readFileSync(configPath, 'utf-8'));
575
- }
576
- }
577
- catch (err) {
578
- error('Failed to read config.json: ' + err.message, ERROR_REASON.CONFIG_PARSE_FAILED);
579
- }
634
+ const config = loadConfigJson(cwd);
580
635
  const { previousValue, existed } = _unsetNestedValue(config, keyPath);
581
636
  // Write back
582
637
  try {
@@ -598,16 +653,7 @@ function unsetConfigValue(cwd, keyPath) {
598
653
  function setConfigValue(cwd, keyPath, parsedValue) {
599
654
  const configPath = node_path_1.default.join(planningDir(cwd), 'config.json');
600
655
  return withPlanningLock(cwd, () => {
601
- // Load existing config or start with empty object
602
- let config = {};
603
- try {
604
- if (node_fs_1.default.existsSync(configPath)) {
605
- config = JSON.parse(node_fs_1.default.readFileSync(configPath, 'utf-8'));
606
- }
607
- }
608
- catch (err) {
609
- error('Failed to read config.json: ' + err.message, ERROR_REASON.CONFIG_PARSE_FAILED);
610
- }
656
+ const config = loadConfigJson(cwd);
611
657
  const previousValue = _setNestedValue(config, keyPath, parsedValue);
612
658
  // Write back
613
659
  try {
@@ -619,6 +665,29 @@ function setConfigValue(cwd, keyPath, parsedValue) {
619
665
  }
620
666
  });
621
667
  }
668
+ /**
669
+ * #4444: read-only preview counterpart to `setConfigValue` — loads config
670
+ * exactly like the real setter and reuses `_setNestedValue` (the SAME
671
+ * traversal/creation logic, including its prototype-pollution guards) on a
672
+ * throwaway in-memory copy that is NEVER written back to disk. This is what
673
+ * makes the dry-run preview provably identical to what the real write would
674
+ * compute, rather than a second, hand-maintained traversal that could drift
675
+ * from the real one.
676
+ */
677
+ function previewConfigValue(cwd, keyPath, parsedValue) {
678
+ const config = loadConfigJson(cwd);
679
+ const previousValue = _setNestedValue(config, keyPath, parsedValue);
680
+ return { key: keyPath, value: parsedValue, previousValue };
681
+ }
682
+ /**
683
+ * #4444: read-only preview counterpart to `unsetConfigValue` — same pattern
684
+ * as `previewConfigValue`, reusing `_unsetNestedValue` on a throwaway copy.
685
+ */
686
+ function previewUnsetConfigValue(cwd, keyPath) {
687
+ const config = loadConfigJson(cwd);
688
+ const { previousValue, existed } = _unsetNestedValue(config, keyPath);
689
+ return { key: keyPath, value: null, previousValue, existed };
690
+ }
622
691
  /**
623
692
  * Batched sibling of setConfigValue: apply multiple key-path writes in a
624
693
  * single load → set-all → write cycle inside ONE withPlanningLock call.
@@ -635,16 +704,7 @@ function setConfigValues(cwd, entries) {
635
704
  }
636
705
  const configPath = node_path_1.default.join(planningDir(cwd), 'config.json');
637
706
  return withPlanningLock(cwd, () => {
638
- // Load existing config or start with empty object
639
- let config = {};
640
- try {
641
- if (node_fs_1.default.existsSync(configPath)) {
642
- config = JSON.parse(node_fs_1.default.readFileSync(configPath, 'utf-8'));
643
- }
644
- }
645
- catch (err) {
646
- error('Failed to read config.json: ' + err.message, ERROR_REASON.CONFIG_PARSE_FAILED);
647
- }
707
+ const config = loadConfigJson(cwd);
648
708
  const results = [];
649
709
  for (const entry of entries) {
650
710
  const previousValue = _setNestedValue(config, entry.keyPath, entry.value);
@@ -676,14 +736,8 @@ function assertEnumValue(parsedValue, rawVal, allowed, label) {
676
736
  error(`Invalid ${label} '${rawVal}'. Valid values: ${allowed.join(', ')}`);
677
737
  }
678
738
  }
679
- /**
680
- * Command to set a value in the config file, allowing nested values via dot notation (e.g.,
681
- * "workflow.research").
682
- *
683
- * Note that this exits the process (via `output()`) even in the happy path; use `setConfigValue()`
684
- * directly if you need to avoid this.
685
- */
686
- function cmdConfigSet(cwd, keyPath, value, raw) {
739
+ function cmdConfigSet(cwd, keyPath, value, raw, options = {}) {
740
+ const dryRun = options.dryRun === true;
687
741
  if (!keyPath) {
688
742
  error('Usage: config-set <key.path> <value>', ERROR_REASON.USAGE);
689
743
  }
@@ -733,6 +787,18 @@ function cmdConfigSet(cwd, keyPath, value, raw) {
733
787
  // present, truthy-adjacent value that consumers must special-case — worst for
734
788
  // secret keys where a leftover value can be passed as a real credential.
735
789
  if (parsedValue === null) {
790
+ if (dryRun) {
791
+ const preview = previewUnsetConfigValue(cwd, kp);
792
+ if ((0, secrets_cjs_1.isSecretKey)(kp)) {
793
+ const maskedPrev = preview.previousValue === undefined
794
+ ? undefined
795
+ : (0, secrets_cjs_1.maskSecret)(preview.previousValue);
796
+ output({ dry_run: true, would_unset: true, key: kp, value: null, previousValue: maskedPrev, masked: true }, raw, `${kp} unset (dry run)`);
797
+ return;
798
+ }
799
+ output({ dry_run: true, would_unset: true, key: kp, value: null, previousValue: preview.previousValue }, raw, `${kp} unset (dry run)`);
800
+ return;
801
+ }
736
802
  const unsetResult = unsetConfigValue(cwd, kp);
737
803
  if ((0, secrets_cjs_1.isSecretKey)(kp)) {
738
804
  const maskedPrev = unsetResult.previousValue === undefined
@@ -752,6 +818,9 @@ function cmdConfigSet(cwd, keyPath, value, raw) {
752
818
  const VALID_CONTEXT_VALUES = ['dev', 'research', 'review'];
753
819
  if (kp === 'context')
754
820
  assertEnumValue(parsedValue, val, VALID_CONTEXT_VALUES, 'context value');
821
+ if (kp === 'phase_id_convention') {
822
+ assertEnumValue(parsedValue, val, VALID_PHASE_ID_CONVENTIONS, 'phase_id_convention');
823
+ }
755
824
  // Codebase drift detector (#2003)
756
825
  const VALID_DRIFT_ACTIONS = ['warn', 'auto-remap'];
757
826
  if (kp === 'workflow.drift_action')
@@ -787,12 +856,25 @@ function cmdConfigSet(cwd, keyPath, value, raw) {
787
856
  error(`Invalid workflow.post_planning_gaps '${val}'. Must be a boolean (true or false).`);
788
857
  }
789
858
  }
859
+ // Compact Content mode gate (#4139)
860
+ if (kp === 'workflow.compact_content') {
861
+ if (typeof parsedValue !== 'boolean') {
862
+ error(`Invalid workflow.compact_content '${val}'. Must be a boolean (true or false).`);
863
+ }
864
+ }
790
865
  // Per-plan executor routing via agent_hint frontmatter (#1689)
791
866
  if (kp === 'workflow.agent_hint_routing') {
792
867
  if (typeof parsedValue !== 'boolean') {
793
868
  error(`Invalid workflow.agent_hint_routing '${val}'. Must be a boolean (true or false).`);
794
869
  }
795
870
  }
871
+ // Planner watchdog opt-out (#4570) — only a real boolean may change the
872
+ // default-on policy. In particular, string "false" must not disable it.
873
+ if (kp === 'planner.stall_detection_enabled') {
874
+ if (typeof parsedValue !== 'boolean') {
875
+ error(`Invalid planner.stall_detection_enabled '${val}'. Must be a boolean (true or false).`);
876
+ }
877
+ }
796
878
  // #3086 — git.create_tag: boolean only
797
879
  if (kp === 'git.create_tag') {
798
880
  if (typeof parsedValue !== 'boolean') {
@@ -835,6 +917,48 @@ function cmdConfigSet(cwd, keyPath, value, raw) {
835
917
  error(`Invalid statusline.show_git '${val}'. Must be a boolean (true or false).`);
836
918
  }
837
919
  }
920
+ // Context-monitor fire-points (#4285) — a percentage of the context window
921
+ // REMAINING, so the domain is 0-100 and the hook compares them against
922
+ // `remaining_percentage`. Rejecting an out-of-domain value here keeps accept
923
+ // and honour in agreement ON THE DOMAIN: the hook falls back to its default
924
+ // for a value outside it, so reporting success would be a lie. That agreement
925
+ // is per-key and no wider — a value accepted here can still be superseded at
926
+ // read time by the hook's pair check, and a scoped write (GSD_PROJECT /
927
+ // GSD_WORKSTREAM) lands in a config the hook does not read at all. The PAIR
928
+ // (critical < warning) is deliberately NOT enforced here: config-set writes
929
+ // one key per call, so a two-step retune can be transiently inconsistent on
930
+ // disk and a check here would reject that intermediate write.
931
+ if (kp === 'hooks.context_warning_threshold' || kp === 'hooks.context_critical_threshold') {
932
+ if (typeof parsedValue !== 'number' || !Number.isFinite(parsedValue) || parsedValue < 0 || parsedValue > 100) {
933
+ error(`Invalid ${kp} '${val}'. Must be a number between 0 and 100 (percent of context window remaining).`);
934
+ }
935
+ // The two ENDPOINTS that are in range but can never form a valid pair are
936
+ // refused here rather than stored (#4285 review). `critical < warning` must
937
+ // hold at read time and BOTH sides are clamped to 0-100, so `warning: 0`
938
+ // has no legal partner (nothing is below 0) and `critical: 100` has none
939
+ // either (nothing above 100). Either one is silently discarded by the hook
940
+ // for EVERY value of the other key — verified: both resolve to the 35/25
941
+ // defaults against a present, absent, or extreme partner, while 0.001 and
942
+ // 99.999 are honoured.
943
+ //
944
+ // Storing a value the reader can never honour is exactly the
945
+ // accept-then-discard shape this codebase refuses elsewhere, so this fails
946
+ // at write time where the operator can see it. The pair itself is still NOT
947
+ // checked here — config-set writes one key per call, so a two-step retune
948
+ // is legitimately inconsistent on disk in between.
949
+ if (kp === 'hooks.context_warning_threshold' && parsedValue === 0) {
950
+ error(`Invalid ${kp} '${val}'. 0 is in range but unusable: the monitor requires `
951
+ + `hooks.context_critical_threshold < hooks.context_warning_threshold, and no valid `
952
+ + `critical value is below 0, so a warning of 0 would always fall back to the 35/25 `
953
+ + `defaults. Use a value above 0.`);
954
+ }
955
+ if (kp === 'hooks.context_critical_threshold' && parsedValue === 100) {
956
+ error(`Invalid ${kp} '${val}'. 100 is in range but unusable: the monitor requires `
957
+ + `hooks.context_critical_threshold < hooks.context_warning_threshold, and no valid `
958
+ + `warning value is above 100, so a critical of 100 would always fall back to the `
959
+ + `35/25 defaults. Use a value below 100.`);
960
+ }
961
+ }
838
962
  // Fallow scope + profile enum validation (#3424)
839
963
  const VALID_FALLOW_SCOPES = ['phase', 'repo'];
840
964
  if (kp === 'code_quality.fallow.scope')
@@ -921,6 +1045,19 @@ function cmdConfigSet(cwd, keyPath, value, raw) {
921
1045
  }
922
1046
  }
923
1047
  }
1048
+ if (dryRun) {
1049
+ const preview = previewConfigValue(cwd, kp, parsedValue);
1050
+ if ((0, secrets_cjs_1.isSecretKey)(kp)) {
1051
+ const masked = (0, secrets_cjs_1.maskSecret)(parsedValue);
1052
+ const maskedPrev = preview.previousValue === undefined
1053
+ ? undefined
1054
+ : (0, secrets_cjs_1.maskSecret)(preview.previousValue);
1055
+ output({ dry_run: true, would_update: true, key: kp, value: masked, previousValue: maskedPrev, masked: true }, raw, `${kp}=${masked} (dry run)`);
1056
+ return;
1057
+ }
1058
+ output({ dry_run: true, would_update: true, key: kp, value: parsedValue, previousValue: preview.previousValue }, raw, `${kp}=${String(parsedValue)} (dry run)`);
1059
+ return;
1060
+ }
924
1061
  const setConfigValueResult = setConfigValue(cwd, kp, parsedValue);
925
1062
  // Mask secrets in both JSON and text output. The plaintext is written
926
1063
  // to config.json (that's where secrets live on disk); the CLI output
@@ -1046,6 +1183,9 @@ function cmdConfigGet(cwd, keyPath, raw, defaultValue) {
1046
1183
  }
1047
1184
  error(`Key not found: ${kp}`, ERROR_REASON.CONFIG_KEY_NOT_FOUND);
1048
1185
  }
1186
+ if (kp === 'planner.stall_detection_enabled') {
1187
+ current = resolvePlannerStallDetectionEnabled(current);
1188
+ }
1049
1189
  // Never echo plaintext for sensitive keys via config-get. Plaintext lives
1050
1190
  // in config.json on disk; the CLI surface always shows the masked form.
1051
1191
  if ((0, secrets_cjs_1.isSecretKey)(kp)) {
@@ -1075,7 +1215,7 @@ function resolveFromRootConfig(cwd, kp) {
1075
1215
  // diverges from planningRoot without a workstream and loadConfigResolved does NOT
1076
1216
  // inherit root — matching the runtime's own `if (ws)` gate keeps the two surfaces
1077
1217
  // from diverging on the project-scoped (non-workstream) case.
1078
- if (!process.env['GSD_WORKSTREAM'])
1218
+ if (!resolveEnvWorkstream())
1079
1219
  return { found: false, value: undefined };
1080
1220
  const root = planningRoot(cwd);
1081
1221
  const rootConfigPath = node_path_1.default.join(root, 'config.json');
@@ -1182,7 +1322,7 @@ function cmdConfigPath(cwd, _raw, workstreamContext = null) {
1182
1322
  * (caller uses `await` which is safe on a sync return value).
1183
1323
  */
1184
1324
  function cmdMigrateConfig(cwd, raw) {
1185
- const ws = process.env['GSD_WORKSTREAM'] || null;
1325
+ const ws = resolveEnvWorkstream();
1186
1326
  // #3749: resolve the migration target through the project-aware resolver so
1187
1327
  // GSD_PROJECT scopes the write; migrateOnDisk itself cannot (see its
1188
1328
  // configPathOverride note).
@@ -1230,6 +1370,7 @@ function cmdMigrateConfig(cwd, raw) {
1230
1370
  }
1231
1371
  module.exports = {
1232
1372
  VALID_CONFIG_KEYS,
1373
+ VALID_PHASE_ID_CONVENTIONS,
1233
1374
  cmdConfigEnsureSection,
1234
1375
  cmdConfigSet,
1235
1376
  cmdConfigGet,
@@ -435,7 +435,7 @@ function cmdClassify(cwd, options = {}, raw) {
435
435
  }
436
436
  let resolvedPath;
437
437
  try {
438
- resolvedPath = (0, security_cjs_1.requireSafePath)(filePath, cwd, 'SUMMARY file', { allowAbsolute: true });
438
+ resolvedPath = (0, security_cjs_1.requireSafePath)(filePath, cwd, 'SUMMARY file', security_cjs_1.PathAcceptance.AbsoluteInsideRoot);
439
439
  }
440
440
  catch (e) {
441
441
  // Emit a structured command error instead of leaking a raw stack trace.