@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
@@ -23,7 +23,7 @@ const node_fs_1 = __importDefault(require("node:fs"));
23
23
  const node_path_1 = __importDefault(require("node:path"));
24
24
  // eslint-disable-next-line @typescript-eslint/no-require-imports
25
25
  const io = require("./io.cjs");
26
- const { output, error } = io;
26
+ const { output, error, formatDiagnosticToken } = io;
27
27
  const pattern_cjs_1 = require("./pattern.cjs");
28
28
  // eslint-disable-next-line @typescript-eslint/no-require-imports
29
29
  const planningWorkspace = require("./planning-workspace.cjs");
@@ -36,6 +36,9 @@ const { scanPhasePlans } = planScanMod;
36
36
  // eslint-disable-next-line @typescript-eslint/no-require-imports
37
37
  const phaseIdMod = require("./phase-id.cjs");
38
38
  const { scopeToPhase } = phaseIdMod;
39
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
40
+ const planningScopeMod = require("./planning-scope.cjs");
41
+ const { SCOPE } = planningScopeMod;
39
42
  /**
40
43
  * Parse REQ-IDs from REQUIREMENTS.md content.
41
44
  *
@@ -54,6 +57,17 @@ function parseRequirements(reqMd) {
54
57
  // The **ID** is extracted from the bullet text caller-side — the seam provides
55
58
  // the raw text; we parse the bold-ID prefix from it here.
56
59
  const boldIdRe = new RegExp(`^\\*\\*(${ID_PATTERN})\\*\\*\\s*(.*)$`);
60
+ // The shipped template (gsd-core/templates/requirements.md) writes
61
+ // `- [ ] **AUTH-01**: User can sign up` — a single separator delimiter
62
+ // between the bold ID and the description. Strip AT MOST ONE leading
63
+ // delimiter (plus its surrounding whitespace) before the final `.trim()`,
64
+ // mirroring roadmap-parser.cts's `stripLeadingDelimiter` delimiter set
65
+ // (em dash, en dash, colon, hyphen) — that helper is not exported, and its
66
+ // own `+`-quantified strip removes an entire delimiter RUN, which would
67
+ // also eat a second, meaningful marker (`**X-01**: -- weird` must keep the
68
+ // `--`), so the set is mirrored here with a single-occurrence match instead
69
+ // of reused verbatim.
70
+ const ONE_LEADING_DELIMITER_RE = /^\s*[—–:-]\s*/;
57
71
  for (const bullet of (0, markdown_sectionizer_cjs_1.iterateBullets)(reqMd)) {
58
72
  if (bullet.marker !== 'checkbox-unchecked' && bullet.marker !== 'checkbox-checked')
59
73
  continue;
@@ -65,7 +79,8 @@ function parseRequirements(reqMd) {
65
79
  continue;
66
80
  if (!seen.has(id)) {
67
81
  seen.add(id);
68
- out.push({ id, text: (m[2] || '').trim() });
82
+ const rawText = m[2] || '';
83
+ out.push({ id, text: rawText.replace(ONE_LEADING_DELIMITER_RE, '').trim() });
69
84
  }
70
85
  }
71
86
  // Pipe-table-row path and separator-row skip stay caller-side
@@ -290,6 +305,8 @@ function runGapAnalysis(cwd, phaseDir, options = {}) {
290
305
  table: '',
291
306
  summary: 'workflow.post_planning_gaps disabled — skipping post-planning gap analysis',
292
307
  counts: { total: 0, covered: 0, uncovered: 0 },
308
+ phase_dir_read_error: null,
309
+ phase_dir_scope: SCOPE.COMPLETE,
293
310
  };
294
311
  }
295
312
  const absPhaseDir = node_path_1.default.isAbsolute(phaseDir) ? phaseDir : node_path_1.default.join(cwd, phaseDir);
@@ -312,12 +329,20 @@ function runGapAnalysis(cwd, phaseDir, options = {}) {
312
329
  }
313
330
  // Read the phase directory once; reuse the listing for both context detection
314
331
  // and plan-file enumeration (avoids redundant readdirSync calls).
315
- let phaseDirFiles = [];
316
- try {
317
- if (node_fs_1.default.existsSync(absPhaseDir))
318
- phaseDirFiles = node_fs_1.default.readdirSync(absPhaseDir);
319
- }
320
- catch { /* unreadable */ }
332
+ //
333
+ // #4014 (epic #3473 B4): findContextMdIn's directory-string form now owns
334
+ // the listing + ENOENT-vs-other discrimination, retiring the local
335
+ // existsSync-guarded readdirSync try/catch — ENOENT (genuinely absent)
336
+ // and a successful-but-empty read both resolve to SCOPE.COMPLETE with an
337
+ // empty listing, exactly like the existsSync guard's short-circuit did.
338
+ const { files: phaseDirFiles, scope: phaseDirScope } = findContextMdIn(absPhaseDir);
339
+ // #3885 (ADR-3473 §8.5): `phaseDirReadError` stays additive for the
340
+ // shipped `phase_dir_read_error` JSON field — derived from `phaseDirScope`
341
+ // rather than from its own caught error, since findContextMdIn's
342
+ // directory-string form reports SCOPE, not the raw errno message.
343
+ const phaseDirReadError = phaseDirScope === SCOPE.UNREADABLE
344
+ ? `Could not read phase directory ${formatDiagnosticToken(absPhaseDir)}`
345
+ : null;
321
346
  // #3511-class: scope the raw listing to this phase dir before the
322
347
  // phase-numbered -CONTEXT.md predicate. `phaseDirFiles` itself stays raw —
323
348
  // it is also reused below only as a `.length > 0` guard ahead of
@@ -384,6 +409,8 @@ function runGapAnalysis(cwd, phaseDir, options = {}) {
384
409
  table: formatGapTable(rows) + '\n' + coverageSummary + '\n\n' + mismatchMsg,
385
410
  summary: coverageSummary + '; extracted 0 of N — possible format mismatch',
386
411
  counts: { total: rows.length, covered, uncovered },
412
+ phase_dir_read_error: phaseDirReadError,
413
+ phase_dir_scope: phaseDirScope,
387
414
  };
388
415
  }
389
416
  return {
@@ -392,6 +419,8 @@ function runGapAnalysis(cwd, phaseDir, options = {}) {
392
419
  table: mismatchMsg,
393
420
  summary: 'extracted 0 of N — possible format mismatch',
394
421
  counts: { total: 0, covered: 0, uncovered: 0 },
422
+ phase_dir_read_error: phaseDirReadError,
423
+ phase_dir_scope: phaseDirScope,
395
424
  };
396
425
  }
397
426
  // #1365: if no items at all, surface a clean no-check message.
@@ -408,6 +437,8 @@ function runGapAnalysis(cwd, phaseDir, options = {}) {
408
437
  table: '## Post-Planning Gap Analysis\n\nNo requirements or decisions to check.\n',
409
438
  summary: 'no requirements or decisions to check',
410
439
  counts: { total: 0, covered: 0, uncovered: 0 },
440
+ phase_dir_read_error: phaseDirReadError,
441
+ phase_dir_scope: phaseDirScope,
411
442
  };
412
443
  }
413
444
  const rows = sortRows([
@@ -425,6 +456,8 @@ function runGapAnalysis(cwd, phaseDir, options = {}) {
425
456
  table: formatGapTable(rows) + '\n' + summary + '\n',
426
457
  summary,
427
458
  counts: { total: rows.length, covered, uncovered },
459
+ phase_dir_read_error: phaseDirReadError,
460
+ phase_dir_scope: phaseDirScope,
428
461
  };
429
462
  }
430
463
  function cmdGapAnalysis(cwd, args, raw) {
@@ -34,57 +34,138 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
34
34
  return (mod && mod.__esModule) ? mod : { "default": mod };
35
35
  };
36
36
  Object.defineProperty(exports, "__esModule", { value: true });
37
- exports.readConfigBaseBranch = readConfigBaseBranch;
37
+ exports._readGitNested = _readGitNested;
38
+ exports._readGitKey = _readGitKey;
38
39
  exports.trySymbolicRef = trySymbolicRef;
39
40
  exports.tryRemoteShow = tryRemoteShow;
40
41
  exports.tryLocalBranch = tryLocalBranch;
41
42
  exports.resolveBaseBranchDiagnostics = resolveBaseBranchDiagnostics;
42
43
  exports.resolveBaseBranch = resolveBaseBranch;
44
+ exports.resolveProtectedBranchStatus = resolveProtectedBranchStatus;
43
45
  exports.gitWorktreeInfoInternal = gitWorktreeInfoInternal;
44
46
  exports.phaseStartCommit = phaseStartCommit;
45
47
  exports.changedFilesSince = changedFilesSince;
46
48
  exports.cmdGitBaseBranch = cmdGitBaseBranch;
47
- const node_fs_1 = __importDefault(require("node:fs"));
48
49
  const node_path_1 = __importDefault(require("node:path"));
50
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
51
+ const configLoader = require("./config-loader.cjs");
52
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
53
+ const io = require("./io.cjs");
54
+ const configuration_cjs_1 = require("./configuration.cjs");
55
+ const security_cjs_1 = require("./security.cjs");
49
56
  const shell_command_projection_cjs_1 = require("./shell-command-projection.cjs");
50
- // ─── Helpers ──────────────────────────────────────────────────────────────────
57
+ const { error, ERROR_REASON } = io;
58
+ const { loadConfig: loadConfigSeam } = configLoader;
59
+ /** Render a rejected config value for a diagnostic without throwing on exotic input. */
60
+ function renderRejected(value) {
61
+ try {
62
+ return (0, security_cjs_1.sanitizeLabel)(JSON.stringify(value) ?? String(value));
63
+ }
64
+ catch {
65
+ return (0, security_cjs_1.sanitizeLabel)(String(value));
66
+ }
67
+ }
68
+ /** Nested-only read of `git.<field>`, mirroring `loadConfigResolved`'s `getNested`. */
69
+ function _readGitNested(config, field) {
70
+ const git = config['git'];
71
+ if (git !== null && typeof git === 'object' && !Array.isArray(git)) {
72
+ return git[field];
73
+ }
74
+ return undefined;
75
+ }
76
+ /**
77
+ * Flat-then-nested read, mirroring `loadConfigResolved`'s own `get()`.
78
+ *
79
+ * Used for `base_branch` ONLY, and only because it HAS a legacy flat spelling:
80
+ * `normalizeLegacyKeys` normally hoists it away, so a flat key that SURVIVED
81
+ * normalization is one the migration refused (a non-object `git` section,
82
+ * #3760) and is the user's last remaining expression of intent.
83
+ *
84
+ * `protected_branches` deliberately does NOT use this. It is new in #3552 with
85
+ * no legacy form, so honouring a top-level spelling would invent an
86
+ * undocumented alias that silently outranks the canonical nested key
87
+ * (round-4 external review).
88
+ */
89
+ function _readGitKey(config, field) {
90
+ if (config[field] !== undefined)
91
+ return config[field];
92
+ return _readGitNested(config, field);
93
+ }
51
94
  /**
52
- * Safely look up `git.base_branch` from the project's config.json.
53
- * Returns the configured value (a non-empty, non-null string) or null.
95
+ * Read the effective root/workstream configuration once for branch policy.
96
+ *
97
+ * Production takes the `loadConfig` branch, with `persist: false` — this is a
98
+ * PREDICATE, invoked on every `execute-phase` and every `ship` run, and a
99
+ * question must not rewrite the file it is asking about. Without it, any project
100
+ * carrying a legacy flat key (`base_branch`, `branching_strategy`, `depth`, …)
101
+ * has `.planning/config.json` silently normalized and rewritten by a call whose
102
+ * entire contract is to answer a boolean (#3648 review Blocker 1).
103
+ *
104
+ * The `readFile` branch is a unit-test seam, NOT a second production path, and
105
+ * it is deliberately narrower than `loadConfig`. It covers exactly two of
106
+ * production's steps — `normalizeLegacyKeys`, then the flat-then-nested lookup
107
+ * — over a single `<cwd>/.planning/config.json`. It does NOT apply
108
+ * root/workstream `_deepMergeConfig`, builtin or `~/.gsd/defaults.json`
109
+ * defaults, or `mergeFederatedConfig`. Tests that assert on any of those must
110
+ * drive `loadConfig` instead; the seam's own tests are scoped to normalization
111
+ * and shape validation, which is all it reproduces (#3648 review Major 3).
54
112
  */
55
- function readConfigBaseBranch(planningDir, deps) {
56
- const readFile = deps?.readFile ??
57
- ((p) => { try {
58
- return node_fs_1.default.readFileSync(p, 'utf8');
113
+ function readEffectiveGitConfig(cwd, deps) {
114
+ let config = {};
115
+ if (deps?.readFile && !deps.loadConfig) {
116
+ const raw = deps.readFile(node_path_1.default.join(cwd, '.planning', 'config.json'));
117
+ if (raw) {
118
+ try {
119
+ const parsed = JSON.parse(raw);
120
+ if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {
121
+ const { parsed: normalized } = (0, configuration_cjs_1.normalizeLegacyKeys)(parsed);
122
+ config = {
123
+ base_branch: _readGitKey(normalized, 'base_branch'),
124
+ protected_branches: _readGitNested(normalized, 'protected_branches'),
125
+ allow_default_branch_commits: _readGitNested(normalized, 'allow_default_branch_commits'),
126
+ };
127
+ }
128
+ }
129
+ catch { /* malformed direct edit contributes no policy values */ }
59
130
  }
60
- catch {
61
- return null;
62
- } });
63
- const configPath = node_path_1.default.join(planningDir, 'config.json');
64
- const raw = readFile(configPath);
65
- if (!raw)
66
- return null;
67
- let cfg;
68
- try {
69
- cfg = JSON.parse(raw);
70
131
  }
71
- catch {
72
- return null;
132
+ else {
133
+ try {
134
+ config = (deps?.loadConfig ?? loadConfigSeam)(cwd, { persist: false });
135
+ }
136
+ catch { /* configuration loading is fail-soft for branch resolution */ }
73
137
  }
74
- if (!cfg || typeof cfg !== 'object' || Array.isArray(cfg))
75
- return null;
76
- const top = cfg;
77
- // Support both "git.base_branch" (nested) and "base_branch" (flat legacy)
78
- const gitSection = top.git;
79
- if (gitSection && typeof gitSection === 'object' && !Array.isArray(gitSection)) {
80
- const nested = gitSection.base_branch;
81
- if (typeof nested === 'string' && nested.trim())
82
- return nested.trim();
138
+ const rawBaseBranch = config.base_branch;
139
+ const baseBranch = typeof rawBaseBranch === 'string' && rawBaseBranch.trim()
140
+ ? rawBaseBranch.trim()
141
+ : null;
142
+ // A protection predicate must not fail OPEN. `config-set` validation is
143
+ // bypassable by a direct edit of .planning/config.json, so one bad element
144
+ // discarding the whole list would silently answer "not protected" for names
145
+ // the user believes are protected — the exact failure #3552 exists to close,
146
+ // reintroduced through a different door (#3648 review Blocker 3). Drop only
147
+ // the bad elements, and report every rejection so it cannot pass unnoticed.
148
+ const rawProtectedBranches = config.protected_branches;
149
+ const protectedBranches = [];
150
+ const rejectedProtectedBranches = [];
151
+ if (Array.isArray(rawProtectedBranches)) {
152
+ for (const branch of rawProtectedBranches) {
153
+ if (typeof branch === 'string' && branch.trim() !== '') {
154
+ protectedBranches.push(branch.trim());
155
+ }
156
+ else {
157
+ rejectedProtectedBranches.push(renderRejected(branch));
158
+ }
159
+ }
160
+ }
161
+ else if (rawProtectedBranches !== undefined && rawProtectedBranches !== null) {
162
+ // Not a list at all — contributes no names, but is still a misconfiguration.
163
+ rejectedProtectedBranches.push(renderRejected(rawProtectedBranches));
83
164
  }
84
- const flat = top.base_branch;
85
- if (typeof flat === 'string' && flat.trim())
86
- return flat.trim();
87
- return null;
165
+ // #3819: nested-only read (mirrors protected_branches — new key, no legacy
166
+ // flat form, so a top-level spelling must not become an undocumented alias).
167
+ const allowDefaultBranchCommits = config.allow_default_branch_commits === true;
168
+ return { baseBranch, protectedBranches, rejectedProtectedBranches, allowDefaultBranchCommits };
88
169
  }
89
170
  /**
90
171
  * Try `git symbolic-ref --short refs/remotes/origin/HEAD` (no network).
@@ -170,7 +251,7 @@ function tryLocalBranch(cwd, execGit) {
170
251
  * Consults the full precedence ladder and always returns a non-empty string.
171
252
  * Never throws.
172
253
  */
173
- function resolveBaseBranchDiagnostics(cwd, deps) {
254
+ function resolveBaseBranchDiagnosticsWithConfig(cwd, configured, deps) {
174
255
  const rawExecGit = deps?.execGit ?? shell_command_projection_cjs_1.execGit;
175
256
  // A genuine execGit failure (timeout, or the call could not even spawn —
176
257
  // e.g. git missing, surfaced as exitCode 127 with `error` set) is distinct
@@ -186,10 +267,7 @@ function resolveBaseBranchDiagnostics(cwd, deps) {
186
267
  anyGitFailure = true;
187
268
  return r;
188
269
  };
189
- // Derive .planning dir relative to cwd (mirrors planningDir() in planning-workspace.cjs)
190
- const planningDir = node_path_1.default.join(cwd, '.planning');
191
270
  // 1. Config override
192
- const configured = readConfigBaseBranch(planningDir, deps);
193
271
  if (configured)
194
272
  return { branch: configured, verified: true };
195
273
  // 2. symbolic-ref (fast, no network)
@@ -210,6 +288,10 @@ function resolveBaseBranchDiagnostics(cwd, deps) {
210
288
  // not answer (#3057 B4).
211
289
  return { branch: 'main', verified: !anyGitFailure };
212
290
  }
291
+ function resolveBaseBranchDiagnostics(cwd, deps) {
292
+ const { baseBranch } = readEffectiveGitConfig(cwd, deps);
293
+ return resolveBaseBranchDiagnosticsWithConfig(cwd, baseBranch, deps);
294
+ }
213
295
  /**
214
296
  * Resolve the default/base branch for the repository at `cwd`.
215
297
  *
@@ -220,6 +302,23 @@ function resolveBaseBranchDiagnostics(cwd, deps) {
220
302
  function resolveBaseBranch(cwd, deps) {
221
303
  return resolveBaseBranchDiagnostics(cwd, deps).branch;
222
304
  }
305
+ /** Resolve the base branch plus configured protected-branch extensions. */
306
+ function resolveProtectedBranchStatus(cwd, currentBranch, deps) {
307
+ const effectiveConfig = readEffectiveGitConfig(cwd, deps);
308
+ const { branch: baseBranch, verified } = resolveBaseBranchDiagnosticsWithConfig(cwd, effectiveConfig.baseBranch, deps);
309
+ const protectedBranches = [...new Set([
310
+ ...(effectiveConfig.allowDefaultBranchCommits ? [] : [baseBranch]),
311
+ ...effectiveConfig.protectedBranches,
312
+ ])];
313
+ return {
314
+ baseBranch,
315
+ protectedBranches,
316
+ rejectedProtectedBranches: effectiveConfig.rejectedProtectedBranches,
317
+ isProtected: protectedBranches.includes(currentBranch),
318
+ verified,
319
+ allowDefaultBranchCommits: effectiveConfig.allowDefaultBranchCommits,
320
+ };
321
+ }
223
322
  /**
224
323
  * Detect whether `cwd` sits inside a git worktree, and if so, return the
225
324
  * absolute path of the worktree root.
@@ -352,7 +451,51 @@ function changedFilesSince(cwd, sinceRef, execGit) {
352
451
  * Resolves the default branch and writes it to stdout (raw string, newline-terminated).
353
452
  * Called by workflows via `gsd_run query git.base-branch`.
354
453
  */
355
- function cmdGitBaseBranch(cwd, _args, deps) {
454
+ function cmdGitBaseBranch(cwd, args, deps) {
455
+ if (args[0] === '--is-protected') {
456
+ if (args.length > 2) {
457
+ error('Usage: git base-branch --is-protected [<branch>]', ERROR_REASON.USAGE);
458
+ }
459
+ const writeDiagnostic = deps?.writeDiagnostic ?? ((s) => process.stderr.write(s));
460
+ // `git branch --show-current` prints nothing on a detached HEAD, so the
461
+ // call sites legitimately pass an explicit empty string: no protected
462
+ // branch is named '', the answer is false, and that is not a fault worth
463
+ // reporting. The flag with NO argument is a different thing — a caller bug
464
+ // that `args[1] ?? ''` used to collapse into the detached-HEAD case. Same
465
+ // handling, but said out loud so the two can be told apart.
466
+ //
467
+ // This diagnostic deliberately does NOT state the answer. The empty branch
468
+ // matches no protected name, but the fail-closed guard below still renders
469
+ // `true` when the base branch could not be verified — so promising "false"
470
+ // here would contradict what this same call prints on stdout (#3648 review).
471
+ if (args.length < 2) {
472
+ writeDiagnostic(`⚠ git-base-branch: --is-protected was called without a branch argument; ` +
473
+ `treating it as an empty branch name. Pass the branch to test, ` +
474
+ `e.g. --is-protected "$CURRENT_BRANCH".\n`);
475
+ }
476
+ const status = resolveProtectedBranchStatus(cwd, args[1] ?? '', deps);
477
+ if (status.rejectedProtectedBranches.length > 0) {
478
+ writeDiagnostic(`⚠ git-base-branch: ignoring ${status.rejectedProtectedBranches.length} unusable ` +
479
+ `git.protected_branches entr${status.rejectedProtectedBranches.length === 1 ? 'y' : 'ies'} ` +
480
+ `(${status.rejectedProtectedBranches.join(', ')}) — each must be a non-empty branch name. ` +
481
+ `The remaining names are still enforced. See #3552.\n`);
482
+ }
483
+ // A protection guard must fail closed: if the base branch could not be
484
+ // verified against this repository (a git query timed out or failed to
485
+ // run — #3057 B4), report "protected" rather than silently trusting an
486
+ // unverified guess that might happen to not match the current branch.
487
+ const rendered = String(status.verified ? status.isProtected : true);
488
+ if (!status.verified) {
489
+ writeDiagnostic(`⚠ git-base-branch: --is-protected could not verify repository branch metadata; ` +
490
+ `defaulting to protected (fail-closed). See #3057.\n`);
491
+ }
492
+ const write = deps?.write ?? ((s) => process.stdout.write(s));
493
+ write(rendered + '\n');
494
+ return rendered;
495
+ }
496
+ if (args.length > 0) {
497
+ error(`Unknown flag for git.base-branch: ${args[0]}`, ERROR_REASON.USAGE);
498
+ }
356
499
  const { branch, verified } = resolveBaseBranchDiagnostics(cwd, deps);
357
500
  if (!verified) {
358
501
  const writeDiagnostic = deps?.writeDiagnostic ?? ((s) => process.stderr.write(s));
@@ -35,7 +35,7 @@ const healthDiagnosticMod = require("../health-diagnostic-types.cjs");
35
35
  const { SEVERITY, adviseRemedy } = healthDiagnosticMod;
36
36
  // eslint-disable-next-line @typescript-eslint/no-require-imports
37
37
  const phaseIdMod = require("../phase-id.cjs");
38
- const { isSentinelPhaseId } = phaseIdMod;
38
+ const { isSentinelPhaseDir } = phaseIdMod;
39
39
  // ─── C001 — gap in disk phase numbering (integer sequence) ────────────────
40
40
  // (verify.cts:1504-1519)
41
41
  function checkC001(snapshot) {
@@ -47,8 +47,12 @@ function checkC001(snapshot) {
47
47
  return [];
48
48
  const integerPhases = snapshot.allPhaseDirNames.value
49
49
  // #3225: exclude sentinel phase ids (999.x/0.x) — never part of the
50
- // sequential numbering, mirrors verify.cts:1510 verbatim.
51
- .filter((p) => !p.includes('.') && !isSentinelPhaseId(p))
50
+ // sequential numbering, mirrors verify.cts:1510 verbatim. The dot filter
51
+ // already drops every code-prefixed (bracket) name before the sentinel
52
+ // test, so the dir-aware recognizer below is defense-in-depth for the
53
+ // dotless legacy forms — kept so this guard cannot regress if the dot
54
+ // filter is ever loosened (#3639).
55
+ .filter((p) => !p.includes('.') && !isSentinelPhaseDir(p))
52
56
  .map((p) => parseInt(p, 10))
53
57
  .filter((n) => !Number.isNaN(n))
54
58
  .sort((a, b) => a - b);
@@ -56,7 +56,13 @@ const healthDiagnosticMod = require("../health-diagnostic-types.cjs");
56
56
  const { SEVERITY, adviseRemedy } = healthDiagnosticMod;
57
57
  // eslint-disable-next-line @typescript-eslint/no-require-imports
58
58
  const validateMod = require("../validate.cjs");
59
- const { phaseDirNameRe } = validateMod;
59
+ // #612: `isPhaseDirName` is the convention-SELECTED shape test wrapping
60
+ // `phaseDirNameRe`. Handed no convention it delegates to that very regex, so a
61
+ // legacy repo's W005 reading is byte-identical; handed 'bracket' it also
62
+ // recognizes `{CODE}.{MM}-{PP}-slug`, which `phaseDirNameRe` rejects outright —
63
+ // left un-threaded, W005 fires on EVERY phase directory of a repo that opted
64
+ // into the convention, i.e. the check inverts on exactly the repos PR-2 widens.
65
+ const { isPhaseDirName } = validateMod;
60
66
  // eslint-disable-next-line @typescript-eslint/no-require-imports
61
67
  const phaseIdMod = require("../phase-id.cjs");
62
68
  const { extractPhaseToken, normalizePhaseName, comparePhaseNum } = phaseIdMod;
@@ -64,7 +70,7 @@ const { extractPhaseToken, normalizePhaseName, comparePhaseNum } = phaseIdMod;
64
70
  function checkW005(snapshot) {
65
71
  const diagnostics = [];
66
72
  for (const name of snapshot.phaseDirs.value) {
67
- if (!name.match(phaseDirNameRe)) {
73
+ if (!isPhaseDirName(name, snapshot.phaseIdConvention)) {
68
74
  diagnostics.push({
69
75
  code: 'W005',
70
76
  severity: SEVERITY.WARNING,
@@ -104,7 +104,7 @@ const planningScopeMod = require("../planning-scope.cjs");
104
104
  const { SCOPE } = planningScopeMod;
105
105
  // eslint-disable-next-line @typescript-eslint/no-require-imports
106
106
  const phaseIdMod = require("../phase-id.cjs");
107
- const { matchPhaseDirs, normalizePhaseName, extractPhaseToken, isSentinelPhaseId } = phaseIdMod;
107
+ const { matchPhaseDirs, normalizePhaseName, extractPhaseToken, isSentinelPhaseId, isSentinelPhaseDir } = phaseIdMod;
108
108
  // eslint-disable-next-line @typescript-eslint/no-require-imports
109
109
  const validateMod = require("../validate.cjs");
110
110
  const { phaseVariants } = validateMod;
@@ -116,8 +116,16 @@ const { phaseVariants } = validateMod;
116
116
  * does the roadmap claim, for W007) call this — one matcher, reused, per the
117
117
  * file-level comment.
118
118
  */
119
- function dirsForPhase(dirs, phaseId) {
120
- const canonical = matchPhaseDirs(dirs, normalizePhaseName(phaseId)).matches;
119
+ function dirsForPhase(dirs, phaseId, convention) {
120
+ // #612: `convention` reaches BOTH operands of this pairing or it pairs
121
+ // nothing. The phases come from a bracket-aware heading scan and the dirs
122
+ // from a bracket-aware disk scan, so a convention-less selector here leaves
123
+ // W006's existence check and W007's `claimedDirs` empty on exactly the repos
124
+ // both scans just widened — every bracket phase reads as "no directory on
125
+ // disk" AND every bracket directory reads as unclaimed. `matchPhaseDirs`
126
+ // forwards it to its primary `phaseTokenMatches`; handed nothing it is
127
+ // byte-identical, so no legacy repo's pairing changes.
128
+ const canonical = matchPhaseDirs(dirs, normalizePhaseName(phaseId), convention).matches;
121
129
  if (canonical.length > 0)
122
130
  return canonical;
123
131
  // Bug 2 (#3309 W006/W007 migration cluster, found while fixing the
@@ -139,7 +147,12 @@ function dirsForPhase(dirs, phaseId) {
139
147
  // alone misses without re-deriving a second matcher.
140
148
  const variants = phaseVariants(phaseId);
141
149
  return dirs.filter((d) => {
142
- const token = extractPhaseToken(d).toUpperCase();
150
+ // #612: the same convention the primary matcher above got. This fallback
151
+ // compares a DIRECTORY-derived token against phase-id variants, so left
152
+ // convention-less it reads `GSD.02-05-slug` with the legacy grammar and can
153
+ // never agree with a bracket phase id — the fallback would then be dead on
154
+ // precisely the convention it is reached for.
155
+ const token = extractPhaseToken(d, convention).toUpperCase();
143
156
  for (const variant of variants) {
144
157
  if (token === variant.toUpperCase())
145
158
  return true;
@@ -173,15 +186,40 @@ function checkW006(snapshot) {
173
186
  if (snapshot.roadmapDeclaredPhases.scope !== SCOPE.COMPLETE)
174
187
  return [];
175
188
  const dirs = snapshot.allPhaseDirNames.value;
189
+ const convention = snapshot.phaseIdConvention;
176
190
  const archivedTokens = new Set(snapshot.archivedPhaseTokens.value);
191
+ // No separate scope guard: `roadmapSentinelPhaseTokens` and
192
+ // `roadmapDeclaredPhases` are produced by ONE builder call
193
+ // (`buildRoadmapDeclaredPhasesField`) from ONE `buildRoadmapPhaseVariants`
194
+ // result, so their scopes are yoked by construction — the COMPLETE check
195
+ // above covers both. Stated rather than left implicit, because reading the
196
+ // sentinel set unconditionally would be a latent bug the day the two fields
197
+ // acquire independent builders: an UNREADABLE sentinel set silently degrades
198
+ // to "nothing is a sentinel," which ADDS W006 warnings rather than dropping
199
+ // them.
200
+ const sentinelTokens = new Set(snapshot.roadmapSentinelPhaseTokens.value);
177
201
  const checkboxes = snapshot.roadmapPhaseCheckboxes.value;
178
202
  const diagnostics = [];
179
203
  for (const { phaseId } of snapshot.roadmapDeclaredPhases.value) {
180
- // #3225: sentinel phase ids (999.x/0.x) are never-on-roadmap by
181
- // convention; a sentinel heading shouldn't demand a directory.
182
- if (isSentinelPhaseId(phaseId))
204
+ // A backlog/icebox phase legitimately has no directory. The two guards are
205
+ // DISJOINT, which is why both are needed and neither subsumes the other:
206
+ //
207
+ // #612 `roadmapSentinelPhaseTokens` — bracket sentinels. Under the
208
+ // bracket convention the sentinel-ness lives in the MILESTONE, not
209
+ // the token: the heading `### [GSD.999] 07:` yields phase id `07`,
210
+ // so a bare-token test cannot see it. The snapshot's
211
+ // `buildRoadmapPhaseVariants` call carries the bracket id alongside
212
+ // and reports the token here. Empty unless the bracket convention
213
+ // is active, so no legacy repo's reading changes.
214
+ // #3225 `isSentinelPhaseId` — the legacy/bare leading-int rule (999.x/0.x),
215
+ // which sees `### Phase 999:` and is blind to `[GSD.999] 07`.
216
+ //
217
+ // Upstream's call is kept one-arg verbatim: every `phaseId` here is a bare
218
+ // token, never a `{CODE}.{MM}`-qualified id, so a convention argument could
219
+ // not change its result.
220
+ if (sentinelTokens.has(phaseId) || isSentinelPhaseId(phaseId))
183
221
  continue;
184
- if (dirsForPhase(dirs, phaseId).length > 0)
222
+ if (dirsForPhase(dirs, phaseId, convention).length > 0)
185
223
  continue;
186
224
  // Bug 1 (#3309 W006/W007 migration cluster): a phase whose ONLY
187
225
  // directory lives under a milestone archive
@@ -209,10 +247,10 @@ function checkW006(snapshot) {
209
247
  // ─── W007 — an on-disk phase directory has no matching ROADMAP entry ──────
210
248
  // (verify.cts:2086-2101)
211
249
  /** Every directory in `dirs` that ANY declared roadmap phase resolves to. */
212
- function computeClaimedDirs(dirs, declaredPhases) {
250
+ function computeClaimedDirs(dirs, declaredPhases, convention) {
213
251
  const claimed = new Set();
214
252
  for (const { phaseId } of declaredPhases) {
215
- for (const dir of dirsForPhase(dirs, phaseId))
253
+ for (const dir of dirsForPhase(dirs, phaseId, convention))
216
254
  claimed.add(dir);
217
255
  }
218
256
  return claimed;
@@ -222,17 +260,25 @@ function checkW007(snapshot) {
222
260
  if (snapshot.roadmapDeclaredPhases.scope !== SCOPE.COMPLETE)
223
261
  return [];
224
262
  const dirs = snapshot.allPhaseDirNames.value;
225
- const claimedDirs = computeClaimedDirs(dirs, snapshot.roadmapDeclaredPhases.value);
263
+ const convention = snapshot.phaseIdConvention;
264
+ const claimedDirs = computeClaimedDirs(dirs, snapshot.roadmapDeclaredPhases.value, convention);
226
265
  const diagnostics = [];
227
266
  for (const dirName of dirs) {
228
267
  // `extractPhaseToken` is the phase-id.cts owner `PHASE_TOKEN_FROM_DIR_RE`
229
268
  // (`src/validate.cts:73-76`) is documented to match exactly
230
269
  // (verify.cts's original `p` key from `collectDiskPhaseEntries`,
231
270
  // `verify.cts:1373-1397`) — same token, relocated read, not reinvented.
232
- const token = extractPhaseToken(dirName);
271
+ // #612: `collectDiskPhaseEntries` keyed that map through the
272
+ // convention-aware extractor, so the token this message names must be
273
+ // derived the same way — otherwise a bracket repo's unclaimed
274
+ // `GSD.02-05-slug` is reported as phase `GSD.02` rather than `05`.
275
+ const token = extractPhaseToken(dirName, convention);
233
276
  // #3225: a sentinel dir on disk (999-interim, 0-drafts) is defined as
234
- // never-on-roadmap; it must not trigger W007.
235
- if (isSentinelPhaseId(token))
277
+ // never-on-roadmap; it must not trigger W007. #3639: judged on the DIR
278
+ // NAME via the dir-aware recognizer — the extracted token is
279
+ // milestone-stripped, so a bracket sentinel (GSD.999-07-icebox) was
280
+ // invisible to the id predicate and false-fired as an orphan.
281
+ if (isSentinelPhaseDir(dirName))
236
282
  continue;
237
283
  if (claimedDirs.has(dirName))
238
284
  continue;