@opengsd/gsd-core 1.13.0 → 1.15.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 (441) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/README.ja-JP.md +3 -3
  4. package/README.ko-KR.md +3 -3
  5. package/README.pt-BR.md +3 -3
  6. package/README.zh-CN.md +3 -3
  7. package/agents/gsd-advisor-researcher.compact.md +85 -0
  8. package/agents/gsd-ai-researcher.compact.md +96 -0
  9. package/agents/gsd-assumptions-analyzer.compact.md +81 -0
  10. package/agents/gsd-code-fixer.compact.md +459 -0
  11. package/agents/gsd-code-fixer.md +9 -8
  12. package/agents/gsd-code-reviewer.compact.md +269 -0
  13. package/agents/gsd-code-reviewer.md +15 -3
  14. package/agents/gsd-codebase-mapper.compact.md +760 -0
  15. package/agents/gsd-debug-session-manager.compact.md +360 -0
  16. package/agents/gsd-debug-session-manager.md +17 -2
  17. package/agents/gsd-debugger.md +2 -2
  18. package/agents/gsd-doc-classifier.compact.md +192 -0
  19. package/agents/gsd-doc-synthesizer.compact.md +200 -0
  20. package/agents/gsd-doc-verifier.compact.md +143 -0
  21. package/agents/gsd-doc-writer.compact.md +440 -0
  22. package/agents/gsd-dom-verifier.compact.md +138 -0
  23. package/agents/gsd-domain-researcher.compact.md +141 -0
  24. package/agents/gsd-eval-auditor.compact.md +160 -0
  25. package/agents/gsd-eval-auditor.md +1 -1
  26. package/agents/gsd-eval-planner.compact.md +137 -0
  27. package/agents/gsd-executor.md +13 -8
  28. package/agents/gsd-framework-selector.compact.md +82 -0
  29. package/agents/gsd-integration-checker.compact.md +245 -0
  30. package/agents/gsd-intel-updater.compact.md +226 -0
  31. package/agents/gsd-intel-updater.md +1 -1
  32. package/agents/gsd-mempalace-curator.compact.md +45 -0
  33. package/agents/gsd-nyquist-auditor.compact.md +179 -0
  34. package/agents/gsd-pattern-mapper.compact.md +275 -0
  35. package/agents/gsd-phase-researcher.md +19 -11
  36. package/agents/gsd-plan-checker.md +8 -7
  37. package/agents/gsd-planner.md +12 -8
  38. package/agents/gsd-project-researcher.compact.md +587 -0
  39. package/agents/gsd-project-researcher.md +1 -1
  40. package/agents/gsd-research-synthesizer.compact.md +212 -0
  41. package/agents/gsd-research-synthesizer.md +1 -1
  42. package/agents/gsd-roadmapper.compact.md +454 -0
  43. package/agents/gsd-roadmapper.md +13 -0
  44. package/agents/gsd-security-auditor.compact.md +162 -0
  45. package/agents/gsd-ui-auditor.compact.md +404 -0
  46. package/agents/gsd-ui-auditor.md +155 -17
  47. package/agents/gsd-ui-checker.compact.md +277 -0
  48. package/agents/gsd-ui-researcher.compact.md +282 -0
  49. package/agents/gsd-ui-researcher.md +1 -1
  50. package/agents/gsd-user-profiler.compact.md +108 -0
  51. package/agents/gsd-verifier.md +10 -9
  52. package/bin/install.js +848 -163
  53. package/commands/gsd/autonomous.md +2 -2
  54. package/commands/gsd/capture.md +1 -1
  55. package/commands/gsd/cleanup.md +1 -0
  56. package/commands/gsd/code-review.md +2 -1
  57. package/commands/gsd/complete-milestone.md +1 -0
  58. package/commands/gsd/config.md +1 -0
  59. package/commands/gsd/debug.md +1 -0
  60. package/commands/gsd/graphify.md +1 -0
  61. package/commands/gsd/health.md +1 -0
  62. package/commands/gsd/mempalace-capture.md +8 -3
  63. package/commands/gsd/mempalace-recall.md +1 -0
  64. package/commands/gsd/new-milestone.md +1 -0
  65. package/commands/gsd/new-project.md +1 -0
  66. package/commands/gsd/next.md +1 -0
  67. package/commands/gsd/pause-work.md +1 -0
  68. package/commands/gsd/phase.md +1 -0
  69. package/commands/gsd/plan-review-convergence.md +6 -6
  70. package/commands/gsd/pr-branch.md +1 -0
  71. package/commands/gsd/progress.md +1 -1
  72. package/commands/gsd/quick-batch.md +1 -1
  73. package/commands/gsd/resume-work.md +1 -0
  74. package/commands/gsd/review-backlog.md +1 -0
  75. package/commands/gsd/review.md +2 -3
  76. package/commands/gsd/settings.md +2 -1
  77. package/commands/gsd/stats.md +1 -0
  78. package/commands/gsd/thread.md +1 -0
  79. package/commands/gsd/workspace.md +1 -0
  80. package/commands/gsd/workstreams.md +1 -0
  81. package/gsd-core/bin/check-latest-version.cjs +8 -3
  82. package/gsd-core/bin/gsd-tools.cjs +672 -146
  83. package/gsd-core/bin/lib/adr-parser.cjs +4 -2
  84. package/gsd-core/bin/lib/artifacts.cjs +2 -1
  85. package/gsd-core/bin/lib/audit.cjs +119 -34
  86. package/gsd-core/bin/lib/broken-windows.cjs +168 -49
  87. package/gsd-core/bin/lib/capability-lifecycle.cjs +10 -6
  88. package/gsd-core/bin/lib/capability-loader.cjs +135 -1
  89. package/gsd-core/bin/lib/capability-registry.cjs +96 -189
  90. package/gsd-core/bin/lib/capability-source.cjs +19 -2
  91. package/gsd-core/bin/lib/capability-validator.cjs +14 -2
  92. package/gsd-core/bin/lib/check-command-router.cjs +213 -49
  93. package/gsd-core/bin/lib/code-review-depth.cjs +2 -2
  94. package/gsd-core/bin/lib/codex-agent-toml.cjs +21 -25
  95. package/gsd-core/bin/lib/commands.cjs +823 -112
  96. package/gsd-core/bin/lib/config-loader.cjs +66 -4
  97. package/gsd-core/bin/lib/config.cjs +186 -45
  98. package/gsd-core/bin/lib/coverage.cjs +1 -1
  99. package/gsd-core/bin/lib/decisions.cjs +164 -45
  100. package/gsd-core/bin/lib/external-descriptor-trust.cjs +29 -14
  101. package/gsd-core/bin/lib/frontmatter.cjs +13 -0
  102. package/gsd-core/bin/lib/graphify.cjs +10 -2
  103. package/gsd-core/bin/lib/gsd2-import.cjs +1 -2
  104. package/gsd-core/bin/lib/health-diagnostic-rules/state-consistency.cjs +12 -1
  105. package/gsd-core/bin/lib/health-diagnostic-rules/worktree-health.cjs +1 -1
  106. package/gsd-core/bin/lib/host-runtime-detection.cjs +9 -0
  107. package/gsd-core/bin/lib/init.cjs +614 -86
  108. package/gsd-core/bin/lib/install-engine.cjs +29 -3
  109. package/gsd-core/bin/lib/install-profiles.cjs +14 -0
  110. package/gsd-core/bin/lib/installer-migrations.cjs +41 -5
  111. package/gsd-core/bin/lib/loop-resolver.cjs +50 -31
  112. package/gsd-core/bin/lib/mcp-catalog.cjs +2 -2
  113. package/gsd-core/bin/lib/milestone.cjs +37 -13
  114. package/gsd-core/bin/lib/model-resolver.cjs +253 -53
  115. package/gsd-core/bin/lib/phase-command-router.cjs +16 -2
  116. package/gsd-core/bin/lib/phase-id-card.cjs +32 -0
  117. package/gsd-core/bin/lib/phase-id-display.cjs +78 -0
  118. package/gsd-core/bin/lib/phase-id.cjs +268 -27
  119. package/gsd-core/bin/lib/phase-lifecycle.cjs +61 -0
  120. package/gsd-core/bin/lib/phase-locator.cjs +29 -10
  121. package/gsd-core/bin/lib/phase.cjs +393 -88
  122. package/gsd-core/bin/lib/plan-document.cjs +49 -1
  123. package/gsd-core/bin/lib/planning-document.cjs +459 -0
  124. package/gsd-core/bin/lib/planning-inspect.cjs +52 -19
  125. package/gsd-core/bin/lib/planning-snapshot.cjs +61 -12
  126. package/gsd-core/bin/lib/planning-workspace.cjs +57 -3
  127. package/gsd-core/bin/lib/pr-branch-patterns.cjs +57 -0
  128. package/gsd-core/bin/lib/pristine-baseline.cjs +182 -0
  129. package/gsd-core/bin/lib/probe-core.cjs +7 -1
  130. package/gsd-core/bin/lib/prohibition-enforcement.cjs +91 -4
  131. package/gsd-core/bin/lib/project-root.cjs +41 -2
  132. package/gsd-core/bin/lib/quick-batch.cjs +1 -1
  133. package/gsd-core/bin/lib/refactor-trigger-command-router.cjs +61 -2
  134. package/gsd-core/bin/lib/research-store.cjs +11 -12
  135. package/gsd-core/bin/lib/review-lane-descriptor.cjs +10 -30
  136. package/gsd-core/bin/lib/review-lane-invocation.cjs +23 -0
  137. package/gsd-core/bin/lib/review-reviewer-selection.cjs +2 -2
  138. package/gsd-core/bin/lib/reviewer-step-dispatch.cjs +337 -0
  139. package/gsd-core/bin/lib/roadmap-command-router.cjs +12 -4
  140. package/gsd-core/bin/lib/roadmap-parser.cjs +219 -18
  141. package/gsd-core/bin/lib/roadmap-upgrade.cjs +1539 -13
  142. package/gsd-core/bin/lib/roadmap.cjs +356 -42
  143. package/gsd-core/bin/lib/runtime-artifact-conversion.cjs +310 -41
  144. package/gsd-core/bin/lib/runtime-artifact-install-plan.cjs +15 -4
  145. package/gsd-core/bin/lib/runtime-artifact-layout.cjs +13 -5
  146. package/gsd-core/bin/lib/runtime-homes.cjs +4 -0
  147. package/gsd-core/bin/lib/runtime-hooks-surface.cjs +408 -37
  148. package/gsd-core/bin/lib/runtime-name-policy.cjs +111 -1
  149. package/gsd-core/bin/lib/security.cjs +126 -7
  150. package/gsd-core/bin/lib/shell-command-projection.cjs +10 -6
  151. package/gsd-core/bin/lib/state-document.cjs +130 -28
  152. package/gsd-core/bin/lib/state-md-schema.cjs +21 -14
  153. package/gsd-core/bin/lib/state-transition.cjs +181 -30
  154. package/gsd-core/bin/lib/state.cjs +265 -27
  155. package/gsd-core/bin/lib/surface.cjs +77 -3
  156. package/gsd-core/bin/lib/task-command-router.cjs +12 -6
  157. package/gsd-core/bin/lib/tdd-red-evidence.cjs +78 -5
  158. package/gsd-core/bin/lib/uat-predicate.cjs +47 -4
  159. package/gsd-core/bin/lib/uat.cjs +9 -1
  160. package/gsd-core/bin/lib/ui-consideration-probe.cjs +15 -2
  161. package/gsd-core/bin/lib/ui-frontend-evidence.cjs +100 -9
  162. package/gsd-core/bin/lib/undo-commit-selection.cjs +131 -0
  163. package/gsd-core/bin/lib/update-context.cjs +30 -24
  164. package/gsd-core/bin/lib/vendor/js-yaml.cjs +11 -3
  165. package/gsd-core/bin/lib/verification.cjs +315 -30
  166. package/gsd-core/bin/lib/verify-command-grounding.cjs +47 -3
  167. package/gsd-core/bin/lib/verify.cjs +320 -48
  168. package/gsd-core/bin/lib/workstream-inventory.cjs +1 -0
  169. package/gsd-core/bin/lib/worktree-base-ref.cjs +482 -73
  170. package/gsd-core/bin/lib/worktree-safety.cjs +797 -58
  171. package/gsd-core/bin/shared/config-defaults.manifest.json +4 -0
  172. package/gsd-core/bin/shared/config-schema.manifest.json +6 -0
  173. package/gsd-core/bin/verify-reapply-patches.cjs +439 -80
  174. package/gsd-core/references/checkpoints.md +5 -3
  175. package/gsd-core/references/compact-content-gate.md +66 -0
  176. package/gsd-core/references/edge-probe-fixtures/01-round-half-even/expected-coverage.json +28 -3
  177. package/gsd-core/references/edge-probe-fixtures/02-merge-intervals/expected-coverage.json +37 -4
  178. package/gsd-core/references/edge-probe-fixtures/03-truncate-graphemes/expected-coverage.json +28 -3
  179. package/gsd-core/references/edge-probe-fixtures/04-money-rounding/expected-coverage.json +28 -3
  180. package/gsd-core/references/edge-probe-fixtures/05-list-dedupe/expected-coverage.json +37 -4
  181. package/gsd-core/references/edge-probe-fixtures/06-resolved-mixed/expected-coverage.json +37 -4
  182. package/gsd-core/references/edge-probe.md +195 -21
  183. package/gsd-core/references/execute-phase-between-wave-reset.md +7 -6
  184. package/gsd-core/references/execute-phase-wave-guard.md +22 -11
  185. package/gsd-core/references/gsd-run-resolver.md +1 -1
  186. package/gsd-core/references/loop-hook-dispatch.md +18 -0
  187. package/gsd-core/references/model-profiles.md +13 -4
  188. package/gsd-core/references/phase-argument-parsing.md +9 -7
  189. package/gsd-core/references/phase-id-convention.md +28 -0
  190. package/gsd-core/references/planner-gap-closure.md +2 -0
  191. package/gsd-core/references/planner-load-graph-context.md +24 -13
  192. package/gsd-core/references/planner-verify-command-grounding.md +14 -0
  193. package/gsd-core/references/planning-config.md +14 -2
  194. package/gsd-core/references/tdd.md +30 -4
  195. package/gsd-core/references/thinking-models-planning.md +18 -2
  196. package/gsd-core/references/ui-consideration-probe.md +10 -5
  197. package/gsd-core/references/verification-patterns.md +17 -4
  198. package/gsd-core/references/verify-command-path-resolvability.md +10 -2
  199. package/gsd-core/references/worktree-path-safety.md +433 -2
  200. package/gsd-core/templates/README.md +7 -1
  201. package/gsd-core/templates/state.md +6 -3
  202. package/gsd-core/templates/summary.compact.md +212 -0
  203. package/gsd-core/templates/user-setup.compact.md +199 -0
  204. package/gsd-core/templates/user-setup.md +0 -9
  205. package/gsd-core/templates/verification-report.md +1 -1
  206. package/gsd-core/workflows/_runtime-launcher.snippet.sh +1 -1
  207. package/gsd-core/workflows/add-backlog.md +1 -1
  208. package/gsd-core/workflows/add-phase.md +1 -1
  209. package/gsd-core/workflows/add-tests.md +2 -2
  210. package/gsd-core/workflows/add-todo.md +6 -5
  211. package/gsd-core/workflows/ai-integration-phase.md +11 -3
  212. package/gsd-core/workflows/audit-fix.md +1 -1
  213. package/gsd-core/workflows/audit-milestone.md +1 -1
  214. package/gsd-core/workflows/audit-uat.md +1 -1
  215. package/gsd-core/workflows/autonomous/steps/converge-fail-fast.md +9 -18
  216. package/gsd-core/workflows/autonomous.md +29 -16
  217. package/gsd-core/workflows/check-todos.md +6 -4
  218. package/gsd-core/workflows/cleanup.md +5 -3
  219. package/gsd-core/workflows/code-review/steps/dispatch-fix.md +4 -3
  220. package/gsd-core/workflows/code-review/steps/structural-pre-pass.md +8 -1
  221. package/gsd-core/workflows/code-review-fix.md +108 -22
  222. package/gsd-core/workflows/code-review.md +216 -73
  223. package/gsd-core/workflows/complete-milestone/detail/elaboration.md +274 -0
  224. package/gsd-core/workflows/complete-milestone.md +41 -264
  225. package/gsd-core/workflows/debug.md +3 -3
  226. package/gsd-core/workflows/diagnose-issues.md +1 -1
  227. package/gsd-core/workflows/discuss-phase/modes/advisor.md +1 -1
  228. package/gsd-core/workflows/discuss-phase/modes/chain.md +1 -1
  229. package/gsd-core/workflows/discuss-phase-assumptions.md +1 -1
  230. package/gsd-core/workflows/discuss-phase.md +1 -1
  231. package/gsd-core/workflows/do.md +2 -2
  232. package/gsd-core/workflows/docs-update/detail/elaboration.md +179 -0
  233. package/gsd-core/workflows/docs-update.md +17 -158
  234. package/gsd-core/workflows/edit-phase.md +1 -1
  235. package/gsd-core/workflows/eval-review.md +10 -3
  236. package/gsd-core/workflows/execute-phase/detail/elaboration.md +124 -0
  237. package/gsd-core/workflows/execute-phase/steps/code-review-disposition.md +1017 -0
  238. package/gsd-core/workflows/execute-phase/steps/codebase-drift-gate.md +19 -4
  239. package/gsd-core/workflows/execute-phase/steps/completion-reconciliation.md +56 -0
  240. package/gsd-core/workflows/execute-phase/steps/executor-isolation-dispatch.md +43 -4
  241. package/gsd-core/workflows/execute-phase/steps/executor-progress-policy.md +43 -0
  242. package/gsd-core/workflows/execute-phase/steps/gap-closure-artifacts.md +1 -1
  243. package/gsd-core/workflows/execute-phase/steps/partial-wave.md +1 -1
  244. package/gsd-core/workflows/execute-phase/steps/per-plan-executor-routing.md +1 -1
  245. package/gsd-core/workflows/execute-phase/steps/per-plan-worktree-gate.md +1 -1
  246. package/gsd-core/workflows/execute-phase/steps/post-merge-gate.md +46 -9
  247. package/gsd-core/workflows/execute-phase/steps/protected-branch.md +1 -1
  248. package/gsd-core/workflows/execute-phase/steps/ready-wave-gate.md +37 -0
  249. package/gsd-core/workflows/execute-phase/steps/regression-gate-run.md +1 -1
  250. package/gsd-core/workflows/execute-phase/steps/sequential-root-pin.md +35 -0
  251. package/gsd-core/workflows/execute-phase/steps/stale-reverification.md +24 -0
  252. package/gsd-core/workflows/execute-phase/steps/tdd-applicability-resolution.md +1 -1
  253. package/gsd-core/workflows/execute-phase/steps/threat-id-gate.md +28 -0
  254. package/gsd-core/workflows/execute-phase/steps/wave-post-gate-hooks.md +1 -1
  255. package/gsd-core/workflows/execute-phase/steps/worktree-base-check.md +25 -0
  256. package/gsd-core/workflows/execute-phase.md +83 -172
  257. package/gsd-core/workflows/execute-plan.md +24 -10
  258. package/gsd-core/workflows/explore.md +3 -3
  259. package/gsd-core/workflows/extract-learnings.md +2 -1
  260. package/gsd-core/workflows/fast.md +1 -1
  261. package/gsd-core/workflows/forensics.md +1 -1
  262. package/gsd-core/workflows/graduation.md +1 -1
  263. package/gsd-core/workflows/health.md +2 -2
  264. package/gsd-core/workflows/help/modes/full.compact.md +398 -0
  265. package/gsd-core/workflows/help/modes/full.md +5 -5
  266. package/gsd-core/workflows/help/modes/topic.md +15 -5
  267. package/gsd-core/workflows/help.md +1 -1
  268. package/gsd-core/workflows/import.md +2 -2
  269. package/gsd-core/workflows/inbox.md +2 -2
  270. package/gsd-core/workflows/ingest-docs.md +3 -3
  271. package/gsd-core/workflows/insert-phase.md +1 -1
  272. package/gsd-core/workflows/list-seeds.md +1 -1
  273. package/gsd-core/workflows/list-workspaces.md +1 -1
  274. package/gsd-core/workflows/manager.md +2 -2
  275. package/gsd-core/workflows/map-codebase.md +52 -5
  276. package/gsd-core/workflows/milestone-summary.md +1 -1
  277. package/gsd-core/workflows/mvp-phase.md +1 -1
  278. package/gsd-core/workflows/new-milestone.md +56 -14
  279. package/gsd-core/workflows/new-project/detail/elaboration.md +216 -0
  280. package/gsd-core/workflows/new-project/steps/auto-mode-config.md +3 -3
  281. package/gsd-core/workflows/new-project/steps/codebase-map-offer.md +1 -1
  282. package/gsd-core/workflows/new-project.md +39 -209
  283. package/gsd-core/workflows/new-workspace.md +2 -2
  284. package/gsd-core/workflows/next.md +1 -1
  285. package/gsd-core/workflows/note.md +1 -1
  286. package/gsd-core/workflows/onboard.md +1 -1
  287. package/gsd-core/workflows/pause-work.md +1 -1
  288. package/gsd-core/workflows/plan-phase/detail/elaboration.md +209 -0
  289. package/gsd-core/workflows/plan-phase/steps/chunked-planning-mode.md +17 -5
  290. package/gsd-core/workflows/plan-phase/steps/prd-express-path.md +1 -1
  291. package/gsd-core/workflows/plan-phase/steps/stall-detection-helpers.md +23 -5
  292. package/gsd-core/workflows/plan-phase.md +45 -187
  293. package/gsd-core/workflows/plan-review-convergence.md +21 -5
  294. package/gsd-core/workflows/plant-seed.md +62 -20
  295. package/gsd-core/workflows/pr-branch.md +132 -20
  296. package/gsd-core/workflows/profile-user.md +2 -2
  297. package/gsd-core/workflows/progress.md +1 -1
  298. package/gsd-core/workflows/quick/steps/plan-checker-loop.md +25 -0
  299. package/gsd-core/workflows/quick/steps/quick-verification.md +1 -1
  300. package/gsd-core/workflows/quick/steps/worktree-pre-dispatch-commit.md +1 -1
  301. package/gsd-core/workflows/quick-batch/steps/batch-init.md +1 -1
  302. package/gsd-core/workflows/quick-batch/steps/completion.md +1 -1
  303. package/gsd-core/workflows/quick-batch/steps/merge-wave.md +1 -1
  304. package/gsd-core/workflows/quick-batch/steps/planner-wave.md +1 -1
  305. package/gsd-core/workflows/quick-batch/steps/research-phase.md +1 -1
  306. package/gsd-core/workflows/quick-batch/steps/resume-mode.md +1 -1
  307. package/gsd-core/workflows/quick-batch/steps/verification-wave.md +1 -1
  308. package/gsd-core/workflows/quick-batch/steps/worktree-dispatch.md +1 -1
  309. package/gsd-core/workflows/quick-batch.md +1 -1
  310. package/gsd-core/workflows/quick.md +29 -10
  311. package/gsd-core/workflows/reapply-patches.md +86 -6
  312. package/gsd-core/workflows/remove-phase.md +1 -1
  313. package/gsd-core/workflows/remove-workspace.md +2 -2
  314. package/gsd-core/workflows/resume-project.md +1 -1
  315. package/gsd-core/workflows/review.md +31 -16
  316. package/gsd-core/workflows/scan.md +1 -1
  317. package/gsd-core/workflows/secure-phase.md +3 -2
  318. package/gsd-core/workflows/settings-advanced.md +30 -10
  319. package/gsd-core/workflows/settings-integrations.md +2 -3
  320. package/gsd-core/workflows/settings.md +22 -9
  321. package/gsd-core/workflows/ship.md +3 -2
  322. package/gsd-core/workflows/sketch-wrap-up.md +1 -1
  323. package/gsd-core/workflows/sketch.md +1 -1
  324. package/gsd-core/workflows/smart-entry.md +2 -2
  325. package/gsd-core/workflows/spec-phase.md +15 -5
  326. package/gsd-core/workflows/spike-wrap-up.md +1 -1
  327. package/gsd-core/workflows/spike.md +1 -1
  328. package/gsd-core/workflows/stats.md +1 -1
  329. package/gsd-core/workflows/sync-skills.md +5 -5
  330. package/gsd-core/workflows/thread.md +1 -1
  331. package/gsd-core/workflows/transition.md +1 -1
  332. package/gsd-core/workflows/ui-phase.md +44 -8
  333. package/gsd-core/workflows/ui-review.md +18 -4
  334. package/gsd-core/workflows/ultraplan-phase.md +1 -1
  335. package/gsd-core/workflows/undo.md +339 -20
  336. package/gsd-core/workflows/update.md +14 -12
  337. package/gsd-core/workflows/validate-phase.md +3 -2
  338. package/gsd-core/workflows/verify-work/detail/elaboration.md +230 -0
  339. package/gsd-core/workflows/verify-work/steps/automated-ui-verification.md +1 -1
  340. package/gsd-core/workflows/verify-work/steps/mvp-uat-framing.md +1 -1
  341. package/gsd-core/workflows/verify-work.md +101 -196
  342. package/hooks/dist/gsd-agent-isolation-guard.js +66 -16
  343. package/hooks/dist/gsd-context-monitor.js +88 -15
  344. package/hooks/dist/gsd-cursor-subagent-start.js +34 -14
  345. package/hooks/dist/gsd-secret-read-guard.js +71 -19
  346. package/hooks/dist/gsd-statusline.js +81 -20
  347. package/hooks/dist/gsd-validate-commit.sh +97 -8
  348. package/hooks/dist/gsd-worktree-path-guard.js +25 -14
  349. package/hooks/dist/gsd-write-guard.js +46 -1
  350. package/hooks/dist/lib/dispatch-identity.js +187 -0
  351. package/hooks/dist/lib/filename-classification.js +64 -0
  352. package/hooks/dist/lib/isolation-deny-reason.js +53 -1
  353. package/hooks/dist/lib/isolation-sentinel.js +58 -19
  354. package/hooks/gsd-agent-isolation-guard.js +66 -16
  355. package/hooks/gsd-context-monitor.js +88 -15
  356. package/hooks/gsd-cursor-subagent-start.js +34 -14
  357. package/hooks/gsd-secret-read-guard.js +71 -19
  358. package/hooks/gsd-statusline.js +81 -20
  359. package/hooks/gsd-validate-commit.sh +97 -8
  360. package/hooks/gsd-worktree-path-guard.js +25 -14
  361. package/hooks/gsd-write-guard.js +46 -1
  362. package/hooks/lib/dispatch-identity.js +187 -0
  363. package/hooks/lib/filename-classification.js +64 -0
  364. package/hooks/lib/isolation-deny-reason.js +53 -1
  365. package/hooks/lib/isolation-sentinel.js +58 -19
  366. package/package.json +11 -6
  367. package/scripts/benchmark-compact-content-variants.cjs +298 -0
  368. package/scripts/benchmark-compact-content.cjs +368 -0
  369. package/scripts/build-hooks.js +15 -6
  370. package/scripts/check-contract-drift.cjs +131 -12
  371. package/scripts/check-env.cjs +36 -8
  372. package/scripts/check-glossary-refs.cjs +25 -21
  373. package/scripts/ci-next-health.cjs +271 -0
  374. package/scripts/ci-prepare-test-scope.cjs +7 -7
  375. package/scripts/ci-test-scope.cjs +126 -20
  376. package/scripts/ci-timeout-report.cjs +1 -1
  377. package/scripts/command-contract-helpers.cjs +3 -0
  378. package/scripts/diff-touches-shipped-paths.cjs +1 -1
  379. package/scripts/docs-guard-registry.cjs +35 -2
  380. package/scripts/gen-adr-index.cjs +8 -2
  381. package/scripts/gen-inventory-manifest.cjs +12 -0
  382. package/scripts/gen-loop-host-contract.cjs +69 -0
  383. package/scripts/gen-platform-conformance-tier.cjs +557 -0
  384. package/scripts/lib/drift-scan.cjs +1 -1
  385. package/scripts/lib/macos-conformance-tier.generated.cjs +224 -0
  386. package/scripts/lib/ndjson-reporter.cjs +3 -2
  387. package/scripts/lib/npm-version-check-diagnosis.cjs +59 -0
  388. package/scripts/lib/platform-conformance-tier.generated.cjs +287 -0
  389. package/scripts/lib/suite-detection.cjs +32 -0
  390. package/scripts/lint-allowed-tools-parity.cjs +221 -0
  391. package/scripts/lint-docs-guard-registration.exempt-baseline.cjs +47 -3
  392. package/scripts/lint-phase-arg-assignment.cjs +257 -0
  393. package/scripts/lint-phase-id-drift.cjs +623 -13
  394. package/scripts/lint-pr-branch-pattern-drift.cjs +148 -0
  395. package/scripts/lint-response-language-coverage.cjs +9 -3
  396. package/scripts/lint-retired-runtime-name.cjs +619 -0
  397. package/scripts/lint-source-test-name-collision.cjs +1 -1
  398. package/scripts/lint-state-write-path-drift.cjs +93 -0
  399. package/scripts/lint-test-file-count.allowlist.json +29 -9
  400. package/scripts/lint-vendored-deps.cjs +128 -17
  401. package/scripts/lint-workflow-shellcheck-baseline.json +100 -0
  402. package/scripts/prompt-injection-scan.sh +18 -0
  403. package/scripts/release-tarball-smoke.cjs +194 -1
  404. package/scripts/workflow-size.cjs +139 -0
  405. package/skills/gsd-autonomous/SKILL.md +2 -2
  406. package/skills/gsd-capture/SKILL.md +1 -1
  407. package/skills/gsd-cleanup/SKILL.md +1 -0
  408. package/skills/gsd-code-review/SKILL.md +2 -1
  409. package/skills/gsd-complete-milestone/SKILL.md +1 -0
  410. package/skills/gsd-config/SKILL.md +1 -0
  411. package/skills/gsd-debug/SKILL.md +1 -0
  412. package/skills/gsd-graphify/SKILL.md +1 -0
  413. package/skills/gsd-health/SKILL.md +1 -0
  414. package/skills/gsd-mempalace-capture/SKILL.md +8 -3
  415. package/skills/gsd-mempalace-recall/SKILL.md +1 -0
  416. package/skills/gsd-new-milestone/SKILL.md +1 -0
  417. package/skills/gsd-new-project/SKILL.md +1 -0
  418. package/skills/gsd-next/SKILL.md +1 -0
  419. package/skills/gsd-pause-work/SKILL.md +1 -0
  420. package/skills/gsd-phase/SKILL.md +1 -0
  421. package/skills/gsd-plan-review-convergence/SKILL.md +5 -5
  422. package/skills/gsd-pr-branch/SKILL.md +1 -0
  423. package/skills/gsd-progress/SKILL.md +1 -1
  424. package/skills/gsd-quick-batch/SKILL.md +1 -1
  425. package/skills/gsd-resume-work/SKILL.md +1 -0
  426. package/skills/gsd-review/SKILL.md +2 -3
  427. package/skills/gsd-review-backlog/SKILL.md +1 -0
  428. package/skills/gsd-settings/SKILL.md +2 -1
  429. package/skills/gsd-stats/SKILL.md +1 -0
  430. package/skills/gsd-thread/SKILL.md +1 -0
  431. package/skills/gsd-workspace/SKILL.md +1 -0
  432. package/skills/gsd-workstreams/SKILL.md +1 -0
  433. package/vscode/package.json +1 -1
  434. package/gsd-core/templates/claude-md.md +0 -145
  435. package/gsd-core/templates/codebase/concerns.md +0 -310
  436. package/gsd-core/templates/codebase/conventions.md +0 -307
  437. package/gsd-core/templates/codebase/integrations.md +0 -280
  438. package/gsd-core/templates/codebase/structure.md +0 -285
  439. package/gsd-core/templates/codebase/testing.md +0 -480
  440. package/gsd-core/templates/debug-subagent-prompt.md +0 -91
  441. package/gsd-core/templates/discovery.md +0 -146
@@ -61,6 +61,13 @@ const planDependencyGraphMod = require("./plan-dependency-graph.cjs");
61
61
  // eslint-disable-next-line @typescript-eslint/no-require-imports
62
62
  const verificationMod = require("./verification.cjs");
63
63
  const { isPhaseComplete } = verificationMod;
64
+ // #4129: the single owner of "count the ROADMAP's milestone Complete rows"
65
+ // (phase-lifecycle.cts) — reused for the completed-phases numerator floor so
66
+ // this scan cannot grow a second ROADMAP parser. Pure computation module (no
67
+ // I/O), so it introduces no cycle on this path.
68
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
69
+ const phaseLifecycleMod = require("./phase-lifecycle.cjs");
70
+ const { deriveProgressFromRoadmap } = phaseLifecycleMod;
64
71
  // eslint-disable-next-line @typescript-eslint/no-require-imports
65
72
  const planningScopeMod = require("./planning-scope.cjs");
66
73
  const { SCOPE } = planningScopeMod;
@@ -286,7 +293,7 @@ function cmdStateGet(cwd, section, raw) {
286
293
  // Try to find markdown section or field
287
294
  const fieldEscaped = (0, pattern_cjs_1.escapeRegex)(section);
288
295
  // Check for **field:** value (bold format)
289
- const boldPattern = new RegExp(`\\*\\*${fieldEscaped}:\\*\\*\\s*(.*)`, 'i');
296
+ const boldPattern = new RegExp(`^[ \\t]*\\*\\*${fieldEscaped}:\\*\\*[ \\t]*(.*)`, 'im');
290
297
  const boldMatch = content.match(boldPattern);
291
298
  if (boldMatch) {
292
299
  output({ [section]: boldMatch[1].trim() }, raw, boldMatch[1].trim());
@@ -314,13 +321,10 @@ function readTextArgOrFile(cwd, value, filePath, label) {
314
321
  return value;
315
322
  // Path traversal guard: ensure file resolves within project directory
316
323
  // eslint-disable-next-line @typescript-eslint/no-require-imports, @typescript-eslint/unbound-method
317
- const { validatePath } = require('./security.cjs');
318
- const pathCheck = validatePath(filePath, cwd, { allowAbsolute: true });
319
- if (!pathCheck.safe) {
320
- throw new Error(`${label} path rejected: ${pathCheck.error}`);
321
- }
324
+ const { assertWithinRoot, PathAcceptance } = require('./security.cjs');
325
+ const contained = assertWithinRoot(filePath, cwd, `${label} path`, PathAcceptance.AbsoluteInsideRoot);
322
326
  try {
323
- return node_fs_1.default.readFileSync(pathCheck.resolved, 'utf-8').trimEnd();
327
+ return node_fs_1.default.readFileSync(contained, 'utf-8').trimEnd();
324
328
  }
325
329
  catch {
326
330
  throw new Error(`${label} file not found: ${filePath}`);
@@ -489,7 +493,31 @@ function cmdStateUpdate(cwd, field, value) {
489
493
  // (#3345's direction) — reported separately from `updated` because this
490
494
  // command's contract is a single-field boolean, not a per-field array.
491
495
  const reconciled = reconcileReportedFields(statePath, preWriteState, updated ? [field] : [], divergedFields);
492
- updated = reconciled.includes(field);
496
+ // #4488: `updateCore` itself already told us whether it matched the field
497
+ // (`updated`, captured above `readModifyWriteStateMd` runs it) — that is a
498
+ // real signal, not a guess. `reconcileReportedFields` answers a DIFFERENT
499
+ // question ("what changed on disk") and, per its own docstring, reports
500
+ // `[]` whenever `preWriteState.fm` is `undefined`. That happens in two
501
+ // known cases, both of which mean "no snapshot was ever captured", not
502
+ // "nothing happened": (a) `readModifyWriteStateMd`'s #948 no-op guard
503
+ // fires because the transform's output was byte-identical to the input —
504
+ // the requested value already equals what's on disk, so the field WAS
505
+ // found and there was simply nothing left to change; (b)
506
+ // `applyPostSyncPreservation`'s `isUnparseableFrontmatter` early return —
507
+ // the ORIGINAL frontmatter block was malformed, so preservation never
508
+ // runs, yet `readModifyWriteStateMd` still persists the transform's raw
509
+ // output via `platformWriteSync`. In neither case did preservation
510
+ // discard or rewrite what the transform wrote, so trusting the
511
+ // transform's own `updated` signal here is never a false positive.
512
+ // Collapsing either case into the same `false` as "field not found" is
513
+ // the #4488 bug — `explainUpdateFailure` then reports a message that is
514
+ // actively false (it tells the caller to add a line that is already
515
+ // there). Every other `false` origin (case-D fallback did not apply, or
516
+ // the transform genuinely found nothing) is unaffected: there
517
+ // `preWriteState.fm` is defined (a normal sync ran) or `updated` was
518
+ // already false before this line.
519
+ const noopBecauseAlreadyCorrect = updated && preWriteState.fm === undefined;
520
+ updated = reconciled.includes(field) || noopBecauseAlreadyCorrect;
493
521
  const preserved = reconciled.filter((f) => f !== field);
494
522
  if (updated) {
495
523
  // #3699 case D: surfaced so a caller can tell "wrote the body source" from
@@ -1560,6 +1588,18 @@ function cmdStateResolveBlocker(cwd, text, raw) {
1560
1588
  }
1561
1589
  }
1562
1590
  function cmdStateRecordSession(cwd, options, raw) {
1591
+ // #4186: a bare invocation is a usage error, not a heartbeat write. The
1592
+ // pre-#4186 handler accepted zero arguments and still refreshed
1593
+ // `Last session` / `Last Date` / `last_updated` — a caller probing the
1594
+ // command's signature (the way other subcommands encourage) silently
1595
+ // mutated STATE.md. Mirrors `state update`'s required-arg guard
1596
+ // (cmdStateUpdate: `error('field and value required for state update')`),
1597
+ // including its ordering: validation precedes the STATE.md existence
1598
+ // check. Either flag suffices — `--resume-file` alone carries an explicit
1599
+ // value the handler must persist.
1600
+ if (!options.stopped_at && (options.resume_file === undefined || options.resume_file === null)) {
1601
+ error('stopped-at or resume-file required for state record-session');
1602
+ }
1563
1603
  const statePath = planningPaths(cwd).state;
1564
1604
  if (!node_fs_1.default.existsSync(statePath)) {
1565
1605
  output({ error: 'STATE.md not found' }, raw, undefined);
@@ -1569,10 +1609,31 @@ function cmdStateRecordSession(cwd, options, raw) {
1569
1609
  const updated = [];
1570
1610
  let sessionCreated = false;
1571
1611
  const divergedFields = [];
1612
+ // #4763 (1): last-writer-wins stays (the recorded single-slot handoff design),
1613
+ // but a displaced record is no longer silent. The pre-write session record is
1614
+ // captured below and surfaced in the payload under `replacedRecord` whenever a
1615
+ // replacement actually changes a non-empty prior value.
1616
+ const priorRecord = {};
1572
1617
  // ADR-3473 §8.7 (#3872): caller-allocated out-param, filled with the
1573
1618
  // transaction's own pre-write snapshot + body by `applyPostSyncPreservation`.
1574
1619
  const preWriteState = {};
1575
1620
  readModifyWriteStateMd(statePath, (content) => {
1621
+ // #4763 (1): read the pre-write session record. The capture mirrors the
1622
+ // WRITER, not the snapshot reader: stateReplaceField replaces the FIRST
1623
+ // case-insensitive label match anywhere in the document, so the capture is
1624
+ // document-wide too — scoping it to ## Session would miss an archive-section
1625
+ // line the writer actually displaced. Continuation lines join via
1626
+ // stateFieldContinuation (the sanctioned joiner — stateExtractField alone is
1627
+ // first-line-only), so a wrapped multi-line handoff is surfaced whole.
1628
+ const capturePrior = (fieldName) => {
1629
+ const first = (0, state_document_cjs_1.stateExtractField)(content, fieldName);
1630
+ if (first === null)
1631
+ return undefined;
1632
+ const cont = (0, state_document_cjs_1.stateFieldContinuation)(content, fieldName);
1633
+ return cont ? `${first}\n${cont}` : first;
1634
+ };
1635
+ priorRecord.stoppedAt = capturePrior('Stopped At');
1636
+ priorRecord.resumeFile = capturePrior('Resume File');
1576
1637
  // Update Last session / Last Date
1577
1638
  let result = (0, state_document_cjs_1.stateReplaceField)(content, 'Last session', now);
1578
1639
  if (result) {
@@ -1796,6 +1857,27 @@ function cmdStateRecordSession(cwd, options, raw) {
1796
1857
  const result = { recorded: true, updated: reconciledUpdated };
1797
1858
  if (sessionCreated)
1798
1859
  result['created'] = true;
1860
+ // #4763 (1): surface any non-empty prior record the write displaced. Gated
1861
+ // on reconciledUpdated (the fields that actually persisted, post-#3957
1862
+ // reconcile) rather than the pre-reconciliation updated[]. Only fields with
1863
+ // real prior content differing from the caller's value count — same-value
1864
+ // rewrites, the insert path (no prior label), and the #944 template-default
1865
+ // DWIM (defaults match case-insensitively, so a case-variant 'none' →
1866
+ // 'None' rewrite is normalization, not displacement) are excluded.
1867
+ const isResumeTemplateDefault = priorRecord.resumeFile !== undefined
1868
+ && state_document_cjs_1.KNOWN_TEMPLATE_DEFAULTS['Resume File'].some((d) => d.toLowerCase() === priorRecord.resumeFile?.toLowerCase());
1869
+ const replacedRecord = {};
1870
+ if (reconciledUpdated.includes('Stopped At') && priorRecord.stoppedAt
1871
+ && priorRecord.stoppedAt !== options.stopped_at) {
1872
+ replacedRecord['Stopped At'] = priorRecord.stoppedAt;
1873
+ }
1874
+ if (reconciledUpdated.includes('Resume File') && priorRecord.resumeFile
1875
+ && !isResumeTemplateDefault
1876
+ && priorRecord.resumeFile !== (options.resume_file ?? undefined)) {
1877
+ replacedRecord['Resume File'] = priorRecord.resumeFile;
1878
+ }
1879
+ if (Object.keys(replacedRecord).length > 0)
1880
+ result['replacedRecord'] = replacedRecord;
1799
1881
  output(result, raw, 'true');
1800
1882
  }
1801
1883
  else if (updated.length === 0) {
@@ -2512,7 +2594,10 @@ storedCompletedPhases, storedTotalPlans, storedCompletedPlans) {
2512
2594
  // own comment on that field). Folding this consumer onto the raw
2513
2595
  // summaries-met flag was the exact "consolidate two of three and
2514
2596
  // leave the third" gap §7.4's forcing function rules out.
2515
- if (isPhaseComplete(phaseDir).value.complete)
2597
+ // #612: `phaseConvention` threaded so a bracket phase dir resolves
2598
+ // and scopes its verification report like its legacy twin — the
2599
+ // read-side half of the same thread cmdStateSync gets below.
2600
+ if (isPhaseComplete(phaseDir, { convention: phaseConvention }).value.complete)
2516
2601
  diskCompletedPhases++;
2517
2602
  }
2518
2603
  // Count phase headings from ROADMAP — single source of truth for
@@ -2619,6 +2704,34 @@ storedCompletedPhases, storedTotalPlans, storedCompletedPlans) {
2619
2704
  // write silently clobbered the three stored siblings with the
2620
2705
  // under-scoped disk numbers.
2621
2706
  const diskCountsWithheld = milestonedButUnbounded || roadmapAbsentWithAssertedMilestone;
2707
+ // #4129: floor the completed-phases numerator at the ROADMAP's own
2708
+ // milestone Complete-row count. The disk numerator counts ONLY
2709
+ // phase dirs whose *-VERIFICATION.md routes `passed` (isPhaseComplete,
2710
+ // #2957 disk-strict — the gate stays untouched), so a completed
2711
+ // phase whose verification reads `stale` (a SUMMARY committed or
2712
+ // edited after it, #2348 clean-commit-time clock) or `missing`
2713
+ // (pre-verification era, hand-flipped ROADMAP row) drops out of the
2714
+ // count forever — while every other surface (the ROADMAP row
2715
+ // `phase complete` just flipped, the body `Completed Phases` field
2716
+ // completePhaseCore derives from deriveProgressFromRoadmap) still
2717
+ // asserts the phase complete. max(disk, ROADMAP) keeps the disk
2718
+ // signal for gap detection (a verification-passed phase whose ROADMAP
2719
+ // row is not yet flipped still counts) while never UNDER-counting
2720
+ // what the ROADMAP asserts. Scoped exactly like the denominator:
2721
+ // the same milestone window (roadmapScope), the same
2722
+ // safeToUseRoadmapCount gate, and never under the #3354/#3573
2723
+ // withhold — a whole-document Complete-row count must not leak
2724
+ // through an untrustworthy scope. Reuses deriveProgressFromRoadmap
2725
+ // (phase-lifecycle.cts, the one owner of "read the Progress table")
2726
+ // — no second ROADMAP parser here. A ROADMAP without a canonical
2727
+ // `## Progress` table resolves no table → floor inert (disk count
2728
+ // stands), the owner's own answer to "what is countable".
2729
+ const roadmapCompletedPhases = roadmapScope !== null && safeToUseRoadmapCount && !diskCountsWithheld
2730
+ ? deriveProgressFromRoadmap(roadmapScope).completedPhases
2731
+ : null;
2732
+ const flooredCompletedPhases = roadmapCompletedPhases !== null
2733
+ ? Math.max(diskCompletedPhases, roadmapCompletedPhases)
2734
+ : diskCompletedPhases;
2622
2735
  return {
2623
2736
  // The two WITHHOLD shapes (#3354 milestoned-but-unbounded, #3573
2624
2737
  // roadmap-absent-with-asserted-milestone) must be evaluated BEFORE
@@ -2629,7 +2742,7 @@ storedCompletedPhases, storedTotalPlans, storedCompletedPlans) {
2629
2742
  ? null
2630
2743
  : (safeToUseRoadmapCount ? Math.max(phaseDirs.length, roadmapPhaseCount) : phaseDirs.length),
2631
2744
  milestoneBounded,
2632
- completedPhases: diskCountsWithheld ? null : diskCompletedPhases,
2745
+ completedPhases: diskCountsWithheld ? null : flooredCompletedPhases,
2633
2746
  totalPlans: diskCountsWithheld ? null : diskTotalPlans,
2634
2747
  completedPlans: diskCountsWithheld ? null : diskTotalSummaries,
2635
2748
  phaseDirScope,
@@ -2722,19 +2835,19 @@ storedCompletedPhases, storedTotalPlans, storedCompletedPlans) {
2722
2835
  progressPercent = parseInt(pctMatch[1], 10);
2723
2836
  }
2724
2837
  let normalizedStatus = (0, state_document_cjs_1.normalizeStateStatus)(status, pausedAt);
2725
- // #3578: normalizeStateStatus matches 'complete' as a case-insensitive
2726
- // SUBSTRING, so the phase-completion prose cmdStateCompletePhase writes to
2727
- // the body (`Phase ${N} complete`) collapses to the milestone-level
2728
- // 'completed' status even when other phases remain open. Phase-level
2729
- // prose must never decide milestone-level status — completedPhases /
2730
- // totalPhases / diskScope, already derived above from a disk scan, are
2731
- // the authority on whether the MILESTONE is actually done. Only override
2732
- // when: (a) normalizeStateStatus actually landed on 'completed'; (b) the
2733
- // raw prose is UNAMBIGUOUSLY phase-completion prose — the anchored
2734
- // pattern below deliberately excludes "All phases complete" (no `\S+`
2735
- // phase token) and milestone-close prose like "v1.0 milestone complete"
2736
- // (no leading "phase"); and (c) the counters are trustworthy (a COMPLETE
2737
- // disk scope, both counts are finite numbers, and a positive
2838
+ // #3578: the declared status vocabulary (#4186) recognizes
2839
+ // `Phase ${N} complete` (state.cts's own phase-completion write) and maps
2840
+ // it to `completed`, so the phase-completion prose still collapses to the
2841
+ // milestone-level status even when other phases remain open — this guard
2842
+ // demotes it back. Phase-level prose must never decide milestone-level
2843
+ // status — completedPhases / totalPhases / diskScope, already derived above
2844
+ // from a disk scan, are the authority on whether the MILESTONE is actually
2845
+ // done. Only override when: (a) normalizeStateStatus actually landed on
2846
+ // 'completed'; (b) the raw prose is UNAMBIGUOUSLY phase-completion prose —
2847
+ // the anchored pattern below deliberately excludes "All phases complete"
2848
+ // (no `\S+` phase token) and milestone-close prose like "v1.0 milestone
2849
+ // complete" (no leading "phase"); and (c) the counters are trustworthy (a
2850
+ // COMPLETE disk scope, both counts are finite numbers, and a positive
2738
2851
  // denominator) and affirmatively disagree with 'completed'. In every
2739
2852
  // other case normalizedStatus is left exactly as normalizeStateStatus
2740
2853
  // returned it.
@@ -2996,6 +3109,75 @@ function readStoredTotalPlans(existingFm) {
2996
3109
  function readStoredCompletedPlans(existingFm) {
2997
3110
  return readStoredProgressCounter(existingFm, 'completed_plans');
2998
3111
  }
3112
+ /**
3113
+ * #4129: is this authoritativeFm value a PARTIAL progress intent? The #2736
3114
+ * seam was string-only (names); #4129 extends it with one object direction —
3115
+ * the `progress` key carrying the sub-keys a transition resolved
3116
+ * authoritatively (completePhase's ROADMAP-derived completed_phases/percent).
3117
+ * Anything else keeps the seam's existing contract untouched.
3118
+ */
3119
+ function isPartialProgressIntent(value) {
3120
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
3121
+ }
3122
+ /**
3123
+ * #4129: merge a PARTIAL progress intent (see isPartialProgressIntent) into a
3124
+ * frontmatter object's `progress` block. Sub-keys are accepted only when they
3125
+ * are a declared `progress.*` row in FIELD_CLASSIFICATION — the single policy
3126
+ * source (ADR-3408 §8.5) decides which leaves exist; an intent may not invent
3127
+ * one. Returns whether anything changed.
3128
+ *
3129
+ * `completedOnlyRaise` (both application sites use it): completed counters
3130
+ * apply only when strictly greater than what is already in the block, so no
3131
+ * intent can LOWER a count another trustworthy signal already established —
3132
+ * at the pre-preservation site the disk derivation's own count (a
3133
+ * verification-passed phase whose ROADMAP row drifted behind), at the
3134
+ * post-preservation re-assert the #2969 monotonic property preservation just
3135
+ * enforced. `percent` follows its sibling: it is applied when a completed
3136
+ * counter moved this call (the intent percent was computed from the intent
3137
+ * counters and is coherent with them) or when the block has no percent to
3138
+ * lose (a repair, never a regression of an upstream withhold — the withhold
3139
+ * nulled percent upstream precisely so no write would re-assert one over
3140
+ * untrustworthy counts; here the intent's own counts ARE the trustworthy
3141
+ * source, the post-completion ROADMAP).
3142
+ */
3143
+ function applyAuthoritativeProgressSubkeys(fm, intent, opts) {
3144
+ const current = fm['progress'];
3145
+ const base = isPartialProgressIntent(current)
3146
+ ? { ...current }
3147
+ : {};
3148
+ let changed = false;
3149
+ let completedMoved = false;
3150
+ for (const [subkey, value] of Object.entries(intent)) {
3151
+ if (typeof value !== 'number' || !Number.isFinite(value))
3152
+ continue;
3153
+ if (!getFieldClassification(`progress.${subkey}`))
3154
+ continue;
3155
+ const isCompletedCounter = subkey === 'completed_phases' || subkey === 'completed_plans';
3156
+ if (isCompletedCounter && opts.completedOnlyRaise) {
3157
+ const currentNum = (0, state_document_cjs_1.toFiniteNumber)(base[subkey]);
3158
+ if (currentNum !== null && currentNum >= value)
3159
+ continue;
3160
+ }
3161
+ if (isCompletedCounter && !Object.is(base[subkey], value))
3162
+ completedMoved = true;
3163
+ if (!Object.is(base[subkey], value)) {
3164
+ base[subkey] = value;
3165
+ changed = true;
3166
+ }
3167
+ }
3168
+ // percent: applied only when a completed counter moved (coherent with the
3169
+ // counters that just landed) or when no percent exists to contradict.
3170
+ const intentPercent = intent['percent'];
3171
+ if (typeof intentPercent === 'number' && Number.isFinite(intentPercent) && (completedMoved || (0, state_document_cjs_1.toFiniteNumber)(base['percent']) === null)) {
3172
+ if (!Object.is(base['percent'], intentPercent)) {
3173
+ base['percent'] = intentPercent;
3174
+ changed = true;
3175
+ }
3176
+ }
3177
+ if (changed)
3178
+ fm['progress'] = base;
3179
+ return changed;
3180
+ }
2999
3181
  function syncStateFrontmatter(content, cwd, authoritativeFm, sanctionedPermanentEmptyFallback) {
3000
3182
  // Read existing frontmatter BEFORE stripping — it may contain values
3001
3183
  // that the body no longer has (e.g., Status field removed by an agent).
@@ -3201,11 +3383,22 @@ function syncStateFrontmatter(content, cwd, authoritativeFm, sanctionedPermanent
3201
3383
  // parenthetical (`Closer-ruling measurement (D1a)` → `D1a`) — never runs
3202
3384
  // the final word on a field the transition just resolved. The prose parser
3203
3385
  // remains the fallback for genuinely unknown prose only.
3386
+ // #4129: the `progress` key carries a PARTIAL block (the object direction of
3387
+ // this seam — see applyAuthoritativeProgressSubkeys) for the same reason:
3388
+ // completePhase holds the POST-completion ROADMAP, and the disk scan this
3389
+ // function drives reads the PRE-completion one. The intent is applied as a
3390
+ // FLOOR here too (completedOnlyRaise): a derivation that already counted
3391
+ // MORE completed phases than the ROADMAP table asserts (verification-passed
3392
+ // phases whose table rows drifted behind) must not be lowered by the intent
3393
+ // — the two signals agree on direction (up), never on subtraction.
3204
3394
  if (authoritativeFm) {
3205
3395
  for (const [key, value] of Object.entries(authoritativeFm)) {
3206
3396
  if (typeof value === 'string' && value.trim().length > 0) {
3207
3397
  derivedFm[key] = value;
3208
3398
  }
3399
+ else if (key === 'progress' && isPartialProgressIntent(value)) {
3400
+ applyAuthoritativeProgressSubkeys(derivedFm, value, { completedOnlyRaise: true });
3401
+ }
3209
3402
  }
3210
3403
  }
3211
3404
  // #3257: propagate full-line frontmatter comments from the extracted source onto the
@@ -3782,6 +3975,10 @@ function applyPostSyncPreservation(originalContent, transformedContent, syncedCo
3782
3975
  // (equal), so the #1695 restore fires and would put the stale pre-transition
3783
3976
  // name back over the authoritative one. Intent beats both the prose
3784
3977
  // re-derivation and the curated restore — the transition just resolved it.
3978
+ // #4129: for the `progress` key the re-assert is a FLOOR, not an override —
3979
+ // the #2969 monotonic property preservation just enforced (completed
3980
+ // counters never move down) must not be undone by the intent, so completed
3981
+ // sub-keys apply only-raise here (see applyAuthoritativeProgressSubkeys).
3785
3982
  let authoritativeReasserted = false;
3786
3983
  if (authoritativeFm) {
3787
3984
  for (const [key, value] of Object.entries(authoritativeFm)) {
@@ -3789,6 +3986,11 @@ function applyPostSyncPreservation(originalContent, transformedContent, syncedCo
3789
3986
  preservation.postFm[key] = value;
3790
3987
  authoritativeReasserted = true;
3791
3988
  }
3989
+ else if (key === 'progress' && isPartialProgressIntent(value)) {
3990
+ if (applyAuthoritativeProgressSubkeys(preservation.postFm, value, { completedOnlyRaise: true })) {
3991
+ authoritativeReasserted = true;
3992
+ }
3993
+ }
3792
3994
  }
3793
3995
  }
3794
3996
  let finalContent = syncedContent;
@@ -4454,6 +4656,26 @@ function cmdStateJson(cwd, raw) {
4454
4656
  * Fixes: #1102 (plan counts), #1103 (status/last_activity), #1104 (body text).
4455
4657
  */
4456
4658
  function cmdStateBeginPhase(cwd, phaseNumber, phaseName, planCount, raw) {
4659
+ // #4138: `--phase` is this verb's one required argument, and an invocation
4660
+ // that names no phase must fail closed BEFORE any read-modify-write runs —
4661
+ // previously the missing flag flowed through as null and the transition
4662
+ // serialised `String(null)` into the body (`Phase: null — EXECUTING`,
4663
+ // `Status: Executing Phase null`, `last_activity_desc: Phase null execution
4664
+ // started`) while the post-sync frontmatter rebuild dropped current_phase /
4665
+ // current_phase_name entirely, so a single argument-less call un-set the
4666
+ // phase identity. The guard mirrors the sibling usage errors that already
4667
+ // exit non-zero (`state update`'s "field and value required", the router's
4668
+ // "unexpected positional argument" / "Invalid --plans value"), NOT
4669
+ // `cmdStateMilestoneSwitch`'s `output({error})` form, which exits 0 — the
4670
+ // issue's Expected is explicit: "Exit non-zero with a usage message and
4671
+ // write nothing." Empty and whitespace-only values are the same missing
4672
+ // argument (CONTRIBUTING.md CLI matrix); a flag-shaped `--phase --name x`
4673
+ // resolves to null in parseNamedArgs and lands here too. Runs before the
4674
+ // STATE.md existence check so argument validation always precedes I/O, and
4675
+ // before claimMilestonePhase so no phase-"null" milestone claim is taken.
4676
+ if (phaseNumber == null || String(phaseNumber).trim() === '') {
4677
+ error('phase required (--phase <N>)');
4678
+ }
4457
4679
  const statePath = planningPaths(cwd).state;
4458
4680
  if (!node_fs_1.default.existsSync(statePath)) {
4459
4681
  output({ error: 'STATE.md not found' }, raw, undefined);
@@ -4467,7 +4689,11 @@ function cmdStateBeginPhase(cwd, phaseNumber, phaseName, planCount, raw) {
4467
4689
  // #1230 post-sync preservation, and the no-op write guard.
4468
4690
  const intent = {
4469
4691
  kind: 'beginPhase',
4470
- phaseNumber,
4692
+ // The guard above made this non-null/non-empty; `error` is never-returning
4693
+ // at runtime but this module's destructured io binding does not narrow CFA,
4694
+ // so the narrowed fact is restated once (cmdStateUpdate's `field as string`
4695
+ // idiom, state.cts:782).
4696
+ phaseNumber: phaseNumber,
4471
4697
  phaseName: phaseName ?? null,
4472
4698
  planCount: planCount ?? null,
4473
4699
  };
@@ -4763,6 +4989,12 @@ function updatePerformanceMetricsSection(content, cwd, phaseNum, planCount, summ
4763
4989
  * Updates Status to "Ready to execute", Total Plans, Last Activity.
4764
4990
  */
4765
4991
  function cmdStatePlannedPhase(cwd, phaseNumber, phaseName, planCount, raw) {
4992
+ // #4383: mirror begin-phase's command-boundary guard. A missing phase must
4993
+ // fail before even looking up STATE.md so no invalid invocation can enter
4994
+ // the read-modify-write path and serialize a null/blank phase identity.
4995
+ if (phaseNumber == null || String(phaseNumber).trim() === '') {
4996
+ error('phase required (--phase <N>)');
4997
+ }
4766
4998
  const statePath = planningPaths(cwd).state;
4767
4999
  if (!node_fs_1.default.existsSync(statePath)) {
4768
5000
  output({ error: 'STATE.md not found' }, raw, undefined);
@@ -4778,7 +5010,7 @@ function cmdStatePlannedPhase(cwd, phaseNumber, phaseName, planCount, raw) {
4778
5010
  // still owns the lock, the #1230 preservation, and the no-op write guard.
4779
5011
  const intent = {
4780
5012
  kind: 'plannedPhase',
4781
- phaseNumber,
5013
+ phaseNumber: phaseNumber,
4782
5014
  phaseName: phaseName ?? null,
4783
5015
  planCount: planCount ?? null,
4784
5016
  };
@@ -5134,7 +5366,10 @@ function cmdStateValidate(cwd, raw, opts = {}) {
5134
5366
  // ("verification passed" drift), not a false S007.
5135
5367
  const files = node_fs_1.default.readdirSync(phaseDirPath);
5136
5368
  const phaseDirBaseName = node_path_1.default.basename(phaseDirPath);
5137
- const verificationFiles = scopeToPhase(files.filter(f => f.includes('VERIFICATION') && f.endsWith('.md')), phaseDirBaseName);
5369
+ // #612: `validateConvention` threaded (already resolved above for
5370
+ // `phaseKeyFromDir`) so the S006/S007 scan scopes bracket dirs by
5371
+ // their real token instead of the include-everything fail-safe.
5372
+ const verificationFiles = scopeToPhase(files.filter(f => f.includes('VERIFICATION') && f.endsWith('.md')), phaseDirBaseName, validateConvention);
5138
5373
  for (const vf of verificationFiles) {
5139
5374
  try {
5140
5375
  const vContent = node_fs_1.default.readFileSync(node_path_1.default.join(phaseDirPath, vf), 'utf-8');
@@ -5325,7 +5560,10 @@ function cmdStateSync(cwd, options, raw) {
5325
5560
  // was a second, independent consumer of the same raw field the initial
5326
5561
  // migration missed — without it, `state sync` and `state json` disagreed
5327
5562
  // on completed_phases for the identical disk state.
5328
- if (isPhaseComplete(dirPath).value.complete)
5563
+ // #612: `syncConvention` threaded — the write-side half of
5564
+ // buildStateFrontmatter's thread above, so `state sync` and `state json`
5565
+ // keep agreeing on completed_phases under the bracket convention.
5566
+ if (isPhaseComplete(dirPath, { convention: syncConvention }).value.complete)
5329
5567
  diskCompletedPhases++;
5330
5568
  // Track the highest phase with incomplete plans (or any plans)
5331
5569
  const phaseMatch = dir.match(new RegExp(`^(${PHASE_NUMBER_TOKEN_SOURCE})`, 'i'));
@@ -61,6 +61,18 @@ const runtimeArtifactInstallPlan = require("./runtime-artifact-install-plan.cjs"
61
61
  const retiredArtifactCleanup = require("./retired-artifact-cleanup.cjs");
62
62
  const { assertDestWithinConfigHome } = runtimeArtifactInstallPlan;
63
63
  const SURFACE_FILE_NAME = '.gsd-surface.json';
64
+ const INSTALL_MANIFEST_FILE_NAME = 'gsd-file-manifest.json';
65
+ /** Read only the installer-owned relative-include style, failing closed. */
66
+ function readRelativeIncludePrefix(runtimeConfigDir) {
67
+ try {
68
+ const parsed = JSON.parse(node_fs_1.default.readFileSync(node_path_1.default.join(runtimeConfigDir, INSTALL_MANIFEST_FILE_NAME), 'utf8'));
69
+ const prefix = parsed?.['relativeIncludePrefix'];
70
+ return typeof prefix === 'string' ? prefix : undefined;
71
+ }
72
+ catch {
73
+ return undefined;
74
+ }
75
+ }
64
76
  /**
65
77
  * Read the surface state from a runtime config directory.
66
78
  *
@@ -341,7 +353,11 @@ function applySurface(runtimeConfigDir, layout, manifest, clusterMap, registry,
341
353
  const _isGlobal = (0, install_scope_cjs_1.isGlobalScope)(layout.scope ?? 'global');
342
354
  const _isOpencode = layout.runtime === 'opencode';
343
355
  const _isWindowsHost = (opts?.platform ?? process.platform) === 'win32';
344
- const _pathPrefix = runtimeArtifactConversion._computePathPrefix({ isGlobal: _isGlobal, isOpencode: _isOpencode, isWindowsHost: _isWindowsHost, resolvedTarget: _resolvedTarget, homeDir: _homeDir });
356
+ // #4377: style is an install-time fact, not a process environment setting.
357
+ // A later gsd-tools surface apply runs in another process, so it must reuse
358
+ // the persisted, root-relative prefix the installer actually emitted.
359
+ const _relativeIncludePrefix = readRelativeIncludePrefix(runtimeConfigDir);
360
+ const _pathPrefix = runtimeArtifactConversion._computePathPrefix({ isGlobal: _isGlobal, isOpencode: _isOpencode, isWindowsHost: _isWindowsHost, resolvedTarget: _resolvedTarget, homeDir: _homeDir, projectRelative: !_isGlobal && typeof _relativeIncludePrefix === 'string', projectRelativePath: _relativeIncludePrefix, localDirName: runtimeArtifactConversion._localIncludeDirName(layout.runtime) });
345
361
  const _attribution = opts?.resolveAttribution ? opts.resolveAttribution(layout.runtime) : undefined;
346
362
  // #2875 Part 2 (row I1): layout.configDir is this call's install root.
347
363
  const agentCtx = { runtime: layout.runtime, pathPrefix: _pathPrefix, attribution: _attribution, targetDir: layout.configDir };
@@ -364,14 +380,25 @@ function applySurface(runtimeConfigDir, layout, manifest, clusterMap, registry,
364
380
  try {
365
381
  for (const kind of layout.kinds) {
366
382
  let staged;
367
- if (kind.kind === 'agents') {
383
+ // #4211: kimi-agents is an AGENT kind — kimiAgentsKind.stage() forwards
384
+ // agentCtx into stageAgentsForRuntimeWithConverter exactly as agentsKind
385
+ // does, and createRuntimeArtifactInstallPlan hands every kind the
386
+ // context. Staging it bare here dropped the path-prefix rewrites and the
387
+ // attribution trailer from Kimi's generated subagents, and (under an
388
+ // unmodified `full` profile) staged only the skill-referenced subset the
389
+ // install path stages with `skills: '*'`.
390
+ if (kind.kind === 'agents' || kind.kind === 'kimi-agents') {
368
391
  const agentProfile = _isUnmodifiedFull ? { ...resolved, skills: '*' } : resolved;
369
392
  staged = kind.stage(agentProfile, agentCtx);
370
393
  }
371
394
  else {
372
395
  staged = kind.stage(resolved);
373
396
  }
374
- if (kind.kind === 'skills') {
397
+ // #4211: kimi-agents takes the skill-body rewrite too —
398
+ // createRuntimeArtifactInstallPlan routes `skills` and `kimi-agents`
399
+ // through rewriteStagedSkillBodies together, so omitting it here left
400
+ // Kimi's surface-materialized prompts with unrewritten paths.
401
+ if (kind.kind === 'skills' || kind.kind === 'kimi-agents') {
375
402
  runtimeArtifactConversion.rewriteStagedSkillBodies(staged, {
376
403
  runtime: layout.runtime,
377
404
  configDir: layout.configDir,
@@ -572,6 +599,53 @@ function _syncGsdDir(stagedDir, destDir, kind, manifest, runtime) {
572
599
  // (no agentFileExtension declared) keep the staged filename verbatim.
573
600
  const _agentExt = runtime ? runtimeArtifactConversion.agentFileExtensionFor(runtime) : undefined;
574
601
  const isRenamedAgents = !!_agentExt && kindName === 'agents';
602
+ if (kindName === 'kimi-agents') {
603
+ // #4211: Kimi's managed tree is `gsd.yaml` + `gsd.md` + `subagents/gsd-*.{yaml,md}`
604
+ // (runtime-artifact-layout.cts kimiAgentsKind), and install copies it
605
+ // RECURSIVELY (_copyStaged in src/install-engine.cts). Surface apply fell
606
+ // through to the flat command/agent branch below, which reads only `*.md`
607
+ // at the top level: the YAML half and the whole subagents/ subtree were
608
+ // dropped, and `gsd.md` was written as `gsdgsd.md` (the flat branch
609
+ // re-applies kind.prefix to a name that already carries it). A surface
610
+ // change could therefore corrupt Kimi's installed artifacts while still
611
+ // reporting success.
612
+ node_fs_1.default.cpSync(stagedDir, destDir, { recursive: true });
613
+ // Prune GSD-owned files the new surface no longer stages, with exactly the
614
+ // ownership rule install's _removeGsdEntries applies to this kind: the two
615
+ // root files, and `gsd-`-prefixed .yaml/.md under subagents/. Everything
616
+ // else in the directory is user-owned and is preserved.
617
+ const _rootStaged = new Set(node_fs_1.default.readdirSync(stagedDir));
618
+ for (const fileName of ['gsd.yaml', 'gsd.md']) {
619
+ if (!_rootStaged.has(fileName)) {
620
+ try {
621
+ node_fs_1.default.rmSync(node_path_1.default.join(destDir, fileName), { force: true });
622
+ }
623
+ catch { /* ignore */ }
624
+ }
625
+ }
626
+ const _stagedSubagentsDir = node_path_1.default.join(stagedDir, 'subagents');
627
+ const _destSubagentsDir = node_path_1.default.join(destDir, 'subagents');
628
+ const _stagedSubagents = node_fs_1.default.existsSync(_stagedSubagentsDir)
629
+ ? new Set(node_fs_1.default.readdirSync(_stagedSubagentsDir))
630
+ : new Set();
631
+ if (node_fs_1.default.existsSync(_destSubagentsDir)) {
632
+ for (const entry of node_fs_1.default.readdirSync(_destSubagentsDir, { withFileTypes: true })) {
633
+ if (!entry.isFile())
634
+ continue;
635
+ if (!entry.name.startsWith('gsd-'))
636
+ continue;
637
+ if (!entry.name.endsWith('.yaml') && !entry.name.endsWith('.md'))
638
+ continue;
639
+ if (_stagedSubagents.has(entry.name))
640
+ continue;
641
+ try {
642
+ node_fs_1.default.rmSync(node_path_1.default.join(_destSubagentsDir, entry.name), { force: true });
643
+ }
644
+ catch { /* ignore */ }
645
+ }
646
+ }
647
+ return;
648
+ }
575
649
  if (kindName === 'skills') {
576
650
  // Skills kind: work with directories, not files.
577
651
  // Each staged entry is a directory named ${prefix}${stem}.
@@ -11,6 +11,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
11
11
  };
12
12
  const node_fs_1 = __importDefault(require("node:fs"));
13
13
  const node_path_1 = __importDefault(require("node:path"));
14
+ const security_cjs_1 = require("./security.cjs");
14
15
  // eslint-disable-next-line @typescript-eslint/no-require-imports
15
16
  const ioMod = require("./io.cjs");
16
17
  const { output, error, ERROR_REASON } = ioMod;
@@ -108,12 +109,14 @@ function routeResolveContent({ args, cwd, raw }, deps = {}) {
108
109
  return;
109
110
  }
110
111
  const projectRoot = node_path_1.default.resolve(cwd || process.cwd());
111
- const resolvedPlanPath = node_path_1.default.resolve(projectRoot, plan);
112
- const rel = node_path_1.default.relative(projectRoot, resolvedPlanPath);
113
- if (rel === '..' || rel.startsWith(`..${node_path_1.default.sep}`)) {
112
+ // Lexical containment (ADR-4650): this path is validated before existence is
113
+ // checked below, so realpath resolution is neither available nor required.
114
+ const contained = (0, security_cjs_1.tryWithinRootLexical)(plan, projectRoot);
115
+ if (contained === null) {
114
116
  error(`Plan file is outside project scope: ${plan}`, ERROR_REASON.USAGE);
115
117
  return;
116
118
  }
119
+ const resolvedPlanPath = contained;
117
120
  if (!node_fs_1.default.existsSync(resolvedPlanPath)) {
118
121
  error(`Plan file not found: ${plan}`, ERROR_REASON.USAGE);
119
122
  return;
@@ -173,11 +176,14 @@ function routeTaskCommand({ args, cwd, raw }) {
173
176
  else if (args[2]) {
174
177
  const projectRoot = node_path_1.default.resolve(cwd || process.cwd());
175
178
  const requestedPath = args[2];
176
- const resolvedTaskPath = node_path_1.default.resolve(projectRoot, requestedPath);
177
- const rel = node_path_1.default.relative(projectRoot, resolvedTaskPath);
178
- if (rel === '..' || rel.startsWith(`..${node_path_1.default.sep}`)) {
179
+ // Lexical containment (ADR-4650): validated before existence is checked below.
180
+ // `error()` here does not return/throw (preserved from before this migration),
181
+ // so resolvedTaskPath must still be computed identically on the rejected path.
182
+ const contained = (0, security_cjs_1.tryWithinRootLexical)(requestedPath, projectRoot);
183
+ if (contained === null) {
179
184
  error(`Task file is outside project scope: ${requestedPath}`, ERROR_REASON.USAGE);
180
185
  }
186
+ const resolvedTaskPath = contained ?? node_path_1.default.resolve(projectRoot, requestedPath);
181
187
  if (!node_fs_1.default.existsSync(resolvedTaskPath)) {
182
188
  error(`Task file not found: ${requestedPath}`, ERROR_REASON.USAGE);
183
189
  }