@opengsd/gsd-core 1.10.0 → 1.12.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 (544) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/agents/gsd-code-fixer.md +1 -1
  4. package/agents/gsd-debug-session-manager.md +12 -1
  5. package/agents/gsd-debugger.md +1 -1
  6. package/agents/gsd-doc-synthesizer.md +2 -4
  7. package/agents/gsd-dom-verifier.md +169 -0
  8. package/agents/gsd-eval-auditor.md +1 -1
  9. package/agents/gsd-executor.md +22 -14
  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 +5 -3
  13. package/agents/gsd-pattern-mapper.md +11 -0
  14. package/agents/gsd-phase-researcher.md +23 -2
  15. package/agents/gsd-plan-checker.md +50 -53
  16. package/agents/gsd-planner.md +50 -50
  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 +63 -4
  21. package/agents/gsd-ui-researcher.md +41 -3
  22. package/agents/gsd-user-profiler.md +3 -0
  23. package/agents/gsd-verifier.md +13 -4
  24. package/bin/install.js +1448 -1103
  25. package/commands/gsd/code-review.md +1 -1
  26. package/commands/gsd/discuss-phase.md +1 -1
  27. package/commands/gsd/execute-phase.md +1 -1
  28. package/commands/gsd/import.md +1 -1
  29. package/commands/gsd/map-codebase.md +1 -1
  30. package/commands/gsd/mempalace-capture.md +1 -1
  31. package/commands/gsd/mempalace-recall.md +1 -1
  32. package/commands/gsd/new-milestone.md +1 -1
  33. package/commands/gsd/quick.md +9 -5
  34. package/commands/gsd/review-backlog.md +2 -1
  35. package/commands/gsd/verify-work.md +1 -1
  36. package/gsd-core/bin/gsd-tools.cjs +1035 -138
  37. package/gsd-core/bin/lib/active-workstream-store.cjs +146 -22
  38. package/gsd-core/bin/lib/adr-parser.cjs +13 -7
  39. package/gsd-core/bin/lib/agent-install-check.cjs +392 -32
  40. package/gsd-core/bin/lib/api-coverage.cjs +33 -14
  41. package/gsd-core/bin/lib/artifacts.cjs +5 -0
  42. package/gsd-core/bin/lib/assumption-delta.cjs +32 -15
  43. package/gsd-core/bin/lib/audit-command-router.cjs +9 -2
  44. package/gsd-core/bin/lib/audit.cjs +1026 -268
  45. package/gsd-core/bin/lib/broken-windows.cjs +306 -28
  46. package/gsd-core/bin/lib/capability-consent.cjs +149 -15
  47. package/gsd-core/bin/lib/capability-lifecycle.cjs +45 -0
  48. package/gsd-core/bin/lib/capability-lock.cjs +10 -4
  49. package/gsd-core/bin/lib/capability-registry.cjs +845 -130
  50. package/gsd-core/bin/lib/capability-source.cjs +92 -0
  51. package/gsd-core/bin/lib/capability-state.cjs +18 -3
  52. package/gsd-core/bin/lib/capability-trust.cjs +444 -25
  53. package/gsd-core/bin/lib/capability-validator.cjs +700 -40
  54. package/gsd-core/bin/lib/capability-writer.cjs +3 -2
  55. package/gsd-core/bin/lib/check-command-router.cjs +216 -42
  56. package/gsd-core/bin/lib/claude-orchestration.cjs +56 -3
  57. package/gsd-core/bin/lib/cli-exit.cjs +496 -10
  58. package/gsd-core/bin/lib/code-review-depth.cjs +288 -0
  59. package/gsd-core/bin/lib/codex-agent-toml.cjs +735 -0
  60. package/gsd-core/bin/lib/command-aliases.cjs +22 -0
  61. package/gsd-core/bin/lib/command-arg-projection.cjs +144 -14
  62. package/gsd-core/bin/lib/command-roster.cjs +44 -1
  63. package/gsd-core/bin/lib/command-routing-hub.cjs +31 -2
  64. package/gsd-core/bin/lib/commands.cjs +1172 -108
  65. package/gsd-core/bin/lib/commonjs-marker.cjs +12 -6
  66. package/gsd-core/bin/lib/complexity-trigger.cjs +1192 -0
  67. package/gsd-core/bin/lib/config-loader.cjs +187 -23
  68. package/gsd-core/bin/lib/config.cjs +102 -3
  69. package/gsd-core/bin/lib/configuration.cjs +129 -37
  70. package/gsd-core/bin/lib/core-utils.cjs +208 -33
  71. package/gsd-core/bin/lib/decisions.cjs +23 -0
  72. package/gsd-core/bin/lib/edge-probe.cjs +9 -1
  73. package/gsd-core/bin/lib/estimate-cli.cjs +55 -11
  74. package/gsd-core/bin/lib/exit-code-registry.cjs +98 -0
  75. package/gsd-core/bin/lib/fallow-runner.cjs +20 -44
  76. package/gsd-core/bin/lib/frontmatter.cjs +899 -229
  77. package/gsd-core/bin/lib/gap-checker.cjs +95 -10
  78. package/gsd-core/bin/lib/git-base-branch.cjs +276 -39
  79. package/gsd-core/bin/lib/gsd2-import.cjs +10 -1
  80. package/gsd-core/bin/lib/health-diagnostic-rules/agent-install.cjs +101 -0
  81. package/gsd-core/bin/lib/health-diagnostic-rules/config-validation.cjs +348 -0
  82. package/gsd-core/bin/lib/health-diagnostic-rules/consistency.cjs +149 -0
  83. package/gsd-core/bin/lib/health-diagnostic-rules/install-surface-shadowing.cjs +98 -0
  84. package/gsd-core/bin/lib/health-diagnostic-rules/milestone-archive-hygiene.cjs +100 -0
  85. package/gsd-core/bin/lib/health-diagnostic-rules/phase-structure.cjs +222 -0
  86. package/gsd-core/bin/lib/health-diagnostic-rules/roadmap-disk-consistency.cjs +268 -0
  87. package/gsd-core/bin/lib/health-diagnostic-rules/root-existence.cjs +161 -0
  88. package/gsd-core/bin/lib/health-diagnostic-rules/state-consistency.cjs +303 -0
  89. package/gsd-core/bin/lib/health-diagnostic-rules/worktree-health.cjs +187 -0
  90. package/gsd-core/bin/lib/health-diagnostic-types.cjs +68 -0
  91. package/gsd-core/bin/lib/health-diagnostic.cjs +451 -0
  92. package/gsd-core/bin/lib/host-integration.cjs +39 -6
  93. package/gsd-core/bin/lib/host-runtime-detection.cjs +134 -0
  94. package/gsd-core/bin/lib/init-command-router.cjs +118 -21
  95. package/gsd-core/bin/lib/init.cjs +439 -168
  96. package/gsd-core/bin/lib/install-effort-resolver.cjs +73 -30
  97. package/gsd-core/bin/lib/install-engine.cjs +811 -259
  98. package/gsd-core/bin/lib/install-fs-adapter.cjs +262 -0
  99. package/gsd-core/bin/lib/install-model-override-resolver.cjs +235 -0
  100. package/gsd-core/bin/lib/install-profiles.cjs +212 -61
  101. package/gsd-core/bin/lib/install-scope.cjs +270 -0
  102. package/gsd-core/bin/lib/install-shadow-report.cjs +385 -0
  103. package/gsd-core/bin/lib/installed-surface-resolver.cjs +381 -0
  104. package/gsd-core/bin/lib/installer-migration-report.cjs +3 -0
  105. package/gsd-core/bin/lib/installer-migrations/010-antigravity-retire-confighome-artifacts.cjs +169 -0
  106. package/gsd-core/bin/lib/installer-migrations.cjs +148 -38
  107. package/gsd-core/bin/lib/intel.cjs +101 -26
  108. package/gsd-core/bin/lib/io.cjs +170 -15
  109. package/gsd-core/bin/lib/learnings.cjs +85 -14
  110. package/gsd-core/bin/lib/legacy-cleanup.cjs +8 -2
  111. package/gsd-core/bin/lib/markdown-sectionizer.cjs +2 -1
  112. package/gsd-core/bin/lib/markdown-table.cjs +183 -22
  113. package/gsd-core/bin/lib/milestone-lock.cjs +248 -0
  114. package/gsd-core/bin/lib/milestone.cjs +842 -73
  115. package/gsd-core/bin/lib/model-catalog.cjs +232 -16
  116. package/gsd-core/bin/lib/model-resolver.cjs +193 -68
  117. package/gsd-core/bin/lib/normalize-test-command.cjs +1 -1
  118. package/gsd-core/bin/lib/onboard-projection.cjs +5 -1
  119. package/gsd-core/bin/lib/pattern.cjs +122 -0
  120. package/gsd-core/bin/lib/phase-estimation.cjs +18 -9
  121. package/gsd-core/bin/lib/phase-id.cjs +514 -40
  122. package/gsd-core/bin/lib/phase-lifecycle.cjs +52 -19
  123. package/gsd-core/bin/lib/phase-locator.cjs +262 -34
  124. package/gsd-core/bin/lib/phase.cjs +1038 -214
  125. package/gsd-core/bin/lib/plan-dependency-graph.cjs +72 -1
  126. package/gsd-core/bin/lib/plan-document.cjs +263 -0
  127. package/gsd-core/bin/lib/plan-drift-guard.cjs +120 -0
  128. package/gsd-core/bin/lib/plan-scan.cjs +98 -3
  129. package/gsd-core/bin/lib/planning-command-router.cjs +61 -0
  130. package/gsd-core/bin/lib/planning-inspect.cjs +1168 -0
  131. package/gsd-core/bin/lib/planning-scope.cjs +31 -0
  132. package/gsd-core/bin/lib/planning-snapshot.cjs +894 -0
  133. package/gsd-core/bin/lib/planning-workspace.cjs +112 -6
  134. package/gsd-core/bin/lib/probe-core.cjs +5 -2
  135. package/gsd-core/bin/lib/profile-output.cjs +1 -1
  136. package/gsd-core/bin/lib/profile-pipeline-command-router.cjs +50 -7
  137. package/gsd-core/bin/lib/profile-pipeline.cjs +6 -3
  138. package/gsd-core/bin/lib/real-home-guard.cjs +419 -0
  139. package/gsd-core/bin/lib/refactor-trigger-command-router.cjs +766 -0
  140. package/gsd-core/bin/lib/retired-artifact-cleanup.cjs +11 -6
  141. package/gsd-core/bin/lib/review-lane-descriptor.cjs +22 -13
  142. package/gsd-core/bin/lib/review-lane-invocation.cjs +30 -0
  143. package/gsd-core/bin/lib/review-lane-runner.cjs +421 -66
  144. package/gsd-core/bin/lib/review-reviewer-selection.cjs +13 -18
  145. package/gsd-core/bin/lib/roadmap-command-router.cjs +59 -11
  146. package/gsd-core/bin/lib/roadmap-parser.cjs +1006 -184
  147. package/gsd-core/bin/lib/roadmap-upgrade.cjs +37 -10
  148. package/gsd-core/bin/lib/roadmap.cjs +442 -96
  149. package/gsd-core/bin/lib/runtime-artifact-conversion.cjs +702 -52
  150. package/gsd-core/bin/lib/runtime-artifact-install-plan.cjs +14 -2
  151. package/gsd-core/bin/lib/runtime-artifact-layout.cjs +459 -55
  152. package/gsd-core/bin/lib/runtime-config-adapter-registry.cjs +3 -2
  153. package/gsd-core/bin/lib/runtime-homes.cjs +69 -3
  154. package/gsd-core/bin/lib/runtime-hooks-surface.cjs +402 -58
  155. package/gsd-core/bin/lib/runtime-identity.cjs +234 -0
  156. package/gsd-core/bin/lib/runtime-name-policy.cjs +3 -1
  157. package/gsd-core/bin/lib/runtime-slash.cjs +96 -8
  158. package/gsd-core/bin/lib/security.cjs +104 -5
  159. package/gsd-core/bin/lib/shell-command-projection.cjs +342 -7
  160. package/gsd-core/bin/lib/smart-entry.cjs +133 -23
  161. package/gsd-core/bin/lib/spec-section.cjs +12 -7
  162. package/gsd-core/bin/lib/state-command-router.cjs +52 -19
  163. package/gsd-core/bin/lib/state-contract.cjs +359 -0
  164. package/gsd-core/bin/lib/state-document.cjs +338 -8
  165. package/gsd-core/bin/lib/state-md-schema.cjs +221 -0
  166. package/gsd-core/bin/lib/state-transition.cjs +846 -176
  167. package/gsd-core/bin/lib/state.cjs +2589 -369
  168. package/gsd-core/bin/lib/surface.cjs +33 -11
  169. package/gsd-core/bin/lib/task-command-router.cjs +111 -1
  170. package/gsd-core/bin/lib/task-content-resolution.cjs +368 -0
  171. package/gsd-core/bin/lib/teams-status.cjs +4 -1
  172. package/gsd-core/bin/lib/text-lines.cjs +80 -0
  173. package/gsd-core/bin/lib/token-scanner.cjs +76 -0
  174. package/gsd-core/bin/lib/uat-predicate.cjs +67 -23
  175. package/gsd-core/bin/lib/uat.cjs +1761 -167
  176. package/gsd-core/bin/lib/ui-consideration-probe.cjs +9 -1
  177. package/gsd-core/bin/lib/ui-frontend-evidence.cjs +157 -0
  178. package/gsd-core/bin/lib/ui-safety-gate.cjs +51 -12
  179. package/gsd-core/bin/lib/unusable-input.cjs +37 -0
  180. package/gsd-core/bin/lib/update-context.cjs +8 -2
  181. package/gsd-core/bin/lib/user-artifact-staging.cjs +705 -0
  182. package/gsd-core/bin/lib/validate-command-router.cjs +2 -2
  183. package/gsd-core/bin/lib/validate.cjs +20 -6
  184. package/gsd-core/bin/lib/vendor/README.md +75 -0
  185. package/gsd-core/bin/lib/vendor/js-yaml.cjs +3014 -0
  186. package/gsd-core/bin/lib/vendor/re2js.cjs +6480 -0
  187. package/gsd-core/bin/lib/vendor/re2js.d.cts +938 -0
  188. package/gsd-core/bin/lib/verification-command-router.cjs +2 -1
  189. package/gsd-core/bin/lib/verification.cjs +272 -9
  190. package/gsd-core/bin/lib/verify-command-grounding.cjs +846 -0
  191. package/gsd-core/bin/lib/verify.cjs +453 -918
  192. package/gsd-core/bin/lib/workstream-inventory-builder.cjs +53 -32
  193. package/gsd-core/bin/lib/workstream-inventory.cjs +63 -10
  194. package/gsd-core/bin/lib/workstream-name-policy.cjs +25 -4
  195. package/gsd-core/bin/lib/workstream.cjs +2 -2
  196. package/gsd-core/bin/lib/worktree-base-ref.cjs +66 -12
  197. package/gsd-core/bin/lib/worktree-safety.cjs +341 -18
  198. package/gsd-core/bin/shared/config-defaults.manifest.json +8 -1
  199. package/gsd-core/bin/shared/config-schema.manifest.json +12 -1
  200. package/gsd-core/bin/shared/exit-codes.json +8 -0
  201. package/gsd-core/bin/shared/exit-codes.sh +20 -0
  202. package/gsd-core/bin/shared/model-catalog.json +8 -1
  203. package/gsd-core/references/agent-contracts.md +44 -26
  204. package/gsd-core/references/api-coverage.md +24 -2
  205. package/gsd-core/references/autonomous-smart-discuss.md +3 -3
  206. package/gsd-core/references/checkpoints.md +39 -21
  207. package/gsd-core/references/context-budget.md +1 -1
  208. package/gsd-core/references/decimal-phase-calculation.md +5 -5
  209. package/gsd-core/references/dispatch-isolation-gate.md +138 -0
  210. package/gsd-core/references/doc-conflict-engine.md +1 -1
  211. package/gsd-core/references/edge-probe.md +8 -0
  212. package/gsd-core/references/execute-mvp-tdd.md +4 -6
  213. package/gsd-core/references/execute-phase-between-wave-reset.md +15 -14
  214. package/gsd-core/references/execute-phase-context-guard.md +1 -1
  215. package/gsd-core/references/execute-phase-response-language.md +1 -1
  216. package/gsd-core/references/execute-phase-wave-guard.md +17 -11
  217. package/gsd-core/references/failing-direction.md +78 -0
  218. package/gsd-core/references/gate-prompts.md +1 -1
  219. package/gsd-core/references/git-integration.md +5 -5
  220. package/gsd-core/references/git-planning-commit.md +5 -4
  221. package/gsd-core/references/gsd-run-resolver.md +1 -1
  222. package/gsd-core/references/loop-hook-dispatch.md +61 -2
  223. package/gsd-core/references/model-profiles.md +12 -4
  224. package/gsd-core/references/mvp-concepts.md +9 -9
  225. package/gsd-core/references/nyquist-compliance.md +74 -0
  226. package/gsd-core/references/offer-next.md +3 -5
  227. package/gsd-core/references/phase-argument-parsing.md +3 -3
  228. package/gsd-core/references/planner-failing-direction.md +53 -0
  229. package/gsd-core/references/planner-guidance.md +3 -9
  230. package/gsd-core/references/planner-human-verify-mode.md +15 -1
  231. package/gsd-core/references/planner-preconditions.md +1 -1
  232. package/gsd-core/references/planner-reviews.md +1 -1
  233. package/gsd-core/references/planner-revision.md +1 -1
  234. package/gsd-core/references/planner-verify-command-grounding.md +17 -0
  235. package/gsd-core/references/planning-config.md +44 -13
  236. package/gsd-core/references/reviewer-instances.md +31 -0
  237. package/gsd-core/references/revision-loop.md +1 -1
  238. package/gsd-core/references/runtime-aware-dispatch.md +1 -1
  239. package/gsd-core/references/specless-probe-fallback.md +1 -1
  240. package/gsd-core/references/tdd.md +1 -3
  241. package/gsd-core/references/ui-brand.md +65 -21
  242. package/gsd-core/references/ui-consideration-probe.md +1 -1
  243. package/gsd-core/references/universal-anti-patterns.md +5 -5
  244. package/gsd-core/references/verifier-phase-gates.md +192 -0
  245. package/gsd-core/references/verify-command-path-resolvability.md +42 -0
  246. package/gsd-core/references/verify-mvp-mode.md +2 -2
  247. package/gsd-core/references/workstream-flag.md +33 -17
  248. package/gsd-core/templates/README.md +1 -1
  249. package/gsd-core/templates/SECURITY.md +3 -3
  250. package/gsd-core/templates/UI-SPEC.md +25 -3
  251. package/gsd-core/templates/VALIDATION.md +3 -3
  252. package/gsd-core/templates/discussion-log.md +1 -1
  253. package/gsd-core/templates/phase-prompt.md +5 -4
  254. package/gsd-core/templates/state.md +11 -4
  255. package/gsd-core/templates/verification-report.md +9 -1
  256. package/gsd-core/workflows/_runtime-launcher.snippet.sh +1 -1
  257. package/gsd-core/workflows/add-backlog.md +1 -1
  258. package/gsd-core/workflows/add-phase.md +3 -3
  259. package/gsd-core/workflows/add-tests.md +3 -8
  260. package/gsd-core/workflows/add-todo.md +1 -1
  261. package/gsd-core/workflows/ai-integration-phase.md +13 -20
  262. package/gsd-core/workflows/audit-fix.md +12 -3
  263. package/gsd-core/workflows/audit-milestone.md +9 -9
  264. package/gsd-core/workflows/audit-uat.md +17 -2
  265. package/gsd-core/workflows/autonomous/steps/converge-fail-fast.md +2 -2
  266. package/gsd-core/workflows/autonomous.md +11 -27
  267. package/gsd-core/workflows/check-todos.md +1 -1
  268. package/gsd-core/workflows/cleanup.md +64 -5
  269. package/gsd-core/workflows/code-review/steps/structural-pre-pass.md +14 -4
  270. package/gsd-core/workflows/code-review-fix.md +38 -11
  271. package/gsd-core/workflows/code-review.md +159 -52
  272. package/gsd-core/workflows/complete-milestone.md +151 -23
  273. package/gsd-core/workflows/debug.md +12 -8
  274. package/gsd-core/workflows/diagnose-issues.md +47 -15
  275. package/gsd-core/workflows/discuss-phase/modes/advisor.md +1 -1
  276. package/gsd-core/workflows/discuss-phase/modes/chain.md +5 -8
  277. package/gsd-core/workflows/discuss-phase/modes/default.md +1 -1
  278. package/gsd-core/workflows/discuss-phase/modes/text.md +1 -1
  279. package/gsd-core/workflows/discuss-phase-assumptions/steps/auto-advance-dispatch.md +1 -3
  280. package/gsd-core/workflows/discuss-phase-assumptions.md +4 -3
  281. package/gsd-core/workflows/discuss-phase.md +1 -1
  282. package/gsd-core/workflows/do.md +3 -6
  283. package/gsd-core/workflows/docs-update.md +5 -4
  284. package/gsd-core/workflows/edit-phase.md +27 -2
  285. package/gsd-core/workflows/eval-review.md +7 -14
  286. package/gsd-core/workflows/execute-phase/steps/codebase-drift-gate.md +1 -1
  287. package/gsd-core/workflows/execute-phase/steps/executor-isolation-dispatch.md +142 -15
  288. package/gsd-core/workflows/execute-phase/steps/gap-closure-artifacts.md +1 -1
  289. package/gsd-core/workflows/execute-phase/steps/partial-wave.md +1 -1
  290. package/gsd-core/workflows/execute-phase/steps/per-plan-executor-routing.md +77 -0
  291. package/gsd-core/workflows/execute-phase/steps/per-plan-worktree-gate.md +24 -4
  292. package/gsd-core/workflows/execute-phase/steps/post-merge-gate.md +2 -2
  293. package/gsd-core/workflows/execute-phase/steps/protected-branch.md +21 -0
  294. package/gsd-core/workflows/execute-phase/steps/regression-gate-run.md +2 -2
  295. package/gsd-core/workflows/execute-phase/steps/wave-post-gate-hooks.md +39 -0
  296. package/gsd-core/workflows/execute-phase.md +72 -100
  297. package/gsd-core/workflows/execute-plan.md +52 -15
  298. package/gsd-core/workflows/explore.md +131 -4
  299. package/gsd-core/workflows/extract-learnings.md +1 -1
  300. package/gsd-core/workflows/fast.md +10 -2
  301. package/gsd-core/workflows/forensics.md +1 -1
  302. package/gsd-core/workflows/graduation.md +5 -5
  303. package/gsd-core/workflows/health.md +76 -10
  304. package/gsd-core/workflows/import.md +18 -15
  305. package/gsd-core/workflows/inbox.md +4 -5
  306. package/gsd-core/workflows/ingest-docs.md +49 -16
  307. package/gsd-core/workflows/insert-phase.md +5 -5
  308. package/gsd-core/workflows/list-seeds.md +5 -3
  309. package/gsd-core/workflows/list-workspaces.md +1 -1
  310. package/gsd-core/workflows/manager.md +12 -23
  311. package/gsd-core/workflows/map-codebase.md +1 -1
  312. package/gsd-core/workflows/milestone-summary.md +1 -1
  313. package/gsd-core/workflows/mvp-phase.md +8 -5
  314. package/gsd-core/workflows/new-milestone.md +22 -29
  315. package/gsd-core/workflows/new-project/steps/auto-mode-config.md +1 -1
  316. package/gsd-core/workflows/new-project.md +26 -40
  317. package/gsd-core/workflows/new-workspace.md +1 -1
  318. package/gsd-core/workflows/next.md +14 -2
  319. package/gsd-core/workflows/pause-work.md +1 -1
  320. package/gsd-core/workflows/plan-phase/steps/adr-ingest-express-path.md +1 -1
  321. package/gsd-core/workflows/plan-phase/steps/chunked-planning-mode.md +1 -1
  322. package/gsd-core/workflows/plan-phase/steps/prd-express-path.md +2 -4
  323. package/gsd-core/workflows/plan-phase/steps/stall-detection-helpers.md +3 -3
  324. package/gsd-core/workflows/plan-phase.md +162 -59
  325. package/gsd-core/workflows/plan-review-convergence.md +96 -11
  326. package/gsd-core/workflows/plant-seed.md +2 -2
  327. package/gsd-core/workflows/pr-branch.md +187 -51
  328. package/gsd-core/workflows/profile-user.md +16 -14
  329. package/gsd-core/workflows/progress.md +61 -18
  330. package/gsd-core/workflows/quick/steps/discussion-phase.md +1 -3
  331. package/gsd-core/workflows/quick/steps/plan-checker-loop.md +5 -7
  332. package/gsd-core/workflows/quick/steps/quick-verification.md +28 -9
  333. package/gsd-core/workflows/quick/steps/research-phase.md +4 -6
  334. package/gsd-core/workflows/quick/steps/worktree-pre-dispatch-commit.md +3 -3
  335. package/gsd-core/workflows/quick.md +55 -44
  336. package/gsd-core/workflows/remove-phase.md +4 -4
  337. package/gsd-core/workflows/remove-workspace.md +2 -2
  338. package/gsd-core/workflows/resume-project.md +8 -12
  339. package/gsd-core/workflows/review.md +219 -20
  340. package/gsd-core/workflows/scan.md +1 -1
  341. package/gsd-core/workflows/secure-phase.md +3 -3
  342. package/gsd-core/workflows/session-report.md +2 -1
  343. package/gsd-core/workflows/settings-advanced.md +7 -9
  344. package/gsd-core/workflows/settings-integrations.md +64 -31
  345. package/gsd-core/workflows/settings.md +69 -7
  346. package/gsd-core/workflows/ship.md +116 -50
  347. package/gsd-core/workflows/sketch-wrap-up.md +11 -17
  348. package/gsd-core/workflows/sketch.md +12 -18
  349. package/gsd-core/workflows/smart-entry.md +3 -5
  350. package/gsd-core/workflows/spec-phase.md +53 -13
  351. package/gsd-core/workflows/spike-wrap-up.md +7 -11
  352. package/gsd-core/workflows/spike.md +20 -31
  353. package/gsd-core/workflows/stats.md +2 -2
  354. package/gsd-core/workflows/sync-skills.md +64 -9
  355. package/gsd-core/workflows/thread.md +11 -7
  356. package/gsd-core/workflows/transition.md +49 -14
  357. package/gsd-core/workflows/ui-phase.md +15 -21
  358. package/gsd-core/workflows/ui-review.md +8 -12
  359. package/gsd-core/workflows/ultraplan-phase.md +5 -13
  360. package/gsd-core/workflows/undo.md +8 -16
  361. package/gsd-core/workflows/update.md +7 -11
  362. package/gsd-core/workflows/validate-phase.md +3 -3
  363. package/gsd-core/workflows/verify-work/steps/automated-ui-verification.md +25 -1
  364. package/gsd-core/workflows/verify-work/steps/mvp-uat-framing.md +1 -1
  365. package/gsd-core/workflows/verify-work.md +66 -25
  366. package/hooks/dist/gsd-agent-isolation-guard.js +158 -30
  367. package/hooks/dist/gsd-check-update-worker.js +56 -13
  368. package/hooks/dist/gsd-check-update.js +19 -1
  369. package/hooks/dist/gsd-config-reload.js +18 -12
  370. package/hooks/dist/gsd-context-monitor.js +19 -10
  371. package/hooks/dist/gsd-cursor-post-tool.js +3 -1
  372. package/hooks/dist/gsd-cursor-pre-tool.js +2 -3
  373. package/hooks/dist/gsd-cursor-session-start.js +2 -1
  374. package/hooks/dist/gsd-cursor-stop.js +2 -1
  375. package/hooks/dist/gsd-cursor-subagent-start.js +83 -3
  376. package/hooks/dist/gsd-cursor-subagent-stop.js +6 -3
  377. package/hooks/dist/gsd-ensure-canonical-path.js +2 -1
  378. package/hooks/dist/gsd-graphify-update.sh +22 -18
  379. package/hooks/dist/gsd-node-runner.sh +76 -0
  380. package/hooks/dist/gsd-phase-boundary.sh +1 -0
  381. package/hooks/dist/gsd-prompt-guard.js +37 -27
  382. package/hooks/dist/gsd-read-guard.js +16 -7
  383. package/hooks/dist/gsd-read-injection-scanner.js +55 -32
  384. package/hooks/dist/gsd-session-state.sh +1 -0
  385. package/hooks/dist/gsd-statusline.js +231 -24
  386. package/hooks/dist/gsd-update-banner.js +22 -1
  387. package/hooks/dist/gsd-validate-commit.sh +80 -6
  388. package/hooks/dist/gsd-windsurf-pre-command.js +16 -11
  389. package/hooks/dist/gsd-windsurf-pre-write.js +22 -13
  390. package/hooks/dist/gsd-workflow-guard.js +162 -46
  391. package/hooks/dist/gsd-worktree-path-guard.js +36 -21
  392. package/hooks/dist/gsd-write-guard.js +35 -25
  393. package/hooks/dist/lib/cli-exit.js +560 -0
  394. package/hooks/dist/lib/exit-code-registry.js +98 -0
  395. package/hooks/dist/lib/git-cmd.js +92 -59
  396. package/hooks/dist/lib/git-probe.js +84 -0
  397. package/hooks/dist/lib/hook-exit.js +81 -0
  398. package/hooks/dist/lib/injection-patterns.js +45 -0
  399. package/hooks/dist/lib/isolation-deny-reason.js +39 -0
  400. package/hooks/dist/lib/isolation-sentinel.js +9 -0
  401. package/hooks/dist/managed-hooks-registry.cjs +3 -0
  402. package/hooks/gsd-agent-isolation-guard.js +158 -30
  403. package/hooks/gsd-check-update-worker.js +56 -13
  404. package/hooks/gsd-check-update.js +19 -1
  405. package/hooks/gsd-config-reload.js +18 -12
  406. package/hooks/gsd-context-monitor.js +19 -10
  407. package/hooks/gsd-cursor-post-tool.js +3 -1
  408. package/hooks/gsd-cursor-pre-tool.js +2 -3
  409. package/hooks/gsd-cursor-session-start.js +2 -1
  410. package/hooks/gsd-cursor-stop.js +2 -1
  411. package/hooks/gsd-cursor-subagent-start.js +83 -3
  412. package/hooks/gsd-cursor-subagent-stop.js +6 -3
  413. package/hooks/gsd-ensure-canonical-path.js +2 -1
  414. package/hooks/gsd-graphify-update.sh +22 -18
  415. package/hooks/gsd-node-runner.sh +76 -0
  416. package/hooks/gsd-phase-boundary.sh +1 -0
  417. package/hooks/gsd-prompt-guard.js +37 -27
  418. package/hooks/gsd-read-guard.js +16 -7
  419. package/hooks/gsd-read-injection-scanner.js +55 -32
  420. package/hooks/gsd-session-state.sh +1 -0
  421. package/hooks/gsd-statusline.js +231 -24
  422. package/hooks/gsd-update-banner.js +22 -1
  423. package/hooks/gsd-validate-commit.sh +80 -6
  424. package/hooks/gsd-windsurf-pre-command.js +16 -11
  425. package/hooks/gsd-windsurf-pre-write.js +22 -13
  426. package/hooks/gsd-workflow-guard.js +162 -46
  427. package/hooks/gsd-worktree-path-guard.js +36 -21
  428. package/hooks/gsd-write-guard.js +35 -25
  429. package/hooks/lib/cli-exit.js +560 -0
  430. package/hooks/lib/exit-code-registry.js +98 -0
  431. package/hooks/lib/git-cmd.js +92 -59
  432. package/hooks/lib/git-probe.js +84 -0
  433. package/hooks/lib/hook-exit.js +81 -0
  434. package/hooks/lib/injection-patterns.js +45 -0
  435. package/hooks/lib/isolation-deny-reason.js +39 -0
  436. package/hooks/lib/isolation-sentinel.js +9 -0
  437. package/hooks/managed-hooks-registry.cjs +3 -0
  438. package/package.json +28 -11
  439. package/pi/gsd.cjs +19 -5
  440. package/scripts/base64-scan.sh +74 -12
  441. package/scripts/baselines/planning-prompt-drift-baseline.json +4 -0
  442. package/scripts/baselines/planning-snapshot-bypass-baseline.json +12 -0
  443. package/scripts/baselines/unreachable-guard-drift-baseline.json +4 -0
  444. package/scripts/build-hooks.js +5 -0
  445. package/scripts/changeset/lint.cjs +60 -5
  446. package/scripts/check-alias-drift.cjs +7 -43
  447. package/scripts/check-contract-drift.cjs +297 -0
  448. package/scripts/check-glossary-refs.cjs +77 -15
  449. package/scripts/check-mutation-score-ratchet.cjs +156 -0
  450. package/scripts/ci-check-job-near-cap.cjs +49 -0
  451. package/scripts/ci-pr-mergeability.cjs +262 -0
  452. package/scripts/ci-test-scope.cjs +64 -14
  453. package/scripts/ci-timeout-report.cjs +230 -0
  454. package/scripts/command-contract-helpers.cjs +903 -1
  455. package/scripts/docs-guard-registry.cjs +396 -0
  456. package/scripts/gen-adr-index.cjs +728 -38
  457. package/scripts/gen-capability-registry.cjs +11 -21
  458. package/scripts/gen-context-index.cjs +2 -11
  459. package/scripts/gen-exit-code-docs.cjs +318 -0
  460. package/scripts/gen-exit-code-registry.cjs +891 -0
  461. package/scripts/gen-features.cjs +836 -0
  462. package/scripts/gen-health-docs.cjs +390 -0
  463. package/scripts/gen-hooks-cli-exit.cjs +239 -0
  464. package/scripts/gen-install-tree-fixtures.cjs +2 -2
  465. package/scripts/gen-inventory-manifest.cjs +50 -4
  466. package/scripts/gen-loop-host-contract.cjs +138 -25
  467. package/scripts/gen-registry.cjs +3 -14
  468. package/scripts/gen-scripts-cli-exit.cjs +185 -0
  469. package/scripts/gen-state-md-docs.cjs +727 -0
  470. package/scripts/{test-failure-reasons.cjs → gsd-test-gate-reasons.cjs} +6 -0
  471. package/scripts/lib/alias-drift-families.cjs +46 -0
  472. package/scripts/lib/ci-job-timing.cjs +72 -0
  473. package/scripts/lib/cli-exit.cjs +546 -44
  474. package/scripts/lib/drift-scan.cjs +308 -0
  475. package/scripts/lib/exit-code-registry.cjs +98 -0
  476. package/scripts/lib/ndjson-reporter.cjs +119 -0
  477. package/scripts/lint-allow-test-rule-refs.allowlist.json +1 -26
  478. package/scripts/lint-allow-test-rule-refs.effective-ceiling.json +4 -0
  479. package/scripts/lint-allow-test-rule-refs.unverified-ceiling.json +3 -0
  480. package/scripts/lint-canary-version-leak.cjs +73 -0
  481. package/scripts/lint-command-contract.cjs +96 -13
  482. package/scripts/lint-completion-predicate-drift.cjs +933 -0
  483. package/scripts/lint-completion-ratio-drift.cjs +214 -0
  484. package/scripts/lint-default-flip-documentation.cjs +193 -0
  485. package/scripts/lint-docs-guard-registration.cjs +495 -0
  486. package/scripts/lint-docs-guard-registration.exempt-baseline.cjs +193 -0
  487. package/scripts/lint-eslint-glob-coverage.allowlist.json +38 -0
  488. package/scripts/lint-eslint-glob-coverage.cjs +340 -0
  489. package/scripts/{lint-fix-has-regression-test.cjs → lint-fix-has-regression-tests.cjs} +12 -6
  490. package/scripts/lint-frontmatter-scalar-broad-grep.cjs +237 -0
  491. package/scripts/lint-health-diagnostic-rule-table.cjs +461 -0
  492. package/scripts/lint-hooks-runtime-build-seam.cjs +262 -0
  493. package/scripts/lint-milestone-window-drift.cjs +468 -0
  494. package/scripts/lint-mutation-test-derivation-drift.cjs +86 -0
  495. package/scripts/lint-phase-enumeration-drift.cjs +492 -0
  496. package/scripts/lint-plan-count-drift.cjs +318 -0
  497. package/scripts/lint-planning-artifact-writer-drift.cjs +398 -0
  498. package/scripts/lint-planning-prompt-drift.cjs +471 -0
  499. package/scripts/lint-planning-snapshot-bypass-drift.cjs +544 -0
  500. package/scripts/lint-regression-test-names.cjs +15 -13
  501. package/scripts/lint-removed-but-needed.cjs +488 -0
  502. package/scripts/lint-seam-enforcement.cjs +182 -0
  503. package/scripts/lint-slug-derivation-drift.cjs +921 -0
  504. package/scripts/lint-source-test-name-collision.cjs +241 -0
  505. package/scripts/lint-state-field-drift.cjs +805 -0
  506. package/scripts/lint-state-write-path-drift.cjs +950 -0
  507. package/scripts/lint-test-file-count.allowlist.json +137 -8
  508. package/scripts/lint-test-file-count.cjs +25 -3
  509. package/scripts/lint-unreachable-guard-drift.cjs +830 -0
  510. package/scripts/lint-vendored-deps.cjs +297 -0
  511. package/scripts/mutation-matrix.cjs +599 -50
  512. package/scripts/pr-changed-files.cjs +63 -0
  513. package/scripts/pr-template-policy.cjs +14 -4
  514. package/scripts/prompt-injection-scan.sh +100 -14
  515. package/scripts/require-issue-link-policy.cjs +192 -0
  516. package/scripts/secret-scan.sh +75 -13
  517. package/scripts/select-docs-guards.cjs +56 -0
  518. package/scripts/sync-runtime-launcher.cjs +24 -7
  519. package/skills/gsd-autonomous/SKILL.md +0 -1
  520. package/skills/gsd-code-review/SKILL.md +1 -1
  521. package/skills/gsd-discuss-phase/SKILL.md +1 -1
  522. package/skills/gsd-execute-phase/SKILL.md +1 -2
  523. package/skills/gsd-import/SKILL.md +1 -1
  524. package/skills/gsd-map-codebase/SKILL.md +1 -1
  525. package/skills/gsd-mempalace-capture/SKILL.md +1 -1
  526. package/skills/gsd-mempalace-recall/SKILL.md +1 -1
  527. package/skills/gsd-new-milestone/SKILL.md +1 -1
  528. package/skills/gsd-next/SKILL.md +0 -1
  529. package/skills/gsd-plan-phase/SKILL.md +0 -1
  530. package/skills/gsd-progress/SKILL.md +0 -1
  531. package/skills/gsd-quick/SKILL.md +9 -5
  532. package/skills/gsd-review-backlog/SKILL.md +2 -1
  533. package/skills/gsd-stats/SKILL.md +0 -1
  534. package/skills/gsd-verify-work/SKILL.md +1 -1
  535. package/vscode/package.json +1 -1
  536. package/bin/lib/ui-safety-gate.cjs +0 -107
  537. package/gsd-core/workflows/discovery-phase.md +0 -298
  538. package/gsd-core/workflows/plan-milestone-gaps.md +0 -281
  539. package/gsd-core/workflows/verify-phase.md +0 -574
  540. package/scripts/affected-tests-lib.cjs +0 -554
  541. package/scripts/lint-allow-test-rule-refs.cjs +0 -162
  542. package/scripts/lint-emitted-drift-ack.cjs +0 -344
  543. package/scripts/run-affected-tests.cjs +0 -7
  544. package/scripts/run-tests.cjs +0 -1051
@@ -21,12 +21,19 @@
21
21
  * - Issue #4 (open-gsd/gsd-core)
22
22
  */
23
23
  Object.defineProperty(exports, "__esModule", { value: true });
24
+ exports.locateProgressTable = locateProgressTable;
24
25
  exports.deriveProgressFromRoadmap = deriveProgressFromRoadmap;
26
+ exports.clampPercentFromFraction = clampPercentFromFraction;
25
27
  exports.clampPercent = clampPercent;
26
28
  const markdown_table_cjs_1 = require("./markdown-table.cjs");
29
+ // eslint-disable-next-line @typescript-eslint/no-require-imports -- phase-id.cjs is an export= CommonJS module
30
+ const phaseIdMod = require("./phase-id.cjs");
31
+ const { isSentinelPhaseId } = phaseIdMod;
27
32
  /**
28
- * Derive completed_phases, total_phases, and total_plans from ROADMAP content.
29
- * Root cause fix for issue #4 — see gen-phase-lifecycle.mjs for full documentation.
33
+ * #3227: the single owner of "where is this ROADMAP's Progress table".
34
+ * Lifted verbatim out of deriveProgressFromRoadmap so `state-contract.cts`
35
+ * enumerates phases from THE SAME table this module derives its counts from.
36
+ * A second copy of this locator is the DEFECT.GENERATIVE-FIX shape.
30
37
  *
31
38
  * ADR-2143 §3 ("addressed by NAME, never ordinal"): the Progress table is
32
39
  * located via the markdown-table seam's `findTableWithColumns`, which is
@@ -45,6 +52,24 @@ const markdown_table_cjs_1 = require("./markdown-table.cjs");
45
52
  * to scanning the whole input, preserving the "Progress table not under a
46
53
  * `## Progress` heading, or not the first table in the document, still
47
54
  * resolves" behaviour.
55
+ */
56
+ function locateProgressTable(roadmapContent) {
57
+ const progressMatch = roadmapContent.match(/^##[ \t]+Progress\b/im);
58
+ let scoped = roadmapContent;
59
+ if (progressMatch && progressMatch.index !== undefined) {
60
+ const afterHeading = roadmapContent.slice(progressMatch.index);
61
+ const nextHeading = afterHeading.search(/\n#{1,2}[ \t]/);
62
+ scoped = nextHeading >= 0 ? afterHeading.slice(0, nextHeading) : afterHeading;
63
+ }
64
+ return (0, markdown_table_cjs_1.findTableWithColumns)(scoped, ['Phase', 'Plans Complete', 'Status', 'Completed']);
65
+ }
66
+ /**
67
+ * Derive completed_phases, total_phases, and total_plans from ROADMAP content.
68
+ * Root cause fix for issue #4 — see gen-phase-lifecycle.mjs for full documentation.
69
+ *
70
+ * The Progress table itself is located by `locateProgressTable` (ADR-2143 §3,
71
+ * lifted out as #3227's single-owner extraction) — this function consumes
72
+ * that table.
48
73
  *
49
74
  * Cells are read by column NAME (`r['Status']`, `r['Plans Complete']`,
50
75
  * `r['Phase']`), fixing #2137 (the old position-based regex assumed "Status"
@@ -63,28 +88,16 @@ function deriveProgressFromRoadmap(roadmapContent) {
63
88
  // `{ ok: false, reason }`, not an exception — so the catch was masking
64
89
  // nothing but dead code paths. Removed per ADR-2143 §5; the public
65
90
  // `RoadmapProgress` contract (nulls = absent) is unchanged.
66
- //
67
- // ADR-2143 §3: read the Progress table by column NAME (order/injection-invariant),
68
- // via the markdown-table seam. Scope to the `## Progress` section when present
69
- // (#2012 decoy avoidance); a headingless milestone slice (#1445) falls back to the
70
- // whole input. Requires the canonical Phase/Plans Complete/Status/Completed columns
71
- // in any order (extra columns ignored) — supersedes findTableBySchema's exact-schema lookup.
72
- const progressMatch = roadmapContent.match(/^##[ \t]+Progress\b/im);
73
- let scoped = roadmapContent;
74
- if (progressMatch && progressMatch.index !== undefined) {
75
- const afterHeading = roadmapContent.slice(progressMatch.index);
76
- const nextHeading = afterHeading.search(/\n#{1,2}[ \t]/);
77
- scoped = nextHeading >= 0 ? afterHeading.slice(0, nextHeading) : afterHeading;
78
- }
79
- const table = (0, markdown_table_cjs_1.findTableWithColumns)(scoped, ['Phase', 'Plans Complete', 'Status', 'Completed']);
91
+ const table = locateProgressTable(roadmapContent);
80
92
  if (table) {
81
93
  const allRows = table.rows;
82
94
  const completed = allRows.filter((r) => /^complete$/i.test((r['Status'] ?? '').trim())).length;
83
95
  completedPhases = completed > 0 ? completed : null;
84
- // Data rows only (exclude 999.x backlog phases). Mirrors init.cts /^999(?:\.|$)/ filter.
96
+ // Data rows only (exclude sentinel phases 0 and 999.x).
97
+ // #3185: canonical sentinel predicate (SENTINEL_RANGES [0,999]) — this was a local 999-only literal that admitted Phase 0.
85
98
  const dataRows = allRows.filter((r) => {
86
99
  const phase = (r['Phase'] ?? '').trim();
87
- return /^\d/.test(phase) && !/^999\b/.test(phase);
100
+ return /^\d/.test(phase) && !isSentinelPhaseId(phase);
88
101
  });
89
102
  totalPhases = dataRows.length > 0 ? dataRows.length : null;
90
103
  let totalPlansSum = 0;
@@ -98,12 +111,32 @@ function deriveProgressFromRoadmap(roadmapContent) {
98
111
  }
99
112
  return { completedPhases, totalPhases, totalPlans };
100
113
  }
114
+ /**
115
+ * Compute progress percent clamped to 100 from an already-computed FRACTION.
116
+ *
117
+ * ADR-3180 Decision 7 (#3180): the completion-RATIO derivation has exactly one
118
+ * owner, and this is its kernel — the single place the `fraction -> integer
119
+ * percent` rounding and the 100 ceiling are expressed. `clampPercent` below is
120
+ * the count-shaped entry point and delegates here; a caller that already holds a
121
+ * fraction (rather than a completed/total pair) calls this directly instead of
122
+ * re-deriving `Math.min(100, Math.round(f * 100))` locally.
123
+ *
124
+ * Enforced by `scripts/lint-completion-ratio-drift.cjs`.
125
+ */
126
+ function clampPercentFromFraction(fraction) {
127
+ return Math.min(100, Math.round(fraction * 100));
128
+ }
101
129
  /**
102
130
  * Compute progress percent clamped to 100.
103
131
  * Root cause fix for issue #4 — see gen-phase-lifecycle.mjs for full documentation.
132
+ *
133
+ * A non-positive (or absent) denominator yields `0` — "nothing to complete" is
134
+ * reported as 0%, never as 100%. Every `.planning/` completion percentage in this
135
+ * codebase routes through here (ADR-3180 Decision 7); the `total > 0 ? ... : 0`
136
+ * ternary that used to precede each inline copy IS this function's first line.
104
137
  */
105
138
  function clampPercent(completed, total) {
106
139
  if (!total || total <= 0)
107
140
  return 0;
108
- return Math.min(100, Math.round((completed / total) * 100));
141
+ return clampPercentFromFraction(completed / total);
109
142
  }
@@ -12,7 +12,7 @@
12
12
  *
13
13
  * Dependencies (leaf modules only — no loadConfig):
14
14
  * - node:fs / node:path (stdlib)
15
- * - ./phase-id.cjs (normalizePhaseName, phaseTokenMatches, extractPhaseToken)
15
+ * - ./phase-id.cjs (normalizePhaseName, matchPhaseDirs, phaseNumberForMatch)
16
16
  * - ./core-utils.cjs (readSubdirectories, getPhaseFileStats, extractCanonicalPlanId, toPosixPath)
17
17
  * - ./planning-workspace.cjs (planningDir)
18
18
  */
@@ -23,19 +23,25 @@ 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 phaseIdModule = require("./phase-id.cjs");
26
- const { normalizePhaseName, phaseTokenMatches, extractPhaseToken } = phaseIdModule;
26
+ const { normalizePhaseName, matchPhaseDirs, phaseNumberForMatch, isSentinelPhaseId, comparePhaseNum } = phaseIdModule;
27
27
  // eslint-disable-next-line @typescript-eslint/no-require-imports
28
28
  const coreUtilsModule = require("./core-utils.cjs");
29
- const { readSubdirectories, getPhaseFileStats, extractCanonicalPlanId, toPosixPath } = coreUtilsModule;
29
+ const { readSubdirectories, getPhaseFileStats, extractCanonicalPlanId, toPosixPath, findUnsummarizedPlans } = coreUtilsModule;
30
30
  // eslint-disable-next-line @typescript-eslint/no-require-imports
31
31
  const planningWorkspace = require("./planning-workspace.cjs");
32
- const { planningDir } = planningWorkspace;
32
+ const { planningDir, planningRoot } = planningWorkspace;
33
33
  // eslint-disable-next-line @typescript-eslint/no-require-imports
34
34
  const frontmatterModule = require("./frontmatter.cjs");
35
35
  const { extractFrontmatter } = frontmatterModule;
36
36
  // eslint-disable-next-line @typescript-eslint/no-require-imports
37
37
  const planDependencyGraphModule = require("./plan-dependency-graph.cjs");
38
38
  const { computeHaltPropagation, buildSummaryFileIndex, isSummaryFileHalted } = planDependencyGraphModule;
39
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
40
+ const roadmapParserModule = require("./roadmap-parser.cjs");
41
+ const { getMilestonePhaseFilter } = roadmapParserModule;
42
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
43
+ const planningScopeMod = require("./planning-scope.cjs");
44
+ const { SCOPE } = planningScopeMod;
39
45
  /**
40
46
  * #2830: parse a plan file's `depends_on` frontmatter. Returns [] — never
41
47
  * throws — on a missing/unreadable/malformed plan or absent field, matching
@@ -68,33 +74,85 @@ function parsePlanDependsOn(phaseDir, planFile) {
68
74
  * exactly the shape that let the original #2855 bug (hardcoded root path)
69
75
  * exist in one copy and not the other. Sharing this seam means a future
70
76
  * change to how the archive tree is located only needs to happen once.
71
- * Most-recent-milestone-first order (reverse-sorted directory names).
77
+ * Most-recent-milestone-first order, compared numerically segment-by-segment
78
+ * on the version (e.g. `v1.10` before `v1.9`) — NOT lexicographically. A
79
+ * lexicographic `.sort().reverse()` (the prior implementation) ranks `v1.9`
80
+ * ahead of `v1.10` because the string `"1.9"` sorts after `"1.10"`; that is
81
+ * deterministic but wrong for every double-digit-or-higher minor/patch
82
+ * version, and #3458 is what first surfaces archived phases in audit output
83
+ * where the misordering becomes user-visible.
72
84
  * Never throws: an absent/unreadable milestones/ dir yields [].
73
85
  */
74
- function listArchiveVersionDirs(cwd) {
75
- const milestonesDir = node_path_1.default.join(planningDir(cwd), 'milestones');
76
- if (!node_fs_1.default.existsSync(milestonesDir))
77
- return [];
86
+ function compareArchiveVersionDesc(aName, bName) {
87
+ const aParts = (aName.match(/^v([\d.]+)-phases$/)?.[1] ?? '').split('.').map(Number);
88
+ const bParts = (bName.match(/^v([\d.]+)-phases$/)?.[1] ?? '').split('.').map(Number);
89
+ const len = Math.max(aParts.length, bParts.length);
90
+ for (let i = 0; i < len; i++) {
91
+ const a = aParts[i] ?? 0;
92
+ const b = bParts[i] ?? 0;
93
+ if (a !== b)
94
+ return b - a; // descending: newest (numerically largest) first
95
+ }
96
+ return 0;
97
+ }
98
+ function listArchiveVersionDirs(cwd, wsOverride) {
99
+ // #3804: enumerate BOTH archive shapes under the CURRENT SCOPE's milestones
100
+ // tree (planningDir — GSD_WORKSTREAM/GSD_PROJECT-aware, exactly the
101
+ // #2855 scoping findPhaseInternal and getArchivedPhaseDirs rely on):
102
+ // flat: <scope>/milestones/vX.Y-phases/<phase-dir>/
103
+ // workstream archive: <scope>/milestones/ws-<slug>-<date>/phases/<phase-dir>/
104
+ // Pre-#3804 only the flat shape matched, so workstream-archived milestones
105
+ // were invisible (the reporter's repo: 20 hidden phase artifacts). The
106
+ // ws-* shape's phase dirs sit one level deeper (under phases/) and its dir
107
+ // name fails ^v[\d.]+-phases$ — both the name AND the level are modeled.
108
+ // Version labels: flat keeps the bare vX.Y; ws-* shapes carry the dir name
109
+ // (no numeric version). Flat-newest-first (compareArchiveVersionDesc), then
110
+ // ws dirs by name descending — deterministic. The AUDIT's cross-workstream
111
+ // enumeration (audit.cts listAuditPhaseTargets) calls this helper once per
112
+ // tree (root + each workstream) rather than widening this scope — the
113
+ // #2855 no-leak contract for findPhaseInternal/getArchivedPhaseDirs is
114
+ // preserved untouched.
115
+ const milestonesDir = node_path_1.default.join(planningDir(cwd, wsOverride ?? undefined), 'milestones');
116
+ const out = [];
117
+ const seen = new Set();
118
+ const pushVersion = (version, archivePath) => {
119
+ const rel = toPosixPath(node_path_1.default.relative(cwd, archivePath));
120
+ if (seen.has(rel))
121
+ return;
122
+ seen.add(rel);
123
+ out.push({ version, archivePath });
124
+ };
125
+ let entries;
78
126
  try {
79
- const milestoneEntries = node_fs_1.default.readdirSync(milestonesDir, { withFileTypes: true });
80
- return milestoneEntries
81
- .filter(e => e.isDirectory() && /^v[\d.]+-phases$/.test(e.name))
82
- .map(e => e.name)
83
- .sort()
84
- .reverse()
85
- .map(archiveName => ({
86
- version: archiveName.match(/^(v[\d.]+)-phases$/)[1],
87
- archivePath: node_path_1.default.join(milestonesDir, archiveName),
88
- }));
127
+ entries = node_fs_1.default.readdirSync(milestonesDir, { withFileTypes: true });
89
128
  }
90
129
  catch {
91
130
  return [];
92
131
  }
132
+ const flat = entries
133
+ .filter(e => e.isDirectory() && /^v[\d.]+-phases$/.test(e.name))
134
+ .map(e => e.name)
135
+ .sort(compareArchiveVersionDesc);
136
+ for (const archiveName of flat) {
137
+ pushVersion(archiveName.match(/^(v[\d.]+)-phases$/)[1], node_path_1.default.join(milestonesDir, archiveName));
138
+ }
139
+ const wsDirs = entries
140
+ .filter(e => e.isDirectory() && /^ws-/.test(e.name))
141
+ .map(e => e.name)
142
+ .sort()
143
+ .reverse();
144
+ for (const wsName of wsDirs) {
145
+ pushVersion(wsName, node_path_1.default.join(milestonesDir, wsName, 'phases'));
146
+ }
147
+ return out;
93
148
  }
94
149
  function searchPhaseInDir(baseDir, relBase, normalized) {
95
150
  try {
96
151
  const dirs = readSubdirectories(baseDir, true);
97
- const matches = dirs.filter(d => phaseTokenMatches(d, normalized));
152
+ // #2528: canonical two-pass selection (exact token match, then the
153
+ // bare-integer leading-digit-run fallback) shared with the find-phase and
154
+ // phase-plan-index scans — see phase-id.cts::matchPhaseDirs.
155
+ const { matches, usedBareFallback } = matchPhaseDirs(dirs, normalized);
98
156
  if (matches.length === 0)
99
157
  return null;
100
158
  // #2237: fail loud when multiple directories match the same bare phase
@@ -121,7 +179,7 @@ function searchPhaseInDir(baseDir, relBase, normalized) {
121
179
  };
122
180
  }
123
181
  const match = matches[0];
124
- const phaseToken = extractPhaseToken(match);
182
+ const phaseToken = phaseNumberForMatch(match, usedBareFallback);
125
183
  const phaseNumber = phaseToken || normalized;
126
184
  const afterToken = match.slice(phaseToken ? phaseToken.length : 0).replace(/^-/, '');
127
185
  const phaseName = afterToken || null;
@@ -129,16 +187,13 @@ function searchPhaseInDir(baseDir, relBase, normalized) {
129
187
  const { plans: unsortedPlans, summaries: unsortedSummaries, hasResearch, hasContext, hasVerification, hasReviews } = getPhaseFileStats(phaseDir);
130
188
  const plans = unsortedPlans.sort();
131
189
  const summaries = unsortedSummaries.sort();
132
- const completedPlanIds = new Set(summaries.flatMap(s => {
133
- const exact = s.replace('-SUMMARY.md', '').replace('SUMMARY.md', '');
134
- const canonical = extractCanonicalPlanId(s);
135
- return canonical === exact ? [exact] : [exact, canonical];
136
- }));
137
- const incompletePlans = plans.filter(p => {
138
- const planId = p.replace('-PLAN.md', '').replace('PLAN.md', '');
139
- const canonical = extractCanonicalPlanId(p);
140
- return !completedPlanIds.has(planId) && !completedPlanIds.has(canonical);
141
- });
190
+ // #3183 (ADR-3180 Decision 2): the summary→plan pairing used to be a
191
+ // bespoke rule local to this function (a third pairing rule alongside
192
+ // scanPhasePlans's completion check and countMatchedSummaries). Routed
193
+ // through the canonical core-utils.findUnsummarizedPlans instead, which
194
+ // shares its `summaryCandidates` matching rule with countMatchedSummaries
195
+ // so the count and this named list can never disagree.
196
+ const incompletePlans = findUnsummarizedPlans(plans, summaries);
142
197
  // #2830: reverse lookup from a completed plan's id (exact or canonical) to
143
198
  // its actual summary filename. Shared builder (also used by phase.cts's
144
199
  // cmdPhasePlanIndex) so the two can never disagree about which summary
@@ -188,7 +243,12 @@ function searchPhaseInDir(baseDir, relBase, normalized) {
188
243
  directory: toPosixPath(node_path_1.default.join(relBase, match)),
189
244
  phase_number: phaseNumber,
190
245
  phase_name: phaseName,
191
- phase_slug: phaseName ? phaseName.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '') : null,
246
+ // #3883 (ADR-3473 §8.3): delegate to the canonical slug formula
247
+ // (generateSlugInternal, core-utils.cts) rather than re-implementing
248
+ // it. `maxLen: null` preserves this site's pre-migration untruncated
249
+ // contract — the 60-char default would drop an on-disk phase slug's
250
+ // reported value out of sync with the real directory name.
251
+ phase_slug: phaseName ? coreUtilsModule.generateSlugInternal(phaseName, null) : null,
192
252
  plans,
193
253
  summaries,
194
254
  incomplete_plans: incompletePlans,
@@ -233,13 +293,139 @@ function findPhaseInternal(cwd, phase) {
233
293
  }
234
294
  return null;
235
295
  }
236
- function getArchivedPhaseDirs(cwd) {
296
+ /**
297
+ * #3185 (epic #3180 Phase 3, ADR-3180 Decision 1 row "Phase enumeration"):
298
+ * the SINGLE canonical owner of "which phase directories belong to the current
299
+ * milestone". Applies the milestone window AND the sentinel filter, in that
300
+ * order, and returns the surviving directory names.
301
+ *
302
+ * Before this existed the derivation had four independent implementations and
303
+ * only `cmdRoadmapAnalyze` carried both halves; `cmdProgressRender`,
304
+ * `cmdStats` and `cmdPhasesList` each carried neither or one.
305
+ *
306
+ * TWO THINGS THIS GETS RIGHT THAT A HEADING-SIDE FILTER CANNOT:
307
+ *
308
+ * 1. The sentinel test runs against DIRECTORY NAMES and is UNCONDITIONAL.
309
+ * `getMilestonePhaseFilter` excludes sentinels from its ROADMAP HEADING
310
+ * set, but when that set is empty it degrades to a literal `() => true`
311
+ * pass-all predicate and never consults the heading set at all — so its
312
+ * own sentinel exclusion becomes unreachable exactly when it is needed,
313
+ * and every directory on disk (backlog included) is reported as a
314
+ * current-milestone phase. That degrade is the #3167 symptom path.
315
+ *
316
+ * 2. The sentinel predicate is the canonical `isSentinelPhaseId`
317
+ * (`src/phase-id.cts`, SENTINEL_RANGES [0, 999]), not a local literal.
318
+ * The rule had five copies and three different regexes before this phase,
319
+ * and they disagreed about Phase 0.
320
+ *
321
+ * The pass-all degrade is narrowed MINIMALLY: it stays over-inclusive for
322
+ * non-sentinel directories, so a project whose window declares no phases
323
+ * still sees its real phase directories. Only sentinels are refused.
324
+ *
325
+ * `scope` distinguishes a REAL empty from a NON-answer (ADR-3180 Decision 2):
326
+ * an absent `phasesDir` is a real empty (a new project genuinely has no
327
+ * phases) and inherits the window's scope, whereas a `phasesDir` that exists
328
+ * but cannot be read is UNREADABLE.
329
+ *
330
+ * `opts.ws` is tri-state, matching `planningDir`'s own contract: `undefined`
331
+ * (the default — do not pass `ws` at all) resolves the AMBIENT workstream
332
+ * from `GSD_WORKSTREAM`; `null` FORCES the project root regardless of any
333
+ * ambient workstream; a string forces that specific workstream.
334
+ */
335
+ function listMilestonePhaseDirs(phasesDir, opts = {}) {
336
+ // #3597: `ws` must default to `undefined`, NOT `null`. `undefined` means
337
+ // "resolve the ambient workstream" (mirrors planningDir's own contract,
338
+ // src/planning-workspace.cts:124); `null` means "force the project root".
339
+ // Every cwd-bearing caller derives `phasesDir` ambiently (planningPaths(cwd)
340
+ // / planningDir(cwd) with no explicit ws), so defaulting `ws` to `null` here
341
+ // forced the milestone WINDOW to the root ROADMAP while the caller's
342
+ // `phasesDir` stayed workstream-scoped — numerator and denominator drawn
343
+ // from different scoped sets (ADR-3180 §7.6 rule 3 violation). That is what
344
+ // made `--ws <name> progress` read `phase_scope: "unreadable"` and withhold
345
+ // `percent` once `workstream create` migrated the root ROADMAP away.
346
+ const { cwd, ws, versionOverride = null, phaseIdConvention = null } = opts;
347
+ // Without a cwd there is nothing to scope AGAINST — the caller asked for an
348
+ // unscoped read, which is a real answer (mirrors extractCurrentMilestoneScoped's
349
+ // row 1). Sentinels are still refused: they are never milestone phases.
350
+ let inWindow = () => true;
351
+ let scope = SCOPE.COMPLETE;
352
+ if (cwd) {
353
+ const filter = getMilestonePhaseFilter(cwd, versionOverride, phaseIdConvention, ws);
354
+ inWindow = filter;
355
+ scope = filter.scope;
356
+ }
357
+ // An ABSENT phases dir is a real empty, not a failure: a freshly-created
358
+ // project genuinely has no phase directories yet. Distinguishing this from
359
+ // the unreadable case below is the whole point of the scope discriminator.
360
+ if (!node_fs_1.default.existsSync(phasesDir))
361
+ return { value: [], scope };
362
+ let names;
363
+ try {
364
+ names = node_fs_1.default.readdirSync(phasesDir, { withFileTypes: true })
365
+ .filter((e) => e.isDirectory())
366
+ .map((e) => e.name);
367
+ }
368
+ catch {
369
+ // The directory EXISTS but could not be read (EACCES/EIO). An empty list
370
+ // here is a NON-answer and must not be reported as "this milestone has no
371
+ // phases" — that collapse is the defect class this epic removes.
372
+ return { value: [], scope: SCOPE.UNREADABLE };
373
+ }
374
+ const value = names
375
+ .filter((name) => inWindow(name) && !isSentinelPhaseId(name, phaseIdConvention ?? undefined))
376
+ .sort((a, b) => comparePhaseNum(a, b));
377
+ return { value, scope };
378
+ }
379
+ /**
380
+ * #3882 (ADR-3473 §8.2, issue #3882): the single owner of "the PHYSICAL set
381
+ * of phase directories on disk, entirely un-windowed" — the OTHER axis
382
+ * `listMilestonePhaseDirs` above deliberately does not offer. That owner
383
+ * refuses sentinels UNCONDITIONALLY (see its own doc comment); it has no way
384
+ * to say "physical set, sentinels included". That is exactly what an
385
+ * archival, lookup-index, or health-sweep caller needs — e.g. a heading ->
386
+ * directory lookup index that must resolve a directory regardless of
387
+ * milestone window (`cmdRoadmapAnalyze`'s `_phaseDirNames`,
388
+ * `cmdInitMilestoneOp`'s `diskPhaseDirs`) — which is why those callers used
389
+ * to hand-roll a `readdirSync` instead of calling either owner.
390
+ *
391
+ * `includeSentinels` is REQUIRED, with no default value. #3882/ADR-3473 §8.2:
392
+ * "a caller that wants sentinels asks for them explicitly" — obtaining
393
+ * sentinel-inclusion by silent omission is exactly the defect class this
394
+ * axis exists to close, so the call site is refused at COMPILE TIME without
395
+ * it, not merely documented against it here.
396
+ *
397
+ * Mirrors `listMilestonePhaseDirs`'s own absent/unreadable handling
398
+ * (ADR-3180 Decision 2): an ABSENT `phasesDir` is a real empty (a project
399
+ * with no phase directories yet), `scope: SCOPE.COMPLETE`; a `phasesDir`
400
+ * that EXISTS but cannot be read is a NON-answer, `scope: SCOPE.UNREADABLE`
401
+ * — a caller must not treat that empty list as "this project has no
402
+ * phases."
403
+ */
404
+ function listAllPhaseDirs(phasesDir, opts) {
405
+ const { includeSentinels, phaseIdConvention = null } = opts;
406
+ if (!node_fs_1.default.existsSync(phasesDir))
407
+ return { value: [], scope: SCOPE.COMPLETE };
408
+ let names;
409
+ try {
410
+ names = node_fs_1.default.readdirSync(phasesDir, { withFileTypes: true })
411
+ .filter((e) => e.isDirectory())
412
+ .map((e) => e.name);
413
+ }
414
+ catch {
415
+ return { value: [], scope: SCOPE.UNREADABLE };
416
+ }
417
+ const value = names
418
+ .filter((name) => includeSentinels || !isSentinelPhaseId(name, phaseIdConvention ?? undefined))
419
+ .sort((a, b) => comparePhaseNum(a, b));
420
+ return { value, scope: SCOPE.COMPLETE };
421
+ }
422
+ function getArchivedPhaseDirs(cwd, wsOverride) {
237
423
  // #2855: same workstream-scoped resolution as findPhaseInternal above, via
238
424
  // the shared listArchiveVersionDirs helper. `phase.list --include-archived`
239
425
  // (the primary non-init consumer) must not leak a different workstream's
240
426
  // archive either.
241
427
  const results = [];
242
- for (const { version, archivePath } of listArchiveVersionDirs(cwd)) {
428
+ for (const { version, archivePath } of listArchiveVersionDirs(cwd, wsOverride)) {
243
429
  const dirs = readSubdirectories(archivePath, true);
244
430
  for (const dir of dirs) {
245
431
  results.push({
@@ -252,8 +438,50 @@ function getArchivedPhaseDirs(cwd) {
252
438
  }
253
439
  return results;
254
440
  }
441
+ /**
442
+ * #3804 — the CROSS-WORKSTREAM archive enumeration the audit surfaces need.
443
+ * getArchivedPhaseDirs above is deliberately #2855-SCOPED (an ambient
444
+ * workstream must not leak other trees' phases to findPhaseInternal), but
445
+ * audit-uat's charter (#2766: outstanding items do not stop mattering) is
446
+ * cross-workstream: enumerate the project root plus every workstream's own
447
+ * milestones tree, labeling workstream entries '<ws>/<version>' so
448
+ * acknowledge-by-label stays unambiguous. Deduped by full path (the same
449
+ * tree cannot be reached twice, but the guard keeps the invariant explicit).
450
+ */
451
+ function getAllArchivedPhaseDirs(cwd) {
452
+ const out = [];
453
+ const seen = new Set();
454
+ const collect = (labelPrefix, wsOverride) => {
455
+ for (const archived of getArchivedPhaseDirs(cwd, wsOverride)) {
456
+ const rel = toPosixPath(node_path_1.default.relative(cwd, archived.fullPath));
457
+ if (seen.has(rel))
458
+ continue;
459
+ seen.add(rel);
460
+ out.push({ ...archived, milestone: `${labelPrefix}${archived.milestone}` });
461
+ }
462
+ };
463
+ collect('', null);
464
+ const workstreamsDir = node_path_1.default.join(planningRoot(cwd), 'workstreams');
465
+ try {
466
+ const wsEntries = node_fs_1.default.readdirSync(workstreamsDir, { withFileTypes: true })
467
+ .filter((e) => e.isDirectory())
468
+ .map((e) => e.name)
469
+ .sort()
470
+ .reverse();
471
+ for (const ws of wsEntries) {
472
+ collect(`${ws}/`, ws);
473
+ }
474
+ }
475
+ catch {
476
+ /* no workstreams dir — the root pass above already ran */
477
+ }
478
+ return out;
479
+ }
255
480
  module.exports = {
256
481
  searchPhaseInDir,
257
482
  findPhaseInternal,
258
483
  getArchivedPhaseDirs,
484
+ getAllArchivedPhaseDirs,
485
+ listMilestonePhaseDirs,
486
+ listAllPhaseDirs,
259
487
  };