@opengsd/gsd-core 1.13.0 → 1.15.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (441) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/README.ja-JP.md +3 -3
  4. package/README.ko-KR.md +3 -3
  5. package/README.pt-BR.md +3 -3
  6. package/README.zh-CN.md +3 -3
  7. package/agents/gsd-advisor-researcher.compact.md +85 -0
  8. package/agents/gsd-ai-researcher.compact.md +96 -0
  9. package/agents/gsd-assumptions-analyzer.compact.md +81 -0
  10. package/agents/gsd-code-fixer.compact.md +459 -0
  11. package/agents/gsd-code-fixer.md +9 -8
  12. package/agents/gsd-code-reviewer.compact.md +269 -0
  13. package/agents/gsd-code-reviewer.md +15 -3
  14. package/agents/gsd-codebase-mapper.compact.md +760 -0
  15. package/agents/gsd-debug-session-manager.compact.md +360 -0
  16. package/agents/gsd-debug-session-manager.md +17 -2
  17. package/agents/gsd-debugger.md +2 -2
  18. package/agents/gsd-doc-classifier.compact.md +192 -0
  19. package/agents/gsd-doc-synthesizer.compact.md +200 -0
  20. package/agents/gsd-doc-verifier.compact.md +143 -0
  21. package/agents/gsd-doc-writer.compact.md +440 -0
  22. package/agents/gsd-dom-verifier.compact.md +138 -0
  23. package/agents/gsd-domain-researcher.compact.md +141 -0
  24. package/agents/gsd-eval-auditor.compact.md +160 -0
  25. package/agents/gsd-eval-auditor.md +1 -1
  26. package/agents/gsd-eval-planner.compact.md +137 -0
  27. package/agents/gsd-executor.md +13 -8
  28. package/agents/gsd-framework-selector.compact.md +82 -0
  29. package/agents/gsd-integration-checker.compact.md +245 -0
  30. package/agents/gsd-intel-updater.compact.md +226 -0
  31. package/agents/gsd-intel-updater.md +1 -1
  32. package/agents/gsd-mempalace-curator.compact.md +45 -0
  33. package/agents/gsd-nyquist-auditor.compact.md +179 -0
  34. package/agents/gsd-pattern-mapper.compact.md +275 -0
  35. package/agents/gsd-phase-researcher.md +19 -11
  36. package/agents/gsd-plan-checker.md +8 -7
  37. package/agents/gsd-planner.md +12 -8
  38. package/agents/gsd-project-researcher.compact.md +587 -0
  39. package/agents/gsd-project-researcher.md +1 -1
  40. package/agents/gsd-research-synthesizer.compact.md +212 -0
  41. package/agents/gsd-research-synthesizer.md +1 -1
  42. package/agents/gsd-roadmapper.compact.md +454 -0
  43. package/agents/gsd-roadmapper.md +13 -0
  44. package/agents/gsd-security-auditor.compact.md +162 -0
  45. package/agents/gsd-ui-auditor.compact.md +404 -0
  46. package/agents/gsd-ui-auditor.md +155 -17
  47. package/agents/gsd-ui-checker.compact.md +277 -0
  48. package/agents/gsd-ui-researcher.compact.md +282 -0
  49. package/agents/gsd-ui-researcher.md +1 -1
  50. package/agents/gsd-user-profiler.compact.md +108 -0
  51. package/agents/gsd-verifier.md +10 -9
  52. package/bin/install.js +848 -163
  53. package/commands/gsd/autonomous.md +2 -2
  54. package/commands/gsd/capture.md +1 -1
  55. package/commands/gsd/cleanup.md +1 -0
  56. package/commands/gsd/code-review.md +2 -1
  57. package/commands/gsd/complete-milestone.md +1 -0
  58. package/commands/gsd/config.md +1 -0
  59. package/commands/gsd/debug.md +1 -0
  60. package/commands/gsd/graphify.md +1 -0
  61. package/commands/gsd/health.md +1 -0
  62. package/commands/gsd/mempalace-capture.md +8 -3
  63. package/commands/gsd/mempalace-recall.md +1 -0
  64. package/commands/gsd/new-milestone.md +1 -0
  65. package/commands/gsd/new-project.md +1 -0
  66. package/commands/gsd/next.md +1 -0
  67. package/commands/gsd/pause-work.md +1 -0
  68. package/commands/gsd/phase.md +1 -0
  69. package/commands/gsd/plan-review-convergence.md +6 -6
  70. package/commands/gsd/pr-branch.md +1 -0
  71. package/commands/gsd/progress.md +1 -1
  72. package/commands/gsd/quick-batch.md +1 -1
  73. package/commands/gsd/resume-work.md +1 -0
  74. package/commands/gsd/review-backlog.md +1 -0
  75. package/commands/gsd/review.md +2 -3
  76. package/commands/gsd/settings.md +2 -1
  77. package/commands/gsd/stats.md +1 -0
  78. package/commands/gsd/thread.md +1 -0
  79. package/commands/gsd/workspace.md +1 -0
  80. package/commands/gsd/workstreams.md +1 -0
  81. package/gsd-core/bin/check-latest-version.cjs +8 -3
  82. package/gsd-core/bin/gsd-tools.cjs +672 -146
  83. package/gsd-core/bin/lib/adr-parser.cjs +4 -2
  84. package/gsd-core/bin/lib/artifacts.cjs +2 -1
  85. package/gsd-core/bin/lib/audit.cjs +119 -34
  86. package/gsd-core/bin/lib/broken-windows.cjs +168 -49
  87. package/gsd-core/bin/lib/capability-lifecycle.cjs +10 -6
  88. package/gsd-core/bin/lib/capability-loader.cjs +135 -1
  89. package/gsd-core/bin/lib/capability-registry.cjs +96 -189
  90. package/gsd-core/bin/lib/capability-source.cjs +19 -2
  91. package/gsd-core/bin/lib/capability-validator.cjs +14 -2
  92. package/gsd-core/bin/lib/check-command-router.cjs +213 -49
  93. package/gsd-core/bin/lib/code-review-depth.cjs +2 -2
  94. package/gsd-core/bin/lib/codex-agent-toml.cjs +21 -25
  95. package/gsd-core/bin/lib/commands.cjs +823 -112
  96. package/gsd-core/bin/lib/config-loader.cjs +66 -4
  97. package/gsd-core/bin/lib/config.cjs +186 -45
  98. package/gsd-core/bin/lib/coverage.cjs +1 -1
  99. package/gsd-core/bin/lib/decisions.cjs +164 -45
  100. package/gsd-core/bin/lib/external-descriptor-trust.cjs +29 -14
  101. package/gsd-core/bin/lib/frontmatter.cjs +13 -0
  102. package/gsd-core/bin/lib/graphify.cjs +10 -2
  103. package/gsd-core/bin/lib/gsd2-import.cjs +1 -2
  104. package/gsd-core/bin/lib/health-diagnostic-rules/state-consistency.cjs +12 -1
  105. package/gsd-core/bin/lib/health-diagnostic-rules/worktree-health.cjs +1 -1
  106. package/gsd-core/bin/lib/host-runtime-detection.cjs +9 -0
  107. package/gsd-core/bin/lib/init.cjs +614 -86
  108. package/gsd-core/bin/lib/install-engine.cjs +29 -3
  109. package/gsd-core/bin/lib/install-profiles.cjs +14 -0
  110. package/gsd-core/bin/lib/installer-migrations.cjs +41 -5
  111. package/gsd-core/bin/lib/loop-resolver.cjs +50 -31
  112. package/gsd-core/bin/lib/mcp-catalog.cjs +2 -2
  113. package/gsd-core/bin/lib/milestone.cjs +37 -13
  114. package/gsd-core/bin/lib/model-resolver.cjs +253 -53
  115. package/gsd-core/bin/lib/phase-command-router.cjs +16 -2
  116. package/gsd-core/bin/lib/phase-id-card.cjs +32 -0
  117. package/gsd-core/bin/lib/phase-id-display.cjs +78 -0
  118. package/gsd-core/bin/lib/phase-id.cjs +268 -27
  119. package/gsd-core/bin/lib/phase-lifecycle.cjs +61 -0
  120. package/gsd-core/bin/lib/phase-locator.cjs +29 -10
  121. package/gsd-core/bin/lib/phase.cjs +393 -88
  122. package/gsd-core/bin/lib/plan-document.cjs +49 -1
  123. package/gsd-core/bin/lib/planning-document.cjs +459 -0
  124. package/gsd-core/bin/lib/planning-inspect.cjs +52 -19
  125. package/gsd-core/bin/lib/planning-snapshot.cjs +61 -12
  126. package/gsd-core/bin/lib/planning-workspace.cjs +57 -3
  127. package/gsd-core/bin/lib/pr-branch-patterns.cjs +57 -0
  128. package/gsd-core/bin/lib/pristine-baseline.cjs +182 -0
  129. package/gsd-core/bin/lib/probe-core.cjs +7 -1
  130. package/gsd-core/bin/lib/prohibition-enforcement.cjs +91 -4
  131. package/gsd-core/bin/lib/project-root.cjs +41 -2
  132. package/gsd-core/bin/lib/quick-batch.cjs +1 -1
  133. package/gsd-core/bin/lib/refactor-trigger-command-router.cjs +61 -2
  134. package/gsd-core/bin/lib/research-store.cjs +11 -12
  135. package/gsd-core/bin/lib/review-lane-descriptor.cjs +10 -30
  136. package/gsd-core/bin/lib/review-lane-invocation.cjs +23 -0
  137. package/gsd-core/bin/lib/review-reviewer-selection.cjs +2 -2
  138. package/gsd-core/bin/lib/reviewer-step-dispatch.cjs +337 -0
  139. package/gsd-core/bin/lib/roadmap-command-router.cjs +12 -4
  140. package/gsd-core/bin/lib/roadmap-parser.cjs +219 -18
  141. package/gsd-core/bin/lib/roadmap-upgrade.cjs +1539 -13
  142. package/gsd-core/bin/lib/roadmap.cjs +356 -42
  143. package/gsd-core/bin/lib/runtime-artifact-conversion.cjs +310 -41
  144. package/gsd-core/bin/lib/runtime-artifact-install-plan.cjs +15 -4
  145. package/gsd-core/bin/lib/runtime-artifact-layout.cjs +13 -5
  146. package/gsd-core/bin/lib/runtime-homes.cjs +4 -0
  147. package/gsd-core/bin/lib/runtime-hooks-surface.cjs +408 -37
  148. package/gsd-core/bin/lib/runtime-name-policy.cjs +111 -1
  149. package/gsd-core/bin/lib/security.cjs +126 -7
  150. package/gsd-core/bin/lib/shell-command-projection.cjs +10 -6
  151. package/gsd-core/bin/lib/state-document.cjs +130 -28
  152. package/gsd-core/bin/lib/state-md-schema.cjs +21 -14
  153. package/gsd-core/bin/lib/state-transition.cjs +181 -30
  154. package/gsd-core/bin/lib/state.cjs +265 -27
  155. package/gsd-core/bin/lib/surface.cjs +77 -3
  156. package/gsd-core/bin/lib/task-command-router.cjs +12 -6
  157. package/gsd-core/bin/lib/tdd-red-evidence.cjs +78 -5
  158. package/gsd-core/bin/lib/uat-predicate.cjs +47 -4
  159. package/gsd-core/bin/lib/uat.cjs +9 -1
  160. package/gsd-core/bin/lib/ui-consideration-probe.cjs +15 -2
  161. package/gsd-core/bin/lib/ui-frontend-evidence.cjs +100 -9
  162. package/gsd-core/bin/lib/undo-commit-selection.cjs +131 -0
  163. package/gsd-core/bin/lib/update-context.cjs +30 -24
  164. package/gsd-core/bin/lib/vendor/js-yaml.cjs +11 -3
  165. package/gsd-core/bin/lib/verification.cjs +315 -30
  166. package/gsd-core/bin/lib/verify-command-grounding.cjs +47 -3
  167. package/gsd-core/bin/lib/verify.cjs +320 -48
  168. package/gsd-core/bin/lib/workstream-inventory.cjs +1 -0
  169. package/gsd-core/bin/lib/worktree-base-ref.cjs +482 -73
  170. package/gsd-core/bin/lib/worktree-safety.cjs +797 -58
  171. package/gsd-core/bin/shared/config-defaults.manifest.json +4 -0
  172. package/gsd-core/bin/shared/config-schema.manifest.json +6 -0
  173. package/gsd-core/bin/verify-reapply-patches.cjs +439 -80
  174. package/gsd-core/references/checkpoints.md +5 -3
  175. package/gsd-core/references/compact-content-gate.md +66 -0
  176. package/gsd-core/references/edge-probe-fixtures/01-round-half-even/expected-coverage.json +28 -3
  177. package/gsd-core/references/edge-probe-fixtures/02-merge-intervals/expected-coverage.json +37 -4
  178. package/gsd-core/references/edge-probe-fixtures/03-truncate-graphemes/expected-coverage.json +28 -3
  179. package/gsd-core/references/edge-probe-fixtures/04-money-rounding/expected-coverage.json +28 -3
  180. package/gsd-core/references/edge-probe-fixtures/05-list-dedupe/expected-coverage.json +37 -4
  181. package/gsd-core/references/edge-probe-fixtures/06-resolved-mixed/expected-coverage.json +37 -4
  182. package/gsd-core/references/edge-probe.md +195 -21
  183. package/gsd-core/references/execute-phase-between-wave-reset.md +7 -6
  184. package/gsd-core/references/execute-phase-wave-guard.md +22 -11
  185. package/gsd-core/references/gsd-run-resolver.md +1 -1
  186. package/gsd-core/references/loop-hook-dispatch.md +18 -0
  187. package/gsd-core/references/model-profiles.md +13 -4
  188. package/gsd-core/references/phase-argument-parsing.md +9 -7
  189. package/gsd-core/references/phase-id-convention.md +28 -0
  190. package/gsd-core/references/planner-gap-closure.md +2 -0
  191. package/gsd-core/references/planner-load-graph-context.md +24 -13
  192. package/gsd-core/references/planner-verify-command-grounding.md +14 -0
  193. package/gsd-core/references/planning-config.md +14 -2
  194. package/gsd-core/references/tdd.md +30 -4
  195. package/gsd-core/references/thinking-models-planning.md +18 -2
  196. package/gsd-core/references/ui-consideration-probe.md +10 -5
  197. package/gsd-core/references/verification-patterns.md +17 -4
  198. package/gsd-core/references/verify-command-path-resolvability.md +10 -2
  199. package/gsd-core/references/worktree-path-safety.md +433 -2
  200. package/gsd-core/templates/README.md +7 -1
  201. package/gsd-core/templates/state.md +6 -3
  202. package/gsd-core/templates/summary.compact.md +212 -0
  203. package/gsd-core/templates/user-setup.compact.md +199 -0
  204. package/gsd-core/templates/user-setup.md +0 -9
  205. package/gsd-core/templates/verification-report.md +1 -1
  206. package/gsd-core/workflows/_runtime-launcher.snippet.sh +1 -1
  207. package/gsd-core/workflows/add-backlog.md +1 -1
  208. package/gsd-core/workflows/add-phase.md +1 -1
  209. package/gsd-core/workflows/add-tests.md +2 -2
  210. package/gsd-core/workflows/add-todo.md +6 -5
  211. package/gsd-core/workflows/ai-integration-phase.md +11 -3
  212. package/gsd-core/workflows/audit-fix.md +1 -1
  213. package/gsd-core/workflows/audit-milestone.md +1 -1
  214. package/gsd-core/workflows/audit-uat.md +1 -1
  215. package/gsd-core/workflows/autonomous/steps/converge-fail-fast.md +9 -18
  216. package/gsd-core/workflows/autonomous.md +29 -16
  217. package/gsd-core/workflows/check-todos.md +6 -4
  218. package/gsd-core/workflows/cleanup.md +5 -3
  219. package/gsd-core/workflows/code-review/steps/dispatch-fix.md +4 -3
  220. package/gsd-core/workflows/code-review/steps/structural-pre-pass.md +8 -1
  221. package/gsd-core/workflows/code-review-fix.md +108 -22
  222. package/gsd-core/workflows/code-review.md +216 -73
  223. package/gsd-core/workflows/complete-milestone/detail/elaboration.md +274 -0
  224. package/gsd-core/workflows/complete-milestone.md +41 -264
  225. package/gsd-core/workflows/debug.md +3 -3
  226. package/gsd-core/workflows/diagnose-issues.md +1 -1
  227. package/gsd-core/workflows/discuss-phase/modes/advisor.md +1 -1
  228. package/gsd-core/workflows/discuss-phase/modes/chain.md +1 -1
  229. package/gsd-core/workflows/discuss-phase-assumptions.md +1 -1
  230. package/gsd-core/workflows/discuss-phase.md +1 -1
  231. package/gsd-core/workflows/do.md +2 -2
  232. package/gsd-core/workflows/docs-update/detail/elaboration.md +179 -0
  233. package/gsd-core/workflows/docs-update.md +17 -158
  234. package/gsd-core/workflows/edit-phase.md +1 -1
  235. package/gsd-core/workflows/eval-review.md +10 -3
  236. package/gsd-core/workflows/execute-phase/detail/elaboration.md +124 -0
  237. package/gsd-core/workflows/execute-phase/steps/code-review-disposition.md +1017 -0
  238. package/gsd-core/workflows/execute-phase/steps/codebase-drift-gate.md +19 -4
  239. package/gsd-core/workflows/execute-phase/steps/completion-reconciliation.md +56 -0
  240. package/gsd-core/workflows/execute-phase/steps/executor-isolation-dispatch.md +43 -4
  241. package/gsd-core/workflows/execute-phase/steps/executor-progress-policy.md +43 -0
  242. package/gsd-core/workflows/execute-phase/steps/gap-closure-artifacts.md +1 -1
  243. package/gsd-core/workflows/execute-phase/steps/partial-wave.md +1 -1
  244. package/gsd-core/workflows/execute-phase/steps/per-plan-executor-routing.md +1 -1
  245. package/gsd-core/workflows/execute-phase/steps/per-plan-worktree-gate.md +1 -1
  246. package/gsd-core/workflows/execute-phase/steps/post-merge-gate.md +46 -9
  247. package/gsd-core/workflows/execute-phase/steps/protected-branch.md +1 -1
  248. package/gsd-core/workflows/execute-phase/steps/ready-wave-gate.md +37 -0
  249. package/gsd-core/workflows/execute-phase/steps/regression-gate-run.md +1 -1
  250. package/gsd-core/workflows/execute-phase/steps/sequential-root-pin.md +35 -0
  251. package/gsd-core/workflows/execute-phase/steps/stale-reverification.md +24 -0
  252. package/gsd-core/workflows/execute-phase/steps/tdd-applicability-resolution.md +1 -1
  253. package/gsd-core/workflows/execute-phase/steps/threat-id-gate.md +28 -0
  254. package/gsd-core/workflows/execute-phase/steps/wave-post-gate-hooks.md +1 -1
  255. package/gsd-core/workflows/execute-phase/steps/worktree-base-check.md +25 -0
  256. package/gsd-core/workflows/execute-phase.md +83 -172
  257. package/gsd-core/workflows/execute-plan.md +24 -10
  258. package/gsd-core/workflows/explore.md +3 -3
  259. package/gsd-core/workflows/extract-learnings.md +2 -1
  260. package/gsd-core/workflows/fast.md +1 -1
  261. package/gsd-core/workflows/forensics.md +1 -1
  262. package/gsd-core/workflows/graduation.md +1 -1
  263. package/gsd-core/workflows/health.md +2 -2
  264. package/gsd-core/workflows/help/modes/full.compact.md +398 -0
  265. package/gsd-core/workflows/help/modes/full.md +5 -5
  266. package/gsd-core/workflows/help/modes/topic.md +15 -5
  267. package/gsd-core/workflows/help.md +1 -1
  268. package/gsd-core/workflows/import.md +2 -2
  269. package/gsd-core/workflows/inbox.md +2 -2
  270. package/gsd-core/workflows/ingest-docs.md +3 -3
  271. package/gsd-core/workflows/insert-phase.md +1 -1
  272. package/gsd-core/workflows/list-seeds.md +1 -1
  273. package/gsd-core/workflows/list-workspaces.md +1 -1
  274. package/gsd-core/workflows/manager.md +2 -2
  275. package/gsd-core/workflows/map-codebase.md +52 -5
  276. package/gsd-core/workflows/milestone-summary.md +1 -1
  277. package/gsd-core/workflows/mvp-phase.md +1 -1
  278. package/gsd-core/workflows/new-milestone.md +56 -14
  279. package/gsd-core/workflows/new-project/detail/elaboration.md +216 -0
  280. package/gsd-core/workflows/new-project/steps/auto-mode-config.md +3 -3
  281. package/gsd-core/workflows/new-project/steps/codebase-map-offer.md +1 -1
  282. package/gsd-core/workflows/new-project.md +39 -209
  283. package/gsd-core/workflows/new-workspace.md +2 -2
  284. package/gsd-core/workflows/next.md +1 -1
  285. package/gsd-core/workflows/note.md +1 -1
  286. package/gsd-core/workflows/onboard.md +1 -1
  287. package/gsd-core/workflows/pause-work.md +1 -1
  288. package/gsd-core/workflows/plan-phase/detail/elaboration.md +209 -0
  289. package/gsd-core/workflows/plan-phase/steps/chunked-planning-mode.md +17 -5
  290. package/gsd-core/workflows/plan-phase/steps/prd-express-path.md +1 -1
  291. package/gsd-core/workflows/plan-phase/steps/stall-detection-helpers.md +23 -5
  292. package/gsd-core/workflows/plan-phase.md +45 -187
  293. package/gsd-core/workflows/plan-review-convergence.md +21 -5
  294. package/gsd-core/workflows/plant-seed.md +62 -20
  295. package/gsd-core/workflows/pr-branch.md +132 -20
  296. package/gsd-core/workflows/profile-user.md +2 -2
  297. package/gsd-core/workflows/progress.md +1 -1
  298. package/gsd-core/workflows/quick/steps/plan-checker-loop.md +25 -0
  299. package/gsd-core/workflows/quick/steps/quick-verification.md +1 -1
  300. package/gsd-core/workflows/quick/steps/worktree-pre-dispatch-commit.md +1 -1
  301. package/gsd-core/workflows/quick-batch/steps/batch-init.md +1 -1
  302. package/gsd-core/workflows/quick-batch/steps/completion.md +1 -1
  303. package/gsd-core/workflows/quick-batch/steps/merge-wave.md +1 -1
  304. package/gsd-core/workflows/quick-batch/steps/planner-wave.md +1 -1
  305. package/gsd-core/workflows/quick-batch/steps/research-phase.md +1 -1
  306. package/gsd-core/workflows/quick-batch/steps/resume-mode.md +1 -1
  307. package/gsd-core/workflows/quick-batch/steps/verification-wave.md +1 -1
  308. package/gsd-core/workflows/quick-batch/steps/worktree-dispatch.md +1 -1
  309. package/gsd-core/workflows/quick-batch.md +1 -1
  310. package/gsd-core/workflows/quick.md +29 -10
  311. package/gsd-core/workflows/reapply-patches.md +86 -6
  312. package/gsd-core/workflows/remove-phase.md +1 -1
  313. package/gsd-core/workflows/remove-workspace.md +2 -2
  314. package/gsd-core/workflows/resume-project.md +1 -1
  315. package/gsd-core/workflows/review.md +31 -16
  316. package/gsd-core/workflows/scan.md +1 -1
  317. package/gsd-core/workflows/secure-phase.md +3 -2
  318. package/gsd-core/workflows/settings-advanced.md +30 -10
  319. package/gsd-core/workflows/settings-integrations.md +2 -3
  320. package/gsd-core/workflows/settings.md +22 -9
  321. package/gsd-core/workflows/ship.md +3 -2
  322. package/gsd-core/workflows/sketch-wrap-up.md +1 -1
  323. package/gsd-core/workflows/sketch.md +1 -1
  324. package/gsd-core/workflows/smart-entry.md +2 -2
  325. package/gsd-core/workflows/spec-phase.md +15 -5
  326. package/gsd-core/workflows/spike-wrap-up.md +1 -1
  327. package/gsd-core/workflows/spike.md +1 -1
  328. package/gsd-core/workflows/stats.md +1 -1
  329. package/gsd-core/workflows/sync-skills.md +5 -5
  330. package/gsd-core/workflows/thread.md +1 -1
  331. package/gsd-core/workflows/transition.md +1 -1
  332. package/gsd-core/workflows/ui-phase.md +44 -8
  333. package/gsd-core/workflows/ui-review.md +18 -4
  334. package/gsd-core/workflows/ultraplan-phase.md +1 -1
  335. package/gsd-core/workflows/undo.md +339 -20
  336. package/gsd-core/workflows/update.md +14 -12
  337. package/gsd-core/workflows/validate-phase.md +3 -2
  338. package/gsd-core/workflows/verify-work/detail/elaboration.md +230 -0
  339. package/gsd-core/workflows/verify-work/steps/automated-ui-verification.md +1 -1
  340. package/gsd-core/workflows/verify-work/steps/mvp-uat-framing.md +1 -1
  341. package/gsd-core/workflows/verify-work.md +101 -196
  342. package/hooks/dist/gsd-agent-isolation-guard.js +66 -16
  343. package/hooks/dist/gsd-context-monitor.js +88 -15
  344. package/hooks/dist/gsd-cursor-subagent-start.js +34 -14
  345. package/hooks/dist/gsd-secret-read-guard.js +71 -19
  346. package/hooks/dist/gsd-statusline.js +81 -20
  347. package/hooks/dist/gsd-validate-commit.sh +97 -8
  348. package/hooks/dist/gsd-worktree-path-guard.js +25 -14
  349. package/hooks/dist/gsd-write-guard.js +46 -1
  350. package/hooks/dist/lib/dispatch-identity.js +187 -0
  351. package/hooks/dist/lib/filename-classification.js +64 -0
  352. package/hooks/dist/lib/isolation-deny-reason.js +53 -1
  353. package/hooks/dist/lib/isolation-sentinel.js +58 -19
  354. package/hooks/gsd-agent-isolation-guard.js +66 -16
  355. package/hooks/gsd-context-monitor.js +88 -15
  356. package/hooks/gsd-cursor-subagent-start.js +34 -14
  357. package/hooks/gsd-secret-read-guard.js +71 -19
  358. package/hooks/gsd-statusline.js +81 -20
  359. package/hooks/gsd-validate-commit.sh +97 -8
  360. package/hooks/gsd-worktree-path-guard.js +25 -14
  361. package/hooks/gsd-write-guard.js +46 -1
  362. package/hooks/lib/dispatch-identity.js +187 -0
  363. package/hooks/lib/filename-classification.js +64 -0
  364. package/hooks/lib/isolation-deny-reason.js +53 -1
  365. package/hooks/lib/isolation-sentinel.js +58 -19
  366. package/package.json +11 -6
  367. package/scripts/benchmark-compact-content-variants.cjs +298 -0
  368. package/scripts/benchmark-compact-content.cjs +368 -0
  369. package/scripts/build-hooks.js +15 -6
  370. package/scripts/check-contract-drift.cjs +131 -12
  371. package/scripts/check-env.cjs +36 -8
  372. package/scripts/check-glossary-refs.cjs +25 -21
  373. package/scripts/ci-next-health.cjs +271 -0
  374. package/scripts/ci-prepare-test-scope.cjs +7 -7
  375. package/scripts/ci-test-scope.cjs +126 -20
  376. package/scripts/ci-timeout-report.cjs +1 -1
  377. package/scripts/command-contract-helpers.cjs +3 -0
  378. package/scripts/diff-touches-shipped-paths.cjs +1 -1
  379. package/scripts/docs-guard-registry.cjs +35 -2
  380. package/scripts/gen-adr-index.cjs +8 -2
  381. package/scripts/gen-inventory-manifest.cjs +12 -0
  382. package/scripts/gen-loop-host-contract.cjs +69 -0
  383. package/scripts/gen-platform-conformance-tier.cjs +557 -0
  384. package/scripts/lib/drift-scan.cjs +1 -1
  385. package/scripts/lib/macos-conformance-tier.generated.cjs +224 -0
  386. package/scripts/lib/ndjson-reporter.cjs +3 -2
  387. package/scripts/lib/npm-version-check-diagnosis.cjs +59 -0
  388. package/scripts/lib/platform-conformance-tier.generated.cjs +287 -0
  389. package/scripts/lib/suite-detection.cjs +32 -0
  390. package/scripts/lint-allowed-tools-parity.cjs +221 -0
  391. package/scripts/lint-docs-guard-registration.exempt-baseline.cjs +47 -3
  392. package/scripts/lint-phase-arg-assignment.cjs +257 -0
  393. package/scripts/lint-phase-id-drift.cjs +623 -13
  394. package/scripts/lint-pr-branch-pattern-drift.cjs +148 -0
  395. package/scripts/lint-response-language-coverage.cjs +9 -3
  396. package/scripts/lint-retired-runtime-name.cjs +619 -0
  397. package/scripts/lint-source-test-name-collision.cjs +1 -1
  398. package/scripts/lint-state-write-path-drift.cjs +93 -0
  399. package/scripts/lint-test-file-count.allowlist.json +29 -9
  400. package/scripts/lint-vendored-deps.cjs +128 -17
  401. package/scripts/lint-workflow-shellcheck-baseline.json +100 -0
  402. package/scripts/prompt-injection-scan.sh +18 -0
  403. package/scripts/release-tarball-smoke.cjs +194 -1
  404. package/scripts/workflow-size.cjs +139 -0
  405. package/skills/gsd-autonomous/SKILL.md +2 -2
  406. package/skills/gsd-capture/SKILL.md +1 -1
  407. package/skills/gsd-cleanup/SKILL.md +1 -0
  408. package/skills/gsd-code-review/SKILL.md +2 -1
  409. package/skills/gsd-complete-milestone/SKILL.md +1 -0
  410. package/skills/gsd-config/SKILL.md +1 -0
  411. package/skills/gsd-debug/SKILL.md +1 -0
  412. package/skills/gsd-graphify/SKILL.md +1 -0
  413. package/skills/gsd-health/SKILL.md +1 -0
  414. package/skills/gsd-mempalace-capture/SKILL.md +8 -3
  415. package/skills/gsd-mempalace-recall/SKILL.md +1 -0
  416. package/skills/gsd-new-milestone/SKILL.md +1 -0
  417. package/skills/gsd-new-project/SKILL.md +1 -0
  418. package/skills/gsd-next/SKILL.md +1 -0
  419. package/skills/gsd-pause-work/SKILL.md +1 -0
  420. package/skills/gsd-phase/SKILL.md +1 -0
  421. package/skills/gsd-plan-review-convergence/SKILL.md +5 -5
  422. package/skills/gsd-pr-branch/SKILL.md +1 -0
  423. package/skills/gsd-progress/SKILL.md +1 -1
  424. package/skills/gsd-quick-batch/SKILL.md +1 -1
  425. package/skills/gsd-resume-work/SKILL.md +1 -0
  426. package/skills/gsd-review/SKILL.md +2 -3
  427. package/skills/gsd-review-backlog/SKILL.md +1 -0
  428. package/skills/gsd-settings/SKILL.md +2 -1
  429. package/skills/gsd-stats/SKILL.md +1 -0
  430. package/skills/gsd-thread/SKILL.md +1 -0
  431. package/skills/gsd-workspace/SKILL.md +1 -0
  432. package/skills/gsd-workstreams/SKILL.md +1 -0
  433. package/vscode/package.json +1 -1
  434. package/gsd-core/templates/claude-md.md +0 -145
  435. package/gsd-core/templates/codebase/concerns.md +0 -310
  436. package/gsd-core/templates/codebase/conventions.md +0 -307
  437. package/gsd-core/templates/codebase/integrations.md +0 -280
  438. package/gsd-core/templates/codebase/structure.md +0 -285
  439. package/gsd-core/templates/codebase/testing.md +0 -480
  440. package/gsd-core/templates/debug-subagent-prompt.md +0 -91
  441. package/gsd-core/templates/discovery.md +0 -146
@@ -34,6 +34,52 @@ const prohibition_enforcement_cjs_1 = require("./prohibition-enforcement.cjs");
34
34
  function baseOf(p) {
35
35
  return typeof p === 'string' ? (p.split(/[\\/]/).pop() ?? p) : '';
36
36
  }
37
+ /**
38
+ * #4724 — Surefire/Failsafe XML summary parsed by TAG-BOUNDARY scanning, NOT
39
+ * by a lazy `(.*?)<\/testcase>` regex: Surefire writes PASSING cases as
40
+ * self-closing `<testcase … />` elements, so a lazy span from a green case to
41
+ * the next closing tag swallows every case between and misreports names beside
42
+ * the right failure message. The scanner walks `<testcase` start tags; each
43
+ * case's extent is its own tag (self-closing) or the segment up to its
44
+ * `</testcase>` closer (testcases never nest). A `<failure` or `<error` child
45
+ * marks the case failing; the reported name is `classname#name`. Any parse
46
+ * anomaly degrades to "case not failing" — the module stays fail-closed.
47
+ */
48
+ function parseSurefireSummary(output) {
49
+ let tests = 0;
50
+ let fail = 0;
51
+ const failing_tests = [];
52
+ let idx = output.indexOf('<testcase');
53
+ while (idx !== -1) {
54
+ const tagEnd = output.indexOf('>', idx);
55
+ // #4724 review (HIGH): a `<testcase` with no `>` (truncated/stream-cut
56
+ // output) must terminate the scan — resuming the search at -1 clamps to 0
57
+ // and re-finds the same tag forever. Degrade to what was scanned so far
58
+ // (fail-closed: an incomplete report proves nothing).
59
+ if (tagEnd === -1)
60
+ break;
61
+ tests++;
62
+ const tagText = output.slice(idx, tagEnd + 1);
63
+ // Self-closing (`/>`): the case has NO body — a failing sibling after it
64
+ // must never be attributed to this case (the #4724 spanning trap).
65
+ const selfClosing = output[tagEnd - 1] === '/';
66
+ const closeIdx = selfClosing ? -1 : output.indexOf('</testcase>', tagEnd);
67
+ // CDATA sections are verbatim captured output (e.g. System.out echoing
68
+ // test source) — their content must never be scanned for failure tags
69
+ // (#4724 review: CDATA phantom failures).
70
+ const body = (selfClosing
71
+ ? ''
72
+ : output.slice(tagEnd + 1, closeIdx === -1 ? output.length : closeIdx)).replace(/<!\[CDATA\[[\s\S]*?\]\]>/g, '');
73
+ const name = /name="([^"]*)"/.exec(tagText)?.[1] ?? '';
74
+ const classname = /classname="([^"]*)"/.exec(tagText)?.[1] ?? '';
75
+ if (body.includes('<failure') || body.includes('<error')) {
76
+ fail++;
77
+ failing_tests.push(classname ? `${classname}#${name}` : name);
78
+ }
79
+ idx = output.indexOf('<testcase', closeIdx === -1 ? tagEnd : closeIdx);
80
+ }
81
+ return { tests, pass: tests - fail, fail, failing_tests };
82
+ }
37
83
  /** Coerce and validate the raw record's scalar fields. Returns null exit_code only when absent/non-numeric. */
38
84
  function readInput(input) {
39
85
  const command = typeof input?.command === 'string' ? input.command : '';
@@ -69,8 +115,34 @@ function classifyRedEvidence(input) {
69
115
  };
70
116
  }
71
117
  const { command, exitCode, output, targetTest } = parsed;
72
- const summary = (0, prohibition_enforcement_cjs_1.parseNodeTestSummary)(output);
73
- const failing = (0, prohibition_enforcement_cjs_1.tapFailedTestNames)(output);
118
+ // #4724: format detection. Surefire/Failsafe XML (a `<testsuite>` element) is
119
+ // parsed by tag-boundary scanning; everything else stays on the proven TAP
120
+ // primitives. Unknown formats keep the TAP parse — which finds nothing — and
121
+ // fail closed below.
122
+ // #4724 review: BOTH element names must appear — a TAP error message that
123
+ // merely quotes "<testsuite>" (e.g. "expected <testsuite> was 2") must not
124
+ // flip a genuine TAP red into the XML path.
125
+ const isSurefireXml = output.includes('<testsuite') && output.includes('<testcase');
126
+ let summary;
127
+ let failing;
128
+ if (isSurefireXml) {
129
+ const sf = parseSurefireSummary(output);
130
+ summary = { tests: sf.tests, pass: sf.pass, fail: sf.fail };
131
+ failing = sf.failing_tests;
132
+ }
133
+ else {
134
+ summary = (0, prohibition_enforcement_cjs_1.parseNodeTestSummary)(output);
135
+ failing = (0, prohibition_enforcement_cjs_1.tapFailedTestNames)(output);
136
+ }
137
+ // Surefire target matching is class-level (the plan names the class); the
138
+ // entry's last classname segment or its test name must carry the target.
139
+ const surefireTargetMatches = isSurefireXml
140
+ ? failing.some((entry) => {
141
+ const cls = entry.split('#')[0] ?? '';
142
+ const nm = entry.split('#')[1] ?? '';
143
+ return cls === targetTest || cls.endsWith(`.${targetTest}`) || nm === targetTest;
144
+ })
145
+ : failing.includes(targetTest);
74
146
  const evidence = {
75
147
  command,
76
148
  exit_code: exitCode,
@@ -90,14 +162,15 @@ function classifyRedEvidence(input) {
90
162
  if (summary.tests === 0) {
91
163
  return { verdict: 'INVALID_RED', reason: 'zero_tests_discovered', evidence };
92
164
  }
93
- // Nonzero exit but TAP reports no failing test: harness/setup/parser crash
94
- // whose failure never reached a test assertion (or unparseable output).
165
+ // Nonzero exit but the report shows no failing test: harness/setup/parser
166
+ // crash whose failure never reached a test assertion (or unparseable output).
95
167
  if (summary.fail === 0 || failing.length === 0) {
96
168
  return { verdict: 'INVALID_RED', reason: 'nonzero_exit_without_test_failure', evidence };
97
169
  }
98
170
  // Fixture/load failure: every failing entry is named like the target FILE —
99
171
  // node reports a load-time crash (throw-on-require, syntax error, ENOENT
100
172
  // fixture) as a file-named `not ok 1 - <file>`, never the target test.
173
+ // (TAP-only shape: Surefire failures carry class#method names.)
101
174
  const targetBase = baseOf(input?.targetFile ?? '');
102
175
  const distinctlyNamed = failing.filter((n) => (targetBase ? baseOf(n) !== targetBase : true));
103
176
  if (distinctlyNamed.length === 0) {
@@ -105,7 +178,7 @@ function classifyRedEvidence(input) {
105
178
  }
106
179
  // Unrelated failure: real tests ran and failed, but none is the target test
107
180
  // the plan named — an unrelated assertion must not authorize GREEN.
108
- if (!distinctlyNamed.includes(targetTest)) {
181
+ if (!(isSurefireXml ? surefireTargetMatches : distinctlyNamed.includes(targetTest))) {
109
182
  return { verdict: 'INVALID_RED', reason: 'no_target_test_failure', evidence };
110
183
  }
111
184
  return { verdict: 'RED_EVIDENCE_OK', reason: 'target_test_failed', evidence };
@@ -47,6 +47,22 @@ const BLOCKING_VERIFICATION_FM_STATUSES = new Set([
47
47
  ]);
48
48
  // UAT test-item `result` values that count as passing
49
49
  const PASSING_RESULTS = new Set(['passed', 'pass']);
50
+ // #4546 — a `skipped` test-item whose reason carries the verify-work writer's
51
+ // deferral template prefix ("Deferred follow-up: …", #1921) is a deliberately
52
+ // deferred follow-up: non-blocking. Quote-tolerant (the writer wraps the value
53
+ // in double quotes) and case-insensitive (human-edited files vary). Anything
54
+ // else — a reasonless skip, a non-deferral reason — still blocks. PARITY: this
55
+ // matcher and the writer template in gsd-core/workflows/verify-work.md
56
+ // (process_response) are two halves of one contract, pinned together by
57
+ // tests/verify-work-deferred-promotion.test.cjs.
58
+ const DEFERRED_REASON_RE = /^["']?deferred follow-up\b/i;
59
+ // Trust note (#4546 review): the reason line is user-authored state — an
60
+ // author could equally write `result: passed` — so this prefix is an
61
+ // AUTHORING contract with the verify-work writer, not a security boundary.
62
+ // A hand-written deferral that skips the UAT file's ## Deferred Follow-Ups
63
+ // section also bypasses the complete_session promotion offer; the section is
64
+ // the durable project-level record. Variant spellings that do not match
65
+ // ("Deferred follow-ups:", "followup") block — fail-closed by design.
50
66
  // ─── stripFalsePositiveContexts ───────────────────────────────────────────────
51
67
  /**
52
68
  * Remove contexts that can contain `result: ...` lines that are NOT real test results:
@@ -166,16 +182,28 @@ function parseUatResultItems(cleanContent) {
166
182
  // ambiguity counting, matching src/uat.cts's contract.
167
183
  // Uses [ \t]* (not \s*) so the captured value must sit on the SAME line as result:.
168
184
  // A result: key with the value on a subsequent line yields no match → 'missing' (blocker).
185
+ // #4546: the `reason:` line is captured with the same frame and FIRST-match
186
+ // rule — it is the deferral signal the evaluator needs (a `skipped` item
187
+ // whose reason is the verify-work writer's "Deferred follow-up:" template
188
+ // is non-blocking). Quoted values are captured with their quotes so the
189
+ // evaluator's matcher can tolerate them exactly as written.
169
190
  const RESULT_LINE_RE = /^result:[ \t]*\[?([\w-]+)\]?/i;
170
191
  const resultMatch = blockContent
171
192
  .split('\n')
172
193
  .map((line) => line.match(RESULT_LINE_RE))
173
194
  .find((m) => m !== null) ?? null;
195
+ const REASON_LINE_RE = /^reason:[ \t]*(.*)$/i;
196
+ const reasonMatch = blockContent
197
+ .split('\n')
198
+ .map((line) => line.match(REASON_LINE_RE))
199
+ .find((m) => m !== null) ?? null;
200
+ const reason = reasonMatch ? reasonMatch[1].trim() : '';
174
201
  if (resultMatch) {
175
202
  items.push({
176
203
  test: h.test,
177
204
  name: h.name,
178
205
  result: resultMatch[1].toLowerCase(),
206
+ reason,
179
207
  });
180
208
  }
181
209
  else {
@@ -184,6 +212,7 @@ function parseUatResultItems(cleanContent) {
184
212
  test: h.test,
185
213
  name: h.name,
186
214
  result: 'missing',
215
+ reason,
187
216
  });
188
217
  }
189
218
  }
@@ -198,7 +227,13 @@ function parseUatResultItems(cleanContent) {
198
227
  * Pass requires at least one real passing check AND no blockers.
199
228
  */
200
229
  function evaluateUatPassed(phaseFullDir, opts) {
201
- const requireVerification = opts?.policy?.requireVerification === true;
230
+ // uatOnly (#4663) takes precedence: it evaluates the UAT rows ONLY, skipping
231
+ // the VERIFICATION-file blockers entirely. The verify-work canonicalize
232
+ // pre-check needs exactly that — it runs while the report still reads
233
+ // `human_needed`, which is itself a blocking verification status, so the
234
+ // full predicate could never pass there and the flip would deadlock.
235
+ const uatOnly = opts?.policy?.uatOnly === true;
236
+ const requireVerification = !uatOnly && opts?.policy?.requireVerification === true;
202
237
  const blockers = [];
203
238
  const checks = [];
204
239
  const uatFiles = [];
@@ -221,7 +256,7 @@ function evaluateUatPassed(phaseFullDir, opts) {
221
256
  checks: [],
222
257
  blockers,
223
258
  no_uat_artifacts,
224
- policy: { require_verification: requireVerification },
259
+ policy: { require_verification: requireVerification, uat_only: uatOnly },
225
260
  // readVerificationStatus was never reached on this early-return path.
226
261
  verification_stale_check_indeterminate: false,
227
262
  };
@@ -269,13 +304,20 @@ function evaluateUatPassed(phaseFullDir, opts) {
269
304
  const cleanContent = stripFalsePositiveContexts(raw);
270
305
  const items = parseUatResultItems(cleanContent);
271
306
  for (const item of items) {
272
- const passing = PASSING_RESULTS.has(item.result);
307
+ // #4546: a `skipped` item whose reason matches the verify-work writer's
308
+ // "Deferred follow-up:" template is a deliberately deferred follow-up
309
+ // (#1921) — non-blocking. Quote-tolerant because the writer emits the
310
+ // reason WITH its wrapping quotes. Everything else — pending, blocked,
311
+ // issue, missing, and a plain or non-deferral skipped — still blocks.
312
+ const deferred = item.result === 'skipped' && DEFERRED_REASON_RE.test(item.reason);
313
+ const passing = PASSING_RESULTS.has(item.result) || deferred;
273
314
  checks.push({
274
315
  file,
275
316
  test: item.test,
276
317
  name: item.name,
277
318
  result: item.result,
278
319
  passing,
320
+ deferred,
279
321
  });
280
322
  if (!passing) {
281
323
  blockers.push(`${file}: test ${item.test} (${item.result})`);
@@ -284,7 +326,7 @@ function evaluateUatPassed(phaseFullDir, opts) {
284
326
  }
285
327
  // ── Process VERIFICATION files ─────────────────────────────────────────────
286
328
  let hasPassingVerification = false;
287
- for (const file of verFileNames) {
329
+ for (const file of uatOnly ? [] : verFileNames) {
288
330
  verificationFiles.push(file);
289
331
  const verificationFilePath = node_path_1.default.join(phaseFullDir, file);
290
332
  let raw = '';
@@ -340,6 +382,7 @@ function evaluateUatPassed(phaseFullDir, opts) {
340
382
  no_uat_artifacts,
341
383
  policy: {
342
384
  require_verification: requireVerification,
385
+ uat_only: uatOnly,
343
386
  },
344
387
  verification_stale_check_indeterminate: verificationStaleCheckIndeterminate,
345
388
  };
@@ -322,7 +322,7 @@ function cmdRenderCheckpoint(cwd, options = {}, raw) {
322
322
  if (!filePath) {
323
323
  error('UAT file required: use uat render-checkpoint --file <path>');
324
324
  }
325
- const resolvedPath = (0, security_cjs_1.requireSafePath)(filePath, cwd, 'UAT file', { allowAbsolute: true });
325
+ const resolvedPath = (0, security_cjs_1.requireSafePath)(filePath, cwd, 'UAT file', security_cjs_1.PathAcceptance.AbsoluteInsideRoot);
326
326
  if (!node_fs_1.default.existsSync(resolvedPath)) {
327
327
  error(`UAT file not found: ${filePath}`);
328
328
  }
@@ -3579,6 +3579,14 @@ function categorizeItem(rawResult, reason, blockedBy) {
3579
3579
  return 'blocked';
3580
3580
  }
3581
3581
  if (result === 'skipped') {
3582
+ // #4546 — a deliberately deferred follow-up (the verify-work writer's
3583
+ // "Deferred follow-up:" template, #1921) is its own category, checked
3584
+ // BEFORE the keyword families so e.g. "…on the release build next
3585
+ // version" is not misfiled as build_needed. Must agree with
3586
+ // uat-predicate.cts's DEFERRED_REASON_RE (gate/audit agreement,
3587
+ // #3078-CR), pinned by tests/uat-predicate.test.cjs.
3588
+ if (reason && /^["']?deferred follow-up\b/i.test(reason))
3589
+ return 'deferred';
3582
3590
  if (reason) {
3583
3591
  if (/server|not running|not available/i.test(reason))
3584
3592
  return 'server_blocked';
@@ -117,6 +117,15 @@ function validateRequirement(element) {
117
117
  if (r.elements == null && !(typeof r.text === 'string' && r.text.trim())) {
118
118
  throw new Error(`element ${element.id} text must be a non-empty string when no elements override is provided`);
119
119
  }
120
+ // text_en (#4657) is optional, but when present it must be a non-empty string. An empty
121
+ // string is NOT caught by `??` (only null/undefined are), so an unvalidated `text_en: ''`
122
+ // would silently win `text_en ?? text` and classify against '' — the same fail-open shape
123
+ // #1110/#2773 already exist to eliminate, just moved one field over. Validated
124
+ // unconditionally (not gated on whether `elements` will make it unused) so bad data fails
125
+ // closed even when it happens to be dead for this particular call.
126
+ if (r.text_en != null && !(typeof r.text_en === 'string' && r.text_en.trim())) {
127
+ throw new Error(`element ${element.id} text_en must be a non-empty string when present`);
128
+ }
120
129
  }
121
130
  /** Validate a UI-consideration resolution against the UI verification vocabulary (delegated, D-06). */
122
131
  function validateResolution(resolution) {
@@ -143,7 +152,11 @@ function proposeConsiderations(element) {
143
152
  kinds = element.elements;
144
153
  }
145
154
  else {
146
- kinds = classifyElement(element.text);
155
+ // #4657: prefer the English translation when present — UI_CUES are English-only
156
+ // word-boundary patterns, so a non-English `text` (e.g. response_language projects)
157
+ // would otherwise classify to zero kinds. validateRequirement (called above) has
158
+ // already guaranteed text_en, if present, is a non-empty string.
159
+ kinds = classifyElement((element.text_en ?? element.text));
147
160
  if (kinds.length === 0) {
148
161
  // Prose present but no element cue matched. Do NOT silently drop it (#1110): a UI element
149
162
  // whose phrasing missed every cue would otherwise vanish from coverage with no signal — the
@@ -210,7 +223,7 @@ function proposeElements(elements) {
210
223
  const considerations = proposeConsiderations(el);
211
224
  const kinds = Array.isArray(el.elements)
212
225
  ? el.elements // already validated inside proposeConsiderations
213
- : classifyElement(el.text);
226
+ : classifyElement((el.text_en ?? el.text)); // #4657: text_en ?? text
214
227
  const unclassified = !Array.isArray(el.elements) && kinds.length === 0;
215
228
  const categories = unclassified ? [] : applicableCategories(kinds);
216
229
  return { id: el.id, kinds, categories, considerations, unclassified };
@@ -23,6 +23,19 @@
23
23
  * html, ...) are deliberately NOT static evidence: docs sites and
24
24
  * markdown/bash/config repos routinely carry stray `.html`/`.css`, which
25
25
  * is precisely the false-positive class #3312 reports.
26
+ * (c) any `*.xaml` file, or a `*.swift` / `*.kt` / `*.dart` file whose
27
+ * content carries its ecosystem's UI-framework import marker
28
+ * (`import SwiftUI` / `import UIKit`, `androidx.compose`,
29
+ * `package:flutter`) (#4658). Native UI projects (SwiftUI, Jetpack
30
+ * Compose, Flutter, .NET MAUI) carry neither (a) nor (b), which made the
31
+ * gate structurally unreachable for them. Source files match on the
32
+ * IMPORT, not the extension alone — the same unambiguity bar that
33
+ * justifies (b)'s subset and excludes (css, scss, html): a non-UI Swift
34
+ * package (a CLI, a server) imports Foundation, not SwiftUI, and must
35
+ * stay silent. `.xaml` is extension-alone for the same reason `.tsx` is —
36
+ * the extension itself is unambiguous. Marker matching is case-sensitive
37
+ * (imports are case-sensitive in all four ecosystems); extension
38
+ * matching is case-insensitive, mirroring `UI_COMPONENT_FILE_RE`.
26
39
  *
27
40
  * All I/O failures degrade to `false` (no evidence) — never throw.
28
41
  */
@@ -30,12 +43,28 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
30
43
  return (mod && mod.__esModule) ? mod : { "default": mod };
31
44
  };
32
45
  Object.defineProperty(exports, "__esModule", { value: true });
33
- exports.UI_COMPONENT_FILE_RE = void 0;
46
+ exports.NATIVE_UI_CONTENT_MARKERS = exports.NATIVE_UI_XAML_RE = exports.NATIVE_UI_SOURCE_RE = exports.UI_COMPONENT_FILE_RE = void 0;
34
47
  exports.hasStaticFrontendEvidence = hasStaticFrontendEvidence;
35
48
  const node_fs_1 = __importDefault(require("node:fs"));
36
49
  const node_path_1 = __importDefault(require("node:path"));
37
50
  /** Component-framework file extensions — the static-evidence subset of UI_FILE_EXTENSIONS_RE. */
38
51
  exports.UI_COMPONENT_FILE_RE = /\.(tsx|jsx|vue|svelte)$/i;
52
+ /** Native UI source files whose CONTENT is scanned for an import marker (#4658). */
53
+ exports.NATIVE_UI_SOURCE_RE = /\.(swift|kt|dart)$/i;
54
+ /** Native UI files that are evidence by extension alone — `.xaml`, the `.tsx` analogue. */
55
+ exports.NATIVE_UI_XAML_RE = /\.xaml$/i;
56
+ /**
57
+ * Per-extension UI-framework import markers for `NATIVE_UI_SOURCE_RE` files
58
+ * (#4658). Every marker embeds its ecosystem's import keyword, so a bare
59
+ * framework-name mention in prose or a comment is not evidence; the Dart
60
+ * marker carries both legal quote styles. Case-sensitive: imports are
61
+ * case-sensitive in Swift, Kotlin and Dart.
62
+ */
63
+ exports.NATIVE_UI_CONTENT_MARKERS = {
64
+ '.swift': ['import SwiftUI', 'import UIKit'],
65
+ '.kt': ['import androidx.compose'],
66
+ '.dart': ["import 'package:flutter", 'import "package:flutter'],
67
+ };
39
68
  /**
40
69
  * UI-framework package.json dependencies (dependencies OR devDependencies).
41
70
  * Component frameworks/renderers only — deliberately excludes meta tooling
@@ -111,9 +140,16 @@ function packageJsonHasUiFramework(projectDir) {
111
140
  }
112
141
  return false;
113
142
  }
114
- function treeHasComponentFile(projectDir) {
115
- // Iterative BFS — bounded by MAX_WALK_ENTRIES so a pathological tree cannot
116
- // stall the gate. Symlinks are never followed (withFileTypes + isDirectory).
143
+ /**
144
+ * Shared bounded BFS over the project tree — the single home of the walk
145
+ * semantics both evidence walks depend on: SKIP_DIRS pruning, the
146
+ * MAX_WALK_ENTRIES entry cap (cap-hit → `false`: the tree is treated as
147
+ * scanned and evidence stays undecided), symlinks never followed
148
+ * (withFileTypes Dirents), unreadable directories skipped. `visit` is called
149
+ * for every regular file with its name and full path; returning `true` stops
150
+ * the walk with `true` (evidence found).
151
+ */
152
+ function walkProjectFiles(projectDir, visit) {
117
153
  const queue = [projectDir];
118
154
  let visited = 0;
119
155
  while (queue.length > 0 && visited < MAX_WALK_ENTRIES) {
@@ -133,25 +169,80 @@ function treeHasComponentFile(projectDir) {
133
169
  if (!SKIP_DIRS.has(entry.name))
134
170
  queue.push(node_path_1.default.join(dir, entry.name));
135
171
  }
136
- else if (entry.isFile() && exports.UI_COMPONENT_FILE_RE.test(entry.name)) {
172
+ else if (entry.isFile() && visit(entry.name, node_path_1.default.join(dir, entry.name))) {
137
173
  return true;
138
174
  }
139
175
  }
140
176
  }
141
177
  return false;
142
178
  }
179
+ function treeHasComponentFile(projectDir) {
180
+ return walkProjectFiles(projectDir, (name) => exports.UI_COMPONENT_FILE_RE.test(name));
181
+ }
182
+ /**
183
+ * Read at most the first 64 KiB of `file` and report whether any of `markers`
184
+ * occurs in it (#4658). Import sections live at the top of a source file, so a
185
+ * bounded prefix read keeps the gate's plan-time cost profile without reading
186
+ * generated monsters in full. Any I/O failure degrades to false — never throw.
187
+ */
188
+ function fileHasAnyMarker(file, markers) {
189
+ let fd;
190
+ try {
191
+ fd = node_fs_1.default.openSync(file, 'r');
192
+ }
193
+ catch {
194
+ return false;
195
+ }
196
+ try {
197
+ const bytes = Buffer.alloc(64 * 1024);
198
+ const read = node_fs_1.default.readSync(fd, bytes, 0, bytes.length, 0);
199
+ const prefix = bytes.toString('utf8', 0, read);
200
+ return markers.some((m) => prefix.includes(m));
201
+ }
202
+ catch {
203
+ return false;
204
+ }
205
+ finally {
206
+ try {
207
+ node_fs_1.default.closeSync(fd);
208
+ }
209
+ catch {
210
+ // already closed — nothing to degrade
211
+ }
212
+ }
213
+ }
214
+ /**
215
+ * Native-UI walk over the shared bounded BFS (#4658): `.xaml` is evidence by
216
+ * extension alone; `.swift`/`.kt`/`.dart` are evidence only when the file's
217
+ * content carries its ecosystem's import marker.
218
+ */
219
+ function treeHasNativeUiFile(projectDir) {
220
+ return walkProjectFiles(projectDir, (name, fullPath) => {
221
+ if (exports.NATIVE_UI_XAML_RE.test(name))
222
+ return true;
223
+ if (exports.NATIVE_UI_SOURCE_RE.test(name)) {
224
+ const markers = exports.NATIVE_UI_CONTENT_MARKERS[node_path_1.default.extname(name).toLowerCase()];
225
+ return markers != null && fileHasAnyMarker(fullPath, markers);
226
+ }
227
+ return false;
228
+ });
229
+ }
143
230
  /**
144
231
  * Does the project tree carry static evidence of a frontend?
145
232
  *
146
233
  * @param projectDir - Absolute path to the project root (the gate's cwd).
147
- * @returns true when package.json declares a UI-framework dependency or the
148
- * tree contains a component-framework file; false otherwise (including
149
- * on any I/O failure — evidence must be affirmative).
234
+ * @returns true when package.json declares a UI-framework dependency, the tree
235
+ * contains a component-framework file, or the tree contains native UI
236
+ * evidence (a `.xaml` file, or a `.swift`/`.kt`/`.dart` file carrying
237
+ * its ecosystem's UI import marker — #4658); false otherwise
238
+ * (including on any I/O failure — evidence must be affirmative).
150
239
  */
151
240
  function hasStaticFrontendEvidence(projectDir) {
152
241
  if (typeof projectDir !== 'string' || projectDir === '')
153
242
  return false;
154
243
  if (packageJsonHasUiFramework(projectDir))
155
244
  return true;
156
- return treeHasComponentFile(projectDir);
245
+ if (treeHasComponentFile(projectDir))
246
+ return true;
247
+ return treeHasNativeUiFile(projectDir);
157
248
  }
@@ -0,0 +1,131 @@
1
+ "use strict";
2
+ /**
3
+ * Undo Commit Selection — issue #4661 (absorbed into epic #4906, Phase 5).
4
+ *
5
+ * Pure scope-matching logic for `/gsd:undo --phase N` / `--plan N-M`, replacing
6
+ * the two hand-rolled `git log --oneline | grep -E ...` pipelines that used to
7
+ * live in `gsd-core/workflows/undo.md`. Those greps were unanchored substring
8
+ * matches against a live regex built from unvalidated user input — four
9
+ * documented bug classes (see #4661):
10
+ *
11
+ * 1. The id was interpolated raw into an ERE: `.` and `+` are live
12
+ * metacharacters there, so a malformed or merely differently-shaped id
13
+ * silently matched the WRONG phase.
14
+ * 2. The grep was unanchored, so a commit that only MENTIONS a scope
15
+ * (`docs(99-01): explain feat(03-01): commit convention`) was wrongly
16
+ * selected as a DECLARATION of it.
17
+ * 3. Phase-mode's `\):` suffix and plan-mode's missing suffix disagreed on
18
+ * the same commit (`feat(03-01)!: breaking change`).
19
+ * 4. The "obvious" fix — anchoring `git log --oneline`'s record start —
20
+ * is a trap: it silently drops `fixup! ...` / `Revert "..."` wrapper
21
+ * commits (a worse failure, a silent partial revert) and returns zero
22
+ * matches under `color.ui=always` (ANSI codes precede the sha).
23
+ *
24
+ * This module closes all four by parsing each commit subject through the
25
+ * SAME anchored conventional-commit grammar the changelog/PR-title gate uses
26
+ * (`HEADER_RE`, reused verbatim from `scripts/release-notes/conventional-title.cjs`
27
+ * — see the require() below), then comparing the extracted scope to the
28
+ * caller-supplied target id by exact STRING EQUALITY. No regex is built from
29
+ * user input at any point past `validatePhaseNumber` (src/security.cts),
30
+ * which the CLI caller (`gsd-tools.cjs query select-revert-commits`) runs
31
+ * BEFORE this module ever sees the id.
32
+ *
33
+ * Deliberately no `fs`/`child_process` in this file: it operates purely on
34
+ * `{sha, subject}` records the caller already fetched (e.g. via `git log
35
+ * --format=%H%x00%s`), so it is unit-testable without touching git or the
36
+ * filesystem.
37
+ *
38
+ * KNOWN LIMIT (documented, not a defect): `unwrapSubject` unwraps ONE level
39
+ * of a `fixup! `/`squash! `/`Revert "..."` wrapper. A doubly-wrapped subject
40
+ * (e.g. `Revert "fixup! feat(03-01): work"`) is treated as unparseable (no
41
+ * declared scope) after that single unwrap — it is NOT selected, never
42
+ * mis-selected. This bounds the module to the wrapper shapes git itself
43
+ * produces (`fixup!`/`squash!` from `--fixup`/`--squash`, `Revert "..."` from
44
+ * `git revert`), each of which wraps a single ordinary commit subject, not
45
+ * another wrapper.
46
+ */
47
+ var __importDefault = (this && this.__importDefault) || function (mod) {
48
+ return (mod && mod.__esModule) ? mod : { "default": mod };
49
+ };
50
+ Object.defineProperty(exports, "__esModule", { value: true });
51
+ exports.selectCommitsByDeclaredScope = selectCommitsByDeclaredScope;
52
+ const node_path_1 = __importDefault(require("node:path"));
53
+ // Reuse the exact HEADER_RE the changelog classifier and PR-title CI gate
54
+ // already depend on (#1549) — never fork a second copy of this grammar. A
55
+ // bare `require()` (not a TS `import ... = require(...)`) is used
56
+ // deliberately: `scripts/` sits outside this build's `rootDir` ("src", see
57
+ // tsconfig.build.json) and carries no `.d.ts`, so a type-checked import-equals
58
+ // would fail module resolution (TS7016) under `strict`/`noImplicitAny`. A
59
+ // plain `require()` resolves through Node's ambient `NodeRequire` type
60
+ // (`(id: string) => any`) instead, sidestepping that check entirely — the
61
+ // cast below is the only place the resulting shape is asserted.
62
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
63
+ const conventionalTitle = require(node_path_1.default.resolve(__dirname, '..', '..', '..', 'scripts', 'release-notes', 'conventional-title.cjs'));
64
+ const { HEADER_RE } = conventionalTitle;
65
+ // ─── Subject unwrapping (Known limit: exactly ONE level) ──────────────────
66
+ const FIXUP_PREFIX = 'fixup! ';
67
+ const SQUASH_PREFIX = 'squash! ';
68
+ const REVERT_RE = /^Revert "(.*)"$/;
69
+ /**
70
+ * Strip exactly one `fixup! `/`squash! `/`Revert "..."` wrapper layer off a
71
+ * commit subject, returning the (possibly) unwrapped remainder. Never
72
+ * recurses — a doubly-wrapped subject is returned with only its outermost
73
+ * layer removed, which is intentionally still unparseable by `HEADER_RE` (see
74
+ * module doc comment).
75
+ */
76
+ function unwrapSubject(subject) {
77
+ if (subject.startsWith(FIXUP_PREFIX))
78
+ return subject.slice(FIXUP_PREFIX.length);
79
+ if (subject.startsWith(SQUASH_PREFIX))
80
+ return subject.slice(SQUASH_PREFIX.length);
81
+ const revertMatch = REVERT_RE.exec(subject);
82
+ if (revertMatch)
83
+ return revertMatch[1];
84
+ return subject;
85
+ }
86
+ /**
87
+ * Parse a (possibly wrapped) commit subject into its declared conventional-
88
+ * commit scope, or `null` when the subject carries no parseable scope: no
89
+ * clean `type[(scope)][!]:` header at all, or a header with no `(scope)`
90
+ * group. Never regex-matches against untrusted `targetId` — this function
91
+ * only ever consumes the commit's OWN subject text.
92
+ */
93
+ function parseDeclaredScope(subject) {
94
+ const unwrapped = unwrapSubject(subject);
95
+ const m = HEADER_RE.exec(unwrapped);
96
+ if (!m)
97
+ return null;
98
+ const scopeWithParens = m[2]; // e.g. "(03-01)" — includes parens, or undefined
99
+ if (!scopeWithParens)
100
+ return null;
101
+ // HEADER_RE's scope group is `\([^)]*\)` — always at least the two paren
102
+ // characters when present, so slicing them off is safe unconditionally.
103
+ return scopeWithParens.slice(1, -1);
104
+ }
105
+ /**
106
+ * Select commits whose DECLARED conventional-commit scope identifies
107
+ * `targetId`, by exact string equality (`'plan'` mode) or exact-or-phase-
108
+ * prefixed equality (`'phase'` mode). `targetId` is assumed ALREADY
109
+ * VALIDATED/NORMALIZED by the caller (e.g. via `validatePhaseNumber` from
110
+ * `src/security.cts`) — this function performs no validation of its own, and
111
+ * never builds a regex from it (string equality / `startsWith` only), so a
112
+ * malformed or metacharacter-laden `targetId` cannot cause a wildcard-style
113
+ * over-match the way the retired grep pipelines could.
114
+ */
115
+ function selectCommitsByDeclaredScope(commits, targetId, mode = 'plan') {
116
+ const phasePrefix = `${targetId}-`;
117
+ const classified = commits.map(({ sha, subject }) => {
118
+ const parsedScope = parseDeclaredScope(subject);
119
+ let matched = false;
120
+ if (parsedScope !== null) {
121
+ matched = mode === 'phase'
122
+ ? parsedScope === targetId || parsedScope.startsWith(phasePrefix)
123
+ : parsedScope === targetId;
124
+ }
125
+ return { sha, subject, parsedScope, matched };
126
+ });
127
+ const selected = classified
128
+ .filter((c) => c.matched)
129
+ .map(({ sha, subject }) => ({ sha, subject }));
130
+ return { selected, classified };
131
+ }