@opengsd/gsd-core 1.11.0 → 1.13.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 (498) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/.opencode/plugins/gsd-core.js +12 -0
  4. package/agents/gsd-code-fixer.md +1 -1
  5. package/agents/gsd-debug-session-manager.md +1 -1
  6. package/agents/gsd-debugger.md +1 -1
  7. package/agents/gsd-dom-verifier.md +169 -0
  8. package/agents/gsd-eval-auditor.md +1 -1
  9. package/agents/gsd-executor.md +78 -42
  10. package/agents/gsd-framework-selector.md +1 -3
  11. package/agents/gsd-intel-updater.md +1 -1
  12. package/agents/gsd-mempalace-curator.md +0 -1
  13. package/agents/gsd-pattern-mapper.md +11 -0
  14. package/agents/gsd-phase-researcher.md +3 -1
  15. package/agents/gsd-plan-checker.md +91 -112
  16. package/agents/gsd-planner.md +20 -4
  17. package/agents/gsd-project-researcher.md +1 -1
  18. package/agents/gsd-research-synthesizer.md +2 -2
  19. package/agents/gsd-roadmapper.md +15 -11
  20. package/agents/gsd-ui-checker.md +82 -7
  21. package/agents/gsd-ui-researcher.md +70 -3
  22. package/agents/gsd-verifier.md +24 -2
  23. package/bin/install.js +847 -200
  24. package/commands/gsd/discuss-phase.md +1 -1
  25. package/commands/gsd/execute-phase.md +1 -1
  26. package/commands/gsd/import.md +1 -1
  27. package/commands/gsd/ns-workflow.md +2 -1
  28. package/commands/gsd/phase.md +1 -1
  29. package/commands/gsd/quick-batch.md +105 -0
  30. package/commands/gsd/quick.md +8 -4
  31. package/commands/gsd/surface.md +18 -8
  32. package/gsd-core/bin/gsd-tools.cjs +761 -100
  33. package/gsd-core/bin/lib/active-workstream-store.cjs +8 -0
  34. package/gsd-core/bin/lib/adr-parser.cjs +13 -7
  35. package/gsd-core/bin/lib/agent-install-check.cjs +162 -0
  36. package/gsd-core/bin/lib/api-coverage.cjs +30 -9
  37. package/gsd-core/bin/lib/artifacts.cjs +2 -0
  38. package/gsd-core/bin/lib/assumption-delta.cjs +30 -11
  39. package/gsd-core/bin/lib/audit.cjs +163 -41
  40. package/gsd-core/bin/lib/broken-windows.cjs +306 -28
  41. package/gsd-core/bin/lib/capability-activation.cjs +27 -0
  42. package/gsd-core/bin/lib/capability-lock.cjs +10 -4
  43. package/gsd-core/bin/lib/capability-registry.cjs +785 -144
  44. package/gsd-core/bin/lib/capability-state.cjs +25 -4
  45. package/gsd-core/bin/lib/capability-validator.cjs +321 -18
  46. package/gsd-core/bin/lib/capability-writer.cjs +14 -4
  47. package/gsd-core/bin/lib/check-command-router.cjs +229 -6
  48. package/gsd-core/bin/lib/claude-orchestration.cjs +10 -25
  49. package/gsd-core/bin/lib/cli-exit.cjs +496 -10
  50. package/gsd-core/bin/lib/clusters.cjs +1 -0
  51. package/gsd-core/bin/lib/code-review-depth.cjs +288 -0
  52. package/gsd-core/bin/lib/codex-agent-toml.cjs +410 -4
  53. package/gsd-core/bin/lib/command-aliases.cjs +16 -0
  54. package/gsd-core/bin/lib/command-arg-projection.cjs +144 -14
  55. package/gsd-core/bin/lib/command-routing-hub.cjs +31 -2
  56. package/gsd-core/bin/lib/commands.cjs +877 -54
  57. package/gsd-core/bin/lib/complexity-trigger.cjs +26 -6
  58. package/gsd-core/bin/lib/config-loader.cjs +121 -29
  59. package/gsd-core/bin/lib/config.cjs +92 -2
  60. package/gsd-core/bin/lib/configuration.cjs +129 -37
  61. package/gsd-core/bin/lib/core-utils.cjs +118 -14
  62. package/gsd-core/bin/lib/decisions.cjs +213 -1
  63. package/gsd-core/bin/lib/edge-probe.cjs +23 -2
  64. package/gsd-core/bin/lib/estimate-cli.cjs +55 -11
  65. package/gsd-core/bin/lib/exit-code-registry.cjs +98 -0
  66. package/gsd-core/bin/lib/file-overlap-partitioner.cjs +74 -0
  67. package/gsd-core/bin/lib/frontmatter.cjs +975 -326
  68. package/gsd-core/bin/lib/gap-checker.cjs +41 -8
  69. package/gsd-core/bin/lib/git-base-branch.cjs +182 -39
  70. package/gsd-core/bin/lib/health-diagnostic-rules/consistency.cjs +7 -3
  71. package/gsd-core/bin/lib/health-diagnostic-rules/phase-structure.cjs +8 -2
  72. package/gsd-core/bin/lib/health-diagnostic-rules/roadmap-disk-consistency.cjs +60 -14
  73. package/gsd-core/bin/lib/health-diagnostic-rules/state-consistency.cjs +75 -22
  74. package/gsd-core/bin/lib/health-diagnostic-rules/worktree-health.cjs +22 -8
  75. package/gsd-core/bin/lib/health-diagnostic.cjs +23 -3
  76. package/gsd-core/bin/lib/host-integration.cjs +96 -11
  77. package/gsd-core/bin/lib/init-command-router.cjs +132 -21
  78. package/gsd-core/bin/lib/init.cjs +252 -56
  79. package/gsd-core/bin/lib/install-engine.cjs +252 -15
  80. package/gsd-core/bin/lib/install-model-override-resolver.cjs +78 -1
  81. package/gsd-core/bin/lib/install-profiles.cjs +100 -18
  82. package/gsd-core/bin/lib/installer-migration-report.cjs +4 -0
  83. package/gsd-core/bin/lib/installer-migrations/010-antigravity-retire-confighome-artifacts.cjs +169 -0
  84. package/gsd-core/bin/lib/installer-migrations.cjs +10 -7
  85. package/gsd-core/bin/lib/intel.cjs +101 -26
  86. package/gsd-core/bin/lib/io.cjs +195 -15
  87. package/gsd-core/bin/lib/learnings.cjs +85 -14
  88. package/gsd-core/bin/lib/legacy-cleanup.cjs +8 -2
  89. package/gsd-core/bin/lib/loop-resolver.cjs +14 -8
  90. package/gsd-core/bin/lib/markdown-table.cjs +175 -4
  91. package/gsd-core/bin/lib/milestone.cjs +112 -7
  92. package/gsd-core/bin/lib/model-catalog.cjs +177 -19
  93. package/gsd-core/bin/lib/model-resolver.cjs +10 -28
  94. package/gsd-core/bin/lib/onboard-projection.cjs +5 -1
  95. package/gsd-core/bin/lib/phase-command-router.cjs +13 -6
  96. package/gsd-core/bin/lib/phase-estimation.cjs +17 -8
  97. package/gsd-core/bin/lib/phase-id.cjs +321 -13
  98. package/gsd-core/bin/lib/phase-lifecycle.cjs +24 -16
  99. package/gsd-core/bin/lib/phase-locator.cjs +138 -17
  100. package/gsd-core/bin/lib/phase.cjs +1175 -115
  101. package/gsd-core/bin/lib/plan-document.cjs +273 -0
  102. package/gsd-core/bin/lib/plan-scan.cjs +13 -2
  103. package/gsd-core/bin/lib/planning-command-router.cjs +61 -0
  104. package/gsd-core/bin/lib/planning-inspect.cjs +1168 -0
  105. package/gsd-core/bin/lib/planning-snapshot.cjs +165 -34
  106. package/gsd-core/bin/lib/planning-workspace.cjs +159 -28
  107. package/gsd-core/bin/lib/probe-core.cjs +4 -1
  108. package/gsd-core/bin/lib/profile-pipeline-command-router.cjs +50 -7
  109. package/gsd-core/bin/lib/profile-pipeline.cjs +6 -3
  110. package/gsd-core/bin/lib/quick-batch-command-router.cjs +285 -0
  111. package/gsd-core/bin/lib/quick-batch-dispatch.cjs +250 -0
  112. package/gsd-core/bin/lib/quick-batch.cjs +840 -0
  113. package/gsd-core/bin/lib/real-home-guard.cjs +419 -0
  114. package/gsd-core/bin/lib/refactor-trigger-command-router.cjs +71 -45
  115. package/gsd-core/bin/lib/review-lane-descriptor.cjs +62 -14
  116. package/gsd-core/bin/lib/review-lane-invocation.cjs +73 -1
  117. package/gsd-core/bin/lib/review-lane-runner.cjs +136 -10
  118. package/gsd-core/bin/lib/roadmap-command-router.cjs +45 -31
  119. package/gsd-core/bin/lib/roadmap-parser.cjs +577 -41
  120. package/gsd-core/bin/lib/roadmap.cjs +248 -64
  121. package/gsd-core/bin/lib/runtime-artifact-conversion.cjs +329 -41
  122. package/gsd-core/bin/lib/runtime-artifact-install-plan.cjs +16 -17
  123. package/gsd-core/bin/lib/runtime-artifact-layout.cjs +320 -109
  124. package/gsd-core/bin/lib/runtime-hooks-surface.cjs +487 -83
  125. package/gsd-core/bin/lib/runtime-identity.cjs +234 -0
  126. package/gsd-core/bin/lib/runtime-slash.cjs +72 -2
  127. package/gsd-core/bin/lib/shell-command-projection.cjs +75 -8
  128. package/gsd-core/bin/lib/smart-entry.cjs +19 -31
  129. package/gsd-core/bin/lib/spec-section.cjs +12 -7
  130. package/gsd-core/bin/lib/state-command-router.cjs +47 -18
  131. package/gsd-core/bin/lib/state-contract.cjs +359 -0
  132. package/gsd-core/bin/lib/state-document.cjs +216 -5
  133. package/gsd-core/bin/lib/state-md-schema.cjs +231 -0
  134. package/gsd-core/bin/lib/state-transition.cjs +850 -145
  135. package/gsd-core/bin/lib/state.cjs +1629 -287
  136. package/gsd-core/bin/lib/surface.cjs +33 -10
  137. package/gsd-core/bin/lib/task-command-router.cjs +111 -1
  138. package/gsd-core/bin/lib/task-content-resolution.cjs +368 -0
  139. package/gsd-core/bin/lib/tdd-red-evidence.cjs +133 -0
  140. package/gsd-core/bin/lib/teams-status.cjs +4 -1
  141. package/gsd-core/bin/lib/uat-predicate.cjs +58 -20
  142. package/gsd-core/bin/lib/uat.cjs +2542 -387
  143. package/gsd-core/bin/lib/ui-consideration-probe.cjs +9 -1
  144. package/gsd-core/bin/lib/ui-safety-gate.cjs +37 -7
  145. package/gsd-core/bin/lib/unusable-input.cjs +13 -0
  146. package/gsd-core/bin/lib/update-context.cjs +6 -2
  147. package/gsd-core/bin/lib/validate-command-router.cjs +2 -2
  148. package/gsd-core/bin/lib/validate.cjs +230 -12
  149. package/gsd-core/bin/lib/vendor/README.md +43 -5
  150. package/gsd-core/bin/lib/vendor/js-yaml.cjs +3014 -0
  151. package/gsd-core/bin/lib/verification-command-router.cjs +2 -1
  152. package/gsd-core/bin/lib/verification.cjs +287 -13
  153. package/gsd-core/bin/lib/verify-command-grounding.cjs +846 -0
  154. package/gsd-core/bin/lib/verify-command-router.cjs +1 -0
  155. package/gsd-core/bin/lib/verify.cjs +441 -56
  156. package/gsd-core/bin/lib/workstream-inventory.cjs +20 -2
  157. package/gsd-core/bin/lib/workstream-name-policy.cjs +25 -4
  158. package/gsd-core/bin/lib/worktree-base-ref.cjs +66 -12
  159. package/gsd-core/bin/lib/worktree-safety.cjs +185 -21
  160. package/gsd-core/bin/shared/config-defaults.manifest.json +7 -1
  161. package/gsd-core/bin/shared/config-schema.manifest.json +13 -0
  162. package/gsd-core/bin/shared/exit-codes.json +8 -0
  163. package/gsd-core/bin/shared/exit-codes.sh +20 -0
  164. package/gsd-core/bin/shared/model-catalog.json +8 -1
  165. package/gsd-core/bin/verify-reapply-patches.cjs +70 -3
  166. package/gsd-core/references/agent-contracts.md +6 -5
  167. package/gsd-core/references/api-coverage.md +24 -2
  168. package/gsd-core/references/autonomous-smart-discuss.md +3 -3
  169. package/gsd-core/references/checkpoints.md +37 -19
  170. package/gsd-core/references/decimal-phase-calculation.md +5 -5
  171. package/gsd-core/references/edge-probe.md +17 -5
  172. package/gsd-core/references/execute-mvp-tdd.md +18 -18
  173. package/gsd-core/references/execute-phase-between-wave-reset.md +9 -12
  174. package/gsd-core/references/execute-phase-response-language.md +6 -0
  175. package/gsd-core/references/execute-phase-wave-guard.md +11 -9
  176. package/gsd-core/references/executor-examples.md +42 -0
  177. package/gsd-core/references/failing-direction.md +78 -0
  178. package/gsd-core/references/few-shot-examples/plan-checker.md +15 -15
  179. package/gsd-core/references/gate-prompts.md +1 -1
  180. package/gsd-core/references/git-integration.md +5 -5
  181. package/gsd-core/references/git-planning-commit.md +3 -3
  182. package/gsd-core/references/gsd-run-resolver.md +1 -1
  183. package/gsd-core/references/loop-hook-dispatch.md +22 -0
  184. package/gsd-core/references/model-profiles.md +1 -1
  185. package/gsd-core/references/mvp-concepts.md +2 -2
  186. package/gsd-core/references/nyquist-compliance.md +74 -0
  187. package/gsd-core/references/offer-next.md +3 -5
  188. package/gsd-core/references/phase-argument-parsing.md +3 -3
  189. package/gsd-core/references/plan-checker-examples.md +41 -0
  190. package/gsd-core/references/planner-antipatterns.md +25 -0
  191. package/gsd-core/references/planner-chunked.md +5 -1
  192. package/gsd-core/references/planner-coupling.md +42 -0
  193. package/gsd-core/references/planner-failing-direction.md +53 -0
  194. package/gsd-core/references/planner-human-verify-mode.md +15 -1
  195. package/gsd-core/references/planner-quick-batch.md +71 -0
  196. package/gsd-core/references/planner-reviews.md +47 -0
  197. package/gsd-core/references/planner-revision.md +76 -3
  198. package/gsd-core/references/planner-verify-command-grounding.md +17 -0
  199. package/gsd-core/references/planning-config.md +39 -9
  200. package/gsd-core/references/response-language-directive.md +9 -0
  201. package/gsd-core/references/reviewer-instances.md +31 -0
  202. package/gsd-core/references/revision-loop.md +118 -11
  203. package/gsd-core/references/runtime-aware-dispatch.md +1 -1
  204. package/gsd-core/references/tdd.md +15 -12
  205. package/gsd-core/references/ui-brand.md +65 -21
  206. package/gsd-core/references/ui-consideration-probe.md +1 -1
  207. package/gsd-core/references/universal-anti-patterns.md +2 -2
  208. package/gsd-core/references/verifier-evidence-gate.md +160 -0
  209. package/gsd-core/references/verify-command-path-resolvability.md +42 -0
  210. package/gsd-core/references/verify-mvp-mode.md +1 -1
  211. package/gsd-core/references/workstream-flag.md +11 -11
  212. package/gsd-core/templates/README.md +1 -1
  213. package/gsd-core/templates/SECURITY.md +3 -3
  214. package/gsd-core/templates/UI-SPEC.md +25 -3
  215. package/gsd-core/templates/VALIDATION.md +3 -3
  216. package/gsd-core/templates/phase-prompt.md +7 -0
  217. package/gsd-core/templates/state.md +7 -0
  218. package/gsd-core/templates/verification-report.md +5 -0
  219. package/gsd-core/workflows/_runtime-launcher.snippet.sh +1 -1
  220. package/gsd-core/workflows/add-backlog.md +3 -1
  221. package/gsd-core/workflows/add-phase.md +5 -3
  222. package/gsd-core/workflows/add-tests.md +4 -9
  223. package/gsd-core/workflows/add-todo.md +2 -2
  224. package/gsd-core/workflows/ai-integration-phase.md +5 -10
  225. package/gsd-core/workflows/analyze-dependencies.md +2 -0
  226. package/gsd-core/workflows/audit-fix.md +14 -3
  227. package/gsd-core/workflows/audit-milestone.md +11 -9
  228. package/gsd-core/workflows/audit-uat.md +19 -2
  229. package/gsd-core/workflows/autonomous/steps/converge-fail-fast.md +2 -2
  230. package/gsd-core/workflows/autonomous.md +12 -26
  231. package/gsd-core/workflows/check-todos.md +2 -2
  232. package/gsd-core/workflows/cleanup.md +3 -3
  233. package/gsd-core/workflows/code-review/steps/structural-pre-pass.md +16 -14
  234. package/gsd-core/workflows/code-review-fix.md +3 -1
  235. package/gsd-core/workflows/code-review.md +192 -69
  236. package/gsd-core/workflows/complete-milestone.md +28 -14
  237. package/gsd-core/workflows/debug.md +6 -4
  238. package/gsd-core/workflows/diagnose-issues.md +17 -7
  239. package/gsd-core/workflows/discuss-phase/modes/advisor.md +3 -1
  240. package/gsd-core/workflows/discuss-phase/modes/all.md +2 -0
  241. package/gsd-core/workflows/discuss-phase/modes/analyze.md +2 -0
  242. package/gsd-core/workflows/discuss-phase/modes/auto.md +2 -0
  243. package/gsd-core/workflows/discuss-phase/modes/batch.md +2 -0
  244. package/gsd-core/workflows/discuss-phase/modes/chain.md +5 -7
  245. package/gsd-core/workflows/discuss-phase/modes/default.md +2 -0
  246. package/gsd-core/workflows/discuss-phase/modes/power.md +2 -0
  247. package/gsd-core/workflows/discuss-phase/modes/text.md +3 -1
  248. package/gsd-core/workflows/discuss-phase/templates/context.md +2 -0
  249. package/gsd-core/workflows/discuss-phase/templates/discussion-log.md +2 -0
  250. package/gsd-core/workflows/discuss-phase-assumptions/steps/auto-advance-dispatch.md +1 -3
  251. package/gsd-core/workflows/discuss-phase-assumptions.md +3 -3
  252. package/gsd-core/workflows/discuss-phase-power.md +2 -0
  253. package/gsd-core/workflows/discuss-phase.md +2 -2
  254. package/gsd-core/workflows/do.md +46 -19
  255. package/gsd-core/workflows/docs-update.md +6 -5
  256. package/gsd-core/workflows/edit-phase.md +3 -1
  257. package/gsd-core/workflows/eval-review.md +5 -10
  258. package/gsd-core/workflows/execute-phase/steps/codebase-drift-gate.md +3 -1
  259. package/gsd-core/workflows/execute-phase/steps/executor-isolation-dispatch.md +129 -11
  260. package/gsd-core/workflows/execute-phase/steps/gap-closure-artifacts.md +1 -1
  261. package/gsd-core/workflows/execute-phase/steps/partial-wave.md +1 -1
  262. package/gsd-core/workflows/execute-phase/steps/per-plan-executor-routing.md +1 -1
  263. package/gsd-core/workflows/execute-phase/steps/per-plan-worktree-gate.md +29 -5
  264. package/gsd-core/workflows/execute-phase/steps/post-merge-gate.md +2 -2
  265. package/gsd-core/workflows/execute-phase/steps/protected-branch.md +21 -0
  266. package/gsd-core/workflows/execute-phase/steps/regression-gate-run.md +4 -2
  267. package/gsd-core/workflows/execute-phase/steps/tdd-applicability-resolution.md +25 -0
  268. package/gsd-core/workflows/execute-phase/steps/wave-post-gate-hooks.md +39 -0
  269. package/gsd-core/workflows/execute-phase/steps/worktree-recovery-policy.md +2 -0
  270. package/gsd-core/workflows/execute-phase.md +68 -66
  271. package/gsd-core/workflows/execute-plan.md +25 -20
  272. package/gsd-core/workflows/explore.md +3 -1
  273. package/gsd-core/workflows/extract-learnings.md +3 -1
  274. package/gsd-core/workflows/fast.md +8 -2
  275. package/gsd-core/workflows/forensics.md +3 -1
  276. package/gsd-core/workflows/graduation.md +6 -6
  277. package/gsd-core/workflows/health.md +4 -7
  278. package/gsd-core/workflows/help/modes/brief.md +2 -0
  279. package/gsd-core/workflows/help/modes/default.md +2 -0
  280. package/gsd-core/workflows/help/modes/full.md +12 -0
  281. package/gsd-core/workflows/help/modes/topic.md +2 -0
  282. package/gsd-core/workflows/help.md +2 -0
  283. package/gsd-core/workflows/import.md +17 -14
  284. package/gsd-core/workflows/inbox.md +5 -6
  285. package/gsd-core/workflows/ingest-docs.md +45 -12
  286. package/gsd-core/workflows/insert-phase.md +7 -5
  287. package/gsd-core/workflows/list-phase-assumptions.md +2 -0
  288. package/gsd-core/workflows/list-seeds.md +7 -3
  289. package/gsd-core/workflows/list-workspaces.md +3 -1
  290. package/gsd-core/workflows/manager.md +15 -26
  291. package/gsd-core/workflows/map-codebase.md +3 -1
  292. package/gsd-core/workflows/milestone-summary.md +3 -1
  293. package/gsd-core/workflows/mvp-phase.md +3 -3
  294. package/gsd-core/workflows/new-milestone.md +10 -22
  295. package/gsd-core/workflows/new-project/steps/auto-mode-config.md +1 -1
  296. package/gsd-core/workflows/new-project.md +17 -29
  297. package/gsd-core/workflows/new-workspace.md +2 -2
  298. package/gsd-core/workflows/next.md +4 -2
  299. package/gsd-core/workflows/node-repair.md +2 -0
  300. package/gsd-core/workflows/note.md +2 -0
  301. package/gsd-core/workflows/onboard.md +1 -1
  302. package/gsd-core/workflows/pause-work.md +20 -5
  303. package/gsd-core/workflows/plan-phase/steps/adr-ingest-express-path.md +1 -1
  304. package/gsd-core/workflows/plan-phase/steps/chunked-planning-mode.md +100 -18
  305. package/gsd-core/workflows/plan-phase/steps/prd-express-path.md +4 -4
  306. package/gsd-core/workflows/plan-phase/steps/stall-detection-helpers.md +12 -3
  307. package/gsd-core/workflows/plan-phase.md +251 -54
  308. package/gsd-core/workflows/plan-review-convergence.md +148 -19
  309. package/gsd-core/workflows/plant-seed.md +3 -3
  310. package/gsd-core/workflows/pr-branch.md +195 -51
  311. package/gsd-core/workflows/profile-user.md +17 -15
  312. package/gsd-core/workflows/progress/steps/forensic-audit.md +1 -1
  313. package/gsd-core/workflows/progress.md +52 -15
  314. package/gsd-core/workflows/quick/steps/discussion-phase.md +1 -3
  315. package/gsd-core/workflows/quick/steps/plan-checker-loop.md +38 -5
  316. package/gsd-core/workflows/quick/steps/quick-verification.md +2 -4
  317. package/gsd-core/workflows/quick/steps/research-phase.md +5 -7
  318. package/gsd-core/workflows/quick/steps/worktree-pre-dispatch-commit.md +3 -3
  319. package/gsd-core/workflows/quick-batch/steps/batch-init.md +55 -0
  320. package/gsd-core/workflows/quick-batch/steps/completion.md +65 -0
  321. package/gsd-core/workflows/quick-batch/steps/merge-wave.md +100 -0
  322. package/gsd-core/workflows/quick-batch/steps/plan-checker-loop.md +147 -0
  323. package/gsd-core/workflows/quick-batch/steps/planner-wave.md +158 -0
  324. package/gsd-core/workflows/quick-batch/steps/research-phase.md +95 -0
  325. package/gsd-core/workflows/quick-batch/steps/resume-mode.md +49 -0
  326. package/gsd-core/workflows/quick-batch/steps/verification-wave.md +73 -0
  327. package/gsd-core/workflows/quick-batch/steps/worktree-dispatch.md +169 -0
  328. package/gsd-core/workflows/quick-batch.md +203 -0
  329. package/gsd-core/workflows/quick.md +33 -32
  330. package/gsd-core/workflows/reapply-patches.md +2 -0
  331. package/gsd-core/workflows/remove-phase.md +6 -4
  332. package/gsd-core/workflows/remove-workspace.md +3 -3
  333. package/gsd-core/workflows/resume-project.md +14 -14
  334. package/gsd-core/workflows/review.md +404 -21
  335. package/gsd-core/workflows/scan.md +3 -1
  336. package/gsd-core/workflows/section-manifest.json +12 -0
  337. package/gsd-core/workflows/secure-phase.md +3 -3
  338. package/gsd-core/workflows/session-report.md +2 -0
  339. package/gsd-core/workflows/settings-advanced.md +9 -9
  340. package/gsd-core/workflows/settings-integrations.md +66 -32
  341. package/gsd-core/workflows/settings.md +4 -6
  342. package/gsd-core/workflows/ship.md +22 -16
  343. package/gsd-core/workflows/sketch-wrap-up.md +13 -17
  344. package/gsd-core/workflows/sketch.md +13 -19
  345. package/gsd-core/workflows/smart-entry.md +4 -6
  346. package/gsd-core/workflows/spec-phase.md +31 -4
  347. package/gsd-core/workflows/spike-wrap-up.md +9 -11
  348. package/gsd-core/workflows/spike.md +21 -32
  349. package/gsd-core/workflows/stats.md +4 -2
  350. package/gsd-core/workflows/sync-skills.md +13 -5
  351. package/gsd-core/workflows/thread.md +13 -7
  352. package/gsd-core/workflows/transition.md +7 -5
  353. package/gsd-core/workflows/ui-phase.md +36 -21
  354. package/gsd-core/workflows/ui-review.md +7 -11
  355. package/gsd-core/workflows/ultraplan-phase.md +7 -13
  356. package/gsd-core/workflows/undo.md +9 -17
  357. package/gsd-core/workflows/update.md +47 -48
  358. package/gsd-core/workflows/validate-phase.md +3 -3
  359. package/gsd-core/workflows/verify-work/steps/automated-ui-verification.md +25 -1
  360. package/gsd-core/workflows/verify-work/steps/mvp-uat-framing.md +1 -1
  361. package/gsd-core/workflows/verify-work.md +106 -21
  362. package/hooks/dist/gsd-agent-isolation-guard.js +77 -38
  363. package/hooks/dist/gsd-check-update-worker.js +19 -2
  364. package/hooks/dist/gsd-config-reload.js +18 -12
  365. package/hooks/dist/gsd-context-monitor.js +302 -22
  366. package/hooks/dist/gsd-cursor-post-tool.js +3 -1
  367. package/hooks/dist/gsd-cursor-pre-tool.js +3 -1
  368. package/hooks/dist/gsd-cursor-session-start.js +2 -1
  369. package/hooks/dist/gsd-cursor-stop.js +2 -1
  370. package/hooks/dist/gsd-cursor-subagent-start.js +28 -23
  371. package/hooks/dist/gsd-cursor-subagent-stop.js +3 -1
  372. package/hooks/dist/gsd-ensure-canonical-path.js +2 -1
  373. package/hooks/dist/gsd-graphify-update.sh +22 -18
  374. package/hooks/dist/gsd-node-runner.sh +77 -0
  375. package/hooks/dist/gsd-phase-boundary.sh +1 -0
  376. package/hooks/dist/gsd-prompt-guard.js +46 -12
  377. package/hooks/dist/gsd-read-guard.js +18 -7
  378. package/hooks/dist/gsd-read-injection-scanner.js +22 -13
  379. package/hooks/dist/gsd-secret-read-guard.js +1079 -0
  380. package/hooks/dist/gsd-session-state.sh +1 -0
  381. package/hooks/dist/gsd-statusline.js +222 -29
  382. package/hooks/dist/gsd-validate-commit.sh +523 -12
  383. package/hooks/dist/gsd-windsurf-pre-command.js +16 -11
  384. package/hooks/dist/gsd-windsurf-pre-write.js +22 -13
  385. package/hooks/dist/gsd-workflow-guard.js +36 -17
  386. package/hooks/dist/gsd-worktree-path-guard.js +36 -21
  387. package/hooks/dist/gsd-write-guard.js +35 -25
  388. package/hooks/dist/lib/cli-exit.js +560 -0
  389. package/hooks/dist/lib/exit-code-registry.js +98 -0
  390. package/hooks/dist/lib/git-cmd.js +210 -1
  391. package/hooks/dist/lib/git-probe.js +84 -0
  392. package/hooks/dist/lib/hook-exit.js +81 -0
  393. package/hooks/dist/lib/injection-patterns.js +36 -6
  394. package/hooks/dist/managed-hooks-registry.cjs +4 -0
  395. package/hooks/gsd-agent-isolation-guard.js +77 -38
  396. package/hooks/gsd-check-update-worker.js +19 -2
  397. package/hooks/gsd-config-reload.js +18 -12
  398. package/hooks/gsd-context-monitor.js +302 -22
  399. package/hooks/gsd-cursor-post-tool.js +3 -1
  400. package/hooks/gsd-cursor-pre-tool.js +3 -1
  401. package/hooks/gsd-cursor-session-start.js +2 -1
  402. package/hooks/gsd-cursor-stop.js +2 -1
  403. package/hooks/gsd-cursor-subagent-start.js +28 -23
  404. package/hooks/gsd-cursor-subagent-stop.js +3 -1
  405. package/hooks/gsd-ensure-canonical-path.js +2 -1
  406. package/hooks/gsd-graphify-update.sh +22 -18
  407. package/hooks/gsd-node-runner.sh +77 -0
  408. package/hooks/gsd-phase-boundary.sh +1 -0
  409. package/hooks/gsd-prompt-guard.js +46 -12
  410. package/hooks/gsd-read-guard.js +18 -7
  411. package/hooks/gsd-read-injection-scanner.js +22 -13
  412. package/hooks/gsd-secret-read-guard.js +1079 -0
  413. package/hooks/gsd-session-state.sh +1 -0
  414. package/hooks/gsd-statusline.js +222 -29
  415. package/hooks/gsd-validate-commit.sh +523 -12
  416. package/hooks/gsd-windsurf-pre-command.js +16 -11
  417. package/hooks/gsd-windsurf-pre-write.js +22 -13
  418. package/hooks/gsd-workflow-guard.js +36 -17
  419. package/hooks/gsd-worktree-path-guard.js +36 -21
  420. package/hooks/gsd-write-guard.js +35 -25
  421. package/hooks/hooks.json +6 -0
  422. package/hooks/lib/cli-exit.js +560 -0
  423. package/hooks/lib/exit-code-registry.js +98 -0
  424. package/hooks/lib/git-cmd.js +210 -1
  425. package/hooks/lib/git-probe.js +84 -0
  426. package/hooks/lib/hook-exit.js +81 -0
  427. package/hooks/lib/injection-patterns.js +36 -6
  428. package/hooks/managed-hooks-registry.cjs +4 -0
  429. package/package.json +14 -9
  430. package/scripts/base64-scan.sh +74 -12
  431. package/scripts/build-hooks.js +12 -0
  432. package/scripts/check-glossary-refs.cjs +77 -15
  433. package/scripts/check-mutation-score-ratchet.cjs +156 -0
  434. package/scripts/ci-check-job-near-cap.cjs +49 -0
  435. package/scripts/ci-pr-mergeability.cjs +262 -0
  436. package/scripts/ci-test-scope.cjs +52 -12
  437. package/scripts/ci-timeout-report.cjs +230 -0
  438. package/scripts/docs-guard-registry.cjs +406 -0
  439. package/scripts/gen-capability-registry.cjs +8 -6
  440. package/scripts/gen-exit-code-docs.cjs +318 -0
  441. package/scripts/gen-exit-code-registry.cjs +891 -0
  442. package/scripts/gen-features.cjs +836 -0
  443. package/scripts/gen-hooks-cli-exit.cjs +239 -0
  444. package/scripts/gen-install-tree-fixtures.cjs +2 -2
  445. package/scripts/gen-loop-host-contract.cjs +189 -4
  446. package/scripts/gen-scripts-cli-exit.cjs +185 -0
  447. package/scripts/gen-state-md-docs.cjs +727 -0
  448. package/scripts/{test-failure-reasons.cjs → gsd-test-gate-reasons.cjs} +6 -0
  449. package/scripts/lib/ci-job-timing.cjs +72 -0
  450. package/scripts/lib/cli-exit.cjs +546 -44
  451. package/scripts/lib/drift-scan.cjs +32 -2
  452. package/scripts/lib/exit-code-registry.cjs +98 -0
  453. package/scripts/lib/ndjson-reporter.cjs +119 -0
  454. package/scripts/lib/shellcheck-fetch.cjs +247 -0
  455. package/scripts/lint-allow-test-rule-refs.allowlist.json +0 -6
  456. package/scripts/lint-allow-test-rule-refs.effective-ceiling.json +1 -1
  457. package/scripts/lint-allow-test-rule-refs.unverified-ceiling.json +1 -1
  458. package/scripts/lint-docs-guard-registration.cjs +495 -0
  459. package/scripts/lint-docs-guard-registration.exempt-baseline.cjs +198 -0
  460. package/scripts/lint-eslint-glob-coverage.allowlist.json +4 -0
  461. package/scripts/{lint-fix-has-regression-test.cjs → lint-fix-has-regression-tests.cjs} +12 -6
  462. package/scripts/lint-health-diagnostic-rule-table.cjs +65 -8
  463. package/scripts/lint-mutation-test-derivation-drift.cjs +86 -0
  464. package/scripts/lint-phase-enumeration-drift.cjs +45 -14
  465. package/scripts/lint-phase-id-drift.cjs +133 -8
  466. package/scripts/lint-planning-prompt-drift.cjs +38 -1
  467. package/scripts/lint-portable-grep.cjs +176 -0
  468. package/scripts/lint-removed-but-needed.cjs +184 -16
  469. package/scripts/lint-response-language-coverage.cjs +524 -0
  470. package/scripts/lint-seam-enforcement.cjs +182 -0
  471. package/scripts/lint-slug-derivation-drift.cjs +921 -0
  472. package/scripts/lint-source-test-name-collision.cjs +241 -0
  473. package/scripts/lint-state-write-path-drift.cjs +337 -432
  474. package/scripts/lint-test-file-count.allowlist.json +124 -4
  475. package/scripts/lint-test-file-count.cjs +25 -3
  476. package/scripts/lint-unreachable-guard-drift.cjs +51 -64
  477. package/scripts/lint-vendored-deps.cjs +208 -35
  478. package/scripts/lint-workflow-shellcheck-baseline.json +1027 -0
  479. package/scripts/lint-workflow-shellcheck.cjs +614 -0
  480. package/scripts/mutation-matrix.cjs +599 -50
  481. package/scripts/npm-audit-baseline.cjs +376 -0
  482. package/scripts/prompt-injection-scan.sh +83 -14
  483. package/scripts/require-issue-link-policy.cjs +16 -1
  484. package/scripts/secret-scan.sh +75 -13
  485. package/scripts/select-docs-guards.cjs +56 -0
  486. package/scripts/sync-runtime-launcher.cjs +22 -3
  487. package/skills/gsd-discuss-phase/SKILL.md +1 -1
  488. package/skills/gsd-execute-phase/SKILL.md +1 -1
  489. package/skills/gsd-import/SKILL.md +1 -1
  490. package/skills/gsd-ns-workflow/SKILL.md +1 -0
  491. package/skills/gsd-phase/SKILL.md +1 -1
  492. package/skills/gsd-quick/SKILL.md +8 -4
  493. package/skills/gsd-quick-batch/SKILL.md +105 -0
  494. package/skills/gsd-surface/SKILL.md +18 -8
  495. package/vscode/package.json +1 -1
  496. package/bin/lib/ui-safety-gate.cjs +0 -109
  497. package/scripts/lint-emitted-drift-ack.cjs +0 -344
  498. package/scripts/state-write-path-drift-baseline.json +0 -19
@@ -956,6 +956,29 @@ function reanchorBaseline(prev, key, score, opts = {}) {
956
956
  // ─── Proposal render/parse ──────────────────────────────────────────────────
957
957
  const PROPOSAL_JSON_FENCE_OPEN = '````json';
958
958
  const PROPOSAL_JSON_FENCE_CLOSE = '````';
959
+ // Reader-side fence tolerance (#3657 — same defect class as the WINDOWS.md
960
+ // ledger): CommonMark formatters (Prettier et al.) narrow the written
961
+ // 4-backtick fence to the shortest legal width (3) whenever the body holds no
962
+ // backtick run, and a canonical-JSON candidates array never does. Locate the
963
+ // block by a line-anchored 3+ fence and close on a run at least as wide
964
+ // (CommonMark: a shorter run does not close). The writer above is unchanged.
965
+ // This module is a leaf (CONTEXT.md — imports only node:fs/node:path), so the
966
+ // span logic is local rather than imported from broken-windows.
967
+ const PROPOSAL_FENCE_OPEN_RE = /^(`{3,})json[ \t]*\r?$/m;
968
+ function locateProposalJsonBlock(text) {
969
+ const open = text.match(PROPOSAL_FENCE_OPEN_RE);
970
+ if (!open || open.index === undefined)
971
+ return null;
972
+ const width = open[1].length;
973
+ const bodyStart = open.index + open[0].length;
974
+ for (const close of text.slice(bodyStart).matchAll(/^(`{3,})[ \t]*\r?$/gm)) {
975
+ if (close[1].length < width)
976
+ continue;
977
+ const bodyEnd = close.index ?? 0;
978
+ return { jsonText: text.slice(bodyStart, bodyStart + bodyEnd).trim() };
979
+ }
980
+ return null;
981
+ }
959
982
  function renderProposal(p) {
960
983
  const fm = [
961
984
  '---',
@@ -1012,13 +1035,10 @@ function parseProposal(text) {
1012
1035
  return null;
1013
1036
  fm[m[1]] = m[2].trim();
1014
1037
  }
1015
- const jsonStart = text.indexOf(PROPOSAL_JSON_FENCE_OPEN);
1016
- if (jsonStart === -1)
1017
- return null;
1018
- const jsonEnd = text.indexOf(PROPOSAL_JSON_FENCE_CLOSE, jsonStart + PROPOSAL_JSON_FENCE_OPEN.length);
1019
- if (jsonEnd === -1)
1038
+ const span = locateProposalJsonBlock(text);
1039
+ if (span === null)
1020
1040
  return null;
1021
- const jsonText = text.slice(jsonStart + PROPOSAL_JSON_FENCE_OPEN.length, jsonEnd).trim();
1041
+ const jsonText = span.jsonText;
1022
1042
  let candidates;
1023
1043
  try {
1024
1044
  candidates = JSON.parse(jsonText);
@@ -13,7 +13,8 @@
13
13
  *
14
14
  * Dependencies (leaf modules only):
15
15
  * - node:fs / node:os / node:path (stdlib)
16
- * - ./configuration.cjs (normalizeLegacyKeys, CONFIG_DEFAULTS as CANONICAL_CONFIG_DEFAULTS)
16
+ * - ./configuration.cjs (normalizeLegacyKeys, isConfigSection, CONFIG_DEFAULTS as CANONICAL_CONFIG_DEFAULTS)
17
+ * - ./unusable-input.cjs (warnUnusableInput, UNUSABLE_REASON — #3760)
17
18
  * - ./config-schema.cjs (VALID_CONFIG_KEYS, DYNAMIC_KEY_PATTERNS)
18
19
  * - ./planning-workspace.cjs (planningDir, planningRoot)
19
20
  * - ./shell-command-projection.cjs (execGit, platformWriteSync, platformReadSync)
@@ -39,6 +40,12 @@ const configuration_cjs_1 = require("./configuration.cjs");
39
40
  const configSchema = require("./config-schema.cjs");
40
41
  const { VALID_CONFIG_KEYS, DYNAMIC_KEY_PATTERNS, isCentralConfigKey: _isCentralConfigKeyFn } = configSchema;
41
42
  const model_catalog_cjs_1 = require("./model-catalog.cjs");
43
+ // #3760: the ADR-1411 out-of-band diagnostic seam. loadConfig returns `.config`
44
+ // alone, so an in-band `skipped` record would be unreachable to nearly every
45
+ // caller — "a reason no caller reads is an unreachable field" (ADR-1411).
46
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
47
+ const unusableInputModule = require("./unusable-input.cjs");
48
+ const { UNUSABLE_REASON: _UNUSABLE_REASON, warnUnusableInput: _warnUnusableInput } = unusableInputModule;
42
49
  // ─── Federated Config (ADR-857 phase 3b) ─────────────────────────────────────
43
50
  // eslint-disable-next-line @typescript-eslint/no-require-imports
44
51
  const federatedConfigModule = require("./federated-config.cjs");
@@ -77,6 +84,7 @@ function _resetFederatedRegistryForTests() {
77
84
  * - git.* → flat git keys (branching_strategy, templates)
78
85
  * - workflow.* → flat names (research, verifier, …)
79
86
  * - planning.sub_repos → sub_repos
87
+ * - planning.pr_strict → pr_strict
80
88
  * - planning.commit_docs / search_gitignored → top-level flat keys
81
89
  */
82
90
  // CANONICAL_CONFIG_DEFAULTS is typed as Record<string, unknown> from configuration.cjs;
@@ -91,6 +99,23 @@ function _getNestedConfigDefault(section, field) {
91
99
  }
92
100
  return undefined;
93
101
  }
102
+ /** Shared flat-then-nested config lookup; exported for parity tests. */
103
+ function _getConfigValue(parsed, key, nested) {
104
+ if (parsed[key] !== undefined)
105
+ return parsed[key];
106
+ if (nested && parsed[nested.section] && typeof parsed[nested.section] === 'object' && parsed[nested.section] !== null) {
107
+ return parsed[nested.section][nested.field];
108
+ }
109
+ return undefined;
110
+ }
111
+ /** Shared nested-only config lookup; exported for parity tests. */
112
+ function _getConfigNested(parsed, section, field) {
113
+ const sec = parsed[section];
114
+ if (sec !== null && typeof sec === 'object' && !Array.isArray(sec)) {
115
+ return sec[field];
116
+ }
117
+ return undefined;
118
+ }
94
119
  const CONFIG_DEFAULTS = {
95
120
  model_profile: _getConfigDefault('model_profile'),
96
121
  commit_docs: _getConfigDefault('commit_docs'),
@@ -111,6 +136,7 @@ const CONFIG_DEFAULTS = {
111
136
  exa_search: _getConfigDefault('exa_search'),
112
137
  text_mode: _getNestedConfigDefault('workflow', 'text_mode'),
113
138
  sub_repos: _getNestedConfigDefault('planning', 'sub_repos'),
139
+ pr_strict: _getNestedConfigDefault('planning', 'pr_strict'),
114
140
  resolve_model_ids: _getConfigDefault('resolve_model_ids'),
115
141
  context_window: _getConfigDefault('context_window'),
116
142
  phase_naming: _getConfigDefault('phase_naming'),
@@ -120,7 +146,10 @@ const CONFIG_DEFAULTS = {
120
146
  security_asvs_level: _getNestedConfigDefault('workflow', 'security_asvs_level'),
121
147
  security_block_on: _getNestedConfigDefault('workflow', 'security_block_on'),
122
148
  post_planning_gaps: _getNestedConfigDefault('workflow', 'post_planning_gaps'),
149
+ research_before_questions: _getNestedConfigDefault('workflow', 'research_before_questions'), // #3894
123
150
  smart_zone_tokens: _getNestedConfigDefault('workflow', 'smart_zone_tokens'),
151
+ inline_plan_threshold: _getNestedConfigDefault('workflow', 'inline_plan_threshold'), // #3801
152
+ max_prompt_tokens: _getNestedConfigDefault('review', 'max_prompt_tokens'),
124
153
  };
125
154
  /**
126
155
  * Deep-merge two plain config objects. `overlay` wins on key conflict.
@@ -305,7 +334,7 @@ function _resetRuntimeWarningCacheForTests() {
305
334
  // drift in either direction.
306
335
  const GLOBAL_DEFAULTS_RESOLUTION_KEYS = [
307
336
  'model_profile', 'commit_docs', 'research', 'plan_checker', 'verifier',
308
- 'nyquist_validation', 'post_planning_gaps', 'parallelization', 'text_mode',
337
+ 'nyquist_validation', 'post_planning_gaps', 'research_before_questions', 'parallelization', 'text_mode',
309
338
  'resolve_model_ids', 'context_window', 'subagent_timeout', 'model_overrides',
310
339
  'models', 'granularity', 'granularities', 'planning', 'dynamic_routing',
311
340
  'effort', 'fast_mode', 'agent_skills', 'response_language', 'runtime',
@@ -322,11 +351,14 @@ function _warnShadowedGlobalDefaults(globalDefaults, globalPath) {
322
351
  // `?? globalDefaults['workflow']?.['post_planning_gaps']` fallback in
323
352
  // _globalBaseCfg) — a global file using only the nested form is equally
324
353
  // shadowed, so it reports under its dotted name.
325
- if (!shadowed.includes('post_planning_gaps')) {
326
- const wf = globalDefaults['workflow'];
327
- if (wf && typeof wf === 'object' && !Array.isArray(wf) &&
328
- Object.prototype.hasOwnProperty.call(wf, 'post_planning_gaps')) {
329
- shadowed.push('workflow.post_planning_gaps');
354
+ // #3894: research_before_questions gets the same nested-alias reporting.
355
+ const nestedAliasKeys = ['post_planning_gaps', 'research_before_questions'];
356
+ const wf = globalDefaults['workflow'];
357
+ if (wf && typeof wf === 'object' && !Array.isArray(wf)) {
358
+ for (const k of nestedAliasKeys) {
359
+ if (!shadowed.includes(k) && Object.prototype.hasOwnProperty.call(wf, k)) {
360
+ shadowed.push(`workflow.${k}`);
361
+ }
330
362
  }
331
363
  }
332
364
  if (shadowed.length === 0)
@@ -560,8 +592,18 @@ function _warnUnusableConfig(fault) {
560
592
  * diagnostic names the file. Before this, a trailing comma in config.json was
561
593
  * byte-identical to the file not existing: builtin defaults, degraded:false,
562
594
  * and the user's entire configuration silently discarded.
595
+ *
596
+ * `options.persist: false` (#3648) suppresses the two normalize-then-write-back
597
+ * side effects below. Resolution is otherwise identical — same precedence, same
598
+ * returned object — the migrated shape simply stays in memory. Callers that only
599
+ * ASK the config something (a predicate, a status readout) pass it so that a read
600
+ * cannot dirty the working tree; the ~30 callers that omit it keep persisting, so
601
+ * a legacy config is still migrated exactly once by ordinary use.
563
602
  */
564
603
  function loadConfigResolved(cwd, options = {}) {
604
+ // Opt-OUT, not opt-in: omitting the option must preserve the historical
605
+ // write-back for every existing caller.
606
+ const persist = options['persist'] !== false;
565
607
  // NOTE: loadConfigResolved resolves from cwd AS-IS (no walk-up).
566
608
  // Callers that need ancestor-anchoring (e.g. cmdAgentSkills) must do so
567
609
  // themselves via findProjectRoot() before calling this function.
@@ -614,24 +656,37 @@ function loadConfigResolved(cwd, options = {}) {
614
656
  if (rootRead.kind !== 'ok')
615
657
  throw new Error('root config absent or unusable');
616
658
  rootParsed = rootRead.data;
617
- const { parsed: rootNormalized, normalizations: rootNorms } = (0, configuration_cjs_1.normalizeLegacyKeys)(rootParsed);
659
+ const { parsed: rootNormalized, normalizations: rootNorms, skipped: rootSkipped } = (0, configuration_cjs_1.normalizeLegacyKeys)(rootParsed);
660
+ if (rootSkipped.length > 0) {
661
+ _warnUnusableInput({ reason: _UNUSABLE_REASON.CONFIG_SECTION_NOT_OBJECT, source: rootConfigPath });
662
+ }
618
663
  if (rootNorms.length > 0) {
619
664
  for (const norm of rootNorms) {
620
665
  if (norm.requiresFilesystem && !rootNormalized.planning?.['sub_repos']) {
621
666
  const detected = getDetectedSubRepos();
622
667
  if (detected.length > 0) {
623
- if (!rootNormalized.planning)
668
+ // #3760: `if (!planning) planning = {}` treated a non-empty STRING as an
669
+ // already-present section, and the next line then assigned onto a
670
+ // primitive — a strict-mode TypeError the enclosing catch swallowed,
671
+ // discarding the user's whole config. `requiresFilesystem` now only
672
+ // reaches here when the section is absent or an object (configuration.cts
673
+ // block 3 refuses otherwise and reports it via `skipped`), so this
674
+ // narrowing chooses between merge and create and never discards.
675
+ if (!(0, configuration_cjs_1.isConfigSection)(rootNormalized.planning)) {
624
676
  rootNormalized.planning = {};
677
+ }
625
678
  rootNormalized.planning['sub_repos'] = detected;
626
679
  rootNormalized.planning['commit_docs'] = false;
627
680
  }
628
681
  }
629
682
  }
630
683
  rootParsed = rootNormalized;
631
- try {
632
- (0, shell_command_projection_cjs_1.platformWriteSync)(rootConfigPath, JSON.stringify(rootParsed, null, 2));
684
+ if (persist) {
685
+ try {
686
+ (0, shell_command_projection_cjs_1.platformWriteSync)(rootConfigPath, JSON.stringify(rootParsed, null, 2));
687
+ }
688
+ catch { /* ignore */ }
633
689
  }
634
- catch { /* ignore */ }
635
690
  }
636
691
  else {
637
692
  rootParsed = rootNormalized;
@@ -658,7 +713,10 @@ function loadConfigResolved(cwd, options = {}) {
658
713
  const fileHadKeys = Object.keys(read.data).length > 0;
659
714
  let configDirty = false;
660
715
  {
661
- const { parsed: normalized, normalizations } = (0, configuration_cjs_1.normalizeLegacyKeys)(fileData);
716
+ const { parsed: normalized, normalizations, skipped } = (0, configuration_cjs_1.normalizeLegacyKeys)(fileData);
717
+ if (skipped.length > 0) {
718
+ _warnUnusableInput({ reason: _UNUSABLE_REASON.CONFIG_SECTION_NOT_OBJECT, source: configPath });
719
+ }
662
720
  if (normalizations.length > 0) {
663
721
  Object.keys(fileData).forEach(k => delete fileData[k]);
664
722
  Object.assign(fileData, normalized);
@@ -667,7 +725,8 @@ function loadConfigResolved(cwd, options = {}) {
667
725
  if (norm.requiresFilesystem && !fileData.planning?.['sub_repos']) {
668
726
  const detected = getDetectedSubRepos();
669
727
  if (detected.length > 0) {
670
- if (!fileData.planning)
728
+ // #3760 — see the identical guard on the root-config path above.
729
+ if (!(0, configuration_cjs_1.isConfigSection)(fileData.planning))
671
730
  fileData.planning = {};
672
731
  fileData.planning['sub_repos'] = detected;
673
732
  fileData.planning['commit_docs'] = false;
@@ -682,14 +741,17 @@ function loadConfigResolved(cwd, options = {}) {
682
741
  if (detected.length > 0) {
683
742
  const sorted = [...currentSubRepos].sort();
684
743
  if (JSON.stringify(sorted) !== JSON.stringify(detected)) {
685
- if (!fileData.planning)
744
+ // #3760 — reachable only when `planning` already yielded a non-empty
745
+ // sub_repos array, so it is an object here; the narrowing keeps the
746
+ // assignment total rather than relying on that from three frames away.
747
+ if (!(0, configuration_cjs_1.isConfigSection)(fileData.planning))
686
748
  fileData.planning = {};
687
749
  fileData.planning['sub_repos'] = detected;
688
750
  configDirty = true;
689
751
  }
690
752
  }
691
753
  }
692
- if (configDirty) {
754
+ if (configDirty && persist) {
693
755
  try {
694
756
  (0, shell_command_projection_cjs_1.platformWriteSync)(configPath, JSON.stringify(fileData, null, 2));
695
757
  }
@@ -734,17 +796,19 @@ function loadConfigResolved(cwd, options = {}) {
734
796
  }
735
797
  }
736
798
  _warnUnknownProfileOverrides(parsed, '.planning/config.json');
737
- const get = (key, nested) => {
738
- if (parsed[key] !== undefined)
739
- return parsed[key];
740
- if (nested && parsed[nested.section] && typeof parsed[nested.section] === 'object' && parsed[nested.section] !== null) {
741
- const sec = parsed[nested.section];
742
- if (sec[nested.field] !== undefined) {
743
- return sec[nested.field];
744
- }
745
- }
746
- return undefined;
747
- };
799
+ const get = (key, nested) => _getConfigValue(parsed, key, nested);
800
+ /**
801
+ * Nested-ONLY read — no top-level fallback (#3648).
802
+ *
803
+ * `get()`'s flat-then-nested order exists for keys that have a legacy flat
804
+ * spelling `normalizeLegacyKeys` migrates (`branching_strategy`,
805
+ * `base_branch`, …); for those, honouring the flat key is back-compat. A key
806
+ * introduced with no legacy form has nothing to be compatible WITH, so
807
+ * routing it through `get()` would invent an undocumented top-level alias
808
+ * that silently outranks the canonical nested key. Use this instead for new
809
+ * `<section>.<field>` keys (round-4 external review).
810
+ */
811
+ const getNested = (section, field) => _getConfigNested(parsed, section, field);
748
812
  const parallelization = (() => {
749
813
  const val = get('parallelization');
750
814
  if (typeof val === 'boolean')
@@ -765,6 +829,9 @@ function loadConfigResolved(cwd, options = {}) {
765
829
  })(),
766
830
  search_gitignored: get('search_gitignored', { section: 'planning', field: 'search_gitignored' }) ?? defaults.search_gitignored,
767
831
  branching_strategy: get('branching_strategy', { section: 'git', field: 'branching_strategy' }) ?? defaults.branching_strategy,
832
+ base_branch: get('base_branch', { section: 'git', field: 'base_branch' }),
833
+ protected_branches: getNested('git', 'protected_branches'),
834
+ allow_default_branch_commits: getNested('git', 'allow_default_branch_commits'),
768
835
  phase_branch_template: get('phase_branch_template', { section: 'git', field: 'phase_branch_template' }) ?? defaults.phase_branch_template,
769
836
  milestone_branch_template: get('milestone_branch_template', { section: 'git', field: 'milestone_branch_template' }) ?? defaults.milestone_branch_template,
770
837
  quick_branch_template: get('quick_branch_template', { section: 'git', field: 'quick_branch_template' }) ?? defaults.quick_branch_template,
@@ -778,17 +845,20 @@ function loadConfigResolved(cwd, options = {}) {
778
845
  firecrawl: get('firecrawl') ?? defaults.firecrawl,
779
846
  exa_search: get('exa_search') ?? defaults.exa_search,
780
847
  mvp_mode: get('mvp_mode', { section: 'workflow', field: 'mvp_mode' }) ?? false,
848
+ tdd_mode: getNested('workflow', 'tdd_mode') ?? false,
781
849
  text_mode: get('text_mode', { section: 'workflow', field: 'text_mode' }) ?? defaults.text_mode,
782
850
  auto_advance: get('auto_advance', { section: 'workflow', field: 'auto_advance' }) ?? false,
783
851
  _auto_chain_active: get('_auto_chain_active', { section: 'workflow', field: '_auto_chain_active' }) ?? false,
784
852
  mode: get('mode') ?? 'interactive',
785
853
  sub_repos: get('sub_repos', { section: 'planning', field: 'sub_repos' }) ?? defaults.sub_repos,
854
+ pr_strict: get('pr_strict', { section: 'planning', field: 'pr_strict' }) ?? defaults.pr_strict,
786
855
  resolve_model_ids: get('resolve_model_ids') ?? defaults.resolve_model_ids,
787
856
  context_window: get('context_window') ?? defaults.context_window,
788
857
  phase_naming: get('phase_naming') ?? defaults.phase_naming,
789
858
  project_code: get('project_code') ?? defaults.project_code,
790
859
  subagent_timeout: get('subagent_timeout', { section: 'workflow', field: 'subagent_timeout' }) ?? defaults.subagent_timeout,
791
860
  model_overrides: (parsed['model_overrides']) || null,
861
+ agent_tools: (parsed['agent_tools']) || null,
792
862
  models: (parsed['models']) || null,
793
863
  granularity: parsed['granularity'] !== undefined ? parsed['granularity'] : null,
794
864
  granularities: (parsed['granularities']) || null,
@@ -813,6 +883,13 @@ function loadConfigResolved(cwd, options = {}) {
813
883
  claude_md_path: get('claude_md_path') || null,
814
884
  claude_md_assembly: (parsed['claude_md_assembly']) || null,
815
885
  phase_id_convention: get('phase_id_convention') ?? null,
886
+ // #3691: the documented central review key. Declared here (not federated —
887
+ // it is central, see config-schema.manifest.json validKeys) so the existing
888
+ // `review.*` per-lane keys the federated overlay below adds land as SIBLINGS
889
+ // on this same object rather than being clobbered by it.
890
+ review: {
891
+ max_prompt_tokens: get('max_prompt_tokens', { section: 'review', field: 'max_prompt_tokens' }) ?? defaults.max_prompt_tokens,
892
+ },
816
893
  };
817
894
  // ADR-857 phase 3b: federated config overlay
818
895
  try {
@@ -873,8 +950,15 @@ function loadConfigResolved(cwd, options = {}) {
873
950
  // Fix 2: Early intercept — workstream requested but ws config.json absent (or dir absent)
874
951
  // AND root config was loaded. Covers BOTH "dir exists, no config.json" AND "dir absent".
875
952
  // This delivers the #1366 acceptance criterion: nonexistent GSD_WORKSTREAM yields root, degraded.
953
+ //
954
+ // Both fallback recursions below forward `options` and override ONLY `workstream`.
955
+ // A bare `{ workstream: null }` silently dropped every other option, so a caller's
956
+ // `persist: false` was discarded on exactly this path and the root config was
957
+ // rewritten by a read (#3648, found by external review). The explicit
958
+ // `workstream: null` still wins the `hasOwnProperty` check at the top of this
959
+ // function, so spreading cannot let `workstreamContext` reintroduce a workstream.
876
960
  if (wsRequested && rootParsed) {
877
- const fb = loadConfigResolved(cwd, { workstream: null });
961
+ const fb = loadConfigResolved(cwd, { ...options, workstream: null });
878
962
  return fallback({ config: fb.config, source: 'root', degraded: true });
879
963
  }
880
964
  // Branch B, C, D, E
@@ -882,7 +966,7 @@ function loadConfigResolved(cwd, options = {}) {
882
966
  if (rootParsed) {
883
967
  // Branch B: workstream requested but ws config.json absent; root config present.
884
968
  // (Only reached when wsRequested is false — e.g. ws='' with .planning/workstreams//config.json)
885
- const fb = loadConfigResolved(cwd, { workstream: null });
969
+ const fb = loadConfigResolved(cwd, { ...options, workstream: null });
886
970
  return fallback({ config: fb.config, source: 'root', degraded: true });
887
971
  }
888
972
  // Branch C: .planning/ exists but no config.json and no root config — federated/builtin defaults
@@ -920,6 +1004,12 @@ function loadConfigResolved(cwd, options = {}) {
920
1004
  post_planning_gaps: (globalDefaults['post_planning_gaps'])
921
1005
  ?? globalDefaults['workflow']?.['post_planning_gaps']
922
1006
  ?? defaults.post_planning_gaps,
1007
+ // #3894: same nested-alias shape as post_planning_gaps above — the key
1008
+ // was silently dropped from global defaults, so it was unavailable at
1009
+ // user scope AND inert at project scope on the /gsd-quick path.
1010
+ research_before_questions: (globalDefaults['research_before_questions'])
1011
+ ?? globalDefaults['workflow']?.['research_before_questions']
1012
+ ?? defaults.research_before_questions,
923
1013
  parallelization: (globalDefaults['parallelization']) ?? defaults.parallelization,
924
1014
  text_mode: (globalDefaults['text_mode']) ?? defaults.text_mode,
925
1015
  resolve_model_ids: (globalDefaults['resolve_model_ids']) ?? defaults.resolve_model_ids,
@@ -979,6 +1069,8 @@ module.exports = {
979
1069
  CONFIG_DEFAULTS,
980
1070
  _getConfigDefault,
981
1071
  _getNestedConfigDefault,
1072
+ _getConfigValue,
1073
+ _getConfigNested,
982
1074
  _deepMergeConfig,
983
1075
  _warnedUnknownConfigKeys,
984
1076
  _warnedShadowedGlobalKeys,
@@ -16,6 +16,9 @@ const node_os_1 = __importDefault(require("node:os"));
16
16
  const io = require("./io.cjs");
17
17
  const { output, error, ERROR_REASON } = io;
18
18
  // eslint-disable-next-line @typescript-eslint/no-require-imports
19
+ const cliExitMod = require("./cli-exit.cjs");
20
+ const { ExitError } = cliExitMod;
21
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
19
22
  const configLoader = require("./config-loader.cjs");
20
23
  const { CONFIG_DEFAULTS } = configLoader;
21
24
  const shell_command_projection_cjs_1 = require("./shell-command-projection.cjs");
@@ -31,6 +34,13 @@ const { VALID_CONFIG_KEYS, isValidConfigKey, getCapabilityConfigSchema } = confi
31
34
  const secrets_cjs_1 = require("./secrets.cjs");
32
35
  const review_reviewer_selection_cjs_1 = require("./review-reviewer-selection.cjs");
33
36
  const configuration_cjs_1 = require("./configuration.cjs");
37
+ // #3760: the ADR-1411 out-of-band diagnostic. It lives here rather than inside
38
+ // `migrateOnDisk` because `configuration.cjs` must stay loadable from an install
39
+ // layout holding only itself plus its manifests (#3571) — see the note at the top
40
+ // of configuration.cts.
41
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
42
+ const unusableInputModule = require("./unusable-input.cjs");
43
+ const { UNUSABLE_REASON, warnUnusableInput } = unusableInputModule;
34
44
  // ─── Constants ────────────────────────────────────────────────────────────────
35
45
  const CONFIG_KEY_SUGGESTIONS = {
36
46
  'workflow.nyquist_validation_enabled': 'workflow.nyquist_validation',
@@ -74,6 +84,15 @@ const SCHEMA_DEFAULTS = {
74
84
  // Derived from the defaults manifest rather than restated, so the manifest
75
85
  // stays the single source of truth for the smart-zone budget (#2630).
76
86
  'workflow.smart_zone_tokens': CONFIG_DEFAULTS.smart_zone_tokens,
87
+ // #2971: /gsd:pr-branch reads this key directly; an absent key must resolve to the
88
+ // manifest default rather than "Key not found". Derived from the defaults manifest so
89
+ // the manifest stays the single source of truth.
90
+ 'planning.pr_strict': CONFIG_DEFAULTS.pr_strict,
91
+ // #3801: execute-plan reads this key on every run; an absent key must resolve
92
+ // to the manifest default (2) rather than "Key not Found" — previously the
93
+ // effective default existed only as the workflow's shell fallback and the
94
+ // docs disagreed (settings-advanced said 3). Manifest stays the one owner.
95
+ 'workflow.inline_plan_threshold': CONFIG_DEFAULTS.inline_plan_threshold,
77
96
  };
78
97
  /**
79
98
  * Resolve a schema-level default for an absent key (#2256). Checks the legacy
@@ -121,6 +140,35 @@ function validateKnownConfigKeyPath(keyPath) {
121
140
  error(`Unknown config key: ${keyPath}. Did you mean ${suggested}?`, ERROR_REASON.CONFIG_INVALID_KEY);
122
141
  }
123
142
  }
143
+ /**
144
+ * Is `value` an acceptable `git.protected_branches` list (#3552)?
145
+ *
146
+ * A non-empty array whose every element is a string with non-whitespace
147
+ * content. Exported so a property test can pin this predicate against the
148
+ * resolver's own per-entry filter in `git-base-branch.cts` — `config-set` must
149
+ * only accept lists the resolver will honour in full, with nothing rejected.
150
+ * The two are deliberately different shapes (all-or-nothing here, per-entry
151
+ * there, because a direct file edit bypasses this check), so nothing keeps them
152
+ * agreeing except a test that asks both.
153
+ */
154
+ function isValidProtectedBranches(value) {
155
+ if (!Array.isArray(value) || value.length === 0)
156
+ return false;
157
+ const entries = value;
158
+ // Index, do NOT use `.every()`. `.every()` SKIPS holes, so a sparse array
159
+ // (`["main", , "develop"]`) passed this check while the resolver's `for...of`
160
+ // — which yields `undefined` for a hole — rejected that element. The two
161
+ // surfaces then disagreed about the same value. JSON cannot express a hole,
162
+ // so neither surface meets one in production, but "unreachable" is not a
163
+ // reason to leave two definitions of the same predicate contradicting each
164
+ // other (round-4 external review).
165
+ for (let i = 0; i < entries.length; i += 1) {
166
+ const branch = entries[i];
167
+ if (typeof branch !== 'string' || branch.trim().length === 0)
168
+ return false;
169
+ }
170
+ return true;
171
+ }
124
172
  function validateShipPrBodySections(value) {
125
173
  if (!Array.isArray(value)) {
126
174
  error('Invalid ship.pr_body_sections value. Expected a JSON array of section objects.');
@@ -751,6 +799,11 @@ function cmdConfigSet(cwd, keyPath, value, raw) {
751
799
  error(`Invalid git.create_tag '${val}'. Must be a boolean (true or false).`);
752
800
  }
753
801
  }
802
+ if (kp === 'git.protected_branches') {
803
+ if (!isValidProtectedBranches(parsedValue)) {
804
+ error(`Invalid git.protected_branches '${val}'. Must be a non-empty array of non-empty branch names.`);
805
+ }
806
+ }
754
807
  if (kp === 'ship.pr_body_sections') {
755
808
  validateShipPrBodySections(parsedValue);
756
809
  }
@@ -926,6 +979,16 @@ function cmdConfigGet(cwd, keyPath, raw, defaultValue) {
926
979
  }
927
980
  }
928
981
  catch (err) {
982
+ // ADR-3889: error() now throws ExitError (carries no message) instead of
983
+ // calling process.exit() directly. The message-sniffing check below
984
+ // (`.startsWith('No config.json')`) can never match an ExitError raised
985
+ // by the "no config.json" error() call above it — ExitError.message
986
+ // defaults to `process exit ${code}` when no message is passed — so
987
+ // without this unconditional guard that ExitError falls through and gets
988
+ // re-wrapped as a WRONG reason (CONFIG_PARSE_FAILED instead of
989
+ // CONFIG_NO_FILE) with a nonsense message, plus a duplicate stderr write.
990
+ if (err instanceof ExitError)
991
+ throw err;
929
992
  if (err.message.startsWith('No config.json'))
930
993
  throw err;
931
994
  error('Failed to read config.json: ' + err.message, ERROR_REASON.CONFIG_PARSE_FAILED);
@@ -1120,16 +1183,42 @@ function cmdConfigPath(cwd, _raw, workstreamContext = null) {
1120
1183
  */
1121
1184
  function cmdMigrateConfig(cwd, raw) {
1122
1185
  const ws = process.env['GSD_WORKSTREAM'] || null;
1123
- const report = (0, configuration_cjs_1.migrateOnDisk)(cwd, ws || undefined);
1186
+ // #3749: resolve the migration target through the project-aware resolver so
1187
+ // GSD_PROJECT scopes the write; migrateOnDisk itself cannot (see its
1188
+ // configPathOverride note).
1189
+ const scopedConfigPath = node_path_1.default.join(planningDir(cwd, ws || undefined), 'config.json');
1190
+ const report = (0, configuration_cjs_1.migrateOnDisk)(cwd, ws || undefined, scopedConfigPath);
1191
+ // #3760: deduplicated on (path, reason), so a repeated invocation stays quiet.
1192
+ if (report.skipped.length > 0) {
1193
+ warnUnusableInput({
1194
+ reason: UNUSABLE_REASON.CONFIG_SECTION_NOT_OBJECT,
1195
+ source: node_path_1.default.join(planningDir(cwd, ws || undefined), 'config.json'),
1196
+ });
1197
+ }
1124
1198
  if (raw) {
1125
- if (!report.migrated) {
1199
+ // #3760: a refused migration is NOT an already-canonical config. Reporting
1200
+ // "no legacy keys found" when a legacy key was found and declined would send
1201
+ // the user away believing there is nothing to fix — and the thing to fix is
1202
+ // the one thing only they can fix, by hand.
1203
+ const declined = report.skipped;
1204
+ const declinedLines = declined.map(s => ` ${s.from} → ${s.to} SKIPPED: '${s.section}' holds a ${s.sectionType}, not an object`);
1205
+ if (!report.migrated && declined.length === 0) {
1126
1206
  const msg = 'No legacy keys found — config is already canonical.';
1127
1207
  output(msg, true, msg);
1128
1208
  }
1209
+ else if (!report.migrated) {
1210
+ const lines = [
1211
+ 'Not migrated — every legacy key found was left in place:',
1212
+ ...declinedLines,
1213
+ 'Fix the section by hand, then re-run. Nothing was written.',
1214
+ ].join('\n');
1215
+ output(lines, true, lines);
1216
+ }
1129
1217
  else {
1130
1218
  const lines = [
1131
1219
  `Migrated: ${String(report.wrote)}`,
1132
1220
  ...report.normalizations.map(n => ` ${n.from} → ${n.to}`),
1221
+ ...declinedLines,
1133
1222
  ].join('\n');
1134
1223
  output(lines, true, lines);
1135
1224
  }
@@ -1151,4 +1240,5 @@ module.exports = {
1151
1240
  // Exported for programmatic use by capability-writer and tests
1152
1241
  setConfigValue,
1153
1242
  setConfigValues,
1243
+ isValidProtectedBranches,
1154
1244
  };