@opengsd/gsd-core 1.10.0 → 1.12.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 (544) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/agents/gsd-code-fixer.md +1 -1
  4. package/agents/gsd-debug-session-manager.md +12 -1
  5. package/agents/gsd-debugger.md +1 -1
  6. package/agents/gsd-doc-synthesizer.md +2 -4
  7. package/agents/gsd-dom-verifier.md +169 -0
  8. package/agents/gsd-eval-auditor.md +1 -1
  9. package/agents/gsd-executor.md +22 -14
  10. package/agents/gsd-framework-selector.md +1 -3
  11. package/agents/gsd-intel-updater.md +1 -1
  12. package/agents/gsd-mempalace-curator.md +5 -3
  13. package/agents/gsd-pattern-mapper.md +11 -0
  14. package/agents/gsd-phase-researcher.md +23 -2
  15. package/agents/gsd-plan-checker.md +50 -53
  16. package/agents/gsd-planner.md +50 -50
  17. package/agents/gsd-project-researcher.md +1 -1
  18. package/agents/gsd-research-synthesizer.md +2 -2
  19. package/agents/gsd-roadmapper.md +15 -11
  20. package/agents/gsd-ui-checker.md +63 -4
  21. package/agents/gsd-ui-researcher.md +41 -3
  22. package/agents/gsd-user-profiler.md +3 -0
  23. package/agents/gsd-verifier.md +13 -4
  24. package/bin/install.js +1448 -1103
  25. package/commands/gsd/code-review.md +1 -1
  26. package/commands/gsd/discuss-phase.md +1 -1
  27. package/commands/gsd/execute-phase.md +1 -1
  28. package/commands/gsd/import.md +1 -1
  29. package/commands/gsd/map-codebase.md +1 -1
  30. package/commands/gsd/mempalace-capture.md +1 -1
  31. package/commands/gsd/mempalace-recall.md +1 -1
  32. package/commands/gsd/new-milestone.md +1 -1
  33. package/commands/gsd/quick.md +9 -5
  34. package/commands/gsd/review-backlog.md +2 -1
  35. package/commands/gsd/verify-work.md +1 -1
  36. package/gsd-core/bin/gsd-tools.cjs +1035 -138
  37. package/gsd-core/bin/lib/active-workstream-store.cjs +146 -22
  38. package/gsd-core/bin/lib/adr-parser.cjs +13 -7
  39. package/gsd-core/bin/lib/agent-install-check.cjs +392 -32
  40. package/gsd-core/bin/lib/api-coverage.cjs +33 -14
  41. package/gsd-core/bin/lib/artifacts.cjs +5 -0
  42. package/gsd-core/bin/lib/assumption-delta.cjs +32 -15
  43. package/gsd-core/bin/lib/audit-command-router.cjs +9 -2
  44. package/gsd-core/bin/lib/audit.cjs +1026 -268
  45. package/gsd-core/bin/lib/broken-windows.cjs +306 -28
  46. package/gsd-core/bin/lib/capability-consent.cjs +149 -15
  47. package/gsd-core/bin/lib/capability-lifecycle.cjs +45 -0
  48. package/gsd-core/bin/lib/capability-lock.cjs +10 -4
  49. package/gsd-core/bin/lib/capability-registry.cjs +845 -130
  50. package/gsd-core/bin/lib/capability-source.cjs +92 -0
  51. package/gsd-core/bin/lib/capability-state.cjs +18 -3
  52. package/gsd-core/bin/lib/capability-trust.cjs +444 -25
  53. package/gsd-core/bin/lib/capability-validator.cjs +700 -40
  54. package/gsd-core/bin/lib/capability-writer.cjs +3 -2
  55. package/gsd-core/bin/lib/check-command-router.cjs +216 -42
  56. package/gsd-core/bin/lib/claude-orchestration.cjs +56 -3
  57. package/gsd-core/bin/lib/cli-exit.cjs +496 -10
  58. package/gsd-core/bin/lib/code-review-depth.cjs +288 -0
  59. package/gsd-core/bin/lib/codex-agent-toml.cjs +735 -0
  60. package/gsd-core/bin/lib/command-aliases.cjs +22 -0
  61. package/gsd-core/bin/lib/command-arg-projection.cjs +144 -14
  62. package/gsd-core/bin/lib/command-roster.cjs +44 -1
  63. package/gsd-core/bin/lib/command-routing-hub.cjs +31 -2
  64. package/gsd-core/bin/lib/commands.cjs +1172 -108
  65. package/gsd-core/bin/lib/commonjs-marker.cjs +12 -6
  66. package/gsd-core/bin/lib/complexity-trigger.cjs +1192 -0
  67. package/gsd-core/bin/lib/config-loader.cjs +187 -23
  68. package/gsd-core/bin/lib/config.cjs +102 -3
  69. package/gsd-core/bin/lib/configuration.cjs +129 -37
  70. package/gsd-core/bin/lib/core-utils.cjs +208 -33
  71. package/gsd-core/bin/lib/decisions.cjs +23 -0
  72. package/gsd-core/bin/lib/edge-probe.cjs +9 -1
  73. package/gsd-core/bin/lib/estimate-cli.cjs +55 -11
  74. package/gsd-core/bin/lib/exit-code-registry.cjs +98 -0
  75. package/gsd-core/bin/lib/fallow-runner.cjs +20 -44
  76. package/gsd-core/bin/lib/frontmatter.cjs +899 -229
  77. package/gsd-core/bin/lib/gap-checker.cjs +95 -10
  78. package/gsd-core/bin/lib/git-base-branch.cjs +276 -39
  79. package/gsd-core/bin/lib/gsd2-import.cjs +10 -1
  80. package/gsd-core/bin/lib/health-diagnostic-rules/agent-install.cjs +101 -0
  81. package/gsd-core/bin/lib/health-diagnostic-rules/config-validation.cjs +348 -0
  82. package/gsd-core/bin/lib/health-diagnostic-rules/consistency.cjs +149 -0
  83. package/gsd-core/bin/lib/health-diagnostic-rules/install-surface-shadowing.cjs +98 -0
  84. package/gsd-core/bin/lib/health-diagnostic-rules/milestone-archive-hygiene.cjs +100 -0
  85. package/gsd-core/bin/lib/health-diagnostic-rules/phase-structure.cjs +222 -0
  86. package/gsd-core/bin/lib/health-diagnostic-rules/roadmap-disk-consistency.cjs +268 -0
  87. package/gsd-core/bin/lib/health-diagnostic-rules/root-existence.cjs +161 -0
  88. package/gsd-core/bin/lib/health-diagnostic-rules/state-consistency.cjs +303 -0
  89. package/gsd-core/bin/lib/health-diagnostic-rules/worktree-health.cjs +187 -0
  90. package/gsd-core/bin/lib/health-diagnostic-types.cjs +68 -0
  91. package/gsd-core/bin/lib/health-diagnostic.cjs +451 -0
  92. package/gsd-core/bin/lib/host-integration.cjs +39 -6
  93. package/gsd-core/bin/lib/host-runtime-detection.cjs +134 -0
  94. package/gsd-core/bin/lib/init-command-router.cjs +118 -21
  95. package/gsd-core/bin/lib/init.cjs +439 -168
  96. package/gsd-core/bin/lib/install-effort-resolver.cjs +73 -30
  97. package/gsd-core/bin/lib/install-engine.cjs +811 -259
  98. package/gsd-core/bin/lib/install-fs-adapter.cjs +262 -0
  99. package/gsd-core/bin/lib/install-model-override-resolver.cjs +235 -0
  100. package/gsd-core/bin/lib/install-profiles.cjs +212 -61
  101. package/gsd-core/bin/lib/install-scope.cjs +270 -0
  102. package/gsd-core/bin/lib/install-shadow-report.cjs +385 -0
  103. package/gsd-core/bin/lib/installed-surface-resolver.cjs +381 -0
  104. package/gsd-core/bin/lib/installer-migration-report.cjs +3 -0
  105. package/gsd-core/bin/lib/installer-migrations/010-antigravity-retire-confighome-artifacts.cjs +169 -0
  106. package/gsd-core/bin/lib/installer-migrations.cjs +148 -38
  107. package/gsd-core/bin/lib/intel.cjs +101 -26
  108. package/gsd-core/bin/lib/io.cjs +170 -15
  109. package/gsd-core/bin/lib/learnings.cjs +85 -14
  110. package/gsd-core/bin/lib/legacy-cleanup.cjs +8 -2
  111. package/gsd-core/bin/lib/markdown-sectionizer.cjs +2 -1
  112. package/gsd-core/bin/lib/markdown-table.cjs +183 -22
  113. package/gsd-core/bin/lib/milestone-lock.cjs +248 -0
  114. package/gsd-core/bin/lib/milestone.cjs +842 -73
  115. package/gsd-core/bin/lib/model-catalog.cjs +232 -16
  116. package/gsd-core/bin/lib/model-resolver.cjs +193 -68
  117. package/gsd-core/bin/lib/normalize-test-command.cjs +1 -1
  118. package/gsd-core/bin/lib/onboard-projection.cjs +5 -1
  119. package/gsd-core/bin/lib/pattern.cjs +122 -0
  120. package/gsd-core/bin/lib/phase-estimation.cjs +18 -9
  121. package/gsd-core/bin/lib/phase-id.cjs +514 -40
  122. package/gsd-core/bin/lib/phase-lifecycle.cjs +52 -19
  123. package/gsd-core/bin/lib/phase-locator.cjs +262 -34
  124. package/gsd-core/bin/lib/phase.cjs +1038 -214
  125. package/gsd-core/bin/lib/plan-dependency-graph.cjs +72 -1
  126. package/gsd-core/bin/lib/plan-document.cjs +263 -0
  127. package/gsd-core/bin/lib/plan-drift-guard.cjs +120 -0
  128. package/gsd-core/bin/lib/plan-scan.cjs +98 -3
  129. package/gsd-core/bin/lib/planning-command-router.cjs +61 -0
  130. package/gsd-core/bin/lib/planning-inspect.cjs +1168 -0
  131. package/gsd-core/bin/lib/planning-scope.cjs +31 -0
  132. package/gsd-core/bin/lib/planning-snapshot.cjs +894 -0
  133. package/gsd-core/bin/lib/planning-workspace.cjs +112 -6
  134. package/gsd-core/bin/lib/probe-core.cjs +5 -2
  135. package/gsd-core/bin/lib/profile-output.cjs +1 -1
  136. package/gsd-core/bin/lib/profile-pipeline-command-router.cjs +50 -7
  137. package/gsd-core/bin/lib/profile-pipeline.cjs +6 -3
  138. package/gsd-core/bin/lib/real-home-guard.cjs +419 -0
  139. package/gsd-core/bin/lib/refactor-trigger-command-router.cjs +766 -0
  140. package/gsd-core/bin/lib/retired-artifact-cleanup.cjs +11 -6
  141. package/gsd-core/bin/lib/review-lane-descriptor.cjs +22 -13
  142. package/gsd-core/bin/lib/review-lane-invocation.cjs +30 -0
  143. package/gsd-core/bin/lib/review-lane-runner.cjs +421 -66
  144. package/gsd-core/bin/lib/review-reviewer-selection.cjs +13 -18
  145. package/gsd-core/bin/lib/roadmap-command-router.cjs +59 -11
  146. package/gsd-core/bin/lib/roadmap-parser.cjs +1006 -184
  147. package/gsd-core/bin/lib/roadmap-upgrade.cjs +37 -10
  148. package/gsd-core/bin/lib/roadmap.cjs +442 -96
  149. package/gsd-core/bin/lib/runtime-artifact-conversion.cjs +702 -52
  150. package/gsd-core/bin/lib/runtime-artifact-install-plan.cjs +14 -2
  151. package/gsd-core/bin/lib/runtime-artifact-layout.cjs +459 -55
  152. package/gsd-core/bin/lib/runtime-config-adapter-registry.cjs +3 -2
  153. package/gsd-core/bin/lib/runtime-homes.cjs +69 -3
  154. package/gsd-core/bin/lib/runtime-hooks-surface.cjs +402 -58
  155. package/gsd-core/bin/lib/runtime-identity.cjs +234 -0
  156. package/gsd-core/bin/lib/runtime-name-policy.cjs +3 -1
  157. package/gsd-core/bin/lib/runtime-slash.cjs +96 -8
  158. package/gsd-core/bin/lib/security.cjs +104 -5
  159. package/gsd-core/bin/lib/shell-command-projection.cjs +342 -7
  160. package/gsd-core/bin/lib/smart-entry.cjs +133 -23
  161. package/gsd-core/bin/lib/spec-section.cjs +12 -7
  162. package/gsd-core/bin/lib/state-command-router.cjs +52 -19
  163. package/gsd-core/bin/lib/state-contract.cjs +359 -0
  164. package/gsd-core/bin/lib/state-document.cjs +338 -8
  165. package/gsd-core/bin/lib/state-md-schema.cjs +221 -0
  166. package/gsd-core/bin/lib/state-transition.cjs +846 -176
  167. package/gsd-core/bin/lib/state.cjs +2589 -369
  168. package/gsd-core/bin/lib/surface.cjs +33 -11
  169. package/gsd-core/bin/lib/task-command-router.cjs +111 -1
  170. package/gsd-core/bin/lib/task-content-resolution.cjs +368 -0
  171. package/gsd-core/bin/lib/teams-status.cjs +4 -1
  172. package/gsd-core/bin/lib/text-lines.cjs +80 -0
  173. package/gsd-core/bin/lib/token-scanner.cjs +76 -0
  174. package/gsd-core/bin/lib/uat-predicate.cjs +67 -23
  175. package/gsd-core/bin/lib/uat.cjs +1761 -167
  176. package/gsd-core/bin/lib/ui-consideration-probe.cjs +9 -1
  177. package/gsd-core/bin/lib/ui-frontend-evidence.cjs +157 -0
  178. package/gsd-core/bin/lib/ui-safety-gate.cjs +51 -12
  179. package/gsd-core/bin/lib/unusable-input.cjs +37 -0
  180. package/gsd-core/bin/lib/update-context.cjs +8 -2
  181. package/gsd-core/bin/lib/user-artifact-staging.cjs +705 -0
  182. package/gsd-core/bin/lib/validate-command-router.cjs +2 -2
  183. package/gsd-core/bin/lib/validate.cjs +20 -6
  184. package/gsd-core/bin/lib/vendor/README.md +75 -0
  185. package/gsd-core/bin/lib/vendor/js-yaml.cjs +3014 -0
  186. package/gsd-core/bin/lib/vendor/re2js.cjs +6480 -0
  187. package/gsd-core/bin/lib/vendor/re2js.d.cts +938 -0
  188. package/gsd-core/bin/lib/verification-command-router.cjs +2 -1
  189. package/gsd-core/bin/lib/verification.cjs +272 -9
  190. package/gsd-core/bin/lib/verify-command-grounding.cjs +846 -0
  191. package/gsd-core/bin/lib/verify.cjs +453 -918
  192. package/gsd-core/bin/lib/workstream-inventory-builder.cjs +53 -32
  193. package/gsd-core/bin/lib/workstream-inventory.cjs +63 -10
  194. package/gsd-core/bin/lib/workstream-name-policy.cjs +25 -4
  195. package/gsd-core/bin/lib/workstream.cjs +2 -2
  196. package/gsd-core/bin/lib/worktree-base-ref.cjs +66 -12
  197. package/gsd-core/bin/lib/worktree-safety.cjs +341 -18
  198. package/gsd-core/bin/shared/config-defaults.manifest.json +8 -1
  199. package/gsd-core/bin/shared/config-schema.manifest.json +12 -1
  200. package/gsd-core/bin/shared/exit-codes.json +8 -0
  201. package/gsd-core/bin/shared/exit-codes.sh +20 -0
  202. package/gsd-core/bin/shared/model-catalog.json +8 -1
  203. package/gsd-core/references/agent-contracts.md +44 -26
  204. package/gsd-core/references/api-coverage.md +24 -2
  205. package/gsd-core/references/autonomous-smart-discuss.md +3 -3
  206. package/gsd-core/references/checkpoints.md +39 -21
  207. package/gsd-core/references/context-budget.md +1 -1
  208. package/gsd-core/references/decimal-phase-calculation.md +5 -5
  209. package/gsd-core/references/dispatch-isolation-gate.md +138 -0
  210. package/gsd-core/references/doc-conflict-engine.md +1 -1
  211. package/gsd-core/references/edge-probe.md +8 -0
  212. package/gsd-core/references/execute-mvp-tdd.md +4 -6
  213. package/gsd-core/references/execute-phase-between-wave-reset.md +15 -14
  214. package/gsd-core/references/execute-phase-context-guard.md +1 -1
  215. package/gsd-core/references/execute-phase-response-language.md +1 -1
  216. package/gsd-core/references/execute-phase-wave-guard.md +17 -11
  217. package/gsd-core/references/failing-direction.md +78 -0
  218. package/gsd-core/references/gate-prompts.md +1 -1
  219. package/gsd-core/references/git-integration.md +5 -5
  220. package/gsd-core/references/git-planning-commit.md +5 -4
  221. package/gsd-core/references/gsd-run-resolver.md +1 -1
  222. package/gsd-core/references/loop-hook-dispatch.md +61 -2
  223. package/gsd-core/references/model-profiles.md +12 -4
  224. package/gsd-core/references/mvp-concepts.md +9 -9
  225. package/gsd-core/references/nyquist-compliance.md +74 -0
  226. package/gsd-core/references/offer-next.md +3 -5
  227. package/gsd-core/references/phase-argument-parsing.md +3 -3
  228. package/gsd-core/references/planner-failing-direction.md +53 -0
  229. package/gsd-core/references/planner-guidance.md +3 -9
  230. package/gsd-core/references/planner-human-verify-mode.md +15 -1
  231. package/gsd-core/references/planner-preconditions.md +1 -1
  232. package/gsd-core/references/planner-reviews.md +1 -1
  233. package/gsd-core/references/planner-revision.md +1 -1
  234. package/gsd-core/references/planner-verify-command-grounding.md +17 -0
  235. package/gsd-core/references/planning-config.md +44 -13
  236. package/gsd-core/references/reviewer-instances.md +31 -0
  237. package/gsd-core/references/revision-loop.md +1 -1
  238. package/gsd-core/references/runtime-aware-dispatch.md +1 -1
  239. package/gsd-core/references/specless-probe-fallback.md +1 -1
  240. package/gsd-core/references/tdd.md +1 -3
  241. package/gsd-core/references/ui-brand.md +65 -21
  242. package/gsd-core/references/ui-consideration-probe.md +1 -1
  243. package/gsd-core/references/universal-anti-patterns.md +5 -5
  244. package/gsd-core/references/verifier-phase-gates.md +192 -0
  245. package/gsd-core/references/verify-command-path-resolvability.md +42 -0
  246. package/gsd-core/references/verify-mvp-mode.md +2 -2
  247. package/gsd-core/references/workstream-flag.md +33 -17
  248. package/gsd-core/templates/README.md +1 -1
  249. package/gsd-core/templates/SECURITY.md +3 -3
  250. package/gsd-core/templates/UI-SPEC.md +25 -3
  251. package/gsd-core/templates/VALIDATION.md +3 -3
  252. package/gsd-core/templates/discussion-log.md +1 -1
  253. package/gsd-core/templates/phase-prompt.md +5 -4
  254. package/gsd-core/templates/state.md +11 -4
  255. package/gsd-core/templates/verification-report.md +9 -1
  256. package/gsd-core/workflows/_runtime-launcher.snippet.sh +1 -1
  257. package/gsd-core/workflows/add-backlog.md +1 -1
  258. package/gsd-core/workflows/add-phase.md +3 -3
  259. package/gsd-core/workflows/add-tests.md +3 -8
  260. package/gsd-core/workflows/add-todo.md +1 -1
  261. package/gsd-core/workflows/ai-integration-phase.md +13 -20
  262. package/gsd-core/workflows/audit-fix.md +12 -3
  263. package/gsd-core/workflows/audit-milestone.md +9 -9
  264. package/gsd-core/workflows/audit-uat.md +17 -2
  265. package/gsd-core/workflows/autonomous/steps/converge-fail-fast.md +2 -2
  266. package/gsd-core/workflows/autonomous.md +11 -27
  267. package/gsd-core/workflows/check-todos.md +1 -1
  268. package/gsd-core/workflows/cleanup.md +64 -5
  269. package/gsd-core/workflows/code-review/steps/structural-pre-pass.md +14 -4
  270. package/gsd-core/workflows/code-review-fix.md +38 -11
  271. package/gsd-core/workflows/code-review.md +159 -52
  272. package/gsd-core/workflows/complete-milestone.md +151 -23
  273. package/gsd-core/workflows/debug.md +12 -8
  274. package/gsd-core/workflows/diagnose-issues.md +47 -15
  275. package/gsd-core/workflows/discuss-phase/modes/advisor.md +1 -1
  276. package/gsd-core/workflows/discuss-phase/modes/chain.md +5 -8
  277. package/gsd-core/workflows/discuss-phase/modes/default.md +1 -1
  278. package/gsd-core/workflows/discuss-phase/modes/text.md +1 -1
  279. package/gsd-core/workflows/discuss-phase-assumptions/steps/auto-advance-dispatch.md +1 -3
  280. package/gsd-core/workflows/discuss-phase-assumptions.md +4 -3
  281. package/gsd-core/workflows/discuss-phase.md +1 -1
  282. package/gsd-core/workflows/do.md +3 -6
  283. package/gsd-core/workflows/docs-update.md +5 -4
  284. package/gsd-core/workflows/edit-phase.md +27 -2
  285. package/gsd-core/workflows/eval-review.md +7 -14
  286. package/gsd-core/workflows/execute-phase/steps/codebase-drift-gate.md +1 -1
  287. package/gsd-core/workflows/execute-phase/steps/executor-isolation-dispatch.md +142 -15
  288. package/gsd-core/workflows/execute-phase/steps/gap-closure-artifacts.md +1 -1
  289. package/gsd-core/workflows/execute-phase/steps/partial-wave.md +1 -1
  290. package/gsd-core/workflows/execute-phase/steps/per-plan-executor-routing.md +77 -0
  291. package/gsd-core/workflows/execute-phase/steps/per-plan-worktree-gate.md +24 -4
  292. package/gsd-core/workflows/execute-phase/steps/post-merge-gate.md +2 -2
  293. package/gsd-core/workflows/execute-phase/steps/protected-branch.md +21 -0
  294. package/gsd-core/workflows/execute-phase/steps/regression-gate-run.md +2 -2
  295. package/gsd-core/workflows/execute-phase/steps/wave-post-gate-hooks.md +39 -0
  296. package/gsd-core/workflows/execute-phase.md +72 -100
  297. package/gsd-core/workflows/execute-plan.md +52 -15
  298. package/gsd-core/workflows/explore.md +131 -4
  299. package/gsd-core/workflows/extract-learnings.md +1 -1
  300. package/gsd-core/workflows/fast.md +10 -2
  301. package/gsd-core/workflows/forensics.md +1 -1
  302. package/gsd-core/workflows/graduation.md +5 -5
  303. package/gsd-core/workflows/health.md +76 -10
  304. package/gsd-core/workflows/import.md +18 -15
  305. package/gsd-core/workflows/inbox.md +4 -5
  306. package/gsd-core/workflows/ingest-docs.md +49 -16
  307. package/gsd-core/workflows/insert-phase.md +5 -5
  308. package/gsd-core/workflows/list-seeds.md +5 -3
  309. package/gsd-core/workflows/list-workspaces.md +1 -1
  310. package/gsd-core/workflows/manager.md +12 -23
  311. package/gsd-core/workflows/map-codebase.md +1 -1
  312. package/gsd-core/workflows/milestone-summary.md +1 -1
  313. package/gsd-core/workflows/mvp-phase.md +8 -5
  314. package/gsd-core/workflows/new-milestone.md +22 -29
  315. package/gsd-core/workflows/new-project/steps/auto-mode-config.md +1 -1
  316. package/gsd-core/workflows/new-project.md +26 -40
  317. package/gsd-core/workflows/new-workspace.md +1 -1
  318. package/gsd-core/workflows/next.md +14 -2
  319. package/gsd-core/workflows/pause-work.md +1 -1
  320. package/gsd-core/workflows/plan-phase/steps/adr-ingest-express-path.md +1 -1
  321. package/gsd-core/workflows/plan-phase/steps/chunked-planning-mode.md +1 -1
  322. package/gsd-core/workflows/plan-phase/steps/prd-express-path.md +2 -4
  323. package/gsd-core/workflows/plan-phase/steps/stall-detection-helpers.md +3 -3
  324. package/gsd-core/workflows/plan-phase.md +162 -59
  325. package/gsd-core/workflows/plan-review-convergence.md +96 -11
  326. package/gsd-core/workflows/plant-seed.md +2 -2
  327. package/gsd-core/workflows/pr-branch.md +187 -51
  328. package/gsd-core/workflows/profile-user.md +16 -14
  329. package/gsd-core/workflows/progress.md +61 -18
  330. package/gsd-core/workflows/quick/steps/discussion-phase.md +1 -3
  331. package/gsd-core/workflows/quick/steps/plan-checker-loop.md +5 -7
  332. package/gsd-core/workflows/quick/steps/quick-verification.md +28 -9
  333. package/gsd-core/workflows/quick/steps/research-phase.md +4 -6
  334. package/gsd-core/workflows/quick/steps/worktree-pre-dispatch-commit.md +3 -3
  335. package/gsd-core/workflows/quick.md +55 -44
  336. package/gsd-core/workflows/remove-phase.md +4 -4
  337. package/gsd-core/workflows/remove-workspace.md +2 -2
  338. package/gsd-core/workflows/resume-project.md +8 -12
  339. package/gsd-core/workflows/review.md +219 -20
  340. package/gsd-core/workflows/scan.md +1 -1
  341. package/gsd-core/workflows/secure-phase.md +3 -3
  342. package/gsd-core/workflows/session-report.md +2 -1
  343. package/gsd-core/workflows/settings-advanced.md +7 -9
  344. package/gsd-core/workflows/settings-integrations.md +64 -31
  345. package/gsd-core/workflows/settings.md +69 -7
  346. package/gsd-core/workflows/ship.md +116 -50
  347. package/gsd-core/workflows/sketch-wrap-up.md +11 -17
  348. package/gsd-core/workflows/sketch.md +12 -18
  349. package/gsd-core/workflows/smart-entry.md +3 -5
  350. package/gsd-core/workflows/spec-phase.md +53 -13
  351. package/gsd-core/workflows/spike-wrap-up.md +7 -11
  352. package/gsd-core/workflows/spike.md +20 -31
  353. package/gsd-core/workflows/stats.md +2 -2
  354. package/gsd-core/workflows/sync-skills.md +64 -9
  355. package/gsd-core/workflows/thread.md +11 -7
  356. package/gsd-core/workflows/transition.md +49 -14
  357. package/gsd-core/workflows/ui-phase.md +15 -21
  358. package/gsd-core/workflows/ui-review.md +8 -12
  359. package/gsd-core/workflows/ultraplan-phase.md +5 -13
  360. package/gsd-core/workflows/undo.md +8 -16
  361. package/gsd-core/workflows/update.md +7 -11
  362. package/gsd-core/workflows/validate-phase.md +3 -3
  363. package/gsd-core/workflows/verify-work/steps/automated-ui-verification.md +25 -1
  364. package/gsd-core/workflows/verify-work/steps/mvp-uat-framing.md +1 -1
  365. package/gsd-core/workflows/verify-work.md +66 -25
  366. package/hooks/dist/gsd-agent-isolation-guard.js +158 -30
  367. package/hooks/dist/gsd-check-update-worker.js +56 -13
  368. package/hooks/dist/gsd-check-update.js +19 -1
  369. package/hooks/dist/gsd-config-reload.js +18 -12
  370. package/hooks/dist/gsd-context-monitor.js +19 -10
  371. package/hooks/dist/gsd-cursor-post-tool.js +3 -1
  372. package/hooks/dist/gsd-cursor-pre-tool.js +2 -3
  373. package/hooks/dist/gsd-cursor-session-start.js +2 -1
  374. package/hooks/dist/gsd-cursor-stop.js +2 -1
  375. package/hooks/dist/gsd-cursor-subagent-start.js +83 -3
  376. package/hooks/dist/gsd-cursor-subagent-stop.js +6 -3
  377. package/hooks/dist/gsd-ensure-canonical-path.js +2 -1
  378. package/hooks/dist/gsd-graphify-update.sh +22 -18
  379. package/hooks/dist/gsd-node-runner.sh +76 -0
  380. package/hooks/dist/gsd-phase-boundary.sh +1 -0
  381. package/hooks/dist/gsd-prompt-guard.js +37 -27
  382. package/hooks/dist/gsd-read-guard.js +16 -7
  383. package/hooks/dist/gsd-read-injection-scanner.js +55 -32
  384. package/hooks/dist/gsd-session-state.sh +1 -0
  385. package/hooks/dist/gsd-statusline.js +231 -24
  386. package/hooks/dist/gsd-update-banner.js +22 -1
  387. package/hooks/dist/gsd-validate-commit.sh +80 -6
  388. package/hooks/dist/gsd-windsurf-pre-command.js +16 -11
  389. package/hooks/dist/gsd-windsurf-pre-write.js +22 -13
  390. package/hooks/dist/gsd-workflow-guard.js +162 -46
  391. package/hooks/dist/gsd-worktree-path-guard.js +36 -21
  392. package/hooks/dist/gsd-write-guard.js +35 -25
  393. package/hooks/dist/lib/cli-exit.js +560 -0
  394. package/hooks/dist/lib/exit-code-registry.js +98 -0
  395. package/hooks/dist/lib/git-cmd.js +92 -59
  396. package/hooks/dist/lib/git-probe.js +84 -0
  397. package/hooks/dist/lib/hook-exit.js +81 -0
  398. package/hooks/dist/lib/injection-patterns.js +45 -0
  399. package/hooks/dist/lib/isolation-deny-reason.js +39 -0
  400. package/hooks/dist/lib/isolation-sentinel.js +9 -0
  401. package/hooks/dist/managed-hooks-registry.cjs +3 -0
  402. package/hooks/gsd-agent-isolation-guard.js +158 -30
  403. package/hooks/gsd-check-update-worker.js +56 -13
  404. package/hooks/gsd-check-update.js +19 -1
  405. package/hooks/gsd-config-reload.js +18 -12
  406. package/hooks/gsd-context-monitor.js +19 -10
  407. package/hooks/gsd-cursor-post-tool.js +3 -1
  408. package/hooks/gsd-cursor-pre-tool.js +2 -3
  409. package/hooks/gsd-cursor-session-start.js +2 -1
  410. package/hooks/gsd-cursor-stop.js +2 -1
  411. package/hooks/gsd-cursor-subagent-start.js +83 -3
  412. package/hooks/gsd-cursor-subagent-stop.js +6 -3
  413. package/hooks/gsd-ensure-canonical-path.js +2 -1
  414. package/hooks/gsd-graphify-update.sh +22 -18
  415. package/hooks/gsd-node-runner.sh +76 -0
  416. package/hooks/gsd-phase-boundary.sh +1 -0
  417. package/hooks/gsd-prompt-guard.js +37 -27
  418. package/hooks/gsd-read-guard.js +16 -7
  419. package/hooks/gsd-read-injection-scanner.js +55 -32
  420. package/hooks/gsd-session-state.sh +1 -0
  421. package/hooks/gsd-statusline.js +231 -24
  422. package/hooks/gsd-update-banner.js +22 -1
  423. package/hooks/gsd-validate-commit.sh +80 -6
  424. package/hooks/gsd-windsurf-pre-command.js +16 -11
  425. package/hooks/gsd-windsurf-pre-write.js +22 -13
  426. package/hooks/gsd-workflow-guard.js +162 -46
  427. package/hooks/gsd-worktree-path-guard.js +36 -21
  428. package/hooks/gsd-write-guard.js +35 -25
  429. package/hooks/lib/cli-exit.js +560 -0
  430. package/hooks/lib/exit-code-registry.js +98 -0
  431. package/hooks/lib/git-cmd.js +92 -59
  432. package/hooks/lib/git-probe.js +84 -0
  433. package/hooks/lib/hook-exit.js +81 -0
  434. package/hooks/lib/injection-patterns.js +45 -0
  435. package/hooks/lib/isolation-deny-reason.js +39 -0
  436. package/hooks/lib/isolation-sentinel.js +9 -0
  437. package/hooks/managed-hooks-registry.cjs +3 -0
  438. package/package.json +28 -11
  439. package/pi/gsd.cjs +19 -5
  440. package/scripts/base64-scan.sh +74 -12
  441. package/scripts/baselines/planning-prompt-drift-baseline.json +4 -0
  442. package/scripts/baselines/planning-snapshot-bypass-baseline.json +12 -0
  443. package/scripts/baselines/unreachable-guard-drift-baseline.json +4 -0
  444. package/scripts/build-hooks.js +5 -0
  445. package/scripts/changeset/lint.cjs +60 -5
  446. package/scripts/check-alias-drift.cjs +7 -43
  447. package/scripts/check-contract-drift.cjs +297 -0
  448. package/scripts/check-glossary-refs.cjs +77 -15
  449. package/scripts/check-mutation-score-ratchet.cjs +156 -0
  450. package/scripts/ci-check-job-near-cap.cjs +49 -0
  451. package/scripts/ci-pr-mergeability.cjs +262 -0
  452. package/scripts/ci-test-scope.cjs +64 -14
  453. package/scripts/ci-timeout-report.cjs +230 -0
  454. package/scripts/command-contract-helpers.cjs +903 -1
  455. package/scripts/docs-guard-registry.cjs +396 -0
  456. package/scripts/gen-adr-index.cjs +728 -38
  457. package/scripts/gen-capability-registry.cjs +11 -21
  458. package/scripts/gen-context-index.cjs +2 -11
  459. package/scripts/gen-exit-code-docs.cjs +318 -0
  460. package/scripts/gen-exit-code-registry.cjs +891 -0
  461. package/scripts/gen-features.cjs +836 -0
  462. package/scripts/gen-health-docs.cjs +390 -0
  463. package/scripts/gen-hooks-cli-exit.cjs +239 -0
  464. package/scripts/gen-install-tree-fixtures.cjs +2 -2
  465. package/scripts/gen-inventory-manifest.cjs +50 -4
  466. package/scripts/gen-loop-host-contract.cjs +138 -25
  467. package/scripts/gen-registry.cjs +3 -14
  468. package/scripts/gen-scripts-cli-exit.cjs +185 -0
  469. package/scripts/gen-state-md-docs.cjs +727 -0
  470. package/scripts/{test-failure-reasons.cjs → gsd-test-gate-reasons.cjs} +6 -0
  471. package/scripts/lib/alias-drift-families.cjs +46 -0
  472. package/scripts/lib/ci-job-timing.cjs +72 -0
  473. package/scripts/lib/cli-exit.cjs +546 -44
  474. package/scripts/lib/drift-scan.cjs +308 -0
  475. package/scripts/lib/exit-code-registry.cjs +98 -0
  476. package/scripts/lib/ndjson-reporter.cjs +119 -0
  477. package/scripts/lint-allow-test-rule-refs.allowlist.json +1 -26
  478. package/scripts/lint-allow-test-rule-refs.effective-ceiling.json +4 -0
  479. package/scripts/lint-allow-test-rule-refs.unverified-ceiling.json +3 -0
  480. package/scripts/lint-canary-version-leak.cjs +73 -0
  481. package/scripts/lint-command-contract.cjs +96 -13
  482. package/scripts/lint-completion-predicate-drift.cjs +933 -0
  483. package/scripts/lint-completion-ratio-drift.cjs +214 -0
  484. package/scripts/lint-default-flip-documentation.cjs +193 -0
  485. package/scripts/lint-docs-guard-registration.cjs +495 -0
  486. package/scripts/lint-docs-guard-registration.exempt-baseline.cjs +193 -0
  487. package/scripts/lint-eslint-glob-coverage.allowlist.json +38 -0
  488. package/scripts/lint-eslint-glob-coverage.cjs +340 -0
  489. package/scripts/{lint-fix-has-regression-test.cjs → lint-fix-has-regression-tests.cjs} +12 -6
  490. package/scripts/lint-frontmatter-scalar-broad-grep.cjs +237 -0
  491. package/scripts/lint-health-diagnostic-rule-table.cjs +461 -0
  492. package/scripts/lint-hooks-runtime-build-seam.cjs +262 -0
  493. package/scripts/lint-milestone-window-drift.cjs +468 -0
  494. package/scripts/lint-mutation-test-derivation-drift.cjs +86 -0
  495. package/scripts/lint-phase-enumeration-drift.cjs +492 -0
  496. package/scripts/lint-plan-count-drift.cjs +318 -0
  497. package/scripts/lint-planning-artifact-writer-drift.cjs +398 -0
  498. package/scripts/lint-planning-prompt-drift.cjs +471 -0
  499. package/scripts/lint-planning-snapshot-bypass-drift.cjs +544 -0
  500. package/scripts/lint-regression-test-names.cjs +15 -13
  501. package/scripts/lint-removed-but-needed.cjs +488 -0
  502. package/scripts/lint-seam-enforcement.cjs +182 -0
  503. package/scripts/lint-slug-derivation-drift.cjs +921 -0
  504. package/scripts/lint-source-test-name-collision.cjs +241 -0
  505. package/scripts/lint-state-field-drift.cjs +805 -0
  506. package/scripts/lint-state-write-path-drift.cjs +950 -0
  507. package/scripts/lint-test-file-count.allowlist.json +137 -8
  508. package/scripts/lint-test-file-count.cjs +25 -3
  509. package/scripts/lint-unreachable-guard-drift.cjs +830 -0
  510. package/scripts/lint-vendored-deps.cjs +297 -0
  511. package/scripts/mutation-matrix.cjs +599 -50
  512. package/scripts/pr-changed-files.cjs +63 -0
  513. package/scripts/pr-template-policy.cjs +14 -4
  514. package/scripts/prompt-injection-scan.sh +100 -14
  515. package/scripts/require-issue-link-policy.cjs +192 -0
  516. package/scripts/secret-scan.sh +75 -13
  517. package/scripts/select-docs-guards.cjs +56 -0
  518. package/scripts/sync-runtime-launcher.cjs +24 -7
  519. package/skills/gsd-autonomous/SKILL.md +0 -1
  520. package/skills/gsd-code-review/SKILL.md +1 -1
  521. package/skills/gsd-discuss-phase/SKILL.md +1 -1
  522. package/skills/gsd-execute-phase/SKILL.md +1 -2
  523. package/skills/gsd-import/SKILL.md +1 -1
  524. package/skills/gsd-map-codebase/SKILL.md +1 -1
  525. package/skills/gsd-mempalace-capture/SKILL.md +1 -1
  526. package/skills/gsd-mempalace-recall/SKILL.md +1 -1
  527. package/skills/gsd-new-milestone/SKILL.md +1 -1
  528. package/skills/gsd-next/SKILL.md +0 -1
  529. package/skills/gsd-plan-phase/SKILL.md +0 -1
  530. package/skills/gsd-progress/SKILL.md +0 -1
  531. package/skills/gsd-quick/SKILL.md +9 -5
  532. package/skills/gsd-review-backlog/SKILL.md +2 -1
  533. package/skills/gsd-stats/SKILL.md +0 -1
  534. package/skills/gsd-verify-work/SKILL.md +1 -1
  535. package/vscode/package.json +1 -1
  536. package/bin/lib/ui-safety-gate.cjs +0 -107
  537. package/gsd-core/workflows/discovery-phase.md +0 -298
  538. package/gsd-core/workflows/plan-milestone-gaps.md +0 -281
  539. package/gsd-core/workflows/verify-phase.md +0 -574
  540. package/scripts/affected-tests-lib.cjs +0 -554
  541. package/scripts/lint-allow-test-rule-refs.cjs +0 -162
  542. package/scripts/lint-emitted-drift-ack.cjs +0 -344
  543. package/scripts/run-affected-tests.cjs +0 -7
  544. package/scripts/run-tests.cjs +0 -1051
@@ -0,0 +1,488 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ /**
5
+ * lint-removed-but-needed.cjs — DEFECT.REMOVED-BUT-NEEDED (CONTEXT.md).
6
+ *
7
+ * ## Why
8
+ *
9
+ * A file/key gets removed because "no longer used" without verifying every
10
+ * consumer (workflows, docs, manifests, npm scripts, tests). #3316: root
11
+ * `package-lock.json` was deleted while `package.json` still declares deps
12
+ * and workflows still use `cache: 'npm'` + `npm ci` (which require a
13
+ * lockfile). e3b52c70: docs referenced a removed `/gsd-new-workspace`
14
+ * workflow after it was deleted. #3560: a deleted workflow was pinned by an
15
+ * existence assertion in `tests/phase.test.cjs` — the lint passed clean and
16
+ * the breakage surfaced only as four red tests on the remote runner,
17
+ * because `tests/` was not scanned at all.
18
+ *
19
+ * ## What this checks
20
+ *
21
+ * For every file deleted (`git diff --name-status <base>...HEAD`, status
22
+ * `D`), grep the post-diff tree for the deleted file's basename:
23
+ *
24
+ * - `.github/workflows/`, `gsd-core/`, `docs/` (excluding `docs/adr/**` and
25
+ * `docs/research/**` — see "Historical-record exemption" below),
26
+ * `package.json` — ANY surviving reference fails (the original rule).
27
+ * - `tests/` — scanned with a discriminator (#3565): a reference that PINS
28
+ * existence (`fs.existsSync(path)`, `readFileSync`, `require`, or the
29
+ * basename as a quoted object key) fails; a reference that ASSERTS
30
+ * ABSENCE (`assert.ok(!content.includes('x.md'))`, `!fs.existsSync(...)`)
31
+ * is the correct post-deletion state and passes. The naive widening —
32
+ * flagging every mention — was tried in #3560 and reverted: an absence
33
+ * assertion must contain the basename to assert the file is gone, so
34
+ * without the two kinds separated the guard fires on correct code and
35
+ * gets disabled. A mention that is neither a pin nor an absence
36
+ * assertion (prose, a path assembled on another line) is not flagged —
37
+ * a documented known limit, chosen because a false violation reds a
38
+ * correct tree while a missed prose mention only loses one detection
39
+ * channel.
40
+ *
41
+ * `package-lock.json` deletions additionally fail if any workflow still
42
+ * uses `npm ci` or `cache: 'npm'`/`cache: "npm"` — those depend on a
43
+ * lockfile even though they never spell out its filename.
44
+ *
45
+ * ## False-positive risk (moderate, per audit)
46
+ *
47
+ * A common basename (`index.js`, `config.json`) can coincidentally match an
48
+ * unrelated file, and this only catches LITERAL string references — not a
49
+ * variable holding the filename or a glob that happened to match it.
50
+ *
51
+ * ## Basename-collision refinement (#3907)
52
+ *
53
+ * #3907 deleted `bin/lib/ui-safety-gate.cjs` while the SEPARATE, still-live
54
+ * `gsd-core/bin/lib/ui-safety-gate.cjs` survives — every reference to the
55
+ * survivor (including it referencing itself) was misattributed to the
56
+ * deletion, 14 false violations on a correct tree. This epic is about
57
+ * de-duplicating modules, so "delete one of two files sharing a basename"
58
+ * recurs by construction.
59
+ *
60
+ * The fix is precision, not suppression: for each deleted file, if its
61
+ * basename is ALSO the basename of a file that still exists in the
62
+ * post-diff tree — `git ls-files` (tracked files) UNIONED with the
63
+ * already filesystem-walked corpus/testsCorpus file lists, because
64
+ * `git ls-files` alone misses gitignored BUILD ARTIFACTS such as
65
+ * `gsd-core/bin/lib/*.cjs` (compiled from `.cts`, never committed) —
66
+ * matching switches from the bare basename to the deleted file's full
67
+ * repo-relative path. Because the deletion is already committed on this
68
+ * branch, none of these sources can list the deleted file itself, so any
69
+ * hit is necessarily a DIFFERENT file. A genuine
70
+ * full-path reference to the deleted file is still caught (strictly more
71
+ * precise, not weaker); a bare-basename reference becomes correctly
72
+ * unattributable to the deleted file specifically and is no longer
73
+ * blamed on it. When no surviving file shares the basename, nothing
74
+ * changes — the original basename rule still applies exactly as before.
75
+ *
76
+ * Residual known limit: a bare-basename reference to a deleted file whose
77
+ * basename survives elsewhere is now invisible to this guard (it cannot
78
+ * tell whether the bare mention meant the deleted file or its
79
+ * basename-twin). Same trade the docstring already makes for prose
80
+ * mentions above — a false violation reds a correct tree; a missed
81
+ * ambiguous mention only loses one detection channel.
82
+ *
83
+ * ## Historical-record exemption (#3942)
84
+ *
85
+ * `docs/adr/**` and `docs/research/**` are excluded from the `docs/` scan.
86
+ * An ADR's or a post-mortem's entire job is to record what was retired —
87
+ * naming the deleted file IS the point of the document, not a defect — so
88
+ * without this exemption a PR could never write the ADR that explains its
89
+ * own deletion in the same PR that performs it (it would have to land in a
90
+ * follow-up, after the fact, which is backwards for a decision record).
91
+ * The exemption is narrow and applies only to these two directories: every
92
+ * other document under `docs/` (guides, `TESTING-SUITES.md`, generated
93
+ * indexes, etc.) still enforces "ANY surviving reference fails" exactly as
94
+ * before. `.github/workflows/`, `gsd-core/`, and `package.json` are
95
+ * likewise unaffected — none of those are historical-record surfaces.
96
+ */
97
+
98
+ const fs = require('node:fs');
99
+ const path = require('node:path');
100
+ const cp = require('node:child_process');
101
+ const { ExitError, runMain } = require('./lib/cli-exit.cjs');
102
+ const { escapeRegex } = require('../gsd-core/bin/lib/pattern.cjs');
103
+ const { sanitizeEcho } = require('./command-contract-helpers.cjs');
104
+
105
+ const ROOT = path.join(__dirname, '..');
106
+ const SCAN_ROOTS = ['.github/workflows', 'gsd-core', 'docs'];
107
+ const EXTRA_FILES = ['package.json'];
108
+ const TESTS_ROOT = 'tests';
109
+
110
+ // Skip these when walking SCAN_ROOTS — binary/generated content that can
111
+ // never carry a meaningful basename reference, and is often large.
112
+ const SKIP_EXT = new Set(['.png', '.jpg', '.jpeg', '.gif', '.ico', '.woff', '.woff2', '.ttf', '.zip']);
113
+
114
+ /**
115
+ * Pure: does `content` contain a literal reference to `basename`, delimited
116
+ * by non-identifier/non-path characters on both sides (so "foo.json" doesn't
117
+ * match inside "old-foo.json.bak" style names but does match in a normal
118
+ * path/prose context)?
119
+ * @param {string} content
120
+ * @param {string} basename
121
+ * @returns {boolean}
122
+ */
123
+ function referencesBasename(content, basename) {
124
+ const re = new RegExp(`(^|[^\\w.-])${escapeRegex(basename)}($|[^\\w.-])`);
125
+ return re.test(content);
126
+ }
127
+
128
+ /**
129
+ * Pure: does `content` contain a literal reference to the FULL
130
+ * repo-relative path `relPath` (used instead of {@link referencesBasename}
131
+ * when the deleted file's basename collides with a surviving file — #3907)?
132
+ *
133
+ * Same delimited-match idea as `referencesBasename`, but the left boundary
134
+ * additionally excludes `/` — a `/` immediately to the left would mean the
135
+ * matched text is really a SUFFIX of a longer path (e.g. content contains
136
+ * `gsd-core/bin/lib/x.cjs` and `relPath` is `bin/lib/x.cjs`: without this,
137
+ * the survivor's own path would be mistaken for a reference to the
138
+ * deleted file it merely shares a basename with).
139
+ * @param {string} content
140
+ * @param {string} relPath - repo-relative path, forward-slash separated
141
+ * @returns {boolean}
142
+ */
143
+ function referencesPath(content, relPath) {
144
+ const re = new RegExp(`(^|[^\\w./-])${escapeRegex(relPath)}($|[^\\w.-])`);
145
+ return re.test(content);
146
+ }
147
+
148
+ /**
149
+ * Pure: given the post-diff tree's file list (repo-relative paths), build
150
+ * the set of basenames that still exist. Used to decide, per deleted file,
151
+ * whether basename matching would be ambiguous (#3907).
152
+ * @param {string[]} survivingFiles - repo-relative paths of files present
153
+ * in the post-diff tree (must NOT include the deleted files themselves)
154
+ * @returns {Set<string>}
155
+ */
156
+ function buildSurvivingBasenames(survivingFiles) {
157
+ const set = new Set();
158
+ for (const f of survivingFiles) set.add(path.basename(f));
159
+ return set;
160
+ }
161
+
162
+ /**
163
+ * Pure: given the deleted file's basename, does content contain a
164
+ * lockfile-dependent idiom (`npm ci`, `cache: 'npm'` / `cache: "npm"`)?
165
+ * Only meaningful for package-lock.json deletions.
166
+ * @param {string} content
167
+ * @returns {boolean}
168
+ */
169
+ function referencesNpmLockfileDependency(content) {
170
+ return /\bnpm ci\b/.test(content) || /cache:\s*['"]npm['"]/.test(content);
171
+ }
172
+
173
+ function walk(dir) {
174
+ const out = [];
175
+ let entries;
176
+ try {
177
+ entries = fs.readdirSync(dir, { withFileTypes: true });
178
+ } catch {
179
+ return out;
180
+ }
181
+ for (const entry of entries) {
182
+ const full = path.join(dir, entry.name);
183
+ if (entry.isDirectory()) out.push(...walk(full));
184
+ else if (entry.isFile() && !SKIP_EXT.has(path.extname(entry.name))) out.push(full);
185
+ }
186
+ return out;
187
+ }
188
+
189
+ /**
190
+ * Pure: given a list of deleted basenames and a `{ file, content }[]` corpus
191
+ * of the post-diff tree, find every surviving reference.
192
+ * @param {string[]} deletedFiles - repo-relative deleted paths
193
+ * @param {{ file: string, content: string }[]} corpus
194
+ * @param {Set<string>} [survivingBasenames] - basenames still present in the
195
+ * post-diff tree (#3907); when a deleted file's basename is in this set,
196
+ * matching switches from basename to the deleted file's full path
197
+ * @returns {{ deletedFile: string, referencedIn: string, reason: string }[]}
198
+ */
199
+ function findSurvivingReferences(deletedFiles, corpus, survivingBasenames = new Set()) {
200
+ const violations = [];
201
+ for (const deletedFile of deletedFiles) {
202
+ const basename = path.basename(deletedFile);
203
+ const collides = survivingBasenames.has(basename);
204
+ for (const { file, content } of corpus) {
205
+ const matched = collides ? referencesPath(content, deletedFile) : referencesBasename(content, basename);
206
+ if (matched) {
207
+ violations.push({
208
+ deletedFile,
209
+ referencedIn: file,
210
+ reason: collides
211
+ ? `full path '${deletedFile}' still referenced (basename '${basename}' also belongs to a surviving file, so matched by path — #3907)`
212
+ : `basename '${basename}' still referenced`,
213
+ });
214
+ }
215
+ }
216
+ if (basename === 'package-lock.json') {
217
+ for (const { file, content } of corpus) {
218
+ if (file.startsWith('.github/workflows') && referencesNpmLockfileDependency(content)) {
219
+ violations.push({
220
+ deletedFile,
221
+ referencedIn: file,
222
+ reason: '`npm ci` / `cache: \'npm\'` still present — both require a lockfile',
223
+ });
224
+ }
225
+ }
226
+ }
227
+ }
228
+ return violations;
229
+ }
230
+
231
+ /**
232
+ * Pure: classify ONE line of a test file that references `basename`.
233
+ * Returns 'asserts-absence' | 'pins-existence' | null.
234
+ *
235
+ * - asserts-absence: a NEGATED check carrying the basename —
236
+ * `assert.ok(!content.includes('x.md'))`, `!fs.existsSync(p)`,
237
+ * `!/x\.md/.test(s)`. The basename must appear for the assertion to
238
+ * work, so this is the CORRECT post-deletion state (#3560's reverted
239
+ * naive widening fired exactly here).
240
+ * - pins-existence: a direct dependency on the file existing —
241
+ * `fs.existsSync/readFileSync/statSync/readdirSync/require(...)` on the
242
+ * same line, or the basename as a quoted object key (`'x.md': ...`,
243
+ * the allowlist trap — a key means the test resolves against the file).
244
+ * - null: neither (prose mention, path assembled on another line). Not a
245
+ * violation — see the header's known-limit note.
246
+ *
247
+ * @param {string} line
248
+ * @param {string} basename
249
+ * @param {string} [matchTarget] - text to look for as a quoted object key
250
+ * (#3907: the deleted file's full path when its basename collides with a
251
+ * surviving file; defaults to `basename`, the original behaviour)
252
+ * @returns {'asserts-absence'|'pins-existence'|null}
253
+ */
254
+ function classifyTestReference(line, basename, matchTarget = basename) {
255
+ const negatedCheck =
256
+ /!\s*[A-Za-z_$][\w.$]*\.(includes|indexOf|search|startsWith|endsWith|match)\s*\(/.test(line) ||
257
+ /!\s*(fs\.)?(existsSync|statSync)\s*\(/.test(line) ||
258
+ // [^/\n]* (not .*) so an adversarial line cannot backtrack the regex
259
+ /!\s*\/[^/\n]*\/\s*\.(test|match)\s*\(/.test(line);
260
+ if (negatedCheck) return 'asserts-absence';
261
+ const pinsByCall =
262
+ /\b(existsSync|readFileSync|statSync|readdirSync|require|accessSync)\s*\(/.test(line) ||
263
+ /\brequire\s*\(?['"]/.test(line);
264
+ if (pinsByCall) return 'pins-existence';
265
+ // Template literal needs \\s so the RegExp constructor receives \s — a
266
+ // bare \s in a template literal collapses to 's' and silently matches a
267
+ // literal 's' instead of whitespace.
268
+ if (new RegExp(`['"\`]${escapeRegex(matchTarget)}['"\`]\\s*:`).test(line)) return 'pins-existence';
269
+ return null;
270
+ }
271
+
272
+ /**
273
+ * Pure: for every deleted basename, scan the tests corpus line by line and
274
+ * report each `pins-existence` reference as a violation. `asserts-absence`
275
+ * references are explicitly NOT violations; unclassifiable mentions are
276
+ * skipped (known limit). Violations are per-reference — one test file
277
+ * carrying both an absence assertion and an existence pin reports the pin.
278
+ *
279
+ * @param {string[]} deletedFiles - repo-relative deleted paths
280
+ * @param {{ file: string, content: string }[]} testsCorpus
281
+ * @param {Set<string>} [survivingBasenames] - basenames still present in the
282
+ * post-diff tree (#3907); when a deleted file's basename is in this set,
283
+ * matching switches from basename to the deleted file's full path, same
284
+ * refinement as {@link findSurvivingReferences} so the two scanners
285
+ * cannot disagree about what "a reference" means
286
+ * @returns {{ deletedFile: string, referencedIn: string, reason: string }[]}
287
+ */
288
+ function findSurvivingTestReferences(deletedFiles, testsCorpus, survivingBasenames = new Set()) {
289
+ const violations = [];
290
+ for (const deletedFile of deletedFiles) {
291
+ const basename = path.basename(deletedFile);
292
+ const collides = survivingBasenames.has(basename);
293
+ const matchTarget = collides ? deletedFile : basename;
294
+ // The left-boundary class matches referencesPath's when collides
295
+ // (excludes '/' so a survivor's own longer path isn't mistaken for a
296
+ // reference to the deleted file it merely shares a basename with).
297
+ const refRe = collides
298
+ ? new RegExp(`(^|[^\\w./-])${escapeRegex(matchTarget)}($|[^\\w.-])`)
299
+ : new RegExp(`(^|[^\\w.-])${escapeRegex(matchTarget)}($|[^\\w.-])`);
300
+ for (const { file, content } of testsCorpus) {
301
+ for (const line of content.split(/\r?\n/)) {
302
+ if (!refRe.test(line)) continue;
303
+ const kind = classifyTestReference(line, basename, matchTarget);
304
+ if (kind === 'pins-existence') {
305
+ violations.push({
306
+ deletedFile,
307
+ referencedIn: file,
308
+ // control-char-stripped + capped: this text is echoed into lint
309
+ // output that AI agents read as trusted instructions
310
+ reason: collides
311
+ ? `pins-existence: test depends on deleted path '${matchTarget}' — ${sanitizeEcho(line.trim())}`
312
+ : `pins-existence: test depends on deleted basename '${basename}' — ${sanitizeEcho(line.trim())}`,
313
+ });
314
+ }
315
+ // 'asserts-absence' is correct post-deletion state; null is a
316
+ // documented known limit — neither is a violation.
317
+ }
318
+ }
319
+ }
320
+ return violations;
321
+ }
322
+
323
+ function getDeletedFiles(root, baseRef) {
324
+ // Deliberately let a git failure (unresolvable ref, no merge base, etc.)
325
+ // propagate as a plain Error — main() treats ANY scan() failure as "cannot
326
+ // resolve this base ref in this environment" and degrades to a skip,
327
+ // matching lint-fix-has-regression-tests.cjs. There is no failure mode here
328
+ // that should hard-exit non-zero; a real drift is only ever reported once
329
+ // the diff succeeds and findSurvivingReferences finds a violation.
330
+ const out = cp.execFileSync('git', ['diff', '--name-status', `${baseRef}...HEAD`], {
331
+ cwd: root,
332
+ encoding: 'utf8',
333
+ timeout: 15000,
334
+ });
335
+ return out
336
+ .trim()
337
+ .split('\n')
338
+ .filter(Boolean)
339
+ .filter((line) => line.startsWith('D\t'))
340
+ .map((line) => line.slice(2));
341
+ }
342
+
343
+ /**
344
+ * All tracked files at the current tree state (the post-diff tree, since
345
+ * this scan runs against a branch where the deletion is already committed —
346
+ * `git ls-files` therefore CANNOT list a deleted file, so any hit for a
347
+ * deleted file's basename is necessarily a different, surviving file).
348
+ * Used to build `survivingBasenames` for the #3907 collision refinement.
349
+ * @param {string} root
350
+ * @returns {string[]} repo-relative paths, forward-slash separated
351
+ */
352
+ function getSurvivingFiles(root) {
353
+ const out = cp.execFileSync('git', ['ls-files'], {
354
+ cwd: root,
355
+ encoding: 'utf8',
356
+ timeout: 15000,
357
+ });
358
+ return out
359
+ .trim()
360
+ .split('\n')
361
+ .filter(Boolean)
362
+ .map((f) => f.replace(/\\/g, '/'));
363
+ }
364
+
365
+ // #3942: docs/adr/** and docs/research/** are historical records — an ADR's
366
+ // or a post-mortem's whole job is to narrate what was retired, so naming a
367
+ // file this same PR deletes is the point, not DEFECT.REMOVED-BUT-NEEDED.
368
+ // Narrow and explicit: only these two `docs/` subtrees are exempt; every
369
+ // other document under `docs/` still enforces the original rule unchanged.
370
+ const DOCS_HISTORICAL_RECORD_PREFIXES = ['docs/adr/', 'docs/research/'];
371
+
372
+ /**
373
+ * Pure: is `relFile` (repo-relative, forward-slash separated) inside one of
374
+ * the exempt historical-record directories (#3942)?
375
+ * @param {string} relFile
376
+ * @returns {boolean}
377
+ */
378
+ function isDocsHistoricalRecord(relFile) {
379
+ return DOCS_HISTORICAL_RECORD_PREFIXES.some((prefix) => relFile.startsWith(prefix));
380
+ }
381
+
382
+ function buildCorpus(root) {
383
+ const corpus = [];
384
+ for (const rel of SCAN_ROOTS) {
385
+ for (const abs of walk(path.join(root, rel))) {
386
+ const relFile = path.relative(root, abs).replace(/\\/g, '/');
387
+ if (rel === 'docs' && isDocsHistoricalRecord(relFile)) continue;
388
+ try {
389
+ corpus.push({ file: relFile, content: fs.readFileSync(abs, 'utf8') });
390
+ } catch {
391
+ // unreadable (broken symlink, binary that slipped past SKIP_EXT) — skip
392
+ }
393
+ }
394
+ }
395
+ for (const rel of EXTRA_FILES) {
396
+ const abs = path.join(root, rel);
397
+ try {
398
+ corpus.push({ file: rel, content: fs.readFileSync(abs, 'utf8') });
399
+ } catch {
400
+ // optional file absent — skip
401
+ }
402
+ }
403
+ return corpus;
404
+ }
405
+
406
+ function scan(root, baseRef) {
407
+ const deletedFiles = getDeletedFiles(root, baseRef);
408
+ if (deletedFiles.length === 0) return [];
409
+ const corpus = buildCorpus(root);
410
+ // tests/ arm (#3565): same deletions, discriminated references. A bare
411
+ // mention that pins nothing is skipped; an absence assertion is the
412
+ // correct post-deletion state; only a pin on a deleted file fails.
413
+ const testsCorpus = [];
414
+ for (const abs of walk(path.join(root, TESTS_ROOT))) {
415
+ try {
416
+ testsCorpus.push({
417
+ file: path.relative(root, abs).replace(/\\/g, '/'),
418
+ content: fs.readFileSync(abs, 'utf8'),
419
+ });
420
+ } catch {
421
+ // unreadable (broken symlink, binary that slipped past SKIP_EXT) — skip
422
+ }
423
+ }
424
+ // #3907: per-deletion basename-collision detection — if a surviving file
425
+ // shares the basename, matching switches from basename to full path (see
426
+ // the header's "Basename-collision refinement" section) for BOTH arms.
427
+ // `git ls-files` alone misses gitignored BUILD ARTIFACTS (e.g.
428
+ // gsd-core/bin/lib/*.cjs, compiled from .cts and never committed) —
429
+ // exactly the #3907 collision partner — so it is unioned with the
430
+ // already filesystem-walked corpus/testsCorpus file lists, which do see
431
+ // them.
432
+ const survivingBasenames = buildSurvivingBasenames([
433
+ ...getSurvivingFiles(root),
434
+ ...corpus.map((c) => c.file),
435
+ ...testsCorpus.map((t) => t.file),
436
+ ]);
437
+ const violations = findSurvivingReferences(deletedFiles, corpus, survivingBasenames);
438
+ violations.push(...findSurvivingTestReferences(deletedFiles, testsCorpus, survivingBasenames));
439
+ return violations;
440
+ }
441
+
442
+ function main() {
443
+ const baseRef = `origin/${process.env.GSD_REMOVED_BUT_NEEDED_BASE || process.env.GITHUB_BASE_REF || 'next'}`;
444
+ let violations;
445
+ try {
446
+ violations = scan(ROOT, baseRef);
447
+ } catch (e) {
448
+ // origin/<base> unreachable in this environment (e.g. a shallow local
449
+ // clone with no matching remote-tracking ref) — degrade to a skip rather
450
+ // than a false failure, matching lint-fix-has-regression-tests.cjs.
451
+ console.log(`lint-removed-but-needed: could not resolve ${baseRef}, skipping (${e.message})`);
452
+ return;
453
+ }
454
+ if (violations.length > 0) {
455
+ const detail = violations
456
+ .map((v) => ` ${v.deletedFile} deleted, but still referenced in ${v.referencedIn}: ${v.reason}`)
457
+ .join('\n');
458
+ throw new ExitError(
459
+ 1,
460
+ 'lint-removed-but-needed: a deleted file is still referenced by a live consumer\n'
461
+ + '(DEFECT.REMOVED-BUT-NEEDED). Either restore the file or update every consumer in the\n'
462
+ + 'same commit — do not paper over with a workaround that loses reproducibility:\n'
463
+ + detail,
464
+ );
465
+ }
466
+ console.log('ok lint-removed-but-needed: no deleted file has a surviving reference');
467
+ }
468
+
469
+ module.exports = {
470
+ referencesBasename,
471
+ referencesPath,
472
+ buildSurvivingBasenames,
473
+ referencesNpmLockfileDependency,
474
+ findSurvivingReferences,
475
+ classifyTestReference,
476
+ findSurvivingTestReferences,
477
+ getDeletedFiles,
478
+ getSurvivingFiles,
479
+ buildCorpus,
480
+ isDocsHistoricalRecord,
481
+ scan,
482
+ SCAN_ROOTS,
483
+ EXTRA_FILES,
484
+ TESTS_ROOT,
485
+ DOCS_HISTORICAL_RECORD_PREFIXES,
486
+ };
487
+
488
+ if (require.main === module) runMain(main);
@@ -0,0 +1,182 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ /**
5
+ * #3626: verify every CONTEXT.md `SEAM.<id>.owns=` claim carries a
6
+ * `SEAM.<id>.enforced-by=` pointer that RESOLVES — the named lint rule is
7
+ * registered in eslint.config.mjs and its source file exists, or the named
8
+ * test file exists on disk. Deliberately resolves-only (maintainer decision,
9
+ * chat, 2026-08-27): it does not verify the mechanism's surface actually
10
+ * covers the seam's files — see .gsd/phase/feat-3626-context-seam-claim-gate/40-design.md.
11
+ *
12
+ * Design: .gsd/phase/feat-3626-context-seam-claim-gate/40-design.md
13
+ * Test matrix: .gsd/phase/feat-3626-context-seam-claim-gate/50-test-matrix.md
14
+ *
15
+ * Usage:
16
+ * node scripts/lint-seam-enforcement.cjs [path-to-context-md]
17
+ */
18
+
19
+ const fs = require('fs');
20
+ const path = require('path');
21
+ const { ExitError, runMain } = require('./lib/cli-exit.cjs');
22
+
23
+ const ROOT = path.resolve(__dirname, '..');
24
+ const DEFAULT_CONTEXT_PATH = path.join(ROOT, 'CONTEXT.md');
25
+ const ESLINT_CONFIG_PATH = path.join(ROOT, 'eslint.config.mjs');
26
+
27
+ const SEAM_FACT_RE = /^`SEAM\.([A-Za-z0-9_-]+)\.(owns|enforced-by)=(.*)`\s*$/;
28
+
29
+ /**
30
+ * Extract SEAM.<id>.owns / SEAM.<id>.enforced-by facts from CONTEXT.md text.
31
+ * Any other `*.SEAM.*` line (WORKTREE.SEAM.*, PLANNING.PATH.SEAM.*, ...) does
32
+ * not match this anchored regex and is silently ignored — see design doc
33
+ * "Not-corruption / negative space".
34
+ *
35
+ * Returns { owns: Map<id,string>, enforcedBy: Map<id,string> }.
36
+ */
37
+ function extractSeamFacts(text) {
38
+ const owns = new Map();
39
+ const enforcedBy = new Map();
40
+ for (const rawLine of text.split('\n')) {
41
+ const line = rawLine.trim();
42
+ const match = SEAM_FACT_RE.exec(line);
43
+ if (!match) continue;
44
+ const [, id, key, value] = match;
45
+ if (key === 'owns') {
46
+ owns.set(id, value);
47
+ } else {
48
+ enforcedBy.set(id, value);
49
+ }
50
+ }
51
+ return { owns, enforcedBy };
52
+ }
53
+
54
+ /**
55
+ * Parse a `lint-rule:<name>` or `test:<path>` enforcement pointer value.
56
+ * Returns { scheme: 'lint-rule'|'test'|null, target: string }.
57
+ */
58
+ function parseEnforcementPointer(value) {
59
+ const lintMatch = /^lint-rule:(\S+)$/.exec(value);
60
+ if (lintMatch) return { scheme: 'lint-rule', target: lintMatch[1] };
61
+ const testMatch = /^test:(\S+)$/.exec(value);
62
+ if (testMatch) return { scheme: 'test', target: testMatch[1] };
63
+ return { scheme: null, target: value };
64
+ }
65
+
66
+ /**
67
+ * Pure check function — no filesystem access, everything injected, so the
68
+ * unit tests can drive every branch (dangling pointer, unrecognized scheme,
69
+ * owns-with-no-enforced-by, enforced-by-with-no-owns, ...) without touching
70
+ * disk. `deps.ruleIsRegistered(name)` / `deps.ruleFileExists(name)` /
71
+ * `deps.testFileExists(relPath)` are the three injected filesystem seams.
72
+ *
73
+ * Returns an array of finding strings; empty means clean.
74
+ */
75
+ function checkSeamFacts({ owns, enforcedBy }, deps) {
76
+ const findings = [];
77
+ const ids = new Set([...owns.keys(), ...enforcedBy.keys()]);
78
+
79
+ for (const id of ids) {
80
+ const hasOwns = owns.has(id);
81
+ const hasEnforcedBy = enforcedBy.has(id);
82
+
83
+ if (hasOwns && !hasEnforcedBy) {
84
+ findings.push(`SEAM.${id}: declared (owns=${JSON.stringify(owns.get(id))}) but no enforced-by pointer — no enforcement pointer`);
85
+ continue;
86
+ }
87
+ if (hasEnforcedBy && !hasOwns) {
88
+ findings.push(`SEAM.${id}: has enforced-by but no matching owns claim — enforcement pointer with no ownership claim`);
89
+ continue;
90
+ }
91
+
92
+ const pointerValue = enforcedBy.get(id);
93
+ const { scheme, target } = parseEnforcementPointer(pointerValue);
94
+
95
+ if (scheme === 'lint-rule') {
96
+ if (!deps.ruleIsRegistered(target)) {
97
+ findings.push(`SEAM.${id}: enforced-by=lint-rule:${target} — dangling lint-rule pointer (not registered in eslint.config.mjs)`);
98
+ } else if (!deps.ruleFileExists(target)) {
99
+ findings.push(`SEAM.${id}: enforced-by=lint-rule:${target} — registered rule has no source file`);
100
+ }
101
+ } else if (scheme === 'test') {
102
+ if (!deps.testFileExists(target)) {
103
+ findings.push(`SEAM.${id}: enforced-by=test:${target} — dangling test-anchor pointer (file does not exist)`);
104
+ }
105
+ } else {
106
+ findings.push(`SEAM.${id}: enforced-by=${JSON.stringify(pointerValue)} — unrecognized enforcement-pointer scheme (expected lint-rule: or test:)`);
107
+ }
108
+ }
109
+
110
+ return findings.sort();
111
+ }
112
+
113
+ /**
114
+ * Parse eslint.config.mjs's localPlugin.rules map to get the set of
115
+ * registered rule names, e.g. { 'no-source-grep': ..., 'no-private-binary-resolution': ... }.
116
+ * A textual scan, not a real ESM import — this file is a static, hand-authored
117
+ * literal object (see eslint.config.mjs:35-60), and importing ESM config from
118
+ * a CJS script for one lint gate is not worth the module-system friction.
119
+ */
120
+ function readRegisteredRuleNames(eslintConfigText) {
121
+ const rulesBlockMatch = /const localPlugin = \{\s*rules: \{([\s\S]*?)\},\s*\};/.exec(eslintConfigText);
122
+ if (!rulesBlockMatch) return new Set();
123
+ const names = new Set();
124
+ const keyRe = /'([a-z0-9-]+)':/g;
125
+ let m;
126
+ while ((m = keyRe.exec(rulesBlockMatch[1])) !== null) {
127
+ names.add(m[1]);
128
+ }
129
+ return names;
130
+ }
131
+
132
+ function main() {
133
+ const contextPath = process.argv[2] ? path.resolve(process.argv[2]) : DEFAULT_CONTEXT_PATH;
134
+
135
+ let contextText;
136
+ try {
137
+ contextText = fs.readFileSync(contextPath, 'utf8');
138
+ } catch (error) {
139
+ throw new ExitError(1, `lint-seam-enforcement: failed to read ${contextPath}: ${error.message}`);
140
+ }
141
+
142
+ let eslintConfigText = '';
143
+ try {
144
+ eslintConfigText = fs.readFileSync(ESLINT_CONFIG_PATH, 'utf8');
145
+ } catch {
146
+ eslintConfigText = '';
147
+ }
148
+ const registeredRules = readRegisteredRuleNames(eslintConfigText);
149
+
150
+ const facts = extractSeamFacts(contextText);
151
+ const totalIds = new Set([...facts.owns.keys(), ...facts.enforcedBy.keys()]).size;
152
+
153
+ if (totalIds === 0) {
154
+ process.stdout.write('ok lint-seam-enforcement: 0 SEAM.*.owns/enforced-by claims found in CONTEXT.md — nothing to check\n');
155
+ return 0;
156
+ }
157
+
158
+ const findings = checkSeamFacts(facts, {
159
+ ruleIsRegistered: (name) => registeredRules.has(name),
160
+ ruleFileExists: (name) => fs.existsSync(path.join(ROOT, 'eslint-rules', `${name}.cjs`)),
161
+ testFileExists: (relPath) => fs.existsSync(path.join(ROOT, relPath)),
162
+ });
163
+
164
+ if (findings.length === 0) {
165
+ process.stdout.write(`ok lint-seam-enforcement: ${totalIds} seam claim(s) checked, all enforcement pointers resolve\n`);
166
+ return 0;
167
+ }
168
+
169
+ process.stderr.write(`ERROR lint-seam-enforcement: ${findings.length} unbacked seam claim(s) in ${path.relative(ROOT, contextPath)}\n`);
170
+ for (const finding of findings) {
171
+ process.stderr.write(` - ${finding}\n`);
172
+ }
173
+ process.stderr.write('Every SEAM.<id>.owns claim needs a SEAM.<id>.enforced-by=lint-rule:<name>|test:<path> pointer that resolves.\n');
174
+ process.stderr.write('Back the claim with a real, registered rule or existing test, or correct the prose to stop claiming single ownership.\n');
175
+ return 1;
176
+ }
177
+
178
+ module.exports = { extractSeamFacts, parseEnforcementPointer, checkSeamFacts, readRegisteredRuleNames };
179
+
180
+ if (require.main === module) {
181
+ runMain(main);
182
+ }