@opengsd/gsd-core 1.12.0 → 1.14.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 (455) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/.opencode/plugins/gsd-core.js +12 -0
  4. package/agents/gsd-advisor-researcher.compact.md +85 -0
  5. package/agents/gsd-ai-researcher.compact.md +96 -0
  6. package/agents/gsd-assumptions-analyzer.compact.md +81 -0
  7. package/agents/gsd-code-fixer.compact.md +458 -0
  8. package/agents/gsd-code-fixer.md +5 -5
  9. package/agents/gsd-code-reviewer.compact.md +269 -0
  10. package/agents/gsd-code-reviewer.md +15 -3
  11. package/agents/gsd-codebase-mapper.compact.md +760 -0
  12. package/agents/gsd-debug-session-manager.compact.md +345 -0
  13. package/agents/gsd-doc-classifier.compact.md +192 -0
  14. package/agents/gsd-doc-synthesizer.compact.md +200 -0
  15. package/agents/gsd-doc-verifier.compact.md +143 -0
  16. package/agents/gsd-doc-writer.compact.md +440 -0
  17. package/agents/gsd-dom-verifier.compact.md +138 -0
  18. package/agents/gsd-domain-researcher.compact.md +141 -0
  19. package/agents/gsd-eval-auditor.compact.md +160 -0
  20. package/agents/gsd-eval-planner.compact.md +137 -0
  21. package/agents/gsd-executor.md +63 -35
  22. package/agents/gsd-framework-selector.compact.md +82 -0
  23. package/agents/gsd-integration-checker.compact.md +245 -0
  24. package/agents/gsd-intel-updater.compact.md +226 -0
  25. package/agents/gsd-mempalace-curator.compact.md +45 -0
  26. package/agents/gsd-nyquist-auditor.compact.md +179 -0
  27. package/agents/gsd-pattern-mapper.compact.md +275 -0
  28. package/agents/gsd-plan-checker.md +76 -57
  29. package/agents/gsd-planner.md +14 -0
  30. package/agents/gsd-project-researcher.compact.md +587 -0
  31. package/agents/gsd-research-synthesizer.compact.md +212 -0
  32. package/agents/gsd-roadmapper.compact.md +454 -0
  33. package/agents/gsd-roadmapper.md +13 -0
  34. package/agents/gsd-security-auditor.compact.md +162 -0
  35. package/agents/gsd-ui-auditor.compact.md +404 -0
  36. package/agents/gsd-ui-checker.compact.md +277 -0
  37. package/agents/gsd-ui-checker.md +19 -3
  38. package/agents/gsd-ui-researcher.compact.md +282 -0
  39. package/agents/gsd-ui-researcher.md +29 -0
  40. package/agents/gsd-user-profiler.compact.md +108 -0
  41. package/agents/gsd-verifier.md +23 -1
  42. package/bin/install.js +444 -134
  43. package/commands/gsd/cleanup.md +1 -0
  44. package/commands/gsd/code-review.md +2 -1
  45. package/commands/gsd/complete-milestone.md +1 -0
  46. package/commands/gsd/config.md +1 -0
  47. package/commands/gsd/debug.md +1 -0
  48. package/commands/gsd/execute-phase.md +1 -1
  49. package/commands/gsd/graphify.md +1 -0
  50. package/commands/gsd/health.md +1 -0
  51. package/commands/gsd/mempalace-capture.md +1 -0
  52. package/commands/gsd/mempalace-recall.md +1 -0
  53. package/commands/gsd/new-milestone.md +1 -0
  54. package/commands/gsd/new-project.md +1 -0
  55. package/commands/gsd/next.md +1 -0
  56. package/commands/gsd/ns-workflow.md +2 -1
  57. package/commands/gsd/pause-work.md +1 -0
  58. package/commands/gsd/phase.md +2 -1
  59. package/commands/gsd/pr-branch.md +1 -0
  60. package/commands/gsd/quick-batch.md +105 -0
  61. package/commands/gsd/resume-work.md +1 -0
  62. package/commands/gsd/review-backlog.md +1 -0
  63. package/commands/gsd/settings.md +2 -1
  64. package/commands/gsd/stats.md +1 -0
  65. package/commands/gsd/surface.md +18 -8
  66. package/commands/gsd/thread.md +1 -0
  67. package/commands/gsd/workspace.md +1 -0
  68. package/commands/gsd/workstreams.md +1 -0
  69. package/gsd-core/bin/check-latest-version.cjs +8 -3
  70. package/gsd-core/bin/gsd-tools.cjs +532 -174
  71. package/gsd-core/bin/lib/adr-parser.cjs +1 -1
  72. package/gsd-core/bin/lib/artifacts.cjs +2 -1
  73. package/gsd-core/bin/lib/audit.cjs +39 -22
  74. package/gsd-core/bin/lib/broken-windows.cjs +168 -49
  75. package/gsd-core/bin/lib/capability-activation.cjs +27 -0
  76. package/gsd-core/bin/lib/capability-lifecycle.cjs +10 -6
  77. package/gsd-core/bin/lib/capability-loader.cjs +135 -1
  78. package/gsd-core/bin/lib/capability-registry.cjs +528 -116
  79. package/gsd-core/bin/lib/capability-source.cjs +19 -2
  80. package/gsd-core/bin/lib/capability-state.cjs +7 -1
  81. package/gsd-core/bin/lib/capability-validator.cjs +134 -5
  82. package/gsd-core/bin/lib/capability-writer.cjs +14 -4
  83. package/gsd-core/bin/lib/check-command-router.cjs +198 -38
  84. package/gsd-core/bin/lib/claude-orchestration.cjs +10 -25
  85. package/gsd-core/bin/lib/clusters.cjs +1 -0
  86. package/gsd-core/bin/lib/code-review-depth.cjs +2 -2
  87. package/gsd-core/bin/lib/command-aliases.cjs +16 -0
  88. package/gsd-core/bin/lib/commands.cjs +981 -79
  89. package/gsd-core/bin/lib/config-loader.cjs +4 -0
  90. package/gsd-core/bin/lib/config.cjs +153 -38
  91. package/gsd-core/bin/lib/core-utils.cjs +34 -7
  92. package/gsd-core/bin/lib/coverage.cjs +1 -1
  93. package/gsd-core/bin/lib/decisions.cjs +343 -28
  94. package/gsd-core/bin/lib/edge-probe.cjs +14 -1
  95. package/gsd-core/bin/lib/external-descriptor-trust.cjs +29 -14
  96. package/gsd-core/bin/lib/file-overlap-partitioner.cjs +74 -0
  97. package/gsd-core/bin/lib/frontmatter.cjs +137 -23
  98. package/gsd-core/bin/lib/gap-checker.cjs +22 -13
  99. package/gsd-core/bin/lib/git-base-branch.cjs +10 -2
  100. package/gsd-core/bin/lib/gsd2-import.cjs +1 -2
  101. package/gsd-core/bin/lib/health-diagnostic-rules/phase-structure.cjs +8 -2
  102. package/gsd-core/bin/lib/health-diagnostic-rules/roadmap-disk-consistency.cjs +54 -11
  103. package/gsd-core/bin/lib/health-diagnostic-rules/state-consistency.cjs +87 -23
  104. package/gsd-core/bin/lib/health-diagnostic-rules/worktree-health.cjs +1 -1
  105. package/gsd-core/bin/lib/host-integration.cjs +57 -5
  106. package/gsd-core/bin/lib/init-command-router.cjs +14 -0
  107. package/gsd-core/bin/lib/init.cjs +539 -60
  108. package/gsd-core/bin/lib/install-engine.cjs +199 -14
  109. package/gsd-core/bin/lib/install-model-override-resolver.cjs +45 -0
  110. package/gsd-core/bin/lib/install-profiles.cjs +36 -14
  111. package/gsd-core/bin/lib/installer-migration-report.cjs +1 -0
  112. package/gsd-core/bin/lib/installer-migrations.cjs +33 -4
  113. package/gsd-core/bin/lib/io.cjs +35 -0
  114. package/gsd-core/bin/lib/loop-resolver.cjs +64 -39
  115. package/gsd-core/bin/lib/markdown-table.cjs +123 -0
  116. package/gsd-core/bin/lib/mcp-catalog.cjs +2 -2
  117. package/gsd-core/bin/lib/milestone.cjs +41 -10
  118. package/gsd-core/bin/lib/model-resolver.cjs +101 -10
  119. package/gsd-core/bin/lib/phase-command-router.cjs +20 -7
  120. package/gsd-core/bin/lib/phase-id.cjs +412 -31
  121. package/gsd-core/bin/lib/phase-lifecycle.cjs +61 -0
  122. package/gsd-core/bin/lib/phase.cjs +941 -98
  123. package/gsd-core/bin/lib/plan-document.cjs +10 -0
  124. package/gsd-core/bin/lib/planning-inspect.cjs +34 -18
  125. package/gsd-core/bin/lib/planning-snapshot.cjs +206 -30
  126. package/gsd-core/bin/lib/planning-workspace.cjs +153 -29
  127. package/gsd-core/bin/lib/pristine-baseline.cjs +182 -0
  128. package/gsd-core/bin/lib/prohibition-enforcement.cjs +91 -4
  129. package/gsd-core/bin/lib/quick-batch-command-router.cjs +285 -0
  130. package/gsd-core/bin/lib/quick-batch-dispatch.cjs +250 -0
  131. package/gsd-core/bin/lib/quick-batch.cjs +840 -0
  132. package/gsd-core/bin/lib/refactor-trigger-command-router.cjs +61 -2
  133. package/gsd-core/bin/lib/research-store.cjs +11 -12
  134. package/gsd-core/bin/lib/review-lane-descriptor.cjs +53 -5
  135. package/gsd-core/bin/lib/review-lane-invocation.cjs +96 -1
  136. package/gsd-core/bin/lib/review-lane-runner.cjs +136 -10
  137. package/gsd-core/bin/lib/reviewer-step-dispatch.cjs +337 -0
  138. package/gsd-core/bin/lib/roadmap-parser.cjs +555 -41
  139. package/gsd-core/bin/lib/roadmap.cjs +292 -69
  140. package/gsd-core/bin/lib/runtime-artifact-conversion.cjs +260 -43
  141. package/gsd-core/bin/lib/runtime-artifact-install-plan.cjs +28 -20
  142. package/gsd-core/bin/lib/runtime-artifact-layout.cjs +294 -108
  143. package/gsd-core/bin/lib/runtime-hooks-surface.cjs +408 -47
  144. package/gsd-core/bin/lib/security.cjs +126 -7
  145. package/gsd-core/bin/lib/shell-command-projection.cjs +4 -0
  146. package/gsd-core/bin/lib/smart-entry.cjs +7 -9
  147. package/gsd-core/bin/lib/state-document.cjs +159 -32
  148. package/gsd-core/bin/lib/state-md-schema.cjs +44 -27
  149. package/gsd-core/bin/lib/state-transition.cjs +465 -62
  150. package/gsd-core/bin/lib/state.cjs +906 -151
  151. package/gsd-core/bin/lib/surface.cjs +83 -10
  152. package/gsd-core/bin/lib/task-command-router.cjs +12 -6
  153. package/gsd-core/bin/lib/tdd-red-evidence.cjs +133 -0
  154. package/gsd-core/bin/lib/uat.cjs +1420 -516
  155. package/gsd-core/bin/lib/update-context.cjs +36 -26
  156. package/gsd-core/bin/lib/validate.cjs +230 -12
  157. package/gsd-core/bin/lib/vendor/js-yaml.cjs +11 -3
  158. package/gsd-core/bin/lib/verification-command-router.cjs +2 -1
  159. package/gsd-core/bin/lib/verification.cjs +316 -23
  160. package/gsd-core/bin/lib/verify-command-grounding.cjs +1 -1
  161. package/gsd-core/bin/lib/verify-command-router.cjs +1 -0
  162. package/gsd-core/bin/lib/verify.cjs +531 -36
  163. package/gsd-core/bin/lib/workstream-inventory.cjs +21 -2
  164. package/gsd-core/bin/lib/worktree-safety.cjs +21 -7
  165. package/gsd-core/bin/shared/config-defaults.manifest.json +1 -0
  166. package/gsd-core/bin/shared/config-schema.manifest.json +13 -0
  167. package/gsd-core/bin/verify-reapply-patches.cjs +507 -81
  168. package/gsd-core/references/agent-contracts.md +3 -3
  169. package/gsd-core/references/compact-content-gate.md +66 -0
  170. package/gsd-core/references/edge-probe.md +17 -13
  171. package/gsd-core/references/execute-mvp-tdd.md +18 -16
  172. package/gsd-core/references/execute-phase-response-language.md +6 -0
  173. package/gsd-core/references/executor-examples.md +42 -0
  174. package/gsd-core/references/few-shot-examples/plan-checker.md +15 -15
  175. package/gsd-core/references/loop-hook-dispatch.md +18 -0
  176. package/gsd-core/references/model-profiles.md +12 -3
  177. package/gsd-core/references/mvp-concepts.md +2 -2
  178. package/gsd-core/references/plan-checker-examples.md +41 -0
  179. package/gsd-core/references/planner-antipatterns.md +25 -0
  180. package/gsd-core/references/planner-chunked.md +5 -1
  181. package/gsd-core/references/planner-coupling.md +42 -0
  182. package/gsd-core/references/planner-quick-batch.md +71 -0
  183. package/gsd-core/references/planner-reviews.md +47 -0
  184. package/gsd-core/references/planner-revision.md +75 -2
  185. package/gsd-core/references/planning-config.md +5 -1
  186. package/gsd-core/references/response-language-directive.md +9 -0
  187. package/gsd-core/references/revision-loop.md +118 -11
  188. package/gsd-core/references/tdd.md +17 -9
  189. package/gsd-core/references/thinking-models-planning.md +18 -2
  190. package/gsd-core/references/verification-patterns.md +17 -4
  191. package/gsd-core/references/verifier-evidence-gate.md +160 -0
  192. package/gsd-core/references/worktree-path-safety.md +112 -2
  193. package/gsd-core/templates/README.md +7 -1
  194. package/gsd-core/templates/phase-prompt.md +4 -0
  195. package/gsd-core/templates/state.md +6 -3
  196. package/gsd-core/templates/summary.compact.md +212 -0
  197. package/gsd-core/templates/user-setup.compact.md +199 -0
  198. package/gsd-core/templates/user-setup.md +0 -9
  199. package/gsd-core/templates/verification-report.md +5 -0
  200. package/gsd-core/workflows/add-backlog.md +2 -0
  201. package/gsd-core/workflows/add-phase.md +2 -0
  202. package/gsd-core/workflows/add-tests.md +1 -1
  203. package/gsd-core/workflows/add-todo.md +4 -3
  204. package/gsd-core/workflows/ai-integration-phase.md +1 -1
  205. package/gsd-core/workflows/analyze-dependencies.md +2 -0
  206. package/gsd-core/workflows/audit-fix.md +2 -0
  207. package/gsd-core/workflows/audit-milestone.md +2 -0
  208. package/gsd-core/workflows/audit-uat.md +2 -0
  209. package/gsd-core/workflows/autonomous.md +15 -10
  210. package/gsd-core/workflows/check-todos.md +5 -3
  211. package/gsd-core/workflows/cleanup.md +4 -2
  212. package/gsd-core/workflows/code-review/steps/structural-pre-pass.md +22 -13
  213. package/gsd-core/workflows/code-review-fix.md +5 -3
  214. package/gsd-core/workflows/code-review.md +211 -43
  215. package/gsd-core/workflows/complete-milestone/detail/elaboration.md +274 -0
  216. package/gsd-core/workflows/complete-milestone.md +40 -254
  217. package/gsd-core/workflows/debug.md +1 -1
  218. package/gsd-core/workflows/diagnose-issues.md +5 -1
  219. package/gsd-core/workflows/discuss-phase/modes/advisor.md +2 -0
  220. package/gsd-core/workflows/discuss-phase/modes/all.md +2 -0
  221. package/gsd-core/workflows/discuss-phase/modes/analyze.md +2 -0
  222. package/gsd-core/workflows/discuss-phase/modes/auto.md +2 -0
  223. package/gsd-core/workflows/discuss-phase/modes/batch.md +2 -0
  224. package/gsd-core/workflows/discuss-phase/modes/chain.md +2 -0
  225. package/gsd-core/workflows/discuss-phase/modes/default.md +2 -0
  226. package/gsd-core/workflows/discuss-phase/modes/power.md +2 -0
  227. package/gsd-core/workflows/discuss-phase/modes/text.md +2 -0
  228. package/gsd-core/workflows/discuss-phase/templates/context.md +2 -0
  229. package/gsd-core/workflows/discuss-phase/templates/discussion-log.md +2 -0
  230. package/gsd-core/workflows/discuss-phase-assumptions.md +1 -1
  231. package/gsd-core/workflows/discuss-phase-power.md +2 -0
  232. package/gsd-core/workflows/discuss-phase.md +1 -1
  233. package/gsd-core/workflows/do.md +43 -13
  234. package/gsd-core/workflows/docs-update/detail/elaboration.md +179 -0
  235. package/gsd-core/workflows/docs-update.md +15 -156
  236. package/gsd-core/workflows/edit-phase.md +2 -0
  237. package/gsd-core/workflows/eval-review.md +1 -1
  238. package/gsd-core/workflows/execute-phase/detail/elaboration.md +124 -0
  239. package/gsd-core/workflows/execute-phase/steps/codebase-drift-gate.md +20 -3
  240. package/gsd-core/workflows/execute-phase/steps/completion-reconciliation.md +56 -0
  241. package/gsd-core/workflows/execute-phase/steps/executor-isolation-dispatch.md +24 -3
  242. package/gsd-core/workflows/execute-phase/steps/executor-progress-policy.md +43 -0
  243. package/gsd-core/workflows/execute-phase/steps/per-plan-worktree-gate.md +8 -2
  244. package/gsd-core/workflows/execute-phase/steps/regression-gate-run.md +2 -0
  245. package/gsd-core/workflows/execute-phase/steps/sequential-root-pin.md +35 -0
  246. package/gsd-core/workflows/execute-phase/steps/tdd-applicability-resolution.md +25 -0
  247. package/gsd-core/workflows/execute-phase/steps/worktree-recovery-policy.md +2 -0
  248. package/gsd-core/workflows/execute-phase.md +78 -159
  249. package/gsd-core/workflows/execute-plan.md +28 -15
  250. package/gsd-core/workflows/explore.md +2 -0
  251. package/gsd-core/workflows/extract-learnings.md +2 -0
  252. package/gsd-core/workflows/fast.md +6 -0
  253. package/gsd-core/workflows/forensics.md +2 -0
  254. package/gsd-core/workflows/graduation.md +1 -1
  255. package/gsd-core/workflows/health.md +1 -1
  256. package/gsd-core/workflows/help/modes/brief.md +2 -0
  257. package/gsd-core/workflows/help/modes/default.md +2 -0
  258. package/gsd-core/workflows/help/modes/full.compact.md +398 -0
  259. package/gsd-core/workflows/help/modes/full.md +12 -0
  260. package/gsd-core/workflows/help/modes/topic.md +2 -0
  261. package/gsd-core/workflows/help.md +3 -1
  262. package/gsd-core/workflows/import.md +3 -3
  263. package/gsd-core/workflows/inbox.md +1 -1
  264. package/gsd-core/workflows/ingest-docs.md +1 -1
  265. package/gsd-core/workflows/insert-phase.md +2 -0
  266. package/gsd-core/workflows/list-phase-assumptions.md +2 -0
  267. package/gsd-core/workflows/list-seeds.md +2 -0
  268. package/gsd-core/workflows/list-workspaces.md +2 -0
  269. package/gsd-core/workflows/manager.md +3 -3
  270. package/gsd-core/workflows/map-codebase.md +52 -3
  271. package/gsd-core/workflows/milestone-summary.md +2 -0
  272. package/gsd-core/workflows/mvp-phase.md +1 -1
  273. package/gsd-core/workflows/new-milestone.md +55 -13
  274. package/gsd-core/workflows/new-project/detail/elaboration.md +216 -0
  275. package/gsd-core/workflows/new-project.md +37 -205
  276. package/gsd-core/workflows/new-workspace.md +1 -1
  277. package/gsd-core/workflows/next.md +2 -0
  278. package/gsd-core/workflows/node-repair.md +2 -0
  279. package/gsd-core/workflows/note.md +2 -0
  280. package/gsd-core/workflows/onboard.md +1 -1
  281. package/gsd-core/workflows/pause-work.md +19 -4
  282. package/gsd-core/workflows/plan-phase/detail/elaboration.md +209 -0
  283. package/gsd-core/workflows/plan-phase/steps/chunked-planning-mode.md +100 -18
  284. package/gsd-core/workflows/plan-phase/steps/prd-express-path.md +2 -0
  285. package/gsd-core/workflows/plan-phase/steps/stall-detection-helpers.md +9 -0
  286. package/gsd-core/workflows/plan-phase.md +144 -185
  287. package/gsd-core/workflows/plan-review-convergence.md +102 -10
  288. package/gsd-core/workflows/plant-seed.md +1 -1
  289. package/gsd-core/workflows/pr-branch.md +30 -10
  290. package/gsd-core/workflows/profile-user.md +1 -1
  291. package/gsd-core/workflows/progress/steps/forensic-audit.md +1 -1
  292. package/gsd-core/workflows/progress.md +25 -3
  293. package/gsd-core/workflows/quick/steps/plan-checker-loop.md +37 -2
  294. package/gsd-core/workflows/quick/steps/research-phase.md +3 -3
  295. package/gsd-core/workflows/quick-batch/steps/batch-init.md +55 -0
  296. package/gsd-core/workflows/quick-batch/steps/completion.md +65 -0
  297. package/gsd-core/workflows/quick-batch/steps/merge-wave.md +100 -0
  298. package/gsd-core/workflows/quick-batch/steps/plan-checker-loop.md +147 -0
  299. package/gsd-core/workflows/quick-batch/steps/planner-wave.md +158 -0
  300. package/gsd-core/workflows/quick-batch/steps/research-phase.md +95 -0
  301. package/gsd-core/workflows/quick-batch/steps/resume-mode.md +49 -0
  302. package/gsd-core/workflows/quick-batch/steps/verification-wave.md +73 -0
  303. package/gsd-core/workflows/quick-batch/steps/worktree-dispatch.md +169 -0
  304. package/gsd-core/workflows/quick-batch.md +203 -0
  305. package/gsd-core/workflows/quick.md +21 -4
  306. package/gsd-core/workflows/reapply-patches.md +79 -3
  307. package/gsd-core/workflows/remove-phase.md +2 -0
  308. package/gsd-core/workflows/remove-workspace.md +1 -1
  309. package/gsd-core/workflows/resume-project.md +6 -2
  310. package/gsd-core/workflows/review.md +215 -10
  311. package/gsd-core/workflows/scan.md +2 -0
  312. package/gsd-core/workflows/section-manifest.json +12 -0
  313. package/gsd-core/workflows/secure-phase.md +1 -1
  314. package/gsd-core/workflows/session-report.md +2 -0
  315. package/gsd-core/workflows/settings-advanced.md +2 -0
  316. package/gsd-core/workflows/settings-integrations.md +9 -8
  317. package/gsd-core/workflows/settings.md +19 -6
  318. package/gsd-core/workflows/ship.md +10 -10
  319. package/gsd-core/workflows/sketch-wrap-up.md +2 -0
  320. package/gsd-core/workflows/sketch.md +1 -1
  321. package/gsd-core/workflows/smart-entry.md +1 -1
  322. package/gsd-core/workflows/spec-phase.md +24 -19
  323. package/gsd-core/workflows/spike-wrap-up.md +2 -0
  324. package/gsd-core/workflows/spike.md +1 -1
  325. package/gsd-core/workflows/stats.md +2 -0
  326. package/gsd-core/workflows/sync-skills.md +12 -4
  327. package/gsd-core/workflows/thread.md +2 -0
  328. package/gsd-core/workflows/transition.md +2 -0
  329. package/gsd-core/workflows/ui-phase.md +26 -5
  330. package/gsd-core/workflows/ui-review.md +1 -1
  331. package/gsd-core/workflows/ultraplan-phase.md +2 -0
  332. package/gsd-core/workflows/undo.md +1 -1
  333. package/gsd-core/workflows/update.md +48 -43
  334. package/gsd-core/workflows/validate-phase.md +1 -1
  335. package/gsd-core/workflows/verify-work/detail/elaboration.md +230 -0
  336. package/gsd-core/workflows/verify-work.md +68 -182
  337. package/hooks/dist/gsd-agent-isolation-guard.js +42 -16
  338. package/hooks/dist/gsd-check-update-worker.js +19 -2
  339. package/hooks/dist/gsd-context-monitor.js +371 -27
  340. package/hooks/dist/gsd-cursor-subagent-start.js +34 -14
  341. package/hooks/dist/gsd-node-runner.sh +1 -0
  342. package/hooks/dist/gsd-prompt-guard.js +30 -5
  343. package/hooks/dist/gsd-read-guard.js +2 -0
  344. package/hooks/dist/gsd-read-injection-scanner.js +5 -5
  345. package/hooks/dist/gsd-secret-read-guard.js +1105 -0
  346. package/hooks/dist/gsd-statusline.js +18 -10
  347. package/hooks/dist/gsd-validate-commit.sh +474 -7
  348. package/hooks/dist/gsd-workflow-guard.js +2 -1
  349. package/hooks/dist/gsd-worktree-path-guard.js +25 -14
  350. package/hooks/dist/gsd-write-guard.js +46 -1
  351. package/hooks/dist/lib/dispatch-identity.js +187 -0
  352. package/hooks/dist/lib/filename-classification.js +64 -0
  353. package/hooks/dist/lib/git-cmd.js +210 -1
  354. package/hooks/dist/lib/injection-patterns.js +36 -6
  355. package/hooks/dist/lib/isolation-deny-reason.js +53 -1
  356. package/hooks/dist/lib/isolation-sentinel.js +58 -19
  357. package/hooks/dist/managed-hooks-registry.cjs +1 -0
  358. package/hooks/gsd-agent-isolation-guard.js +42 -16
  359. package/hooks/gsd-check-update-worker.js +19 -2
  360. package/hooks/gsd-context-monitor.js +371 -27
  361. package/hooks/gsd-cursor-subagent-start.js +34 -14
  362. package/hooks/gsd-node-runner.sh +1 -0
  363. package/hooks/gsd-prompt-guard.js +30 -5
  364. package/hooks/gsd-read-guard.js +2 -0
  365. package/hooks/gsd-read-injection-scanner.js +5 -5
  366. package/hooks/gsd-secret-read-guard.js +1105 -0
  367. package/hooks/gsd-statusline.js +18 -10
  368. package/hooks/gsd-validate-commit.sh +474 -7
  369. package/hooks/gsd-workflow-guard.js +2 -1
  370. package/hooks/gsd-worktree-path-guard.js +25 -14
  371. package/hooks/gsd-write-guard.js +46 -1
  372. package/hooks/hooks.json +6 -0
  373. package/hooks/lib/dispatch-identity.js +187 -0
  374. package/hooks/lib/filename-classification.js +64 -0
  375. package/hooks/lib/git-cmd.js +210 -1
  376. package/hooks/lib/injection-patterns.js +36 -6
  377. package/hooks/lib/isolation-deny-reason.js +53 -1
  378. package/hooks/lib/isolation-sentinel.js +58 -19
  379. package/hooks/managed-hooks-registry.cjs +1 -0
  380. package/package.json +13 -9
  381. package/scripts/benchmark-compact-content-variants.cjs +298 -0
  382. package/scripts/benchmark-compact-content.cjs +368 -0
  383. package/scripts/build-hooks.js +11 -4
  384. package/scripts/check-contract-drift.cjs +4 -1
  385. package/scripts/check-env.cjs +36 -8
  386. package/scripts/check-glossary-refs.cjs +25 -21
  387. package/scripts/ci-next-health.cjs +271 -0
  388. package/scripts/ci-prepare-test-scope.cjs +7 -7
  389. package/scripts/ci-test-scope.cjs +133 -20
  390. package/scripts/ci-timeout-report.cjs +1 -1
  391. package/scripts/diff-touches-shipped-paths.cjs +1 -1
  392. package/scripts/docs-guard-registry.cjs +17 -2
  393. package/scripts/gen-adr-index.cjs +8 -2
  394. package/scripts/gen-inventory-manifest.cjs +12 -0
  395. package/scripts/gen-loop-host-contract.cjs +67 -15
  396. package/scripts/gen-platform-conformance-tier.cjs +557 -0
  397. package/scripts/lib/drift-scan.cjs +1 -1
  398. package/scripts/lib/macos-conformance-tier.generated.cjs +210 -0
  399. package/scripts/lib/npm-version-check-diagnosis.cjs +59 -0
  400. package/scripts/lib/platform-conformance-tier.generated.cjs +276 -0
  401. package/scripts/lib/shellcheck-fetch.cjs +247 -0
  402. package/scripts/lib/suite-detection.cjs +32 -0
  403. package/scripts/lint-allow-test-rule-refs.allowlist.json +0 -6
  404. package/scripts/lint-allow-test-rule-refs.effective-ceiling.json +1 -1
  405. package/scripts/lint-allow-test-rule-refs.unverified-ceiling.json +1 -1
  406. package/scripts/lint-allowed-tools-parity.cjs +221 -0
  407. package/scripts/lint-docs-guard-registration.exempt-baseline.cjs +24 -2
  408. package/scripts/lint-phase-enumeration-drift.cjs +24 -6
  409. package/scripts/lint-phase-id-drift.cjs +465 -15
  410. package/scripts/lint-portable-grep.cjs +176 -0
  411. package/scripts/lint-response-language-coverage.cjs +530 -0
  412. package/scripts/lint-source-test-name-collision.cjs +1 -1
  413. package/scripts/lint-test-file-count.allowlist.json +4 -1
  414. package/scripts/lint-vendored-deps.cjs +128 -17
  415. package/scripts/lint-workflow-shellcheck-baseline.json +1112 -0
  416. package/scripts/lint-workflow-shellcheck.cjs +614 -0
  417. package/scripts/npm-audit-baseline.cjs +376 -0
  418. package/scripts/prompt-injection-scan.sh +22 -0
  419. package/scripts/require-issue-link-policy.cjs +16 -1
  420. package/scripts/workflow-size.cjs +139 -0
  421. package/skills/gsd-cleanup/SKILL.md +1 -0
  422. package/skills/gsd-code-review/SKILL.md +2 -1
  423. package/skills/gsd-complete-milestone/SKILL.md +1 -0
  424. package/skills/gsd-config/SKILL.md +1 -0
  425. package/skills/gsd-debug/SKILL.md +1 -0
  426. package/skills/gsd-execute-phase/SKILL.md +1 -1
  427. package/skills/gsd-graphify/SKILL.md +1 -0
  428. package/skills/gsd-health/SKILL.md +1 -0
  429. package/skills/gsd-mempalace-capture/SKILL.md +1 -0
  430. package/skills/gsd-mempalace-recall/SKILL.md +1 -0
  431. package/skills/gsd-new-milestone/SKILL.md +1 -0
  432. package/skills/gsd-new-project/SKILL.md +1 -0
  433. package/skills/gsd-next/SKILL.md +1 -0
  434. package/skills/gsd-ns-workflow/SKILL.md +1 -0
  435. package/skills/gsd-pause-work/SKILL.md +1 -0
  436. package/skills/gsd-phase/SKILL.md +2 -1
  437. package/skills/gsd-pr-branch/SKILL.md +1 -0
  438. package/skills/gsd-quick-batch/SKILL.md +105 -0
  439. package/skills/gsd-resume-work/SKILL.md +1 -0
  440. package/skills/gsd-review-backlog/SKILL.md +1 -0
  441. package/skills/gsd-settings/SKILL.md +2 -1
  442. package/skills/gsd-stats/SKILL.md +1 -0
  443. package/skills/gsd-surface/SKILL.md +18 -8
  444. package/skills/gsd-thread/SKILL.md +1 -0
  445. package/skills/gsd-workspace/SKILL.md +1 -0
  446. package/skills/gsd-workstreams/SKILL.md +1 -0
  447. package/vscode/package.json +1 -1
  448. package/gsd-core/templates/claude-md.md +0 -145
  449. package/gsd-core/templates/codebase/concerns.md +0 -310
  450. package/gsd-core/templates/codebase/conventions.md +0 -307
  451. package/gsd-core/templates/codebase/integrations.md +0 -280
  452. package/gsd-core/templates/codebase/structure.md +0 -285
  453. package/gsd-core/templates/codebase/testing.md +0 -480
  454. package/gsd-core/templates/debug-subagent-prompt.md +0 -91
  455. package/gsd-core/templates/discovery.md +0 -146
@@ -32,6 +32,8 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
32
32
  };
33
33
  const node_fs_1 = __importDefault(require("node:fs"));
34
34
  const node_path_1 = __importDefault(require("node:path"));
35
+ const node_crypto_1 = __importDefault(require("node:crypto"));
36
+ const project_root_cjs_1 = require("./project-root.cjs");
35
37
  // eslint-disable-next-line @typescript-eslint/no-require-imports -- io.cjs is an export= CommonJS module
36
38
  const io = require("./io.cjs");
37
39
  // eslint-disable-next-line @typescript-eslint/no-require-imports -- phase-id.cjs is an export= CommonJS module
@@ -46,6 +48,7 @@ const coreUtilsMod = require("./core-utils.cjs");
46
48
  const planningScopeMod = require("./planning-scope.cjs");
47
49
  const shell_command_projection_cjs_1 = require("./shell-command-projection.cjs");
48
50
  const runtime_slash_cjs_1 = require("./runtime-slash.cjs");
51
+ const security_cjs_1 = require("./security.cjs");
49
52
  const { output, error } = io;
50
53
  const { extractPhaseToken, scopeToPhase } = phaseId;
51
54
  const { extractFrontmatter } = frontmatterMod;
@@ -131,10 +134,183 @@ function projectNextCommand(bare, runtime, tail = '') {
131
134
  return '';
132
135
  return `${(0, runtime_slash_cjs_1.formatGsdSlash)(bare, runtime)}${tail}`;
133
136
  }
137
+ /**
138
+ * Real `node:fs`-backed default satisfying FsLike. Every method wraps a call
139
+ * to `fs.<method>` rather than capturing the function reference — existing
140
+ * tests mock individual `fs` methods in place (`t.mock.method(fs, 'statSync', …)`),
141
+ * and a captured reference taken at module-load time would be invisible to
142
+ * that late mock, silently un-mocking this seam's "default" path.
143
+ */
144
+ const defaultFsImpl = {
145
+ readdirSync: (dir) => node_fs_1.default.readdirSync(dir),
146
+ readFileSync: (filePath, encoding) => node_fs_1.default.readFileSync(filePath, encoding),
147
+ statSync: (filePath) => node_fs_1.default.statSync(filePath),
148
+ };
134
149
  /** Normalize separators to posix (git emits `/`; callers may pass `\` on Windows). */
135
150
  function toPosix(p) {
136
151
  return p.replace(/\\/g, '/');
137
152
  }
153
+ /**
154
+ * #4155: canonicalize a covered-input path before it becomes either a
155
+ * dedup/sort/hash key or a confinement-check subject. `path.posix.normalize`
156
+ * collapses `./`, redundant slashes, and internal `..` segments (`a/../../b`
157
+ * → `../b`) — without this, two spellings of the SAME file (`src/x.cts` vs
158
+ * `./src/x.cts`) hash as different covered inputs (spurious `stale`, or a
159
+ * file double-counted into the digest under two keys), and an escape
160
+ * disguised by an internal `..` segment slips past a check that only looks
161
+ * at the string's start.
162
+ */
163
+ function normalizeRel(p) {
164
+ return node_path_1.default.posix.normalize(toPosix(p));
165
+ }
166
+ /** Canonicalize a covered-files list: normalize, de-duplicate, sort — the SAME
167
+ * transform computeCoveredDigest and cmdVerificationFingerprint both need
168
+ * (the digest's own key order; the CLI's own `covered_files` JSON output). */
169
+ function canonicalizeCoveredFiles(files) {
170
+ return Array.from(new Set(files.map(normalizeRel))).sort();
171
+ }
172
+ // ─── #4155: covered-input fingerprint ──────────────────────────────────────────
173
+ /**
174
+ * Bump on any change to the digest's input shape (path list, hashing order,
175
+ * per-file hash algorithm) so an old stored digest can never collide with a
176
+ * differently-computed new one — a version mismatch is just a mismatch.
177
+ */
178
+ const FINGERPRINT_VERSION = 1;
179
+ /**
180
+ * #4155: recompute the deterministic content fingerprint over a verifier's
181
+ * declared covered-input set (phase PLAN/SUMMARY, mapped requirements,
182
+ * implementation files in the change set) and return the versioned digest
183
+ * string, or `null` if the set cannot be resolved.
184
+ *
185
+ * Determinism: paths are de-duplicated and SORTED before hashing (directory
186
+ * enumeration order is irrelevant), each path is resolved relative to
187
+ * `projectRoot` (the absolute checkout path never enters the digest), and
188
+ * file BYTES are hashed (mtime never enters the digest).
189
+ *
190
+ * NOT normalized: line endings. Unlike the report-frontmatter read (which
191
+ * runs every VERIFICATION.md through `normalizeLineEndings`), covered-file
192
+ * bytes are hashed exactly as they sit on disk. A covered text file checked
193
+ * out with CRLF line endings (e.g. a Windows checkout without a `.gitattributes
194
+ * eol=lf` rule pinning it to LF) hashes differently than the same file on an
195
+ * LF checkout — a real cross-platform digest mismatch, not a bug, since GSD
196
+ * installs into arbitrary user projects with no guaranteed line-ending policy.
197
+ *
198
+
199
+ * Fail closed: a covered path that is empty, absolute, escapes
200
+ * `projectRoot` (`..` traversal), or cannot be read (missing, unreadable,
201
+ * not a regular file) makes the WHOLE fingerprint unresolvable — returns
202
+ * `null` — rather than silently hashing a partial set. Callers treat `null`
203
+ * as stale (#4155), the same fail-closed shape #3057 B3 established for the
204
+ * legacy mtime staleness check.
205
+ *
206
+ * Always reads through the REAL `node:fs`, never a caller-injected `FsLike`
207
+ * seam — same reasoning as the root canonicalization below, extended to
208
+ * every covered file: `covered_files` is expected to span the whole
209
+ * `projectRoot` (implementation files under `src/`, not just `.planning/`
210
+ * artifacts), so a caller-scoped containment wrapper narrower than
211
+ * `projectRoot` (e.g. `planning-inspect.cts`'s `containmentEnforcingVerificationFs`,
212
+ * confined to `.planning/`) would reject every implementation-file read and
213
+ * report EVERY fingerprinted phase permanently `stale` regardless of actual
214
+ * drift — the bug this comment now documents against regressing. The
215
+ * `realRel`-vs-`realRoot` re-check a few lines below already does the real
216
+ * confinement work (against `projectRoot`, the correct boundary for this
217
+ * data), so no security property is lost by bypassing a narrower seam here.
218
+ */
219
+ function computeCoveredDigest(projectRoot, coveredFiles) {
220
+ const uniqueSorted = canonicalizeCoveredFiles(coveredFiles);
221
+ if (uniqueSorted.length === 0)
222
+ return null;
223
+ // Canonicalize the root ONCE — every candidate's realpath is checked against
224
+ // this, not the possibly-symlinked `projectRoot` argument itself. Always via
225
+ // the REAL fs, never fsImpl: `projectRoot` is a trusted anchor the CALLER
226
+ // derived (findProjectRoot), not attacker-influenced covered-input data —
227
+ // routing it through a caller-scoped containment seam (e.g. #4155's
228
+ // containmentEnforcingVerificationFs, confined to `.planning/`, a proper
229
+ // SUBSET of `projectRoot`) would reject the root itself and fail every
230
+ // lookup regardless of whether the covered files are legitimate.
231
+ let realRoot;
232
+ try {
233
+ realRoot = node_fs_1.default.realpathSync(projectRoot);
234
+ }
235
+ catch {
236
+ return null;
237
+ }
238
+ const parts = [];
239
+ for (const rel of uniqueSorted) {
240
+ // `normalizeRel` (already applied by `canonicalizeCoveredFiles` above)
241
+ // collapses internal `..` segments before `rel` ever reaches here
242
+ // (`a/../../b` → `../b`), so this start-of-string check is already the
243
+ // full lexical confinement test — no separate post-`path.resolve`
244
+ // re-check can observe a different answer.
245
+ if (rel === '' || rel === '..' || rel.startsWith('../') || node_path_1.default.isAbsolute(rel))
246
+ return null;
247
+ const resolved = node_path_1.default.resolve(projectRoot, rel);
248
+ let bytes;
249
+ try {
250
+ // A regular file INSIDE projectRoot can still be a symlink whose TARGET
251
+ // escapes it — statSync/readFileSync follow symlinks, so the lexical
252
+ // confinement check above is not enough. realpathSync resolves the
253
+ // actual target; re-confining against realRoot closes that gap.
254
+ const real = node_fs_1.default.realpathSync(resolved);
255
+ // Both operands are already realpath-resolved (this fn's own realpathSync calls
256
+ // above), so the shared containment comparison applies directly (ADR-4650) —
257
+ // no re-resolution through assertWithinRoot/tryWithinRoot, which would redo work
258
+ // this function already owns for its exists-vs-escaped tri-state.
259
+ if (!(0, security_cjs_1.isContainedIn)(real, realRoot)) {
260
+ return null;
261
+ }
262
+ const st = node_fs_1.default.statSync(real);
263
+ if (!st.isFile())
264
+ return null;
265
+ bytes = node_fs_1.default.readFileSync(real);
266
+ }
267
+ catch {
268
+ return null;
269
+ }
270
+ const fileHash = node_crypto_1.default.createHash('sha256').update(bytes).digest('hex');
271
+ parts.push(`${rel}\n${fileHash}\n`);
272
+ }
273
+ const aggregate = node_crypto_1.default
274
+ .createHash('sha256')
275
+ .update(`v${FINGERPRINT_VERSION}\n${parts.join('')}`, 'utf-8')
276
+ .digest('hex');
277
+ return `v${FINGERPRINT_VERSION}:sha256:${aggregate}`;
278
+ }
279
+ /**
280
+ * #4155: the content fingerprint only recomputes digests for paths the
281
+ * verifier actually DECLARED in `covered_files` — it has no way to notice a
282
+ * plan or summary added to the phase directory AFTER verification if that
283
+ * new file was never declared. This closes that gap the same way the
284
+ * legacy mtime check always did: by re-scanning the LIVE directory (not the
285
+ * declared list) for every current `*-PLAN.md`/`*-SUMMARY.md` and checking
286
+ * each is represented in `coveredFiles` — matched by suffix (mirrors
287
+ * `matchRequestedFile`'s convention) since `coveredFiles` holds
288
+ * project-root-relative paths while the scan returns phase-relative
289
+ * filenames. Returns `true` if every current plan/summary is covered,
290
+ * `false` otherwise — callers only ever branch on this pass/fail, so no
291
+ * caller needs which artifact was uncovered.
292
+ *
293
+ * Fails CLOSED on an incomplete scan: `scanPhasePlans` never throws on a
294
+ * readdir failure — it reports it via `scope` (`SCOPE.UNREADABLE` for the
295
+ * phase dir itself, `SCOPE.TRUNCATED` for an unreadable nested `plans/`)
296
+ * with whatever files it DID manage to enumerate, per `SCOPE`'s own
297
+ * contract (`planning-scope.cts`): zero items under a non-`COMPLETE` scope
298
+ * is a NON-answer, never "this phase has no plans." Branching on `scope`
299
+ * here (rather than a try/catch, which this scan never triggers) is what
300
+ * makes an unreadable `plans/` dir report `false` instead of silently
301
+ * treating its invisible contents as vacuously covered — the same
302
+ * fail-open regression #3057 B3 fixed for the legacy path.
303
+ */
304
+ function allCurrentArtifactsCovered(phaseDir, coveredFiles) {
305
+ const scan = scanPhasePlans(phaseDir);
306
+ if (scan.scope !== SCOPE.COMPLETE)
307
+ return false;
308
+ const coveredPosix = canonicalizeCoveredFiles(coveredFiles);
309
+ return [...scan.allPlanFiles, ...scan.summaryFiles].every((artifact) => {
310
+ const artifactPosix = toPosix(artifact);
311
+ return coveredPosix.some((c) => c === artifactPosix || c.endsWith(`/${artifactPosix}`));
312
+ });
313
+ }
138
314
  /**
139
315
  * Match a git-emitted (repo-root-relative) path back to the caller's
140
316
  * phaseDir-relative request by exact match or `/`-bounded suffix — precise
@@ -299,7 +475,7 @@ function resolvePhaseArtifactFile(entries, bareName, options = {}) {
299
475
  // filtered out, and if that leaves nothing the code falls through to
300
476
  // `allowBare`/`null` deliberately.
301
477
  const scoped = options.phaseDirName
302
- ? scopeToPhase(candidates, options.phaseDirName)
478
+ ? scopeToPhase(candidates, options.phaseDirName, options.convention)
303
479
  : candidates;
304
480
  if (scoped.length > 0)
305
481
  return scoped[0];
@@ -356,7 +532,7 @@ function resolveVerificationFile(entries, options = {}) {
356
532
  function resolveUatFile(entries, options = {}) {
357
533
  return resolvePhaseArtifactFile(entries, 'UAT.md', options);
358
534
  }
359
- function findStaleVerificationSummary(phaseDir, fsImpl = node_fs_1.default, phaseCleanCommitTimesMs = defaultPhaseCleanCommitTimesMs) {
535
+ function findStaleVerificationSummary(phaseDir, fsImpl = defaultFsImpl, phaseCleanCommitTimesMs = defaultPhaseCleanCommitTimesMs, convention) {
360
536
  // FS errors (TOCTOU: a SUMMARY listed by scanPhasePlans then removed before statSync;
361
537
  // unreadable dir; broken symlink; file->dir swap) must degrade rather than throw
362
538
  // uncaught into callers that are NOT under the planning lock (init.manager /
@@ -372,9 +548,20 @@ function findStaleVerificationSummary(phaseDir, fsImpl = node_fs_1.default, phas
372
548
  // or sentinel-numbered canonically-shaped file cannot outrank this
373
549
  // phase's own (possibly non-canonical) report. #3511: phaseDirName scopes
374
550
  // the fallback path to this same phase (see resolveVerificationFile docs).
551
+ // #4187: allowBare — this staleness seam must see the same report set the
552
+ // status reader sees, or a bare report could never read `stale` while its
553
+ // dashed twin could (two answers from one verb).
375
554
  const phaseDirName = node_path_1.default.basename(phaseDir);
376
- const phaseToken = extractPhaseToken(phaseDirName);
377
- const verificationFile = resolveVerificationFile(phaseFiles, { phaseToken, phaseDirName });
555
+ // #612: derive the token with the resolved convention so a bracket dir's
556
+ // own token is read behind its `{CODE}.{MM}-` prefix. #4187: keep the bare
557
+ // report tier aligned with the status reader.
558
+ const phaseToken = extractPhaseToken(phaseDirName, convention);
559
+ const verificationFile = resolveVerificationFile(phaseFiles, {
560
+ allowBare: true,
561
+ phaseToken,
562
+ phaseDirName,
563
+ convention,
564
+ });
378
565
  if (!verificationFile)
379
566
  return { determined: true, stale: false };
380
567
  const summaryFiles = scanPhasePlans(phaseDir).summaryFiles
@@ -415,7 +602,9 @@ function findStaleVerificationSummary(phaseDir, fsImpl = node_fs_1.default, phas
415
602
  * 1. Find the phase's verification report via `resolveVerificationFile`
416
603
  * (canonical `<phase-token>-VERIFICATION.md` preferred; falls back to the
417
604
  * alphabetically-first `*-VERIFICATION.md` that belongs to THIS phase when
418
- * none is canonical — #3357/#3511). If none → status 'missing'.
605
+ * none is canonical — #3357/#3511; and, when the directory's only report
606
+ * is a bare `VERIFICATION.md`, that file — #4187, matching
607
+ * `verification.resolve-file`). If none → status 'missing'.
419
608
  * 2. Extract `status` from FRONTMATTER ONLY via the shared extractFrontmatter
420
609
  * parser (DEFECT.FRONTMATTER-SCALAR-BROAD-GREP fix — parser anchors at byte 0).
421
610
  * If no frontmatter block or no `status` key → status 'missing'.
@@ -433,10 +622,15 @@ function findStaleVerificationSummary(phaseDir, fsImpl = node_fs_1.default, phas
433
622
  * projected into (#2617).
434
623
  */
435
624
  function readVerificationStatus(phaseDir, opts = {}) {
436
- const fsImpl = opts.fs ?? node_fs_1.default;
625
+ const fsImpl = opts.fs ?? defaultFsImpl;
437
626
  const phaseCleanCommitTimesMs = opts.phaseCleanCommitTimesMs ?? defaultPhaseCleanCommitTimesMs;
438
627
  const runtime = opts.runtime ?? 'claude';
439
- // Phase token for the gaps_found command
628
+ // Phase token for the gaps_found command — deliberately convention-LESS
629
+ // even when `opts.convention` is present: the token becomes a bare COMMAND
630
+ // ARGUMENT below, and a bare bracket phase number is milestone-ambiguous
631
+ // (`02` cannot tell GSD.01-02 from GSD.02-02), so the argument keeps its
632
+ // pre-#612 shape. The convention-aware token is derived separately for
633
+ // FILE RESOLUTION only (`resolutionToken`, at the readdir below).
440
634
  const baseName = node_path_1.default.basename(phaseDir);
441
635
  const phaseToken = extractPhaseToken(baseName);
442
636
  const derivedPhaseNumber = phaseToken.length > 0 ? phaseToken : baseName;
@@ -452,12 +646,21 @@ function readVerificationStatus(phaseDir, opts = {}) {
452
646
  let verificationFile = null;
453
647
  try {
454
648
  const entries = fsImpl.readdirSync(phaseDir);
455
- // #3492: pin selection to THIS phase's own token (already derived above
456
- // for the routed command argument) so a stray cross-phase or
457
- // sentinel-numbered canonically-shaped file cannot outrank this phase's
458
- // own (possibly non-canonical) report. #3511: baseName also scopes the
459
- // fallback path to this same phase (see resolveVerificationFile docs).
460
- verificationFile = resolveVerificationFile(entries, { phaseToken, phaseDirName: baseName });
649
+ // #3492: pin selection to THIS phase's own token so a stray cross-phase
650
+ // or sentinel-numbered canonically-shaped file cannot outrank this phase's
651
+ // own report. #612: derive a separate convention-aware RESOLUTION token
652
+ // for bracket directories while the routed command argument above stays
653
+ // convention-less and milestone-unambiguous. #4187: keep the bare report
654
+ // tier aligned with every other verification reader.
655
+ const resolutionToken = opts.convention === 'bracket'
656
+ ? extractPhaseToken(baseName, opts.convention)
657
+ : phaseToken;
658
+ verificationFile = resolveVerificationFile(entries, {
659
+ allowBare: true,
660
+ phaseToken: resolutionToken,
661
+ phaseDirName: baseName,
662
+ convention: opts.convention,
663
+ });
461
664
  }
462
665
  catch {
463
666
  // Directory unreadable → treat as missing
@@ -470,6 +673,7 @@ function readVerificationStatus(phaseDir, opts = {}) {
470
673
  // extractFrontmatter anchors at byte 0, so body `status:` lines are ignored.
471
674
  const filePath = node_path_1.default.join(phaseDir, verificationFile);
472
675
  let rawStatus = null;
676
+ let fm = {};
473
677
  try {
474
678
  // #3707-CR follow-up MINOR 1: normalize line endings at this read
475
679
  // boundary — this function's own `readFileSync` is the equivalent seam
@@ -482,7 +686,7 @@ function readVerificationStatus(phaseDir, opts = {}) {
482
686
  // verification as if the step never ran, the fail-safe direction but the
483
687
  // same root cause as the false-clean class fixed elsewhere in #3707-CR.
484
688
  const content = normalizeLineEndings(fsImpl.readFileSync(filePath, 'utf-8'));
485
- const fm = extractFrontmatter(content, filePath);
689
+ fm = extractFrontmatter(content, filePath);
486
690
  const statusVal = fm['status'];
487
691
  // status is always a scalar string in a well-formed VERIFICATION.md frontmatter;
488
692
  // only accept string values — arrays and objects are not valid status values.
@@ -507,8 +711,52 @@ function readVerificationStatus(phaseDir, opts = {}) {
507
711
  next_command: projectNextCommand('plan-phase', runtime, `${phaseArg} --gaps`),
508
712
  };
509
713
  }
510
- const staleCheck = findStaleVerificationSummary(phaseDir, fsImpl, phaseCleanCommitTimesMs);
511
- if (staleCheck.determined && staleCheck.stale) {
714
+ // #4155: a report that declares a covered-input fingerprint is checked by
715
+ // RECOMPUTING that fingerprint over current file content — strictly
716
+ // content-grounded, and it REPLACES (not supplements) the legacy
717
+ // SUMMARY-mtime check below for that report. A report with no fingerprint
718
+ // metadata (every report written before #4155) keeps the exact legacy
719
+ // mtime-based behavior, unchanged.
720
+ const coveredFilesVal = fm['covered_files'];
721
+ const coveredDigestVal = fm['covered_digest'];
722
+ // A report OPTS IN to the fingerprint check by declaring EITHER field —
723
+ // once opted in, an incomplete or malformed pair (one field present but
724
+ // not the other, an empty array, a non-array, a blank digest) fails closed
725
+ // to `stale` rather than silently downgrading to the weaker legacy
726
+ // mtime-only check, which would only ever notice a newer SUMMARY.
727
+ const declaresFingerprint = coveredFilesVal !== undefined || coveredDigestVal !== undefined;
728
+ const hasWellFormedFingerprint = Array.isArray(coveredFilesVal) &&
729
+ coveredFilesVal.length > 0 &&
730
+ coveredFilesVal.every((f) => typeof f === 'string') &&
731
+ typeof coveredDigestVal === 'string' &&
732
+ coveredDigestVal.trim().length > 0;
733
+ let staleCheckIndeterminate = false;
734
+ let isStale;
735
+ if (declaresFingerprint) {
736
+ // Stated directly rather than relying on `null !== coveredDigestVal`
737
+ // being true whenever the pair is malformed: `!hasWellFormedFingerprint`
738
+ // fails closed explicitly, and its `||` short-circuit means
739
+ // computeCoveredDigest/allCurrentArtifactsCovered never run on a
740
+ // malformed (wrong-shaped) `coveredFilesVal`. The two `||`s after it
741
+ // short-circuit in turn: the live-directory re-scan (for a plan/summary
742
+ // added AFTER verification and never declared in covered_files) only
743
+ // runs once the digest itself has already matched.
744
+ isStale =
745
+ !hasWellFormedFingerprint ||
746
+ computeCoveredDigest((0, project_root_cjs_1.findProjectRoot)(phaseDir), coveredFilesVal) !== coveredDigestVal ||
747
+ !allCurrentArtifactsCovered(phaseDir, coveredFilesVal);
748
+ }
749
+ else {
750
+ const staleCheck = findStaleVerificationSummary(phaseDir, fsImpl, phaseCleanCommitTimesMs, opts.convention);
751
+ isStale = staleCheck.determined && staleCheck.stale;
752
+ // staleCheck is either {determined:true, stale:false} (checked; nothing
753
+ // stale) or {determined:false} (could not check — fs/scan/clock failure).
754
+ // Both fall through to normal routing below (the pre-existing no-throw
755
+ // fail-open contract is unchanged), but the indeterminate case is flagged
756
+ // on the returned result so a caller can tell the two apart (#3057 B3).
757
+ staleCheckIndeterminate = !staleCheck.determined;
758
+ }
759
+ if (isStale) {
512
760
  const entry = VERIFICATION_ROUTING_TABLE['stale'];
513
761
  return {
514
762
  status: entry.status,
@@ -516,12 +764,6 @@ function readVerificationStatus(phaseDir, opts = {}) {
516
764
  next_command: projectNextCommand('verify-work', runtime, phaseArg),
517
765
  };
518
766
  }
519
- // staleCheck is either {determined:true, stale:false} (checked; nothing
520
- // stale) or {determined:false} (could not check — fs/scan/clock failure).
521
- // Both fall through to normal routing below (the pre-existing no-throw
522
- // fail-open contract is unchanged), but the indeterminate case is flagged
523
- // on the returned result so a caller can tell the two apart (#3057 B3).
524
- const staleCheckIndeterminate = !staleCheck.determined;
525
767
  // 3. Route — exclude internal sentinels from raw-file lookup (they are
526
768
  // constructed internally above, never written by the verifier).
527
769
  if (rawStatus in VERIFICATION_ROUTING_TABLE &&
@@ -576,7 +818,7 @@ function readVerificationStatus(phaseDir, opts = {}) {
576
818
  * never re-derives or requires them itself.
577
819
  */
578
820
  function isPhaseComplete(phaseDir, deps = {}) {
579
- const fsImpl = deps.fs ?? node_fs_1.default;
821
+ const fsImpl = deps.fs ?? defaultFsImpl;
580
822
  let readable = true;
581
823
  try {
582
824
  fsImpl.readdirSync(phaseDir);
@@ -589,6 +831,7 @@ function isPhaseComplete(phaseDir, deps = {}) {
589
831
  phaseCleanCommitTimesMs: deps.phaseCleanCommitTimesMs,
590
832
  runtime: deps.runtime,
591
833
  phaseNumber: deps.phaseNumber,
834
+ convention: deps.convention,
592
835
  });
593
836
  return {
594
837
  value: {
@@ -655,6 +898,54 @@ function cmdVerificationResolveFile(cwd, phaseDirArg, raw) {
655
898
  }
656
899
  output({ verification_file: verificationPath }, raw, verificationPath);
657
900
  }
901
+ /**
902
+ * CLI command handler (#4155): compute the covered-input fingerprint the
903
+ * verifier embeds in VERIFICATION.md frontmatter (`covered_files`,
904
+ * `covered_digest`). The verifier is an LLM agent, not a hashing engine —
905
+ * this command does the deterministic math so the agent only has to name
906
+ * the covered paths and copy the result into frontmatter.
907
+ *
908
+ * Emits `{ covered_files: <sorted deduped paths>, covered_digest: <digest> }`
909
+ * on success. A covered path that is missing, unreadable, or escapes the
910
+ * project root fails the WHOLE command (fail closed — a partial fingerprint
911
+ * would be worse than none): `error()` is called and nothing is emitted.
912
+ *
913
+ * @param cwd - Current working directory.
914
+ * @param phaseDirArg - Phase directory path (absolute or relative to cwd);
915
+ * its project root is the base covered paths resolve against.
916
+ * @param files - Covered-input paths, relative to the project root.
917
+ * @param raw - Whether to emit raw (non-JSON) output: just the
918
+ * `covered_digest` string, so `VAR=$(gsd_run query
919
+ * verification.fingerprint "$PHASE_DIR" ... --raw)` is
920
+ * directly assignable. `covered_files` is unambiguous
921
+ * from the caller's own input list in that mode, so
922
+ * only the computed digest needs a raw form.
923
+ */
924
+ function cmdVerificationFingerprint(cwd, phaseDirArg, files, raw) {
925
+ if (!phaseDirArg) {
926
+ error('phase directory required for verification.fingerprint');
927
+ return;
928
+ }
929
+ if (files.length === 0) {
930
+ error('at least one covered file required for verification.fingerprint');
931
+ return;
932
+ }
933
+ const phaseDir = node_path_1.default.resolve(cwd, phaseDirArg);
934
+ const projectRoot = (0, project_root_cjs_1.findProjectRoot)(phaseDir);
935
+ // canonicalizeCoveredFiles here is for the emitted `covered_files` field —
936
+ // computeCoveredDigest canonicalizes its own `coveredFiles` argument
937
+ // internally too (it must, for callers like readVerificationStatus that
938
+ // pass raw, un-canonicalized frontmatter values), so passing an
939
+ // already-canonical list keeps that internal pass a cheap no-op rather
940
+ // than a second meaningfully different canonicalization.
941
+ const uniqueSorted = canonicalizeCoveredFiles(files);
942
+ const digest = computeCoveredDigest(projectRoot, uniqueSorted);
943
+ if (digest === null) {
944
+ error('could not compute fingerprint — a covered file is missing, unreadable, or escapes the project root');
945
+ return;
946
+ }
947
+ output({ covered_files: uniqueSorted, covered_digest: digest }, raw, digest);
948
+ }
658
949
  module.exports = {
659
950
  VERIFIER_STATUSES,
660
951
  VERIFICATION_ROUTING_TABLE,
@@ -666,4 +957,6 @@ module.exports = {
666
957
  isPhaseComplete,
667
958
  cmdVerificationStatus,
668
959
  cmdVerificationResolveFile,
960
+ computeCoveredDigest,
961
+ cmdVerificationFingerprint,
669
962
  };
@@ -453,7 +453,7 @@ function declaredPathCovers(declaredPaths, norm) {
453
453
  if (typeof p !== 'string')
454
454
  return false;
455
455
  const dp = stripLeadingDotSlash(toSlash(p));
456
- return dp === target || dp.startsWith(target + '/');
456
+ return dp === target || dp.startsWith(target + '/'); // allow-handrolled-containment: declared-path coverage for pending-creation detection, not containment
457
457
  });
458
458
  }
459
459
  /**
@@ -36,6 +36,7 @@ function routeVerifyCommand({ verify, args, cwd, raw, error }) {
36
36
  // per ADR/PRD 3524 §3 / L160 (CJS-only by design). Routing through
37
37
  // recursive dispatch would re-enter this router path.
38
38
  'codebase-drift': () => verify.cmdVerifyCodebaseDrift(cwd, raw),
39
+ 'context-drift': () => verify.cmdVerifyContextDrift(cwd, args[2], raw),
39
40
  },
40
41
  });
41
42
  }