@opengsd/gsd-core 1.17.0 → 1.19.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 (369) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +2 -1
  3. package/.opencode/plugins/gsd-core.js +2 -3
  4. package/agents/gsd-advisor-researcher.compact.md +1 -1
  5. package/agents/gsd-advisor-researcher.md +1 -1
  6. package/agents/gsd-assumptions-analyzer.compact.md +1 -1
  7. package/agents/gsd-assumptions-analyzer.md +1 -1
  8. package/agents/gsd-code-fixer.compact.md +3 -3
  9. package/agents/gsd-code-fixer.md +5 -4
  10. package/agents/gsd-code-reviewer.compact.md +1 -1
  11. package/agents/gsd-code-reviewer.md +1 -1
  12. package/agents/gsd-codebase-mapper.compact.md +1 -1
  13. package/agents/gsd-codebase-mapper.md +1 -1
  14. package/agents/gsd-debugger.md +1 -1
  15. package/agents/gsd-doc-verifier.compact.md +4 -4
  16. package/agents/gsd-doc-verifier.md +4 -3
  17. package/agents/gsd-doc-writer.compact.md +23 -5
  18. package/agents/gsd-doc-writer.md +19 -6
  19. package/agents/gsd-eval-auditor.compact.md +1 -1
  20. package/agents/gsd-eval-auditor.md +1 -1
  21. package/agents/gsd-executor.md +7 -3
  22. package/agents/gsd-integration-checker.compact.md +1 -1
  23. package/agents/gsd-integration-checker.md +1 -1
  24. package/agents/gsd-nyquist-auditor.compact.md +1 -1
  25. package/agents/gsd-nyquist-auditor.md +1 -1
  26. package/agents/gsd-phase-researcher.md +1 -1
  27. package/agents/gsd-plan-checker.md +2 -1
  28. package/agents/gsd-planner.md +5 -4
  29. package/agents/gsd-project-researcher.compact.md +1 -1
  30. package/agents/gsd-project-researcher.md +1 -1
  31. package/agents/gsd-research-synthesizer.compact.md +5 -5
  32. package/agents/gsd-research-synthesizer.md +5 -5
  33. package/agents/gsd-roadmapper.compact.md +1 -1
  34. package/agents/gsd-roadmapper.md +1 -1
  35. package/agents/gsd-security-auditor.compact.md +1 -1
  36. package/agents/gsd-security-auditor.md +1 -1
  37. package/agents/gsd-ui-auditor.compact.md +1 -1
  38. package/agents/gsd-ui-auditor.md +1 -1
  39. package/agents/gsd-ui-checker.compact.md +1 -1
  40. package/agents/gsd-ui-checker.md +1 -1
  41. package/agents/gsd-ui-researcher.compact.md +13 -1
  42. package/agents/gsd-ui-researcher.md +28 -1
  43. package/agents/gsd-verifier.md +2 -2
  44. package/bin/install.js +460 -105
  45. package/commands/gsd/code-review.md +8 -0
  46. package/commands/gsd/pause-work.md +1 -1
  47. package/gsd-core/bin/gsd-tools.cjs +124 -18
  48. package/gsd-core/bin/lib/agent-install-check.cjs +29 -3
  49. package/gsd-core/bin/lib/args-terminator.cjs +114 -0
  50. package/gsd-core/bin/lib/broken-windows.cjs +59 -1
  51. package/gsd-core/bin/lib/capability-ledger.cjs +25 -0
  52. package/gsd-core/bin/lib/capability-lock.cjs +41 -8
  53. package/gsd-core/bin/lib/capability-registry.cjs +162 -74
  54. package/gsd-core/bin/lib/capability-source.cjs +3 -1
  55. package/gsd-core/bin/lib/capability-state.cjs +20 -11
  56. package/gsd-core/bin/lib/capability-validator.cjs +23 -4
  57. package/gsd-core/bin/lib/commands.cjs +5 -1
  58. package/gsd-core/bin/lib/config-loader.cjs +13 -3
  59. package/gsd-core/bin/lib/config.cjs +256 -58
  60. package/gsd-core/bin/lib/core-utils.cjs +34 -0
  61. package/gsd-core/bin/lib/decision-coverage-support.cjs +93 -0
  62. package/gsd-core/bin/lib/gate-context-drift.cjs +8 -3
  63. package/gsd-core/bin/lib/gate-decision-coverage-plan.cjs +21 -1
  64. package/gsd-core/bin/lib/gate-decision-coverage-verify.cjs +19 -1
  65. package/gsd-core/bin/lib/gate-evaluation-scope.cjs +134 -13
  66. package/gsd-core/bin/lib/gate-predicate-evaluator.cjs +23 -3
  67. package/gsd-core/bin/lib/gate-predicate.cjs +8 -12
  68. package/gsd-core/bin/lib/gate-tdd-review-checkpoint.cjs +114 -18
  69. package/gsd-core/bin/lib/gate-ui-safety.cjs +23 -5
  70. package/gsd-core/bin/lib/git-info-exclude.cjs +57 -0
  71. package/gsd-core/bin/lib/health-diagnostic-rules/phase-structure.cjs +18 -3
  72. package/gsd-core/bin/lib/health-diagnostic-types.cjs +4 -0
  73. package/gsd-core/bin/lib/health-diagnostic.cjs +52 -1
  74. package/gsd-core/bin/lib/host-integration.cjs +13 -6
  75. package/gsd-core/bin/lib/init.cjs +76 -5
  76. package/gsd-core/bin/lib/install-effort-resolver.cjs +2 -2
  77. package/gsd-core/bin/lib/install-engine.cjs +246 -32
  78. package/gsd-core/bin/lib/install-model-override-resolver.cjs +4 -3
  79. package/gsd-core/bin/lib/install-profiles.cjs +14 -7
  80. package/gsd-core/bin/lib/installer-migration-report.cjs +2 -0
  81. package/gsd-core/bin/lib/markdown-line-diff.cjs +147 -0
  82. package/gsd-core/bin/lib/markdown-sectionizer.cjs +113 -0
  83. package/gsd-core/bin/lib/milestone-lock.cjs +8 -1
  84. package/gsd-core/bin/lib/milestone-release-map.cjs +244 -0
  85. package/gsd-core/bin/lib/milestone-release-tags.cjs +440 -0
  86. package/gsd-core/bin/lib/milestone.cjs +43 -1
  87. package/gsd-core/bin/lib/model-catalog.cjs +8 -0
  88. package/gsd-core/bin/lib/normalize-test-command.cjs +67 -0
  89. package/gsd-core/bin/lib/phase-id.cjs +21 -4
  90. package/gsd-core/bin/lib/phase-lifecycle.cjs +37 -4
  91. package/gsd-core/bin/lib/phase.cjs +235 -37
  92. package/gsd-core/bin/lib/planning-snapshot.cjs +3 -1
  93. package/gsd-core/bin/lib/planning-workspace.cjs +18 -0
  94. package/gsd-core/bin/lib/pristine-baseline.cjs +11 -3
  95. package/gsd-core/bin/lib/read-only-git-env.cjs +58 -0
  96. package/gsd-core/bin/lib/report-parser.cjs +57 -1
  97. package/gsd-core/bin/lib/review-lane-descriptor.cjs +35 -2
  98. package/gsd-core/bin/lib/review-lane-invocation.cjs +40 -0
  99. package/gsd-core/bin/lib/review-lane-runner.cjs +33 -4
  100. package/gsd-core/bin/lib/review-reviewer-selection.cjs +20 -1
  101. package/gsd-core/bin/lib/roadmap-parser.cjs +12 -0
  102. package/gsd-core/bin/lib/roadmap-upgrade.cjs +39 -2
  103. package/gsd-core/bin/lib/roadmap.cjs +89 -30
  104. package/gsd-core/bin/lib/runtime-artifact-conversion.cjs +428 -16
  105. package/gsd-core/bin/lib/runtime-artifact-layout.cjs +8 -4
  106. package/gsd-core/bin/lib/runtime-homes.cjs +18 -0
  107. package/gsd-core/bin/lib/runtime-hooks-surface.cjs +60 -16
  108. package/gsd-core/bin/lib/shell-command-projection.cjs +123 -16
  109. package/gsd-core/bin/lib/smart-entry.cjs +37 -16
  110. package/gsd-core/bin/lib/state-contract.cjs +29 -9
  111. package/gsd-core/bin/lib/state-document.cjs +47 -5
  112. package/gsd-core/bin/lib/state-transition.cjs +150 -34
  113. package/gsd-core/bin/lib/state.cjs +140 -88
  114. package/gsd-core/bin/lib/surface.cjs +9 -0
  115. package/gsd-core/bin/lib/task-command-router.cjs +7 -28
  116. package/gsd-core/bin/lib/task-content-resolution.cjs +46 -1
  117. package/gsd-core/bin/lib/tdd-red-evidence.cjs +5 -0
  118. package/gsd-core/bin/lib/tdd-test-files.cjs +42 -0
  119. package/gsd-core/bin/lib/verification.cjs +96 -9
  120. package/gsd-core/bin/lib/verify.cjs +38 -1
  121. package/gsd-core/bin/lib/worktree-safety.cjs +22 -1
  122. package/gsd-core/bin/shared/config-defaults.manifest.json +1 -0
  123. package/gsd-core/bin/shared/config-schema.manifest.json +1 -0
  124. package/gsd-core/references/agent-contracts.md +1 -1
  125. package/gsd-core/references/agent-skills-bootstrap.md +26 -7
  126. package/gsd-core/references/git-integration.md +14 -3
  127. package/gsd-core/references/loop-hook-dispatch.md +13 -6
  128. package/gsd-core/references/planner-reversibility.md +18 -4
  129. package/gsd-core/references/planner-verify-command-grounding.md +18 -0
  130. package/gsd-core/references/planning-config.md +2 -0
  131. package/gsd-core/references/tdd.md +5 -4
  132. package/gsd-core/references/verifier-phase-gates.md +8 -1
  133. package/gsd-core/templates/DEBUG.md +16 -0
  134. package/gsd-core/templates/config.json +2 -1
  135. package/gsd-core/templates/verification-report.md +1 -1
  136. package/gsd-core/workflows/add-backlog.md +10 -2
  137. package/gsd-core/workflows/add-phase.md +5 -1
  138. package/gsd-core/workflows/add-tests.md +5 -1
  139. package/gsd-core/workflows/add-todo.md +5 -1
  140. package/gsd-core/workflows/ai-integration-phase.md +5 -1
  141. package/gsd-core/workflows/audit-fix.md +2 -1
  142. package/gsd-core/workflows/audit-milestone.md +21 -9
  143. package/gsd-core/workflows/autonomous/templates/minimal-context.md +41 -0
  144. package/gsd-core/workflows/autonomous.md +52 -64
  145. package/gsd-core/workflows/check-todos.md +5 -1
  146. package/gsd-core/workflows/code-review/steps/structural-pre-pass.md +10 -1
  147. package/gsd-core/workflows/code-review-fix.md +5 -1
  148. package/gsd-core/workflows/code-review.md +66 -8
  149. package/gsd-core/workflows/complete-milestone/detail/elaboration.md +5 -1
  150. package/gsd-core/workflows/complete-milestone/steps/project-md-evolution-review.md +145 -0
  151. package/gsd-core/workflows/complete-milestone/steps/release-map.md +80 -0
  152. package/gsd-core/workflows/complete-milestone.md +64 -157
  153. package/gsd-core/workflows/debug.md +5 -1
  154. package/gsd-core/workflows/diagnose-issues.md +34 -6
  155. package/gsd-core/workflows/discuss-phase/modes/chain.md +5 -1
  156. package/gsd-core/workflows/discuss-phase/modes/power.md +1 -1
  157. package/gsd-core/workflows/discuss-phase-assumptions.md +44 -5
  158. package/gsd-core/workflows/discuss-phase-power.md +19 -2
  159. package/gsd-core/workflows/discuss-phase.md +11 -11
  160. package/gsd-core/workflows/docs-update/detail/elaboration.md +7 -6
  161. package/gsd-core/workflows/docs-update.md +27 -5
  162. package/gsd-core/workflows/edit-phase.md +5 -1
  163. package/gsd-core/workflows/eval-review.md +11 -3
  164. package/gsd-core/workflows/execute-phase/steps/codebase-drift-gate.md +5 -1
  165. package/gsd-core/workflows/execute-phase/steps/per-plan-worktree-gate.md +13 -7
  166. package/gsd-core/workflows/execute-phase/steps/post-merge-gate.md +19 -8
  167. package/gsd-core/workflows/execute-phase/steps/regression-gate-run.md +21 -3
  168. package/gsd-core/workflows/execute-phase/steps/regression-gate.md +18 -1
  169. package/gsd-core/workflows/execute-phase/steps/verify-phase-goal.md +7 -3
  170. package/gsd-core/workflows/execute-phase/steps/wave-post-gate-hooks.md +11 -2
  171. package/gsd-core/workflows/execute-phase.md +46 -11
  172. package/gsd-core/workflows/execute-plan.md +8 -4
  173. package/gsd-core/workflows/extract-learnings.md +6 -2
  174. package/gsd-core/workflows/forensics.md +5 -1
  175. package/gsd-core/workflows/health.md +2 -1
  176. package/gsd-core/workflows/import.md +6 -2
  177. package/gsd-core/workflows/insert-phase.md +5 -1
  178. package/gsd-core/workflows/manager.md +10 -2
  179. package/gsd-core/workflows/map-codebase.md +5 -1
  180. package/gsd-core/workflows/milestone-summary.md +10 -2
  181. package/gsd-core/workflows/new-milestone.md +13 -5
  182. package/gsd-core/workflows/new-project.md +9 -5
  183. package/gsd-core/workflows/next.md +8 -4
  184. package/gsd-core/workflows/pause-work.md +3 -3
  185. package/gsd-core/workflows/plan-phase/steps/auto-advance-check.md +70 -0
  186. package/gsd-core/workflows/plan-phase/steps/chunked-planning-mode.md +7 -3
  187. package/gsd-core/workflows/plan-phase/steps/closed-phase-gate.md +6 -2
  188. package/gsd-core/workflows/plan-phase.md +54 -86
  189. package/gsd-core/workflows/plan-review-convergence.md +17 -5
  190. package/gsd-core/workflows/plant-seed.md +8 -4
  191. package/gsd-core/workflows/progress.md +6 -2
  192. package/gsd-core/workflows/quick/steps/plan-checker-loop.md +1 -1
  193. package/gsd-core/workflows/quick/steps/worktree-pre-dispatch-commit.md +8 -1
  194. package/gsd-core/workflows/quick-batch/steps/plan-checker-loop.md +2 -1
  195. package/gsd-core/workflows/quick-batch.md +5 -1
  196. package/gsd-core/workflows/quick.md +23 -8
  197. package/gsd-core/workflows/remove-phase.md +5 -1
  198. package/gsd-core/workflows/resume-project.md +5 -1
  199. package/gsd-core/workflows/review.md +22 -15
  200. package/gsd-core/workflows/scan.md +5 -1
  201. package/gsd-core/workflows/section-manifest.json +5 -0
  202. package/gsd-core/workflows/secure-phase.md +6 -2
  203. package/gsd-core/workflows/settings.md +4 -3
  204. package/gsd-core/workflows/ship.md +16 -14
  205. package/gsd-core/workflows/spec-phase.md +3 -6
  206. package/gsd-core/workflows/thread.md +10 -2
  207. package/gsd-core/workflows/transition.md +5 -1
  208. package/gsd-core/workflows/ui-phase.md +5 -1
  209. package/gsd-core/workflows/ui-review.md +6 -2
  210. package/gsd-core/workflows/ultraplan-phase.md +5 -1
  211. package/gsd-core/workflows/update.md +5 -1
  212. package/gsd-core/workflows/validate-phase.md +6 -2
  213. package/gsd-core/workflows/verify-work/detail/elaboration.md +1 -1
  214. package/gsd-core/workflows/verify-work/steps/mvp-uat-framing.md +5 -1
  215. package/gsd-core/workflows/verify-work.md +15 -6
  216. package/hooks/dist/gsd-agent-isolation-guard.js +14 -2
  217. package/hooks/dist/gsd-cursor-subagent-start.js +8 -3
  218. package/hooks/dist/gsd-path-export.js +95 -0
  219. package/hooks/dist/gsd-secret-read-guard.js +244 -53
  220. package/hooks/dist/gsd-statusline.js +35 -2
  221. package/hooks/dist/gsd-validate-commit.sh +27 -5
  222. package/hooks/dist/gsd-windsurf-pre-write.js +28 -1
  223. package/hooks/dist/gsd-workflow-guard.js +1 -1
  224. package/hooks/dist/gsd-worktree-path-guard.js +28 -1
  225. package/hooks/dist/lib/isolation-sentinel.js +44 -0
  226. package/hooks/dist/managed-hooks-registry.cjs +1 -0
  227. package/hooks/gsd-agent-isolation-guard.js +14 -2
  228. package/hooks/gsd-cursor-subagent-start.js +8 -3
  229. package/hooks/gsd-path-export.js +95 -0
  230. package/hooks/gsd-secret-read-guard.js +244 -53
  231. package/hooks/gsd-statusline.js +35 -2
  232. package/hooks/gsd-validate-commit.sh +27 -5
  233. package/hooks/gsd-windsurf-pre-write.js +28 -1
  234. package/hooks/gsd-workflow-guard.js +1 -1
  235. package/hooks/gsd-worktree-path-guard.js +28 -1
  236. package/hooks/hooks.json +1 -0
  237. package/hooks/lib/isolation-sentinel.js +44 -0
  238. package/hooks/managed-hooks-registry.cjs +1 -0
  239. package/package.json +13 -6
  240. package/pi/gsd.cjs +1047 -10
  241. package/pi/package-agents/gsd-advisor-researcher.md +113 -0
  242. package/pi/package-agents/gsd-ai-researcher.md +111 -0
  243. package/pi/package-agents/gsd-assumptions-analyzer.md +110 -0
  244. package/pi/package-agents/gsd-code-fixer.md +769 -0
  245. package/pi/package-agents/gsd-code-reviewer.md +402 -0
  246. package/pi/package-agents/gsd-codebase-mapper.md +850 -0
  247. package/pi/package-agents/gsd-debug-session-manager.md +416 -0
  248. package/pi/package-agents/gsd-debugger.md +1274 -0
  249. package/pi/package-agents/gsd-doc-classifier.md +269 -0
  250. package/pi/package-agents/gsd-doc-synthesizer.md +259 -0
  251. package/pi/package-agents/gsd-doc-verifier.md +212 -0
  252. package/pi/package-agents/gsd-doc-writer.md +626 -0
  253. package/pi/package-agents/gsd-dom-verifier.md +164 -0
  254. package/pi/package-agents/gsd-domain-researcher.md +144 -0
  255. package/pi/package-agents/gsd-eval-auditor.md +185 -0
  256. package/pi/package-agents/gsd-eval-planner.md +149 -0
  257. package/pi/package-agents/gsd-executor.md +894 -0
  258. package/pi/package-agents/gsd-framework-selector.md +159 -0
  259. package/pi/package-agents/gsd-integration-checker.md +476 -0
  260. package/pi/package-agents/gsd-intel-updater.md +338 -0
  261. package/pi/package-agents/gsd-mempalace-curator.md +49 -0
  262. package/pi/package-agents/gsd-nyquist-auditor.md +200 -0
  263. package/pi/package-agents/gsd-pattern-mapper.md +340 -0
  264. package/pi/package-agents/gsd-phase-researcher.md +897 -0
  265. package/pi/package-agents/gsd-plan-checker.md +1077 -0
  266. package/pi/package-agents/gsd-planner.md +1003 -0
  267. package/pi/package-agents/gsd-project-researcher.md +611 -0
  268. package/pi/package-agents/gsd-research-synthesizer.md +259 -0
  269. package/pi/package-agents/gsd-roadmapper.md +779 -0
  270. package/pi/package-agents/gsd-security-auditor.md +171 -0
  271. package/pi/package-agents/gsd-ui-auditor.md +581 -0
  272. package/pi/package-agents/gsd-ui-checker.md +417 -0
  273. package/pi/package-agents/gsd-ui-researcher.md +469 -0
  274. package/pi/package-agents/gsd-user-profiler.md +174 -0
  275. package/pi/package-agents/gsd-verifier.md +963 -0
  276. package/pi/package-agents/manifest.json +39 -0
  277. package/pi/package-skills/gsd-add-tests/SKILL.md +39 -0
  278. package/pi/package-skills/gsd-ai-integration-phase/SKILL.md +36 -0
  279. package/pi/package-skills/gsd-audit-fix/SKILL.md +34 -0
  280. package/pi/package-skills/gsd-audit-milestone/SKILL.md +40 -0
  281. package/pi/package-skills/gsd-audit-uat/SKILL.md +26 -0
  282. package/pi/package-skills/gsd-autonomous/SKILL.md +52 -0
  283. package/pi/package-skills/gsd-capture/SKILL.md +68 -0
  284. package/pi/package-skills/gsd-cleanup/SKILL.md +25 -0
  285. package/pi/package-skills/gsd-code-review/SKILL.md +71 -0
  286. package/pi/package-skills/gsd-complete-milestone/SKILL.md +147 -0
  287. package/pi/package-skills/gsd-config/SKILL.md +60 -0
  288. package/pi/package-skills/gsd-debug/SKILL.md +64 -0
  289. package/pi/package-skills/gsd-discuss-phase/SKILL.md +74 -0
  290. package/pi/package-skills/gsd-docs-update/SKILL.md +50 -0
  291. package/pi/package-skills/gsd-eval-review/SKILL.md +35 -0
  292. package/pi/package-skills/gsd-execute-phase/SKILL.md +62 -0
  293. package/pi/package-skills/gsd-explore/SKILL.md +26 -0
  294. package/pi/package-skills/gsd-extract-learnings/SKILL.md +25 -0
  295. package/pi/package-skills/gsd-fast/SKILL.md +33 -0
  296. package/pi/package-skills/gsd-forensics/SKILL.md +59 -0
  297. package/pi/package-skills/gsd-graphify/SKILL.md +210 -0
  298. package/pi/package-skills/gsd-health/SKILL.md +36 -0
  299. package/pi/package-skills/gsd-help/SKILL.md +37 -0
  300. package/pi/package-skills/gsd-import/SKILL.md +46 -0
  301. package/pi/package-skills/gsd-inbox/SKILL.md +42 -0
  302. package/pi/package-skills/gsd-ingest-docs/SKILL.md +43 -0
  303. package/pi/package-skills/gsd-manager/SKILL.md +46 -0
  304. package/pi/package-skills/gsd-map-codebase/SKILL.md +85 -0
  305. package/pi/package-skills/gsd-mempalace-capture/SKILL.md +111 -0
  306. package/pi/package-skills/gsd-mempalace-recall/SKILL.md +111 -0
  307. package/pi/package-skills/gsd-milestone-summary/SKILL.md +54 -0
  308. package/pi/package-skills/gsd-mvp-phase/SKILL.md +45 -0
  309. package/pi/package-skills/gsd-new-milestone/SKILL.md +49 -0
  310. package/pi/package-skills/gsd-new-project/SKILL.md +49 -0
  311. package/pi/package-skills/gsd-next/SKILL.md +34 -0
  312. package/pi/package-skills/gsd-ns-context/SKILL.md +31 -0
  313. package/pi/package-skills/gsd-ns-ideate/SKILL.md +30 -0
  314. package/pi/package-skills/gsd-ns-manage/SKILL.md +42 -0
  315. package/pi/package-skills/gsd-ns-project/SKILL.md +34 -0
  316. package/pi/package-skills/gsd-ns-review/SKILL.md +35 -0
  317. package/pi/package-skills/gsd-ns-workflow/SKILL.md +44 -0
  318. package/pi/package-skills/gsd-onboard/SKILL.md +46 -0
  319. package/pi/package-skills/gsd-pause-work/SKILL.md +48 -0
  320. package/pi/package-skills/gsd-phase/SKILL.md +61 -0
  321. package/pi/package-skills/gsd-plan-phase/SKILL.md +62 -0
  322. package/pi/package-skills/gsd-plan-review-convergence/SKILL.md +63 -0
  323. package/pi/package-skills/gsd-pr-branch/SKILL.md +31 -0
  324. package/pi/package-skills/gsd-profile-user/SKILL.md +48 -0
  325. package/pi/package-skills/gsd-progress/SKILL.md +51 -0
  326. package/pi/package-skills/gsd-quick/SKILL.md +179 -0
  327. package/pi/package-skills/gsd-quick-batch/SKILL.md +114 -0
  328. package/pi/package-skills/gsd-resume-work/SKILL.md +31 -0
  329. package/pi/package-skills/gsd-review/SKILL.md +50 -0
  330. package/pi/package-skills/gsd-review-backlog/SKILL.md +65 -0
  331. package/pi/package-skills/gsd-secure-phase/SKILL.md +37 -0
  332. package/pi/package-skills/gsd-settings/SKILL.md +30 -0
  333. package/pi/package-skills/gsd-ship/SKILL.md +27 -0
  334. package/pi/package-skills/gsd-sketch/SKILL.md +56 -0
  335. package/pi/package-skills/gsd-spec-phase/SKILL.md +63 -0
  336. package/pi/package-skills/gsd-spike/SKILL.md +53 -0
  337. package/pi/package-skills/gsd-stats/SKILL.md +22 -0
  338. package/pi/package-skills/gsd-surface/SKILL.md +177 -0
  339. package/pi/package-skills/gsd-thread/SKILL.md +29 -0
  340. package/pi/package-skills/gsd-ui-phase/SKILL.md +35 -0
  341. package/pi/package-skills/gsd-ui-review/SKILL.md +35 -0
  342. package/pi/package-skills/gsd-ultraplan-phase/SKILL.md +38 -0
  343. package/pi/package-skills/gsd-undo/SKILL.md +38 -0
  344. package/pi/package-skills/gsd-update/SKILL.md +51 -0
  345. package/pi/package-skills/gsd-validate-phase/SKILL.md +37 -0
  346. package/pi/package-skills/gsd-verify-work/SKILL.md +41 -0
  347. package/pi/package-skills/gsd-workspace/SKILL.md +57 -0
  348. package/pi/package-skills/gsd-workstreams/SKILL.md +73 -0
  349. package/scripts/audit-workflow-script-paths.cjs +1 -1
  350. package/scripts/build-hooks.js +4 -0
  351. package/scripts/command-contract-helpers.cjs +1 -1
  352. package/scripts/diagnose-windows-spawn-watcher.cjs +317 -0
  353. package/scripts/docs-guard-registry.cjs +22 -7
  354. package/scripts/gen-health-docs.cjs +13 -1
  355. package/scripts/gen-pi-package-skills.cjs +217 -0
  356. package/scripts/lib/macos-conformance-tier.generated.cjs +19 -0
  357. package/scripts/lib/platform-conformance-tier.generated.cjs +41 -0
  358. package/scripts/lint-docs-guard-registration.exempt-baseline.cjs +3 -3
  359. package/scripts/lint-phase-arg-assignment.cjs +2 -2
  360. package/scripts/lint-retired-runtime-name.cjs +5 -0
  361. package/scripts/lint-workflow-shellcheck-baseline.json +16 -16
  362. package/scripts/npm-audit-baseline.cjs +41 -7
  363. package/scripts/release-tarball-smoke.cjs +1 -0
  364. package/scripts/reviewed-or-maintainer-policy.cjs +181 -0
  365. package/scripts/select-docs-guards.cjs +1 -1
  366. package/scripts/self-test-report-policy.cjs +350 -0
  367. package/skills/gsd-code-review/SKILL.md +8 -0
  368. package/skills/gsd-pause-work/SKILL.md +1 -1
  369. package/vscode/package.json +1 -1
@@ -22,6 +22,8 @@
22
22
  */
23
23
  Object.defineProperty(exports, "__esModule", { value: true });
24
24
  exports.locateProgressTable = locateProgressTable;
25
+ exports.normalizeMilestoneToken = normalizeMilestoneToken;
26
+ exports.milestoneVersionOf = milestoneVersionOf;
25
27
  exports.deriveProgressFromRoadmap = deriveProgressFromRoadmap;
26
28
  exports.clampPercentFromFraction = clampPercentFromFraction;
27
29
  exports.clampPercent = clampPercent;
@@ -68,6 +70,28 @@ function locateProgressTable(roadmapContent) {
68
70
  }
69
71
  return (0, markdown_table_cjs_1.findTableWithColumns)(scoped, ['Phase', 'Plans Complete', 'Status', 'Completed']);
70
72
  }
73
+ /**
74
+ * #5165: a milestone token compared by identity, not spelling — `v1.1`, `V1.1`
75
+ * and ` 1.1 ` name one milestone. A hand-decorated Progress cell
76
+ * (`**v1.1**`, `` `v1.1` ``, `v1.1 (current)`) still names its first
77
+ * whitespace-separated token. Absent/blank tokens normalize to `''`.
78
+ */
79
+ function normalizeMilestoneToken(value) {
80
+ // `[v1.1](ROADMAP.md#v11)` names its link text.
81
+ const unlinked = (milestoneVersionOf(value) ?? '').replace(/\[([^\]]*)\]\([^)]*\)/g, '$1');
82
+ const first = unlinked.split(/\s+/)[0] ?? '';
83
+ return first.replace(/^[*_`]+|[*_`:,;]+$/g, '').replace(/^v/i, '').toLowerCase();
84
+ }
85
+ /**
86
+ * #5165: a STATE.md `milestone:` frontmatter value as a trimmed version string,
87
+ * or null when it is absent, blank or not scalar text.
88
+ */
89
+ function milestoneVersionOf(value) {
90
+ if (typeof value !== 'string' && typeof value !== 'number')
91
+ return null;
92
+ const text = String(value).trim();
93
+ return text === '' ? null : text;
94
+ }
71
95
  /**
72
96
  * Derive completed_phases, total_phases, and total_plans from ROADMAP content.
73
97
  * Root cause fix for issue #4 — see gen-phase-lifecycle.mjs for full documentation.
@@ -82,7 +106,7 @@ function locateProgressTable(roadmapContent) {
82
106
  * 5-column milestone-grouped variant that inserts a `Milestone` column ahead
83
107
  * of them).
84
108
  */
85
- function deriveProgressFromRoadmap(roadmapContent) {
109
+ function deriveProgressFromRoadmap(roadmapContent, milestoneFilter) {
86
110
  let completedPhases = null;
87
111
  let totalPhases = null;
88
112
  let totalPlans = null;
@@ -95,10 +119,19 @@ function deriveProgressFromRoadmap(roadmapContent) {
95
119
  // `RoadmapProgress` contract (nulls = absent) is unchanged.
96
120
  const table = locateProgressTable(roadmapContent);
97
121
  if (table) {
98
- const allRows = table.rows;
122
+ // #5165: the milestone-grouped Progress table (templates/roadmap.md) keeps
123
+ // every milestone's rows, shipped ones included. When the caller names the
124
+ // milestone and the table carries a `Milestone` column, count only that
125
+ // milestone's rows. A table without the column (or a caller without a
126
+ // milestone) keeps the whole-table behavior; a named milestone that matches
127
+ // no row yields nulls (nothing countable) rather than a foreign count.
128
+ const wantMilestone = normalizeMilestoneToken(milestoneFilter);
129
+ const scopedRows = wantMilestone !== '' && table.columns.includes('Milestone')
130
+ ? table.rows.filter((r) => normalizeMilestoneToken(r['Milestone']) === wantMilestone)
131
+ : table.rows;
99
132
  // Data rows only (exclude sentinel phases 0 and 999.x).
100
133
  // #3185: canonical sentinel predicate (SENTINEL_RANGES [0,999]) — this was a local 999-only literal that admitted Phase 0.
101
- const dataRows = allRows.filter((r) => {
134
+ const dataRows = scopedRows.filter((r) => {
102
135
  const phase = (r['Phase'] ?? '').trim();
103
136
  return /^\d/.test(phase) && !isSentinelPhaseId(phase);
104
137
  });
@@ -110,7 +143,7 @@ function deriveProgressFromRoadmap(roadmapContent) {
110
143
  const completed = dataRows.filter((r) => (0, phase_status_cjs_1.parseRoadmapStatusCell)(r['Status']) === phase_status_cjs_1.WIRE_STATUS.COMPLETE).length;
111
144
  completedPhases = completed > 0 ? completed : null;
112
145
  let totalPlansSum = 0;
113
- for (const r of allRows) {
146
+ for (const r of scopedRows) {
114
147
  const cell = (r['Plans Complete'] ?? '').trim();
115
148
  const m = /(\d+)\s*\/\s*(\d+)/.exec(cell);
116
149
  if (m)
@@ -54,13 +54,13 @@ const planningScopeMod = require("./planning-scope.cjs");
54
54
  const { SCOPE } = planningScopeMod;
55
55
  // eslint-disable-next-line @typescript-eslint/no-require-imports -- roadmap-parser.cjs is an export= CommonJS module
56
56
  const roadmapParserMod = require("./roadmap-parser.cjs");
57
- const { stripShippedMilestones, extractCurrentMilestone, currentMilestoneRawRanges, withPhaseSection, findMilestoneScopeHeadingLines } = roadmapParserMod;
57
+ const { stripShippedMilestones, extractCurrentMilestone, currentMilestoneRawRanges, withPhaseSection, editOutsideShippedBlocks, findMilestoneScopeHeadingLines } = roadmapParserMod;
58
58
  // #4129: the single owner of "count the ROADMAP's milestone Complete rows"
59
59
  // (pure computation, no I/O — no cycle on this path) for the intent-first
60
60
  // progress counters the phase-complete transaction passes downstream.
61
61
  // eslint-disable-next-line @typescript-eslint/no-require-imports -- phase-lifecycle.cjs is an export= CommonJS module
62
62
  const phaseLifecycleMod = require("./phase-lifecycle.cjs");
63
- const { deriveProgressFromRoadmap: deriveProgressFromRoadmapForIntent, clampPercent: clampPercentForIntent } = phaseLifecycleMod;
63
+ const { deriveProgressFromRoadmap: deriveProgressFromRoadmapForIntent, clampPercent: clampPercentForIntent, milestoneVersionOf, locateProgressTable, normalizeMilestoneToken } = phaseLifecycleMod;
64
64
  // eslint-disable-next-line @typescript-eslint/no-require-imports -- planning-workspace.cjs is an export= CommonJS module
65
65
  const planningWorkspace = require("./planning-workspace.cjs");
66
66
  // eslint-disable-next-line @typescript-eslint/no-require-imports -- frontmatter.cjs is an export= CommonJS module
@@ -1067,6 +1067,62 @@ function describeGoalShapedTitle(description) {
1067
1067
  return (`description looks goal-shaped, not title-shaped (${reasons}). It was written verbatim ` +
1068
1068
  `as the phase title; consider a short title with the detail moved to **Goal:**.`);
1069
1069
  }
1070
+ /**
1071
+ * Phase numbers the roadmap itself declares — a section header or a roadmap bullet — sentinels
1072
+ * excluded. This is deliberately NOT the allocation set: allocation also counts on-disk directories
1073
+ * and sibling-worktree phases so a new number cannot collide, but a `Depends on:` line has to point
1074
+ * at something a reader of THIS roadmap can find (#5113).
1075
+ */
1076
+ function roadmapDeclaredPhaseNums(rawContent) {
1077
+ const { regex: headerPattern, phaseNumGroup } = buildPhaseHeadingScanRegex(PHASE_HEADING_BASELINE.LABEL_ONLY);
1078
+ const bulletPattern = /^[ \t]*-[ \t]*\[[^\]]{0,200}\][ \t]*\*{0,2}Phase[ \t]+(\d+)(?=[:.\s*]|$)/gim;
1079
+ const nums = new Set();
1080
+ let m;
1081
+ while ((m = headerPattern.exec(rawContent)) !== null) {
1082
+ const num = parseInt(m[phaseNumGroup], 10);
1083
+ if (!isSentinelPhaseId(num))
1084
+ nums.add(num);
1085
+ }
1086
+ while ((m = bulletPattern.exec(rawContent)) !== null) {
1087
+ const num = parseInt(m[1], 10);
1088
+ if (!isSentinelPhaseId(num))
1089
+ nums.add(num);
1090
+ }
1091
+ return [...nums];
1092
+ }
1093
+ /**
1094
+ * The `**Depends on:**` line for a freshly added numeric phase: the nearest lower phase the roadmap
1095
+ * declares, or `Nothing` when it declares none. It was `newId - 1` regardless, which named a phase
1096
+ * that exists only on a sibling branch, as an orphan directory, or not at all when numbering has a
1097
+ * gap (#5113). Custom-named phases carry no dependency line.
1098
+ */
1099
+ function dependsOnLine(newPhaseId, declared) {
1100
+ if (typeof newPhaseId !== 'number')
1101
+ return '';
1102
+ let nearest = null;
1103
+ for (const n of declared)
1104
+ if (n < newPhaseId && (nearest === null || n > nearest))
1105
+ nearest = n;
1106
+ return `\n**Depends on:** ${nearest === null ? 'Nothing' : `Phase ${nearest}`}`;
1107
+ }
1108
+ /**
1109
+ * Splice a phase entry into `rawContent` at `at`, changing nothing else: the blank lines around the
1110
+ * block and its line endings are the block's own, so the surrounding document is published byte for
1111
+ * byte (#5113). `block` is LF text that starts and ends with a newline.
1112
+ */
1113
+ function spliceRoadmapBlock(rawContent, atOffset, block) {
1114
+ const eol = rawContent.includes('\r\n') ? '\r\n' : '\n';
1115
+ // The insertion offset is found by searching for '\n---', which lands between the CR and the LF of
1116
+ // a CRLF pair; splice before the pair instead of through it.
1117
+ const at = rawContent[atOffset - 1] === '\r' && rawContent[atOffset] === '\n' ? atOffset - 1 : atOffset;
1118
+ const before = rawContent.slice(0, at);
1119
+ const after = rawContent.slice(at);
1120
+ const body = block.replace(/^\n+/, '').replace(/\n+$/, '\n').replace(/\n/g, eol);
1121
+ const blankBefore = before === '' || /(?:\r?\n){2}$/.test(before);
1122
+ const lead = blankBefore ? '' : /\n$/.test(before) ? eol : eol + eol;
1123
+ const tail = after === '' || /^\r?\n/.test(after) ? '' : eol;
1124
+ return before + lead + body + tail + after;
1125
+ }
1070
1126
  /**
1071
1127
  * #3163: compute the byte offset in `rawContent` where a new `### Phase N:`
1072
1128
  * entry should be inserted — at the end of the active phase list, scoped to the
@@ -1169,7 +1225,7 @@ function collectSiblingWorktreePhaseNums(cwd, used) {
1169
1225
  timeout: 10_000,
1170
1226
  windowsHide: true,
1171
1227
  maxBuffer: 4 * 1024 * 1024,
1172
- });
1228
+ }); // allow-index-refresh: lists worktrees from .git metadata, never opens the index
1173
1229
  }
1174
1230
  catch {
1175
1231
  return; // not a git repo / git unavailable — unchanged behavior
@@ -1310,13 +1366,11 @@ function cmdPhaseAdd(cwd, description, raw, customId) {
1310
1366
  const dirPath = node_path_1.default.join(planningDir(cwd), 'phases', _dirName);
1311
1367
  (0, shell_command_projection_cjs_1.platformEnsureDir)(dirPath);
1312
1368
  (0, shell_command_projection_cjs_1.platformWriteSync)(node_path_1.default.join(dirPath, '.gitkeep'), '');
1313
- const dependsOn = config.phase_naming === 'custom'
1314
- ? ''
1315
- : `\n**Depends on:** Phase ${typeof _newPhaseId === 'number' ? _newPhaseId - 1 : 'TBD'}`;
1369
+ const dependsOn = config.phase_naming === 'custom' ? '' : dependsOnLine(_newPhaseId, roadmapDeclaredPhaseNums(rawContent));
1316
1370
  const phaseEntry = `\n### Phase ${_newPhaseId}: ${description}\n\n**Goal:** [To be planned]\n**Requirements**: TBD${dependsOn}\n**Plans:** 0 plans\n\nPlans:\n- [ ] TBD (run ${(0, runtime_slash_cjs_1.formatGsdSlash)('plan-phase', (0, runtime_slash_cjs_1.resolveRuntime)(cwd))} ${_newPhaseId} to break down)\n`;
1317
1371
  const insertAt = phaseEntryInsertOffset(rawContent, cwd);
1318
- const updatedContent = rawContent.slice(0, insertAt) + phaseEntry + rawContent.slice(insertAt);
1319
- (0, shell_command_projection_cjs_1.platformWriteSync)(roadmapPath, updatedContent);
1372
+ const updatedContent = spliceRoadmapBlock(rawContent, insertAt, phaseEntry);
1373
+ (0, shell_command_projection_cjs_1.platformWriteSync)(roadmapPath, updatedContent, { verbatim: true });
1320
1374
  return { newPhaseId: _newPhaseId, dirName: _dirName };
1321
1375
  });
1322
1376
  const titleWarning = describeGoalShapedTitle(description);
@@ -1432,12 +1486,10 @@ function cmdPhaseAddBatch(cwd, descriptions, raw) {
1432
1486
  const dirPath = node_path_1.default.join(planningDir(cwd), 'phases', dirName);
1433
1487
  (0, shell_command_projection_cjs_1.platformEnsureDir)(dirPath);
1434
1488
  (0, shell_command_projection_cjs_1.platformWriteSync)(node_path_1.default.join(dirPath, '.gitkeep'), '');
1435
- const dependsOn = config.phase_naming === 'custom'
1436
- ? ''
1437
- : `\n**Depends on:** Phase ${typeof newPhaseId === 'number' ? newPhaseId - 1 : 'TBD'}`;
1489
+ const dependsOn = config.phase_naming === 'custom' ? '' : dependsOnLine(newPhaseId, roadmapDeclaredPhaseNums(rawContent));
1438
1490
  const phaseEntry = `\n### Phase ${newPhaseId}: ${description}\n\n**Goal:** [To be planned]\n**Requirements**: TBD${dependsOn}\n**Plans:** 0 plans\n\nPlans:\n- [ ] TBD (run ${(0, runtime_slash_cjs_1.formatGsdSlash)('plan-phase', (0, runtime_slash_cjs_1.resolveRuntime)(cwd))} ${newPhaseId} to break down)\n`;
1439
1491
  const insertAt = phaseEntryInsertOffset(rawContent, cwd);
1440
- rawContent = rawContent.slice(0, insertAt) + phaseEntry + rawContent.slice(insertAt);
1492
+ rawContent = spliceRoadmapBlock(rawContent, insertAt, phaseEntry);
1441
1493
  added.push({
1442
1494
  phase_number: typeof newPhaseId === 'number' ? newPhaseId : String(newPhaseId),
1443
1495
  padded: typeof newPhaseId === 'number' ? String(newPhaseId).padStart(2, '0') : String(newPhaseId),
@@ -1447,7 +1499,7 @@ function cmdPhaseAddBatch(cwd, descriptions, raw) {
1447
1499
  naming_mode: config.phase_naming,
1448
1500
  });
1449
1501
  }
1450
- (0, shell_command_projection_cjs_1.platformWriteSync)(roadmapPath, rawContent);
1502
+ (0, shell_command_projection_cjs_1.platformWriteSync)(roadmapPath, rawContent, { verbatim: true });
1451
1503
  return added;
1452
1504
  });
1453
1505
  output({ phases: results, count: results.length }, raw);
@@ -1525,6 +1577,21 @@ function scanExistingDecimalPhaseNumbers(phasesDir, rawContent, base) {
1525
1577
  }
1526
1578
  return decimalSet;
1527
1579
  }
1580
+ /**
1581
+ * #5599: true when `token` (a heading/bullet phase number such as "13.1.2") is
1582
+ * a STRICT descendant of `ancestor` ("13"). Compared segment-wise so "130" is
1583
+ * not inside "13", and digit segments compare numerically so a zero-padded
1584
+ * "05.1" matches "5".
1585
+ */
1586
+ function isPhaseTokenWithin(token, ancestor) {
1587
+ const t = token.split('.');
1588
+ const a = ancestor.split('.');
1589
+ if (t.length <= a.length)
1590
+ return false;
1591
+ // Zero padding is not significant ("05" = "5", "03A" = "3A"); case is not either.
1592
+ const canon = (segment) => segment.replace(/^0+(?=\d)/, '').toLowerCase();
1593
+ return a.every((segment, i) => canon(segment) === canon(t[i]));
1594
+ }
1528
1595
  function cmdPhaseInsert(cwd, afterPhase, description, raw, allocation = 'nested') {
1529
1596
  if (!afterPhase || !description) {
1530
1597
  error('after-phase and description required for phase insert');
@@ -1586,6 +1653,11 @@ function cmdPhaseInsert(cwd, afterPhase, description, raw, allocation = 'nested'
1586
1653
  const siblingNextDecimal = siblingDecimalSet.size === 0 ? 1 : Math.max(...siblingDecimalSet) + 1;
1587
1654
  _decimalPhase = `${parentBase}.${siblingNextDecimal}`;
1588
1655
  }
1656
+ // #5599: the family whose subtree the new entry must land at the end of —
1657
+ // the parent for a sibling allocation, the target itself for a nested one.
1658
+ const subtreeRoot = allocation === 'sibling' && lastDotIndex !== -1
1659
+ ? normalizedBase.slice(0, lastDotIndex)
1660
+ : normalizedBase;
1589
1661
  const insertConfig = loadConfig(cwd);
1590
1662
  const projectCode = insertConfig.project_code || '';
1591
1663
  const pfx = projectCode ? `${projectCode}-` : '';
@@ -1623,13 +1695,17 @@ function cmdPhaseInsert(cwd, afterPhase, description, raw, allocation = 'nested'
1623
1695
  }
1624
1696
  const bulletLineEnd = rawContent.indexOf(bulletMatchResult[0]) + bulletMatchResult[0].length;
1625
1697
  const afterBullet = rawContent.slice(bulletLineEnd);
1626
- const nextBulletMatch = afterBullet.match(/\r?\n-\s*\[[ x]\]\s*(?:\*\*)?Phase\s+\d/i);
1627
- let insertIdx;
1628
- if (nextBulletMatch) {
1629
- insertIdx = bulletLineEnd + nextBulletMatch.index;
1630
- }
1631
- else {
1632
- insertIdx = bulletLineEnd;
1698
+ // #5599: land after the last bullet inside the allocation family's
1699
+ // subtree, not before the first following bullet of any number (which is
1700
+ // the target's own later decimal child when one exists).
1701
+ const bulletRe = /\r?\n-\s*\[[ x]\]\s*(?:\*\*)?Phase\s+(?<tok>\d[^\s:*]*)[^\r\n]*/gi;
1702
+ let insertIdx = bulletLineEnd;
1703
+ for (const m of afterBullet.matchAll(bulletRe)) {
1704
+ if (!isPhaseTokenWithin(m.groups.tok, subtreeRoot)) {
1705
+ insertIdx = bulletLineEnd + m.index;
1706
+ break;
1707
+ }
1708
+ insertIdx = bulletLineEnd + m.index + m[0].length;
1633
1709
  }
1634
1710
  updatedContent =
1635
1711
  rawContent.slice(0, insertIdx) + bulletEntry + rawContent.slice(insertIdx);
@@ -1671,13 +1747,19 @@ function cmdPhaseInsert(cwd, afterPhase, description, raw, allocation = 'nested'
1671
1747
  // preserved exactly as the prior literal had it — a pre-existing,
1672
1748
  // deliberately-unchanged asymmetry with the other sites in this file.
1673
1749
  const nextPhaseHeadingPrefix = `#{2,4}\\s+${phaseHeadingPrefixSrcFor(PHASE_HEADING_BASELINE.LABEL_ONLY, insertConvention)}`;
1674
- const nextPhaseMatch = afterHeader.match(new RegExp(`\\r?\\n${nextPhaseHeadingPrefix}\\d[\\d.]*`, 'i'));
1675
- let insertIdx;
1676
- if (nextPhaseMatch) {
1677
- insertIdx = headerIdx + headerMatch[0].length + nextPhaseMatch.index;
1678
- }
1679
- else {
1680
- insertIdx = rawContent.length;
1750
+ // #5599: the boundary is the first following heading OUTSIDE the
1751
+ // allocation family's subtree. Taking the first heading of any number
1752
+ // spliced the new section above the target's own later decimal children
1753
+ // (13, 13.2, 13.1, 14); the allocator already guarantees the new number
1754
+ // exceeds every existing descendant, so "after the last descendant" is
1755
+ // the numerically correct position.
1756
+ const nextPhaseRe = new RegExp(`\\r?\\n${nextPhaseHeadingPrefix}(?<tok>\\d[^\\s:]*)`, 'gi');
1757
+ let insertIdx = rawContent.length;
1758
+ for (const m of afterHeader.matchAll(nextPhaseRe)) {
1759
+ if (!isPhaseTokenWithin(m.groups.tok, subtreeRoot)) {
1760
+ insertIdx = headerIdx + headerMatch[0].length + m.index;
1761
+ break;
1762
+ }
1681
1763
  }
1682
1764
  updatedContent =
1683
1765
  rawContent.slice(0, insertIdx) + phaseEntry + rawContent.slice(insertIdx);
@@ -3061,6 +3143,42 @@ function formatRequirementsLineWarning(phaseNum, rawLine, analysis) {
3061
3143
  oversized,
3062
3144
  };
3063
3145
  }
3146
+ /**
3147
+ * #4972: advisory gaps for the phase artifacts that active `verify:post` steps
3148
+ * declared `onError: "halt"` and `produces` (SECURITY.md, VALIDATION.md).
3149
+ * Missing file, an unresolved `status: draft`, or (SECURITY.md) `threats_open`
3150
+ * above zero. Any other `status` spelling counts as closed — the audit skills
3151
+ * do not pin one value, so this does not invent a vocabulary.
3152
+ */
3153
+ function auditArtifactWarnings(cwd, phaseFullDir) {
3154
+ // eslint-disable-next-line @typescript-eslint/no-require-imports -- lazy: loop-resolver pulls in the capability registry
3155
+ const loopResolver = require('./loop-resolver.cjs');
3156
+ const out = [];
3157
+ const files = node_fs_1.default.readdirSync(phaseFullDir);
3158
+ for (const hook of loopResolver.resolveActiveHooksForPoint(cwd, 'verify:post').activeHooks) {
3159
+ if (hook.kind !== 'step' || hook.onError !== 'halt')
3160
+ continue;
3161
+ for (const artifact of hook.produces ?? []) {
3162
+ if (!/^[A-Z][A-Z0-9-]*\.md$/.test(artifact))
3163
+ continue;
3164
+ const skill = hook.ref?.skill ?? hook.capId;
3165
+ const found = files.filter((f) => f === artifact || f.endsWith(`-${artifact}`)).sort()[0];
3166
+ if (!found) {
3167
+ out.push(`${artifact}: missing — the ${hook.capId} audit (/gsd:${skill}) never ran for this phase, and a verify:post step cannot block the close (#4972)`);
3168
+ continue;
3169
+ }
3170
+ const fm = frontmatterMod.extractFrontmatter(node_fs_1.default.readFileSync(node_path_1.default.join(phaseFullDir, found), 'utf-8'));
3171
+ if (typeof fm['status'] === 'string' && fm['status'].trim().toLowerCase() === 'draft') {
3172
+ out.push(`${found}: status is still draft — the ${hook.capId} audit (/gsd:${skill}) was not completed (#4972)`);
3173
+ }
3174
+ const open = Number(fm['threats_open']);
3175
+ if (artifact === 'SECURITY.md' && Number.isFinite(open) && open > 0) {
3176
+ out.push(`${found}: ${open} threat(s) still open (#4972)`);
3177
+ }
3178
+ }
3179
+ }
3180
+ return out;
3181
+ }
3064
3182
  function cmdPhaseComplete(cwd, phaseNum, raw) {
3065
3183
  if (!phaseNum) {
3066
3184
  error('phase number required for phase complete');
@@ -3274,6 +3392,23 @@ function cmdPhaseComplete(cwd, phaseNum, raw) {
3274
3392
  /* best-effort, same posture as the #2245 pre-scan above: an unreadable
3275
3393
  * SUMMARY means one fewer advisory this run, never a blocked completion. */
3276
3394
  }
3395
+ // #4972: a `verify:post` STEP declared `onError: "halt"` that produces a
3396
+ // phase artifact (security -> SECURITY.md, nyquist -> VALIDATION.md) reads as
3397
+ // a blocking precondition, but a step is advisory by construction (ADR-894
3398
+ // "steps are additive, gates block") and the workflow only prints a line the
3399
+ // operator can ignore — so a phase could close with the audit never run. This
3400
+ // verb is the one place every close passes through, so it names the gap on
3401
+ // the same ADVISORY `warnings[]` channel as the #2572 check above. Advisory,
3402
+ // not a refusal: both capabilities are on by default, so a hard block here
3403
+ // would stop every existing project's next phase close; the security
3404
+ // threats-open gate at `ship:pre` is the enforced one and is untouched.
3405
+ try {
3406
+ warnings.push(...auditArtifactWarnings(cwd, phaseFullDir));
3407
+ }
3408
+ catch {
3409
+ /* best-effort, same posture as the advisories above: an unresolvable hook
3410
+ * set or unreadable artifact means one fewer advisory, never a blocked close. */
3411
+ }
3277
3412
  let nextPhaseNum = null;
3278
3413
  let nextPhaseName = null;
3279
3414
  let isLastPhase = true;
@@ -3346,6 +3481,10 @@ function cmdPhaseComplete(cwd, phaseNum, raw) {
3346
3481
  const runPhaseCompleteTransaction = () => {
3347
3482
  const writes = [];
3348
3483
  let roadmapContent = null;
3484
+ // #5165: the phase was ALREADY complete in the ROADMAP this transaction
3485
+ // started from (read before any mutation below): its checklist box was
3486
+ // checked, or its Progress-table row read Complete.
3487
+ let phaseWasAlreadyComplete = false;
3349
3488
  if (node_fs_1.default.existsSync(roadmapPath)) {
3350
3489
  const originalRoadmapContent = node_fs_1.default.readFileSync(roadmapPath, 'utf-8');
3351
3490
  roadmapContent = originalRoadmapContent;
@@ -3383,6 +3522,12 @@ function cmdPhaseComplete(cwd, phaseNum, raw) {
3383
3522
  // its own `- [ ] Phase N: …` entries inside a fenced code block, but it
3384
3523
  // is a real (and correct) behavioural difference on pathological input.
3385
3524
  const checkboxPattern = new RegExp(`^[ \\t]*(-\\s*\\[)[ ](\\]\\s*(?:\\*\\*)?\\s*Phase\\s+${phaseEscaped}${OPTIONAL_PHASE_TAG_SOURCE}[:\\s][^\\n]*)`, 'i');
3525
+ // #5165: pre-mutation read of the same checklist line, checked this time.
3526
+ const checkedBoxPattern = new RegExp(`^[ \\t]*(-\\s*\\[)[xX](\\]\\s*(?:\\*\\*)?\\s*Phase\\s+${phaseEscaped}${OPTIONAL_PHASE_TAG_SOURCE}[:\\s][^\\n]*)`);
3527
+ const originalMilestoneScope = extractCurrentMilestone(originalRoadmapContent, cwd);
3528
+ phaseWasAlreadyComplete = originalMilestoneScope
3529
+ .split('\n')
3530
+ .some((line) => checkedBoxPattern.test(line));
3386
3531
  // Progress table row: update Plans Complete/Status/Completed columns BY
3387
3532
  // COLUMN NAME (handles 4- or 5-column RoadmapProgress tables) via the
3388
3533
  // markdown-table seam (ADR-2143 §7) — supersedes the prior ordinal
@@ -3399,6 +3544,22 @@ function cmdPhaseComplete(cwd, phaseNum, raw) {
3399
3544
  const phaseCellRe = new RegExp(`^${phaseEscaped}\\.?(?:\\s|$)`, 'i');
3400
3545
  const rowMatch = (row) => phaseCellRe.test((row['Phase'] ?? '').trim());
3401
3546
  const dateShape = /^\d{4}-\d{2}-\d{2}$/;
3547
+ // #5165: a ROADMAP whose checklist box was never ticked (or has none) but
3548
+ // whose Progress-table row already reads Complete is just as complete. A
3549
+ // milestone-grouped table (a `Milestone` column) is read only for the
3550
+ // milestone ROADMAP binds, so a shipped milestone's same-numbered phase
3551
+ // cannot count.
3552
+ if (!phaseWasAlreadyComplete) {
3553
+ const progressTable = locateProgressTable(originalMilestoneScope);
3554
+ if (progressTable !== null) {
3555
+ const wantMilestone = progressTable.columns.includes('Milestone')
3556
+ ? normalizeMilestoneToken(roadmapParserMod.getMilestoneInfo(cwd).value?.version)
3557
+ : null;
3558
+ phaseWasAlreadyComplete = progressTable.rows.some((row) => rowMatch(row)
3559
+ && (0, phase_status_cjs_1.parseRoadmapStatusCell)(row['Status']) === phase_status_cjs_1.WIRE_STATUS.COMPLETE
3560
+ && (wantMilestone === null || (wantMilestone !== '' && normalizeMilestoneToken(row['Milestone']) === wantMilestone)));
3561
+ }
3562
+ }
3402
3563
  /**
3403
3564
  * Within `text` (already scoped to one milestone window by the
3404
3565
  * caller), scope further to the `## Progress` heading section (up to
@@ -3536,7 +3697,11 @@ function cmdPhaseComplete(cwd, phaseNum, raw) {
3536
3697
  const plansResult = (0, markdown_table_cjs_1.updateTableCell)(text, rowMatch, 'Plans Complete', ` ${summaryCount}/${planCount} `);
3537
3698
  if (plansResult.ok)
3538
3699
  text = plansResult.value;
3539
- const statusResult = (0, markdown_table_cjs_1.updateTableCell)(text, rowMatch, 'Status', ` ${(0, phase_status_cjs_1.toRoadmapStatusCell)(phase_status_cjs_1.PHASE_STATUS.COMPLETE).padEnd(11)} `);
3700
+ // #5165: a Status cell that already reads Complete is left untouched —
3701
+ // re-padding it rewrote a cell the re-run had no business editing.
3702
+ const statusResult = (0, markdown_table_cjs_1.updateTableCell)(text, rowMatch, 'Status', (current) => (0, phase_status_cjs_1.parseRoadmapStatusCell)(current) === phase_status_cjs_1.WIRE_STATUS.COMPLETE
3703
+ ? current
3704
+ : ` ${(0, phase_status_cjs_1.toRoadmapStatusCell)(phase_status_cjs_1.PHASE_STATUS.COMPLETE).padEnd(11)} `);
3540
3705
  if (statusResult.ok)
3541
3706
  text = statusResult.value;
3542
3707
  // Preserve only a valid ISO date (#1161: idempotent; self-heal
@@ -3562,12 +3727,15 @@ function cmdPhaseComplete(cwd, phaseNum, raw) {
3562
3727
  const planId = summaryFile.replace('-SUMMARY.md', '').replace('SUMMARY.md', '');
3563
3728
  if (!planId)
3564
3729
  continue;
3565
- const planEscaped = (0, pattern_cjs_1.escapeRegex)(planId);
3730
+ // #5542: only a summary paired with a live (non-superseded) plan
3731
+ // flips a row — the same pairing rule the counts use.
3732
+ if (!coreUtilsMod.summaryPairsWithAnyPlan(phaseInfo['plans'] ?? [], summaryFile))
3733
+ continue;
3566
3734
  // ADR-5057 §6 (Phase 13, #5217): the per-plan flip is a bullet
3567
3735
  // mutation, so it goes through `updateBullet` like the phase
3568
3736
  // checkbox flip above — first matching bullet only, never a
3569
3737
  // fenced line, never a mid-line occurrence.
3570
- const planCheckboxPattern = new RegExp(`^(\\s*-\\s*\\[) (\\]\\s*(?:\\*\\*)?${planEscaped}(?:\\*\\*)?)`, 'i');
3738
+ const planCheckboxPattern = coreUtilsMod.planRowPattern(planId, { anchored: true });
3571
3739
  b = (0, markdown_sectionizer_cjs_1.updateBullet)(b, (_bulletText, rawLine) => planCheckboxPattern.test(rawLine), (rawLine) => rawLine.replace(planCheckboxPattern, '$1x$2'));
3572
3740
  }
3573
3741
  return b;
@@ -3589,7 +3757,13 @@ function cmdPhaseComplete(cwd, phaseNum, raw) {
3589
3757
  }
3590
3758
  }
3591
3759
  else {
3592
- roadmapContent = mutateMilestonePhase(roadmapContent);
3760
+ // #5543: the active milestone could not be resolved (no STATE
3761
+ // `milestone:`, no marker). Writing the whole document would let
3762
+ // `withPhaseSection` pick the first `### Phase N:` in document order,
3763
+ // which can sit inside a shipped `<details>` block. Hide the shipped
3764
+ // blocks from the mutation and restore them untouched; a ROADMAP with
3765
+ // no shipped block is mutated whole, as before.
3766
+ roadmapContent = editOutsideShippedBlocks(roadmapContent, mutateMilestonePhase);
3593
3767
  }
3594
3768
  writes.push({
3595
3769
  filePath: roadmapPath,
@@ -4183,6 +4357,19 @@ function cmdPhaseComplete(cwd, phaseNum, raw) {
4183
4357
  }
4184
4358
  if (node_fs_1.default.existsSync(statePath)) {
4185
4359
  const originalStateContent = (0, shell_command_projection_cjs_1.platformReadSync)(statePath) || '';
4360
+ // #5165: the milestone STATE asserts (ROADMAP-bound identity first,
4361
+ // else STATE's own frontmatter) scopes the post-completion progress
4362
+ // derivation to that milestone's Progress-table rows.
4363
+ const stateFm = frontmatterMod.extractFrontmatter(originalStateContent, statePath);
4364
+ const storedMilestone = roadmapParserMod.getMilestoneInfo(cwd).value?.version
4365
+ ?? milestoneVersionOf(stateFm['milestone']);
4366
+ // #5165: re-completing a phase that is already complete in ROADMAP.md
4367
+ // while STATE has moved past it is a no-op for STATE's position —
4368
+ // the transition, the metrics row and the authoritative current-phase
4369
+ // pin would all rewind it. A first-time completion (box unchecked, or
4370
+ // STATE not past N, #2028 out-of-order) still transitions.
4371
+ const stateAlreadyPast = phaseWasAlreadyComplete
4372
+ && (0, state_transition_cjs_1.isStatePastPhase)(originalStateContent, phaseNum, statePath);
4186
4373
  let stateContent = originalStateContent;
4187
4374
  // ADR-1769 Phase 3: the STATE.md field-update policy (Current Phase
4188
4375
  // shape/name, Status, Current Plan, Last Activity + Description, and
@@ -4197,8 +4384,10 @@ function cmdPhaseComplete(cwd, phaseNum, raw) {
4197
4384
  // NOT go through readModifyWriteStateMd because STATE.md is
4198
4385
  // committed atomically with ROADMAP/REQUIREMENTS, ADR-3408 §8.3 /
4199
4386
  // #3374 / #3469).
4200
- const nextPhaseDisplayName = phaseDisplayNameFromRoadmap(roadmapContent, nextPhaseNum) ??
4201
- phaseDisplayNameFromSlug(nextPhaseName);
4387
+ const nextPhaseDisplayName = stateAlreadyPast
4388
+ ? null
4389
+ : phaseDisplayNameFromRoadmap(roadmapContent, nextPhaseNum) ??
4390
+ phaseDisplayNameFromSlug(nextPhaseName);
4202
4391
  // #4824: classify the SELECTED next phase from disk (the same ladder
4203
4392
  // roadmap analyze uses) so the Status / Current Plan / Stopped-at
4204
4393
  // handoff does not say "ready to plan" for a phase already planned or
@@ -4219,7 +4408,7 @@ function cmdPhaseComplete(cwd, phaseNum, raw) {
4219
4408
  /* best-effort: an unreadable next phase keeps `Ready to plan` */
4220
4409
  }
4221
4410
  }
4222
- const completeResult = (0, state_transition_cjs_1.transitionCore)(stateContent, {
4411
+ const completeResult = stateAlreadyPast ? { content: stateContent } : (0, state_transition_cjs_1.transitionCore)(stateContent, {
4223
4412
  kind: 'completePhase',
4224
4413
  phaseNum,
4225
4414
  nextPhaseNum,
@@ -4228,13 +4417,22 @@ function cmdPhaseComplete(cwd, phaseNum, raw) {
4228
4417
  planCount,
4229
4418
  summaryCount,
4230
4419
  nextPhaseDisk,
4420
+ // #5165: the body counter and the frontmatter progress below count the
4421
+ // SAME milestone's Progress rows.
4422
+ milestone: storedMilestone,
4231
4423
  }, {
4232
4424
  clock: clock_cjs_1.realClock,
4233
- roadmapProvider: () => roadmapContent,
4425
+ // #4901: the body counter derives from the same current-milestone scope
4426
+ // as its frontmatter twin below (postCompletionRoadmapScope), not the
4427
+ // whole file, so the two writers cannot disagree on which
4428
+ // `## Progress` table they count.
4429
+ roadmapProvider: () => (roadmapContent !== null ? extractCurrentMilestone(roadmapContent, cwd) : null),
4234
4430
  sourcePath: statePath,
4235
4431
  });
4236
4432
  stateContent = completeResult.content;
4237
- stateContent = updatePerformanceMetricsSection(stateContent, cwd, phaseNum, planCount, summaryCount);
4433
+ if (!stateAlreadyPast) {
4434
+ stateContent = updatePerformanceMetricsSection(stateContent, cwd, phaseNum, planCount, summaryCount);
4435
+ }
4238
4436
  // #2736: the transition holds the next phase's exact display name in
4239
4437
  // the intent; pass it as authoritative so the sync's prose
4240
4438
  // re-derivation cannot rewrite current_phase_name to the name's own
@@ -4267,7 +4465,7 @@ function cmdPhaseComplete(cwd, phaseNum, raw) {
4267
4465
  ? extractCurrentMilestone(roadmapContent, cwd)
4268
4466
  : null;
4269
4467
  const postCompletionRoadmapProgress = postCompletionRoadmapScope !== null
4270
- ? deriveProgressFromRoadmapForIntent(postCompletionRoadmapScope)
4468
+ ? deriveProgressFromRoadmapForIntent(postCompletionRoadmapScope, storedMilestone)
4271
4469
  : null;
4272
4470
  const authoritativeProgress = postCompletionRoadmapProgress && postCompletionRoadmapProgress.completedPhases !== null
4273
4471
  ? postCompletionRoadmapProgress.totalPhases !== null && postCompletionRoadmapProgress.totalPhases > 0
@@ -129,7 +129,9 @@ function buildPhaseSnapshot(phasesDir, dir, convention) {
129
129
  dir,
130
130
  complete: completionResult.value.complete,
131
131
  verificationStatus: completionResult.value.verification.status,
132
- verificationStatusError: statusError ? { file: statusError.file, message: statusError.message } : null,
132
+ verificationStatusError: statusError
133
+ ? { file: statusError.file, message: statusError.message, rawStatus: statusError.rawStatus }
134
+ : null,
133
135
  planCount: scanResult.planCount,
134
136
  summaryCount: scanResult.summaryCount,
135
137
  scope: worstScope(completionResult.scope, scanResult.scope),
@@ -630,6 +630,22 @@ function findContextMdIn(absDirOrFiles) {
630
630
  return { file: null, files: [], scope: SCOPE.UNREADABLE };
631
631
  }
632
632
  }
633
+ /**
634
+ * Is `name` the phase's requirements SPEC (#5629)? A plain `NN-SPEC.md`: the `-AI-SPEC.md` (AI design
635
+ * contract) and `-UI-SPEC.md` (UI design contract) variants have other purposes and never lock requirements.
636
+ * The one definition behind the init payloads' `has_spec` / `spec_path`, the discuss and plan-phase workflows
637
+ * (which read those fields) and the context-drift gate, so they cannot disagree about which file is "the SPEC".
638
+ */
639
+ function isPhaseSpecFile(name) {
640
+ return name.endsWith('-SPEC.md') && !name.endsWith('-AI-SPEC.md') && !name.endsWith('-UI-SPEC.md');
641
+ }
642
+ /**
643
+ * The phase SPEC in an already-read directory listing, or `null`. The lexically first match, so the answer
644
+ * does not depend on the filesystem's readdir order. The caller's array is not reordered.
645
+ */
646
+ function findPhaseSpecIn(files) {
647
+ return files.filter(isPhaseSpecFile).sort()[0] ?? null;
648
+ }
633
649
  module.exports = {
634
650
  worktreesOptedOut,
635
651
  readScopedConfigValue,
@@ -655,6 +671,8 @@ module.exports = {
655
671
  diagnoseUnresolvedActiveWorkstream,
656
672
  describeUnresolvedWorkstreamReason,
657
673
  findContextMdIn,
674
+ isPhaseSpecFile,
675
+ findPhaseSpecIn,
658
676
  // Test seam (audit M1): inject a deterministic isPidAlive so the liveness-gated
659
677
  // steal decision is exercised without real pids. Mirrors capability-lock.cts.
660
678
  _setLockProbes(probes) {
@@ -34,6 +34,7 @@ const node_fs_1 = __importDefault(require("node:fs"));
34
34
  const node_path_1 = __importDefault(require("node:path"));
35
35
  const node_crypto_1 = __importDefault(require("node:crypto"));
36
36
  const node_child_process_1 = require("node:child_process");
37
+ const read_only_git_env_cjs_1 = require("./read-only-git-env.cjs");
37
38
  /**
38
39
  * SHA-256 hex digest of a file's raw bytes. Byte-for-byte the same digest
39
40
  * install.js fileHash() records into manifests and backup-meta.json.
@@ -144,13 +145,20 @@ function isCleanRelativePosixPath(relPath) {
144
145
  }
145
146
  function gitExec(gitDir, args) {
146
147
  return (0, node_child_process_1.execFileSync)('git', args, {
148
+ // windowsHide (#685): a console-window flash per git call would spam the
149
+ // user on Windows for what is a background, read-only history walk. Kept
150
+ // high in the options so the completeness guard's 400-char scan finds it.
151
+ windowsHide: true,
147
152
  cwd: gitDir,
148
153
  encoding: 'utf8',
154
+ // #5048: every call here is a history/objects walk that never opens the
155
+ // index, so the variable is inert today — set anyway so a future `status` /
156
+ // `diff` caller inherits it. The block is owned by
157
+ // src/read-only-git-env.cts and shared with every other read-only seam.
158
+ // Parity: tests/git-optional-locks-parity.test.cjs.
159
+ env: (0, read_only_git_env_cjs_1.readOnlyGitEnv)(),
149
160
  timeout: GIT_SUBPROCESS_TIMEOUT_MS,
150
161
  maxBuffer: GIT_MAX_BUFFER_BYTES,
151
- // windowsHide (#685): a console-window flash per git call would spam the
152
- // user on Windows for what is a background, read-only history walk.
153
- windowsHide: true,
154
162
  // stderr is discarded: "file absent in commit" is an expected walk outcome,
155
163
  // not operator-visible diagnostics.
156
164
  stdio: ['ignore', 'pipe', 'ignore'],