@opengsd/gsd-core 1.14.0 → 1.15.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (283) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/README.ja-JP.md +3 -3
  4. package/README.ko-KR.md +3 -3
  5. package/README.pt-BR.md +3 -3
  6. package/README.zh-CN.md +3 -3
  7. package/agents/gsd-code-fixer.compact.md +7 -6
  8. package/agents/gsd-code-fixer.md +9 -8
  9. package/agents/gsd-debug-session-manager.compact.md +17 -2
  10. package/agents/gsd-debug-session-manager.md +17 -2
  11. package/agents/gsd-debugger.md +2 -2
  12. package/agents/gsd-eval-auditor.compact.md +1 -1
  13. package/agents/gsd-eval-auditor.md +1 -1
  14. package/agents/gsd-executor.md +13 -8
  15. package/agents/gsd-intel-updater.compact.md +1 -1
  16. package/agents/gsd-intel-updater.md +1 -1
  17. package/agents/gsd-phase-researcher.md +19 -11
  18. package/agents/gsd-plan-checker.md +8 -7
  19. package/agents/gsd-planner.md +12 -8
  20. package/agents/gsd-project-researcher.compact.md +1 -1
  21. package/agents/gsd-project-researcher.md +1 -1
  22. package/agents/gsd-research-synthesizer.compact.md +1 -1
  23. package/agents/gsd-research-synthesizer.md +1 -1
  24. package/agents/gsd-ui-auditor.md +155 -17
  25. package/agents/gsd-ui-researcher.compact.md +1 -1
  26. package/agents/gsd-ui-researcher.md +1 -1
  27. package/agents/gsd-verifier.md +10 -9
  28. package/bin/install.js +642 -95
  29. package/commands/gsd/autonomous.md +2 -2
  30. package/commands/gsd/capture.md +1 -1
  31. package/commands/gsd/mempalace-capture.md +7 -3
  32. package/commands/gsd/plan-review-convergence.md +6 -6
  33. package/commands/gsd/progress.md +1 -1
  34. package/commands/gsd/quick-batch.md +1 -1
  35. package/commands/gsd/review.md +2 -3
  36. package/gsd-core/bin/gsd-tools.cjs +335 -22
  37. package/gsd-core/bin/lib/adr-parser.cjs +3 -1
  38. package/gsd-core/bin/lib/audit.cjs +81 -13
  39. package/gsd-core/bin/lib/capability-registry.cjs +82 -187
  40. package/gsd-core/bin/lib/capability-validator.cjs +0 -1
  41. package/gsd-core/bin/lib/check-command-router.cjs +101 -14
  42. package/gsd-core/bin/lib/codex-agent-toml.cjs +21 -25
  43. package/gsd-core/bin/lib/commands.cjs +175 -42
  44. package/gsd-core/bin/lib/config-loader.cjs +65 -4
  45. package/gsd-core/bin/lib/config.cjs +33 -7
  46. package/gsd-core/bin/lib/decisions.cjs +30 -14
  47. package/gsd-core/bin/lib/frontmatter.cjs +13 -0
  48. package/gsd-core/bin/lib/graphify.cjs +10 -2
  49. package/gsd-core/bin/lib/host-runtime-detection.cjs +9 -0
  50. package/gsd-core/bin/lib/init.cjs +207 -41
  51. package/gsd-core/bin/lib/install-engine.cjs +13 -0
  52. package/gsd-core/bin/lib/installer-migrations.cjs +8 -1
  53. package/gsd-core/bin/lib/milestone.cjs +18 -5
  54. package/gsd-core/bin/lib/model-resolver.cjs +159 -50
  55. package/gsd-core/bin/lib/phase-command-router.cjs +9 -1
  56. package/gsd-core/bin/lib/phase-id-card.cjs +32 -0
  57. package/gsd-core/bin/lib/phase-id-display.cjs +78 -0
  58. package/gsd-core/bin/lib/phase-id.cjs +109 -7
  59. package/gsd-core/bin/lib/phase-locator.cjs +29 -10
  60. package/gsd-core/bin/lib/phase.cjs +227 -26
  61. package/gsd-core/bin/lib/plan-document.cjs +49 -1
  62. package/gsd-core/bin/lib/planning-document.cjs +459 -0
  63. package/gsd-core/bin/lib/planning-inspect.cjs +18 -1
  64. package/gsd-core/bin/lib/planning-workspace.cjs +8 -3
  65. package/gsd-core/bin/lib/pr-branch-patterns.cjs +57 -0
  66. package/gsd-core/bin/lib/probe-core.cjs +7 -1
  67. package/gsd-core/bin/lib/project-root.cjs +41 -2
  68. package/gsd-core/bin/lib/review-lane-descriptor.cjs +10 -30
  69. package/gsd-core/bin/lib/review-reviewer-selection.cjs +2 -2
  70. package/gsd-core/bin/lib/roadmap-command-router.cjs +12 -4
  71. package/gsd-core/bin/lib/roadmap-parser.cjs +163 -3
  72. package/gsd-core/bin/lib/roadmap-upgrade.cjs +1539 -13
  73. package/gsd-core/bin/lib/roadmap.cjs +251 -31
  74. package/gsd-core/bin/lib/runtime-artifact-conversion.cjs +283 -31
  75. package/gsd-core/bin/lib/runtime-artifact-install-plan.cjs +3 -1
  76. package/gsd-core/bin/lib/runtime-homes.cjs +4 -0
  77. package/gsd-core/bin/lib/runtime-hooks-surface.cjs +215 -33
  78. package/gsd-core/bin/lib/runtime-name-policy.cjs +111 -1
  79. package/gsd-core/bin/lib/shell-command-projection.cjs +10 -6
  80. package/gsd-core/bin/lib/state-transition.cjs +39 -2
  81. package/gsd-core/bin/lib/state.cjs +42 -0
  82. package/gsd-core/bin/lib/surface.cjs +17 -1
  83. package/gsd-core/bin/lib/tdd-red-evidence.cjs +78 -5
  84. package/gsd-core/bin/lib/uat-predicate.cjs +47 -4
  85. package/gsd-core/bin/lib/uat.cjs +8 -0
  86. package/gsd-core/bin/lib/ui-consideration-probe.cjs +15 -2
  87. package/gsd-core/bin/lib/ui-frontend-evidence.cjs +100 -9
  88. package/gsd-core/bin/lib/undo-commit-selection.cjs +131 -0
  89. package/gsd-core/bin/lib/verification.cjs +268 -15
  90. package/gsd-core/bin/lib/verify-command-grounding.cjs +46 -2
  91. package/gsd-core/bin/lib/verify.cjs +132 -25
  92. package/gsd-core/bin/lib/worktree-base-ref.cjs +482 -73
  93. package/gsd-core/bin/lib/worktree-safety.cjs +784 -51
  94. package/gsd-core/bin/shared/config-defaults.manifest.json +3 -0
  95. package/gsd-core/bin/shared/config-schema.manifest.json +1 -0
  96. package/gsd-core/references/checkpoints.md +5 -3
  97. package/gsd-core/references/edge-probe-fixtures/01-round-half-even/expected-coverage.json +28 -3
  98. package/gsd-core/references/edge-probe-fixtures/02-merge-intervals/expected-coverage.json +37 -4
  99. package/gsd-core/references/edge-probe-fixtures/03-truncate-graphemes/expected-coverage.json +28 -3
  100. package/gsd-core/references/edge-probe-fixtures/04-money-rounding/expected-coverage.json +28 -3
  101. package/gsd-core/references/edge-probe-fixtures/05-list-dedupe/expected-coverage.json +37 -4
  102. package/gsd-core/references/edge-probe-fixtures/06-resolved-mixed/expected-coverage.json +37 -4
  103. package/gsd-core/references/edge-probe.md +195 -21
  104. package/gsd-core/references/execute-phase-between-wave-reset.md +7 -6
  105. package/gsd-core/references/execute-phase-wave-guard.md +22 -11
  106. package/gsd-core/references/gsd-run-resolver.md +1 -1
  107. package/gsd-core/references/model-profiles.md +1 -1
  108. package/gsd-core/references/phase-argument-parsing.md +9 -7
  109. package/gsd-core/references/phase-id-convention.md +28 -0
  110. package/gsd-core/references/planner-gap-closure.md +2 -0
  111. package/gsd-core/references/planner-load-graph-context.md +24 -13
  112. package/gsd-core/references/planner-verify-command-grounding.md +14 -0
  113. package/gsd-core/references/planning-config.md +11 -2
  114. package/gsd-core/references/tdd.md +27 -4
  115. package/gsd-core/references/ui-consideration-probe.md +10 -5
  116. package/gsd-core/references/verify-command-path-resolvability.md +10 -2
  117. package/gsd-core/references/worktree-path-safety.md +321 -0
  118. package/gsd-core/templates/verification-report.md +1 -1
  119. package/gsd-core/workflows/_runtime-launcher.snippet.sh +1 -1
  120. package/gsd-core/workflows/add-backlog.md +1 -1
  121. package/gsd-core/workflows/add-phase.md +1 -1
  122. package/gsd-core/workflows/add-tests.md +2 -2
  123. package/gsd-core/workflows/add-todo.md +3 -3
  124. package/gsd-core/workflows/ai-integration-phase.md +11 -3
  125. package/gsd-core/workflows/audit-fix.md +1 -1
  126. package/gsd-core/workflows/audit-milestone.md +1 -1
  127. package/gsd-core/workflows/audit-uat.md +1 -1
  128. package/gsd-core/workflows/autonomous/steps/converge-fail-fast.md +9 -18
  129. package/gsd-core/workflows/autonomous.md +16 -6
  130. package/gsd-core/workflows/check-todos.md +2 -2
  131. package/gsd-core/workflows/cleanup.md +2 -2
  132. package/gsd-core/workflows/code-review/steps/dispatch-fix.md +4 -3
  133. package/gsd-core/workflows/code-review/steps/structural-pre-pass.md +1 -1
  134. package/gsd-core/workflows/code-review-fix.md +108 -22
  135. package/gsd-core/workflows/code-review.md +63 -46
  136. package/gsd-core/workflows/complete-milestone/detail/elaboration.md +1 -1
  137. package/gsd-core/workflows/complete-milestone.md +2 -2
  138. package/gsd-core/workflows/debug.md +3 -3
  139. package/gsd-core/workflows/diagnose-issues.md +1 -1
  140. package/gsd-core/workflows/discuss-phase/modes/advisor.md +1 -1
  141. package/gsd-core/workflows/discuss-phase/modes/chain.md +1 -1
  142. package/gsd-core/workflows/discuss-phase-assumptions.md +1 -1
  143. package/gsd-core/workflows/discuss-phase.md +1 -1
  144. package/gsd-core/workflows/do.md +2 -2
  145. package/gsd-core/workflows/docs-update.md +3 -3
  146. package/gsd-core/workflows/edit-phase.md +1 -1
  147. package/gsd-core/workflows/eval-review.md +10 -3
  148. package/gsd-core/workflows/execute-phase/detail/elaboration.md +2 -2
  149. package/gsd-core/workflows/execute-phase/steps/code-review-disposition.md +1017 -0
  150. package/gsd-core/workflows/execute-phase/steps/codebase-drift-gate.md +1 -1
  151. package/gsd-core/workflows/execute-phase/steps/completion-reconciliation.md +3 -3
  152. package/gsd-core/workflows/execute-phase/steps/executor-isolation-dispatch.md +37 -3
  153. package/gsd-core/workflows/execute-phase/steps/gap-closure-artifacts.md +1 -1
  154. package/gsd-core/workflows/execute-phase/steps/partial-wave.md +1 -1
  155. package/gsd-core/workflows/execute-phase/steps/per-plan-executor-routing.md +1 -1
  156. package/gsd-core/workflows/execute-phase/steps/per-plan-worktree-gate.md +1 -1
  157. package/gsd-core/workflows/execute-phase/steps/post-merge-gate.md +46 -9
  158. package/gsd-core/workflows/execute-phase/steps/protected-branch.md +1 -1
  159. package/gsd-core/workflows/execute-phase/steps/ready-wave-gate.md +37 -0
  160. package/gsd-core/workflows/execute-phase/steps/regression-gate-run.md +1 -1
  161. package/gsd-core/workflows/execute-phase/steps/stale-reverification.md +24 -0
  162. package/gsd-core/workflows/execute-phase/steps/tdd-applicability-resolution.md +1 -1
  163. package/gsd-core/workflows/execute-phase/steps/threat-id-gate.md +28 -0
  164. package/gsd-core/workflows/execute-phase/steps/wave-post-gate-hooks.md +1 -1
  165. package/gsd-core/workflows/execute-phase/steps/worktree-base-check.md +25 -0
  166. package/gsd-core/workflows/execute-phase.md +36 -26
  167. package/gsd-core/workflows/execute-plan.md +5 -4
  168. package/gsd-core/workflows/explore.md +3 -3
  169. package/gsd-core/workflows/extract-learnings.md +2 -1
  170. package/gsd-core/workflows/fast.md +1 -1
  171. package/gsd-core/workflows/forensics.md +1 -1
  172. package/gsd-core/workflows/graduation.md +1 -1
  173. package/gsd-core/workflows/health.md +2 -2
  174. package/gsd-core/workflows/help/modes/full.compact.md +3 -3
  175. package/gsd-core/workflows/help/modes/full.md +5 -5
  176. package/gsd-core/workflows/help/modes/topic.md +15 -5
  177. package/gsd-core/workflows/import.md +2 -2
  178. package/gsd-core/workflows/inbox.md +2 -2
  179. package/gsd-core/workflows/ingest-docs.md +3 -3
  180. package/gsd-core/workflows/insert-phase.md +1 -1
  181. package/gsd-core/workflows/list-seeds.md +1 -1
  182. package/gsd-core/workflows/list-workspaces.md +1 -1
  183. package/gsd-core/workflows/manager.md +2 -2
  184. package/gsd-core/workflows/map-codebase.md +2 -2
  185. package/gsd-core/workflows/milestone-summary.md +1 -1
  186. package/gsd-core/workflows/mvp-phase.md +1 -1
  187. package/gsd-core/workflows/new-milestone.md +2 -2
  188. package/gsd-core/workflows/new-project/steps/auto-mode-config.md +3 -3
  189. package/gsd-core/workflows/new-project/steps/codebase-map-offer.md +1 -1
  190. package/gsd-core/workflows/new-project.md +7 -7
  191. package/gsd-core/workflows/new-workspace.md +2 -2
  192. package/gsd-core/workflows/next.md +1 -1
  193. package/gsd-core/workflows/note.md +1 -1
  194. package/gsd-core/workflows/onboard.md +1 -1
  195. package/gsd-core/workflows/pause-work.md +1 -1
  196. package/gsd-core/workflows/plan-phase/detail/elaboration.md +1 -1
  197. package/gsd-core/workflows/plan-phase/steps/chunked-planning-mode.md +17 -5
  198. package/gsd-core/workflows/plan-phase/steps/prd-express-path.md +1 -1
  199. package/gsd-core/workflows/plan-phase/steps/stall-detection-helpers.md +23 -5
  200. package/gsd-core/workflows/plan-phase.md +24 -7
  201. package/gsd-core/workflows/plan-review-convergence.md +21 -5
  202. package/gsd-core/workflows/plant-seed.md +62 -20
  203. package/gsd-core/workflows/pr-branch.md +113 -13
  204. package/gsd-core/workflows/profile-user.md +2 -2
  205. package/gsd-core/workflows/progress.md +1 -1
  206. package/gsd-core/workflows/quick/steps/plan-checker-loop.md +25 -0
  207. package/gsd-core/workflows/quick/steps/quick-verification.md +1 -1
  208. package/gsd-core/workflows/quick/steps/worktree-pre-dispatch-commit.md +1 -1
  209. package/gsd-core/workflows/quick-batch/steps/batch-init.md +1 -1
  210. package/gsd-core/workflows/quick-batch/steps/completion.md +1 -1
  211. package/gsd-core/workflows/quick-batch/steps/merge-wave.md +1 -1
  212. package/gsd-core/workflows/quick-batch/steps/planner-wave.md +1 -1
  213. package/gsd-core/workflows/quick-batch/steps/research-phase.md +1 -1
  214. package/gsd-core/workflows/quick-batch/steps/resume-mode.md +1 -1
  215. package/gsd-core/workflows/quick-batch/steps/verification-wave.md +1 -1
  216. package/gsd-core/workflows/quick-batch/steps/worktree-dispatch.md +1 -1
  217. package/gsd-core/workflows/quick-batch.md +1 -1
  218. package/gsd-core/workflows/quick.md +21 -9
  219. package/gsd-core/workflows/reapply-patches.md +9 -3
  220. package/gsd-core/workflows/remove-phase.md +1 -1
  221. package/gsd-core/workflows/remove-workspace.md +2 -2
  222. package/gsd-core/workflows/resume-project.md +1 -1
  223. package/gsd-core/workflows/review.md +31 -16
  224. package/gsd-core/workflows/scan.md +1 -1
  225. package/gsd-core/workflows/secure-phase.md +3 -2
  226. package/gsd-core/workflows/settings-advanced.md +30 -10
  227. package/gsd-core/workflows/settings-integrations.md +2 -3
  228. package/gsd-core/workflows/settings.md +4 -4
  229. package/gsd-core/workflows/ship.md +3 -2
  230. package/gsd-core/workflows/sketch-wrap-up.md +1 -1
  231. package/gsd-core/workflows/sketch.md +1 -1
  232. package/gsd-core/workflows/smart-entry.md +2 -2
  233. package/gsd-core/workflows/spec-phase.md +15 -5
  234. package/gsd-core/workflows/spike-wrap-up.md +1 -1
  235. package/gsd-core/workflows/spike.md +1 -1
  236. package/gsd-core/workflows/stats.md +1 -1
  237. package/gsd-core/workflows/sync-skills.md +5 -5
  238. package/gsd-core/workflows/thread.md +1 -1
  239. package/gsd-core/workflows/transition.md +1 -1
  240. package/gsd-core/workflows/ui-phase.md +44 -8
  241. package/gsd-core/workflows/ui-review.md +18 -4
  242. package/gsd-core/workflows/ultraplan-phase.md +1 -1
  243. package/gsd-core/workflows/undo.md +339 -20
  244. package/gsd-core/workflows/update.md +7 -7
  245. package/gsd-core/workflows/validate-phase.md +3 -2
  246. package/gsd-core/workflows/verify-work/steps/automated-ui-verification.md +1 -1
  247. package/gsd-core/workflows/verify-work/steps/mvp-uat-framing.md +1 -1
  248. package/gsd-core/workflows/verify-work.md +81 -16
  249. package/hooks/dist/gsd-agent-isolation-guard.js +24 -0
  250. package/hooks/dist/gsd-secret-read-guard.js +27 -1
  251. package/hooks/dist/gsd-statusline.js +70 -13
  252. package/hooks/dist/gsd-validate-commit.sh +63 -4
  253. package/hooks/gsd-agent-isolation-guard.js +24 -0
  254. package/hooks/gsd-secret-read-guard.js +27 -1
  255. package/hooks/gsd-statusline.js +70 -13
  256. package/hooks/gsd-validate-commit.sh +63 -4
  257. package/package.json +3 -2
  258. package/scripts/build-hooks.js +15 -6
  259. package/scripts/check-contract-drift.cjs +127 -11
  260. package/scripts/command-contract-helpers.cjs +3 -0
  261. package/scripts/docs-guard-registry.cjs +28 -0
  262. package/scripts/gen-loop-host-contract.cjs +69 -0
  263. package/scripts/lib/macos-conformance-tier.generated.cjs +14 -0
  264. package/scripts/lib/ndjson-reporter.cjs +3 -2
  265. package/scripts/lib/platform-conformance-tier.generated.cjs +11 -0
  266. package/scripts/lint-docs-guard-registration.exempt-baseline.cjs +28 -1
  267. package/scripts/lint-phase-arg-assignment.cjs +257 -0
  268. package/scripts/lint-phase-id-drift.cjs +290 -5
  269. package/scripts/lint-pr-branch-pattern-drift.cjs +148 -0
  270. package/scripts/lint-retired-runtime-name.cjs +619 -0
  271. package/scripts/lint-state-write-path-drift.cjs +93 -0
  272. package/scripts/lint-test-file-count.allowlist.json +28 -9
  273. package/scripts/lint-workflow-shellcheck-baseline.json +15 -0
  274. package/scripts/prompt-injection-scan.sh +4 -0
  275. package/scripts/release-tarball-smoke.cjs +194 -1
  276. package/skills/gsd-autonomous/SKILL.md +2 -2
  277. package/skills/gsd-capture/SKILL.md +1 -1
  278. package/skills/gsd-mempalace-capture/SKILL.md +7 -3
  279. package/skills/gsd-plan-review-convergence/SKILL.md +5 -5
  280. package/skills/gsd-progress/SKILL.md +1 -1
  281. package/skills/gsd-quick-batch/SKILL.md +1 -1
  282. package/skills/gsd-review/SKILL.md +2 -3
  283. package/vscode/package.json +1 -1
@@ -854,8 +854,15 @@ function scanQuantitativeCriteria(content) {
854
854
  // Not a markdown table: the negated-pipe class matches a SHELL pipeline
855
855
  // stage boundary (git before the next `|`), the same shape the
856
856
  // #429/#968 scanners use; there is no table row to parse.
857
+ // #4774: the lookahead keeps `||` — a logical OR, the construct that
858
+ // HANDLES the failure (e.g. the `git cat-file -e <sha> || echo missing`
859
+ // ghost-control idiom, reachable here when prose apostrophes leave the
860
+ // segment's quote state unclosed) — quiet, while `|` and `|&` (a real
861
+ // pipeline stage, stderr-merged or not) still warn. Accepted trade-off:
862
+ // a QUOTED `||` literal ahead of a real pipe (``git grep 'a||b' f | wc -l``)
863
+ // also goes quiet — the lookahead cannot reach past the doubled pipe.
857
864
  // allow-adhoc-markdown: shell pipeline stage boundary, not a table cell (#4024)
858
- if (/\bgit\s+[a-z][^\n|]*\|/.test(seg)) {
865
+ if (/\bgit\s+[a-z][^\n|]*\|(?!\|)/.test(seg)) {
859
866
  record(warnings, 'R4', '[plan-criteria R4] A fallible `git` in a non-final pipeline stage is swallowed — the pipeline reports ' +
860
867
  'the last stage\'s status, so a broken command reads as clean. Capture the status first.', seenWarn);
861
868
  }
@@ -926,6 +933,13 @@ function extractPlanTaskInfos(content) {
926
933
  const nameArr = (0, markdown_sectionizer_cjs_1.extractTaggedBlocks)(body, 'name');
927
934
  const hasName = nameArr.length > 0;
928
935
  const name = hasName ? nameArr[0].trim() : '';
936
+ // `(?:^|\s)` (not `\b`) so a hyphenated attribute ending in `auto_select`
937
+ // can never be mistaken for the real attribute — the same defensive
938
+ // anchor as extractOptionIds' `id` match below (#4095).
939
+ const autoSelectMatch = attrs.match(/(?:^|\s)auto_select\s*=\s*["']([^"']*)["']/);
940
+ const autoSelect = autoSelectMatch ? autoSelectMatch[1] : null;
941
+ const optionsArr = (0, markdown_sectionizer_cjs_1.extractTaggedBlocks)(body, 'options');
942
+ const optionIds = optionsArr.length > 0 ? extractOptionIds(optionsArr[0]) : [];
929
943
  infos.push({
930
944
  name,
931
945
  type,
@@ -943,6 +957,8 @@ function extractPlanTaskInfos(content) {
943
957
  hasHowToVerify: /<how-to-verify[\s>]/.test(body),
944
958
  hasDecision: /<decision[\s>]/.test(body),
945
959
  hasOptions: /<options[\s>]/.test(body),
960
+ autoSelect,
961
+ optionIds,
946
962
  hasInstructions: /<instructions[\s>]/.test(body),
947
963
  hasVerification: /<verification[\s>]/.test(body),
948
964
  hasResumeSignal: /<resume-signal[\s>]/.test(body),
@@ -954,6 +970,35 @@ function extractPlanTaskInfos(content) {
954
970
  }
955
971
  return infos;
956
972
  }
973
+ /**
974
+ * Extract the `id` attribute of every `<option id="…">` opening tag found in
975
+ * `optionsBody` (the inner text of one `<options>…</options>` block), in
976
+ * document order. Bounded attribute scan (`[^>]{0,500}`), mirroring the same
977
+ * ReDoS-safe idiom `extractPlanTaskInfos` uses for the `<task type="…">`
978
+ * attribute string — this file's established pattern for reading an
979
+ * attribute value without a general XML parser (#4095).
980
+ */
981
+ function extractOptionIds(optionsBody) {
982
+ const ids = [];
983
+ if (typeof optionsBody !== 'string' || optionsBody.length === 0)
984
+ return ids;
985
+ const OPTION_OPEN_RE = /<option(\s[^>]{0,500})?>/g;
986
+ let match;
987
+ while ((match = OPTION_OPEN_RE.exec(optionsBody)) !== null) {
988
+ const attrs = match[1] ?? '';
989
+ // `(?:^|\s)` (not `\b`) so a decoy attribute like `data-id="…"` inside
990
+ // the same opening tag cannot be mistaken for the real `id` — `\b`
991
+ // matches at the `-`→`i` boundary too, which `.match()`'s
992
+ // first-hit-wins semantics would then silently prefer (#4095).
993
+ const idMatch = attrs.match(/(?:^|\s)id\s*=\s*["']([^"']{1,200})["']/);
994
+ if (idMatch)
995
+ ids.push(idMatch[1]);
996
+ if (match.index === OPTION_OPEN_RE.lastIndex) {
997
+ OPTION_OPEN_RE.lastIndex++;
998
+ }
999
+ }
1000
+ return ids;
1001
+ }
957
1002
  function isCheckpointType(type) {
958
1003
  return type.startsWith('checkpoint:');
959
1004
  }
@@ -996,6 +1041,15 @@ function validatePlanTaskStructure(task) {
996
1041
  errors.push(`Task '${taskName}' missing <decision>`);
997
1042
  if (!task.hasOptions)
998
1043
  errors.push(`Task '${taskName}' missing <options>`);
1044
+ if (task.autoSelect !== null) {
1045
+ if (task.autoSelect.length === 0) {
1046
+ errors.push(`Task '${taskName}' auto_select is empty — name an <option id="…">`);
1047
+ }
1048
+ else if (task.hasOptions && !task.optionIds.includes(task.autoSelect)) {
1049
+ errors.push(`Task '${taskName}' auto_select="${task.autoSelect}" does not match any `
1050
+ + `<option id="…"> (available: ${task.optionIds.join(', ') || 'none'})`);
1051
+ }
1052
+ }
999
1053
  break;
1000
1054
  case 'checkpoint:human-action':
1001
1055
  if (!task.hasAction)
@@ -1165,6 +1219,11 @@ function cmdVerifyPhaseCompleteness(cwd, phase, raw) {
1165
1219
  warnings,
1166
1220
  }, raw, errors.length === 0 ? 'complete' : 'incomplete');
1167
1221
  }
1222
+ // #4678: citations may carry a trailing line suffix (":42", ":1-20") that
1223
+ // describes a location inside the file, not part of the path itself.
1224
+ function stripLineSuffix(ref) {
1225
+ return ref.replace(/:\d+(?:-\d+)?$/, '');
1226
+ }
1168
1227
  function cmdVerifyReferences(cwd, filePath, raw) {
1169
1228
  if (!filePath) {
1170
1229
  error('file path required');
@@ -1180,9 +1239,10 @@ function cmdVerifyReferences(cwd, filePath, raw) {
1180
1239
  const atRefs = content.match(/@([^\s\n,)]+\/[^\s\n,)]+)/g) || [];
1181
1240
  for (const ref of atRefs) {
1182
1241
  const cleanRef = ref.slice(1);
1183
- const resolved = cleanRef.startsWith('~/')
1184
- ? node_path_1.default.join(process.env['HOME'] || '', cleanRef.slice(2))
1185
- : node_path_1.default.join(cwd, cleanRef);
1242
+ const fsRef = stripLineSuffix(cleanRef);
1243
+ const resolved = fsRef.startsWith('~/')
1244
+ ? node_path_1.default.join(process.env['HOME'] || '', fsRef.slice(2))
1245
+ : node_path_1.default.join(cwd, fsRef);
1186
1246
  if (node_fs_1.default.existsSync(resolved)) {
1187
1247
  found.push(cleanRef);
1188
1248
  }
@@ -1190,14 +1250,14 @@ function cmdVerifyReferences(cwd, filePath, raw) {
1190
1250
  missing.push(cleanRef);
1191
1251
  }
1192
1252
  }
1193
- const backtickRefs = content.match(/`([^`]+\/[^`]+\.[a-zA-Z]{1,10})`/g) || [];
1253
+ const backtickRefs = content.match(/`([^`]+\/[^`]+\.[a-zA-Z]{1,10}(?::\d+(?:-\d+)?)?)`/g) || [];
1194
1254
  for (const ref of backtickRefs) {
1195
1255
  const cleanRef = ref.slice(1, -1);
1196
1256
  if (cleanRef.startsWith('http') || cleanRef.includes('${') || cleanRef.includes('{{'))
1197
1257
  continue;
1198
1258
  if (found.includes(cleanRef) || missing.includes(cleanRef))
1199
1259
  continue;
1200
- const resolved = node_path_1.default.join(cwd, cleanRef);
1260
+ const resolved = node_path_1.default.join(cwd, stripLineSuffix(cleanRef));
1201
1261
  if (node_fs_1.default.existsSync(resolved)) {
1202
1262
  found.push(cleanRef);
1203
1263
  }
@@ -1259,28 +1319,75 @@ function cmdVerifyArtifacts(cwd, planFilePath, raw) {
1259
1319
  const artFullPath = node_path_1.default.join(cwd, artPath);
1260
1320
  const exists = node_fs_1.default.existsSync(artFullPath);
1261
1321
  const check = { path: artPath, exists, issues: [], passed: false };
1262
- if (exists) {
1263
- const fileContent = (0, shell_command_projection_cjs_1.platformReadSync)(artFullPath) || '';
1264
- const lineCount = fileContent.split('\n').length;
1265
- if (artifact['min_lines'] && lineCount < artifact['min_lines']) {
1266
- check['issues'].push(`Only ${lineCount} lines, need ${artifact['min_lines']}`);
1267
- }
1268
- if (artifact['contains'] && !fileContent.includes(artifact['contains'])) {
1269
- check['issues'].push(`Missing pattern: ${artifact['contains']}`);
1270
- }
1271
- if (artifact['exports']) {
1272
- const exports = Array.isArray(artifact['exports'])
1273
- ? artifact['exports']
1274
- : [artifact['exports']];
1275
- for (const exp of exports) {
1276
- if (!fileContent.includes(exp))
1277
- check['issues'].push(`Missing export: ${exp}`);
1322
+ // #4685: one artifact's I/O problem is that artifact's failure, never the
1323
+ // whole plan's. `safeReadFile` rethrows every errno except ENOENT, so before
1324
+ // this an unreadable entry — a directory most commonly, but equally an EACCES
1325
+ // file or a dangling mount — threw out of the loop and the command reported
1326
+ // NOTHING: not the offending entry, and not the plan's other, perfectly good
1327
+ // artifacts either. A check that disappears is worse than a check that fails,
1328
+ // because a failure is visible.
1329
+ //
1330
+ // Scope of this guard, stated precisely (review nit): the `try` encloses the
1331
+ // whole per-artifact body, but the only statements in it that can throw are the
1332
+ // `statSync` and the read — the `min_lines`/`contains`/`exports` checks below
1333
+ // are pure string operations. So this catches I/O, and nothing here is a
1334
+ // deliberate guard around those criteria checks. A path `fs.existsSync` already
1335
+ // rejected never reaches here either (that is the `File not found` branch), so
1336
+ // this is not a claim to catch every way a path can be unusable.
1337
+ try {
1338
+ if (exists) {
1339
+ // A directory is reported as its own kind of failure, distinct from
1340
+ // `File not found`: the path resolved, it simply is not the thing an
1341
+ // artifact entry can be checked against. Verifying a directory (matching
1342
+ // `contains:`/`min_lines:`/`exports:` across the files inside it) is a
1343
+ // feature decision, deliberately not made here.
1344
+ if (node_fs_1.default.statSync(artFullPath).isDirectory()) {
1345
+ check['issues'].push('Not a file: path is a directory');
1278
1346
  }
1347
+ else {
1348
+ // `safeReadFile` returns null on ENOENT, and `|| ''` would turn that
1349
+ // into an empty file — which an entry carrying only `path`/`provides`
1350
+ // would then PASS, having checked nothing. `statSync` just succeeded, so
1351
+ // a null here means the artifact went away mid-check. Report that rather
1352
+ // than inheriting a pass from it. (Pre-existing above this fix, reachable
1353
+ // through the same race after `existsSync`; found in review.)
1354
+ const rawContent = (0, shell_command_projection_cjs_1.platformReadSync)(artFullPath);
1355
+ if (rawContent === null) {
1356
+ check['issues'].push('Unreadable: disappeared during check');
1357
+ results.push(check);
1358
+ continue;
1359
+ }
1360
+ const fileContent = rawContent;
1361
+ const lineCount = fileContent.split('\n').length;
1362
+ if (artifact['min_lines'] && lineCount < artifact['min_lines']) {
1363
+ check['issues'].push(`Only ${lineCount} lines, need ${artifact['min_lines']}`);
1364
+ }
1365
+ if (artifact['contains'] && !fileContent.includes(artifact['contains'])) {
1366
+ check['issues'].push(`Missing pattern: ${artifact['contains']}`);
1367
+ }
1368
+ if (artifact['exports']) {
1369
+ const exports = Array.isArray(artifact['exports'])
1370
+ ? artifact['exports']
1371
+ : [artifact['exports']];
1372
+ for (const exp of exports) {
1373
+ if (!fileContent.includes(exp))
1374
+ check['issues'].push(`Missing export: ${exp}`);
1375
+ }
1376
+ }
1377
+ check['passed'] = check['issues'].length === 0;
1378
+ }
1379
+ }
1380
+ else {
1381
+ check['issues'].push('File not found');
1279
1382
  }
1280
- check['passed'] = check['issues'].length === 0;
1281
1383
  }
1282
- else {
1283
- check['issues'].push('File not found');
1384
+ catch (err) {
1385
+ // Unreadable for some other reason. Record the errno rather than a generic
1386
+ // message — an operator seeing EACCES acts differently from one seeing EIO —
1387
+ // and leave `passed` false.
1388
+ const e = err;
1389
+ check['issues'].push(`Unreadable: ${e.code || (e.message ?? String(err))}`);
1390
+ check['passed'] = false;
1284
1391
  }
1285
1392
  results.push(check);
1286
1393
  }