@opengsd/gsd-core 1.17.0 → 1.19.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (369) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +2 -1
  3. package/.opencode/plugins/gsd-core.js +2 -3
  4. package/agents/gsd-advisor-researcher.compact.md +1 -1
  5. package/agents/gsd-advisor-researcher.md +1 -1
  6. package/agents/gsd-assumptions-analyzer.compact.md +1 -1
  7. package/agents/gsd-assumptions-analyzer.md +1 -1
  8. package/agents/gsd-code-fixer.compact.md +3 -3
  9. package/agents/gsd-code-fixer.md +5 -4
  10. package/agents/gsd-code-reviewer.compact.md +1 -1
  11. package/agents/gsd-code-reviewer.md +1 -1
  12. package/agents/gsd-codebase-mapper.compact.md +1 -1
  13. package/agents/gsd-codebase-mapper.md +1 -1
  14. package/agents/gsd-debugger.md +1 -1
  15. package/agents/gsd-doc-verifier.compact.md +4 -4
  16. package/agents/gsd-doc-verifier.md +4 -3
  17. package/agents/gsd-doc-writer.compact.md +23 -5
  18. package/agents/gsd-doc-writer.md +19 -6
  19. package/agents/gsd-eval-auditor.compact.md +1 -1
  20. package/agents/gsd-eval-auditor.md +1 -1
  21. package/agents/gsd-executor.md +7 -3
  22. package/agents/gsd-integration-checker.compact.md +1 -1
  23. package/agents/gsd-integration-checker.md +1 -1
  24. package/agents/gsd-nyquist-auditor.compact.md +1 -1
  25. package/agents/gsd-nyquist-auditor.md +1 -1
  26. package/agents/gsd-phase-researcher.md +1 -1
  27. package/agents/gsd-plan-checker.md +2 -1
  28. package/agents/gsd-planner.md +5 -4
  29. package/agents/gsd-project-researcher.compact.md +1 -1
  30. package/agents/gsd-project-researcher.md +1 -1
  31. package/agents/gsd-research-synthesizer.compact.md +5 -5
  32. package/agents/gsd-research-synthesizer.md +5 -5
  33. package/agents/gsd-roadmapper.compact.md +1 -1
  34. package/agents/gsd-roadmapper.md +1 -1
  35. package/agents/gsd-security-auditor.compact.md +1 -1
  36. package/agents/gsd-security-auditor.md +1 -1
  37. package/agents/gsd-ui-auditor.compact.md +1 -1
  38. package/agents/gsd-ui-auditor.md +1 -1
  39. package/agents/gsd-ui-checker.compact.md +1 -1
  40. package/agents/gsd-ui-checker.md +1 -1
  41. package/agents/gsd-ui-researcher.compact.md +13 -1
  42. package/agents/gsd-ui-researcher.md +28 -1
  43. package/agents/gsd-verifier.md +2 -2
  44. package/bin/install.js +460 -105
  45. package/commands/gsd/code-review.md +8 -0
  46. package/commands/gsd/pause-work.md +1 -1
  47. package/gsd-core/bin/gsd-tools.cjs +124 -18
  48. package/gsd-core/bin/lib/agent-install-check.cjs +29 -3
  49. package/gsd-core/bin/lib/args-terminator.cjs +114 -0
  50. package/gsd-core/bin/lib/broken-windows.cjs +59 -1
  51. package/gsd-core/bin/lib/capability-ledger.cjs +25 -0
  52. package/gsd-core/bin/lib/capability-lock.cjs +41 -8
  53. package/gsd-core/bin/lib/capability-registry.cjs +162 -74
  54. package/gsd-core/bin/lib/capability-source.cjs +3 -1
  55. package/gsd-core/bin/lib/capability-state.cjs +20 -11
  56. package/gsd-core/bin/lib/capability-validator.cjs +23 -4
  57. package/gsd-core/bin/lib/commands.cjs +5 -1
  58. package/gsd-core/bin/lib/config-loader.cjs +13 -3
  59. package/gsd-core/bin/lib/config.cjs +256 -58
  60. package/gsd-core/bin/lib/core-utils.cjs +34 -0
  61. package/gsd-core/bin/lib/decision-coverage-support.cjs +93 -0
  62. package/gsd-core/bin/lib/gate-context-drift.cjs +8 -3
  63. package/gsd-core/bin/lib/gate-decision-coverage-plan.cjs +21 -1
  64. package/gsd-core/bin/lib/gate-decision-coverage-verify.cjs +19 -1
  65. package/gsd-core/bin/lib/gate-evaluation-scope.cjs +134 -13
  66. package/gsd-core/bin/lib/gate-predicate-evaluator.cjs +23 -3
  67. package/gsd-core/bin/lib/gate-predicate.cjs +8 -12
  68. package/gsd-core/bin/lib/gate-tdd-review-checkpoint.cjs +114 -18
  69. package/gsd-core/bin/lib/gate-ui-safety.cjs +23 -5
  70. package/gsd-core/bin/lib/git-info-exclude.cjs +57 -0
  71. package/gsd-core/bin/lib/health-diagnostic-rules/phase-structure.cjs +18 -3
  72. package/gsd-core/bin/lib/health-diagnostic-types.cjs +4 -0
  73. package/gsd-core/bin/lib/health-diagnostic.cjs +52 -1
  74. package/gsd-core/bin/lib/host-integration.cjs +13 -6
  75. package/gsd-core/bin/lib/init.cjs +76 -5
  76. package/gsd-core/bin/lib/install-effort-resolver.cjs +2 -2
  77. package/gsd-core/bin/lib/install-engine.cjs +246 -32
  78. package/gsd-core/bin/lib/install-model-override-resolver.cjs +4 -3
  79. package/gsd-core/bin/lib/install-profiles.cjs +14 -7
  80. package/gsd-core/bin/lib/installer-migration-report.cjs +2 -0
  81. package/gsd-core/bin/lib/markdown-line-diff.cjs +147 -0
  82. package/gsd-core/bin/lib/markdown-sectionizer.cjs +113 -0
  83. package/gsd-core/bin/lib/milestone-lock.cjs +8 -1
  84. package/gsd-core/bin/lib/milestone-release-map.cjs +244 -0
  85. package/gsd-core/bin/lib/milestone-release-tags.cjs +440 -0
  86. package/gsd-core/bin/lib/milestone.cjs +43 -1
  87. package/gsd-core/bin/lib/model-catalog.cjs +8 -0
  88. package/gsd-core/bin/lib/normalize-test-command.cjs +67 -0
  89. package/gsd-core/bin/lib/phase-id.cjs +21 -4
  90. package/gsd-core/bin/lib/phase-lifecycle.cjs +37 -4
  91. package/gsd-core/bin/lib/phase.cjs +235 -37
  92. package/gsd-core/bin/lib/planning-snapshot.cjs +3 -1
  93. package/gsd-core/bin/lib/planning-workspace.cjs +18 -0
  94. package/gsd-core/bin/lib/pristine-baseline.cjs +11 -3
  95. package/gsd-core/bin/lib/read-only-git-env.cjs +58 -0
  96. package/gsd-core/bin/lib/report-parser.cjs +57 -1
  97. package/gsd-core/bin/lib/review-lane-descriptor.cjs +35 -2
  98. package/gsd-core/bin/lib/review-lane-invocation.cjs +40 -0
  99. package/gsd-core/bin/lib/review-lane-runner.cjs +33 -4
  100. package/gsd-core/bin/lib/review-reviewer-selection.cjs +20 -1
  101. package/gsd-core/bin/lib/roadmap-parser.cjs +12 -0
  102. package/gsd-core/bin/lib/roadmap-upgrade.cjs +39 -2
  103. package/gsd-core/bin/lib/roadmap.cjs +89 -30
  104. package/gsd-core/bin/lib/runtime-artifact-conversion.cjs +428 -16
  105. package/gsd-core/bin/lib/runtime-artifact-layout.cjs +8 -4
  106. package/gsd-core/bin/lib/runtime-homes.cjs +18 -0
  107. package/gsd-core/bin/lib/runtime-hooks-surface.cjs +60 -16
  108. package/gsd-core/bin/lib/shell-command-projection.cjs +123 -16
  109. package/gsd-core/bin/lib/smart-entry.cjs +37 -16
  110. package/gsd-core/bin/lib/state-contract.cjs +29 -9
  111. package/gsd-core/bin/lib/state-document.cjs +47 -5
  112. package/gsd-core/bin/lib/state-transition.cjs +150 -34
  113. package/gsd-core/bin/lib/state.cjs +140 -88
  114. package/gsd-core/bin/lib/surface.cjs +9 -0
  115. package/gsd-core/bin/lib/task-command-router.cjs +7 -28
  116. package/gsd-core/bin/lib/task-content-resolution.cjs +46 -1
  117. package/gsd-core/bin/lib/tdd-red-evidence.cjs +5 -0
  118. package/gsd-core/bin/lib/tdd-test-files.cjs +42 -0
  119. package/gsd-core/bin/lib/verification.cjs +96 -9
  120. package/gsd-core/bin/lib/verify.cjs +38 -1
  121. package/gsd-core/bin/lib/worktree-safety.cjs +22 -1
  122. package/gsd-core/bin/shared/config-defaults.manifest.json +1 -0
  123. package/gsd-core/bin/shared/config-schema.manifest.json +1 -0
  124. package/gsd-core/references/agent-contracts.md +1 -1
  125. package/gsd-core/references/agent-skills-bootstrap.md +26 -7
  126. package/gsd-core/references/git-integration.md +14 -3
  127. package/gsd-core/references/loop-hook-dispatch.md +13 -6
  128. package/gsd-core/references/planner-reversibility.md +18 -4
  129. package/gsd-core/references/planner-verify-command-grounding.md +18 -0
  130. package/gsd-core/references/planning-config.md +2 -0
  131. package/gsd-core/references/tdd.md +5 -4
  132. package/gsd-core/references/verifier-phase-gates.md +8 -1
  133. package/gsd-core/templates/DEBUG.md +16 -0
  134. package/gsd-core/templates/config.json +2 -1
  135. package/gsd-core/templates/verification-report.md +1 -1
  136. package/gsd-core/workflows/add-backlog.md +10 -2
  137. package/gsd-core/workflows/add-phase.md +5 -1
  138. package/gsd-core/workflows/add-tests.md +5 -1
  139. package/gsd-core/workflows/add-todo.md +5 -1
  140. package/gsd-core/workflows/ai-integration-phase.md +5 -1
  141. package/gsd-core/workflows/audit-fix.md +2 -1
  142. package/gsd-core/workflows/audit-milestone.md +21 -9
  143. package/gsd-core/workflows/autonomous/templates/minimal-context.md +41 -0
  144. package/gsd-core/workflows/autonomous.md +52 -64
  145. package/gsd-core/workflows/check-todos.md +5 -1
  146. package/gsd-core/workflows/code-review/steps/structural-pre-pass.md +10 -1
  147. package/gsd-core/workflows/code-review-fix.md +5 -1
  148. package/gsd-core/workflows/code-review.md +66 -8
  149. package/gsd-core/workflows/complete-milestone/detail/elaboration.md +5 -1
  150. package/gsd-core/workflows/complete-milestone/steps/project-md-evolution-review.md +145 -0
  151. package/gsd-core/workflows/complete-milestone/steps/release-map.md +80 -0
  152. package/gsd-core/workflows/complete-milestone.md +64 -157
  153. package/gsd-core/workflows/debug.md +5 -1
  154. package/gsd-core/workflows/diagnose-issues.md +34 -6
  155. package/gsd-core/workflows/discuss-phase/modes/chain.md +5 -1
  156. package/gsd-core/workflows/discuss-phase/modes/power.md +1 -1
  157. package/gsd-core/workflows/discuss-phase-assumptions.md +44 -5
  158. package/gsd-core/workflows/discuss-phase-power.md +19 -2
  159. package/gsd-core/workflows/discuss-phase.md +11 -11
  160. package/gsd-core/workflows/docs-update/detail/elaboration.md +7 -6
  161. package/gsd-core/workflows/docs-update.md +27 -5
  162. package/gsd-core/workflows/edit-phase.md +5 -1
  163. package/gsd-core/workflows/eval-review.md +11 -3
  164. package/gsd-core/workflows/execute-phase/steps/codebase-drift-gate.md +5 -1
  165. package/gsd-core/workflows/execute-phase/steps/per-plan-worktree-gate.md +13 -7
  166. package/gsd-core/workflows/execute-phase/steps/post-merge-gate.md +19 -8
  167. package/gsd-core/workflows/execute-phase/steps/regression-gate-run.md +21 -3
  168. package/gsd-core/workflows/execute-phase/steps/regression-gate.md +18 -1
  169. package/gsd-core/workflows/execute-phase/steps/verify-phase-goal.md +7 -3
  170. package/gsd-core/workflows/execute-phase/steps/wave-post-gate-hooks.md +11 -2
  171. package/gsd-core/workflows/execute-phase.md +46 -11
  172. package/gsd-core/workflows/execute-plan.md +8 -4
  173. package/gsd-core/workflows/extract-learnings.md +6 -2
  174. package/gsd-core/workflows/forensics.md +5 -1
  175. package/gsd-core/workflows/health.md +2 -1
  176. package/gsd-core/workflows/import.md +6 -2
  177. package/gsd-core/workflows/insert-phase.md +5 -1
  178. package/gsd-core/workflows/manager.md +10 -2
  179. package/gsd-core/workflows/map-codebase.md +5 -1
  180. package/gsd-core/workflows/milestone-summary.md +10 -2
  181. package/gsd-core/workflows/new-milestone.md +13 -5
  182. package/gsd-core/workflows/new-project.md +9 -5
  183. package/gsd-core/workflows/next.md +8 -4
  184. package/gsd-core/workflows/pause-work.md +3 -3
  185. package/gsd-core/workflows/plan-phase/steps/auto-advance-check.md +70 -0
  186. package/gsd-core/workflows/plan-phase/steps/chunked-planning-mode.md +7 -3
  187. package/gsd-core/workflows/plan-phase/steps/closed-phase-gate.md +6 -2
  188. package/gsd-core/workflows/plan-phase.md +54 -86
  189. package/gsd-core/workflows/plan-review-convergence.md +17 -5
  190. package/gsd-core/workflows/plant-seed.md +8 -4
  191. package/gsd-core/workflows/progress.md +6 -2
  192. package/gsd-core/workflows/quick/steps/plan-checker-loop.md +1 -1
  193. package/gsd-core/workflows/quick/steps/worktree-pre-dispatch-commit.md +8 -1
  194. package/gsd-core/workflows/quick-batch/steps/plan-checker-loop.md +2 -1
  195. package/gsd-core/workflows/quick-batch.md +5 -1
  196. package/gsd-core/workflows/quick.md +23 -8
  197. package/gsd-core/workflows/remove-phase.md +5 -1
  198. package/gsd-core/workflows/resume-project.md +5 -1
  199. package/gsd-core/workflows/review.md +22 -15
  200. package/gsd-core/workflows/scan.md +5 -1
  201. package/gsd-core/workflows/section-manifest.json +5 -0
  202. package/gsd-core/workflows/secure-phase.md +6 -2
  203. package/gsd-core/workflows/settings.md +4 -3
  204. package/gsd-core/workflows/ship.md +16 -14
  205. package/gsd-core/workflows/spec-phase.md +3 -6
  206. package/gsd-core/workflows/thread.md +10 -2
  207. package/gsd-core/workflows/transition.md +5 -1
  208. package/gsd-core/workflows/ui-phase.md +5 -1
  209. package/gsd-core/workflows/ui-review.md +6 -2
  210. package/gsd-core/workflows/ultraplan-phase.md +5 -1
  211. package/gsd-core/workflows/update.md +5 -1
  212. package/gsd-core/workflows/validate-phase.md +6 -2
  213. package/gsd-core/workflows/verify-work/detail/elaboration.md +1 -1
  214. package/gsd-core/workflows/verify-work/steps/mvp-uat-framing.md +5 -1
  215. package/gsd-core/workflows/verify-work.md +15 -6
  216. package/hooks/dist/gsd-agent-isolation-guard.js +14 -2
  217. package/hooks/dist/gsd-cursor-subagent-start.js +8 -3
  218. package/hooks/dist/gsd-path-export.js +95 -0
  219. package/hooks/dist/gsd-secret-read-guard.js +244 -53
  220. package/hooks/dist/gsd-statusline.js +35 -2
  221. package/hooks/dist/gsd-validate-commit.sh +27 -5
  222. package/hooks/dist/gsd-windsurf-pre-write.js +28 -1
  223. package/hooks/dist/gsd-workflow-guard.js +1 -1
  224. package/hooks/dist/gsd-worktree-path-guard.js +28 -1
  225. package/hooks/dist/lib/isolation-sentinel.js +44 -0
  226. package/hooks/dist/managed-hooks-registry.cjs +1 -0
  227. package/hooks/gsd-agent-isolation-guard.js +14 -2
  228. package/hooks/gsd-cursor-subagent-start.js +8 -3
  229. package/hooks/gsd-path-export.js +95 -0
  230. package/hooks/gsd-secret-read-guard.js +244 -53
  231. package/hooks/gsd-statusline.js +35 -2
  232. package/hooks/gsd-validate-commit.sh +27 -5
  233. package/hooks/gsd-windsurf-pre-write.js +28 -1
  234. package/hooks/gsd-workflow-guard.js +1 -1
  235. package/hooks/gsd-worktree-path-guard.js +28 -1
  236. package/hooks/hooks.json +1 -0
  237. package/hooks/lib/isolation-sentinel.js +44 -0
  238. package/hooks/managed-hooks-registry.cjs +1 -0
  239. package/package.json +13 -6
  240. package/pi/gsd.cjs +1047 -10
  241. package/pi/package-agents/gsd-advisor-researcher.md +113 -0
  242. package/pi/package-agents/gsd-ai-researcher.md +111 -0
  243. package/pi/package-agents/gsd-assumptions-analyzer.md +110 -0
  244. package/pi/package-agents/gsd-code-fixer.md +769 -0
  245. package/pi/package-agents/gsd-code-reviewer.md +402 -0
  246. package/pi/package-agents/gsd-codebase-mapper.md +850 -0
  247. package/pi/package-agents/gsd-debug-session-manager.md +416 -0
  248. package/pi/package-agents/gsd-debugger.md +1274 -0
  249. package/pi/package-agents/gsd-doc-classifier.md +269 -0
  250. package/pi/package-agents/gsd-doc-synthesizer.md +259 -0
  251. package/pi/package-agents/gsd-doc-verifier.md +212 -0
  252. package/pi/package-agents/gsd-doc-writer.md +626 -0
  253. package/pi/package-agents/gsd-dom-verifier.md +164 -0
  254. package/pi/package-agents/gsd-domain-researcher.md +144 -0
  255. package/pi/package-agents/gsd-eval-auditor.md +185 -0
  256. package/pi/package-agents/gsd-eval-planner.md +149 -0
  257. package/pi/package-agents/gsd-executor.md +894 -0
  258. package/pi/package-agents/gsd-framework-selector.md +159 -0
  259. package/pi/package-agents/gsd-integration-checker.md +476 -0
  260. package/pi/package-agents/gsd-intel-updater.md +338 -0
  261. package/pi/package-agents/gsd-mempalace-curator.md +49 -0
  262. package/pi/package-agents/gsd-nyquist-auditor.md +200 -0
  263. package/pi/package-agents/gsd-pattern-mapper.md +340 -0
  264. package/pi/package-agents/gsd-phase-researcher.md +897 -0
  265. package/pi/package-agents/gsd-plan-checker.md +1077 -0
  266. package/pi/package-agents/gsd-planner.md +1003 -0
  267. package/pi/package-agents/gsd-project-researcher.md +611 -0
  268. package/pi/package-agents/gsd-research-synthesizer.md +259 -0
  269. package/pi/package-agents/gsd-roadmapper.md +779 -0
  270. package/pi/package-agents/gsd-security-auditor.md +171 -0
  271. package/pi/package-agents/gsd-ui-auditor.md +581 -0
  272. package/pi/package-agents/gsd-ui-checker.md +417 -0
  273. package/pi/package-agents/gsd-ui-researcher.md +469 -0
  274. package/pi/package-agents/gsd-user-profiler.md +174 -0
  275. package/pi/package-agents/gsd-verifier.md +963 -0
  276. package/pi/package-agents/manifest.json +39 -0
  277. package/pi/package-skills/gsd-add-tests/SKILL.md +39 -0
  278. package/pi/package-skills/gsd-ai-integration-phase/SKILL.md +36 -0
  279. package/pi/package-skills/gsd-audit-fix/SKILL.md +34 -0
  280. package/pi/package-skills/gsd-audit-milestone/SKILL.md +40 -0
  281. package/pi/package-skills/gsd-audit-uat/SKILL.md +26 -0
  282. package/pi/package-skills/gsd-autonomous/SKILL.md +52 -0
  283. package/pi/package-skills/gsd-capture/SKILL.md +68 -0
  284. package/pi/package-skills/gsd-cleanup/SKILL.md +25 -0
  285. package/pi/package-skills/gsd-code-review/SKILL.md +71 -0
  286. package/pi/package-skills/gsd-complete-milestone/SKILL.md +147 -0
  287. package/pi/package-skills/gsd-config/SKILL.md +60 -0
  288. package/pi/package-skills/gsd-debug/SKILL.md +64 -0
  289. package/pi/package-skills/gsd-discuss-phase/SKILL.md +74 -0
  290. package/pi/package-skills/gsd-docs-update/SKILL.md +50 -0
  291. package/pi/package-skills/gsd-eval-review/SKILL.md +35 -0
  292. package/pi/package-skills/gsd-execute-phase/SKILL.md +62 -0
  293. package/pi/package-skills/gsd-explore/SKILL.md +26 -0
  294. package/pi/package-skills/gsd-extract-learnings/SKILL.md +25 -0
  295. package/pi/package-skills/gsd-fast/SKILL.md +33 -0
  296. package/pi/package-skills/gsd-forensics/SKILL.md +59 -0
  297. package/pi/package-skills/gsd-graphify/SKILL.md +210 -0
  298. package/pi/package-skills/gsd-health/SKILL.md +36 -0
  299. package/pi/package-skills/gsd-help/SKILL.md +37 -0
  300. package/pi/package-skills/gsd-import/SKILL.md +46 -0
  301. package/pi/package-skills/gsd-inbox/SKILL.md +42 -0
  302. package/pi/package-skills/gsd-ingest-docs/SKILL.md +43 -0
  303. package/pi/package-skills/gsd-manager/SKILL.md +46 -0
  304. package/pi/package-skills/gsd-map-codebase/SKILL.md +85 -0
  305. package/pi/package-skills/gsd-mempalace-capture/SKILL.md +111 -0
  306. package/pi/package-skills/gsd-mempalace-recall/SKILL.md +111 -0
  307. package/pi/package-skills/gsd-milestone-summary/SKILL.md +54 -0
  308. package/pi/package-skills/gsd-mvp-phase/SKILL.md +45 -0
  309. package/pi/package-skills/gsd-new-milestone/SKILL.md +49 -0
  310. package/pi/package-skills/gsd-new-project/SKILL.md +49 -0
  311. package/pi/package-skills/gsd-next/SKILL.md +34 -0
  312. package/pi/package-skills/gsd-ns-context/SKILL.md +31 -0
  313. package/pi/package-skills/gsd-ns-ideate/SKILL.md +30 -0
  314. package/pi/package-skills/gsd-ns-manage/SKILL.md +42 -0
  315. package/pi/package-skills/gsd-ns-project/SKILL.md +34 -0
  316. package/pi/package-skills/gsd-ns-review/SKILL.md +35 -0
  317. package/pi/package-skills/gsd-ns-workflow/SKILL.md +44 -0
  318. package/pi/package-skills/gsd-onboard/SKILL.md +46 -0
  319. package/pi/package-skills/gsd-pause-work/SKILL.md +48 -0
  320. package/pi/package-skills/gsd-phase/SKILL.md +61 -0
  321. package/pi/package-skills/gsd-plan-phase/SKILL.md +62 -0
  322. package/pi/package-skills/gsd-plan-review-convergence/SKILL.md +63 -0
  323. package/pi/package-skills/gsd-pr-branch/SKILL.md +31 -0
  324. package/pi/package-skills/gsd-profile-user/SKILL.md +48 -0
  325. package/pi/package-skills/gsd-progress/SKILL.md +51 -0
  326. package/pi/package-skills/gsd-quick/SKILL.md +179 -0
  327. package/pi/package-skills/gsd-quick-batch/SKILL.md +114 -0
  328. package/pi/package-skills/gsd-resume-work/SKILL.md +31 -0
  329. package/pi/package-skills/gsd-review/SKILL.md +50 -0
  330. package/pi/package-skills/gsd-review-backlog/SKILL.md +65 -0
  331. package/pi/package-skills/gsd-secure-phase/SKILL.md +37 -0
  332. package/pi/package-skills/gsd-settings/SKILL.md +30 -0
  333. package/pi/package-skills/gsd-ship/SKILL.md +27 -0
  334. package/pi/package-skills/gsd-sketch/SKILL.md +56 -0
  335. package/pi/package-skills/gsd-spec-phase/SKILL.md +63 -0
  336. package/pi/package-skills/gsd-spike/SKILL.md +53 -0
  337. package/pi/package-skills/gsd-stats/SKILL.md +22 -0
  338. package/pi/package-skills/gsd-surface/SKILL.md +177 -0
  339. package/pi/package-skills/gsd-thread/SKILL.md +29 -0
  340. package/pi/package-skills/gsd-ui-phase/SKILL.md +35 -0
  341. package/pi/package-skills/gsd-ui-review/SKILL.md +35 -0
  342. package/pi/package-skills/gsd-ultraplan-phase/SKILL.md +38 -0
  343. package/pi/package-skills/gsd-undo/SKILL.md +38 -0
  344. package/pi/package-skills/gsd-update/SKILL.md +51 -0
  345. package/pi/package-skills/gsd-validate-phase/SKILL.md +37 -0
  346. package/pi/package-skills/gsd-verify-work/SKILL.md +41 -0
  347. package/pi/package-skills/gsd-workspace/SKILL.md +57 -0
  348. package/pi/package-skills/gsd-workstreams/SKILL.md +73 -0
  349. package/scripts/audit-workflow-script-paths.cjs +1 -1
  350. package/scripts/build-hooks.js +4 -0
  351. package/scripts/command-contract-helpers.cjs +1 -1
  352. package/scripts/diagnose-windows-spawn-watcher.cjs +317 -0
  353. package/scripts/docs-guard-registry.cjs +22 -7
  354. package/scripts/gen-health-docs.cjs +13 -1
  355. package/scripts/gen-pi-package-skills.cjs +217 -0
  356. package/scripts/lib/macos-conformance-tier.generated.cjs +19 -0
  357. package/scripts/lib/platform-conformance-tier.generated.cjs +41 -0
  358. package/scripts/lint-docs-guard-registration.exempt-baseline.cjs +3 -3
  359. package/scripts/lint-phase-arg-assignment.cjs +2 -2
  360. package/scripts/lint-retired-runtime-name.cjs +5 -0
  361. package/scripts/lint-workflow-shellcheck-baseline.json +16 -16
  362. package/scripts/npm-audit-baseline.cjs +41 -7
  363. package/scripts/release-tarball-smoke.cjs +1 -0
  364. package/scripts/reviewed-or-maintainer-policy.cjs +181 -0
  365. package/scripts/select-docs-guards.cjs +1 -1
  366. package/scripts/self-test-report-policy.cjs +350 -0
  367. package/skills/gsd-code-review/SKILL.md +8 -0
  368. package/skills/gsd-pause-work/SKILL.md +1 -1
  369. package/vscode/package.json +1 -1
@@ -10,6 +10,7 @@ exports.RedEvidenceReason = {
10
10
  TargetFailed: 'target_test_failed', Green: 'unexpected_green', Empty: 'zero_tests_discovered',
11
11
  NoFailure: 'nonzero_exit_without_test_failure', LoadFailure: 'fixture_or_load_failure',
12
12
  NoTarget: 'no_target_test_failure', Invalid: 'invalid_record', Unreadable: 'unreadable_record',
13
+ BuildFailure: 'build_failure',
13
14
  };
14
15
  /** Basename of a path-like string ('' for non-strings) — separators `/` and `\`. */
15
16
  function baseOf(p) {
@@ -59,6 +60,10 @@ function classifyRedEvidence(input) {
59
60
  };
60
61
  if (exitCode === 0)
61
62
  return { verdict: exports.RedEvidenceVerdict.Invalid, reason: exports.RedEvidenceReason.Green, evidence };
63
+ // A package that did not compile or load asserted nothing: refuse it as its own reason, ahead of
64
+ // the generic unusable-report refusal, because the fix is different (make the test compile).
65
+ if (report.buildFailed)
66
+ return { verdict: exports.RedEvidenceVerdict.Invalid, reason: exports.RedEvidenceReason.BuildFailure, evidence };
62
67
  if (!report.valid)
63
68
  return { verdict: exports.RedEvidenceVerdict.Invalid, reason: exports.RedEvidenceReason.Invalid, evidence };
64
69
  if (report.tests.length === 0)
@@ -0,0 +1,42 @@
1
+ "use strict";
2
+ /**
3
+ * The single definition of "a test file" for the TDD gate (#5106).
4
+ *
5
+ * Two places decide what a test file is: the behavior-adding check (`task-command-router.cts`) and
6
+ * the RED-commit pathspec in `workflows/execute-phase.md`. They were separate lists, so the pathspec
7
+ * learned the Go / Elixir / RSpec / pytest names in #4715 while the router never did, and a task
8
+ * listing only `placement_test.go` was classified behavior-adding — the gate then asked for a RED
9
+ * commit for the task that IS the RED commit.
10
+ *
11
+ * `TEST_FILE_PATHSPECS` is the list in git-pathspec spelling, which is what the workflow passes to
12
+ * `evaluation-scope --pathspec`; `isTestFilePath` evaluates the same list for a task's `<files>`.
13
+ * A test pins the workflow's pathspec flags to this array, so the two cannot drift apart again.
14
+ */
15
+ Object.defineProperty(exports, "__esModule", { value: true });
16
+ exports.TEST_FILE_PATHSPECS = void 0;
17
+ exports.isTestFilePath = isTestFilePath;
18
+ /** Git-pathspec spelling. A trailing `/` is a directory, anything else a basename glob. */
19
+ exports.TEST_FILE_PATHSPECS = [
20
+ '*.test.*', '*.spec.*', 'tests/', '__tests__/',
21
+ '*_test.go', 'test_*.py', '*_test.py', '*_test.exs', '*_spec.rb', '*_test.rb',
22
+ ];
23
+ /** Directory names a `<files>` entry may sit under. `test/` is the singular spelling many projects use. */
24
+ const TEST_DIRECTORY_NAMES = new Set([
25
+ ...exports.TEST_FILE_PATHSPECS.filter((spec) => spec.endsWith('/')).map((spec) => spec.slice(0, -1)),
26
+ 'test',
27
+ ]);
28
+ function escapeRegex(text) {
29
+ return text.replace(/[.+?^${}()|[\]\\]/g, '\\$&');
30
+ }
31
+ /** A basename glob where `*` is any run of non-separator characters. */
32
+ const FILE_GLOBS = exports.TEST_FILE_PATHSPECS
33
+ .filter((spec) => !spec.endsWith('/'))
34
+ .map((spec) => new RegExp(`^${spec.split('*').map(escapeRegex).join('[^/]*')}$`, 'i'));
35
+ /** True when `file` (either separator) is a test file by the shared convention list. */
36
+ function isTestFilePath(file) {
37
+ const segments = file.split(/[\\/]/).filter(Boolean);
38
+ const base = segments[segments.length - 1] ?? '';
39
+ if (FILE_GLOBS.some((glob) => glob.test(base)))
40
+ return true;
41
+ return segments.slice(0, -1).some((segment) => TEST_DIRECTORY_NAMES.has(segment.toLowerCase()));
42
+ }
@@ -47,6 +47,8 @@ const coreUtilsMod = require("./core-utils.cjs");
47
47
  // eslint-disable-next-line @typescript-eslint/no-require-imports -- planning-scope.cjs is an export= CommonJS module
48
48
  const planningScopeMod = require("./planning-scope.cjs");
49
49
  const shell_command_projection_cjs_1 = require("./shell-command-projection.cjs");
50
+ const text_lines_cjs_1 = require("./text-lines.cjs");
51
+ const frontmatter_fence_cjs_1 = require("./frontmatter-fence.cjs");
50
52
  const runtime_slash_cjs_1 = require("./runtime-slash.cjs");
51
53
  const security_cjs_1 = require("./security.cjs");
52
54
  const pattern_cjs_1 = require("./pattern.cjs");
@@ -607,17 +609,19 @@ function phaseArtifactPaths(phaseDir, projectRoot) {
607
609
  * `.planning/<project>/workstreams/<ws>`), covering a
608
610
  * per-project/per-workstream store symlink (amendment 1,
609
611
  * revised R7).
612
+ * v4 (#5351) — v3 with each CRLF byte pair hashed as LF. Every other
613
+ * byte, including a lone CR and non-UTF-8 input, is preserved.
610
614
  *
611
615
  * A stored digest names its own version (`v<N>:sha256:…`), and
612
616
  * `readVerificationStatus` recomputes under the STORED version rather than
613
617
  * this constant — so bumping it does not flip every already-verified phase
614
- * to `stale` on upgrade. A legacy v1/v2 report keeps its own semantics
615
- * (shared documents included for v1; no report filter or artifact union for
616
- * either) until it is re-fingerprinted; only a version outside
618
+ * to `stale` on upgrade. A legacy v1/v2/v3 report keeps its own raw-byte
619
+ * semantics (shared documents included for v1; no report filter or artifact
620
+ * union for v1/v2) until it is re-fingerprinted; only a version outside
617
621
  * `KNOWN_FINGERPRINT_VERSIONS` is unrecomputable and fails closed.
618
622
  */
619
- const FINGERPRINT_VERSION = 3;
620
- const KNOWN_FINGERPRINT_VERSIONS = new Set([1, 2, 3]);
623
+ const FINGERPRINT_VERSION = 4;
624
+ const KNOWN_FINGERPRINT_VERSIONS = new Set([1, 2, 3, 4]);
621
625
  /**
622
626
  * #4623: the planning roots whose DIRECT children are repo-wide planning
623
627
  * documents, as project-root-relative posix paths. Always `.planning`; plus
@@ -815,7 +819,22 @@ function deriveCoveredDigest(projectRoot, coveredFiles, version = FINGERPRINT_VE
815
819
  catch {
816
820
  return { ok: true, digest: null, files: declared, mappedPhaseDir: artifactMappedPhaseDir };
817
821
  }
818
- const fileHash = node_crypto_1.default.createHash('sha256').update(bytes).digest('hex');
822
+ const hash = node_crypto_1.default.createHash('sha256');
823
+ if (version >= 4) {
824
+ // #5351: drop only the CR in each CRLF pair without decoding the
825
+ // covered input — binary files and lone CR bytes keep their meaning.
826
+ // Hash Buffer slices directly, retaining the LF at each new start.
827
+ let start = 0;
828
+ for (let crlf = bytes.indexOf('\r\n'); crlf !== -1; crlf = bytes.indexOf('\r\n', start)) {
829
+ hash.update(bytes.subarray(start, crlf));
830
+ start = crlf + 1;
831
+ }
832
+ hash.update(bytes.subarray(start));
833
+ }
834
+ else {
835
+ hash.update(bytes);
836
+ }
837
+ const fileHash = hash.digest('hex');
819
838
  parts.push(`${rel}\n${fileHash}\n`);
820
839
  hashed++;
821
840
  }
@@ -1175,6 +1194,72 @@ function findVerificationStatusError(phaseDirs, deps = {}) {
1175
1194
  }
1176
1195
  return null;
1177
1196
  }
1197
+ /**
1198
+ * #5291: legacy report statuses `validate health --repair` may rewrite, and the
1199
+ * closed-set status each becomes. Deliberately minimal — only the value the
1200
+ * maintainer ruled on (`pass_with_caveats` -> `passed`). Every other status
1201
+ * outside the closed set (`pending`, `partial`, …) has NO mapping: it stays
1202
+ * refused and its W030 stays advise-only (fail closed; only `passed` satisfies
1203
+ * the gate). The original value is preserved in `legacy_status`.
1204
+ */
1205
+ const LEGACY_STATUS_REPAIRS = Object.freeze({
1206
+ pass_with_caveats: VERIFICATION_STATUS.PASSED,
1207
+ });
1208
+ /** The closed-set status a known legacy `raw` status repairs to, or `null` when it has no mapping. */
1209
+ function legacyStatusRepairTarget(raw) {
1210
+ return typeof raw === 'string' && Object.prototype.hasOwnProperty.call(LEGACY_STATUS_REPAIRS, raw)
1211
+ ? LEGACY_STATUS_REPAIRS[raw]
1212
+ : null;
1213
+ }
1214
+ /**
1215
+ * #5291: rewrite one report's frontmatter `status:` from a known legacy value
1216
+ * to its closed-set equivalent and record the original as `legacy_status:`
1217
+ * right after it. Touches only those lines — every other byte (body, other
1218
+ * keys, line endings) is preserved — and never overwrites an existing
1219
+ * `legacy_status`. Refuses (writes nothing) for a path that is not a report
1220
+ * contained in its own phase directory, a report with no frontmatter `status`,
1221
+ * and any status without a mapping.
1222
+ */
1223
+ function repairLegacyVerificationStatus(filePath) {
1224
+ if (!isReportContained(node_path_1.default.dirname(filePath), filePath)) {
1225
+ return { ok: false, reason: 'the report is not contained in its phase directory' };
1226
+ }
1227
+ let content;
1228
+ try {
1229
+ content = node_fs_1.default.readFileSync(filePath, 'utf-8');
1230
+ }
1231
+ catch (err) {
1232
+ return { ok: false, reason: err instanceof Error ? err.message : String(err) };
1233
+ }
1234
+ // The fence owner (`locateFrontmatterFence`) is the one place that knows where
1235
+ // the frontmatter body starts and ends (BOM, CRLF, lenient closing fence).
1236
+ const fence = (0, frontmatter_fence_cjs_1.locateFrontmatterFence)(content);
1237
+ if (!fence || !fence.closed)
1238
+ return { ok: false, reason: 'the report has no closed frontmatter' };
1239
+ const lines = (0, text_lines_cjs_1.splitLines)(content.slice(fence.openEnd, fence.bodyEnd));
1240
+ // The YAML reader keeps the LAST of duplicate keys, so the line the repair
1241
+ // edits is the last `status:` line — the very value the refusal named. A
1242
+ // trailing comment (`status: pass_with_caveats # x`) is not part of the value.
1243
+ let statusAt = -1;
1244
+ lines.forEach((l, i) => { if (/^status:/.test(l))
1245
+ statusAt = i; });
1246
+ if (statusAt === -1)
1247
+ return { ok: false, reason: 'the report frontmatter has no status' };
1248
+ const raw = lines[statusAt]
1249
+ .replace(/^status:\s*/, '')
1250
+ .replace(/\s+#.*$/, '')
1251
+ .replace(/\s+$/, '')
1252
+ .replace(/^(["'])(.*)\1$/, '$2');
1253
+ const to = legacyStatusRepairTarget(raw);
1254
+ if (to === null)
1255
+ return { ok: false, reason: `${describeRawStatus(raw)} has no known repair` };
1256
+ const legacyStatusKept = lines.some((l) => /^legacy_status:/.test(l));
1257
+ lines[statusAt] = `status: ${to}`;
1258
+ if (!legacyStatusKept)
1259
+ lines.splice(statusAt + 1, 0, `legacy_status: ${raw}`);
1260
+ (0, shell_command_projection_cjs_1.platformWriteSync)(filePath, content.slice(0, fence.openEnd) + (0, text_lines_cjs_1.joinLines)(lines, fence.eol) + content.slice(fence.bodyEnd), { verbatim: true });
1261
+ return { ok: true, from: raw, to, legacyStatusKept };
1262
+ }
1178
1263
  /**
1179
1264
  * #3518: the shared phase-pinned artifact-selection core BOTH single-pick
1180
1265
  * resolvers (`resolveVerificationFile` for `*-VERIFICATION.md`,
@@ -1499,11 +1584,11 @@ function readVerificationStatus(phaseDir, opts = {}) {
1499
1584
  isStale = true;
1500
1585
  }
1501
1586
  else if (storedVersion >= 3) {
1502
- // #5095 (R1): v3's digest is computed over the declared set UNIONED
1587
+ // #5095 (R1): the v3+ digest is computed over the declared set UNIONED
1503
1588
  // with the phase's own live artifacts — a plan/summary added after
1504
1589
  // fingerprinting already moves the digest itself, so the separate
1505
1590
  // live-directory re-scan (`uncoveredCurrentArtifacts`) is redundant
1506
- // for v3 and is not run.
1591
+ // for v3+ and is not run.
1507
1592
  isStale =
1508
1593
  computeCoveredDigest(projectRoot, coveredFilesVal, storedVersion, { phaseDir }) !== coveredDigestVal;
1509
1594
  if (isStale) {
@@ -1784,7 +1869,7 @@ function parseFingerprintFileArgs(tokens) {
1784
1869
  * directly assignable. #5095 (ADR-5057 Phase 2): `raw`
1785
1870
  * returning only the digest is safe even though the
1786
1871
  * emitted `covered_files` is a SUPERSET of the caller's
1787
- * declared list — the v3 digest is computed over
1872
+ * declared list — the v3+ digest is computed over
1788
1873
  * `canonicalize(declared ∪ phaseArtifactPaths)` on BOTH
1789
1874
  * the emit side (here) and the check side
1790
1875
  * (`readVerificationStatus`), so a `--raw` caller that
@@ -2134,6 +2219,8 @@ const verificationModule = {
2134
2219
  isReportContained,
2135
2220
  routeResult,
2136
2221
  findVerificationStatusError,
2222
+ legacyStatusRepairTarget,
2223
+ repairLegacyVerificationStatus,
2137
2224
  defaultPhaseCleanCommitTimesMs,
2138
2225
  resolvePhaseArtifactFile,
2139
2226
  resolveVerificationFile,
@@ -1147,6 +1147,38 @@ function unreadableFileVerdict(evidence, displayPath) {
1147
1147
  : { error: 'File unreadable', path: displayPath, read_error: evidence.reason };
1148
1148
  return (0, gate_verdict_cjs_1.gateUnreadable)(false, payload);
1149
1149
  }
1150
+ /**
1151
+ * Issue #4927 — a PLAN.md can be cut off by a tool that still reported the write
1152
+ * as successful (runtimes cap tool-call output; a ~450-line plan stopped inside
1153
+ * an XML tag). The planner and the plan checker both run `verify plan-structure`
1154
+ * on every plan, so this is where a silent partial write is caught, independent
1155
+ * of what the write call returned.
1156
+ *
1157
+ * Three signals, each a strong sign of a partial file and none of them a shape a
1158
+ * finished plan carries:
1159
+ * - the incremental-write sentinel (`<!-- gsd:write-continue -->`) left behind
1160
+ * (the planner's large-file fallback never removed it);
1161
+ * - `<tasks>` opened and never closed;
1162
+ * - the file ends inside a tag (`...</ta`). Prose such as `a < b` does not
1163
+ * match: a tag needs a name character straight after the `<` or `</`.
1164
+ */
1165
+ function scanPlanTruncation(content) {
1166
+ const errors = [];
1167
+ const text = content || '';
1168
+ if (/<!--\s*gsd:write-continue\s*-->/.test(text)) {
1169
+ errors.push('Plan appears truncated: the incremental-write sentinel `<!-- gsd:write-continue -->` is still present — '
1170
+ + 'the last section was never written. Finish the write (or re-write the plan) before treating it as complete.');
1171
+ }
1172
+ if (/<tasks>/.test(text) && !/<\/tasks>/.test(text)) {
1173
+ errors.push('Plan appears truncated: `<tasks>` is opened but `</tasks>` never appears — the file was cut off before the task list ended.');
1174
+ }
1175
+ const trimmed = text.replace(/\s+$/, '');
1176
+ const lastLt = trimmed.lastIndexOf('<');
1177
+ if (lastLt !== -1 && trimmed.indexOf('>', lastLt) === -1 && /^<\/?[A-Za-z_!][^<>]{0,200}$/.test(trimmed.slice(lastLt))) {
1178
+ errors.push(`Plan appears truncated: the file ends inside a tag (\`${trimmed.slice(lastLt).slice(0, 40)}\`) — the write stopped mid-payload.`);
1179
+ }
1180
+ return errors;
1181
+ }
1150
1182
  function cmdVerifyPlanStructure(cwd, filePath, raw) {
1151
1183
  if (!filePath) {
1152
1184
  error('file path required');
@@ -1172,6 +1204,8 @@ function cmdVerifyPlanStructure(cwd, filePath, raw) {
1172
1204
  const fm = extractFrontmatter(content, fullPath);
1173
1205
  const errors = [];
1174
1206
  const warnings = [];
1207
+ // #4927: a plan the write tool reported as written but that ends mid-payload.
1208
+ errors.push(...scanPlanTruncation(content));
1175
1209
  const required = ['phase', 'plan', 'type', 'wave', 'depends_on', 'files_modified', 'autonomous', 'must_haves'];
1176
1210
  for (const field of required) {
1177
1211
  if (fm[field] === undefined)
@@ -1220,7 +1254,8 @@ function cmdVerifyPlanStructure(cwd, filePath, raw) {
1220
1254
  continue;
1221
1255
  if (!decisionCheckpointOffsets.some((offset) => offset < at)) {
1222
1256
  warnings.push('Task rated <reversibility rating="one-way"> has no preceding checkpoint:decision — '
1223
- + 'a one-way door must be confirmed before the agent walks through it');
1257
+ + 'a one-way door must be confirmed before the agent walks through it (unless the decision is already settled '
1258
+ + 'in CONTEXT.md, an ingested ADR, or an answered checkpoint)');
1224
1259
  }
1225
1260
  }
1226
1261
  const echoScan = scanNegativeGrepCommentEcho(content);
@@ -1774,6 +1809,8 @@ function repairFixText(slash, action) {
1774
1809
  return `Run ${slash('health')} --repair to add key`;
1775
1810
  case REMEDY_ACTION.BACKFILL_MILESTONES:
1776
1811
  return `Run ${slash('health')} --backfill to synthesize missing entries from archive snapshots`;
1812
+ case REMEDY_ACTION.REPAIR_VERIFICATION_STATUS:
1813
+ return `Run ${slash('health')} --repair to rewrite the legacy status (original kept as legacy_status)`;
1777
1814
  default:
1778
1815
  return '';
1779
1816
  }
@@ -176,7 +176,28 @@ function resolveWorktreeContext(cwd, deps = {}) {
176
176
  reason: 'has_local_planning',
177
177
  };
178
178
  }
179
- return resolveWorktreeLinkage(cwd, deps);
179
+ const linkage = resolveWorktreeLinkage(cwd, deps);
180
+ if (linkage.reason !== 'linked_worktree')
181
+ return linkage;
182
+ // #4885: from a SUBDIRECTORY of a LINKED worktree the cwd has no .planning of its own, but the
183
+ // worktree's own top-level may. That checkout is the project the caller means; remapping to the
184
+ // main checkout would make `commit` and every planning write land in the wrong repository. A linked
185
+ // worktree with no .planning anywhere still remaps to the main checkout, and the main checkout's own
186
+ // subdirectories are untouched.
187
+ const execGit = deps.execGit || execGitDefault;
188
+ const top = execGit(['rev-parse', '--show-toplevel'], { cwd });
189
+ const topText = String(top.stdout || '').trim();
190
+ if (!top.timedOut && top.exitCode === 0 && topText !== '') {
191
+ const topLevel = node_path_1.default.resolve(cwd, topText);
192
+ if (existsSync(node_path_1.default.join(topLevel, '.planning'))) {
193
+ return {
194
+ effectiveRoot: topLevel,
195
+ mode: 'current_directory',
196
+ reason: 'has_local_planning',
197
+ };
198
+ }
199
+ }
200
+ return linkage;
180
201
  }
181
202
  function planWorktreePrune(repoRoot, options = {}, deps = {}) {
182
203
  const parsePorcelain = deps.parseWorktreePorcelain || parseWorktreePorcelain;
@@ -22,6 +22,7 @@
22
22
  "git": {
23
23
  "branching_strategy": "none",
24
24
  "create_tag": true,
25
+ "repo_tagging": "single",
25
26
  "base_branch": null,
26
27
  "phase_branch_template": "gsd/phase-{phase}-{slug}",
27
28
  "milestone_branch_template": "gsd/{milestone}-{slug}",
@@ -51,6 +51,7 @@
51
51
  "git.protected_branches",
52
52
  "git.allow_default_branch_commits",
53
53
  "git.create_tag",
54
+ "git.repo_tagging",
54
55
  "git.phase_branch_template",
55
56
  "git.milestone_branch_template",
56
57
  "git.quick_branch_template",
@@ -33,7 +33,7 @@ This doc describes what IS, not what should be. Casing inconsistencies are docum
33
33
  | gsd-code-reviewer | Source-code review | No marker (writes REVIEW.md) | `gsd-core/workflows/code-review.md` reads REVIEW.md | artifact+query |
34
34
  | gsd-assumptions-analyzer | Assumption extraction | No marker (returns `## Assumptions` sections) | `gsd-core/workflows/discuss-phase-assumptions.md` reads the inline `## Assumptions` sections from the agent's return | structured-return |
35
35
  | gsd-doc-classifier | Planning-doc classification | No marker (writes `.planning/intel/classifications/*.json`) | `gsd-core/workflows/ingest-docs.md` reads the classification JSON | artifact+query |
36
- | gsd-doc-verifier | Doc validation | No marker (writes JSON to `.planning/tmp/`) | `.planning/tmp/verify-{doc_filename}.json` artifact, read by `gsd-core/workflows/docs-update.md` | artifact+query |
36
+ | gsd-doc-verifier | Doc validation | No marker (writes JSON to `.planning/tmp/`) | `{project_root}/.planning/tmp/verify-{doc_key}.json` artifact (the orchestrator-assigned `result_path`, keyed by the doc's full relative path), read by `gsd-core/workflows/docs-update.md` | artifact+query |
37
37
  | gsd-doc-writer | Doc generation | No marker (writes docs directly) | generated doc files, consumed by `gsd-core/workflows/docs-update.md` and `gsd-core/workflows/docs-update/steps/dispatch-monorepo-packages.md` | artifact+query |
38
38
  | gsd-domain-researcher | Domain research | No marker (writes the AI-SPEC.md domain section via Edit) | `gsd-core/workflows/ai-integration-phase.md` reads the AI-SPEC.md section after the agent returns | artifact+query |
39
39
  | gsd-eval-auditor | Evaluation coverage audit | No marker (writes the REVIEW.md audit section) | `gsd-core/workflows/eval-review.md` reads REVIEW.md | artifact+query |
@@ -22,22 +22,41 @@ In your mandatory init step — right after `mandatory-initial-read.md` / the
22
22
  injection also runs (e.g. Claude Code). The guard is what keeps the two seams from
23
23
  doubling the block.
24
24
 
25
- 2. **Query your configured skills.** Use **your own agent type** — the `name:` value in
26
- your frontmatter (e.g. an agent whose frontmatter says `name: gsd-executor` queries
27
- `gsd-executor`). The query is read-only and idempotent — it exits 0 with an empty
28
- block when nothing is configured for your type:
25
+ 2. **Query your configured skills.** Use **your own agent type**: the one your agent
26
+ file states on its `agent_skills:` pointer line, as `(agent type: \`gsd-executor\`)`.
27
+ Do not read it from your frontmatter `name:` — Claude Code shows a subagent only the
28
+ body, the Kimi converter drops frontmatter, and Codex keeps it in a TOML key. Run the
29
+ query with `--skills-only`, in the **same bash call** as the launcher so `gsd_run` is
30
+ defined when it runs. Substitute your type for `<your-agent-type>`:
29
31
 
30
32
  ```bash
31
- _AGENT_SKILLS=$(gsd_run query agent-skills <YOUR-FRONTMATTER-NAME> 2>/dev/null || true)
33
+ # 1. Launcher: paste the bash block from gsd-run-resolver.md here unless gsd_run
34
+ # is already defined in this shell (@~/.claude/gsd-core/references/gsd-run-resolver.md).
35
+ # 2. Query:
36
+ _AGENT_SKILLS=$(gsd_run query agent-skills <your-agent-type> --skills-only)
32
37
  ```
33
38
 
34
- The runtime `gsd_run` resolver is the standard one from
35
- `_runtime-launcher.snippet.sh`; your own init already defines it.
39
+ Not every agent file defines `gsd_run`, so supply it as above rather than assuming
40
+ your init did. The query is read-only and idempotent. Its stderr and exit status stay
41
+ visible, so a missing launcher or a failed query is never mistaken for "nothing
42
+ configured".
36
43
 
37
44
  3. **Read every listed skill.** The block emits entries as `@<path>/SKILL.md`
38
45
  includes — `Read` each one before starting work. If the block is empty, there is
39
46
  nothing to do (zero overhead).
40
47
 
48
+ ## What the query returns, per runtime
49
+
50
+ | Call | Claude | Non-Claude (Codex, Kimi, OpenCode, Kilo, …) |
51
+ |---|---|---|
52
+ | `query agent-skills <type> --skills-only` (the self-load) | configured skills, or an empty block | configured skills, or an empty block |
53
+ | `query agent-skills <type>` (orchestrator-side `${AGENT_SKILLS_*}` injection) | configured skills, or an empty block | configured skills, or — when nothing is configured and the agent is installed — the agent's own **persona** (#2454, #4407) |
54
+
55
+ On a non-Claude runtime the plain query can answer with the agent's whole persona
56
+ (about 47 KB for the executor). `--skills-only` is what keeps the self-load from
57
+ reading your own persona back into your context as if it were a skills block. Never
58
+ run the query without it from inside an agent.
59
+
41
60
  ## What self-load does and does not cover
42
61
 
43
62
  | Skill form | Self-loads? | Notes |
@@ -144,10 +144,10 @@ gsd_run query commit "docs({phase}-{plan}): complete [plan-name] plan" --files .
144
144
  </format>
145
145
 
146
146
  <format name="handoff">
147
- ## Handoff (WIP)
147
+ ## Handoff (Pause)
148
148
 
149
149
  ```
150
- wip: [phase-name] paused at task [X]/[Y]
150
+ docs(pause): [phase-name] paused at task [X]/[Y]
151
151
 
152
152
  Current: [task name]
153
153
  [If blocked:] Blocked: [reason]
@@ -156,7 +156,7 @@ Current: [task name]
156
156
  What to commit:
157
157
 
158
158
  ```bash
159
- gsd_run query commit "wip: [phase-name] paused at task [X]/[Y]" --files .planning/
159
+ gsd_run query commit "docs(pause): [phase-name] paused at task [X]/[Y]" --files .planning/
160
160
  ```
161
161
 
162
162
  </format>
@@ -295,4 +295,15 @@ This stages `src/api/users.ts` and `src/types/user.ts` in the `backend/` repo, a
295
295
 
296
296
  Files that don't match any configured sub-repo are reported as unmatched.
297
297
 
298
+ ### Milestone Tagging Across Sub-Repos
299
+
300
+ A GSD milestone version names planned work in `.planning/`; it is not any repository's release version. With `git.repo_tagging: "per_repo"` and a non-empty `sub_repos`, `/gsd:complete-milestone` records one release map entry per repository (status, release, reference, environment, tag choice) instead of creating one `v[X.Y]` tag in the current directory:
301
+
302
+ 1. `gsd_run query milestone release-tags --map <tags.json> --dry-run` checks every requested tag (repository listed in `sub_repos`, ref resolves, tag name valid, no tag at a different commit) before anything is written.
303
+ 2. After one consolidated confirmation, `gsd_run query milestone release-tags --map <tags.json>` creates the annotated tags, each with the target repository as its working directory. Re-running the same map is idempotent (`already_tagged`).
304
+ 3. `milestone complete <version> --release-map <file>` archives the map by recorded value.
305
+ 4. Nothing is pushed by default. Each repository's tag has its own confirmation naming the repository and its remote.
306
+
307
+ With `git.create_tag: false` the map is still recorded and no tag is created. See [How to tag a multi-repository milestone](../../docs/how-to/tag-a-multi-repo-milestone.md).
308
+
298
309
  </sub_repos_support>
@@ -91,12 +91,19 @@ not one shape of one.
91
91
  capability manifest, which may be third-party — and `gates[].check` is **not** one of the
92
92
  executable surfaces the install consent prompt discloses (`hooks`, command modules,
93
93
  `mcpServers`, reviewer lanes), so a capability can be consented to as declarative-only and
94
- still reach a shell through a gate. Check the query value you read from `activeHooks` against
95
- `^[a-z][a-z0-9-]*( [a-z][a-z0-9-]*)*$` yourself, IN-CONTEXT — **never** by pasting it into a
96
- shell command to be tested there, because a value carrying a quote, `;`, a `` ` ``, `$(`, or a
97
- newline would terminate the assignment and run as its own statement before any shell-side
98
- check could execute. A value that fails is a malformed manifest: record a warning, route it
99
- per the gate's `onError`, and do not run it. Pass `check.predicate` as a **single argv
94
+ still reach a shell through a gate. Normalize first, then validate: the registry declares query
95
+ ids in dotted form (`ui.plan-gate`, `verify.codebase-drift`) and `gsd_run check` accepts them by
96
+ turning each `.` into `-`, so replace every `.` with `-` in the query value you read from
97
+ `activeHooks`, then check that normalized value against
98
+ `^[a-z][a-z0-9]*(-[a-z0-9]+)*( [a-z][a-z0-9]*(-[a-z0-9]+)*)*$` yourself, IN-CONTEXT — **never** by
99
+ pasting it into a shell command to be tested there, because a value carrying a quote, `;`, a
100
+ `` ` ``, `$(`, or a newline would terminate the assignment and run as its own statement before
101
+ any shell-side check could execute. The normalized value is the one value you validate and the
102
+ one value you pass: wherever a workflow shows `${hook.check.query}`, write the normalized value.
103
+ A leading dot, a trailing dot or consecutive dots become a leading, trailing or doubled hyphen,
104
+ which the pattern rejects. A value that fails — or that passes but names no known check, which
105
+ `gsd_run check` rejects as an unknown check subcommand — is a malformed manifest: record a
106
+ warning, route it per the gate's `onError`, and do not run it. Pass `check.predicate` as a **single argv
100
107
  element** for the same reason — never re-quote it into a shell string, where an apostrophe
101
108
  would close the literal. This is the identical requirement `step` → `ref.command` carries
102
109
  above; it was stated there and omitted here, which is the gap #3559 closed.
@@ -20,7 +20,7 @@ this turns out wrong three phases from now, what does undoing it cost?*
20
20
  |---|---|---|
21
21
  | `reversible` | Local and cheap — one file, one function, an implementation swapped behind a stable interface. | `reversible` decisions get no checkpoint and no flag; the task proceeds normally. |
22
22
  | `costly` | Undo touches many call sites or needs a coordinated change — a shared interface shape, a cross-module contract, a dependency major bump. | `costly` decisions are flagged in the plan so the reader sees the weight, but this does not block execution. |
23
- | `one-way` | Undo requires a data migration, breaks a published contract, or cannot be done at all — on-disk/wire format, public API shape, external-service lock-in, a schema other systems already read. | The planner inserts a `checkpoint:decision` **before** the dependent task, so the human confirms the door before the agent walks through it. |
23
+ | `one-way` | Undo requires a data migration, breaks a published contract, or cannot be done at all — on-disk/wire format, public API shape, external-service lock-in, a schema other systems already read. | The planner inserts a `checkpoint:decision` **before** the dependent task, so the human confirms the door before the agent walks through it — unless the human already settled that decision (see *Already settled*). |
24
24
 
25
25
  **When unsure, rate it `reversible`.** The value of this feature is
26
26
  *discrimination*. A planner that rates everything `one-way` produces checkpoint
@@ -101,6 +101,21 @@ the same terms as any other ingested text (ADR-1577,
101
101
  carrying something that belongs in `<context>`, and long free text is where
102
102
  smuggled structure hides.
103
103
 
104
+ ## Already settled
105
+
106
+ A `one-way` decision the human has **already settled** keeps its rating for the
107
+ record and gets **no** new `checkpoint:decision`. It is settled when any of
108
+ these records the human choosing this exact option:
109
+
110
+ - the phase `CONTEXT.md` `<decisions>` block (discuss-phase output);
111
+ - an ADR ingested with `--ingest`;
112
+ - a `checkpoint:decision` earlier in this phase's plans that was already answered.
113
+
114
+ Only a **newly surfaced** `one-way` decision — one nothing above records — earns a
115
+ fresh checkpoint before the implementing task. If you cannot point at the record,
116
+ treat the decision as newly surfaced. `gsd-planner` and this reference state the
117
+ same rule; if they ever diverge, this file wins.
118
+
104
119
  ## Anti-patterns
105
120
 
106
121
  - **Everything is `one-way`.** The most common failure. Re-read the undo cost:
@@ -111,9 +126,8 @@ the same terms as any other ingested text (ADR-1577,
111
126
  - **A rationale that restates the rating.** "This is irreversible because it
112
127
  cannot be undone" tells the reader nothing. Name the migration, the contract,
113
128
  or the dependent system.
114
- - **Gating a decision already made.** If the phase CONTEXT.md records the human
115
- choosing this exact option, the door is already walked through. Keep the
116
- rating for the record; do not insert a checkpoint to re-ask.
129
+ - **Gating a decision already made.** See *Already settled* below: a decision the
130
+ human has already settled does not earn a fresh checkpoint.
117
131
  - **Using the gate as a substitute for design.** The checkpoint buys deliberation
118
132
  on a door you must walk through. The better move, when available, is to *make
119
133
  the decision reversible* — put the format behind a writer seam, version the
@@ -16,6 +16,24 @@ preferred over `cd <dir> && npm run <script>` — it does not depend on the exec
16
16
  `prior_verify_commands` is empty and you cannot ground a path, say so in the plan instead of
17
17
  guessing one.
18
18
 
19
+ **Run a count/match gate before it ships (#4927).** A gate you author — or one a `RESEARCH.md`
20
+ hands you — that counts or matches text (`grep -c`, `grep -q`, `rg`, `test -n "$(grep …)"`) is a
21
+ claim about the tree, and two defects are invisible to reading and take one command to expose. You
22
+ have a shell when you author the plan, so run each such gate against the **current tree** and write
23
+ the pre-change result next to it as its baseline (`# baseline at HEAD: 0`):
24
+
25
+ - **It already passes at HEAD.** `grep -c "Agent" .planning/PROJECT.md` returned `6` before any work
26
+ existed, because the word sat in an unrelated row — the gate measures nothing and passes whether
27
+ or not the work happens. Fix the gate: anchor it to the new heading, key, or line the task
28
+ creates, or assert a delta from the recorded baseline.
29
+ - **Its literal can never match the deliverable.** `grep` is case-sensitive, so `grep -c "quickstart"`
30
+ fails on a correct `## Quickstart`. Match the real casing of the text the task writes, or use
31
+ `-i` when casing is not part of the contract.
32
+
33
+ A gate that is meant to prove a *negative* is checked the other way: it must pass after the work
34
+ and its baseline must show the thing was there to remove. Only read-only commands are safe to run
35
+ this way — run `grep`/`rg`/`test` gates; do not execute a gate that builds, installs, or writes.
36
+
19
37
  **Root-relative, never absolute (#4767).** The paths in your prompt's `<required_reading>` are
20
38
  absolute on purpose — a subagent's cwd may differ from the orchestrator's (#2376). That rule
21
39
  covers prompt *inputs* only. Every path in the plan *body* — `<files>`, `<verify>`, `<automated>`,
@@ -35,6 +35,7 @@ Configuration options for `.planning/` directory behavior.
35
35
  | `git.base_branch` | `null` (auto-detect) | Target branch for PRs and merges (e.g. `"master"`, `"develop"`). When `null`, auto-detects from `git symbolic-ref refs/remotes/origin/HEAD`, falling back to `"main"`. |
36
36
  | `git.protected_branches` | (none) | Optional array of non-empty strings naming additional shared branches that should trigger protected-branch warnings |
37
37
  | `git.create_tag` | `true` | Create git tags on milestone completion |
38
+ | `git.repo_tagging` | `"single"` | Milestone tagging mode: `"single"` (existing behavior) or `"per_repo"` (record one release per configured repository; each tag is pushed only after a per-repository confirmation) |
38
39
  | `git.phase_branch_template` | `"gsd/phase-{phase}-{slug}"` | Branch template for phase strategy |
39
40
  | `git.milestone_branch_template` | `"gsd/{milestone}-{slug}"` | Branch template for milestone strategy |
40
41
  | `git.quick_branch_template` | `null` | Optional branch template for quick-task runs |
@@ -340,6 +341,7 @@ Set via `git.*` namespace (e.g., `"git": { "branching_strategy": "phase" }`).
340
341
  | `git.base_branch` | string\|null | `null` (auto-detect) | Any branch name | Target branch for PRs and merges; auto-detects from `origin/HEAD` when `null` |
341
342
  | `git.protected_branches` | array of non-empty strings | (none) | Non-empty branch names | Optional protected names added to the resolved base branch for execute-phase and ship warnings |
342
343
  | `git.create_tag` | boolean | `true` | `true`, `false` | Create git tags on milestone completion |
344
+ | `git.repo_tagging` | string | `"single"` | `"single"`, `"per_repo"` | Milestone tagging mode; `"per_repo"` opts a multi-repo project into per-repository release records |
343
345
  | `git.phase_branch_template` | string | `"gsd/phase-{phase}-{slug}"` | Template with `{phase}`, `{slug}` | Branch naming template for `phase` strategy |
344
346
  | `git.milestone_branch_template` | string | `"gsd/{milestone}-{slug}"` | Template with `{milestone}`, `{slug}` | Branch naming template for `milestone` strategy |
345
347
  | `git.quick_branch_template` | string\|null | `null` | Template with `{slug}` | Optional branch template for quick-task runs |
@@ -96,11 +96,12 @@ After completion, create SUMMARY.md with:
96
96
  2. Write test describing expected behavior (from `<behavior>` element)
97
97
  3. Run test - it MUST fail **intentionally** (#3770): the TARGET test you named must be the test that fails, on an assertion for the planned behavior. A nonzero exit alone is NOT RED — syntax errors, zero-test discovery, fixture crashes, parser errors, and unrelated assertions are INVALID_RED and must not authorize GREEN.
98
98
  4. Persist the RED evidence from the actual command: `{command, exitCode, output, targetTest, targetFile, expected, actual}`. Preserve the real exit status and unmodified report; record the planned expected result and observed actual result. Write the record inside the project directory; the classifier refuses a path outside it. It reads only these fields, so the further fields below stay in the record for review.
99
- - Select evidence by **report format**, regardless of runner or package manager. The classifier has a shared TAP adapter for Node's built-in test runner and Vitest (`tap` / `tap-flat`), a JUnit XML adapter for Maven Surefire/Failsafe, a swift-testing console adapter for `swift test`, and a Python `unittest` text adapter. Other producers of these formats use the same adapters.
99
+ - Select evidence by **report format**, regardless of runner or package manager. The classifier has a shared TAP adapter for Node's built-in test runner and Vitest (`tap` / `tap-flat`), a JUnit XML adapter for Maven Surefire/Failsafe, a swift-testing console adapter for `swift test`, a Python `unittest` text adapter, and a `go test` text adapter. Other producers of these formats use the same adapters.
100
100
  - TAP: capture the complete reporter output, including its test plan. Node summary comments are optional. The report must start with its TAP output: capture the reporter's own stdout (for npm scripts, `npm run --silent`), not package-manager banners. Use the target test's exact name; when names repeat, use its qualified name as emitted in the report (`tap-flat` emits only the full `file > suite > test` name). Suite/file summary failures are not target assertions. If the reporter is incompatible, rerun the planned target with a supported reporter through the project's test command, capture that real command and result, then submit the rerun's record to the classifier below. Preserve other console output separately rather than editing the report to make it parse.
101
101
  - JUnit XML: capture the unchanged target report generated by the actual run. For Maven this is `target/surefire-reports/TEST-*.xml` or `target/failsafe-reports/TEST-*.xml`. Record the report path, run start, and modification time as `reportPath`, `runStartedAt`, and `reportModifiedAt`; require the report to be newer than the run start and verify the named target class or method executed in that run. Name the target as the method, the class (simple or package-qualified), or `<package.Class>#<method>`. Put one complete XML document in `output`, preserving console output alongside it. Missing, stale, or ambiguous reports require STOP and investigation.
102
+ - `go test`: capture the complete console output (`go test ./...` or `-v`; `-json` is not supported) including every package summary line (`FAIL\tpkg\t0.004s`). A failed test is its `--- FAIL: TestName` line; the same test name failing in two packages is ambiguous and needs the package-qualified target `import/path.TestName`. A package that does not compile (`FAIL\tpkg [build failed]`) or load (`[setup failed]`) asserted nothing — the usual cause is a test naming a symbol that does not exist yet — and the whole run is refused as `build_failure`, even if another package genuinely failed. Make the test compile and fail on its assertion, then record the run again.
102
103
  - swift-testing and `unittest`: capture the complete console output without color (`NO_COLOR=1`), including the final summary. swift-testing needs one `Test` result line (passed, failed, skipped, or cancelled) for every test its `Test run with N tests` summary counts; it names a test by its quoted display name or, without one, by its function name (`addsNumbers()`), and either name identifies the target; repeated swift-testing names are ambiguous and block GREEN. `unittest` needs one `FAIL:`/`ERROR:` header for every failure or error its `FAILED (...)` line counts; repeated headers from one test's failing subTests count as one failing test, a method name that repeats across classes needs its `module.Class.method` id, and a `unittest.loader._FailedTest` entry is a load failure, never RED.
103
- - Pass the unchanged record to `gsd_run check tdd-red-evidence <record.json> --raw` and require `RED_EVIDENCE_OK` before GREEN. Every `INVALID_RED` reason (`unexpected_green`, `zero_tests_discovered`, `nonzero_exit_without_test_failure`, `fixture_or_load_failure`, `no_target_test_failure`, `invalid_record`, `unreadable_record`) trips the gate. `invalid_record` includes unsupported or malformed reports; inspect `evidence.report_errors`. An unsupported format requires a supported reporter or a parser adapter before GREEN. Classifier rejection never authorizes a fallback to self-attestation.
104
+ - Pass the unchanged record to `gsd_run check tdd-red-evidence <record.json> --raw` and require `RED_EVIDENCE_OK` before GREEN. Every `INVALID_RED` reason (`unexpected_green`, `zero_tests_discovered`, `nonzero_exit_without_test_failure`, `fixture_or_load_failure`, `no_target_test_failure`, `build_failure`, `invalid_record`, `unreadable_record`) trips the gate. `invalid_record` includes unsupported or malformed reports; inspect `evidence.report_errors`. An unsupported format requires a supported reporter or a parser adapter before GREEN. Classifier rejection never authorizes a fallback to self-attestation.
104
105
  - After machine validation, inspect whether the target actually executed and failed on the planned assertion for the intended reason. Zero tests, a missing, skipped, or cancelled target, setup, collection, import, syntax, or fixture faults, unrelated failures, unexpected green, and incomplete or ambiguous evidence block GREEN. Record this semantic assessment as `semanticAssessment` in the RED evidence and in the SUMMARY's RED entry; never fabricate reports, counters, or parser verdicts.
105
106
  5. If test passes: feature exists or test is wrong. Investigate.
106
107
  6. Commit: `test({phase}-{plan}): add failing test for [feature]`
@@ -315,7 +316,7 @@ git log --oneline -E --grep="^feat\((0*${PHASE_N})-(0*${PLAN_N})\):" | head -1
315
316
  git log --oneline -E --grep="^refactor\((0*${PHASE_N})-(0*${PLAN_N})\):" | head -1
316
317
  ```
317
318
 
318
- If RED or GREEN gate commits are missing, add a `## TDD Gate Compliance` section to SUMMARY.md with the violation details.
319
+ If RED or GREEN gate commits are missing, or no GREEN commit follows a RED commit (#5275), add a `## TDD Gate Compliance` section to SUMMARY.md with the violation details.
319
320
  </gate_enforcement>
320
321
 
321
322
  <end_of_phase_review>
@@ -341,7 +342,7 @@ TDD Plans: {count} | Gate violations: {count}
341
342
 
342
343
  ### What the Review Checks
343
344
 
344
- 1. **Gate sequence:** Each TDD plan has RED → GREEN commits in order
345
+ 1. **Gate sequence:** Each TDD plan has RED → GREEN commits in order (#5275). Order is commit ancestry, not log position or commit date: a GREEN counts only when it descends from a RED commit of the plan, so a history merged from worktree branches is judged by what each commit descends from. A plan with both commit kinds but no GREEN after a RED fails with `reason: green_before_red` and the line `Plan {id} GREEN precedes RED: no feat(...) commit descends from a test(...) commit.`, distinct from the `missing` row for a plan without a RED or GREEN commit. If git cannot answer the ancestry question, the GREEN keeps its credit rather than becoming a violation. A plan is scored on its own `test(<plan>):` / `feat(<plan>):` commits, with one exception (#4867): a RED-only probe plan is satisfied by a `feat(...)` commit under a plan that declares it in `depends_on`, directly or through a chain. That is the gap-closure shape (probe plan, then the fix plan that depends on it). The plan's own RED is still required, a plan nothing depends on is scored alone, and the report names the plan that supplied the GREEN (`Plan {id} GREEN is supplied by dependent plan {id}.`).
345
346
  2. **Test quality:** RED phase tests fail for the right reason (not import errors or syntax)
346
347
  3. **Minimal GREEN:** Implementation is minimal — no premature optimization in GREEN phase
347
348
  4. **Refactor discipline:** If REFACTOR commit exists, tests still pass
@@ -39,7 +39,12 @@ fi
39
39
  ```
40
40
 
41
41
  The handler returns JSON `{ skipped, blocking: false, total, honored,
42
- not_honored: [...], message }`. A non-zero `DECISION_EXIT` (`69` `UNAVAILABLE`: the gate
42
+ not_honored: [...], message }`, plus `reason` on a skip or a caller error. On a
43
+ caller error about the phase directory (`reason` is `'phase directory not found'`,
44
+ `'phase path is not a directory'`, `'phase directory unreadable'` or
45
+ `'phase directory holds no plans'`), `total` and `honored` are `null` and
46
+ `not_honored` is `[]`; nothing was checked, so there is no count to report.
47
+ A non-zero `DECISION_EXIT` (`69` `UNAVAILABLE`: the gate
43
48
  could not read CONTEXT.md or the phase artifacts, #5170) is **not** "no decisions": record
44
49
  `Decision coverage: not evaluated (the gate could not read its evidence)` in VERIFICATION.md
45
50
  and do not report counts.
@@ -49,6 +54,8 @@ section) to VERIFICATION.md regardless of outcome — even when all
49
54
  decisions are honored, recording the count helps reviewers spot drift over
50
55
  time. Set `decision_coverage` in the verification result to
51
56
  `{honored, total, not_honored: [...]}` so downstream tooling can read it.
57
+ On a caller error that is `{honored: null, total: null, not_honored: []}`;
58
+ pass the nulls through rather than coercing them to `0`.
52
59
 
53
60
  **Status impact:** none. The decision gate does NOT influence the
54
61
  `gaps_found` / `human_needed` / `passed` decision tree in Step 9. Its