@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
@@ -32,6 +32,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
32
32
  const node_fs_1 = __importDefault(require("node:fs"));
33
33
  const node_path_1 = __importDefault(require("node:path"));
34
34
  const markdown_sectionizer_cjs_1 = require("./markdown-sectionizer.cjs");
35
+ const security_cjs_1 = require("./security.cjs");
35
36
  // eslint-disable-next-line @typescript-eslint/no-require-imports -- phase-id.cjs is an export= CommonJS module
36
37
  const phaseIdMod = require("./phase-id.cjs");
37
38
  const { stripProjectCodePrefix, extractPhaseToken, comparePhaseNum } = phaseIdMod;
@@ -380,6 +381,25 @@ function splitSegments(cmd) {
380
381
  .map(s => s.trim())
381
382
  .filter(s => s.length > 0);
382
383
  }
384
+ /**
385
+ * Decode entity-escaped ampersands (`&` → `&`) — #4730, the grounding
386
+ * gate's share of the #3611 defect. Planners emit `<automated>` bodies with
387
+ * `&amp;&amp;` as the chain operator, and the executing agent reads the
388
+ * decoded (rendered) form. Without this decode, `splitSegments` cuts
389
+ * `&amp;&amp;` at its semicolons and a `cd` target absorbs the trailing
390
+ * `&amp` fragment (`rawTarget: "src &amp"`), reporting an existing directory
391
+ * as a `missing_dir` blocker. Applied to the command text inside
392
+ * `resolveVerifyCommandTarget` — before any segment splitting or target
393
+ * resolution, after `result.command` has captured the text verbatim — so the
394
+ * escaped and literal forms of the same command produce identical verdicts.
395
+ * Kept module-private beside `splitSegments`, mirroring the sibling
396
+ * `src/verify.cts`'s own private `decodeEntityAmps` (#3611): the two gates
397
+ * are separate modules that independently parse `<automated>` text, and
398
+ * neither imports the other.
399
+ */
400
+ function decodeEntityAmps(s) {
401
+ return s.replace(/&amp;/g, '&');
402
+ }
383
403
  /** Strip a single matching pair of surrounding quotes, if present. */
384
404
  function stripQuotes(s) {
385
405
  if (s.length >= 2) {
@@ -438,6 +458,16 @@ function stripLeadingDotSlash(s) {
438
458
  * without touching the filesystem. A climb that names a concrete sibling (e.g.
439
459
  * `cd ../../frontend`, the exact #2401 shape) still names something checkable
440
460
  * and falls through to the normal filesystem probe below.
461
+ *
462
+ * An ABSOLUTE target outside the project root takes the same `outside_root`
463
+ * exit (#4767). It is more ambiguous across worktrees, not less: it is pinned
464
+ * to exactly one checkout, and when a planner copies the orchestrator's cwd
465
+ * into `<automated>` that checkout is the main tree — so under worktree
466
+ * isolation the command exists, runs, and passes against code the worktree
467
+ * changed and the main tree did not. Existence is therefore not evidence for
468
+ * an absolute target outside the root, and the filesystem is not consulted.
469
+ * Containment goes through `tryWithinRootLexical` (#4636) — lexical because the
470
+ * probe is read-only and must not depend on the target existing.
441
471
  */
442
472
  function isPureAncestorClimb(rel) {
443
473
  if (rel.length === 0)
@@ -453,7 +483,7 @@ function declaredPathCovers(declaredPaths, norm) {
453
483
  if (typeof p !== 'string')
454
484
  return false;
455
485
  const dp = stripLeadingDotSlash(toSlash(p));
456
- return dp === target || dp.startsWith(target + '/');
486
+ return dp === target || dp.startsWith(target + '/'); // allow-handrolled-containment: declared-path coverage for pending-creation detection, not containment
457
487
  });
458
488
  }
459
489
  /**
@@ -501,7 +531,10 @@ function resolveVerifyCommandTarget(command, options) {
501
531
  if (typeof command !== 'string')
502
532
  return result;
503
533
  result.command = command;
504
- const trimmed = command.trim();
534
+ // #4730: decode entity-escaped ampersands BEFORE segment splitting (see
535
+ // decodeEntityAmps) so the chain operator is visible to the splitter and
536
+ // the verdict matches what the executing agent's decoded form grounds to.
537
+ const trimmed = decodeEntityAmps(command.trim());
505
538
  if (trimmed === '')
506
539
  return result;
507
540
  // Nyquist "MISSING — Wave 0 must create …" sentinel; Dimension 8 owns it.
@@ -550,7 +583,18 @@ function resolveVerifyCommandTarget(command, options) {
550
583
  result.form = form;
551
584
  result.rawTarget = rawTarget;
552
585
  result.target = target;
553
- if (!isAbs) {
586
+ if (isAbs) {
587
+ // #4767: an absolute target outside projectRoot is `outside_root`, same
588
+ // as the bare climb — see isPureAncestorClimb's doc for why existence is
589
+ // not evidence here and the filesystem is deliberately not consulted.
590
+ if ((0, security_cjs_1.tryWithinRootLexical)(target, base) === null) {
591
+ result.status = 'ok';
592
+ result.severity = 'warning';
593
+ result.reason = 'outside_root';
594
+ return result;
595
+ }
596
+ }
597
+ else {
554
598
  const rel = node_path_1.default.relative(base, target);
555
599
  if (isPureAncestorClimb(rel)) {
556
600
  result.status = 'ok';
@@ -13,6 +13,7 @@ const node_fs_1 = __importDefault(require("node:fs"));
13
13
  const node_path_1 = __importDefault(require("node:path"));
14
14
  const node_os_1 = __importDefault(require("node:os"));
15
15
  const validate_cjs_1 = require("./validate.cjs");
16
+ const security_cjs_1 = require("./security.cjs");
16
17
  // eslint-disable-next-line @typescript-eslint/no-require-imports -- planning-workspace.cjs is an export= CommonJS module
17
18
  const planningWorkspace = require("./planning-workspace.cjs");
18
19
  // eslint-disable-next-line @typescript-eslint/no-require-imports -- frontmatter.cjs is an export= CommonJS module
@@ -37,7 +38,7 @@ const worktreeSafetyMod = require("./worktree-safety.cjs");
37
38
  // codebase-drift --name-status parse loop).
38
39
  const { decodeGitQuotedPath } = worktreeSafetyMod;
39
40
  const shell_command_projection_cjs_1 = require("./shell-command-projection.cjs");
40
- const security_cjs_1 = require("./security.cjs");
41
+ const security_cjs_2 = require("./security.cjs");
41
42
  const runtime_slash_cjs_1 = require("./runtime-slash.cjs");
42
43
  const schema_detect_cjs_1 = require("./schema-detect.cjs");
43
44
  const markdown_sectionizer_cjs_1 = require("./markdown-sectionizer.cjs");
@@ -63,7 +64,11 @@ const { SEVERITY: HEALTH_SEVERITY, REMEDY_ACTION, REMEDY_RISK, evaluateRules, ev
63
64
  // eslint-disable-next-line @typescript-eslint/no-require-imports -- planning-snapshot.cjs is an export= CommonJS module
64
65
  const planningSnapshotMod = require("./planning-snapshot.cjs");
65
66
  const { buildPlanningSnapshot } = planningSnapshotMod;
66
- const { planningDir } = planningWorkspace;
67
+ // eslint-disable-next-line @typescript-eslint/no-require-imports -- onboard-projection.cjs is an export= CommonJS module
68
+ const onboardProjectionMod = require("./onboard-projection.cjs");
69
+ const { REQUIRED_CODEBASE_MAP_FILES } = onboardProjectionMod;
70
+ const clock_cjs_1 = require("./clock.cjs");
71
+ const { planningDir, planningRoot, withPlanningLock } = planningWorkspace;
67
72
  const { defaultPhaseCleanCommitTimesMs } = verificationMod;
68
73
  const { extractFrontmatter, parseMustHavesBlock } = frontmatterMod;
69
74
  const { readStateHeadFreshness } = stateMod;
@@ -152,9 +157,12 @@ function verifySummaryCore(cwd, summaryPath, checkFileCount, opts) {
152
157
  if (firstSegment.indexOf('.') > 0)
153
158
  return false;
154
159
  // Containment guard: a `../`-bearing reference must not turn this advisory
155
- // into a filesystem existence probe outside the project.
156
- const resolved = node_path_1.default.resolve(projectRoot, candidate);
157
- if (resolved !== projectRoot && !resolved.startsWith(projectRoot + node_path_1.default.sep))
160
+ // into a filesystem existence probe outside the project. Lexical (ADR-4650
161
+ // decision 6): the candidate is a string pulled from a SUMMARY document and
162
+ // by construction may not exist yet — existence is what gets probed
163
+ // downstream — and this is a pure string-heuristic filter with no other fs
164
+ // access, so a realpath call would also change its cost profile.
165
+ if ((0, security_cjs_1.tryWithinRootLexical)(candidate, projectRoot) === null)
158
166
  return false;
159
167
  return true;
160
168
  };
@@ -846,8 +854,15 @@ function scanQuantitativeCriteria(content) {
846
854
  // Not a markdown table: the negated-pipe class matches a SHELL pipeline
847
855
  // stage boundary (git before the next `|`), the same shape the
848
856
  // #429/#968 scanners use; there is no table row to parse.
857
+ // #4774: the lookahead keeps `||` — a logical OR, the construct that
858
+ // HANDLES the failure (e.g. the `git cat-file -e <sha> || echo missing`
859
+ // ghost-control idiom, reachable here when prose apostrophes leave the
860
+ // segment's quote state unclosed) — quiet, while `|` and `|&` (a real
861
+ // pipeline stage, stderr-merged or not) still warn. Accepted trade-off:
862
+ // a QUOTED `||` literal ahead of a real pipe (``git grep 'a||b' f | wc -l``)
863
+ // also goes quiet — the lookahead cannot reach past the doubled pipe.
849
864
  // allow-adhoc-markdown: shell pipeline stage boundary, not a table cell (#4024)
850
- if (/\bgit\s+[a-z][^\n|]*\|/.test(seg)) {
865
+ if (/\bgit\s+[a-z][^\n|]*\|(?!\|)/.test(seg)) {
851
866
  record(warnings, 'R4', '[plan-criteria R4] A fallible `git` in a non-final pipeline stage is swallowed — the pipeline reports ' +
852
867
  'the last stage\'s status, so a broken command reads as clean. Capture the status first.', seenWarn);
853
868
  }
@@ -918,6 +933,13 @@ function extractPlanTaskInfos(content) {
918
933
  const nameArr = (0, markdown_sectionizer_cjs_1.extractTaggedBlocks)(body, 'name');
919
934
  const hasName = nameArr.length > 0;
920
935
  const name = hasName ? nameArr[0].trim() : '';
936
+ // `(?:^|\s)` (not `\b`) so a hyphenated attribute ending in `auto_select`
937
+ // can never be mistaken for the real attribute — the same defensive
938
+ // anchor as extractOptionIds' `id` match below (#4095).
939
+ const autoSelectMatch = attrs.match(/(?:^|\s)auto_select\s*=\s*["']([^"']*)["']/);
940
+ const autoSelect = autoSelectMatch ? autoSelectMatch[1] : null;
941
+ const optionsArr = (0, markdown_sectionizer_cjs_1.extractTaggedBlocks)(body, 'options');
942
+ const optionIds = optionsArr.length > 0 ? extractOptionIds(optionsArr[0]) : [];
921
943
  infos.push({
922
944
  name,
923
945
  type,
@@ -935,6 +957,8 @@ function extractPlanTaskInfos(content) {
935
957
  hasHowToVerify: /<how-to-verify[\s>]/.test(body),
936
958
  hasDecision: /<decision[\s>]/.test(body),
937
959
  hasOptions: /<options[\s>]/.test(body),
960
+ autoSelect,
961
+ optionIds,
938
962
  hasInstructions: /<instructions[\s>]/.test(body),
939
963
  hasVerification: /<verification[\s>]/.test(body),
940
964
  hasResumeSignal: /<resume-signal[\s>]/.test(body),
@@ -946,6 +970,35 @@ function extractPlanTaskInfos(content) {
946
970
  }
947
971
  return infos;
948
972
  }
973
+ /**
974
+ * Extract the `id` attribute of every `<option id="…">` opening tag found in
975
+ * `optionsBody` (the inner text of one `<options>…</options>` block), in
976
+ * document order. Bounded attribute scan (`[^>]{0,500}`), mirroring the same
977
+ * ReDoS-safe idiom `extractPlanTaskInfos` uses for the `<task type="…">`
978
+ * attribute string — this file's established pattern for reading an
979
+ * attribute value without a general XML parser (#4095).
980
+ */
981
+ function extractOptionIds(optionsBody) {
982
+ const ids = [];
983
+ if (typeof optionsBody !== 'string' || optionsBody.length === 0)
984
+ return ids;
985
+ const OPTION_OPEN_RE = /<option(\s[^>]{0,500})?>/g;
986
+ let match;
987
+ while ((match = OPTION_OPEN_RE.exec(optionsBody)) !== null) {
988
+ const attrs = match[1] ?? '';
989
+ // `(?:^|\s)` (not `\b`) so a decoy attribute like `data-id="…"` inside
990
+ // the same opening tag cannot be mistaken for the real `id` — `\b`
991
+ // matches at the `-`→`i` boundary too, which `.match()`'s
992
+ // first-hit-wins semantics would then silently prefer (#4095).
993
+ const idMatch = attrs.match(/(?:^|\s)id\s*=\s*["']([^"']{1,200})["']/);
994
+ if (idMatch)
995
+ ids.push(idMatch[1]);
996
+ if (match.index === OPTION_OPEN_RE.lastIndex) {
997
+ OPTION_OPEN_RE.lastIndex++;
998
+ }
999
+ }
1000
+ return ids;
1001
+ }
949
1002
  function isCheckpointType(type) {
950
1003
  return type.startsWith('checkpoint:');
951
1004
  }
@@ -988,6 +1041,15 @@ function validatePlanTaskStructure(task) {
988
1041
  errors.push(`Task '${taskName}' missing <decision>`);
989
1042
  if (!task.hasOptions)
990
1043
  errors.push(`Task '${taskName}' missing <options>`);
1044
+ if (task.autoSelect !== null) {
1045
+ if (task.autoSelect.length === 0) {
1046
+ errors.push(`Task '${taskName}' auto_select is empty — name an <option id="…">`);
1047
+ }
1048
+ else if (task.hasOptions && !task.optionIds.includes(task.autoSelect)) {
1049
+ errors.push(`Task '${taskName}' auto_select="${task.autoSelect}" does not match any `
1050
+ + `<option id="…"> (available: ${task.optionIds.join(', ') || 'none'})`);
1051
+ }
1052
+ }
991
1053
  break;
992
1054
  case 'checkpoint:human-action':
993
1055
  if (!task.hasAction)
@@ -1157,6 +1219,11 @@ function cmdVerifyPhaseCompleteness(cwd, phase, raw) {
1157
1219
  warnings,
1158
1220
  }, raw, errors.length === 0 ? 'complete' : 'incomplete');
1159
1221
  }
1222
+ // #4678: citations may carry a trailing line suffix (":42", ":1-20") that
1223
+ // describes a location inside the file, not part of the path itself.
1224
+ function stripLineSuffix(ref) {
1225
+ return ref.replace(/:\d+(?:-\d+)?$/, '');
1226
+ }
1160
1227
  function cmdVerifyReferences(cwd, filePath, raw) {
1161
1228
  if (!filePath) {
1162
1229
  error('file path required');
@@ -1172,9 +1239,10 @@ function cmdVerifyReferences(cwd, filePath, raw) {
1172
1239
  const atRefs = content.match(/@([^\s\n,)]+\/[^\s\n,)]+)/g) || [];
1173
1240
  for (const ref of atRefs) {
1174
1241
  const cleanRef = ref.slice(1);
1175
- const resolved = cleanRef.startsWith('~/')
1176
- ? node_path_1.default.join(process.env['HOME'] || '', cleanRef.slice(2))
1177
- : node_path_1.default.join(cwd, cleanRef);
1242
+ const fsRef = stripLineSuffix(cleanRef);
1243
+ const resolved = fsRef.startsWith('~/')
1244
+ ? node_path_1.default.join(process.env['HOME'] || '', fsRef.slice(2))
1245
+ : node_path_1.default.join(cwd, fsRef);
1178
1246
  if (node_fs_1.default.existsSync(resolved)) {
1179
1247
  found.push(cleanRef);
1180
1248
  }
@@ -1182,14 +1250,14 @@ function cmdVerifyReferences(cwd, filePath, raw) {
1182
1250
  missing.push(cleanRef);
1183
1251
  }
1184
1252
  }
1185
- const backtickRefs = content.match(/`([^`]+\/[^`]+\.[a-zA-Z]{1,10})`/g) || [];
1253
+ const backtickRefs = content.match(/`([^`]+\/[^`]+\.[a-zA-Z]{1,10}(?::\d+(?:-\d+)?)?)`/g) || [];
1186
1254
  for (const ref of backtickRefs) {
1187
1255
  const cleanRef = ref.slice(1, -1);
1188
1256
  if (cleanRef.startsWith('http') || cleanRef.includes('${') || cleanRef.includes('{{'))
1189
1257
  continue;
1190
1258
  if (found.includes(cleanRef) || missing.includes(cleanRef))
1191
1259
  continue;
1192
- const resolved = node_path_1.default.join(cwd, cleanRef);
1260
+ const resolved = node_path_1.default.join(cwd, stripLineSuffix(cleanRef));
1193
1261
  if (node_fs_1.default.existsSync(resolved)) {
1194
1262
  found.push(cleanRef);
1195
1263
  }
@@ -1251,28 +1319,75 @@ function cmdVerifyArtifacts(cwd, planFilePath, raw) {
1251
1319
  const artFullPath = node_path_1.default.join(cwd, artPath);
1252
1320
  const exists = node_fs_1.default.existsSync(artFullPath);
1253
1321
  const check = { path: artPath, exists, issues: [], passed: false };
1254
- if (exists) {
1255
- const fileContent = (0, shell_command_projection_cjs_1.platformReadSync)(artFullPath) || '';
1256
- const lineCount = fileContent.split('\n').length;
1257
- if (artifact['min_lines'] && lineCount < artifact['min_lines']) {
1258
- check['issues'].push(`Only ${lineCount} lines, need ${artifact['min_lines']}`);
1259
- }
1260
- if (artifact['contains'] && !fileContent.includes(artifact['contains'])) {
1261
- check['issues'].push(`Missing pattern: ${artifact['contains']}`);
1262
- }
1263
- if (artifact['exports']) {
1264
- const exports = Array.isArray(artifact['exports'])
1265
- ? artifact['exports']
1266
- : [artifact['exports']];
1267
- for (const exp of exports) {
1268
- if (!fileContent.includes(exp))
1269
- check['issues'].push(`Missing export: ${exp}`);
1322
+ // #4685: one artifact's I/O problem is that artifact's failure, never the
1323
+ // whole plan's. `safeReadFile` rethrows every errno except ENOENT, so before
1324
+ // this an unreadable entry — a directory most commonly, but equally an EACCES
1325
+ // file or a dangling mount — threw out of the loop and the command reported
1326
+ // NOTHING: not the offending entry, and not the plan's other, perfectly good
1327
+ // artifacts either. A check that disappears is worse than a check that fails,
1328
+ // because a failure is visible.
1329
+ //
1330
+ // Scope of this guard, stated precisely (review nit): the `try` encloses the
1331
+ // whole per-artifact body, but the only statements in it that can throw are the
1332
+ // `statSync` and the read — the `min_lines`/`contains`/`exports` checks below
1333
+ // are pure string operations. So this catches I/O, and nothing here is a
1334
+ // deliberate guard around those criteria checks. A path `fs.existsSync` already
1335
+ // rejected never reaches here either (that is the `File not found` branch), so
1336
+ // this is not a claim to catch every way a path can be unusable.
1337
+ try {
1338
+ if (exists) {
1339
+ // A directory is reported as its own kind of failure, distinct from
1340
+ // `File not found`: the path resolved, it simply is not the thing an
1341
+ // artifact entry can be checked against. Verifying a directory (matching
1342
+ // `contains:`/`min_lines:`/`exports:` across the files inside it) is a
1343
+ // feature decision, deliberately not made here.
1344
+ if (node_fs_1.default.statSync(artFullPath).isDirectory()) {
1345
+ check['issues'].push('Not a file: path is a directory');
1346
+ }
1347
+ else {
1348
+ // `safeReadFile` returns null on ENOENT, and `|| ''` would turn that
1349
+ // into an empty file — which an entry carrying only `path`/`provides`
1350
+ // would then PASS, having checked nothing. `statSync` just succeeded, so
1351
+ // a null here means the artifact went away mid-check. Report that rather
1352
+ // than inheriting a pass from it. (Pre-existing above this fix, reachable
1353
+ // through the same race after `existsSync`; found in review.)
1354
+ const rawContent = (0, shell_command_projection_cjs_1.platformReadSync)(artFullPath);
1355
+ if (rawContent === null) {
1356
+ check['issues'].push('Unreadable: disappeared during check');
1357
+ results.push(check);
1358
+ continue;
1359
+ }
1360
+ const fileContent = rawContent;
1361
+ const lineCount = fileContent.split('\n').length;
1362
+ if (artifact['min_lines'] && lineCount < artifact['min_lines']) {
1363
+ check['issues'].push(`Only ${lineCount} lines, need ${artifact['min_lines']}`);
1364
+ }
1365
+ if (artifact['contains'] && !fileContent.includes(artifact['contains'])) {
1366
+ check['issues'].push(`Missing pattern: ${artifact['contains']}`);
1367
+ }
1368
+ if (artifact['exports']) {
1369
+ const exports = Array.isArray(artifact['exports'])
1370
+ ? artifact['exports']
1371
+ : [artifact['exports']];
1372
+ for (const exp of exports) {
1373
+ if (!fileContent.includes(exp))
1374
+ check['issues'].push(`Missing export: ${exp}`);
1375
+ }
1376
+ }
1377
+ check['passed'] = check['issues'].length === 0;
1270
1378
  }
1271
1379
  }
1272
- check['passed'] = check['issues'].length === 0;
1380
+ else {
1381
+ check['issues'].push('File not found');
1382
+ }
1273
1383
  }
1274
- else {
1275
- check['issues'].push('File not found');
1384
+ catch (err) {
1385
+ // Unreadable for some other reason. Record the errno rather than a generic
1386
+ // message — an operator seeing EACCES acts differently from one seeing EIO —
1387
+ // and leave `passed` false.
1388
+ const e = err;
1389
+ check['issues'].push(`Unreadable: ${e.code || (e.message ?? String(err))}`);
1390
+ check['passed'] = false;
1276
1391
  }
1277
1392
  results.push(check);
1278
1393
  }
@@ -1378,11 +1493,11 @@ function cmdVerifyKeyLinks(cwd, planFilePath, raw) {
1378
1493
  // project. Leave sourceContent as null so the existing not-found /
1379
1494
  // pending classification below runs unchanged. Note this guard is
1380
1495
  // narrower than it may look: `from: "."` is a non-empty string, so it
1381
- // still reaches validatePath and safeReadFile below, and DOES read the
1496
+ // still reaches tryWithinRoot and safeReadFile below, and DOES read the
1382
1497
  // cwd directory (yielding "Source read failed: EISDIR") — this branch
1383
1498
  // only short-circuits the true empty-string case.
1384
- const fromCheck = (0, security_cjs_1.validatePath)(fromPath, cwd);
1385
- if (!fromCheck.safe) {
1499
+ const fromContained = (0, security_cjs_2.tryWithinRoot)(fromPath, cwd);
1500
+ if (fromContained === null) {
1386
1501
  // Do not echo result.error — it embeds absolute host paths.
1387
1502
  check['path_rejected'] = 'from';
1388
1503
  check['detail'] = 'Source path rejected — resolves outside the project directory';
@@ -1390,7 +1505,7 @@ function cmdVerifyKeyLinks(cwd, planFilePath, raw) {
1390
1505
  continue;
1391
1506
  }
1392
1507
  try {
1393
- sourceContent = (0, shell_command_projection_cjs_1.platformReadSync)(fromCheck.resolved);
1508
+ sourceContent = (0, shell_command_projection_cjs_1.platformReadSync)(fromContained);
1394
1509
  }
1395
1510
  catch (err) {
1396
1511
  // Report the errno only — never the message or path (untrusted `from:`
@@ -1458,8 +1573,8 @@ function cmdVerifyKeyLinks(cwd, planFilePath, raw) {
1458
1573
  // An empty/missing `to:` is a malformed plan, not a
1459
1574
  // path-confinement violation — only a non-empty path that
1460
1575
  // actually resolves outside the project is path_rejected.
1461
- const toCheck = (0, security_cjs_1.validatePath)(toPath, cwd);
1462
- if (!toCheck.safe) {
1576
+ const toContained = (0, security_cjs_2.tryWithinRoot)(toPath, cwd);
1577
+ if (toContained === null) {
1463
1578
  // Do not read a rejected `to:` — treat as no target content
1464
1579
  // and do not echo result.error, which embeds absolute host
1465
1580
  // paths.
@@ -1467,7 +1582,7 @@ function cmdVerifyKeyLinks(cwd, planFilePath, raw) {
1467
1582
  check['detail'] = `Pattern "${link['pattern']}" not found in source; target path rejected — resolves outside the project directory`;
1468
1583
  }
1469
1584
  else {
1470
- targetContent = (0, shell_command_projection_cjs_1.platformReadSync)(toCheck.resolved);
1585
+ targetContent = (0, shell_command_projection_cjs_1.platformReadSync)(toContained);
1471
1586
  }
1472
1587
  }
1473
1588
  if (targetContent && pat.test(targetContent)) {
@@ -1823,9 +1938,9 @@ function resolvePhaseDirByToken(phasesDir, phaseArg) {
1823
1938
  const matched = matchPhaseDirs(dirNames, normalizedPhase).matches[0];
1824
1939
  if (matched)
1825
1940
  return node_path_1.default.join(phasesDir, matched);
1826
- const check = (0, security_cjs_1.validatePath)(phaseArg, phasesDir);
1827
- if (check.safe && node_fs_1.default.existsSync(check.resolved))
1828
- return check.resolved;
1941
+ const contained = (0, security_cjs_2.tryWithinRoot)(phaseArg, phasesDir);
1942
+ if (contained !== null && node_fs_1.default.existsSync(contained))
1943
+ return contained;
1829
1944
  return null;
1830
1945
  }
1831
1946
  /**
@@ -1986,6 +2101,110 @@ function cmdVerifySchemaDrift(cwd, phaseArg, skipFlag, raw) {
1986
2101
  skipped: isSkipped,
1987
2102
  }, raw);
1988
2103
  }
2104
+ /**
2105
+ * Stamp `last_mapped_commit` (plus `last_mapped_at`) into the frontmatter of
2106
+ * every codebase-map document that exists on disk, using the current HEAD sha.
2107
+ *
2108
+ * #3418: `drift.cjs` shipped a correct `writeMappedCommit` with no production
2109
+ * caller, so no full `/gsd:map-codebase` run ever wrote the machine-readable
2110
+ * baseline that `cmdVerifyCodebaseDrift` reads. The stamp lives in CODE rather
2111
+ * than in a prose instruction to the mapper agent on purpose: an agent that
2112
+ * decides its work is already done skips a prose step silently, which is the
2113
+ * exact class of failure the stamp exists to detect.
2114
+ *
2115
+ * Only documents that already exist are stamped -- a `--fast` map produces four
2116
+ * of the seven, and this must not conjure the missing three as frontmatter-only
2117
+ * stubs that would then satisfy the seven-file completeness probe. `only`
2118
+ * (`--files a.md,b.md`) narrows further, for a caller that refreshed a subset.
2119
+ *
2120
+ * Non-blocking by contract, like every other drift surface: any failure emits
2121
+ * `skipped` with a reason and exits 0.
2122
+ */
2123
+ function cmdStampCodebaseMap(cwd, raw, only) {
2124
+ // Non-hoisted: load-order matters for circular dep guard
2125
+ // eslint-disable-next-line @typescript-eslint/no-require-imports -- drift.cjs is an export= CommonJS module
2126
+ const drift = require('./drift.cjs');
2127
+ const emit = (payload) => output(payload, raw);
2128
+ const skip = (reason) => ({
2129
+ stamped: [],
2130
+ commit: null,
2131
+ stamped_at: null,
2132
+ skipped: true,
2133
+ reason,
2134
+ });
2135
+ try {
2136
+ // Probed outside the lock on purpose: taking the lock creates `.planning/`
2137
+ // if absent, and a stamp against a project that has no codebase map at all
2138
+ // must not conjure the directory. The per-document existence check inside
2139
+ // the lock is the one that matters -- a map deleted after this probe lands
2140
+ // on `no-codebase-map` there rather than being recreated as stubs.
2141
+ const codebaseDir = node_path_1.default.join(planningDir(cwd), 'codebase');
2142
+ if (!node_fs_1.default.existsSync(codebaseDir)) {
2143
+ emit(skip('no-codebase-dir'));
2144
+ return;
2145
+ }
2146
+ // `--files` restricts the stamp to the documents a caller actually
2147
+ // refreshed. The execute-phase auto-remap path rewrites STRUCTURE.md and
2148
+ // ARCHITECTURE.md only; stamping the other five at HEAD there would claim a
2149
+ // currency they do not have. Membership is checked against the closed
2150
+ // seven-document set, so an unknown name is a fail-loud non-answer rather
2151
+ // than a path this function tries to resolve. An empty value is refused
2152
+ // rather than read as "no filter": a caller that meant to narrow the scope
2153
+ // must not silently widen it to all seven.
2154
+ let candidates = REQUIRED_CODEBASE_MAP_FILES;
2155
+ if (only) {
2156
+ if (only.length === 0) {
2157
+ emit(skip('empty-codebase-map-file-filter'));
2158
+ return;
2159
+ }
2160
+ const unknown = only.filter((file) => !candidates.includes(file));
2161
+ if (unknown.length > 0) {
2162
+ emit(skip('unknown-codebase-map-file: ' + unknown.join(',')));
2163
+ return;
2164
+ }
2165
+ candidates = candidates.filter((file) => only.includes(file));
2166
+ }
2167
+ // Everything the stamp reads is read under the lock it writes under, the
2168
+ // same lock the rest of the .planning/ writers take. Two stampers can run
2169
+ // at once (the full map-codebase run and the execute-phase auto-remap);
2170
+ // one that resolved HEAD or listed the present documents before waiting on
2171
+ // the lock would write its now-stale sha over the newer one, or recreate a
2172
+ // document deleted while it waited as a frontmatter-only stub.
2173
+ const result = withPlanningLock(cwd, () => {
2174
+ const revProbe = (0, shell_command_projection_cjs_1.execGit)(['rev-parse', 'HEAD'], { cwd });
2175
+ if (revProbe.exitCode !== 0)
2176
+ return skip('not-a-git-repo');
2177
+ const commit = revProbe.stdout.trim();
2178
+ if (!/^[0-9a-f]{7,40}$/.test(commit))
2179
+ return skip('unreadable-head');
2180
+ const present = candidates.filter((file) => node_fs_1.default.existsSync(node_path_1.default.join(codebaseDir, file)));
2181
+ if (present.length === 0)
2182
+ return skip('no-codebase-map');
2183
+ // Host-local calendar day, matching the `**Analysis Date:**` line the
2184
+ // mapper agent writes -- the two freshness markers must not disagree by
2185
+ // a timezone.
2186
+ const stampedAt = clock_cjs_1.realClock.localToday();
2187
+ const write = drift['writeMappedCommit'];
2188
+ const stamped = [];
2189
+ const failed = [];
2190
+ for (const file of present) {
2191
+ try {
2192
+ write(node_path_1.default.join(codebaseDir, file), commit, stampedAt);
2193
+ stamped.push(file);
2194
+ }
2195
+ catch (err) {
2196
+ // One unwritable document must not cost the stamp on the other six.
2197
+ failed.push({ file, reason: err instanceof Error ? err.message : String(err) });
2198
+ }
2199
+ }
2200
+ return { stamped, failed, commit, stamped_at: stampedAt, skipped: false, reason: null };
2201
+ });
2202
+ emit(result);
2203
+ }
2204
+ catch (err) {
2205
+ emit(skip('exception: ' + (err instanceof Error ? err.message : String(err))));
2206
+ }
2207
+ }
1989
2208
  function cmdVerifyCodebaseDrift(cwd, raw) {
1990
2209
  // Non-hoisted: load-order matters for circular dep guard
1991
2210
  // eslint-disable-next-line @typescript-eslint/no-require-imports -- drift.cjs is an export= CommonJS module
@@ -2034,16 +2253,45 @@ function cmdVerifyCodebaseDrift(cwd, raw) {
2034
2253
  });
2035
2254
  return;
2036
2255
  }
2037
- const EMPTY_TREE = '4b825dc642cb6eb9a060e54bf8d69288fbee4904';
2038
- let base = lastMapped;
2039
- if (!base) {
2040
- base = EMPTY_TREE;
2256
+ // #3418: an absent or unresolvable baseline means NO COMPARISON IS POSSIBLE.
2257
+ // It is neither zero drift nor total drift, and reporting it as either is a
2258
+ // lie the consumer cannot detect. The former fallback diffed HEAD against
2259
+ // the empty tree, so every tracked file read as newly added and the gate
2260
+ // reported maximum drift identically on every run -- which made a genuinely
2261
+ // stale map indistinguishable from a fresh one, and let `spawn_mapper` fire
2262
+ // a whole-repo remap while presenting itself as an incremental one.
2263
+ if (!lastMapped) {
2264
+ emit({
2265
+ block: false,
2266
+ skipped: true,
2267
+ reason: 'no-mapped-commit',
2268
+ action_required: false,
2269
+ directive: 'none',
2270
+ elements: [],
2271
+ last_mapped_commit: null,
2272
+ });
2273
+ return;
2041
2274
  }
2042
- else {
2043
- const verify = (0, shell_command_projection_cjs_1.execGit)(['cat-file', '-t', base], { cwd });
2044
- if (verify.exitCode !== 0)
2045
- base = EMPTY_TREE;
2275
+ const baseProbe = (0, shell_command_projection_cjs_1.execGit)(['cat-file', '-t', lastMapped], { cwd });
2276
+ if (baseProbe.exitCode !== 0 || baseProbe.stdout.trim() !== 'commit') {
2277
+ // A stamp git cannot resolve: history rewrite, GC, or a shallow clone.
2278
+ // Distinct reason from 'no-mapped-commit' -- the map claims a baseline,
2279
+ // this repository just cannot see it, which is an operator-actionable
2280
+ // difference (re-map vs. unshallow). A resolvable non-commit (a tree or
2281
+ // blob sha, a ref name) is the same class of bad baseline: git would
2282
+ // happily diff against it and report drift against the wrong object.
2283
+ emit({
2284
+ block: false,
2285
+ skipped: true,
2286
+ reason: 'unresolvable-mapped-commit',
2287
+ action_required: false,
2288
+ directive: 'none',
2289
+ elements: [],
2290
+ last_mapped_commit: lastMapped,
2291
+ });
2292
+ return;
2046
2293
  }
2294
+ const base = lastMapped;
2047
2295
  const diff = (0, shell_command_projection_cjs_1.execGit)(['diff', '--name-status', base, 'HEAD'], { cwd });
2048
2296
  if (diff.exitCode !== 0) {
2049
2297
  emit({
@@ -2056,6 +2304,27 @@ function cmdVerifyCodebaseDrift(cwd, raw) {
2056
2304
  });
2057
2305
  return;
2058
2306
  }
2307
+ // #3418: GSD's own planning artifacts are not codebase structure. A
2308
+ // map-codebase run commits `.planning/codebase/*.md`, so a correctly
2309
+ // stamped baseline would be re-poisoned by the very commit that carries
2310
+ // the stamp -- the next gate invocation would report the map's own seven
2311
+ // documents as seven new directories, back over the default threshold of
2312
+ // three. Derived from planningRoot() rather than a hardcoded literal so a
2313
+ // repoint of the planning root cannot leave this filter behind.
2314
+ //
2315
+ // `git diff --name-status` always prints repo-root-relative paths, so a cwd
2316
+ // below the root needs the `sub/` prefix or the filter matches nothing.
2317
+ // That prefix comes from git (`--show-prefix`: root-relative, forward
2318
+ // slashes, trailing slash, empty at the root). The rejected alternative was
2319
+ // path.relative(`--show-toplevel`, cwd), which mixes two path producers: on
2320
+ // Windows os.tmpdir() hands back the 8.3 short form while git resolves the
2321
+ // long one, so relative() between them yields a `../..` chain that matches
2322
+ // nothing. The `.planning` half below is safe to compute with relative()
2323
+ // because both of its sides are the same cwd string.
2324
+ const prefixProbe = (0, shell_command_projection_cjs_1.execGit)(['rev-parse', '--show-prefix'], { cwd });
2325
+ const repoPrefix = prefixProbe.exitCode === 0 ? prefixProbe.stdout.trim() : '';
2326
+ const planningPrefix = repoPrefix + node_path_1.default.relative(cwd, planningRoot(cwd)).split(node_path_1.default.sep).join('/') + '/';
2327
+ const isPlanningArtifact = (file) => file.split('\\').join('/').startsWith(planningPrefix);
2059
2328
  const added = [];
2060
2329
  const modified = [];
2061
2330
  const deleted = [];
@@ -2076,6 +2345,8 @@ function cmdVerifyCodebaseDrift(cwd, raw) {
2076
2345
  // ASCII common case — passes through untouched. Both capture groups are
2077
2346
  // decoded: R/C lines carry old AND new paths, either may be quoted.
2078
2347
  const file = decodeGitQuotedPath(m[3] || m[2]);
2348
+ if (isPlanningArtifact(file))
2349
+ continue;
2079
2350
  if (status === 'A' || status === 'R' || status === 'C')
2080
2351
  added.push(file);
2081
2352
  else if (status === 'M')
@@ -2154,5 +2425,6 @@ module.exports = {
2154
2425
  cmdVerifyCodebaseDrift,
2155
2426
  computeContextDrift,
2156
2427
  cmdVerifyContextDrift,
2428
+ cmdStampCodebaseMap,
2157
2429
  STATE_HEAD_ADVISORY_COMMITS,
2158
2430
  };
@@ -733,6 +733,7 @@ module.exports = {
733
733
  inspectWorkstream,
734
734
  isCompletedInventory: workstream_inventory_builder_cjs_1.isCompletedInventory,
735
735
  listWorkstreamInventories,
736
+ readCurrentMilestoneVersion,
736
737
  sortWorkstreamInventories,
737
738
  workstreamsRoot,
738
739
  };