@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
@@ -14,11 +14,14 @@ const node_path_1 = __importDefault(require("node:path"));
14
14
  const pattern_cjs_1 = require("./pattern.cjs");
15
15
  // eslint-disable-next-line @typescript-eslint/no-require-imports
16
16
  const ioMod = require("./io.cjs");
17
- const { output, error, declineNoOp, formatDiagnosticToken } = ioMod;
17
+ const { output, error, declineNoOp, formatDiagnosticToken, ERROR_REASON } = ioMod;
18
18
  // eslint-disable-next-line @typescript-eslint/no-require-imports
19
19
  const cliExitModule = require("./cli-exit.cjs");
20
20
  const { ExitError } = cliExitModule;
21
21
  // eslint-disable-next-line @typescript-eslint/no-require-imports
22
+ const capabilityLedgerMod = require("./capability-ledger.cjs");
23
+ const { readSmallRegularFile } = capabilityLedgerMod;
24
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
22
25
  const stateContract = require("./state-contract.cjs");
23
26
  const { publishStateContract } = stateContract;
24
27
  // eslint-disable-next-line @typescript-eslint/no-require-imports
@@ -316,19 +319,51 @@ function cmdStateGet(cwd, section, raw) {
316
319
  output({ error: `Section or field "${section}" not found` }, raw, '');
317
320
  }
318
321
  }
319
- function readTextArgOrFile(cwd, value, filePath, label) {
322
+ /**
323
+ * #4926: cap on a `--<flag>-file` text input. Twice the 128 KiB one argv string
324
+ * can carry on Linux, so the file form never takes less than the inline form,
325
+ * and the entry echoed back on stdout stays small.
326
+ */
327
+ const TEXT_FILE_MAX_BYTES = 256 * 1024;
328
+ /**
329
+ * Resolve a text argument that may instead be supplied as a file (`--<flag>`
330
+ * or `--<flag>-file`).
331
+ *
332
+ * #4926: the file is a read-once INPUT — its contents are copied into
333
+ * STATE.md and the path itself is never written, recorded, or resolved
334
+ * again — so it is not a project-write resource and is not confined to the
335
+ * project root. A scratch file from an agent's temp directory is accepted.
336
+ * A relative path still resolves against the project root.
337
+ *
338
+ * A file that cannot be read is a caller error and goes through the fault
339
+ * path — stderr, non-zero exit (USAGE), `--json-errors` envelope — rather than
340
+ * an `{ added: false }` payload at exit 0 that a shell caller gating on `$?`
341
+ * reads as success while nothing was written.
342
+ *
343
+ * An unconfined path is hostile input, so the read goes through
344
+ * `readSmallRegularFile`: a FIFO, a device (`/dev/zero`, a piped `/dev/stdin`)
345
+ * or a directory is refused without blocking, and a file over
346
+ * TEXT_FILE_MAX_BYTES is refused without being read.
347
+ */
348
+ function readTextArgOrFile(cwd, value, filePath, flag) {
320
349
  if (!filePath)
321
350
  return value;
322
- // Path traversal guard: ensure file resolves within project directory
323
- // eslint-disable-next-line @typescript-eslint/no-require-imports, @typescript-eslint/unbound-method
324
- const { assertWithinRoot, PathAcceptance } = require('./security.cjs');
325
- const contained = assertWithinRoot(filePath, cwd, `${label} path`, PathAcceptance.AbsoluteInsideRoot);
351
+ const refuse = (why) => error(`--${flag}-file could not be read: ${formatDiagnosticToken(filePath)}${why ? ` (${why})` : ''}`, ERROR_REASON.USAGE);
352
+ let content;
326
353
  try {
327
- return node_fs_1.default.readFileSync(contained, 'utf-8').trimEnd();
328
- }
329
- catch {
330
- throw new Error(`${label} file not found: ${filePath}`);
354
+ content = readSmallRegularFile(node_path_1.default.resolve(cwd, filePath), TEXT_FILE_MAX_BYTES);
331
355
  }
356
+ catch (err) {
357
+ const code = err.code;
358
+ if (code === 'EFBIG')
359
+ return refuse(`larger than ${TEXT_FILE_MAX_BYTES} bytes`);
360
+ if (code === 'EISDIR' || code === 'ENXIO')
361
+ return refuse('not a regular file');
362
+ return refuse(code);
363
+ }
364
+ if (content === null)
365
+ return refuse('ENOENT');
366
+ return content.trimEnd();
332
367
  }
333
368
  function cmdStatePatch(cwd, patches, raw) {
334
369
  // Validate all field names before processing
@@ -735,7 +770,7 @@ function cmdStateAdvancePlan(cwd, raw) {
735
770
  // silently absorbed.
736
771
  const body = stripFrontmatter(content);
737
772
  const positionScope = matchCurrentPositionSection(body) ?? body;
738
- const positionPhase = parseProsePhaseField((0, state_document_cjs_1.stateExtractField)(positionScope, 'Phase')).phase;
773
+ const positionPhase = parseProsePhaseField((0, state_document_cjs_1.stateExtractField)(positionScope, 'Phase'), resolvePhaseIdConvention(cwd)).phase;
739
774
  // #4093: a Current Position section with ZERO labeled fields has no
740
775
  // `Phase:` line either; frontmatter `current_phase` is the documented
741
776
  // survivor of body drift (the reporter's document still carried it, and
@@ -1224,25 +1259,19 @@ function cmdStateUpdateProgress(cwd, raw) {
1224
1259
  }
1225
1260
  function cmdStateAddDecision(cwd, options, raw) {
1226
1261
  const statePath = planningPaths(cwd).state;
1227
- if (!node_fs_1.default.existsSync(statePath)) {
1228
- output({ error: 'STATE.md not found' }, raw, undefined);
1229
- return;
1230
- }
1262
+ // #4926: every refusal from this verb goes through the fault path (non-zero
1263
+ // exit), so a caller gating on `$?` never reads success while nothing was written.
1264
+ if (!node_fs_1.default.existsSync(statePath))
1265
+ return error('STATE.md not found', ERROR_REASON.USAGE);
1231
1266
  const { phase, summary, summary_file, rationale, rationale_file } = options;
1232
- let summaryText = undefined;
1233
- let rationaleText = '';
1234
- try {
1235
- summaryText = readTextArgOrFile(cwd, summary, summary_file, 'summary');
1236
- rationaleText = readTextArgOrFile(cwd, rationale || '', rationale_file, 'rationale') || '';
1237
- }
1238
- catch (err) {
1239
- output({ added: false, reason: err.message }, raw, 'false');
1240
- return;
1241
- }
1242
- if (!summaryText) {
1243
- output({ error: 'summary required' }, raw, undefined);
1244
- return;
1245
- }
1267
+ const summaryText = readTextArgOrFile(cwd, summary, summary_file, 'summary');
1268
+ // The rationale is optional: blank, inline or file, means "no rationale".
1269
+ const rationaleInput = readTextArgOrFile(cwd, rationale || '', rationale_file, 'rationale') || '';
1270
+ const rationaleText = rationaleInput.trim() ? rationaleInput : '';
1271
+ // A blank inline value is refused the same as a blank file (the file form is
1272
+ // trimEnd()ed by readTextArgOrFile, the inline form is not).
1273
+ if (!summaryText || !summaryText.trim())
1274
+ return error('summary required — pass a non-blank --summary or --summary-file', ERROR_REASON.USAGE);
1246
1275
  // #3231/#3481: `--phase` omitted → resolve from the STATE.md being written, via
1247
1276
  // the canonical ladder `state prune` uses. A decision entry is a permanent
1248
1277
  // record, so a literal `[Phase ?]` written while `current_phase` sat three
@@ -1254,7 +1283,7 @@ function cmdStateAddDecision(cwd, options, raw) {
1254
1283
  if (!phaseId) {
1255
1284
  const rawState = node_fs_1.default.readFileSync(statePath, 'utf-8');
1256
1285
  const fm = extractFrontmatter(rawState, statePath);
1257
- phaseId = resolveCurrentPhaseId(fm, stripFrontmatter(rawState)) ?? undefined;
1286
+ phaseId = resolveCurrentPhaseId(fm, stripFrontmatter(rawState), resolvePhaseIdConvention(cwd)) ?? undefined;
1258
1287
  }
1259
1288
  const entry = `- [Phase ${phaseId || '?'}]: ${summaryText}${rationaleText ? ` — ${rationaleText}` : ''}`;
1260
1289
  let _added = false;
@@ -1310,23 +1339,14 @@ function cmdStateAddDecision(cwd, options, raw) {
1310
1339
  }
1311
1340
  function cmdStateAddBlocker(cwd, text, raw) {
1312
1341
  const statePath = planningPaths(cwd).state;
1313
- if (!node_fs_1.default.existsSync(statePath)) {
1314
- output({ error: 'STATE.md not found' }, raw, undefined);
1315
- return;
1316
- }
1342
+ // #4926: every refusal from this verb goes through the fault path (non-zero
1343
+ // exit), so a caller gating on `$?` never reads success while nothing was written.
1344
+ if (!node_fs_1.default.existsSync(statePath))
1345
+ return error('STATE.md not found', ERROR_REASON.USAGE);
1317
1346
  const blockerOptions = typeof text === 'object' && text !== null ? text : { text: text };
1318
- let blockerText = undefined;
1319
- try {
1320
- blockerText = readTextArgOrFile(cwd, blockerOptions.text, blockerOptions.text_file, 'blocker');
1321
- }
1322
- catch (err) {
1323
- output({ added: false, reason: err.message }, raw, 'false');
1324
- return;
1325
- }
1326
- if (!blockerText) {
1327
- output({ error: 'text required' }, raw, undefined);
1328
- return;
1329
- }
1347
+ const blockerText = readTextArgOrFile(cwd, blockerOptions.text, blockerOptions.text_file, 'text');
1348
+ if (!blockerText || !blockerText.trim())
1349
+ return error('text required — pass a non-blank --text or --text-file', ERROR_REASON.USAGE);
1330
1350
  const entry = `- ${blockerText}`;
1331
1351
  let _added = false;
1332
1352
  let created = false;
@@ -1379,25 +1399,16 @@ function cmdStateAddBlocker(cwd, text, raw) {
1379
1399
  }
1380
1400
  function cmdStateAddRoadmapEvolution(cwd, options, raw) {
1381
1401
  const statePath = planningPaths(cwd).state;
1382
- if (!node_fs_1.default.existsSync(statePath)) {
1383
- output({ error: 'STATE.md not found' }, raw, undefined);
1384
- return;
1385
- }
1402
+ // #4926: every refusal from this verb goes through the fault path (non-zero
1403
+ // exit), so a caller gating on `$?` never reads success while nothing was written.
1404
+ if (!node_fs_1.default.existsSync(statePath))
1405
+ return error('STATE.md not found', ERROR_REASON.USAGE);
1386
1406
  const { phase, action, after, note, note_file, urgent } = options;
1387
- let noteText = undefined;
1388
- try {
1389
- noteText = readTextArgOrFile(cwd, note, note_file, 'note');
1390
- }
1391
- catch (err) {
1392
- output({ added: false, reason: err.message }, raw, 'false');
1393
- return;
1394
- }
1407
+ const noteText = readTextArgOrFile(cwd, note, note_file, 'note');
1395
1408
  // Reject missing / empty / whitespace-only notes — an evolution entry with no
1396
1409
  // narrative is meaningless and would corrupt the section with a dangling bullet.
1397
- if (!noteText || !noteText.trim()) {
1398
- output({ error: 'note required' }, raw, undefined);
1399
- return;
1400
- }
1410
+ if (!noteText || !noteText.trim())
1411
+ return error('note required — pass a non-blank --note or --note-file', ERROR_REASON.USAGE);
1401
1412
  // Flatten line breaks so the entry is always a single Markdown bullet. The
1402
1413
  // dedupe + rendering contract is line-oriented; a multiline --note-file would
1403
1414
  // otherwise spill continuation lines outside the bullet and defeat dedupe.
@@ -1417,7 +1428,7 @@ function cmdStateAddRoadmapEvolution(cwd, options, raw) {
1417
1428
  if (!phaseId) {
1418
1429
  const rawState = node_fs_1.default.readFileSync(statePath, 'utf-8');
1419
1430
  const fm = extractFrontmatter(rawState, statePath);
1420
- phaseId = resolveCurrentPhaseId(fm, stripFrontmatter(rawState)) ?? undefined;
1431
+ phaseId = resolveCurrentPhaseId(fm, stripFrontmatter(rawState), resolvePhaseIdConvention(cwd)) ?? undefined;
1421
1432
  }
1422
1433
  const entry = `- Phase ${phaseId || '?'} ${actionText}${afterText}: ${flatNote}${urgentText}`;
1423
1434
  let duplicate = false;
@@ -1984,26 +1995,27 @@ function preferNewerLastActivity(existingFm, derivedFm) {
1984
1995
  derivedFm['last_activity'] = exRaw;
1985
1996
  }
1986
1997
  }
1987
- function parseProsePhaseField(value) {
1998
+ function parseProsePhaseField(value, convention) {
1988
1999
  // #2121 Phase 2 (#2125): delegate to the canonical anchored parser so this
1989
2000
  // module holds no independent prose phase-id regex. Drives #2111 — the
1990
2001
  // anchored parser returns { phase: null } for a "Milestone vX.Y complete"
1991
2002
  // body line (the old unanchored regex mined the minor-version digit, e.g.
1992
2003
  // v0.5 -> "5"), so syncStateFrontmatter's #905 guard preserves the real
1993
2004
  // current_phase instead of clobbering it.
1994
- return parsePhaseFromProse(value);
2005
+ // #5626: under `milestone-prefixed` the id is the whole `M-NN`; the convention comes from its owner.
2006
+ return parsePhaseFromProse(value, convention);
1995
2007
  }
1996
- function resolveStatePhase(fm, body) {
2008
+ function resolveStatePhase(fm, body, convention) {
1997
2009
  const currentPositionScope = matchCurrentPositionSection(body) ?? body;
1998
2010
  const frontmatterRaw = (0, state_document_cjs_1.stateFieldValue)(fm, body, 'current_phase', null).value;
1999
2011
  const legacyRaw = (0, state_document_cjs_1.stateFieldValue)(fm, currentPositionScope, null, 'Current Phase').value;
2000
2012
  const currentPositionRaw = (0, state_document_cjs_1.stateFieldValue)(fm, currentPositionScope, null, 'Phase').value;
2001
2013
  const sources = {
2002
- frontmatter: parseProsePhaseField(frontmatterRaw).phase,
2003
- legacy_current_phase: parseProsePhaseField(legacyRaw).phase,
2004
- current_position_phase: parseProsePhaseField(currentPositionRaw).phase,
2014
+ frontmatter: parseProsePhaseField(frontmatterRaw, convention).phase,
2015
+ legacy_current_phase: parseProsePhaseField(legacyRaw, convention).phase,
2016
+ current_position_phase: parseProsePhaseField(currentPositionRaw, convention).phase,
2005
2017
  };
2006
- const prosePhase = parseProsePhaseField(currentPositionRaw);
2018
+ const prosePhase = parseProsePhaseField(currentPositionRaw, convention);
2007
2019
  return {
2008
2020
  phase: sources.frontmatter ?? sources.legacy_current_phase ?? sources.current_position_phase,
2009
2021
  name: (0, state_document_cjs_1.stateFieldValue)(fm, body, 'current_phase_name', null).value
@@ -2058,9 +2070,9 @@ function resolveStatePhase(fm, body) {
2058
2070
  * Reconcile the two only by giving `resolveStatePhase` an explicit scope
2059
2071
  * parameter — never by pointing this at it and dropping the difference.
2060
2072
  */
2061
- function resolveCurrentPhaseId(fm, body) {
2073
+ function resolveCurrentPhaseId(fm, body, convention) {
2062
2074
  const positionSection = sliceCurrentPositionSection(body);
2063
- const prosePhase = positionSection !== null ? parseProsePhaseField((0, state_document_cjs_1.stateFieldValue)(fm, positionSection, null, 'Phase').value).phase : null;
2075
+ const prosePhase = positionSection !== null ? parseProsePhaseField((0, state_document_cjs_1.stateFieldValue)(fm, positionSection, null, 'Phase').value, convention).phase : null;
2064
2076
  return (0, state_document_cjs_1.stateFieldValue)(fm, body, 'current_phase', 'Current Phase').value ?? prosePhase;
2065
2077
  }
2066
2078
  function parseProseLastActivityField(value) {
@@ -2098,7 +2110,7 @@ function cmdStateSnapshot(cwd, raw) {
2098
2110
  // so it is scopeable exactly like Stopped At under ## Session. Fall back to
2099
2111
  // full-body search only when no ## Current Position section exists, so files
2100
2112
  // with no section heading keep their current behaviour.
2101
- const resolvedPhase = resolveStatePhase(fm, body);
2113
+ const resolvedPhase = resolveStatePhase(fm, body, resolvePhaseIdConvention(cwd));
2102
2114
  const currentPhase = resolvedPhase.phase;
2103
2115
  const currentPhaseName = resolvedPhase.name;
2104
2116
  const totalPhasesRaw = (0, state_document_cjs_1.stateFieldValue)(fm, body, 'total_phases', 'Total Phases').value;
@@ -2520,7 +2532,7 @@ milestoneExplicitlyNull) {
2520
2532
  // section overwrites current_phase here, and the next read surfaces it. Fall
2521
2533
  // back to full-body search when no ## Current Position section exists.
2522
2534
  const currentPositionScope = matchCurrentPositionSection(bodyContent) ?? bodyContent;
2523
- const prosePhase = parseProsePhaseField((0, state_document_cjs_1.stateExtractField)(currentPositionScope, 'Phase'));
2535
+ const prosePhase = parseProsePhaseField((0, state_document_cjs_1.stateExtractField)(currentPositionScope, 'Phase'), cwd ? resolvePhaseIdConvention(cwd) : null);
2524
2536
  const currentPhase = (0, state_document_cjs_1.stateExtractField)(bodyContent, 'Current Phase') ?? prosePhase.phase;
2525
2537
  const currentPhaseName = (0, state_document_cjs_1.stateExtractField)(bodyContent, 'Current Phase Name') ?? prosePhase.name;
2526
2538
  const currentPlan = (0, state_document_cjs_1.stateExtractField)(bodyContent, 'Current Plan');
@@ -2788,8 +2800,14 @@ milestoneExplicitlyNull) {
2788
2800
  // — no second ROADMAP parser here. A ROADMAP without a canonical
2789
2801
  // `## Progress` table resolves no table → floor inert (disk count
2790
2802
  // stands), the owner's own answer to "what is countable".
2803
+ // #5165: the milestone-grouped `## Progress` table keeps every
2804
+ // milestone's rows and sits OUTSIDE any narrower roadmapScope, so the
2805
+ // floor names the milestone STATE asserts (assertedMilestoneVersion —
2806
+ // present on every resync path, unlike the optional storedMilestone
2807
+ // parameter) and the owner counts only that milestone's rows. No row
2808
+ // for it => nulls => the floor is inert and the disk count stands.
2791
2809
  const roadmapCompletedPhases = roadmapScope !== null && safeToUseRoadmapCount && !diskCountsWithheld
2792
- ? deriveProgressFromRoadmap(roadmapScope).completedPhases
2810
+ ? deriveProgressFromRoadmap(roadmapScope, assertedMilestoneVersion ?? storedMilestone).completedPhases
2793
2811
  : null;
2794
2812
  const flooredCompletedPhases = roadmapCompletedPhases !== null
2795
2813
  ? Math.max(diskCompletedPhases, roadmapCompletedPhases)
@@ -3216,6 +3234,8 @@ function applyAuthoritativeProgressSubkeys(fm, intent, opts) {
3216
3234
  for (const [subkey, value] of Object.entries(intent)) {
3217
3235
  if (typeof value !== 'number' || !Number.isFinite(value))
3218
3236
  continue;
3237
+ if (subkey === 'percent')
3238
+ continue; // composed below, never applied raw (#5165)
3219
3239
  if (!getFieldClassification(`progress.${subkey}`))
3220
3240
  continue;
3221
3241
  const isCompletedCounter = subkey === 'completed_phases' || subkey === 'completed_plans';
@@ -3233,10 +3253,18 @@ function applyAuthoritativeProgressSubkeys(fm, intent, opts) {
3233
3253
  }
3234
3254
  // percent: applied only when a completed counter moved (coherent with the
3235
3255
  // counters that just landed) or when no percent exists to contradict.
3256
+ // #5165 / ADR-4629 §8.5: `computeProgressPercent` is the one composition
3257
+ // owner (min(plan fraction, phase fraction)) that every resyncing write uses.
3258
+ // When the block carries the counters it needs, the percent is composed by
3259
+ // that owner over the counters just landed, so a later resyncing write
3260
+ // cannot move what this transition wrote. The intent's own percent (a
3261
+ // phase-only fraction) is only the fallback for a block without plan counts.
3236
3262
  const intentPercent = intent['percent'];
3237
3263
  if (typeof intentPercent === 'number' && Number.isFinite(intentPercent) && (completedMoved || (0, state_document_cjs_1.toFiniteNumber)(base['percent']) === null)) {
3238
- if (!Object.is(base['percent'], intentPercent)) {
3239
- base['percent'] = intentPercent;
3264
+ const composed = (0, state_document_cjs_1.computeProgressPercent)((0, state_document_cjs_1.toFiniteNumber)(base['completed_plans']), (0, state_document_cjs_1.toFiniteNumber)(base['total_plans']), (0, state_document_cjs_1.toFiniteNumber)(base['completed_phases']), (0, state_document_cjs_1.toFiniteNumber)(base['total_phases']), SCOPE.COMPLETE);
3265
+ const nextPercent = composed !== null ? composed : intentPercent;
3266
+ if (!Object.is(base['percent'], nextPercent)) {
3267
+ base['percent'] = nextPercent;
3240
3268
  changed = true;
3241
3269
  }
3242
3270
  }
@@ -3820,7 +3848,10 @@ function assertStatePreservationOptions(options, caller) {
3820
3848
  throw err;
3821
3849
  }
3822
3850
  }
3823
- function applyPostSyncPreservation(originalContent, transformedContent, syncedContent, statePath, options) {
3851
+ function applyPostSyncPreservation(originalContent, transformedContent, syncedContent, statePath, options,
3852
+ // #5626: the project's phase-id convention, resolved by the caller from its cwd (null: unset). Trailing and
3853
+ // optional so `options` stays the fifth parameter the options guard and its callers rely on.
3854
+ phaseIdConvention = null) {
3824
3855
  assertStatePreservationOptions(options, 'applyPostSyncPreservation');
3825
3856
  const { resync, authoritativeFm, deriveProgressKeys, divergedFields, explicitProgressField, preWriteState } = options;
3826
3857
  // Bug #1230: delta heuristic — snapshot pre-transform body source fields so
@@ -3877,7 +3908,7 @@ function applyPostSyncPreservation(originalContent, transformedContent, syncedCo
3877
3908
  const preCurrentPositionScope = matchCurrentPositionSection(preBody) ?? preBody;
3878
3909
  const preBodyCurrentPlan = (0, state_document_cjs_1.stateExtractField)(preBody, 'Current Plan');
3879
3910
  const preBodyCurrentPhase = (0, state_document_cjs_1.stateExtractField)(preBody, 'Current Phase')
3880
- ?? parseProsePhaseField((0, state_document_cjs_1.stateExtractField)(preCurrentPositionScope, 'Phase')).phase;
3911
+ ?? parseProsePhaseField((0, state_document_cjs_1.stateExtractField)(preCurrentPositionScope, 'Phase'), phaseIdConvention).phase;
3881
3912
  const preBodyPausedAt = (0, state_document_cjs_1.stateExtractField)(preSessionScope, 'Paused At');
3882
3913
  const preBodyLastActivityRaw = (0, state_document_cjs_1.stateExtractField)(preBody, 'Last Activity')
3883
3914
  ?? (0, state_document_cjs_1.stateExtractField)(preBody, 'Last activity');
@@ -3903,7 +3934,7 @@ function applyPostSyncPreservation(originalContent, transformedContent, syncedCo
3903
3934
  const postCurrentPositionScope = matchCurrentPositionSection(postBody) ?? postBody;
3904
3935
  const postBodyCurrentPlan = (0, state_document_cjs_1.stateExtractField)(postBody, 'Current Plan');
3905
3936
  const postBodyCurrentPhase = (0, state_document_cjs_1.stateExtractField)(postBody, 'Current Phase')
3906
- ?? parseProsePhaseField((0, state_document_cjs_1.stateExtractField)(postCurrentPositionScope, 'Phase')).phase;
3937
+ ?? parseProsePhaseField((0, state_document_cjs_1.stateExtractField)(postCurrentPositionScope, 'Phase'), phaseIdConvention).phase;
3907
3938
  const postBodyPausedAt = (0, state_document_cjs_1.stateExtractField)(postSessionScope, 'Paused At');
3908
3939
  const postBodyLastActivityRaw = (0, state_document_cjs_1.stateExtractField)(postBody, 'Last Activity')
3909
3940
  ?? (0, state_document_cjs_1.stateExtractField)(postBody, 'Last activity');
@@ -4120,7 +4151,7 @@ function applyPostSyncPreservation(originalContent, transformedContent, syncedCo
4120
4151
  function syncAndPreserveStateMd(originalContent, transformedContent, statePath, cwd, options) {
4121
4152
  assertStatePreservationOptions(options, 'syncAndPreserveStateMd');
4122
4153
  const synced = syncStateFrontmatter(transformedContent, cwd, options.authoritativeFm);
4123
- return applyPostSyncPreservation(originalContent, transformedContent, synced, statePath, options);
4154
+ return applyPostSyncPreservation(originalContent, transformedContent, synced, statePath, options, cwd ? resolvePhaseIdConvention(cwd) : null);
4124
4155
  }
4125
4156
  /**
4126
4157
  * Atomic read-modify-write for STATE.md.
@@ -4778,8 +4809,9 @@ function stateSectionScopeUnits(units, sectionTargets) {
4778
4809
  * matching `readStateTarget`'s long-standing special case.
4779
4810
  */
4780
4811
  function verifyStateWriteIntent(intent, preContent, postContent, statePath, bodyDeltas) {
4781
- const pre = (0, shell_command_projection_cjs_1.normalizeContent)(statePath, preContent).content;
4782
- const post = (0, shell_command_projection_cjs_1.normalizeContent)(statePath, postContent).content;
4812
+ // #5165: the write lands scoped to what it changed (platformWriteSync reads the file it replaces).
4813
+ const pre = (0, shell_command_projection_cjs_1.normalizeContent)(statePath, preContent, { prior: preContent }).content;
4814
+ const post = (0, shell_command_projection_cjs_1.normalizeContent)(statePath, postContent, { prior: preContent }).content;
4783
4815
  const preFm = extractFrontmatter(pre, statePath);
4784
4816
  const postFm = extractFrontmatter(post, statePath);
4785
4817
  const preBody = stripFrontmatter(pre);
@@ -4978,7 +5010,7 @@ function applyStateWriteIntent(statePath, intent, transformFn, cwd, options = {}
4978
5010
  return unwritten(verification);
4979
5011
  (0, shell_command_projection_cjs_1.platformWriteSync)(statePath, candidate);
4980
5012
  const reread = (0, shell_command_projection_cjs_1.platformReadSync)(statePath) || '';
4981
- if (reread !== (0, shell_command_projection_cjs_1.normalizeContent)(statePath, candidate).content) {
5013
+ if (reread !== (0, shell_command_projection_cjs_1.normalizeContent)(statePath, candidate, { prior: pre }).content) {
4982
5014
  return Object.freeze({
4983
5015
  ok: false,
4984
5016
  reasons: Object.freeze([STATE_WRITE_INTENT_FAILURE.REREAD_MISMATCH]),
@@ -5044,7 +5076,7 @@ function cmdStateJson(cwd, raw) {
5044
5076
  const bodyPausedAt = (0, state_document_cjs_1.stateExtractField)(sessionScope, 'Paused At');
5045
5077
  const bodyPhaseSource = (0, state_document_cjs_1.stateExtractField)(body, 'Phase');
5046
5078
  const bodyCurrentPhase = (0, state_document_cjs_1.stateExtractField)(body, 'Current Phase')
5047
- ?? parseProsePhaseField((0, state_document_cjs_1.stateExtractField)(positionScope, 'Phase')).phase;
5079
+ ?? parseProsePhaseField((0, state_document_cjs_1.stateExtractField)(positionScope, 'Phase'), resolvePhaseIdConvention(cwd)).phase;
5048
5080
  const bodyCurrentPlan = (0, state_document_cjs_1.stateExtractField)(body, 'Current Plan');
5049
5081
  const bodyStatus = (0, state_document_cjs_1.stateExtractField)(body, 'Status');
5050
5082
  // #3836: mirrors applyPostSyncPreservation's own derivation (state.cts
@@ -5097,6 +5129,23 @@ function cmdStateJson(cwd, raw) {
5097
5129
  preferNewerLastActivity(existingFm, built);
5098
5130
  output(built, raw, JSON.stringify(built, null, 2));
5099
5131
  }
5132
+ /**
5133
+ * 1-based position of the first plan with no summary, when SOME of the phase's plans already have
5134
+ * one (a gap-closure re-entry: plans 1..17 are done and 18 and 19 are the gap plans, #4945).
5135
+ * `null` when no plan is summarized yet, when every plan is, or when the phase directory cannot be
5136
+ * found: the phase then starts at plan 1, exactly as before. Plans and summaries are paired by the
5137
+ * phase locator's own rule, so this agrees with `init` and `phase-plan-index`.
5138
+ */
5139
+ function firstPlanToRun(cwd, phaseNumber) {
5140
+ const located = phaseLocatorMod.findPhaseInternal(cwd, String(phaseNumber));
5141
+ if (!located || !located.found)
5142
+ return null;
5143
+ const { plans, incomplete_plans: incomplete } = located;
5144
+ if (incomplete.length === 0 || incomplete.length === plans.length)
5145
+ return null;
5146
+ const index = plans.indexOf(incomplete[0]);
5147
+ return index === -1 ? null : index + 1;
5148
+ }
5100
5149
  /**
5101
5150
  * Update STATE.md when a new phase begins execution.
5102
5151
  * Updates body text fields (Current focus, Status, Last Activity, Current Position)
@@ -5144,6 +5193,8 @@ function cmdStateBeginPhase(cwd, phaseNumber, phaseName, planCount, raw) {
5144
5193
  phaseNumber: phaseNumber,
5145
5194
  phaseName: phaseName ?? null,
5146
5195
  planCount: planCount ?? null,
5196
+ // #4945: a gap-closure re-entry starts at the first plan still to run, not at plan 1.
5197
+ startPlan: firstPlanToRun(cwd, phaseNumber),
5147
5198
  };
5148
5199
  const deps = {
5149
5200
  clock: clock_cjs_1.realClock,
@@ -5497,7 +5548,8 @@ function cmdStatePlannedPhase(cwd, phaseNumber, phaseName, planCount, raw) {
5497
5548
  if (!intent.phaseName) {
5498
5549
  const preFm = extractFrontmatter(content, statePath);
5499
5550
  const curatedName = preFm['current_phase_name'];
5500
- if (typeof curatedName === 'string' && curatedName.trim().length > 0) {
5551
+ // #5369: a status fragment a pre-fix write left as the name is not curated; do not reassert it.
5552
+ if (typeof curatedName === 'string' && curatedName.trim().length > 0 && !phaseIdMod.isPlannedPhaseStatusPhrase(curatedName)) {
5501
5553
  rmwOptions.authoritativeFm = { current_phase_name: curatedName };
5502
5554
  }
5503
5555
  }
@@ -5726,7 +5778,7 @@ function cmdStateValidate(cwd, raw, opts = {}) {
5726
5778
  const { fm, body, scope: initialScope } = readStateFrontmatterScoped(content, statePath);
5727
5779
  const scope = initialScope;
5728
5780
  const status = (0, state_document_cjs_1.stateFieldValue)(fm, body, 'status', 'Status').value || '';
5729
- const resolvedPhase = resolveStatePhase(fm, body);
5781
+ const resolvedPhase = resolveStatePhase(fm, body, resolvePhaseIdConvention(cwd));
5730
5782
  const currentPhase = resolvedPhase.phase;
5731
5783
  const totalPlansRaw = (0, state_document_cjs_1.stateFieldValue)(fm, body, 'total_plans_in_phase', 'Total Plans in Phase').value;
5732
5784
  const totalPlansInPhase = totalPlansRaw ? parseInt(totalPlansRaw, 10) : null;
@@ -6180,7 +6232,7 @@ function cmdStatePrune(cwd, options, raw) {
6180
6232
  const body = stripFrontmatter(rawState);
6181
6233
  // Prune needs an integer cutoff, so it parses the resolved id itself; a
6182
6234
  // non-numeric or absent id lands on 0 and prune bails, as before.
6183
- const currentPhase = parseInt(String(resolveCurrentPhaseId(fm, body)), 10) || 0;
6235
+ const currentPhase = parseInt(String(resolveCurrentPhaseId(fm, body, resolvePhaseIdConvention(cwd))), 10) || 0;
6184
6236
  const cutoff = currentPhase - keepRecent;
6185
6237
  if (cutoff <= 0) {
6186
6238
  emit({ pruned: false, reason: `Only ${currentPhase} phases — nothing to prune with --keep-recent ${keepRecent}` }, raw, 'false');
@@ -36,6 +36,7 @@ const node_os_1 = __importDefault(require("node:os"));
36
36
  const node_path_1 = __importDefault(require("node:path"));
37
37
  const shell_command_projection_cjs_1 = require("./shell-command-projection.cjs");
38
38
  const runtime_name_policy_cjs_1 = require("./runtime-name-policy.cjs");
39
+ const args_terminator_cjs_1 = require("./args-terminator.cjs");
39
40
  // eslint-disable-next-line @typescript-eslint/no-require-imports
40
41
  const installProfiles = require("./install-profiles.cjs");
41
42
  // eslint-disable-next-line @typescript-eslint/no-require-imports
@@ -322,6 +323,14 @@ function applySurface(runtimeConfigDir, layout, manifest, clusterMap, registry,
322
323
  if (node_path_1.default.resolve(runtimeConfigDir) !== node_path_1.default.resolve(layout.configDir)) {
323
324
  throw new TypeError('applySurface runtimeConfigDir must match layout.configDir');
324
325
  }
326
+ // #5637: re-staged commands and skills keep the typed-text terminator nonce the install wrote,
327
+ // when it wrote one; this process is not an installer, so nothing armed it.
328
+ // A directory with no nonce (an install from before this change) is re-staged canonical, and a
329
+ // nonce armed earlier in this process for another directory is not carried over.
330
+ if ((0, args_terminator_cjs_1.readInstalledNonce)(layout.configDir) !== null)
331
+ (0, args_terminator_cjs_1.armArgsNonce)(layout.configDir, true);
332
+ else
333
+ (0, args_terminator_cjs_1.disarmArgsNonce)();
325
334
  // #3712: the dest selection below prefers `kind.home` over layout.configDir and
326
335
  // then hands it to the destructive _syncGsdDir, so surface apply is a third
327
336
  // escape route into the developer's real home alongside install/uninstall.
@@ -12,6 +12,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
12
12
  const node_fs_1 = __importDefault(require("node:fs"));
13
13
  const node_path_1 = __importDefault(require("node:path"));
14
14
  const security_cjs_1 = require("./security.cjs");
15
+ const tdd_test_files_cjs_1 = require("./tdd-test-files.cjs");
15
16
  // eslint-disable-next-line @typescript-eslint/no-require-imports
16
17
  const ioMod = require("./io.cjs");
17
18
  const { output, error, ERROR_REASON } = ioMod;
@@ -19,10 +20,8 @@ const { output, error, ERROR_REASON } = ioMod;
19
20
  const planDocumentMod = require("./plan-document.cjs");
20
21
  const { parsePlanDocument } = planDocumentMod;
21
22
  // eslint-disable-next-line @typescript-eslint/no-require-imports
22
- const capabilityLoaderMod = require("./capability-loader.cjs");
23
- // eslint-disable-next-line @typescript-eslint/no-require-imports
24
23
  const taskContentResolutionMod = require("./task-content-resolution.cjs");
25
- const { resolveTaskContent, ResolverAmbiguousError, ResolverFailedError, ResolverTimeoutError, ResolverMalformedOutputError, } = taskContentResolutionMod;
24
+ const { resolveTaskContent, loadResolverCapabilities, isResolverFailure, } = taskContentResolutionMod;
26
25
  // ─── Implementation ───────────────────────────────────────────────────────────
27
26
  function isBehaviorAddingTaskContent(content) {
28
27
  const tddTrue = /\btdd\s*=\s*["']true["']/i.test(content);
@@ -37,9 +36,7 @@ function isBehaviorAddingTaskContent(content) {
37
36
  .filter(Boolean);
38
37
  hasSourceFiles = fileLines.some((file) => !/\.md$/i.test(file) &&
39
38
  !/\.json$/i.test(file) &&
40
- !/\.test\.[^.]+$/i.test(file) &&
41
- !/\.spec\.[^.]+$/i.test(file) &&
42
- !/(^|[\\/])tests?[\\/]/i.test(file) &&
39
+ !(0, tdd_test_files_cjs_1.isTestFilePath)(file) &&
43
40
  !/\.(yml|yaml|toml|ini|cfg|conf|properties)$/i.test(file) &&
44
41
  !/(^|[\\/])\.env(\..+)?$/i.test(file));
45
42
  }
@@ -61,19 +58,6 @@ function isBehaviorAddingTaskContent(content) {
61
58
  reason: isBehaviorAdding ? null : `Not behavior-adding: ${missing.join('; ')}`,
62
59
  };
63
60
  }
64
- /**
65
- * Default (production) capability loader for `resolve-content`: the merged
66
- * first-party + validated-installed-overlay registry (ADR-1244 D2), the
67
- * established runtime read path for "installed capabilities including
68
- * third-party" — as opposed to `capability-loader.cts`'s heavier build-time
69
- * validation entry points or the static `capability-registry.cjs` alone
70
- * (first-party only, would miss a third-party capability's
71
- * `taskContentResolver` declaration entirely).
72
- */
73
- function defaultLoadCapabilities(cwd) {
74
- const registry = capabilityLoaderMod.loadRegistry({ includeInstalled: true, cwd });
75
- return Object.values(registry.capabilities ?? {});
76
- }
77
61
  function parseResolveContentArgs(args) {
78
62
  let plan = null;
79
63
  let taskId = null;
@@ -94,12 +78,10 @@ function parseResolveContentArgs(args) {
94
78
  * (ADR-3646 Decision 2). Resolves one task's content from the external
95
79
  * tracker its `tracker-id` attribute names, via `task-content-resolution.cts`.
96
80
  *
97
- * HARD-HALT CONTRACT: a thrown `ResolverAmbiguousError` / `ResolverFailedError`
98
- * / `ResolverTimeoutError` / `ResolverMalformedOutputError` from
81
+ * HARD-HALT CONTRACT: a thrown resolver failure (`isResolverFailure`) from
99
82
  * `resolveTaskContent` is turned into this CLI's own non-zero exit via
100
83
  * `error()` — never swallowed into a `{resolved: false}` JSON answer. Any
101
- * other thrown error is not one of the four documented resolver-error
102
- * classes and is allowed to propagate uncaught.
84
+ * other thrown error is not a documented resolver-error class and is allowed to propagate uncaught.
103
85
  */
104
86
  function routeResolveContent({ args, cwd, raw }, deps = {}) {
105
87
  const usage = 'Usage: task resolve-content --plan <path> --task-id <tracker-id> --raw';
@@ -128,7 +110,7 @@ function routeResolveContent({ args, cwd, raw }, deps = {}) {
128
110
  error(`No task with tracker-id '${taskId}' found in plan: ${plan}`, ERROR_REASON.USAGE);
129
111
  return;
130
112
  }
131
- const loadCapabilities = deps.loadCapabilities ?? defaultLoadCapabilities;
113
+ const loadCapabilities = deps.loadCapabilities ?? loadResolverCapabilities;
132
114
  const capabilities = loadCapabilities(projectRoot);
133
115
  const resolveFn = deps.resolveTaskContentFn ?? resolveTaskContent;
134
116
  let result;
@@ -136,10 +118,7 @@ function routeResolveContent({ args, cwd, raw }, deps = {}) {
136
118
  result = resolveFn({ trackerId: task.trackerId, capabilities });
137
119
  }
138
120
  catch (err) {
139
- if (err instanceof ResolverAmbiguousError ||
140
- err instanceof ResolverFailedError ||
141
- err instanceof ResolverTimeoutError ||
142
- err instanceof ResolverMalformedOutputError) {
121
+ if (isResolverFailure(err)) {
143
122
  error(err.message, ERROR_REASON.UNKNOWN);
144
123
  return;
145
124
  }
@@ -43,6 +43,8 @@ const node_child_process_1 = __importDefault(require("node:child_process"));
43
43
  // eslint-disable-next-line @typescript-eslint/no-require-imports
44
44
  const ioMod = require("./io.cjs");
45
45
  const { formatDiagnosticToken } = ioMod;
46
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
47
+ const capabilityLoaderMod = require("./capability-loader.cjs");
46
48
  // ─── Result taxonomy ──────────────────────────────────────────────────────────
47
49
  /**
48
50
  * The four non-throwing outcomes of `resolveTaskContent`. Frozen because the
@@ -102,6 +104,17 @@ class ResolverFailedError extends Error {
102
104
  this.stderrTail = stderrTail;
103
105
  }
104
106
  }
107
+ /**
108
+ * The tracker-id's id segment cannot safely be one argv element: a leading `-`
109
+ * reads as an option to the resolver binary, and control characters are never
110
+ * legitimate. Thrown before any spawn.
111
+ */
112
+ class ResolverUnsafeIdError extends Error {
113
+ constructor() {
114
+ super('tracker-id rejected: id must not start with "-" or contain control characters');
115
+ this.name = 'ResolverUnsafeIdError';
116
+ }
117
+ }
105
118
  /** The resolver subprocess exceeded its declared `invoke.timeoutMs` bound. */
106
119
  class ResolverTimeoutError extends Error {
107
120
  timeoutMs;
@@ -312,6 +325,9 @@ function resolveTaskContent(input) {
312
325
  const split = splitTrackerId(input.trackerId);
313
326
  if (split === null)
314
327
  return { kind: TASK_CONTENT_RESULT.NOT_APPLICABLE };
328
+ if (split.id.trimStart().startsWith('-') || /[\u0000-\u001f\u007f]/.test(input.trackerId ?? '')) {
329
+ throw new ResolverUnsafeIdError();
330
+ }
315
331
  const resolver = findResolver(split.prefix, input.capabilities ?? []);
316
332
  if (resolver === null)
317
333
  return { kind: TASK_CONTENT_RESULT.NO_RESOLVER };
@@ -327,7 +343,7 @@ function resolveTaskContent(input) {
327
343
  throw new ResolverAmbiguousError(split.prefix, ids);
328
344
  }
329
345
  const invocation = buildInvocation(resolver, split.id);
330
- const timeoutMs = input.timeoutOverrideMs ?? invocation.timeoutMs;
346
+ const timeoutMs = Math.floor(Math.min(input.timeoutOverrideMs ?? invocation.timeoutMs, invocation.timeoutMs));
331
347
  const execFn = input.execFn ?? realExec;
332
348
  const result = execFn(invocation.binary, invocation.args, { timeout: timeoutMs });
333
349
  if (isExecTimeout(result)) {
@@ -354,12 +370,41 @@ function resolveTaskContent(input) {
354
370
  }
355
371
  return { kind: TASK_CONTENT_RESULT.RESOLVED, content };
356
372
  }
373
+ /**
374
+ * Production capability loader for tracker-id resolution: the merged
375
+ * first-party + validated-installed-overlay registry (ADR-1244 D2), the
376
+ * established runtime read path for "installed capabilities including
377
+ * third-party" — as opposed to `capability-loader.cts`'s heavier build-time
378
+ * validation entry points or the static `capability-registry.cjs` alone
379
+ * (first-party only, would miss a third-party capability's
380
+ * `taskContentResolver` declaration entirely).
381
+ */
382
+ function loadResolverCapabilities(cwd) {
383
+ const registry = capabilityLoaderMod.loadRegistry({ includeInstalled: true, cwd });
384
+ return Object.values(registry.capabilities ?? {});
385
+ }
386
+ /** True for every error `resolveTaskContent` throws as a hard halt (the one classifier all consumers share). */
387
+ function isResolverFailure(err) {
388
+ return (err instanceof ResolverAmbiguousError ||
389
+ err instanceof ResolverFailedError ||
390
+ err instanceof ResolverTimeoutError ||
391
+ err instanceof ResolverMalformedOutputError ||
392
+ err instanceof ResolverUnsafeIdError);
393
+ }
394
+ /** Every text field of resolved content, so a consumer scanning for text cannot miss one when the shape grows. */
395
+ function contentTexts(c) {
396
+ return [c.action, c.verify, ...c.acceptanceCriteria, ...c.readFirst, c.done].filter((v) => v !== null);
397
+ }
357
398
  const taskContentResolution = {
358
399
  TASK_CONTENT_RESULT,
359
400
  splitTrackerId,
360
401
  findResolver,
361
402
  buildInvocation,
362
403
  resolveTaskContent,
404
+ loadResolverCapabilities,
405
+ isResolverFailure,
406
+ contentTexts,
407
+ ResolverUnsafeIdError,
363
408
  ResolverAmbiguousError,
364
409
  ResolverFailedError,
365
410
  ResolverTimeoutError,