@opengsd/gsd-core 1.18.0 → 1.20.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 (308) 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-code-fixer.compact.md +7 -7
  5. package/agents/gsd-code-fixer.md +13 -7
  6. package/agents/gsd-code-reviewer.compact.md +49 -8
  7. package/agents/gsd-code-reviewer.md +53 -14
  8. package/agents/gsd-executor.md +8 -4
  9. package/agents/gsd-verifier.md +1 -1
  10. package/bin/install.js +422 -103
  11. package/commands/gsd/update.md +5 -2
  12. package/gsd-core/bin/gsd-tools.cjs +179 -30
  13. package/gsd-core/bin/lib/agent-install-check.cjs +29 -3
  14. package/gsd-core/bin/lib/args-terminator.cjs +114 -0
  15. package/gsd-core/bin/lib/broken-windows.cjs +59 -1
  16. package/gsd-core/bin/lib/capability-lifecycle.cjs +1 -12
  17. package/gsd-core/bin/lib/capability-lock.cjs +13 -2
  18. package/gsd-core/bin/lib/capability-registry.cjs +376 -103
  19. package/gsd-core/bin/lib/capability-state.cjs +14 -8
  20. package/gsd-core/bin/lib/capability-validator.cjs +32 -0
  21. package/gsd-core/bin/lib/commands.cjs +27 -17
  22. package/gsd-core/bin/lib/config-loader.cjs +13 -3
  23. package/gsd-core/bin/lib/config.cjs +256 -58
  24. package/gsd-core/bin/lib/core-utils.cjs +45 -0
  25. package/gsd-core/bin/lib/decision-coverage-support.cjs +74 -0
  26. package/gsd-core/bin/lib/frontmatter.cjs +21 -3
  27. package/gsd-core/bin/lib/gate-api-coverage-verify-pre.cjs +2 -2
  28. package/gsd-core/bin/lib/gate-context-drift.cjs +9 -4
  29. package/gsd-core/bin/lib/gate-decision-coverage-plan.cjs +6 -1
  30. package/gsd-core/bin/lib/gate-decision-coverage-verify.cjs +6 -1
  31. package/gsd-core/bin/lib/gate-evaluation-scope.cjs +134 -13
  32. package/gsd-core/bin/lib/gate-phase-context.cjs +7 -8
  33. package/gsd-core/bin/lib/gate-schema-drift.cjs +1 -1
  34. package/gsd-core/bin/lib/gate-tdd-review-checkpoint.cjs +63 -22
  35. package/gsd-core/bin/lib/gate-ui-safety.cjs +23 -5
  36. package/gsd-core/bin/lib/git-info-exclude.cjs +52 -0
  37. package/gsd-core/bin/lib/health-diagnostic-rules/phase-structure.cjs +31 -12
  38. package/gsd-core/bin/lib/health-diagnostic-rules/roadmap-disk-consistency.cjs +9 -7
  39. package/gsd-core/bin/lib/health-diagnostic-rules/state-consistency.cjs +3 -3
  40. package/gsd-core/bin/lib/health-diagnostic-types.cjs +4 -0
  41. package/gsd-core/bin/lib/health-diagnostic.cjs +52 -1
  42. package/gsd-core/bin/lib/host-integration.cjs +13 -6
  43. package/gsd-core/bin/lib/init.cjs +245 -68
  44. package/gsd-core/bin/lib/install-engine.cjs +246 -32
  45. package/gsd-core/bin/lib/install-profiles.cjs +9 -4
  46. package/gsd-core/bin/lib/installer-migration-report.cjs +2 -0
  47. package/gsd-core/bin/lib/io.cjs +6 -0
  48. package/gsd-core/bin/lib/markdown-line-diff.cjs +147 -0
  49. package/gsd-core/bin/lib/markdown-sectionizer.cjs +147 -1
  50. package/gsd-core/bin/lib/markdown-table.cjs +23 -0
  51. package/gsd-core/bin/lib/mcp-server.cjs +8 -5
  52. package/gsd-core/bin/lib/milestone-lock.cjs +8 -1
  53. package/gsd-core/bin/lib/milestone-release-map.cjs +244 -0
  54. package/gsd-core/bin/lib/milestone-release-tags.cjs +440 -0
  55. package/gsd-core/bin/lib/milestone.cjs +61 -51
  56. package/gsd-core/bin/lib/model-catalog.cjs +8 -0
  57. package/gsd-core/bin/lib/normalize-test-command.cjs +67 -0
  58. package/gsd-core/bin/lib/phase-entry-grammar.cjs +119 -0
  59. package/gsd-core/bin/lib/phase-id-display.cjs +2 -0
  60. package/gsd-core/bin/lib/phase-id.cjs +629 -38
  61. package/gsd-core/bin/lib/phase-lifecycle.cjs +12 -44
  62. package/gsd-core/bin/lib/phase-locator.cjs +328 -19
  63. package/gsd-core/bin/lib/phase.cjs +3066 -653
  64. package/gsd-core/bin/lib/planning-document.cjs +26 -5
  65. package/gsd-core/bin/lib/planning-inspect.cjs +27 -35
  66. package/gsd-core/bin/lib/planning-snapshot.cjs +15 -8
  67. package/gsd-core/bin/lib/planning-workspace.cjs +21 -3
  68. package/gsd-core/bin/lib/pristine-baseline.cjs +11 -3
  69. package/gsd-core/bin/lib/read-only-git-env.cjs +79 -0
  70. package/gsd-core/bin/lib/refactor-trigger-command-router.cjs +2 -2
  71. package/gsd-core/bin/lib/review-lane-descriptor.cjs +57 -2
  72. package/gsd-core/bin/lib/review-lane-invocation.cjs +125 -18
  73. package/gsd-core/bin/lib/review-lane-runner.cjs +33 -4
  74. package/gsd-core/bin/lib/reviewer-step-dispatch.cjs +11 -9
  75. package/gsd-core/bin/lib/roadmap-parser.cjs +735 -137
  76. package/gsd-core/bin/lib/roadmap-progress.cjs +136 -0
  77. package/gsd-core/bin/lib/roadmap-upgrade.cjs +47 -7
  78. package/gsd-core/bin/lib/roadmap.cjs +383 -123
  79. package/gsd-core/bin/lib/runtime-artifact-conversion.cjs +381 -14
  80. package/gsd-core/bin/lib/runtime-artifact-layout.cjs +8 -4
  81. package/gsd-core/bin/lib/runtime-hooks-surface.cjs +60 -16
  82. package/gsd-core/bin/lib/shell-command-projection.cjs +151 -19
  83. package/gsd-core/bin/lib/smart-entry.cjs +35 -11
  84. package/gsd-core/bin/lib/state-contract.cjs +39 -15
  85. package/gsd-core/bin/lib/state-document.cjs +344 -27
  86. package/gsd-core/bin/lib/state-transition.cjs +156 -32
  87. package/gsd-core/bin/lib/state.cjs +527 -186
  88. package/gsd-core/bin/lib/surface.cjs +9 -0
  89. package/gsd-core/bin/lib/task-command-router.cjs +5 -25
  90. package/gsd-core/bin/lib/task-content-resolution.cjs +46 -1
  91. package/gsd-core/bin/lib/template.cjs +2 -2
  92. package/gsd-core/bin/lib/validate.cjs +3 -2
  93. package/gsd-core/bin/lib/vendor/re2js.cjs +863 -577
  94. package/gsd-core/bin/lib/vendor/re2js.d.cts +284 -219
  95. package/gsd-core/bin/lib/verification.cjs +96 -9
  96. package/gsd-core/bin/lib/verify.cjs +4 -2
  97. package/gsd-core/bin/lib/workstream-inventory.cjs +26 -20
  98. package/gsd-core/bin/lib/worktree-safety.cjs +22 -1
  99. package/gsd-core/bin/shared/config-defaults.manifest.json +1 -0
  100. package/gsd-core/bin/shared/config-schema.manifest.json +1 -0
  101. package/gsd-core/bin/verify-reapply-patches.cjs +411 -10
  102. package/gsd-core/references/cicd-review-patterns.md +45 -0
  103. package/gsd-core/references/git-integration.md +11 -0
  104. package/gsd-core/references/loop-hook-dispatch.md +13 -6
  105. package/gsd-core/references/necessity-review.md +48 -0
  106. package/gsd-core/references/planning-config.md +2 -0
  107. package/gsd-core/references/tdd.md +2 -2
  108. package/gsd-core/templates/DEBUG.md +16 -0
  109. package/gsd-core/templates/config.json +2 -1
  110. package/gsd-core/templates/verification-report.md +1 -1
  111. package/gsd-core/workflows/audit-fix.md +2 -1
  112. package/gsd-core/workflows/code-review/steps/structural-pre-pass.md +10 -1
  113. package/gsd-core/workflows/code-review-fix.md +46 -5
  114. package/gsd-core/workflows/code-review.md +64 -7
  115. package/gsd-core/workflows/complete-milestone/steps/project-md-evolution-review.md +145 -0
  116. package/gsd-core/workflows/complete-milestone/steps/release-map.md +80 -0
  117. package/gsd-core/workflows/complete-milestone.md +37 -150
  118. package/gsd-core/workflows/diagnose-issues.md +29 -5
  119. package/gsd-core/workflows/discuss-phase/modes/power.md +1 -1
  120. package/gsd-core/workflows/discuss-phase-assumptions.md +33 -2
  121. package/gsd-core/workflows/discuss-phase-power.md +19 -2
  122. package/gsd-core/workflows/discuss-phase.md +6 -10
  123. package/gsd-core/workflows/execute-phase/steps/per-plan-worktree-gate.md +13 -7
  124. package/gsd-core/workflows/execute-phase/steps/post-merge-gate.md +7 -2
  125. package/gsd-core/workflows/execute-phase/steps/regression-gate-run.md +2 -1
  126. package/gsd-core/workflows/execute-phase/steps/verify-phase-goal.md +2 -2
  127. package/gsd-core/workflows/execute-phase/steps/wave-post-gate-hooks.md +11 -2
  128. package/gsd-core/workflows/execute-phase.md +10 -6
  129. package/gsd-core/workflows/execute-plan.md +1 -1
  130. package/gsd-core/workflows/health.md +2 -1
  131. package/gsd-core/workflows/import.md +1 -1
  132. package/gsd-core/workflows/plan-phase/steps/chunked-planning-mode.md +2 -2
  133. package/gsd-core/workflows/plan-phase/steps/stall-detection-helpers.md +13 -9
  134. package/gsd-core/workflows/plan-phase.md +14 -14
  135. package/gsd-core/workflows/pr-branch.md +2 -0
  136. package/gsd-core/workflows/quick/steps/plan-checker-loop.md +1 -1
  137. package/gsd-core/workflows/quick/steps/worktree-pre-dispatch-commit.md +8 -1
  138. package/gsd-core/workflows/quick-batch/steps/plan-checker-loop.md +2 -1
  139. package/gsd-core/workflows/quick.md +1 -1
  140. package/gsd-core/workflows/reapply-patches.md +104 -0
  141. package/gsd-core/workflows/review.md +70 -154
  142. package/gsd-core/workflows/section-manifest.json +5 -0
  143. package/gsd-core/workflows/secure-phase.md +1 -1
  144. package/gsd-core/workflows/settings-advanced.md +57 -9
  145. package/gsd-core/workflows/settings.md +1 -0
  146. package/gsd-core/workflows/ship.md +15 -4
  147. package/gsd-core/workflows/spec-phase.md +3 -6
  148. package/gsd-core/workflows/verify-work.md +4 -3
  149. package/hooks/dist/gsd-agent-isolation-guard.js +11 -1
  150. package/hooks/dist/gsd-cursor-subagent-start.js +6 -2
  151. package/hooks/dist/gsd-graphify-update.sh +6 -0
  152. package/hooks/dist/gsd-path-export.js +95 -0
  153. package/hooks/dist/gsd-secret-read-guard.js +172 -39
  154. package/hooks/dist/gsd-statusline.js +37 -3
  155. package/hooks/dist/gsd-windsurf-pre-write.js +29 -2
  156. package/hooks/dist/gsd-workflow-guard.js +3 -2
  157. package/hooks/dist/gsd-worktree-path-guard.js +29 -2
  158. package/hooks/dist/lib/git-probe.js +16 -0
  159. package/hooks/dist/lib/isolation-sentinel.js +44 -0
  160. package/hooks/dist/managed-hooks-registry.cjs +1 -0
  161. package/hooks/gsd-agent-isolation-guard.js +11 -1
  162. package/hooks/gsd-cursor-subagent-start.js +6 -2
  163. package/hooks/gsd-graphify-update.sh +6 -0
  164. package/hooks/gsd-path-export.js +95 -0
  165. package/hooks/gsd-secret-read-guard.js +172 -39
  166. package/hooks/gsd-statusline.js +37 -3
  167. package/hooks/gsd-windsurf-pre-write.js +29 -2
  168. package/hooks/gsd-workflow-guard.js +3 -2
  169. package/hooks/gsd-worktree-path-guard.js +29 -2
  170. package/hooks/hooks.json +1 -0
  171. package/hooks/lib/git-probe.js +16 -0
  172. package/hooks/lib/isolation-sentinel.js +44 -0
  173. package/hooks/managed-hooks-registry.cjs +1 -0
  174. package/package.json +15 -8
  175. package/pi/gsd.cjs +1047 -10
  176. package/pi/package-agents/gsd-advisor-researcher.md +113 -0
  177. package/pi/package-agents/gsd-ai-researcher.md +111 -0
  178. package/pi/package-agents/gsd-assumptions-analyzer.md +110 -0
  179. package/pi/package-agents/gsd-code-fixer.md +774 -0
  180. package/pi/package-agents/gsd-code-reviewer.md +441 -0
  181. package/pi/package-agents/gsd-codebase-mapper.md +850 -0
  182. package/pi/package-agents/gsd-debug-session-manager.md +416 -0
  183. package/pi/package-agents/gsd-debugger.md +1274 -0
  184. package/pi/package-agents/gsd-doc-classifier.md +269 -0
  185. package/pi/package-agents/gsd-doc-synthesizer.md +259 -0
  186. package/pi/package-agents/gsd-doc-verifier.md +212 -0
  187. package/pi/package-agents/gsd-doc-writer.md +626 -0
  188. package/pi/package-agents/gsd-dom-verifier.md +164 -0
  189. package/pi/package-agents/gsd-domain-researcher.md +144 -0
  190. package/pi/package-agents/gsd-eval-auditor.md +185 -0
  191. package/pi/package-agents/gsd-eval-planner.md +149 -0
  192. package/pi/package-agents/gsd-executor.md +898 -0
  193. package/pi/package-agents/gsd-framework-selector.md +159 -0
  194. package/pi/package-agents/gsd-integration-checker.md +476 -0
  195. package/pi/package-agents/gsd-intel-updater.md +338 -0
  196. package/pi/package-agents/gsd-mempalace-curator.md +49 -0
  197. package/pi/package-agents/gsd-nyquist-auditor.md +200 -0
  198. package/pi/package-agents/gsd-pattern-mapper.md +340 -0
  199. package/pi/package-agents/gsd-phase-researcher.md +897 -0
  200. package/pi/package-agents/gsd-plan-checker.md +1077 -0
  201. package/pi/package-agents/gsd-planner.md +1003 -0
  202. package/pi/package-agents/gsd-project-researcher.md +611 -0
  203. package/pi/package-agents/gsd-research-synthesizer.md +259 -0
  204. package/pi/package-agents/gsd-roadmapper.md +779 -0
  205. package/pi/package-agents/gsd-security-auditor.md +171 -0
  206. package/pi/package-agents/gsd-ui-auditor.md +581 -0
  207. package/pi/package-agents/gsd-ui-checker.md +417 -0
  208. package/pi/package-agents/gsd-ui-researcher.md +469 -0
  209. package/pi/package-agents/gsd-user-profiler.md +174 -0
  210. package/pi/package-agents/gsd-verifier.md +963 -0
  211. package/pi/package-agents/manifest.json +39 -0
  212. package/pi/package-skills/gsd-add-tests/SKILL.md +39 -0
  213. package/pi/package-skills/gsd-ai-integration-phase/SKILL.md +36 -0
  214. package/pi/package-skills/gsd-audit-fix/SKILL.md +34 -0
  215. package/pi/package-skills/gsd-audit-milestone/SKILL.md +40 -0
  216. package/pi/package-skills/gsd-audit-uat/SKILL.md +26 -0
  217. package/pi/package-skills/gsd-autonomous/SKILL.md +52 -0
  218. package/pi/package-skills/gsd-capture/SKILL.md +68 -0
  219. package/pi/package-skills/gsd-cleanup/SKILL.md +25 -0
  220. package/pi/package-skills/gsd-code-review/SKILL.md +71 -0
  221. package/pi/package-skills/gsd-complete-milestone/SKILL.md +147 -0
  222. package/pi/package-skills/gsd-config/SKILL.md +60 -0
  223. package/pi/package-skills/gsd-debug/SKILL.md +64 -0
  224. package/pi/package-skills/gsd-discuss-phase/SKILL.md +74 -0
  225. package/pi/package-skills/gsd-docs-update/SKILL.md +50 -0
  226. package/pi/package-skills/gsd-eval-review/SKILL.md +35 -0
  227. package/pi/package-skills/gsd-execute-phase/SKILL.md +62 -0
  228. package/pi/package-skills/gsd-explore/SKILL.md +26 -0
  229. package/pi/package-skills/gsd-extract-learnings/SKILL.md +25 -0
  230. package/pi/package-skills/gsd-fast/SKILL.md +33 -0
  231. package/pi/package-skills/gsd-forensics/SKILL.md +59 -0
  232. package/pi/package-skills/gsd-graphify/SKILL.md +210 -0
  233. package/pi/package-skills/gsd-health/SKILL.md +36 -0
  234. package/pi/package-skills/gsd-help/SKILL.md +37 -0
  235. package/pi/package-skills/gsd-import/SKILL.md +46 -0
  236. package/pi/package-skills/gsd-inbox/SKILL.md +42 -0
  237. package/pi/package-skills/gsd-ingest-docs/SKILL.md +43 -0
  238. package/pi/package-skills/gsd-manager/SKILL.md +46 -0
  239. package/pi/package-skills/gsd-map-codebase/SKILL.md +85 -0
  240. package/pi/package-skills/gsd-mempalace-capture/SKILL.md +111 -0
  241. package/pi/package-skills/gsd-mempalace-recall/SKILL.md +111 -0
  242. package/pi/package-skills/gsd-milestone-summary/SKILL.md +54 -0
  243. package/pi/package-skills/gsd-mvp-phase/SKILL.md +45 -0
  244. package/pi/package-skills/gsd-new-milestone/SKILL.md +49 -0
  245. package/pi/package-skills/gsd-new-project/SKILL.md +49 -0
  246. package/pi/package-skills/gsd-next/SKILL.md +34 -0
  247. package/pi/package-skills/gsd-ns-context/SKILL.md +31 -0
  248. package/pi/package-skills/gsd-ns-ideate/SKILL.md +30 -0
  249. package/pi/package-skills/gsd-ns-manage/SKILL.md +42 -0
  250. package/pi/package-skills/gsd-ns-project/SKILL.md +34 -0
  251. package/pi/package-skills/gsd-ns-review/SKILL.md +35 -0
  252. package/pi/package-skills/gsd-ns-workflow/SKILL.md +44 -0
  253. package/pi/package-skills/gsd-onboard/SKILL.md +46 -0
  254. package/pi/package-skills/gsd-pause-work/SKILL.md +48 -0
  255. package/pi/package-skills/gsd-phase/SKILL.md +61 -0
  256. package/pi/package-skills/gsd-plan-phase/SKILL.md +62 -0
  257. package/pi/package-skills/gsd-plan-review-convergence/SKILL.md +63 -0
  258. package/pi/package-skills/gsd-pr-branch/SKILL.md +31 -0
  259. package/pi/package-skills/gsd-profile-user/SKILL.md +48 -0
  260. package/pi/package-skills/gsd-progress/SKILL.md +51 -0
  261. package/pi/package-skills/gsd-quick/SKILL.md +179 -0
  262. package/pi/package-skills/gsd-quick-batch/SKILL.md +114 -0
  263. package/pi/package-skills/gsd-resume-work/SKILL.md +31 -0
  264. package/pi/package-skills/gsd-review/SKILL.md +50 -0
  265. package/pi/package-skills/gsd-review-backlog/SKILL.md +65 -0
  266. package/pi/package-skills/gsd-secure-phase/SKILL.md +37 -0
  267. package/pi/package-skills/gsd-settings/SKILL.md +30 -0
  268. package/pi/package-skills/gsd-ship/SKILL.md +27 -0
  269. package/pi/package-skills/gsd-sketch/SKILL.md +56 -0
  270. package/pi/package-skills/gsd-spec-phase/SKILL.md +63 -0
  271. package/pi/package-skills/gsd-spike/SKILL.md +53 -0
  272. package/pi/package-skills/gsd-stats/SKILL.md +22 -0
  273. package/pi/package-skills/gsd-surface/SKILL.md +177 -0
  274. package/pi/package-skills/gsd-thread/SKILL.md +29 -0
  275. package/pi/package-skills/gsd-ui-phase/SKILL.md +35 -0
  276. package/pi/package-skills/gsd-ui-review/SKILL.md +35 -0
  277. package/pi/package-skills/gsd-ultraplan-phase/SKILL.md +38 -0
  278. package/pi/package-skills/gsd-undo/SKILL.md +38 -0
  279. package/pi/package-skills/gsd-update/SKILL.md +54 -0
  280. package/pi/package-skills/gsd-validate-phase/SKILL.md +37 -0
  281. package/pi/package-skills/gsd-verify-work/SKILL.md +41 -0
  282. package/pi/package-skills/gsd-workspace/SKILL.md +57 -0
  283. package/pi/package-skills/gsd-workstreams/SKILL.md +73 -0
  284. package/scripts/build-hooks.js +4 -0
  285. package/scripts/ci-next-health.cjs +246 -23
  286. package/scripts/command-contract-helpers.cjs +1 -1
  287. package/scripts/docs-guard-registry.cjs +22 -7
  288. package/scripts/gen-features.cjs +14 -2
  289. package/scripts/gen-health-docs.cjs +13 -1
  290. package/scripts/gen-pi-package-skills.cjs +217 -0
  291. package/scripts/lib/drift-scan.cjs +27 -0
  292. package/scripts/lib/macos-conformance-tier.generated.cjs +17 -1
  293. package/scripts/lib/platform-conformance-tier.generated.cjs +37 -1
  294. package/scripts/lint-docs-guard-registration.exempt-baseline.cjs +3 -3
  295. package/scripts/lint-phase-enumeration-drift.cjs +8 -4
  296. package/scripts/lint-retired-runtime-name.cjs +5 -0
  297. package/scripts/lint-roadmap-progress-drift.cjs +421 -0
  298. package/scripts/mutation-matrix.cjs +23 -1
  299. package/scripts/npm-audit-baseline.cjs +41 -7
  300. package/scripts/reviewed-or-maintainer-policy.cjs +181 -0
  301. package/scripts/select-docs-guards.cjs +1 -1
  302. package/scripts/self-test-report-policy.cjs +350 -0
  303. package/scripts/sync-next-version.cjs +38 -6
  304. package/skills/gsd-update/SKILL.md +5 -2
  305. package/vscode/package.json +1 -1
  306. package/scripts/diagnose-validate-commit-in-tier.cjs +0 -270
  307. package/scripts/diagnose-validate-commit-phases.cjs +0 -348
  308. package/scripts/xtrace-phase-timing.cjs +0 -205
@@ -30,7 +30,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
30
30
  return (mod && mod.__esModule) ? mod : { "default": mod };
31
31
  };
32
32
  Object.defineProperty(exports, "__esModule", { value: true });
33
- exports.PLANNING_ARTIFACTS = void 0;
33
+ exports.PLANNING_ARTIFACTS = exports.FIELD_LINE_BREAK_RE = void 0;
34
34
  exports.parsePlanningDoc = parsePlanningDoc;
35
35
  exports.findField = findField;
36
36
  exports.readNode = readNode;
@@ -48,6 +48,15 @@ const artifacts_cjs_1 = require("./artifacts.cjs");
48
48
  // is imported as a default import (esModuleInterop), not a named import.
49
49
  const frontmatter_cjs_1 = __importDefault(require("./frontmatter.cjs"));
50
50
  const frontmatter_fence_cjs_1 = require("./frontmatter-fence.cjs");
51
+ /**
52
+ * #4998: every character that ends a line for the planning-document readers.
53
+ * Their `^`/`$` run with the `m` flag, and a JavaScript multiline anchor
54
+ * matches at U+2028 LINE SEPARATOR and U+2029 PARAGRAPH SEPARATOR as well as
55
+ * at CR and LF — so a single-line value carrying either one forges a sibling
56
+ * line just as `\n` does. Every single-line writer, here (`setFieldValue`,
57
+ * `replaceProse`) and in `state-document.cts`, refuses a value matching this.
58
+ */
59
+ exports.FIELD_LINE_BREAK_RE = /[\r\n\u2028\u2029]/;
51
60
  const { extractFrontmatter, FRONTMATTER_UNPARSEABLE } = frontmatter_cjs_1.default;
52
61
  /**
53
62
  * Canonical `.planning/` root artifact basenames this seam recognises,
@@ -562,13 +571,25 @@ function setFieldValue(doc, id, value) {
562
571
  return { ok: false, reason: `node kind '${node.kind}' is not writable this phase` };
563
572
  }
564
573
  // #4917 / ADR-4910 Decision 2 & 4: a boldField's token boundary is a LINE
565
- // boundary, not just an offset range — a value containing \n or \r escapes
574
+ // boundary, not just an offset range — a value containing any
575
+ // FIELD_LINE_BREAK_RE character (#4998: U+2028/U+2029 too) escapes
566
576
  // the field's own span and reparses as sibling structure (a forged field)
567
577
  // once spliced back into the source. Decision 4 licenses refusal for any
568
578
  // value the grammar cannot represent; Phase 3 may widen this to escaping,
569
579
  // but Phase 1 refuses outright. Do not remove this as an over-restriction.
570
- if (/[\r\n]/.test(value)) {
571
- return { ok: false, reason: 'field value must not contain a line break (\\r or \\n)' };
580
+ // One carve-out (#4998, Codex review): a U+2028/U+2029 the field ALREADY
581
+ // carries may stay when everything from it on is byte-identical — a rewrite
582
+ // of the text before it (phase renumbering a `Depends on`) adds no line, and
583
+ // refusing it left the field silently unrenumbered.
584
+ const breakAt = value.search(exports.FIELD_LINE_BREAK_RE);
585
+ if (breakAt !== -1) {
586
+ const current = doc.source.slice(node.valueSpan.start, node.valueSpan.end);
587
+ const currentBreak = current.search(exports.FIELD_LINE_BREAK_RE);
588
+ const keepsExistingTail = !/[\r\n]/.test(value) && currentBreak !== -1
589
+ && value.slice(breakAt) === current.slice(currentBreak);
590
+ if (!keepsExistingTail) {
591
+ return { ok: false, reason: 'field value must not contain a line break (\\r, \\n, U+2028 or U+2029)' };
592
+ }
572
593
  }
573
594
  // #4917 / ADR-4910 Decision 4: "a value that cannot be represented in the
574
595
  // grammar is refused by the writer, with a report." This is a GENERAL
@@ -664,7 +685,7 @@ function replaceProse(doc, from, to) {
664
685
  if (typeof from !== 'string' || from.length === 0) {
665
686
  return { ok: false, reason: 'replaceProse: `from` must be a non-empty string' };
666
687
  }
667
- if (typeof to !== 'string' || /[\r\n]/.test(from) || /[\r\n]/.test(to)) {
688
+ if (typeof to !== 'string' || exports.FIELD_LINE_BREAK_RE.test(from) || exports.FIELD_LINE_BREAK_RE.test(to)) {
668
689
  return { ok: false, reason: 'replaceProse: `from` and `to` must be single-line strings' };
669
690
  }
670
691
  if (doc.staged.size > 0) {
@@ -51,7 +51,7 @@ const planningSnapshotMod = require("./planning-snapshot.cjs");
51
51
  const { buildPlanningSnapshot, worstScope } = planningSnapshotMod;
52
52
  // eslint-disable-next-line @typescript-eslint/no-require-imports
53
53
  const planningWorkspaceMod = require("./planning-workspace.cjs");
54
- const { planningPaths } = planningWorkspaceMod;
54
+ const { planningPaths, resolvePhaseIdConvention } = planningWorkspaceMod;
55
55
  // eslint-disable-next-line @typescript-eslint/no-require-imports
56
56
  const planScan = require("./plan-scan.cjs");
57
57
  // eslint-disable-next-line @typescript-eslint/no-require-imports
@@ -74,7 +74,7 @@ const verificationMod = require("./verification.cjs");
74
74
  const { readVerificationStatus, VerificationStatusError, failOnVerificationStatusError } = verificationMod;
75
75
  // eslint-disable-next-line @typescript-eslint/no-require-imports
76
76
  const phaseIdMod = require("./phase-id.cjs");
77
- const { phaseKeyFromDir, phaseKeyFromToken, phaseMarkdownRegexSource } = phaseIdMod;
77
+ const { phaseKeyFromDir, phaseKeyFromToken, phaseMarkdownRegexSource, extractPhaseDependencyTokens } = phaseIdMod;
78
78
  // eslint-disable-next-line @typescript-eslint/no-require-imports
79
79
  const roadmapParserMod = require("./roadmap-parser.cjs");
80
80
  const { extractCurrentMilestone } = roadmapParserMod;
@@ -407,7 +407,7 @@ function findDuplicateIds(reqMd) {
407
407
  }
408
408
  return dupes;
409
409
  }
410
- function buildRequirements(requirementsPath, knownPhaseKeys, diagnostics, planningRoot) {
410
+ function buildRequirements(requirementsPath, knownPhaseKeys, diagnostics, planningRoot, directoryContext) {
411
411
  const doc = readDocument(requirementsPath, planningRoot);
412
412
  if (!doc.exists) {
413
413
  diagnostics.push({
@@ -464,7 +464,7 @@ function buildRequirements(requirementsPath, knownPhaseKeys, diagnostics, planni
464
464
  // here too — it is a FILE-MEMBERSHIP predicate (#3511) for aggregate
465
465
  // `*-UAT.md` / `*-VERIFICATION.md` scans, not a phase-identity equality
466
466
  // test, and is unreliable for decimal phases.
467
- if (!knownPhaseKeys.has(phaseKeyFromToken(token))) {
467
+ if (!knownPhaseKeys.has(phaseKeyFromToken(token, directoryContext))) {
468
468
  diagnostics.push({
469
469
  code: INSPECT_DIAGNOSTIC.REQUIREMENT_PHASE_UNKNOWN,
470
470
  subject: `${item.id}->${token}`,
@@ -915,28 +915,16 @@ function extractGoalProse(sectionBody) {
915
915
  * token scan above), never a document-wide scan. `[]` when the line is
916
916
  * absent.
917
917
  */
918
- function extractDependencyTokens(sectionBody) {
918
+ function extractDependencyTokens(sectionBody, phaseIdConvention) {
919
919
  const m = DEPENDS_ON_LINE_RE.exec(sectionBody);
920
920
  if (!m)
921
921
  return [];
922
- // #4764: phase REFERENCES, not digit runs — the same prose-anchored grammar
923
- // init.manager's dep_phases extraction uses (owner: phase-id.cts's
924
- // PHASE_DEP_REF_SOURCE). The whole-field token scrape this replaces pulled
925
- // calendar dates, git shas and ledger ids in as dependencies. The grammar's
926
- // capture group 1 already excludes the "Phase(s)" anchor word, so no
927
- // prefix-strip literal is needed here. Self-exclusion (init.manager drops
928
- // the row's own number) is deliberately NOT applied: this reader has no row
929
- // context at the extraction site and reports informationally, it does not
930
- // gate.
931
- const refRe = new RegExp(phaseIdMod.PHASE_DEP_REF_SOURCE, 'gi');
932
- const tokenRe = new RegExp(phaseIdMod.PHASE_NUMBER_TOKEN_SOURCE, 'g');
933
- const tokens = [];
934
- let refMatch;
935
- while ((refMatch = refRe.exec(m[1])) !== null) {
936
- for (const t of refMatch[1].matchAll(tokenRe))
937
- tokens.push(t[0]);
938
- }
939
- return sortedUnique(tokens);
922
+ // #4764/#4304: phase REFERENCES, not digit runs. Both this informational
923
+ // reader and init.manager consume phase-id.cts's convention-aware owner, so
924
+ // bracket milestone digits never surface as dependencies. Self-exclusion is
925
+ // deliberately NOT applied here: this site has no row context and does not
926
+ // gate readiness.
927
+ return sortedUnique(extractPhaseDependencyTokens(m[1], phaseIdConvention));
940
928
  }
941
929
  /**
942
930
  * This phase's own ROADMAP.md section body — milestone-scoped via the SAME
@@ -949,9 +937,9 @@ function extractDependencyTokens(sectionBody) {
949
937
  * sibling phase whose TITLE merely mentions this phase's number is never
950
938
  * hijacked.
951
939
  */
952
- function findPhaseRoadmapSection(cwd, roadmapText, phaseId) {
940
+ function findPhaseRoadmapSection(cwd, roadmapText, phaseId, directoryContext) {
953
941
  const scoped = extractCurrentMilestone(roadmapText, cwd);
954
- const headingRe = new RegExp(`^(?:\\[[^\\]]{1,200}\\]\\s*)?Phase\\s+${phaseMarkdownRegexSource(phaseId)}(?=[\\s:(]|$)`, 'i');
942
+ const headingRe = new RegExp(`^(?:\\[[^\\]]{1,200}\\]\\s*)?Phase\\s+${phaseMarkdownRegexSource(phaseId, directoryContext)}(?=[\\s:(]|$)`, 'i');
955
943
  const section = collectSection(scoped, (h) => headingRe.test(h.text));
956
944
  return section ? section.body : null;
957
945
  }
@@ -976,7 +964,7 @@ function findPhaseRoadmapSection(cwd, roadmapText, phaseId) {
976
964
  * `readDocument(paths.roadmap)` read, keeps both the read path and the
977
965
  * "found vs missing vs unreadable" distinction singular.
978
966
  */
979
- function buildPhaseGoalAndDependencies(cwd, roadmapDoc, phaseId, phaseDirLabel, diagnostics) {
967
+ function buildPhaseGoalAndDependencies(cwd, roadmapDoc, phaseId, phaseDirLabel, phaseIdConvention, diagnostics, directoryContext) {
980
968
  if (!roadmapDoc.readable || roadmapDoc.text === null) {
981
969
  diagnostics.push({
982
970
  code: INSPECT_DIAGNOSTIC.ROADMAP_UNSCOPED,
@@ -999,7 +987,7 @@ function buildPhaseGoalAndDependencies(cwd, roadmapDoc, phaseId, phaseDirLabel,
999
987
  dependencies: { value: [], scope: SCOPE.UNSCOPED },
1000
988
  };
1001
989
  }
1002
- const sectionBody = findPhaseRoadmapSection(cwd, roadmapDoc.text, phaseId);
990
+ const sectionBody = findPhaseRoadmapSection(cwd, roadmapDoc.text, phaseId, directoryContext);
1003
991
  if (sectionBody === null) {
1004
992
  diagnostics.push({
1005
993
  code: INSPECT_DIAGNOSTIC.ROADMAP_UNSCOPED,
@@ -1013,7 +1001,7 @@ function buildPhaseGoalAndDependencies(cwd, roadmapDoc, phaseId, phaseDirLabel,
1013
1001
  }
1014
1002
  return {
1015
1003
  goal: { value: extractGoalProse(sectionBody), scope: SCOPE.COMPLETE },
1016
- dependencies: { value: extractDependencyTokens(sectionBody), scope: SCOPE.COMPLETE },
1004
+ dependencies: { value: extractDependencyTokens(sectionBody, phaseIdConvention), scope: SCOPE.COMPLETE },
1017
1005
  };
1018
1006
  }
1019
1007
  // ─── Entry points ─────────────────────────────────────────────────────────────
@@ -1031,6 +1019,7 @@ function buildPlanningInspectResult(cwd) {
1031
1019
  let statusError = null;
1032
1020
  const diagnostics = [];
1033
1021
  const paths = planningPaths(cwd);
1022
+ const phaseIdConvention = resolvePhaseIdConvention(cwd);
1034
1023
  const planningExists = node_fs_1.default.existsSync(paths.planning);
1035
1024
  if (!planningExists) {
1036
1025
  diagnostics.push({
@@ -1040,6 +1029,7 @@ function buildPlanningInspectResult(cwd) {
1040
1029
  });
1041
1030
  }
1042
1031
  const snapshot = buildPlanningSnapshot(cwd);
1032
+ const directoryContext = snapshot.directoryContext;
1043
1033
  if (snapshot.milestone.scope !== SCOPE.COMPLETE) {
1044
1034
  diagnostics.push({
1045
1035
  code: INSPECT_DIAGNOSTIC.ROADMAP_UNSCOPED,
@@ -1072,14 +1062,14 @@ function buildPlanningInspectResult(cwd) {
1072
1062
  // one phase.
1073
1063
  const checkboxByPhaseKey = new Map();
1074
1064
  for (const [token, ticked] of Object.entries(checkboxes)) {
1075
- checkboxByPhaseKey.set(phaseKeyFromToken(token), ticked);
1065
+ checkboxByPhaseKey.set(phaseKeyFromToken(token, directoryContext), ticked);
1076
1066
  }
1077
1067
  const phaseSnapshots = snapshot.phases.value;
1078
1068
  // Canonical per-directory identity keys (`phase-id.cts`'s
1079
1069
  // `phaseKeyFromDir`), not a raw regex scrape — this is the set
1080
1070
  // `buildRequirements` below matches Traceability tokens against via
1081
1071
  // `phaseKeyFromToken`.
1082
- const knownPhaseKeys = new Set(windowed.map((dir) => phaseKeyFromDir(dir)));
1072
+ const knownPhaseKeys = new Set(windowed.map((dir) => phaseKeyFromDir(dir, undefined, directoryContext)));
1083
1073
  // Read once, shared across every phase row's goal/dependencies lookup —
1084
1074
  // the same `readDocument` seam every other document read in this module
1085
1075
  // uses, never a second file-reading path.
@@ -1124,8 +1114,10 @@ function buildPlanningInspectResult(cwd) {
1124
1114
  verification = { status: null, next_action: err.message, route: '' };
1125
1115
  }
1126
1116
  const token = /^(\d+(?:\.\d+)*)/.exec(phase.dir);
1127
- const phaseId = token ? token[1] : null;
1128
- const { goal, dependencies } = buildPhaseGoalAndDependencies(cwd, roadmapDoc, phaseId, phase.dir, diagnostics);
1117
+ const custom = phaseIdMod.customPhaseQuery(phase.dir, directoryContext);
1118
+ const owner = custom && phaseIdMod.customPhaseDirectoryParts(phase.dir, custom);
1119
+ const phaseId = owner ? owner.id : token ? token[1] : null;
1120
+ const { goal, dependencies } = buildPhaseGoalAndDependencies(cwd, roadmapDoc, phaseId, phase.dir, phaseIdConvention, diagnostics, directoryContext);
1129
1121
  // `uat.foldScope`, NOT `uat.scope` (#3078 round-8). The two currently
1130
1122
  // agree at every call site — see `buildUatRows` for why the field is
1131
1123
  // still kept separate — so folding either one here produces the same
@@ -1159,8 +1151,8 @@ function buildPlanningInspectResult(cwd) {
1159
1151
  route: verification.route,
1160
1152
  },
1161
1153
  roadmap_acceptance: {
1162
- checkbox: checkboxByPhaseKey.has(phaseKeyFromDir(phase.dir))
1163
- ? checkboxByPhaseKey.get(phaseKeyFromDir(phase.dir))
1154
+ checkbox: checkboxByPhaseKey.has(phaseKeyFromDir(phase.dir, undefined, directoryContext))
1155
+ ? checkboxByPhaseKey.get(phaseKeyFromDir(phase.dir, undefined, directoryContext))
1164
1156
  : null,
1165
1157
  // ADR-3180 §7.4: a ticked checkbox is a human annotation with no
1166
1158
  // machine authority. Stated in the payload so a consumer cannot
@@ -1174,7 +1166,7 @@ function buildPlanningInspectResult(cwd) {
1174
1166
  scope: folded,
1175
1167
  };
1176
1168
  });
1177
- const requirements = buildRequirements(paths.requirements, knownPhaseKeys, diagnostics, paths.planning);
1169
+ const requirements = buildRequirements(paths.requirements, knownPhaseKeys, diagnostics, paths.planning, directoryContext);
1178
1170
  const phaseScope = worstScope(snapshot.phaseDirs.scope, snapshot.phases.scope, ...phaseRows.map((p) => p.scope));
1179
1171
  const acceptedPhases = makeFraction(phaseRows.filter((p) => p.complete).length, phaseRows.length, phaseScope, 'progress.accepted_phases', diagnostics);
1180
1172
  const completedPlans = makeFraction(phaseRows.reduce((sum, p) => sum + p.summary_count, 0), phaseRows.reduce((sum, p) => sum + p.plan_count, 0), phaseScope, 'progress.completed_plans', diagnostics);
@@ -29,7 +29,7 @@ const roadmapParserMod = require("./roadmap-parser.cjs");
29
29
  const { getMilestoneInfo, extractCurrentMilestone } = roadmapParserMod;
30
30
  // eslint-disable-next-line @typescript-eslint/no-require-imports
31
31
  const phaseLocatorMod = require("./phase-locator.cjs");
32
- const { listMilestonePhaseDirs, listAllPhaseDirs } = phaseLocatorMod;
32
+ const { phaseDirectoryOptions, listMilestonePhaseDirs, listAllPhaseDirs } = phaseLocatorMod;
33
33
  // eslint-disable-next-line @typescript-eslint/no-require-imports
34
34
  const verificationMod = require("./verification.cjs");
35
35
  const { isPhaseComplete } = verificationMod;
@@ -51,6 +51,7 @@ const { planningPaths, planningRoot, resolvePhaseIdConvention, resolveEnvWorkstr
51
51
  // PROSE, not quoted literals.
52
52
  const markdown_sectionizer_cjs_1 = require("./markdown-sectionizer.cjs");
53
53
  const shell_command_projection_cjs_1 = require("./shell-command-projection.cjs");
54
+ const phase_entry_grammar_cjs_1 = require("./phase-entry-grammar.cjs");
54
55
  // eslint-disable-next-line @typescript-eslint/no-require-imports
55
56
  const frontmatterMod = require("./frontmatter.cjs");
56
57
  const { extractFrontmatter, stripFrontmatter } = frontmatterMod;
@@ -129,7 +130,9 @@ function buildPhaseSnapshot(phasesDir, dir, convention) {
129
130
  dir,
130
131
  complete: completionResult.value.complete,
131
132
  verificationStatus: completionResult.value.verification.status,
132
- verificationStatusError: statusError ? { file: statusError.file, message: statusError.message } : null,
133
+ verificationStatusError: statusError
134
+ ? { file: statusError.file, message: statusError.message, rawStatus: statusError.rawStatus }
135
+ : null,
133
136
  planCount: scanResult.planCount,
134
137
  summaryCount: scanResult.summaryCount,
135
138
  scope: worstScope(completionResult.scope, scanResult.scope),
@@ -584,7 +587,7 @@ function buildRoadmapDeclaredPhasesField(roadmapPath, convention) {
584
587
  * diagnostic (W011) whose entire purpose is flagging when the two DISAGREE —
585
588
  * reading the data is not re-litigating who is authoritative.
586
589
  */
587
- function buildRoadmapPhaseCheckboxesField(roadmapPath, convention) {
590
+ function buildRoadmapPhaseCheckboxesField(roadmapPath, convention, directoryContext) {
588
591
  if (!node_fs_1.default.existsSync(roadmapPath)) {
589
592
  return { value: {}, scope: SCOPE.UNREADABLE };
590
593
  }
@@ -603,7 +606,9 @@ function buildRoadmapPhaseCheckboxesField(roadmapPath, convention) {
603
606
  // vanish from the exclusion set and every unstarted bracket phase gains a
604
607
  // W006. NON-capturing (`capturing` defaults false), so the phase token stays
605
608
  // group 2 and the legacy repo compiles a byte-identical source.
606
- const checkboxRe = new RegExp(`-\\s*\\[([xX ])\\].*?${phaseHeadingPrefixSrcFor(PHASE_HEADING_BASELINE.LABEL_ONLY, convention)}0*(${PHASE_NUMBER_TOKEN_SOURCE})${OPTIONAL_PHASE_TAG_SOURCE}[:\\s]`, 'gi');
609
+ // The entry prefix (marker, box, bold token) is the phase-entry grammar's (#5764),
610
+ // the same one `buildNotStartedPhaseVariants` composes; group 1 is the box.
611
+ const checkboxRe = (0, phase_entry_grammar_cjs_1.phaseEntryRegExp)(`${phaseHeadingPrefixSrcFor(PHASE_HEADING_BASELINE.LABEL_ONLY, convention)}0*(${convention === 'bracket' ? PHASE_NUMBER_TOKEN_SOURCE : phaseIdMod.phaseScanTokenSource(directoryContext)})${OPTIONAL_PHASE_TAG_SOURCE}[:\\s]`, 'gi', { state: 'captured' });
607
612
  const value = {};
608
613
  let m;
609
614
  while ((m = checkboxRe.exec(content)) !== null) {
@@ -862,7 +867,7 @@ function buildArchivedPhaseTokensField(planBase, convention) {
862
867
  * ROADMAP.md degrades to an empty list, mirroring every other
863
868
  * ROADMAP-sourced field's absent-file handling.
864
869
  */
865
- function buildCurrentMilestoneRoadmapPhaseIdsField(cwd, roadmapPath, convention) {
870
+ function buildCurrentMilestoneRoadmapPhaseIdsField(cwd, roadmapPath, convention, directoryContext) {
866
871
  if (!node_fs_1.default.existsSync(roadmapPath))
867
872
  return { value: [], scope: SCOPE.UNREADABLE };
868
873
  let content;
@@ -900,7 +905,7 @@ function buildCurrentMilestoneRoadmapPhaseIdsField(cwd, roadmapPath, convention)
900
905
  // pinned to this site by name) would catch that narrowing, since the census
901
906
  // only pins `phaseHeadingPrefixSrcFor`'s own output, not this regex's tail.
902
907
  // Kept as its own composition to preserve this exact byte-for-behavior.
903
- const phasePattern = new RegExp(`#{2,4}\\s*${phaseHeadingPrefixSrcFor(PHASE_HEADING_BASELINE.LABEL_ONLY, convention, Boolean(bracketGroup))}(${PHASE_NUMBER_TOKEN_SOURCE})(?:\\s*\\([^)\\n]{0,200}\\))?\\s*:`, 'gi');
908
+ const phasePattern = new RegExp(`#{2,4}\\s*${phaseHeadingPrefixSrcFor(PHASE_HEADING_BASELINE.LABEL_ONLY, convention, Boolean(bracketGroup))}(${convention !== 'bracket' ? phaseIdMod.phaseScanTokenSource(directoryContext) : PHASE_NUMBER_TOKEN_SOURCE})(?:\\s*\\([^)\\n]{0,200}\\))?\\s*:`, 'gi');
904
909
  const value = [];
905
910
  for (const m of scoped.matchAll(phasePattern)) {
906
911
  const bracketId = bracketGroup ? m[1] : undefined;
@@ -1019,6 +1024,7 @@ function buildPlanningSnapshot(cwd) {
1019
1024
  // See the `phaseIdConvention` field's comment for why one resolution point is
1020
1025
  // load-bearing rather than a micro-optimisation.
1021
1026
  const phaseIdConvention = resolvePhaseIdConvention(cwd) ?? null;
1027
+ const directoryContext = phaseDirectoryOptions(cwd);
1022
1028
  // #4257: the workstream `planningPaths(cwd)` just scoped every read to
1023
1029
  // (its `planningDir` call applies this exact discriminator when handed no
1024
1030
  // `ws`), resolved through the same owner so W002's scope clause names the
@@ -1066,17 +1072,18 @@ function buildPlanningSnapshot(cwd) {
1066
1072
  statePhaseTokens: stateFields.statePhaseTokens,
1067
1073
  stateStatus: stateFields.stateStatus,
1068
1074
  roadmapDeclaredPhases: roadmapDeclared.declared,
1069
- roadmapPhaseCheckboxes: buildRoadmapPhaseCheckboxesField(paths.roadmap, phaseIdConvention),
1075
+ roadmapPhaseCheckboxes: buildRoadmapPhaseCheckboxesField(paths.roadmap, phaseIdConvention, directoryContext),
1070
1076
  researchValidationStatus: buildResearchValidationStatusField(paths.phases, phaseDirs.value, phaseDirs.scope, phaseIdConvention),
1071
1077
  milestoneArchiveStatus: buildMilestoneArchiveStatusField(cwd),
1072
1078
  planningRootFiles: buildPlanningRootFilesField(cwd),
1073
1079
  allPhaseDirNames,
1074
1080
  archivedPhaseTokens: buildArchivedPhaseTokensField(paths.planning, phaseIdConvention),
1075
- currentMilestoneRoadmapPhaseIds: buildCurrentMilestoneRoadmapPhaseIdsField(cwd, paths.roadmap, phaseIdConvention),
1081
+ currentMilestoneRoadmapPhaseIds: buildCurrentMilestoneRoadmapPhaseIdsField(cwd, paths.roadmap, phaseIdConvention, directoryContext),
1076
1082
  perPhasePlanNumbering: perPhasePlanScanFields.perPhasePlanNumbering,
1077
1083
  perPhaseOrphanSummaries: perPhasePlanScanFields.perPhaseOrphanSummaries,
1078
1084
  perPhaseWaveMissingPlans: perPhasePlanScanFields.perPhaseWaveMissingPlans,
1079
1085
  phaseIdConvention,
1086
+ directoryContext,
1080
1087
  roadmapSentinelPhaseTokens: roadmapDeclared.sentinelTokens,
1081
1088
  roadmapBracketIncoherences: buildRoadmapBracketIncoherencesField(paths.roadmap, phaseIdConvention),
1082
1089
  workstream,
@@ -186,12 +186,12 @@ function ownConfigValue(configPath, keyPath) {
186
186
  * GSD_PROJECT/GSD_WORKSTREAM value containing path separators or `..`, and
187
187
  * that shape resolves to "not present".
188
188
  */
189
- function readScopedConfigValue(cwd, keyPath) {
189
+ function readScopedConfigValue(cwd, keyPath, workstream) {
190
190
  try {
191
- const scoped = ownConfigValue(node_path_1.default.join(planningDir(cwd), 'config.json'), keyPath);
191
+ const scoped = ownConfigValue(node_path_1.default.join(planningDir(cwd, workstream), 'config.json'), keyPath);
192
192
  if (scoped.present)
193
193
  return scoped;
194
- if (resolveEnvWorkstream() !== null) {
194
+ if ((workstream === undefined ? resolveEnvWorkstream() : workstream) !== null) {
195
195
  return ownConfigValue(node_path_1.default.join(planningRoot(cwd), 'config.json'), keyPath);
196
196
  }
197
197
  }
@@ -630,6 +630,22 @@ function findContextMdIn(absDirOrFiles) {
630
630
  return { file: null, files: [], scope: SCOPE.UNREADABLE };
631
631
  }
632
632
  }
633
+ /**
634
+ * Is `name` the phase's requirements SPEC (#5629)? A plain `NN-SPEC.md`: the `-AI-SPEC.md` (AI design
635
+ * contract) and `-UI-SPEC.md` (UI design contract) variants have other purposes and never lock requirements.
636
+ * The one definition behind the init payloads' `has_spec` / `spec_path`, the discuss and plan-phase workflows
637
+ * (which read those fields) and the context-drift gate, so they cannot disagree about which file is "the SPEC".
638
+ */
639
+ function isPhaseSpecFile(name) {
640
+ return name.endsWith('-SPEC.md') && !name.endsWith('-AI-SPEC.md') && !name.endsWith('-UI-SPEC.md');
641
+ }
642
+ /**
643
+ * The phase SPEC in an already-read directory listing, or `null`. The lexically first match, so the answer
644
+ * does not depend on the filesystem's readdir order. The caller's array is not reordered.
645
+ */
646
+ function findPhaseSpecIn(files) {
647
+ return files.filter(isPhaseSpecFile).sort()[0] ?? null;
648
+ }
633
649
  module.exports = {
634
650
  worktreesOptedOut,
635
651
  readScopedConfigValue,
@@ -655,6 +671,8 @@ module.exports = {
655
671
  diagnoseUnresolvedActiveWorkstream,
656
672
  describeUnresolvedWorkstreamReason,
657
673
  findContextMdIn,
674
+ isPhaseSpecFile,
675
+ findPhaseSpecIn,
658
676
  // Test seam (audit M1): inject a deterministic isPidAlive so the liveness-gated
659
677
  // steal decision is exercised without real pids. Mirrors capability-lock.cts.
660
678
  _setLockProbes(probes) {
@@ -34,6 +34,7 @@ const node_fs_1 = __importDefault(require("node:fs"));
34
34
  const node_path_1 = __importDefault(require("node:path"));
35
35
  const node_crypto_1 = __importDefault(require("node:crypto"));
36
36
  const node_child_process_1 = require("node:child_process");
37
+ const read_only_git_env_cjs_1 = require("./read-only-git-env.cjs");
37
38
  /**
38
39
  * SHA-256 hex digest of a file's raw bytes. Byte-for-byte the same digest
39
40
  * install.js fileHash() records into manifests and backup-meta.json.
@@ -144,13 +145,20 @@ function isCleanRelativePosixPath(relPath) {
144
145
  }
145
146
  function gitExec(gitDir, args) {
146
147
  return (0, node_child_process_1.execFileSync)('git', args, {
148
+ // windowsHide (#685): a console-window flash per git call would spam the
149
+ // user on Windows for what is a background, read-only history walk. Kept
150
+ // high in the options so the completeness guard's 400-char scan finds it.
151
+ windowsHide: true,
147
152
  cwd: gitDir,
148
153
  encoding: 'utf8',
154
+ // #5048: every call here is a history/objects walk that never opens the
155
+ // index, so the variable is inert today — set anyway so a future `status` /
156
+ // `diff` caller inherits it. The block is owned by
157
+ // src/read-only-git-env.cts and shared with every other read-only seam.
158
+ // Parity: tests/git-optional-locks-parity.test.cjs.
159
+ env: (0, read_only_git_env_cjs_1.readOnlyGitEnv)(),
149
160
  timeout: GIT_SUBPROCESS_TIMEOUT_MS,
150
161
  maxBuffer: GIT_MAX_BUFFER_BYTES,
151
- // windowsHide (#685): a console-window flash per git call would spam the
152
- // user on Windows for what is a background, read-only history walk.
153
- windowsHide: true,
154
162
  // stderr is discarded: "file absent in commit" is an expected walk outcome,
155
163
  // not operator-visible diagnostics.
156
164
  stdio: ['ignore', 'pipe', 'ignore'],
@@ -0,0 +1,79 @@
1
+ "use strict";
2
+ /**
3
+ * read-only-git-env.cts — the single owner of the read-only git env block
4
+ * (#5048). CONTEXT.md / OS Shell Projection seam.
5
+ *
6
+ * By default `git status` refreshes the index and takes an *optional*
7
+ * `.git/index.lock` to write the refreshed copy back. That is a write the caller
8
+ * never asked for, and it turns a pure read into a contender for the lock a
9
+ * real `git add` / `git commit` needs — so a read-only statusline render can
10
+ * make a concurrent commit fail with
11
+ * `Unable to create '.git/index.lock': File exists`. `GIT_OPTIONAL_LOCKS=0`
12
+ * tells git to skip only those *optional* index operations. Nothing a read
13
+ * needs is lost: `git status` reads the index, it just doesn't need to rewrite
14
+ * it.
15
+ *
16
+ * ## Why this module exists
17
+ *
18
+ * The block used to be hand-written at every seam that spawns git for a read.
19
+ * Six copies of `{ ...process.env, GIT_OPTIONAL_LOCKS: '0' }` is six places to
20
+ * forget when a seam is added, and a probe that checks each seam for the literal
21
+ * value passes even when a seam stopped consulting the owner. One owner makes
22
+ * the drift structural instead: the parity test patches THIS function to a
23
+ * sentinel and asserts every seam's spawn carries the sentinel, so a seam that
24
+ * hand-writes the variable again fails red rather than passing quietly.
25
+ * (tests/git-optional-locks-parity.test.cjs, "#5048 every seam reads the shared
26
+ * owner".)
27
+ *
28
+ * ## Why the hook scripts require the built artifact
29
+ *
30
+ * `hooks/*.js` are staged as standalone files at install time, so they reach
31
+ * compiled `src/` modules through `../gsd-core/bin/lib/*.cjs` rather than a
32
+ * sibling hooks/-tree file — the same rule hooks/lib/git-cmd.js follows for
33
+ * token-scanner. They call `readOnlyGitEnv()` per spawn rather than snapshotting
34
+ * the result at module load, so a later `process.env` mutation is visible and the
35
+ * two behave identically.
36
+ */
37
+ Object.defineProperty(exports, "__esModule", { value: true });
38
+ exports.GIT_LOCATION_ENV_VARS = void 0;
39
+ exports.scrubGitLocationEnv = scrubGitLocationEnv;
40
+ exports.readOnlyGitEnv = readOnlyGitEnv;
41
+ /**
42
+ * #5548: env vars that make git act on a repository other than the one `cwd`
43
+ * names (git sets them inside hooks; wrappers export them). This is
44
+ * `git rev-parse --local-env-vars` minus GIT_CONFIG, GIT_CONFIG_PARAMETERS and
45
+ * GIT_CONFIG_COUNT, which inject config (e.g. CI `safe.directory`) rather than
46
+ * select a repository. (They are not repository-neutral: `GIT_CONFIG_COUNT` with a
47
+ * `core.worktree` pair can still redirect git, but a caller who controls the
48
+ * environment can already inject `core.sshCommand` or `core.fsmonitor`.)
49
+ * hooks/lib/git-probe.js carries a parity-tested copy.
50
+ */
51
+ exports.GIT_LOCATION_ENV_VARS = [
52
+ 'GIT_ALTERNATE_OBJECT_DIRECTORIES', 'GIT_OBJECT_DIRECTORY', 'GIT_DIR', 'GIT_WORK_TREE',
53
+ 'GIT_IMPLICIT_WORK_TREE', 'GIT_GRAFT_FILE', 'GIT_INDEX_FILE', 'GIT_NO_REPLACE_OBJECTS',
54
+ 'GIT_REPLACE_REF_BASE', 'GIT_PREFIX', 'GIT_SHALLOW_FILE', 'GIT_COMMON_DIR',
55
+ ];
56
+ /** Copy of `env` without the repo-location vars; case-insensitive (Windows keeps `Git_Dir` casing). */
57
+ function scrubGitLocationEnv(env) {
58
+ return Object.fromEntries(Object.entries(env).filter(([k]) => !exports.GIT_LOCATION_ENV_VARS.includes(k.toUpperCase())));
59
+ }
60
+ /**
61
+ * Build the env for a read-only git spawn.
62
+ *
63
+ * @param base Environment to spread, `process.env` minus the repo-location vars by default (#5548). Callers that
64
+ * layer extra git variables on top (GIT_TERMINAL_PROMPT, a caller's own
65
+ * overrides) pass them in `base`; callers that must let a caller-supplied
66
+ * override win spread the result and apply their own keys afterwards.
67
+ * @returns A fresh object each call — never the cached `process.env`.
68
+ */
69
+ function readOnlyGitEnv(base = scrubGitLocationEnv(process.env)) {
70
+ const env = {};
71
+ for (const [key, value] of Object.entries(base)) {
72
+ // process.env carries undefined-valued keys on some platforms; spawn()
73
+ // rejects a non-string value rather than ignoring it.
74
+ if (value !== undefined)
75
+ env[key] = value;
76
+ }
77
+ env.GIT_OPTIONAL_LOCKS = '0';
78
+ return env;
79
+ }
@@ -58,7 +58,7 @@ const { routeHubCommandFamily } = cjsCommandRouterAdapter;
58
58
  const { isCapabilityActive } = capabilityStateMod;
59
59
  const { resolveConfigKey } = capabilityActivationMod;
60
60
  const { loadConfig } = configLoaderMod;
61
- const { findPhaseInternal, listMilestonePhaseDirs } = phaseLocatorMod;
61
+ const { phaseDirectoryOptions, findPhaseInternal, listMilestonePhaseDirs } = phaseLocatorMod;
62
62
  const { planningDir } = planningWorkspaceMod;
63
63
  const CAPABILITY_ID = 'refactor-trigger';
64
64
  // Default CoreModule implementation. `_core` seam overrides this entirely
@@ -148,7 +148,7 @@ function requirePhaseArg(args, complexity, usage) {
148
148
  * REFACTOR_GIT_UNAVAILABLE, for this case.
149
149
  */
150
150
  function resolvePhaseDirForArg(cwd, phaseArg) {
151
- const search = findPhaseInternal(cwd, phaseArg);
151
+ const search = findPhaseInternal(cwd, phaseArg, undefined, phaseDirectoryOptions(cwd));
152
152
  if (!search || search.found !== true)
153
153
  return null;
154
154
  return { phaseDir: search.directory, padded: search.phase_number };