@opengsd/gsd-core 1.12.0 → 1.14.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 (455) 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-advisor-researcher.compact.md +85 -0
  5. package/agents/gsd-ai-researcher.compact.md +96 -0
  6. package/agents/gsd-assumptions-analyzer.compact.md +81 -0
  7. package/agents/gsd-code-fixer.compact.md +458 -0
  8. package/agents/gsd-code-fixer.md +5 -5
  9. package/agents/gsd-code-reviewer.compact.md +269 -0
  10. package/agents/gsd-code-reviewer.md +15 -3
  11. package/agents/gsd-codebase-mapper.compact.md +760 -0
  12. package/agents/gsd-debug-session-manager.compact.md +345 -0
  13. package/agents/gsd-doc-classifier.compact.md +192 -0
  14. package/agents/gsd-doc-synthesizer.compact.md +200 -0
  15. package/agents/gsd-doc-verifier.compact.md +143 -0
  16. package/agents/gsd-doc-writer.compact.md +440 -0
  17. package/agents/gsd-dom-verifier.compact.md +138 -0
  18. package/agents/gsd-domain-researcher.compact.md +141 -0
  19. package/agents/gsd-eval-auditor.compact.md +160 -0
  20. package/agents/gsd-eval-planner.compact.md +137 -0
  21. package/agents/gsd-executor.md +63 -35
  22. package/agents/gsd-framework-selector.compact.md +82 -0
  23. package/agents/gsd-integration-checker.compact.md +245 -0
  24. package/agents/gsd-intel-updater.compact.md +226 -0
  25. package/agents/gsd-mempalace-curator.compact.md +45 -0
  26. package/agents/gsd-nyquist-auditor.compact.md +179 -0
  27. package/agents/gsd-pattern-mapper.compact.md +275 -0
  28. package/agents/gsd-plan-checker.md +76 -57
  29. package/agents/gsd-planner.md +14 -0
  30. package/agents/gsd-project-researcher.compact.md +587 -0
  31. package/agents/gsd-research-synthesizer.compact.md +212 -0
  32. package/agents/gsd-roadmapper.compact.md +454 -0
  33. package/agents/gsd-roadmapper.md +13 -0
  34. package/agents/gsd-security-auditor.compact.md +162 -0
  35. package/agents/gsd-ui-auditor.compact.md +404 -0
  36. package/agents/gsd-ui-checker.compact.md +277 -0
  37. package/agents/gsd-ui-checker.md +19 -3
  38. package/agents/gsd-ui-researcher.compact.md +282 -0
  39. package/agents/gsd-ui-researcher.md +29 -0
  40. package/agents/gsd-user-profiler.compact.md +108 -0
  41. package/agents/gsd-verifier.md +23 -1
  42. package/bin/install.js +444 -134
  43. package/commands/gsd/cleanup.md +1 -0
  44. package/commands/gsd/code-review.md +2 -1
  45. package/commands/gsd/complete-milestone.md +1 -0
  46. package/commands/gsd/config.md +1 -0
  47. package/commands/gsd/debug.md +1 -0
  48. package/commands/gsd/execute-phase.md +1 -1
  49. package/commands/gsd/graphify.md +1 -0
  50. package/commands/gsd/health.md +1 -0
  51. package/commands/gsd/mempalace-capture.md +1 -0
  52. package/commands/gsd/mempalace-recall.md +1 -0
  53. package/commands/gsd/new-milestone.md +1 -0
  54. package/commands/gsd/new-project.md +1 -0
  55. package/commands/gsd/next.md +1 -0
  56. package/commands/gsd/ns-workflow.md +2 -1
  57. package/commands/gsd/pause-work.md +1 -0
  58. package/commands/gsd/phase.md +2 -1
  59. package/commands/gsd/pr-branch.md +1 -0
  60. package/commands/gsd/quick-batch.md +105 -0
  61. package/commands/gsd/resume-work.md +1 -0
  62. package/commands/gsd/review-backlog.md +1 -0
  63. package/commands/gsd/settings.md +2 -1
  64. package/commands/gsd/stats.md +1 -0
  65. package/commands/gsd/surface.md +18 -8
  66. package/commands/gsd/thread.md +1 -0
  67. package/commands/gsd/workspace.md +1 -0
  68. package/commands/gsd/workstreams.md +1 -0
  69. package/gsd-core/bin/check-latest-version.cjs +8 -3
  70. package/gsd-core/bin/gsd-tools.cjs +532 -174
  71. package/gsd-core/bin/lib/adr-parser.cjs +1 -1
  72. package/gsd-core/bin/lib/artifacts.cjs +2 -1
  73. package/gsd-core/bin/lib/audit.cjs +39 -22
  74. package/gsd-core/bin/lib/broken-windows.cjs +168 -49
  75. package/gsd-core/bin/lib/capability-activation.cjs +27 -0
  76. package/gsd-core/bin/lib/capability-lifecycle.cjs +10 -6
  77. package/gsd-core/bin/lib/capability-loader.cjs +135 -1
  78. package/gsd-core/bin/lib/capability-registry.cjs +528 -116
  79. package/gsd-core/bin/lib/capability-source.cjs +19 -2
  80. package/gsd-core/bin/lib/capability-state.cjs +7 -1
  81. package/gsd-core/bin/lib/capability-validator.cjs +134 -5
  82. package/gsd-core/bin/lib/capability-writer.cjs +14 -4
  83. package/gsd-core/bin/lib/check-command-router.cjs +198 -38
  84. package/gsd-core/bin/lib/claude-orchestration.cjs +10 -25
  85. package/gsd-core/bin/lib/clusters.cjs +1 -0
  86. package/gsd-core/bin/lib/code-review-depth.cjs +2 -2
  87. package/gsd-core/bin/lib/command-aliases.cjs +16 -0
  88. package/gsd-core/bin/lib/commands.cjs +981 -79
  89. package/gsd-core/bin/lib/config-loader.cjs +4 -0
  90. package/gsd-core/bin/lib/config.cjs +153 -38
  91. package/gsd-core/bin/lib/core-utils.cjs +34 -7
  92. package/gsd-core/bin/lib/coverage.cjs +1 -1
  93. package/gsd-core/bin/lib/decisions.cjs +343 -28
  94. package/gsd-core/bin/lib/edge-probe.cjs +14 -1
  95. package/gsd-core/bin/lib/external-descriptor-trust.cjs +29 -14
  96. package/gsd-core/bin/lib/file-overlap-partitioner.cjs +74 -0
  97. package/gsd-core/bin/lib/frontmatter.cjs +137 -23
  98. package/gsd-core/bin/lib/gap-checker.cjs +22 -13
  99. package/gsd-core/bin/lib/git-base-branch.cjs +10 -2
  100. package/gsd-core/bin/lib/gsd2-import.cjs +1 -2
  101. package/gsd-core/bin/lib/health-diagnostic-rules/phase-structure.cjs +8 -2
  102. package/gsd-core/bin/lib/health-diagnostic-rules/roadmap-disk-consistency.cjs +54 -11
  103. package/gsd-core/bin/lib/health-diagnostic-rules/state-consistency.cjs +87 -23
  104. package/gsd-core/bin/lib/health-diagnostic-rules/worktree-health.cjs +1 -1
  105. package/gsd-core/bin/lib/host-integration.cjs +57 -5
  106. package/gsd-core/bin/lib/init-command-router.cjs +14 -0
  107. package/gsd-core/bin/lib/init.cjs +539 -60
  108. package/gsd-core/bin/lib/install-engine.cjs +199 -14
  109. package/gsd-core/bin/lib/install-model-override-resolver.cjs +45 -0
  110. package/gsd-core/bin/lib/install-profiles.cjs +36 -14
  111. package/gsd-core/bin/lib/installer-migration-report.cjs +1 -0
  112. package/gsd-core/bin/lib/installer-migrations.cjs +33 -4
  113. package/gsd-core/bin/lib/io.cjs +35 -0
  114. package/gsd-core/bin/lib/loop-resolver.cjs +64 -39
  115. package/gsd-core/bin/lib/markdown-table.cjs +123 -0
  116. package/gsd-core/bin/lib/mcp-catalog.cjs +2 -2
  117. package/gsd-core/bin/lib/milestone.cjs +41 -10
  118. package/gsd-core/bin/lib/model-resolver.cjs +101 -10
  119. package/gsd-core/bin/lib/phase-command-router.cjs +20 -7
  120. package/gsd-core/bin/lib/phase-id.cjs +412 -31
  121. package/gsd-core/bin/lib/phase-lifecycle.cjs +61 -0
  122. package/gsd-core/bin/lib/phase.cjs +941 -98
  123. package/gsd-core/bin/lib/plan-document.cjs +10 -0
  124. package/gsd-core/bin/lib/planning-inspect.cjs +34 -18
  125. package/gsd-core/bin/lib/planning-snapshot.cjs +206 -30
  126. package/gsd-core/bin/lib/planning-workspace.cjs +153 -29
  127. package/gsd-core/bin/lib/pristine-baseline.cjs +182 -0
  128. package/gsd-core/bin/lib/prohibition-enforcement.cjs +91 -4
  129. package/gsd-core/bin/lib/quick-batch-command-router.cjs +285 -0
  130. package/gsd-core/bin/lib/quick-batch-dispatch.cjs +250 -0
  131. package/gsd-core/bin/lib/quick-batch.cjs +840 -0
  132. package/gsd-core/bin/lib/refactor-trigger-command-router.cjs +61 -2
  133. package/gsd-core/bin/lib/research-store.cjs +11 -12
  134. package/gsd-core/bin/lib/review-lane-descriptor.cjs +53 -5
  135. package/gsd-core/bin/lib/review-lane-invocation.cjs +96 -1
  136. package/gsd-core/bin/lib/review-lane-runner.cjs +136 -10
  137. package/gsd-core/bin/lib/reviewer-step-dispatch.cjs +337 -0
  138. package/gsd-core/bin/lib/roadmap-parser.cjs +555 -41
  139. package/gsd-core/bin/lib/roadmap.cjs +292 -69
  140. package/gsd-core/bin/lib/runtime-artifact-conversion.cjs +260 -43
  141. package/gsd-core/bin/lib/runtime-artifact-install-plan.cjs +28 -20
  142. package/gsd-core/bin/lib/runtime-artifact-layout.cjs +294 -108
  143. package/gsd-core/bin/lib/runtime-hooks-surface.cjs +408 -47
  144. package/gsd-core/bin/lib/security.cjs +126 -7
  145. package/gsd-core/bin/lib/shell-command-projection.cjs +4 -0
  146. package/gsd-core/bin/lib/smart-entry.cjs +7 -9
  147. package/gsd-core/bin/lib/state-document.cjs +159 -32
  148. package/gsd-core/bin/lib/state-md-schema.cjs +44 -27
  149. package/gsd-core/bin/lib/state-transition.cjs +465 -62
  150. package/gsd-core/bin/lib/state.cjs +906 -151
  151. package/gsd-core/bin/lib/surface.cjs +83 -10
  152. package/gsd-core/bin/lib/task-command-router.cjs +12 -6
  153. package/gsd-core/bin/lib/tdd-red-evidence.cjs +133 -0
  154. package/gsd-core/bin/lib/uat.cjs +1420 -516
  155. package/gsd-core/bin/lib/update-context.cjs +36 -26
  156. package/gsd-core/bin/lib/validate.cjs +230 -12
  157. package/gsd-core/bin/lib/vendor/js-yaml.cjs +11 -3
  158. package/gsd-core/bin/lib/verification-command-router.cjs +2 -1
  159. package/gsd-core/bin/lib/verification.cjs +316 -23
  160. package/gsd-core/bin/lib/verify-command-grounding.cjs +1 -1
  161. package/gsd-core/bin/lib/verify-command-router.cjs +1 -0
  162. package/gsd-core/bin/lib/verify.cjs +531 -36
  163. package/gsd-core/bin/lib/workstream-inventory.cjs +21 -2
  164. package/gsd-core/bin/lib/worktree-safety.cjs +21 -7
  165. package/gsd-core/bin/shared/config-defaults.manifest.json +1 -0
  166. package/gsd-core/bin/shared/config-schema.manifest.json +13 -0
  167. package/gsd-core/bin/verify-reapply-patches.cjs +507 -81
  168. package/gsd-core/references/agent-contracts.md +3 -3
  169. package/gsd-core/references/compact-content-gate.md +66 -0
  170. package/gsd-core/references/edge-probe.md +17 -13
  171. package/gsd-core/references/execute-mvp-tdd.md +18 -16
  172. package/gsd-core/references/execute-phase-response-language.md +6 -0
  173. package/gsd-core/references/executor-examples.md +42 -0
  174. package/gsd-core/references/few-shot-examples/plan-checker.md +15 -15
  175. package/gsd-core/references/loop-hook-dispatch.md +18 -0
  176. package/gsd-core/references/model-profiles.md +12 -3
  177. package/gsd-core/references/mvp-concepts.md +2 -2
  178. package/gsd-core/references/plan-checker-examples.md +41 -0
  179. package/gsd-core/references/planner-antipatterns.md +25 -0
  180. package/gsd-core/references/planner-chunked.md +5 -1
  181. package/gsd-core/references/planner-coupling.md +42 -0
  182. package/gsd-core/references/planner-quick-batch.md +71 -0
  183. package/gsd-core/references/planner-reviews.md +47 -0
  184. package/gsd-core/references/planner-revision.md +75 -2
  185. package/gsd-core/references/planning-config.md +5 -1
  186. package/gsd-core/references/response-language-directive.md +9 -0
  187. package/gsd-core/references/revision-loop.md +118 -11
  188. package/gsd-core/references/tdd.md +17 -9
  189. package/gsd-core/references/thinking-models-planning.md +18 -2
  190. package/gsd-core/references/verification-patterns.md +17 -4
  191. package/gsd-core/references/verifier-evidence-gate.md +160 -0
  192. package/gsd-core/references/worktree-path-safety.md +112 -2
  193. package/gsd-core/templates/README.md +7 -1
  194. package/gsd-core/templates/phase-prompt.md +4 -0
  195. package/gsd-core/templates/state.md +6 -3
  196. package/gsd-core/templates/summary.compact.md +212 -0
  197. package/gsd-core/templates/user-setup.compact.md +199 -0
  198. package/gsd-core/templates/user-setup.md +0 -9
  199. package/gsd-core/templates/verification-report.md +5 -0
  200. package/gsd-core/workflows/add-backlog.md +2 -0
  201. package/gsd-core/workflows/add-phase.md +2 -0
  202. package/gsd-core/workflows/add-tests.md +1 -1
  203. package/gsd-core/workflows/add-todo.md +4 -3
  204. package/gsd-core/workflows/ai-integration-phase.md +1 -1
  205. package/gsd-core/workflows/analyze-dependencies.md +2 -0
  206. package/gsd-core/workflows/audit-fix.md +2 -0
  207. package/gsd-core/workflows/audit-milestone.md +2 -0
  208. package/gsd-core/workflows/audit-uat.md +2 -0
  209. package/gsd-core/workflows/autonomous.md +15 -10
  210. package/gsd-core/workflows/check-todos.md +5 -3
  211. package/gsd-core/workflows/cleanup.md +4 -2
  212. package/gsd-core/workflows/code-review/steps/structural-pre-pass.md +22 -13
  213. package/gsd-core/workflows/code-review-fix.md +5 -3
  214. package/gsd-core/workflows/code-review.md +211 -43
  215. package/gsd-core/workflows/complete-milestone/detail/elaboration.md +274 -0
  216. package/gsd-core/workflows/complete-milestone.md +40 -254
  217. package/gsd-core/workflows/debug.md +1 -1
  218. package/gsd-core/workflows/diagnose-issues.md +5 -1
  219. package/gsd-core/workflows/discuss-phase/modes/advisor.md +2 -0
  220. package/gsd-core/workflows/discuss-phase/modes/all.md +2 -0
  221. package/gsd-core/workflows/discuss-phase/modes/analyze.md +2 -0
  222. package/gsd-core/workflows/discuss-phase/modes/auto.md +2 -0
  223. package/gsd-core/workflows/discuss-phase/modes/batch.md +2 -0
  224. package/gsd-core/workflows/discuss-phase/modes/chain.md +2 -0
  225. package/gsd-core/workflows/discuss-phase/modes/default.md +2 -0
  226. package/gsd-core/workflows/discuss-phase/modes/power.md +2 -0
  227. package/gsd-core/workflows/discuss-phase/modes/text.md +2 -0
  228. package/gsd-core/workflows/discuss-phase/templates/context.md +2 -0
  229. package/gsd-core/workflows/discuss-phase/templates/discussion-log.md +2 -0
  230. package/gsd-core/workflows/discuss-phase-assumptions.md +1 -1
  231. package/gsd-core/workflows/discuss-phase-power.md +2 -0
  232. package/gsd-core/workflows/discuss-phase.md +1 -1
  233. package/gsd-core/workflows/do.md +43 -13
  234. package/gsd-core/workflows/docs-update/detail/elaboration.md +179 -0
  235. package/gsd-core/workflows/docs-update.md +15 -156
  236. package/gsd-core/workflows/edit-phase.md +2 -0
  237. package/gsd-core/workflows/eval-review.md +1 -1
  238. package/gsd-core/workflows/execute-phase/detail/elaboration.md +124 -0
  239. package/gsd-core/workflows/execute-phase/steps/codebase-drift-gate.md +20 -3
  240. package/gsd-core/workflows/execute-phase/steps/completion-reconciliation.md +56 -0
  241. package/gsd-core/workflows/execute-phase/steps/executor-isolation-dispatch.md +24 -3
  242. package/gsd-core/workflows/execute-phase/steps/executor-progress-policy.md +43 -0
  243. package/gsd-core/workflows/execute-phase/steps/per-plan-worktree-gate.md +8 -2
  244. package/gsd-core/workflows/execute-phase/steps/regression-gate-run.md +2 -0
  245. package/gsd-core/workflows/execute-phase/steps/sequential-root-pin.md +35 -0
  246. package/gsd-core/workflows/execute-phase/steps/tdd-applicability-resolution.md +25 -0
  247. package/gsd-core/workflows/execute-phase/steps/worktree-recovery-policy.md +2 -0
  248. package/gsd-core/workflows/execute-phase.md +78 -159
  249. package/gsd-core/workflows/execute-plan.md +28 -15
  250. package/gsd-core/workflows/explore.md +2 -0
  251. package/gsd-core/workflows/extract-learnings.md +2 -0
  252. package/gsd-core/workflows/fast.md +6 -0
  253. package/gsd-core/workflows/forensics.md +2 -0
  254. package/gsd-core/workflows/graduation.md +1 -1
  255. package/gsd-core/workflows/health.md +1 -1
  256. package/gsd-core/workflows/help/modes/brief.md +2 -0
  257. package/gsd-core/workflows/help/modes/default.md +2 -0
  258. package/gsd-core/workflows/help/modes/full.compact.md +398 -0
  259. package/gsd-core/workflows/help/modes/full.md +12 -0
  260. package/gsd-core/workflows/help/modes/topic.md +2 -0
  261. package/gsd-core/workflows/help.md +3 -1
  262. package/gsd-core/workflows/import.md +3 -3
  263. package/gsd-core/workflows/inbox.md +1 -1
  264. package/gsd-core/workflows/ingest-docs.md +1 -1
  265. package/gsd-core/workflows/insert-phase.md +2 -0
  266. package/gsd-core/workflows/list-phase-assumptions.md +2 -0
  267. package/gsd-core/workflows/list-seeds.md +2 -0
  268. package/gsd-core/workflows/list-workspaces.md +2 -0
  269. package/gsd-core/workflows/manager.md +3 -3
  270. package/gsd-core/workflows/map-codebase.md +52 -3
  271. package/gsd-core/workflows/milestone-summary.md +2 -0
  272. package/gsd-core/workflows/mvp-phase.md +1 -1
  273. package/gsd-core/workflows/new-milestone.md +55 -13
  274. package/gsd-core/workflows/new-project/detail/elaboration.md +216 -0
  275. package/gsd-core/workflows/new-project.md +37 -205
  276. package/gsd-core/workflows/new-workspace.md +1 -1
  277. package/gsd-core/workflows/next.md +2 -0
  278. package/gsd-core/workflows/node-repair.md +2 -0
  279. package/gsd-core/workflows/note.md +2 -0
  280. package/gsd-core/workflows/onboard.md +1 -1
  281. package/gsd-core/workflows/pause-work.md +19 -4
  282. package/gsd-core/workflows/plan-phase/detail/elaboration.md +209 -0
  283. package/gsd-core/workflows/plan-phase/steps/chunked-planning-mode.md +100 -18
  284. package/gsd-core/workflows/plan-phase/steps/prd-express-path.md +2 -0
  285. package/gsd-core/workflows/plan-phase/steps/stall-detection-helpers.md +9 -0
  286. package/gsd-core/workflows/plan-phase.md +144 -185
  287. package/gsd-core/workflows/plan-review-convergence.md +102 -10
  288. package/gsd-core/workflows/plant-seed.md +1 -1
  289. package/gsd-core/workflows/pr-branch.md +30 -10
  290. package/gsd-core/workflows/profile-user.md +1 -1
  291. package/gsd-core/workflows/progress/steps/forensic-audit.md +1 -1
  292. package/gsd-core/workflows/progress.md +25 -3
  293. package/gsd-core/workflows/quick/steps/plan-checker-loop.md +37 -2
  294. package/gsd-core/workflows/quick/steps/research-phase.md +3 -3
  295. package/gsd-core/workflows/quick-batch/steps/batch-init.md +55 -0
  296. package/gsd-core/workflows/quick-batch/steps/completion.md +65 -0
  297. package/gsd-core/workflows/quick-batch/steps/merge-wave.md +100 -0
  298. package/gsd-core/workflows/quick-batch/steps/plan-checker-loop.md +147 -0
  299. package/gsd-core/workflows/quick-batch/steps/planner-wave.md +158 -0
  300. package/gsd-core/workflows/quick-batch/steps/research-phase.md +95 -0
  301. package/gsd-core/workflows/quick-batch/steps/resume-mode.md +49 -0
  302. package/gsd-core/workflows/quick-batch/steps/verification-wave.md +73 -0
  303. package/gsd-core/workflows/quick-batch/steps/worktree-dispatch.md +169 -0
  304. package/gsd-core/workflows/quick-batch.md +203 -0
  305. package/gsd-core/workflows/quick.md +21 -4
  306. package/gsd-core/workflows/reapply-patches.md +79 -3
  307. package/gsd-core/workflows/remove-phase.md +2 -0
  308. package/gsd-core/workflows/remove-workspace.md +1 -1
  309. package/gsd-core/workflows/resume-project.md +6 -2
  310. package/gsd-core/workflows/review.md +215 -10
  311. package/gsd-core/workflows/scan.md +2 -0
  312. package/gsd-core/workflows/section-manifest.json +12 -0
  313. package/gsd-core/workflows/secure-phase.md +1 -1
  314. package/gsd-core/workflows/session-report.md +2 -0
  315. package/gsd-core/workflows/settings-advanced.md +2 -0
  316. package/gsd-core/workflows/settings-integrations.md +9 -8
  317. package/gsd-core/workflows/settings.md +19 -6
  318. package/gsd-core/workflows/ship.md +10 -10
  319. package/gsd-core/workflows/sketch-wrap-up.md +2 -0
  320. package/gsd-core/workflows/sketch.md +1 -1
  321. package/gsd-core/workflows/smart-entry.md +1 -1
  322. package/gsd-core/workflows/spec-phase.md +24 -19
  323. package/gsd-core/workflows/spike-wrap-up.md +2 -0
  324. package/gsd-core/workflows/spike.md +1 -1
  325. package/gsd-core/workflows/stats.md +2 -0
  326. package/gsd-core/workflows/sync-skills.md +12 -4
  327. package/gsd-core/workflows/thread.md +2 -0
  328. package/gsd-core/workflows/transition.md +2 -0
  329. package/gsd-core/workflows/ui-phase.md +26 -5
  330. package/gsd-core/workflows/ui-review.md +1 -1
  331. package/gsd-core/workflows/ultraplan-phase.md +2 -0
  332. package/gsd-core/workflows/undo.md +1 -1
  333. package/gsd-core/workflows/update.md +48 -43
  334. package/gsd-core/workflows/validate-phase.md +1 -1
  335. package/gsd-core/workflows/verify-work/detail/elaboration.md +230 -0
  336. package/gsd-core/workflows/verify-work.md +68 -182
  337. package/hooks/dist/gsd-agent-isolation-guard.js +42 -16
  338. package/hooks/dist/gsd-check-update-worker.js +19 -2
  339. package/hooks/dist/gsd-context-monitor.js +371 -27
  340. package/hooks/dist/gsd-cursor-subagent-start.js +34 -14
  341. package/hooks/dist/gsd-node-runner.sh +1 -0
  342. package/hooks/dist/gsd-prompt-guard.js +30 -5
  343. package/hooks/dist/gsd-read-guard.js +2 -0
  344. package/hooks/dist/gsd-read-injection-scanner.js +5 -5
  345. package/hooks/dist/gsd-secret-read-guard.js +1105 -0
  346. package/hooks/dist/gsd-statusline.js +18 -10
  347. package/hooks/dist/gsd-validate-commit.sh +474 -7
  348. package/hooks/dist/gsd-workflow-guard.js +2 -1
  349. package/hooks/dist/gsd-worktree-path-guard.js +25 -14
  350. package/hooks/dist/gsd-write-guard.js +46 -1
  351. package/hooks/dist/lib/dispatch-identity.js +187 -0
  352. package/hooks/dist/lib/filename-classification.js +64 -0
  353. package/hooks/dist/lib/git-cmd.js +210 -1
  354. package/hooks/dist/lib/injection-patterns.js +36 -6
  355. package/hooks/dist/lib/isolation-deny-reason.js +53 -1
  356. package/hooks/dist/lib/isolation-sentinel.js +58 -19
  357. package/hooks/dist/managed-hooks-registry.cjs +1 -0
  358. package/hooks/gsd-agent-isolation-guard.js +42 -16
  359. package/hooks/gsd-check-update-worker.js +19 -2
  360. package/hooks/gsd-context-monitor.js +371 -27
  361. package/hooks/gsd-cursor-subagent-start.js +34 -14
  362. package/hooks/gsd-node-runner.sh +1 -0
  363. package/hooks/gsd-prompt-guard.js +30 -5
  364. package/hooks/gsd-read-guard.js +2 -0
  365. package/hooks/gsd-read-injection-scanner.js +5 -5
  366. package/hooks/gsd-secret-read-guard.js +1105 -0
  367. package/hooks/gsd-statusline.js +18 -10
  368. package/hooks/gsd-validate-commit.sh +474 -7
  369. package/hooks/gsd-workflow-guard.js +2 -1
  370. package/hooks/gsd-worktree-path-guard.js +25 -14
  371. package/hooks/gsd-write-guard.js +46 -1
  372. package/hooks/hooks.json +6 -0
  373. package/hooks/lib/dispatch-identity.js +187 -0
  374. package/hooks/lib/filename-classification.js +64 -0
  375. package/hooks/lib/git-cmd.js +210 -1
  376. package/hooks/lib/injection-patterns.js +36 -6
  377. package/hooks/lib/isolation-deny-reason.js +53 -1
  378. package/hooks/lib/isolation-sentinel.js +58 -19
  379. package/hooks/managed-hooks-registry.cjs +1 -0
  380. package/package.json +13 -9
  381. package/scripts/benchmark-compact-content-variants.cjs +298 -0
  382. package/scripts/benchmark-compact-content.cjs +368 -0
  383. package/scripts/build-hooks.js +11 -4
  384. package/scripts/check-contract-drift.cjs +4 -1
  385. package/scripts/check-env.cjs +36 -8
  386. package/scripts/check-glossary-refs.cjs +25 -21
  387. package/scripts/ci-next-health.cjs +271 -0
  388. package/scripts/ci-prepare-test-scope.cjs +7 -7
  389. package/scripts/ci-test-scope.cjs +133 -20
  390. package/scripts/ci-timeout-report.cjs +1 -1
  391. package/scripts/diff-touches-shipped-paths.cjs +1 -1
  392. package/scripts/docs-guard-registry.cjs +17 -2
  393. package/scripts/gen-adr-index.cjs +8 -2
  394. package/scripts/gen-inventory-manifest.cjs +12 -0
  395. package/scripts/gen-loop-host-contract.cjs +67 -15
  396. package/scripts/gen-platform-conformance-tier.cjs +557 -0
  397. package/scripts/lib/drift-scan.cjs +1 -1
  398. package/scripts/lib/macos-conformance-tier.generated.cjs +210 -0
  399. package/scripts/lib/npm-version-check-diagnosis.cjs +59 -0
  400. package/scripts/lib/platform-conformance-tier.generated.cjs +276 -0
  401. package/scripts/lib/shellcheck-fetch.cjs +247 -0
  402. package/scripts/lib/suite-detection.cjs +32 -0
  403. package/scripts/lint-allow-test-rule-refs.allowlist.json +0 -6
  404. package/scripts/lint-allow-test-rule-refs.effective-ceiling.json +1 -1
  405. package/scripts/lint-allow-test-rule-refs.unverified-ceiling.json +1 -1
  406. package/scripts/lint-allowed-tools-parity.cjs +221 -0
  407. package/scripts/lint-docs-guard-registration.exempt-baseline.cjs +24 -2
  408. package/scripts/lint-phase-enumeration-drift.cjs +24 -6
  409. package/scripts/lint-phase-id-drift.cjs +465 -15
  410. package/scripts/lint-portable-grep.cjs +176 -0
  411. package/scripts/lint-response-language-coverage.cjs +530 -0
  412. package/scripts/lint-source-test-name-collision.cjs +1 -1
  413. package/scripts/lint-test-file-count.allowlist.json +4 -1
  414. package/scripts/lint-vendored-deps.cjs +128 -17
  415. package/scripts/lint-workflow-shellcheck-baseline.json +1112 -0
  416. package/scripts/lint-workflow-shellcheck.cjs +614 -0
  417. package/scripts/npm-audit-baseline.cjs +376 -0
  418. package/scripts/prompt-injection-scan.sh +22 -0
  419. package/scripts/require-issue-link-policy.cjs +16 -1
  420. package/scripts/workflow-size.cjs +139 -0
  421. package/skills/gsd-cleanup/SKILL.md +1 -0
  422. package/skills/gsd-code-review/SKILL.md +2 -1
  423. package/skills/gsd-complete-milestone/SKILL.md +1 -0
  424. package/skills/gsd-config/SKILL.md +1 -0
  425. package/skills/gsd-debug/SKILL.md +1 -0
  426. package/skills/gsd-execute-phase/SKILL.md +1 -1
  427. package/skills/gsd-graphify/SKILL.md +1 -0
  428. package/skills/gsd-health/SKILL.md +1 -0
  429. package/skills/gsd-mempalace-capture/SKILL.md +1 -0
  430. package/skills/gsd-mempalace-recall/SKILL.md +1 -0
  431. package/skills/gsd-new-milestone/SKILL.md +1 -0
  432. package/skills/gsd-new-project/SKILL.md +1 -0
  433. package/skills/gsd-next/SKILL.md +1 -0
  434. package/skills/gsd-ns-workflow/SKILL.md +1 -0
  435. package/skills/gsd-pause-work/SKILL.md +1 -0
  436. package/skills/gsd-phase/SKILL.md +2 -1
  437. package/skills/gsd-pr-branch/SKILL.md +1 -0
  438. package/skills/gsd-quick-batch/SKILL.md +105 -0
  439. package/skills/gsd-resume-work/SKILL.md +1 -0
  440. package/skills/gsd-review-backlog/SKILL.md +1 -0
  441. package/skills/gsd-settings/SKILL.md +2 -1
  442. package/skills/gsd-stats/SKILL.md +1 -0
  443. package/skills/gsd-surface/SKILL.md +18 -8
  444. package/skills/gsd-thread/SKILL.md +1 -0
  445. package/skills/gsd-workspace/SKILL.md +1 -0
  446. package/skills/gsd-workstreams/SKILL.md +1 -0
  447. package/vscode/package.json +1 -1
  448. package/gsd-core/templates/claude-md.md +0 -145
  449. package/gsd-core/templates/codebase/concerns.md +0 -310
  450. package/gsd-core/templates/codebase/conventions.md +0 -307
  451. package/gsd-core/templates/codebase/integrations.md +0 -280
  452. package/gsd-core/templates/codebase/structure.md +0 -285
  453. package/gsd-core/templates/codebase/testing.md +0 -480
  454. package/gsd-core/templates/debug-subagent-prompt.md +0 -91
  455. package/gsd-core/templates/discovery.md +0 -146
@@ -0,0 +1,74 @@
1
+ "use strict";
2
+ /**
3
+ * File-overlap partitioner — shared greedy first-fit stage assignment (#3674).
4
+ *
5
+ * Extracted from `claude-orchestration.cts`'s `partitionStages` (#1143), which
6
+ * emits sequential Workflow `parallel()` stage barriers so that no two plans
7
+ * sharing a `files_modified` entry ever cohabit a stage. This module is the
8
+ * SAME algorithm, generalized: it depends on no `Plan`/`Wave` interface from
9
+ * `claude-orchestration.cts` (or any other caller-specific shape), so a future
10
+ * consumer (quick-batch, #3675 / ADR-1239 "Quick-batch binding") can partition
11
+ * its own planned-path items without pulling in orchestration internals.
12
+ *
13
+ * This is a pure, behavior-preserving extraction (#3674's acceptance bar is
14
+ * byte-identical output for `claude-orchestration.cts`'s existing callers —
15
+ * not an improvement). Explicitly OUT of scope, per the #3674 design lock:
16
+ * - dependency-DAG ordering — this module only ever sees a flat item list
17
+ * and file-overlap; a caller resolves dependency order before calling in
18
+ * (same contract `partitionStages` already had);
19
+ * - path normalization — file entries are compared by exact string equality
20
+ * only. `Foo.ts` vs `foo.ts`, or `src/a.ts` vs `src\a.ts`, are treated as
21
+ * DISTINCT files. This is deliberate, not a gap to "fix" during extraction;
22
+ * - filesystem access — this module never reads a path off disk.
23
+ *
24
+ * Zero external dependencies. Pure function. Never throws on well-typed input.
25
+ */
26
+ /**
27
+ * Partition `items` into a near-minimal number of sequential stages (via
28
+ * greedy first-fit — not guaranteed optimal for arbitrary overlap graphs, but
29
+ * correct: no two items sharing a file ever cohabit a stage) such that no two
30
+ * items in the same stage share a file. Each item goes into the earliest
31
+ * stage where it does not overlap any item already there, in input order.
32
+ *
33
+ * An item with an EMPTY `files` array declares no files; it overlaps nothing
34
+ * and coalesces into stage 0 (mirrors `partitionStages`' original behavior —
35
+ * this module cannot guard against undeclared concurrent writes; a caller
36
+ * must declare `files` accurately).
37
+ *
38
+ * File comparison is EXACT STRING EQUALITY — no path normalization, no
39
+ * case-folding, no separator canonicalization. Duplicate `id`s in the input
40
+ * are NOT deduplicated; each item is placed independently, in input order.
41
+ *
42
+ * Deterministic: identical input (including input order) always yields an
43
+ * identical partition.
44
+ */
45
+ function partitionByFileOverlap(items) {
46
+ const stages = [];
47
+ for (const item of items) {
48
+ const fileSet = new Set(item.files);
49
+ let placed = false;
50
+ for (const stage of stages) {
51
+ let overlap = false;
52
+ for (const f of fileSet) {
53
+ if (stage.files.has(f)) {
54
+ overlap = true;
55
+ break;
56
+ }
57
+ }
58
+ if (!overlap) {
59
+ stage.items.push(item);
60
+ for (const f of fileSet)
61
+ stage.files.add(f);
62
+ placed = true;
63
+ break;
64
+ }
65
+ }
66
+ if (!placed) {
67
+ stages.push({ items: [item], files: new Set(fileSet) });
68
+ }
69
+ }
70
+ return stages.map((s) => s.items.map((i) => i.id));
71
+ }
72
+ module.exports = {
73
+ partitionByFileOverlap,
74
+ };
@@ -652,46 +652,137 @@ function countTopLevelKeyShapedLines(region) {
652
652
  * key its deduplication. Optional because this function has 50-odd call sites and several
653
653
  * hold only an in-memory string; those dedup on a content digest instead.
654
654
  */
655
- function extractFrontmatter(content, sourcePath) {
655
+ /**
656
+ * The frontmatter REGION of a document: the text between the opening `---`
657
+ * fence at byte 0 and its closing fence.
658
+ *
659
+ * One fence parser, not two (#3850 review round 2, B1). `extractFrontmatter`
660
+ * and `frontmatterListEntries` need the identical answer to "where does the
661
+ * frontmatter start and stop" — same BOM tolerance (#2977), same byte-0-only
662
+ * fence rule, same CR handling before the closing fence — and differ only in
663
+ * what they do with the region text afterwards. A second copy of this logic is
664
+ * the `DEFECT.GENERATIVE-FIX` shape: it silently stops agreeing the first time
665
+ * either is taught something.
666
+ *
667
+ * `terminated: false` is the fence-opened-but-never-closed case. It is not an
668
+ * error here because the two callers disagree about it: `extractFrontmatter`
669
+ * runs the #1882 truncation probe over the region and may warn, while
670
+ * `frontmatterListEntries` has no array to return and gives up. So the shape
671
+ * is reported and the decision is left to them.
672
+ *
673
+ * `content` is returned alongside because it is the BOM-STRIPPED text, and a
674
+ * caller reporting on the document (the truncation diagnostic) must describe
675
+ * the same bytes the offsets were computed against.
676
+ */
677
+ function frontmatterRegion(content) {
656
678
  // #2977: tolerate a single leading UTF-8 BOM (U+FEFF), which Windows tooling
657
- // (PowerShell `>`/`Out-File` on PS 5.1, several editors) writes by default. Without this
658
- // strip, the byte-0 `startsWith('---')` fence check below fails on the BOM and the whole
659
- // parse collapses to {} — every frontmatter field silently disappears, and the engine
660
- // proceeds as though the file had no frontmatter at all. The BOM is a single codepoint;
661
- // stripping it here restores byte-0 alignment so the rest of the function is unchanged.
662
- // Scope: BOM only. Arbitrary non-BOM content before the fence (leading whitespace/blank
663
- // line/comment) is a separate product-intent decision (tolerate vs diagnose) left to a
664
- // future change — this fix does not broaden the byte-0 fence rule beyond the BOM.
665
- if (content.charCodeAt(0) === 0xFEFF) {
679
+ // (PowerShell `>`/`Out-File` on PS 5.1, several editors) writes by default.
680
+ // Without this strip, the byte-0 `startsWith('---')` fence check below fails
681
+ // on the BOM and the whole parse collapses — every frontmatter field silently
682
+ // disappears, and the engine proceeds as though the file had no frontmatter
683
+ // at all. The BOM is a single codepoint; stripping it here restores byte-0
684
+ // alignment. Scope: BOM only. Arbitrary non-BOM content before the fence
685
+ // (leading whitespace/blank line/comment) is a separate product-intent
686
+ // decision (tolerate vs diagnose) left to a future change.
687
+ if (content.charCodeAt(0) === 0xFEFF)
666
688
  content = content.slice(1);
667
- }
668
- // Match frontmatter only at byte 0 — a `---` block later in the document
669
- // body (YAML examples, horizontal rules) must never be treated as frontmatter.
689
+ // Match frontmatter only at byte 0 — a `---` block later in the document body
690
+ // (YAML examples, horizontal rules) must never be treated as frontmatter.
670
691
  const headerEnd = content.startsWith('---\r\n') ? 5 : content.startsWith('---\n') ? 4 : -1;
671
692
  if (headerEnd === -1)
672
- return {};
693
+ return null;
673
694
  const closingLineStart = content.indexOf('\n---', headerEnd);
674
695
  if (closingLineStart === -1) {
675
- const region = content.slice(headerEnd);
676
- const keyCount = countKeysBeforeTruncation(region);
677
- if (keyCount >= UNTERMINATED_KEY_THRESHOLD && isFrontmatterShaped(region)) {
696
+ return { region: content.slice(headerEnd), terminated: false, content };
697
+ }
698
+ const yamlEnd = content[closingLineStart - 1] === '\r' ? closingLineStart - 1 : closingLineStart;
699
+ return { region: content.slice(headerEnd, yamlEnd), terminated: true, content };
700
+ }
701
+ function extractFrontmatter(content, sourcePath) {
702
+ // Fence location (BOM strip, byte-0 rule, CR handling) lives in
703
+ // `frontmatterRegion` so this and `frontmatterListEntries` cannot drift
704
+ // apart on where the frontmatter is.
705
+ const found = frontmatterRegion(content);
706
+ if (!found)
707
+ return {};
708
+ if (!found.terminated) {
709
+ const keyCount = countKeysBeforeTruncation(found.region);
710
+ if (keyCount >= UNTERMINATED_KEY_THRESHOLD && isFrontmatterShaped(found.region)) {
678
711
  warnUnusableInput({
679
712
  reason: UNUSABLE_REASON.FRONTMATTER_UNTERMINATED,
680
713
  source: sourcePath,
681
- content,
714
+ content: found.content,
682
715
  });
683
716
  }
684
717
  return {};
685
718
  }
686
- const yamlEnd = content[closingLineStart - 1] === '\r' ? closingLineStart - 1 : closingLineStart;
687
- const region = content.slice(headerEnd, yamlEnd);
688
719
  try {
689
- return parseGuardedYamlRegion(region);
720
+ return parseGuardedYamlRegion(found.region);
690
721
  }
691
722
  catch {
692
723
  return unparseableResult();
693
724
  }
694
725
  }
726
+ /**
727
+ * The entries of a top-level frontmatter ARRAY key, VERBATIM — as the values
728
+ * YAML actually describes rather than the display-flattened strings
729
+ * `extractFrontmatter` returns (#3850).
730
+ *
731
+ * `extractFrontmatter` renders each object entry for HUMANS —
732
+ * `normalizeParsedValue` maps `{test, resolution}` to `"test: …, resolution: …"`
733
+ * via `flattenObjectListItem`. That is the right contract for a reader printing
734
+ * a list, and the wrong one for a reader that needs to branch on a specific
735
+ * field: `status`, `resolution` and `reason` are recoverable from that string
736
+ * only by re-parsing prose, which cannot distinguish a real `resolution:` field
737
+ * from the same text inside a quoted `truth:`.
738
+ *
739
+ * This returns the same entries BEFORE that display step, off the same
740
+ * `extractFrontmatter` parse path — same BOM strip (#2977), same byte-0 fence
741
+ * rule (shared via `frontmatterRegion`), same anchor/alias and sentinel guards,
742
+ * same ambiguous-colon repair. It is deliberately NOT a second parser: a
743
+ * hand-rolled fence regex or entry slicer re-loses whatever the real one
744
+ * learned, which is the `DEFECT.GENERATIVE-FIX` shape.
745
+ *
746
+ * EVERY element is returned, at its own index, whatever its type (#3850 review
747
+ * round 3, Blocker). An earlier revision filtered to objects, which compacted
748
+ * the array: a caller numbering entries by array position then numbered the
749
+ * SURVIVORS, so a list mixing object and non-object entries lost rows outright
750
+ * and mis-numbered the rest — the same silently-vanishing-row defect this whole
751
+ * issue exists to close, triggered by entry shape instead of file status.
752
+ * Deciding what a non-object entry MEANS is a caller's judgement (the two
753
+ * readers in `uat.cts` answer it differently and both are right for their own
754
+ * vocabulary); dropping it is nobody's.
755
+ *
756
+ * Returns `null` when the document has no frontmatter, the frontmatter is
757
+ * unterminated or unparseable, the key is absent, or the key is not an array —
758
+ * "nothing to iterate" cases the caller should not have to tell apart.
759
+ */
760
+ function frontmatterListEntries(content, key) {
761
+ const found = frontmatterRegion(content);
762
+ if (!found || !found.terminated)
763
+ return null;
764
+ let raw;
765
+ try {
766
+ refuseAnchorsAndAliases(found.region);
767
+ refuseIfSentinelPresent(found.region);
768
+ raw = restoreNullBytesDeep(loadWithAmbiguousColonRepair(escapeNullBytesForParse(found.region)));
769
+ }
770
+ catch {
771
+ // Same posture as `extractFrontmatter`: an unparseable region is "no
772
+ // frontmatter", never a throw into a caller that was only reading a field.
773
+ return null;
774
+ }
775
+ if (!raw || typeof raw !== 'object' || Array.isArray(raw))
776
+ return null;
777
+ const value = raw[key];
778
+ // `Array.isArray` narrows an `unknown` to `any[]`, and returning that
779
+ // unchecked is how `any` escapes a guarded parser into every caller. The
780
+ // element type genuinely IS unknown here — that is the point of this
781
+ // function — so say so.
782
+ if (!Array.isArray(value))
783
+ return null;
784
+ return value;
785
+ }
695
786
  /**
696
787
  * Escape a string for emission inside a YAML double-quoted scalar (#1779). ADR-3473 §8.1
697
788
  * (#3881): routed through the vendored js-yaml's `dump()` (forced double-quoted style) rather
@@ -816,6 +907,22 @@ function agentScalarNeedsDoubleQuoting(s) {
816
907
  return true;
817
908
  return false;
818
909
  }
910
+ /**
911
+ * #4053 — Quote a numeric-looking scalar that is not all-digit (`22.10`,
912
+ * `1.0`, `1e3`, `0x1F`) so a spec YAML reader keeps it a string: bare `22.10`
913
+ * reloads as the float 22.1 and collides with `22.1`, a different phase.
914
+ *
915
+ * All-digit strings stay bare as a deliberate, scoped trade-off — not a
916
+ * safety guarantee. A leading-zero value (`02`, `017`) also mis-parses under
917
+ * a spec reader (to 2, 17). It is left unquoted because zero-padded ids
918
+ * (`plan: 01`, `phase: 02`) are the pervasive GSD convention, quoting them
919
+ * all is the blanket quoting #4053 asked to avoid, and the loss is padding
920
+ * rather than identity: `02` and `2` normalize to the same phase; `22.1` and
921
+ * `22.10` do not.
922
+ */
923
+ function generalScalarNeedsNumericQuoting(s) {
924
+ return YAML_NUMERIC_RE.test(s) && !/^\d+$/.test(s);
925
+ }
819
926
  function reconstructFrontmatter(obj) {
820
927
  const lines = [];
821
928
  // #3257: read the full-line-comment channel (set by parseGuardedYamlRegion when comments
@@ -899,13 +1006,13 @@ function reconstructFrontmatter(obj) {
899
1006
  else {
900
1007
  // eslint-disable-next-line @typescript-eslint/no-base-to-string
901
1008
  const sv = String(subval);
902
- lines.push(` ${subkey}: ${sv.includes(':') || sv.includes('#') || scalarNeedsDoubleQuoting(sv) ? `"${escapeDoubleQuotedScalar(sv)}"` : sv}`);
1009
+ lines.push(` ${subkey}: ${sv.includes(':') || sv.includes('#') || scalarNeedsDoubleQuoting(sv) || generalScalarNeedsNumericQuoting(sv) ? `"${escapeDoubleQuotedScalar(sv)}"` : sv}`);
903
1010
  }
904
1011
  }
905
1012
  }
906
1013
  else {
907
1014
  const sv = String(value);
908
- if (sv.includes(':') || sv.includes('#') || sv.startsWith('[') || sv.startsWith('{') || scalarNeedsDoubleQuoting(sv)) {
1015
+ if (sv.includes(':') || sv.includes('#') || sv.startsWith('[') || sv.startsWith('{') || scalarNeedsDoubleQuoting(sv) || generalScalarNeedsNumericQuoting(sv)) {
909
1016
  lines.push(`${key}: "${escapeDoubleQuotedScalar(sv)}"`);
910
1017
  }
911
1018
  else {
@@ -1529,6 +1636,13 @@ module.exports = {
1529
1636
  stripFrontmatter,
1530
1637
  noOpObjectListSetError,
1531
1638
  parseMustHavesBlock,
1639
+ // #3850: an array key's entries as parsed OBJECTS, for a caller that must
1640
+ // branch on an entry's `status:`/`resolution:` rather than print it. Off the
1641
+ // same parse path as `extractFrontmatter`, minus only the display flattening.
1642
+ frontmatterListEntries,
1643
+ // #3850: the display rendering itself, so a caller deriving a name from those
1644
+ // objects produces the byte-identical string `extractFrontmatter` would have.
1645
+ flattenObjectListItem,
1532
1646
  FRONTMATTER_SCHEMAS,
1533
1647
  cmdFrontmatterGet,
1534
1648
  cmdFrontmatterSet,
@@ -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
  *
@@ -303,6 +306,7 @@ function runGapAnalysis(cwd, phaseDir, options = {}) {
303
306
  summary: 'workflow.post_planning_gaps disabled — skipping post-planning gap analysis',
304
307
  counts: { total: 0, covered: 0, uncovered: 0 },
305
308
  phase_dir_read_error: null,
309
+ phase_dir_scope: SCOPE.COMPLETE,
306
310
  };
307
311
  }
308
312
  const absPhaseDir = node_path_1.default.isAbsolute(phaseDir) ? phaseDir : node_path_1.default.join(cwd, phaseDir);
@@ -325,19 +329,20 @@ function runGapAnalysis(cwd, phaseDir, options = {}) {
325
329
  }
326
330
  // Read the phase directory once; reuse the listing for both context detection
327
331
  // and plan-file enumeration (avoids redundant readdirSync calls).
328
- let phaseDirFiles = [];
329
- // #3885 (ADR-3473 §8.5): the existsSync guard above already means a catch
330
- // here is NEVER "genuinely absent" (ENOENT) — this directory exists, so any
331
- // failure to list it is a real read error (EACCES/EIO/...) and must be
332
- // named, not folded into the same `[]` an absent directory produces.
333
- let phaseDirReadError = null;
334
- try {
335
- if (node_fs_1.default.existsSync(absPhaseDir))
336
- phaseDirFiles = node_fs_1.default.readdirSync(absPhaseDir);
337
- }
338
- catch (err) {
339
- phaseDirReadError = `Could not read phase directory ${formatDiagnosticToken(absPhaseDir)}: ${formatDiagnosticToken(err?.message ?? String(err))}`;
340
- }
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;
341
346
  // #3511-class: scope the raw listing to this phase dir before the
342
347
  // phase-numbered -CONTEXT.md predicate. `phaseDirFiles` itself stays raw —
343
348
  // it is also reused below only as a `.length > 0` guard ahead of
@@ -405,6 +410,7 @@ function runGapAnalysis(cwd, phaseDir, options = {}) {
405
410
  summary: coverageSummary + '; extracted 0 of N — possible format mismatch',
406
411
  counts: { total: rows.length, covered, uncovered },
407
412
  phase_dir_read_error: phaseDirReadError,
413
+ phase_dir_scope: phaseDirScope,
408
414
  };
409
415
  }
410
416
  return {
@@ -414,6 +420,7 @@ function runGapAnalysis(cwd, phaseDir, options = {}) {
414
420
  summary: 'extracted 0 of N — possible format mismatch',
415
421
  counts: { total: 0, covered: 0, uncovered: 0 },
416
422
  phase_dir_read_error: phaseDirReadError,
423
+ phase_dir_scope: phaseDirScope,
417
424
  };
418
425
  }
419
426
  // #1365: if no items at all, surface a clean no-check message.
@@ -431,6 +438,7 @@ function runGapAnalysis(cwd, phaseDir, options = {}) {
431
438
  summary: 'no requirements or decisions to check',
432
439
  counts: { total: 0, covered: 0, uncovered: 0 },
433
440
  phase_dir_read_error: phaseDirReadError,
441
+ phase_dir_scope: phaseDirScope,
434
442
  };
435
443
  }
436
444
  const rows = sortRows([
@@ -449,6 +457,7 @@ function runGapAnalysis(cwd, phaseDir, options = {}) {
449
457
  summary,
450
458
  counts: { total: rows.length, covered, uncovered },
451
459
  phase_dir_read_error: phaseDirReadError,
460
+ phase_dir_scope: phaseDirScope,
452
461
  };
453
462
  }
454
463
  function cmdGapAnalysis(cwd, args, raw) {
@@ -122,6 +122,7 @@ function readEffectiveGitConfig(cwd, deps) {
122
122
  config = {
123
123
  base_branch: _readGitKey(normalized, 'base_branch'),
124
124
  protected_branches: _readGitNested(normalized, 'protected_branches'),
125
+ allow_default_branch_commits: _readGitNested(normalized, 'allow_default_branch_commits'),
125
126
  };
126
127
  }
127
128
  }
@@ -161,7 +162,10 @@ function readEffectiveGitConfig(cwd, deps) {
161
162
  // Not a list at all — contributes no names, but is still a misconfiguration.
162
163
  rejectedProtectedBranches.push(renderRejected(rawProtectedBranches));
163
164
  }
164
- return { baseBranch, protectedBranches, rejectedProtectedBranches };
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 };
165
169
  }
166
170
  /**
167
171
  * Try `git symbolic-ref --short refs/remotes/origin/HEAD` (no network).
@@ -302,13 +306,17 @@ function resolveBaseBranch(cwd, deps) {
302
306
  function resolveProtectedBranchStatus(cwd, currentBranch, deps) {
303
307
  const effectiveConfig = readEffectiveGitConfig(cwd, deps);
304
308
  const { branch: baseBranch, verified } = resolveBaseBranchDiagnosticsWithConfig(cwd, effectiveConfig.baseBranch, deps);
305
- const protectedBranches = [...new Set([baseBranch, ...effectiveConfig.protectedBranches])];
309
+ const protectedBranches = [...new Set([
310
+ ...(effectiveConfig.allowDefaultBranchCommits ? [] : [baseBranch]),
311
+ ...effectiveConfig.protectedBranches,
312
+ ])];
306
313
  return {
307
314
  baseBranch,
308
315
  protectedBranches,
309
316
  rejectedProtectedBranches: effectiveConfig.rejectedProtectedBranches,
310
317
  isProtected: protectedBranches.includes(currentBranch),
311
318
  verified,
319
+ allowDefaultBranchCommits: effectiveConfig.allowDefaultBranchCommits,
312
320
  };
313
321
  }
314
322
  /**
@@ -307,8 +307,7 @@ function buildStateMd(phaseMap) {
307
307
  const currentPhaseNum = currentEntry ? zeroPad(currentEntry.phaseNum) : zeroPad(totalPhases);
308
308
  const currentSlug = currentEntry ? slugify(currentEntry.slice.title) : 'complete';
309
309
  const status = currentEntry ? 'Ready to plan' : 'All phases complete';
310
- const filled = Math.round(pct / 10);
311
- const bar = `[${'█'.repeat(filled)}${'░'.repeat(10 - filled)}]`;
310
+ const bar = `[${(0, phase_lifecycle_cjs_1.renderProgressBar)(pct, 10)}]`;
312
311
  const today = clock_cjs_1.realClock.localToday();
313
312
  return [
314
313
  '# Project State',
@@ -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,
@@ -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,14 +260,19 @@ 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
277
  // never-on-roadmap; it must not trigger W007. #3639: judged on the DIR
235
278
  // NAME via the dir-aware recognizer — the extracted token is