@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
@@ -0,0 +1,440 @@
1
+ "use strict";
2
+ /**
3
+ * Milestone Release Tags — #5705 (epic #3628, Phase 1).
4
+ *
5
+ * `gsd-tools milestone release-tags --map <file> [--dry-run]`: preflight and
6
+ * create annotated tags across the repositories listed in `sub_repos` from a
7
+ * caller-supplied map `[{ repo, tag, ref?, message?, skip? }]`.
8
+ *
9
+ * Contract (the result shape is read by Phases 2-3 of the epic, so it is fixed
10
+ * here and pinned by tests):
11
+ *
12
+ * { ok, phase: 'preflight' | 'apply' | 'dry_run',
13
+ * repos: { [key]: { status, tag?, commit?, reason?, error?,
14
+ * existing_commit?, requested_commit?, detached_head? } },
15
+ * created: string[], // "repo:tag" for every tag written
16
+ * errors: [{ repo?, tag?, reason, error? }] }
17
+ *
18
+ * `key` is the normalized repo path. When one repo receives more than one
19
+ * non-skipped entry (two tags), every entry of that repo is keyed `repo:tag`
20
+ * instead, so no result is overwritten.
21
+ *
22
+ * Every entry is preflighted before the first write, so a predictable failure
23
+ * (unknown repo, bad tag name, unresolved ref, conflicting tag) writes nothing
24
+ * anywhere. Tag creation itself is not transactional across repositories: a
25
+ * failure after the first write is reported with the list of tags already
26
+ * written (`created`), never rolled back, and a re-run with the same map
27
+ * reports those tags as `already_tagged` and retries only the failed ones.
28
+ *
29
+ * Nothing is pushed, no existing tag is ever overwritten, and every git call is
30
+ * an argv array run with the target repository as cwd under a 15 s bound.
31
+ */
32
+ var __importDefault = (this && this.__importDefault) || function (mod) {
33
+ return (mod && mod.__esModule) ? mod : { "default": mod };
34
+ };
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.RELEASE_TAG_REASONS = exports.MAX_RELEASE_TAG_ENTRIES = void 0;
37
+ exports.preflightReleaseTags = preflightReleaseTags;
38
+ exports.createReleaseTags = createReleaseTags;
39
+ exports.cmdMilestoneReleaseTags = cmdMilestoneReleaseTags;
40
+ const node_fs_1 = __importDefault(require("node:fs"));
41
+ const node_path_1 = __importDefault(require("node:path"));
42
+ const shell_command_projection_cjs_1 = require("./shell-command-projection.cjs");
43
+ const security_cjs_1 = require("./security.cjs");
44
+ const command_arg_projection_cjs_1 = require("./command-arg-projection.cjs");
45
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
46
+ const ioMod = require("./io.cjs");
47
+ const { output, ERROR_REASON } = ioMod;
48
+ const error = ioMod.error;
49
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
50
+ const configLoaderMod = require("./config-loader.cjs");
51
+ const { loadConfig } = configLoaderMod;
52
+ // ─── Constants ────────────────────────────────────────────────────────────
53
+ /** Maximum entries accepted in one request. Exactly this many are accepted. */
54
+ exports.MAX_RELEASE_TAG_ENTRIES = 256;
55
+ /** Bound on every git subprocess (ms). */
56
+ const RELEASE_TAG_GIT_TIMEOUT_MS = 15000;
57
+ /** Largest `--map` file read from disk (bytes). */
58
+ const MAX_MAP_FILE_BYTES = 1_048_576;
59
+ /** Every reason code this module can emit — a closed set, pinned against CONTEXT.md. */
60
+ exports.RELEASE_TAG_REASONS = Object.freeze([
61
+ 'no_sub_repos',
62
+ 'invalid_request',
63
+ 'too_many_entries',
64
+ 'repo_not_configured',
65
+ 'repo_path_invalid',
66
+ 'repo_missing',
67
+ 'not_a_git_repo',
68
+ 'tag_invalid',
69
+ 'ref_unresolved',
70
+ 'tag_conflict',
71
+ 'duplicate_entry',
72
+ 'git_timeout',
73
+ 'tag_create_failed',
74
+ ]);
75
+ // ─── Helpers ──────────────────────────────────────────────────────────────
76
+ function newResult(phase) {
77
+ // Null prototype: a hostile repo key such as `__proto__` is plain data.
78
+ return { ok: true, phase, repos: Object.create(null), created: [], errors: [] };
79
+ }
80
+ function topLevelError(reason, message) {
81
+ const result = newResult('preflight');
82
+ result.ok = false;
83
+ result.errors.push({ reason, error: message });
84
+ return result;
85
+ }
86
+ function isPlainObject(v) {
87
+ return typeof v === 'object' && v !== null && !Array.isArray(v);
88
+ }
89
+ /**
90
+ * Normalize a repository path to a forward-slash relative form, or null when
91
+ * it is absolute, contains a `..` segment (either separator), a NUL, or is
92
+ * empty. Backslashes are converted FIRST so `a\..\b` cannot slip past.
93
+ */
94
+ function normalizeRepoPath(raw) {
95
+ const s = raw.replace(/\\/g, '/');
96
+ if (s === '' || s.includes('\0') || s.startsWith('/') || /^[A-Za-z]:/.test(s))
97
+ return null;
98
+ const segments = s.split('/');
99
+ if (segments.includes('..'))
100
+ return null;
101
+ const kept = segments.filter((seg) => seg !== '' && seg !== '.');
102
+ return kept.length === 0 ? null : kept.join('/');
103
+ }
104
+ function readSubRepos(cwd) {
105
+ const config = loadConfig(cwd);
106
+ const raw = config['sub_repos'];
107
+ const out = new Set();
108
+ if (!Array.isArray(raw))
109
+ return out;
110
+ for (const item of raw) {
111
+ if (typeof item !== 'string')
112
+ continue;
113
+ const n = normalizeRepoPath(item);
114
+ if (n !== null)
115
+ out.add(n);
116
+ }
117
+ return out;
118
+ }
119
+ /** Validate request shape. Returns a top-level failure, or null when well-formed. */
120
+ function validateRequest(entries) {
121
+ if (!Array.isArray(entries)) {
122
+ return topLevelError('invalid_request', 'request must be a JSON array of entry objects');
123
+ }
124
+ if (entries.length > exports.MAX_RELEASE_TAG_ENTRIES) {
125
+ return topLevelError('too_many_entries', `request has ${entries.length} entries; the limit is ${exports.MAX_RELEASE_TAG_ENTRIES}`);
126
+ }
127
+ const result = newResult('preflight');
128
+ entries.forEach((entry, index) => {
129
+ const where = `entry ${index}`;
130
+ if (!isPlainObject(entry)) {
131
+ result.errors.push({ reason: 'invalid_request', error: `${where}: must be an object` });
132
+ return;
133
+ }
134
+ if (typeof entry['repo'] !== 'string') {
135
+ result.errors.push({ reason: 'invalid_request', error: `${where}: "repo" must be a string` });
136
+ return;
137
+ }
138
+ const repo = entry['repo'];
139
+ if (entry['skip'] === true)
140
+ return;
141
+ if (entry['skip'] !== undefined && typeof entry['skip'] !== 'boolean') {
142
+ result.errors.push({ repo, reason: 'invalid_request', error: `${where}: "skip" must be a boolean` });
143
+ }
144
+ if (typeof entry['tag'] !== 'string') {
145
+ result.errors.push({ repo, reason: 'invalid_request', error: `${where}: "tag" must be a string` });
146
+ }
147
+ for (const field of ['ref', 'message']) {
148
+ if (entry[field] !== undefined && typeof entry[field] !== 'string') {
149
+ result.errors.push({ repo, reason: 'invalid_request', error: `${where}: "${field}" must be a string` });
150
+ }
151
+ }
152
+ });
153
+ if (result.errors.length > 0) {
154
+ result.ok = false;
155
+ return result;
156
+ }
157
+ return null;
158
+ }
159
+ // ─── Preflight ────────────────────────────────────────────────────────────
160
+ function runPreflight(cwd, entries, options, phase) {
161
+ const shape = validateRequest(entries);
162
+ if (shape)
163
+ return { result: shape, plans: [] };
164
+ const list = entries;
165
+ const exec = options.execGit ?? shell_command_projection_cjs_1.execGit;
166
+ const timeout = options.timeoutMs ?? RELEASE_TAG_GIT_TIMEOUT_MS;
167
+ const run = (dir, args) => exec(args, { cwd: dir, timeout });
168
+ const subRepos = readSubRepos(cwd);
169
+ if (subRepos.size === 0) {
170
+ return { result: topLevelError('no_sub_repos', 'no sub_repos configured in .planning/config.json'), plans: [] };
171
+ }
172
+ // A repo that receives more than one non-skipped entry is keyed `repo:tag`.
173
+ const perRepo = new Map();
174
+ for (const entry of list) {
175
+ if (entry.skip === true)
176
+ continue;
177
+ const k = normalizeRepoPath(entry.repo) ?? entry.repo;
178
+ perRepo.set(k, (perRepo.get(k) ?? 0) + 1);
179
+ }
180
+ // A repo given both a skip entry and a tag entry is contradictory; the check is
181
+ // order-independent so preflight never reports `skipped` for a repo that gets tagged.
182
+ const skippedRepos = new Set();
183
+ for (const entry of list) {
184
+ if (entry.skip === true)
185
+ skippedRepos.add(normalizeRepoPath(entry.repo) ?? entry.repo);
186
+ }
187
+ const result = newResult(phase);
188
+ const plans = [];
189
+ const seen = new Set();
190
+ for (const entry of list) {
191
+ const rawRepo = entry.repo;
192
+ const normalized = normalizeRepoPath(rawRepo);
193
+ const repo = normalized ?? rawRepo;
194
+ if (entry.skip === true) {
195
+ const skipKey = `skip\0${repo}`;
196
+ if (seen.has(skipKey)) {
197
+ result.ok = false;
198
+ result.errors.push({ repo, reason: 'duplicate_entry' });
199
+ }
200
+ else {
201
+ seen.add(skipKey);
202
+ // When the repo also has a tag entry, that entry records the duplicate_entry error
203
+ // under the same key; writing `skipped` here would overwrite it order-dependently.
204
+ if ((perRepo.get(repo) ?? 0) === 0)
205
+ result.repos[repo] = { status: 'skipped' };
206
+ }
207
+ continue;
208
+ }
209
+ const tag = entry.tag;
210
+ const key = (perRepo.get(repo) ?? 0) > 1 ? `${repo}:${tag}` : repo;
211
+ const fail = (reason, extra = {}) => {
212
+ result.ok = false;
213
+ result.repos[key] = { status: 'error', tag, reason, ...extra };
214
+ result.errors.push({ repo, tag, reason, ...(extra.error ? { error: extra.error } : {}) });
215
+ };
216
+ if (skippedRepos.has(repo)) {
217
+ fail('duplicate_entry', { error: 'repo has both a skip entry and a tag entry' });
218
+ continue;
219
+ }
220
+ if (normalized === null || (0, security_cjs_1.tryWithinRoot)(normalized, cwd) === null) {
221
+ fail('repo_path_invalid', { error: 'repo path must be relative and stay inside the project root' });
222
+ continue;
223
+ }
224
+ if (!subRepos.has(normalized)) {
225
+ fail('repo_not_configured', { error: 'repo is not listed in sub_repos' });
226
+ continue;
227
+ }
228
+ const pairKey = `${normalized}\0${tag}`;
229
+ if (seen.has(pairKey)) {
230
+ result.ok = false;
231
+ result.errors.push({ repo, tag, reason: 'duplicate_entry' });
232
+ continue;
233
+ }
234
+ seen.add(pairKey);
235
+ const dir = node_path_1.default.join(cwd, normalized);
236
+ let isDir = false;
237
+ try {
238
+ isDir = node_fs_1.default.statSync(dir).isDirectory();
239
+ }
240
+ catch {
241
+ isDir = false;
242
+ }
243
+ if (!isDir) {
244
+ fail('repo_missing', { error: 'repo directory does not exist' });
245
+ continue;
246
+ }
247
+ const top = run(dir, ['rev-parse', '--show-toplevel']);
248
+ if ((0, shell_command_projection_cjs_1.isSpawnTimeout)(top)) {
249
+ fail('git_timeout', { error: 'git rev-parse timed out' });
250
+ continue;
251
+ }
252
+ let isOwnWorkTree = false;
253
+ if (top.exitCode === 0) {
254
+ try {
255
+ // `.native` expands Windows 8.3 short names (RUNNER~1) the way git's long-form path does.
256
+ isOwnWorkTree = node_fs_1.default.realpathSync.native(top.stdout) === node_fs_1.default.realpathSync.native(dir);
257
+ }
258
+ catch {
259
+ isOwnWorkTree = false;
260
+ }
261
+ }
262
+ if (!isOwnWorkTree) {
263
+ fail('not_a_git_repo', { error: 'directory is not the root of a git work tree' });
264
+ continue;
265
+ }
266
+ if (tag === '' || tag.startsWith('-') || tag.includes('\0')) {
267
+ fail('tag_invalid', { error: 'tag name is empty or starts with "-"' });
268
+ continue;
269
+ }
270
+ const fmt = run(dir, ['check-ref-format', `refs/tags/${tag}`]);
271
+ if ((0, shell_command_projection_cjs_1.isSpawnTimeout)(fmt)) {
272
+ fail('git_timeout', { error: 'git check-ref-format timed out' });
273
+ continue;
274
+ }
275
+ if (fmt.exitCode !== 0) {
276
+ fail('tag_invalid', { error: 'tag name rejected by git check-ref-format' });
277
+ continue;
278
+ }
279
+ const refOmitted = entry.ref === undefined;
280
+ const ref = entry.ref ?? 'HEAD';
281
+ if (ref === '' || ref.startsWith('-') || ref.includes('\0')) {
282
+ fail('ref_unresolved', { error: 'ref is empty or starts with "-"' });
283
+ continue;
284
+ }
285
+ const resolved = run(dir, ['rev-parse', '--verify', '--quiet', `${ref}^{commit}`]);
286
+ if ((0, shell_command_projection_cjs_1.isSpawnTimeout)(resolved)) {
287
+ fail('git_timeout', { error: 'git rev-parse timed out' });
288
+ continue;
289
+ }
290
+ if (resolved.exitCode !== 0 || resolved.stdout === '') {
291
+ fail('ref_unresolved', { error: `ref "${ref}" does not resolve to a commit` });
292
+ continue;
293
+ }
294
+ const commit = resolved.stdout;
295
+ let detached;
296
+ if (refOmitted) {
297
+ const head = run(dir, ['symbolic-ref', '-q', 'HEAD']);
298
+ if ((0, shell_command_projection_cjs_1.isSpawnTimeout)(head)) {
299
+ fail('git_timeout', { error: 'git symbolic-ref timed out' });
300
+ continue;
301
+ }
302
+ if (head.exitCode === 1)
303
+ detached = true;
304
+ }
305
+ const existing = run(dir, ['rev-parse', '--verify', '--quiet', `refs/tags/${tag}^{commit}`]);
306
+ if ((0, shell_command_projection_cjs_1.isSpawnTimeout)(existing)) {
307
+ fail('git_timeout', { error: 'git rev-parse timed out' });
308
+ continue;
309
+ }
310
+ if (existing.exitCode === 0 && existing.stdout !== '') {
311
+ if (existing.stdout === commit) {
312
+ result.repos[key] = { status: 'already_tagged', tag, commit, ...(detached ? { detached_head: true } : {}) };
313
+ }
314
+ else {
315
+ fail('tag_conflict', {
316
+ error: 'tag already exists at a different commit',
317
+ existing_commit: existing.stdout,
318
+ requested_commit: commit,
319
+ });
320
+ }
321
+ continue;
322
+ }
323
+ const anyRef = run(dir, ['show-ref', '--verify', '--quiet', `refs/tags/${tag}`]);
324
+ if ((0, shell_command_projection_cjs_1.isSpawnTimeout)(anyRef)) {
325
+ fail('git_timeout', { error: 'git show-ref timed out' });
326
+ continue;
327
+ }
328
+ if (anyRef.exitCode === 0) {
329
+ fail('tag_conflict', {
330
+ error: 'tag already exists and does not point at a commit',
331
+ existing_commit: null,
332
+ requested_commit: commit,
333
+ });
334
+ continue;
335
+ }
336
+ result.repos[key] = { status: 'would_create', tag, commit, ...(detached ? { detached_head: true } : {}) };
337
+ plans.push({
338
+ key,
339
+ repo: normalized,
340
+ dir,
341
+ tag,
342
+ message: entry.message === undefined || entry.message === '' ? tag : entry.message,
343
+ commit,
344
+ ...(detached ? { detached: true } : {}),
345
+ });
346
+ }
347
+ return { result, plans };
348
+ }
349
+ /** Validate a request without writing anything. */
350
+ function preflightReleaseTags(cwd, entries, options = {}) {
351
+ return runPreflight(cwd, entries, options, 'preflight').result;
352
+ }
353
+ /**
354
+ * Preflight, then (unless `dryRun`) create each annotated tag in its own repo.
355
+ * A preflight failure returns before any write. A creation failure does not
356
+ * stop the remaining repos; the result lists every tag already written.
357
+ */
358
+ function createReleaseTags(cwd, entries, options = {}) {
359
+ const dryRun = options.dryRun === true;
360
+ const { result, plans } = runPreflight(cwd, entries, options, dryRun ? 'dry_run' : 'apply');
361
+ if (!result.ok) {
362
+ result.phase = 'preflight';
363
+ return result;
364
+ }
365
+ if (dryRun)
366
+ return result;
367
+ const exec = options.execGit ?? shell_command_projection_cjs_1.execGit;
368
+ const timeout = options.timeoutMs ?? RELEASE_TAG_GIT_TIMEOUT_MS;
369
+ for (const plan of plans) {
370
+ const r = exec(['tag', '-a', '--message', plan.message, '--', plan.tag, plan.commit], { cwd: plan.dir, timeout });
371
+ if (r.exitCode === 0) {
372
+ result.repos[plan.key] = {
373
+ status: 'created',
374
+ tag: plan.tag,
375
+ commit: plan.commit,
376
+ ...(plan.detached ? { detached_head: true } : {}),
377
+ };
378
+ result.created.push(`${plan.repo}:${plan.tag}`);
379
+ continue;
380
+ }
381
+ const reason = (0, shell_command_projection_cjs_1.isSpawnTimeout)(r) ? 'git_timeout' : 'tag_create_failed';
382
+ const message = r.stderr || r.stdout || ((0, shell_command_projection_cjs_1.isSpawnTimeout)(r) ? 'git tag timed out' : 'git tag failed');
383
+ result.ok = false;
384
+ result.repos[plan.key] = { status: 'failed', tag: plan.tag, commit: plan.commit, reason, error: message };
385
+ result.errors.push({ repo: plan.repo, tag: plan.tag, reason, error: message });
386
+ }
387
+ return result;
388
+ }
389
+ // ─── CLI adapter ──────────────────────────────────────────────────────────
390
+ /**
391
+ * `milestone release-tags --map <file> [--dry-run]`. `args` is the full argv
392
+ * tail (`['milestone', 'release-tags', ...flags]`). Prints the result as JSON
393
+ * and sets a non-zero exit code when `ok` is false.
394
+ */
395
+ function cmdMilestoneReleaseTags(cwd, args, raw) {
396
+ const parsed = (0, command_arg_projection_cjs_1.parseNamedArgsOrExit)(args, { valueFlags: ['map'], booleanFlags: ['dry-run'], positionals: 2 }, error);
397
+ const mapArg = parsed['map'];
398
+ if (typeof mapArg !== 'string' || mapArg === '') {
399
+ error('--map <path-to-json-file> required for milestone release-tags', ERROR_REASON.USAGE);
400
+ return;
401
+ }
402
+ // A relative --map is relative to the shell's working directory, not the project root.
403
+ const mapPath = node_path_1.default.resolve(process.cwd(), mapArg);
404
+ let mapStat = null;
405
+ let statError = '';
406
+ try {
407
+ mapStat = node_fs_1.default.statSync(mapPath);
408
+ }
409
+ catch (e) {
410
+ statError = e instanceof Error ? e.message : String(e);
411
+ }
412
+ if (mapStat === null) {
413
+ error(`cannot read --map file: ${statError}`, ERROR_REASON.USAGE);
414
+ return;
415
+ }
416
+ // A FIFO or device reports size 0 and would bypass the size cap on read.
417
+ if (!mapStat.isFile()) {
418
+ error('--map must be a regular file', ERROR_REASON.USAGE);
419
+ return;
420
+ }
421
+ if (mapStat.size > MAX_MAP_FILE_BYTES) {
422
+ error(`--map file exceeds ${MAX_MAP_FILE_BYTES} byte limit (got ${mapStat.size})`, ERROR_REASON.USAGE);
423
+ return;
424
+ }
425
+ let text = '';
426
+ try {
427
+ text = node_fs_1.default.readFileSync(mapPath, 'utf-8');
428
+ }
429
+ catch (e) {
430
+ error(`cannot read --map file: ${e instanceof Error ? e.message : String(e)}`, ERROR_REASON.USAGE);
431
+ return;
432
+ }
433
+ const json = (0, security_cjs_1.safeJsonParse)(text, { maxLength: MAX_MAP_FILE_BYTES, label: '--map file' });
434
+ const result = json.ok
435
+ ? createReleaseTags(cwd, json.value, { dryRun: parsed['dry-run'] === true })
436
+ : topLevelError('invalid_request', json.error);
437
+ output(result, raw);
438
+ if (!result.ok)
439
+ process.exitCode = 1;
440
+ }
@@ -40,6 +40,7 @@ const { publishStateContract } = stateContract;
40
40
  const phaseIdMod = require("./phase-id.cjs");
41
41
  const { normalizePhaseName, matchPhaseDirs, isSentinelPhaseId, isSentinelPhaseDir, buildPhaseHeadingScanRegex, PHASE_HEADING_BASELINE } = phaseIdMod;
42
42
  const pattern_cjs_1 = require("./pattern.cjs");
43
+ const milestone_release_map_cjs_1 = require("./milestone-release-map.cjs");
43
44
  // eslint-disable-next-line @typescript-eslint/no-require-imports
44
45
  const roadmapParserMod = require("./roadmap-parser.cjs");
45
46
  const { getMilestonePhaseFilter, extractCurrentMilestone, getMilestoneInfo, sliceMilestoneWindow, } = roadmapParserMod;
@@ -524,6 +525,41 @@ function cmdMilestoneComplete(cwd, version, options, raw) {
524
525
  const phasesDir = planningPaths(cwd).phases;
525
526
  const today = clock_cjs_1.realClock.localToday();
526
527
  const milestoneName = options.name || version;
528
+ // #5706 (epic #3628): validate the caller-supplied release map BEFORE any
529
+ // read-for-decision or write below, so a bad map leaves the tree untouched
530
+ // (no archive directory, no MILESTONES.md or STATE.md change). An archived
531
+ // map is a recorded value: a differing map for an already-archived version is
532
+ // refused rather than silently overwritten; an identical one is left as is.
533
+ let releaseMap = null;
534
+ let releaseMapFile = null;
535
+ let releaseMapNeedsWrite = false;
536
+ if (typeof options.releaseMap === 'string') {
537
+ const loaded = (0, milestone_release_map_cjs_1.loadReleaseMapFile)(node_path_1.default.resolve(cwd, options.releaseMap));
538
+ if (!loaded.ok) {
539
+ error(`milestone complete: invalid --release-map — nothing has been changed.\n${loaded.errors.map((e) => ` - ${e}`).join('\n')}`);
540
+ }
541
+ else {
542
+ releaseMap = loaded.map;
543
+ releaseMapFile = (0, milestone_release_map_cjs_1.releaseMapArchivePath)(archiveDir, version);
544
+ const serialized = (0, milestone_release_map_cjs_1.serializeReleaseMapArchive)(version, releaseMap);
545
+ let existingText = null;
546
+ try {
547
+ existingText = node_fs_1.default.readFileSync(releaseMapFile, 'utf-8');
548
+ }
549
+ catch (e) {
550
+ if (e.code !== 'ENOENT') {
551
+ error(`milestone complete: ${node_path_1.default.basename(releaseMapFile)} already exists and cannot be read — nothing has been changed.`);
552
+ }
553
+ }
554
+ if (existingText !== null && existingText !== serialized) {
555
+ error(`milestone complete: ${node_path_1.default.basename(releaseMapFile)} already exists with a different release map; the archived map is a recorded value and is not overwritten — nothing has been changed.`);
556
+ }
557
+ releaseMapNeedsWrite = existingText === null;
558
+ }
559
+ }
560
+ const releaseMapReport = releaseMap && releaseMapFile
561
+ ? { path: node_path_1.default.relative(cwd, releaseMapFile).split(node_path_1.default.sep).join('/'), entries: releaseMap.length }
562
+ : null;
527
563
  // ADR-3408 §8.5 / #3469: "liberal but visible" — when the write-seam
528
564
  // composition's preservation stage restores a curated frontmatter value
529
565
  // over a disagreeing freshly-derived one, that divergence is surfaced
@@ -860,6 +896,7 @@ function cmdMilestoneComplete(cwd, version, options, raw) {
860
896
  phases_archive_skipped: phasesArchiveSkippedForScope,
861
897
  phases_archive_skip_reason: phasesArchiveSkipReason,
862
898
  quick: quickDirsToArchive,
899
+ ...(releaseMapReport ? { release_map: { target: releaseMapReport.path, entries: releaseMapReport.entries } } : {}),
863
900
  },
864
901
  would_update: {
865
902
  milestones_md: node_path_1.default.relative(cwd, milestonesPath).split(node_path_1.default.sep).join('/'),
@@ -885,6 +922,10 @@ function cmdMilestoneComplete(cwd, version, options, raw) {
885
922
  // archive directory behind. Reaching this point means the run is
886
923
  // committed to mutating.
887
924
  (0, shell_command_projection_cjs_1.platformEnsureDir)(archiveDir);
925
+ // #5706: archive the validated release map by recorded value.
926
+ if (releaseMap && releaseMapFile && releaseMapNeedsWrite) {
927
+ (0, shell_command_projection_cjs_1.platformWriteSync)(releaseMapFile, (0, milestone_release_map_cjs_1.serializeReleaseMapArchive)(version, releaseMap));
928
+ }
888
929
  // Archive ROADMAP.md
889
930
  if (node_fs_1.default.existsSync(roadmapPath)) {
890
931
  const roadmapContent = node_fs_1.default.readFileSync(roadmapPath, 'utf-8');
@@ -909,7 +950,7 @@ function cmdMilestoneComplete(cwd, version, options, raw) {
909
950
  }
910
951
  // Create/append MILESTONES.md entry
911
952
  const accomplishmentsList = accomplishments.map((a) => `- ${a}`).join('\n');
912
- const milestoneEntry = `## ${version} ${milestoneName} (Shipped: ${today})\n\n**Phases completed:** ${phaseCount} phases, ${totalPlans} plans, ${totalTasks} tasks\n\n**Key accomplishments:**\n${accomplishmentsList || '- (none recorded)'}\n\n---\n\n`;
953
+ const milestoneEntry = `## ${version} ${milestoneName} (Shipped: ${today})\n\n**Phases completed:** ${phaseCount} phases, ${totalPlans} plans, ${totalTasks} tasks\n\n**Key accomplishments:**\n${accomplishmentsList || '- (none recorded)'}\n\n${releaseMap ? `${(0, milestone_release_map_cjs_1.renderReleaseMapMarkdown)(releaseMap)}\n` : ''}---\n\n`;
913
954
  // #3685: mirror requirementsUpdated's diff-tracking contract — the result
914
955
  // below used to report `milestones_updated: true` hardcoded, never
915
956
  // consulting whether the MILESTONES.md write actually changed anything.
@@ -1166,6 +1207,7 @@ function cmdMilestoneComplete(cwd, version, options, raw) {
1166
1207
  milestones_updated: milestonesUpdated,
1167
1208
  state_updated: stateUpdated,
1168
1209
  preservation_warnings: preservationWarnings,
1210
+ ...(releaseMapReport ? { release_map: releaseMapReport } : {}),
1169
1211
  };
1170
1212
  output(result, raw);
1171
1213
  // #3227 (design doc §40 row 26 / "Not-corruption" rule): a refreshed
@@ -245,6 +245,14 @@ exports.EFFORT_ARGV = {
245
245
  supported: new Set(['low', 'medium', 'high', 'xhigh', 'max']),
246
246
  clamp: (level) => (level === 'minimal' ? 'low' : level),
247
247
  },
248
+ // Verified against `pi --help` (pi 1.3.0, #5557): `--thinking <level>` — "Set
249
+ // thinking level: off, minimal, low, medium, high, xhigh, max". `off` is a
250
+ // pi-only level with no universal counterpart, so it is not in `supported`.
251
+ pi: {
252
+ render: (level) => ['--thinking', level],
253
+ supported: new Set(['minimal', 'low', 'medium', 'high', 'xhigh', 'max']),
254
+ clamp: (level) => level,
255
+ },
248
256
  };
249
257
  /**
250
258
  * Clamp a universal effort level to what a host actually accepts, or null.
@@ -180,8 +180,75 @@ function normalizeTestCommand(rawCmd, cwd) {
180
180
  function cmdNormalizeTestCommand(cwd, rawCmd) {
181
181
  process.stdout.write(normalizeTestCommand(rawCmd ?? '', cwd));
182
182
  }
183
+ const PACKAGE_MANAGERS = ['npm', 'pnpm', 'yarn', 'bun'];
184
+ // Lockfile order only matters when a project somehow carries several; the
185
+ // `packageManager` field outranks all of them.
186
+ const LOCKFILE_MANAGERS = [
187
+ ['pnpm-lock.yaml', 'pnpm'],
188
+ ['yarn.lock', 'yarn'],
189
+ ['bun.lock', 'bun'],
190
+ ['bun.lockb', 'bun'],
191
+ ];
192
+ // The script name is spliced into a shell command by the caller, so it is
193
+ // restricted to a plain identifier. Anything else is refused (empty output).
194
+ const SCRIPT_NAME = /^[A-Za-z0-9_][A-Za-z0-9:_-]{0,63}$/;
195
+ // Same bounded-read stance as the rest of this module: a package.json larger
196
+ // than this is not a real manifest, so it is not parsed.
197
+ const MAX_PACKAGE_JSON_BYTES = 1024 * 1024;
198
+ function packageManagerFromField(cwd) {
199
+ try {
200
+ const pkgPath = node_path_1.default.join(cwd, 'package.json');
201
+ const stat = node_fs_1.default.statSync(pkgPath);
202
+ if (!stat.isFile() || stat.size > MAX_PACKAGE_JSON_BYTES)
203
+ return null;
204
+ const pkg = JSON.parse(node_fs_1.default.readFileSync(pkgPath, 'utf-8'));
205
+ if (typeof pkg !== 'object' || pkg === null)
206
+ return null;
207
+ const field = pkg.packageManager;
208
+ if (typeof field !== 'string')
209
+ return null;
210
+ const name = field.split('@')[0];
211
+ return PACKAGE_MANAGERS.includes(name) ? name : null;
212
+ }
213
+ catch {
214
+ return null;
215
+ }
216
+ }
217
+ function detectPackageManager(cwd) {
218
+ const declared = packageManagerFromField(cwd);
219
+ if (declared)
220
+ return declared;
221
+ for (const [lockfile, manager] of LOCKFILE_MANAGERS) {
222
+ try {
223
+ if (node_fs_1.default.statSync(node_path_1.default.join(cwd, lockfile)).isFile())
224
+ return manager;
225
+ }
226
+ catch { /* absent: try the next lockfile */ }
227
+ }
228
+ return 'npm';
229
+ }
230
+ /**
231
+ * Command that runs package script `script` with the project's package manager.
232
+ * `test` uses the manager's short form where it has one; `bun test` is bun's
233
+ * own runner, not the package script, so bun always goes through `bun run`.
234
+ * Returns '' for a script name that is not a plain identifier.
235
+ */
236
+ function resolvePackageManagerCommand(script, cwd) {
237
+ if (typeof script !== 'string' || !SCRIPT_NAME.test(script))
238
+ return '';
239
+ const manager = detectPackageManager(cwd);
240
+ if (script === 'test' && manager !== 'bun')
241
+ return `${manager} test`;
242
+ return `${manager} run ${script}`;
243
+ }
244
+ /** CLI handler for `gsd-tools query resolve-package-manager <script>`. */
245
+ function cmdResolvePackageManager(cwd, script) {
246
+ process.stdout.write(resolvePackageManagerCommand(script ?? '', cwd));
247
+ }
183
248
  module.exports = {
184
249
  normalizeTestCommand,
185
250
  cmdNormalizeTestCommand,
186
251
  isAlreadyOneShot,
252
+ resolvePackageManagerCommand,
253
+ cmdResolvePackageManager,
187
254
  };
@@ -1470,13 +1470,27 @@ function parentPhaseKey(key) {
1470
1470
  * `{ phase: null }` because they do not begin with a phase token. The name
1471
1471
  * extraction (parenthetical or em-dash tail, minus status words) is unchanged.
1472
1472
  */
1473
- function parsePhaseFromProse(value) {
1473
+ /**
1474
+ * True when `value` is the planned-phase writer's own status phrase
1475
+ * (`READY TO EXECUTE`), which can sit in the name position of a `Phase:` line but is
1476
+ * never a phase name (#5369). The one owner of that phrase: the prose parser and the
1477
+ * planned-phase writers both ask here.
1478
+ */
1479
+ function isPlannedPhaseStatusPhrase(value) {
1480
+ return typeof value === 'string' && /^ready to execute$/i.test(value.trim());
1481
+ }
1482
+ // The leading phase id of a prose field. #5626: under `milestone-prefixed` the id is the whole `M-NN`
1483
+ // (`3-01`), so the number capture takes one optional `-NN` segment; every other convention (and an absent
1484
+ // one) keeps the dotted-only capture, where `3-01` reads as phase `3` exactly as before.
1485
+ const PHASE_PROSE_RE = /^\s*(?:Phase\s+)?(?:[A-Z][A-Z0-9_]*-)?(\d+[A-Z]?(?:\.\d+)*)\b/i;
1486
+ const PHASE_PROSE_MILESTONE_PREFIXED_RE = /^\s*(?:Phase\s+)?(?:[A-Z][A-Z0-9_]*-)?(\d+(?:-\d+)?[A-Z]?(?:\.\d+)*)\b/i;
1487
+ function parsePhaseFromProse(value, convention) {
1474
1488
  if (!value)
1475
1489
  return { phase: null, name: null };
1476
1490
  // Coerce defensively so a non-string caller cannot throw on this canonical
1477
1491
  // surface (mirrors the sibling #2121 functions' String(...) handling).
1478
1492
  const str = String(value);
1479
- const phaseMatch = str.match(/^\s*(?:Phase\s+)?(?:[A-Z][A-Z0-9_]*-)?(\d+[A-Z]?(?:\.\d+)*)\b/i);
1493
+ const phaseMatch = str.match(convention === 'milestone-prefixed' ? PHASE_PROSE_MILESTONE_PREFIXED_RE : PHASE_PROSE_RE);
1480
1494
  // The name-extraction quantifiers are length-bounded so a crafted long
1481
1495
  // unterminated run (many `(` or `—`) in an untrusted STATE.md field value
1482
1496
  // cannot drive O(n^2) regex backtracking (CPU-exhaustion DoS). A real phase
@@ -1501,7 +1515,9 @@ function parsePhaseFromProse(value) {
1501
1515
  // The precedence-decision vocabulary is deliberately broader than the final
1502
1516
  // name-nulling filter below: a dash tail that merely LOOKS like a status
1503
1517
  // annotation should lose to a parenthetical name, without changing which
1504
- // extracted names are nulled (that set stays the long-standing three).
1518
+ // extracted names are nulled (that set is the long-standing three plus the
1519
+ // planned-phase writer's own `READY TO EXECUTE`, #5369: with no parenthetical
1520
+ // name to prefer it was otherwise harvested as the phase name).
1505
1521
  const STATUS_WORD_RE = /^(?:complete|executing|not started)$/i;
1506
1522
  const STATUSY_TAIL_RE = /^(?:completed?|executing|not started|planning|planned|ready(?:\s+to\s+\S.{0,50})?|done|in progress|blocked|paused|verifying)$/i;
1507
1523
  const dashRaw = dashName?.[1]?.trim() ?? null;
@@ -1513,7 +1529,7 @@ function parsePhaseFromProse(value) {
1513
1529
  // `(Name) — STATUS` shape); with no parenthetical it stays the best guess.
1514
1530
  && !(parenName && /^[A-Z][A-Z0-9_-]*$/.test(dashRaw));
1515
1531
  const rawName = dashIsName ? dashRaw : (parenName?.[1] ?? dashRaw ?? null);
1516
- const name = rawName && !STATUS_WORD_RE.test(rawName.trim())
1532
+ const name = rawName && !STATUS_WORD_RE.test(rawName.trim()) && !isPlannedPhaseStatusPhrase(rawName)
1517
1533
  ? rawName.trim()
1518
1534
  : null;
1519
1535
  return {
@@ -1682,6 +1698,7 @@ module.exports = {
1682
1698
  phaseKeyFromProse,
1683
1699
  parentPhaseKey,
1684
1700
  parsePhaseFromProse,
1701
+ isPlannedPhaseStatusPhrase,
1685
1702
  stripConfiguredProjectCodePrefix,
1686
1703
  isForeignPrefixedPhaseQuery,
1687
1704
  roadmapPhaseLookupSources,