@opengsd/gsd-core 1.4.4 → 1.5.0-rc.2

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 (197) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/README.md +3 -3
  3. package/agents/gsd-code-fixer.md +3 -2
  4. package/agents/gsd-debug-session-manager.md +2 -1
  5. package/agents/gsd-debugger.md +4 -3
  6. package/agents/gsd-executor.md +17 -16
  7. package/agents/gsd-intel-updater.md +38 -41
  8. package/agents/gsd-phase-researcher.md +8 -8
  9. package/agents/gsd-plan-checker.md +23 -13
  10. package/agents/gsd-planner.md +32 -188
  11. package/agents/gsd-project-researcher.md +5 -4
  12. package/agents/gsd-research-synthesizer.md +2 -1
  13. package/agents/gsd-ui-researcher.md +2 -1
  14. package/agents/gsd-verifier.md +12 -11
  15. package/bin/install.js +965 -1486
  16. package/commands/gsd/autonomous.md +5 -1
  17. package/commands/gsd/ns-manage.md +8 -1
  18. package/commands/gsd/ns-project.md +5 -0
  19. package/commands/gsd/ns-review.md +4 -1
  20. package/commands/gsd/ns-workflow.md +7 -1
  21. package/commands/gsd/plan-review-convergence.md +5 -4
  22. package/commands/gsd/surface.md +12 -5
  23. package/gemini-extension.json +1 -1
  24. package/gsd-core/bin/gsd-tools.cjs +198 -101
  25. package/gsd-core/bin/gsd_run +20 -0
  26. package/gsd-core/bin/lib/audit-command-router.cjs +61 -0
  27. package/gsd-core/bin/lib/capability-registry.cjs +2234 -0
  28. package/gsd-core/bin/lib/capability-state.cjs +336 -0
  29. package/gsd-core/bin/lib/check-command-router.cjs +133 -2
  30. package/gsd-core/bin/lib/cli-exit.cjs +22 -3
  31. package/gsd-core/bin/lib/config-loader.cjs +716 -0
  32. package/gsd-core/bin/lib/configuration.cjs +4 -34
  33. package/gsd-core/bin/lib/core-utils.cjs +198 -0
  34. package/gsd-core/bin/lib/core.cjs +107 -1817
  35. package/gsd-core/bin/lib/edge-probe.cjs +173 -0
  36. package/gsd-core/bin/lib/fallow-runner.cjs +63 -25
  37. package/gsd-core/bin/lib/federated-config.cjs +182 -0
  38. package/gsd-core/bin/lib/graphify-command-router.cjs +74 -0
  39. package/gsd-core/bin/lib/init.cjs +58 -12
  40. package/gsd-core/bin/lib/install-profiles.cjs +157 -3
  41. package/gsd-core/bin/lib/intel-command-router.cjs +116 -0
  42. package/gsd-core/bin/lib/intel.cjs +3 -3
  43. package/gsd-core/bin/lib/io.cjs +222 -0
  44. package/gsd-core/bin/lib/loop-host-contract.cjs +105 -0
  45. package/gsd-core/bin/lib/loop-resolver.cjs +460 -0
  46. package/gsd-core/bin/lib/model-resolver.cjs +426 -0
  47. package/gsd-core/bin/lib/phase-command-router.cjs +20 -0
  48. package/gsd-core/bin/lib/phase-id.cjs +215 -0
  49. package/gsd-core/bin/lib/phase-locator.cjs +148 -0
  50. package/gsd-core/bin/lib/phase.cjs +17 -0
  51. package/gsd-core/bin/lib/probe-core.cjs +257 -0
  52. package/gsd-core/bin/lib/profile-pipeline.cjs +2 -2
  53. package/gsd-core/bin/lib/roadmap-parser.cjs +443 -0
  54. package/gsd-core/bin/lib/roadmap.cjs +44 -1
  55. package/gsd-core/bin/lib/runtime-artifact-layout.cjs +92 -95
  56. package/gsd-core/bin/lib/runtime-config-adapter-registry.cjs +68 -29
  57. package/gsd-core/bin/lib/runtime-homes.cjs +163 -87
  58. package/gsd-core/bin/lib/runtime-hooks-surface.cjs +1439 -0
  59. package/gsd-core/bin/lib/runtime-name-policy.cjs +2 -1
  60. package/gsd-core/bin/lib/runtime-slash.cjs +7 -2
  61. package/gsd-core/bin/lib/shell-command-projection.cjs +13 -0
  62. package/gsd-core/bin/lib/state-document.cjs +8 -0
  63. package/gsd-core/bin/lib/state.cjs +114 -2
  64. package/gsd-core/bin/lib/surface.cjs +66 -14
  65. package/gsd-core/bin/lib/uat-predicate.cjs +329 -0
  66. package/gsd-core/bin/lib/update-context.cjs +4 -1
  67. package/gsd-core/bin/lib/verify.cjs +104 -1
  68. package/gsd-core/bin/lib/worktree-base-ref.cjs +33 -8
  69. package/gsd-core/bin/shared/model-catalog.json +11 -6
  70. package/gsd-core/bin/shared/runtime-aliases.manifest.json +5 -1
  71. package/gsd-core/references/edge-probe-fixtures/01-round-half-even/expected-coverage.json +7 -0
  72. package/gsd-core/references/edge-probe-fixtures/01-round-half-even/requirements.json +1 -0
  73. package/gsd-core/references/edge-probe-fixtures/02-merge-intervals/expected-coverage.json +8 -0
  74. package/gsd-core/references/edge-probe-fixtures/02-merge-intervals/requirements.json +1 -0
  75. package/gsd-core/references/edge-probe-fixtures/03-truncate-graphemes/expected-coverage.json +7 -0
  76. package/gsd-core/references/edge-probe-fixtures/03-truncate-graphemes/requirements.json +1 -0
  77. package/gsd-core/references/edge-probe-fixtures/04-money-rounding/expected-coverage.json +7 -0
  78. package/gsd-core/references/edge-probe-fixtures/04-money-rounding/requirements.json +1 -0
  79. package/gsd-core/references/edge-probe-fixtures/05-list-dedupe/expected-coverage.json +8 -0
  80. package/gsd-core/references/edge-probe-fixtures/05-list-dedupe/requirements.json +1 -0
  81. package/gsd-core/references/edge-probe-fixtures/06-resolved-mixed/expected-coverage.json +8 -0
  82. package/gsd-core/references/edge-probe-fixtures/06-resolved-mixed/requirements.json +1 -0
  83. package/gsd-core/references/edge-probe-fixtures/06-resolved-mixed/resolutions.json +4 -0
  84. package/gsd-core/references/edge-probe.md +261 -0
  85. package/gsd-core/references/planner-antipatterns.md +41 -0
  86. package/gsd-core/references/planner-guidance.md +186 -0
  87. package/gsd-core/references/planner-reviews.md +5 -2
  88. package/gsd-core/templates/phase-prompt.md +7 -7
  89. package/gsd-core/templates/project.md +19 -2
  90. package/gsd-core/templates/spec.md +12 -0
  91. package/gsd-core/templates/summary-complex.md +1 -0
  92. package/gsd-core/templates/summary-minimal.md +1 -0
  93. package/gsd-core/templates/summary-standard.md +1 -0
  94. package/gsd-core/templates/summary.md +1 -0
  95. package/gsd-core/workflows/_runtime-launcher.snippet.sh +1 -1
  96. package/gsd-core/workflows/add-backlog.md +1 -1
  97. package/gsd-core/workflows/add-phase.md +1 -1
  98. package/gsd-core/workflows/add-tests.md +1 -1
  99. package/gsd-core/workflows/add-todo.md +1 -1
  100. package/gsd-core/workflows/ai-integration-phase.md +1 -1
  101. package/gsd-core/workflows/audit-fix.md +1 -1
  102. package/gsd-core/workflows/audit-milestone.md +1 -1
  103. package/gsd-core/workflows/audit-uat.md +1 -1
  104. package/gsd-core/workflows/autonomous.md +111 -51
  105. package/gsd-core/workflows/check-todos.md +1 -1
  106. package/gsd-core/workflows/cleanup.md +1 -1
  107. package/gsd-core/workflows/code-review-fix.md +6 -4
  108. package/gsd-core/workflows/code-review.md +53 -17
  109. package/gsd-core/workflows/complete-milestone.md +11 -5
  110. package/gsd-core/workflows/debug.md +1 -1
  111. package/gsd-core/workflows/diagnose-issues.md +1 -1
  112. package/gsd-core/workflows/discuss-phase/modes/advisor.md +1 -1
  113. package/gsd-core/workflows/discuss-phase/modes/auto.md +1 -1
  114. package/gsd-core/workflows/discuss-phase/modes/chain.md +1 -1
  115. package/gsd-core/workflows/discuss-phase-assumptions.md +1 -1
  116. package/gsd-core/workflows/discuss-phase.md +8 -1
  117. package/gsd-core/workflows/do.md +1 -1
  118. package/gsd-core/workflows/docs-update.md +1 -1
  119. package/gsd-core/workflows/edit-phase.md +1 -1
  120. package/gsd-core/workflows/eval-review.md +4 -1
  121. package/gsd-core/workflows/execute-phase/steps/codebase-drift-gate.md +1 -1
  122. package/gsd-core/workflows/execute-phase/steps/post-merge-gate.md +1 -1
  123. package/gsd-core/workflows/execute-phase.md +8 -1
  124. package/gsd-core/workflows/execute-plan.md +1 -1
  125. package/gsd-core/workflows/explore.md +1 -1
  126. package/gsd-core/workflows/extract-learnings.md +1 -1
  127. package/gsd-core/workflows/forensics.md +1 -1
  128. package/gsd-core/workflows/graduation.md +1 -1
  129. package/gsd-core/workflows/health.md +1 -1
  130. package/gsd-core/workflows/help/modes/full.md +1 -1
  131. package/gsd-core/workflows/import.md +1 -1
  132. package/gsd-core/workflows/ingest-docs.md +1 -1
  133. package/gsd-core/workflows/insert-phase.md +1 -1
  134. package/gsd-core/workflows/list-workspaces.md +1 -1
  135. package/gsd-core/workflows/manager.md +1 -1
  136. package/gsd-core/workflows/map-codebase.md +1 -1
  137. package/gsd-core/workflows/milestone-summary.md +1 -1
  138. package/gsd-core/workflows/mvp-phase.md +1 -1
  139. package/gsd-core/workflows/new-milestone.md +9 -1
  140. package/gsd-core/workflows/new-project.md +9 -1
  141. package/gsd-core/workflows/new-workspace.md +1 -1
  142. package/gsd-core/workflows/next.md +1 -1
  143. package/gsd-core/workflows/pause-work.md +1 -1
  144. package/gsd-core/workflows/plan-milestone-gaps.md +1 -1
  145. package/gsd-core/workflows/plan-phase.md +65 -28
  146. package/gsd-core/workflows/plan-review-convergence.md +60 -33
  147. package/gsd-core/workflows/plant-seed.md +1 -1
  148. package/gsd-core/workflows/profile-user.md +1 -1
  149. package/gsd-core/workflows/progress.md +1 -1
  150. package/gsd-core/workflows/quick.md +2 -2
  151. package/gsd-core/workflows/remove-phase.md +1 -1
  152. package/gsd-core/workflows/remove-workspace.md +1 -1
  153. package/gsd-core/workflows/resume-project.md +1 -1
  154. package/gsd-core/workflows/review.md +1 -1
  155. package/gsd-core/workflows/scan.md +1 -1
  156. package/gsd-core/workflows/secure-phase.md +1 -1
  157. package/gsd-core/workflows/settings-advanced.md +7 -7
  158. package/gsd-core/workflows/settings-integrations.md +1 -1
  159. package/gsd-core/workflows/settings.md +2 -2
  160. package/gsd-core/workflows/ship.md +8 -1
  161. package/gsd-core/workflows/sketch-wrap-up.md +1 -1
  162. package/gsd-core/workflows/sketch.md +1 -1
  163. package/gsd-core/workflows/spec-phase.md +130 -1
  164. package/gsd-core/workflows/spike-wrap-up.md +1 -1
  165. package/gsd-core/workflows/spike.md +1 -1
  166. package/gsd-core/workflows/stats.md +1 -1
  167. package/gsd-core/workflows/thread.md +1 -1
  168. package/gsd-core/workflows/transition.md +1 -1
  169. package/gsd-core/workflows/ui-phase.md +1 -1
  170. package/gsd-core/workflows/ui-review.md +1 -1
  171. package/gsd-core/workflows/ultraplan-phase.md +1 -1
  172. package/gsd-core/workflows/update.md +2 -2
  173. package/gsd-core/workflows/validate-phase.md +1 -1
  174. package/gsd-core/workflows/verify-phase.md +1 -1
  175. package/gsd-core/workflows/verify-work.md +8 -1
  176. package/package.json +11 -3
  177. package/scripts/base64-scan.sh +1 -1
  178. package/scripts/changeset/cli.cjs +8 -1
  179. package/scripts/changeset/lint.cjs +38 -2
  180. package/scripts/ci-test-scope.cjs +21 -10
  181. package/scripts/gen-capability-registry.cjs +2293 -0
  182. package/scripts/gen-loop-host-contract.cjs +471 -0
  183. package/scripts/lib/allowlist-ratchet.cjs +101 -1
  184. package/scripts/lint-regression-test-names.allowlist.json +269 -0
  185. package/scripts/lint-regression-test-names.cjs +117 -0
  186. package/scripts/lint-test-file-count.allowlist.json +25 -4
  187. package/scripts/lint-windows-test-portability.cjs +178 -0
  188. package/scripts/prompt-injection-scan.sh +4 -4
  189. package/scripts/research-profiles.cjs +10 -10
  190. package/scripts/run-tests.cjs +133 -29
  191. package/scripts/secret-scan.sh +3 -3
  192. package/scripts/sync-next-version.cjs +133 -0
  193. package/scripts/sync-runtime-launcher.cjs +21 -5
  194. package/scripts/update-size-baseline.cjs +68 -0
  195. package/scripts/workflow-policy.cjs +42 -9
  196. package/scripts/workflow-size.cjs +90 -0
  197. package/scripts/run-cross-platform-tests.cjs +0 -67
@@ -0,0 +1,269 @@
1
+ [
2
+ "bug-10-semver-policy-consolidation.test.cjs",
3
+ "bug-130-finishinstall-opencode-testmode.test.cjs",
4
+ "bug-131-release-tarball-smoke-explicit-home.test.cjs",
5
+ "bug-14-progress-auto-flag-dropped.test.cjs",
6
+ "bug-167-query-meta-command.test.cjs",
7
+ "bug-17-askuserquestion-option-cap.test.cjs",
8
+ "bug-170-workflow-fallback-install-hint.test.cjs",
9
+ "bug-1736-local-install-commands.test.cjs",
10
+ "bug-1754-js-hook-guard.test.cjs",
11
+ "bug-1817-sh-hook-guard.test.cjs",
12
+ "bug-1818-unknown-flags.test.cjs",
13
+ "bug-1826-phases-clear-confirm.test.cjs",
14
+ "bug-1829-inherit-model-profile.test.cjs",
15
+ "bug-1834-sh-hooks-installed.test.cjs",
16
+ "bug-1891-file-resolution.test.cjs",
17
+ "bug-190-bridge-collapse.test.cjs",
18
+ "bug-1906-hook-relative-paths.test.cjs",
19
+ "bug-1908-uninstall-manifest.test.cjs",
20
+ "bug-1924-preserve-user-artifacts.test.cjs",
21
+ "bug-1967-cache-invalidation.test.cjs",
22
+ "bug-1974-context-exhaustion-record.test.cjs",
23
+ "bug-2002-offer-next-context.test.cjs",
24
+ "bug-2004-pr-branch-milestone.test.cjs",
25
+ "bug-21-state-md-template-frontmatter.test.cjs",
26
+ "bug-211-launcher-home-fallback.test.cjs",
27
+ "bug-2136-sh-hook-version.test.cjs",
28
+ "bug-214-phase-researcher-write-truncation-contract.test.cjs",
29
+ "bug-214-writer-agents-write-truncation-contract.test.cjs",
30
+ "bug-222-research-synthesizer-write-contract.test.cjs",
31
+ "bug-224-pick-stdout-capture.test.cjs",
32
+ "bug-2248-local-install-statusline.test.cjs",
33
+ "bug-2256-model-overrides-transport.test.cjs",
34
+ "bug-2268-parallel-discuss.test.cjs",
35
+ "bug-2344-read-guard-claudecode-env.test.cjs",
36
+ "bug-2346-agent-read-loop-guards.test.cjs",
37
+ "bug-2351-intel-kilo-layout.test.cjs",
38
+ "bug-2376-opencode-windows-home-path.test.cjs",
39
+ "bug-2384-post-merge-deletion-audit.test.cjs",
40
+ "bug-2388-plan-phase-no-branch-rename.test.cjs",
41
+ "bug-2396-makefile-test-priority.test.cjs",
42
+ "bug-2399-commit-docs-plan-phase.test.cjs",
43
+ "bug-2410-stream-checkpoint-heartbeats.test.cjs",
44
+ "bug-2418-antigravity-bare-path.test.cjs",
45
+ "bug-2419-project-researcher-agent.test.cjs",
46
+ "bug-2421-planner-grep-gate-hygiene.test.cjs",
47
+ "bug-2424-reapply-patches-baseline-detection.test.cjs",
48
+ "bug-2432-quick-plan-predispatch-commit.test.cjs",
49
+ "bug-2451-context-monitor-over-report.test.cjs",
50
+ "bug-2470-update-md-claude-path.test.cjs",
51
+ "bug-2492-context-coverage-gate.test.cjs",
52
+ "bug-2501-resurrection-detection.test.cjs",
53
+ "bug-2502-insert-phase-state-update.test.cjs",
54
+ "bug-2504-uat-foundation-phases.test.cjs",
55
+ "bug-2506-settings-profile-nonclaude-warning.test.cjs",
56
+ "bug-2516-inherit-model-execute-phase.test.cjs",
57
+ "bug-2520-read-guard-hook-subprocess-env.test.cjs",
58
+ "bug-2523-quick-deferred-items.test.cjs",
59
+ "bug-2530-valid-config-keys.test.cjs",
60
+ "bug-2543-gsd-slash-namespace.test.cjs",
61
+ "bug-2545-copilot-unreplaced-paths.test.cjs",
62
+ "bug-2549-2550-2552-discuss-phase-context.test.cjs",
63
+ "bug-2554-decimal-phase-filter.test.cjs",
64
+ "bug-2557-gemini-local-hook-paths.test.cjs",
65
+ "bug-2559-stale-search-year.test.cjs",
66
+ "bug-260-worktree-path-guard.test.cjs",
67
+ "bug-2601-inherit-model-profile.test.cjs",
68
+ "bug-261-worktree-force-add-guard.test.cjs",
69
+ "bug-2630-state-frontmatter-milestone-switch.test.cjs",
70
+ "bug-2638-sub-repos-canonical-location.test.cjs",
71
+ "bug-2643-skill-frontmatter-name.test.cjs",
72
+ "bug-2659-audit-open-crash.test.cjs",
73
+ "bug-2660-one-liner-extraction.test.cjs",
74
+ "bug-2661-roadmap-sync-parallel.test.cjs",
75
+ "bug-2686-review-fix-worktree.test.cjs",
76
+ "bug-2698-crlf-install.test.cjs",
77
+ "bug-2760-codex-install-defensive.test.cjs",
78
+ "bug-2769-requirements-header-variants.test.cjs",
79
+ "bug-2770-annotate-deps-int-coerce.test.cjs",
80
+ "bug-2771-user-profile-manifest.test.cjs",
81
+ "bug-2772-gitmodules-path-intersection.test.cjs",
82
+ "bug-2784-update-cache-clear-path.test.cjs",
83
+ "bug-279-codex-agent-mapping.test.cjs",
84
+ "bug-2794-opencode-model-profile-overrides.test.cjs",
85
+ "bug-2798-context-window-config-key.test.cjs",
86
+ "bug-2801-ingest-docs-handler.test.cjs",
87
+ "bug-2808-skill-hyphen-name.test.cjs",
88
+ "bug-2831-opencode-home-path-prefix.test.cjs",
89
+ "bug-2836-audit-open-summary-uat-drift.test.cjs",
90
+ "bug-2838-summary-rescue-gitignored-planning.test.cjs",
91
+ "bug-2839-review-fix-transactional-cleanup.test.cjs",
92
+ "bug-2851-workflow-bare-gsd-tools.test.cjs",
93
+ "bug-2866-codex-strip-no-trailing-newline.test.cjs",
94
+ "bug-2876-skill-frontmatter-quote.test.cjs",
95
+ "bug-2911-audit-open-output-shape.test.cjs",
96
+ "bug-2912-progress-context-authority.test.cjs",
97
+ "bug-2916-handle-branching-default-base.test.cjs",
98
+ "bug-2942-detect-custom-skills.test.cjs",
99
+ "bug-2943-config-get-context-window-default.test.cjs",
100
+ "bug-2948-spike-wrap-up-dispatch.test.cjs",
101
+ "bug-2949-sketch-wrap-up-dispatch.test.cjs",
102
+ "bug-2950-stale-command-refs.test.cjs",
103
+ "bug-2954-help-md-slash-command-stubs.test.cjs",
104
+ "bug-2957-claude-global-postinstall-message.test.cjs",
105
+ "bug-2969-verify-reapply-patches.test.cjs",
106
+ "bug-2973-profile-user-skills-path.test.cjs",
107
+ "bug-2979-hook-absolute-node.test.cjs",
108
+ "bug-2986-config-schema-mutation-killers.test.cjs",
109
+ "bug-2990-code-fixer-worktree-branch.test.cjs",
110
+ "bug-2992-check-latest-version.test.cjs",
111
+ "bug-2994-verify-reapply-patches-installed-path.test.cjs",
112
+ "bug-2995-post-install-script-paths.test.cjs",
113
+ "bug-2998-pristine-dir-populated.test.cjs",
114
+ "bug-3017-codex-hook-absolute-node.test.cjs",
115
+ "bug-3018-codex-discuss-fallback.test.cjs",
116
+ "bug-3019-help-passthrough.test.cjs",
117
+ "bug-3037-gemini-duplicate-commands.test.cjs",
118
+ "bug-3050-update-backup-eacces-nonfatal.test.cjs",
119
+ "bug-3054-stale-gsd-next-references.test.cjs",
120
+ "bug-3072-optional-sketch-findings-guard.test.cjs",
121
+ "bug-3083-resume-route-clear.test.cjs",
122
+ "bug-3086-git-create-tag-config-gate.test.cjs",
123
+ "bug-3087-planner-directive-language.test.cjs",
124
+ "bug-3096-ai-integration-phase-parallel-race.test.cjs",
125
+ "bug-3097-3099-executor-worktree-path-safety.test.cjs",
126
+ "bug-3120-secure-phase-empty-register.test.cjs",
127
+ "bug-3126-global-skills-base-runtime-path.test.cjs",
128
+ "bug-3127-state-begin-phase-idempotent.test.cjs",
129
+ "bug-3128-roadmap-plan-count-slug-layout.test.cjs",
130
+ "bug-3129-validate-commit-git-bypass.test.cjs",
131
+ "bug-3130-update-npx-robust-invocation.test.cjs",
132
+ "bug-3135-capture-backlog-workflow.test.cjs",
133
+ "bug-3150-stats-json-decimal-phase-gaps.test.cjs",
134
+ "bug-3156-plan-phase-opencode-dispatch.test.cjs",
135
+ "bug-3163-codex-agents-md.test.cjs",
136
+ "bug-3168-task-to-agent-rename.test.cjs",
137
+ "bug-3181-node-cellar-path.test.cjs",
138
+ "bug-3195-quick-resurrection-guard.test.cjs",
139
+ "bug-3197-gsd-tools-config-whitelist.test.cjs",
140
+ "bug-321-config-defaults-clone-strategy.test.cjs",
141
+ "bug-3212-execute-phase-stall-safe-resume.test.cjs",
142
+ "bug-3227-config-set-model-overrides.test.cjs",
143
+ "bug-3236-capture-seed-one-shot.test.cjs",
144
+ "bug-3242-state-update-progress-trample.test.cjs",
145
+ "bug-3243-dotted-command-form.test.cjs",
146
+ "bug-3245-codex-toml-floats.test.cjs",
147
+ "bug-3257-nested-plans-undercount.test.cjs",
148
+ "bug-3258-no-stale-gsd-intel-references.test.cjs",
149
+ "bug-3275-fmstr-non-string-scalars.test.cjs",
150
+ "bug-3285-codex-hooks-state-allowed.test.cjs",
151
+ "bug-3286-state-write-routing.test.cjs",
152
+ "bug-3288-model-catalog-install-path.test.cjs",
153
+ "bug-3290-intel-updater-layout-block.test.cjs",
154
+ "bug-33-settings-model-profile-adaptive.test.cjs",
155
+ "bug-3320-planner-deep-work-rules.test.cjs",
156
+ "bug-3321-verifier-runs-probes.test.cjs",
157
+ "bug-3346-codex-aot-toml-key.test.cjs",
158
+ "bug-3357-codex-legacy-hooks-json-migration.test.cjs",
159
+ "bug-3360-codex-execute-phase-worktrees.test.cjs",
160
+ "bug-338-local-install-settings-local-json.test.cjs",
161
+ "bug-3381-verify-work-workstream.test.cjs",
162
+ "bug-3384-secondary-defects.test.cjs",
163
+ "bug-3407-pristine-stale-content.test.cjs",
164
+ "bug-3413-shell-command-projection.test.cjs",
165
+ "bug-3418-progress-flag-routing.test.cjs",
166
+ "bug-3426-codex-windows-hooks.test.cjs",
167
+ "bug-3427-3433-codex-install-shape.test.cjs",
168
+ "bug-3430-planner-phase-contract.test.cjs",
169
+ "bug-3431-debug-command-yaml.test.cjs",
170
+ "bug-3441-path-action-projection.test.cjs",
171
+ "bug-3442-codex-legacy-hooks-json-migration.test.cjs",
172
+ "bug-3442-shim-projection-drift-guard.test.cjs",
173
+ "bug-3446-resume-continue-here-discovery.test.cjs",
174
+ "bug-3454-state-dollar-backreference-growth.test.cjs",
175
+ "bug-3489-complete-phase-idempotent.test.cjs",
176
+ "bug-3491-nested-git-worktree.test.cjs",
177
+ "bug-3509-path-spaces.test.cjs",
178
+ "bug-3516-reapply-patches-gsd-update-filter.test.cjs",
179
+ "bug-3521-quick-cleanup-cwd-pin.test.cjs",
180
+ "bug-3523-cjs-loadconfig-branching-strategy-warning.test.cjs",
181
+ "bug-3537-padded-id-against-unpadded-roadmap.test.cjs",
182
+ "bug-3541-installer-migration-prompt-user-resolution.test.cjs",
183
+ "bug-3542-executor-git-stash-prohibition.test.cjs",
184
+ "bug-3562-codex-install-skill-surface.test.cjs",
185
+ "bug-3566-codex-hooks-feature-canonical-key.test.cjs",
186
+ "bug-3571-configuration-manifest-install-path.test.cjs",
187
+ "bug-3582-codex-skills-materialized.test.cjs",
188
+ "bug-3584-runtime-slash-emitters.test.cjs",
189
+ "bug-3584-runtime-slash-formatter.test.cjs",
190
+ "bug-3588-npm-audit-clean.test.cjs",
191
+ "bug-3599-roadmap-get-phase-project-code-prefix.test.cjs",
192
+ "bug-3605-stale-research-insert-phase-agent-refs.test.cjs",
193
+ "bug-3608-antigravity-update-runtime-classification.test.cjs",
194
+ "bug-3610-installer-migration-bundled-hooks-classification.test.cjs",
195
+ "bug-3628-bundled-hook-classifier-whitelist.test.cjs",
196
+ "bug-3631-router-raw-flag.test.cjs",
197
+ "bug-3657-verify-reapply-patches-pristine-drift.test.cjs",
198
+ "bug-3659-applysurface-prune-skill-dirs.test.cjs",
199
+ "bug-3668-workflow-runtime-resolution.test.cjs",
200
+ "bug-3670-cursor-local-install-migration-lock.test.cjs",
201
+ "bug-3677-agent-colon-namespace-leak.test.cjs",
202
+ "bug-3678-executor-commit-docs-respect.test.cjs",
203
+ "bug-3683-command-colon-namespace-leak.test.cjs",
204
+ "bug-3683-command-cross-reference-invariant.test.cjs",
205
+ "bug-3683-workflow-colon-namespace-leak.test.cjs",
206
+ "bug-3689-resume-glob-nomatch.test.cjs",
207
+ "bug-3691-annotate-deps-plans-block-variants.test.cjs",
208
+ "bug-3706-ui-safety-gate-false-positives.test.cjs",
209
+ "bug-3707-locked-worktree-cleanup.test.cjs",
210
+ "bug-3727-code-review-fix-flag-dispatch.test.cjs",
211
+ "bug-3735-profiles-core-includes-surface.test.cjs",
212
+ "bug-3739-gap-checker-padded-prefix-context.test.cjs",
213
+ "bug-376-claude-js-hook-gsd-rewriter.test.cjs",
214
+ "bug-378-update-check-scoped-name.test.cjs",
215
+ "bug-3784-gsd-settings-model-profile-ui-omits-adaptive.test.cjs",
216
+ "bug-3805-fast-md-log-to-state-schema.test.cjs",
217
+ "bug-3808-codex-adapter-text-mode-fallback.test.cjs",
218
+ "bug-3810-no-gsd-sdk-runtime-refs.test.cjs",
219
+ "bug-384-agents-runtime-aware.test.cjs",
220
+ "bug-397-state-preserve-executor-authored.test.cjs",
221
+ "bug-410-install-defaults-test-mode-guard.test.cjs",
222
+ "bug-416-archive-dir-null.test.cjs",
223
+ "bug-442-config-dir-equals-in-path.test.cjs",
224
+ "bug-444-resolver-local-claude-install.test.cjs",
225
+ "bug-447-gap-analysis-phase-req-ids.test.cjs",
226
+ "bug-474-clock-seam-date-determinism.test.cjs",
227
+ "bug-492-effort-manifest-fallback.test.cjs",
228
+ "bug-500-planned-phase-progress-corruption.test.cjs",
229
+ "bug-501-flat-phase-details-milestone-leak.test.cjs",
230
+ "bug-503-update-agent-antigravity-detection.test.cjs",
231
+ "bug-505-remove-dead-sdk-verification.test.cjs",
232
+ "bug-549-total-phases-overcounts-with-phase-section-heading.test.cjs",
233
+ "bug-557-details-summary-milestone-strip.test.cjs",
234
+ "bug-570-codex-leak-scanner.test.cjs",
235
+ "bug-571-doc-writer-fix-mode-edit-only.test.cjs",
236
+ "bug-580-local-sh-hook-bash-wrapper.test.cjs",
237
+ "bug-619-codebase-drift-gate-shim.test.cjs",
238
+ "bug-621-plan-phase-gap-analysis-gsd-run.test.cjs",
239
+ "bug-622-graphify-optional-graph-html.test.cjs",
240
+ "bug-630-wave-cleanup-orchestrator-root.test.cjs",
241
+ "bug-637-workflow-no-hardcoded-home-tool.test.cjs",
242
+ "bug-641-files-from-suite-token.test.cjs",
243
+ "bug-663-redos-roadmap-phase-parsing.test.cjs",
244
+ "bug-685-windowshide-spawn.test.cjs",
245
+ "bug-687-agy-timeout.test.cjs",
246
+ "bug-704-codex-launcher-path-corruption.test.cjs",
247
+ "bug-730-milestone-phase-details-scope.test.cjs",
248
+ "bug-782-cline-skills-emission.test.cjs",
249
+ "bug-783-kilo-global-skills-base.test.cjs",
250
+ "bug-851-codex-quick-adapter-agent-type-fallback.test.cjs",
251
+ "bug-853-bg-dispatch-runtime-gating.test.cjs",
252
+ "bug-866-profile-pipeline-temp-root.test.cjs",
253
+ "bug-891-non-claude-runtime-home-fallback.test.cjs",
254
+ "bug-892-validate-checklist-roadmap-phases.test.cjs",
255
+ "bug-905-state-syncstatefrontmatter-preserve-scalars.test.cjs",
256
+ "bug-924-claude-flat-skill-layout.test.cjs",
257
+ "bug-925-context-monitor-hook-event-name.test.cjs",
258
+ "bug-936-no-nested-spawner-wrap.test.cjs",
259
+ "bug-941-managed-hooks-registry-manifest.test.cjs",
260
+ "bug-947-hermes-gsd-prefix.test.cjs",
261
+ "bug-948-state-noop-write-guard.test.cjs",
262
+ "bug-950-quick-summary-status-complete.test.cjs",
263
+ "bug-967-verify-key-links-strict-paths.test.cjs",
264
+ "bug-969-test-infra-flake-hardening.test.cjs",
265
+ "bug-974-graphify-budget-missing-value.test.cjs",
266
+ "bug-977-fnm-multishell-path.test.cjs",
267
+ "bug-978-milestone-complete-force.test.cjs",
268
+ "bug-983-trae-windsurf-claude-path-leak.test.cjs"
269
+ ]
@@ -0,0 +1,117 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ /**
5
+ * lint-regression-test-names.cjs — ban NEW top-level bug-NNNN test files.
6
+ *
7
+ * ## Why
8
+ *
9
+ * The 2026-06 CI audit found 244 one-off `tests/bug-NNNN-*.test.cjs` files —
10
+ * ~38% of the suite. `node --test` spawns one child process per FILE, so file
11
+ * count (not test count) is the unit of CI overhead, and it is worst on the
12
+ * Windows lanes where every spawn is Defender-scanned. Each regression test
13
+ * belongs in the owning module's main test file as a regression case (e.g. a
14
+ * `describe('regressions')` block in `tests/<module>.test.cjs`), where it
15
+ * costs zero additional processes.
16
+ *
17
+ * ## What this enforces
18
+ *
19
+ * Identity ratchet (scripts/lib/allowlist-ratchet.cjs) over basenames matching
20
+ * /^bug-\d+.*\.test\.cjs$/ in tests/:
21
+ * - A NEW bug-* file (not in the allowlist) fails: fold the regression into
22
+ * the owning module's test file instead.
23
+ * - A REMOVED bug-* file with a stale allowlist entry also fails: prune the
24
+ * entry from scripts/lint-regression-test-names.allowlist.json so the
25
+ * baseline only ever shrinks.
26
+ *
27
+ * ## --update (allowlist drift repair)
28
+ *
29
+ * `node scripts/lint-regression-test-names.cjs --update` regenerates the
30
+ * allowlist from the files currently in tests/ and reports what changed.
31
+ * Use it when the failure is INHERITED drift, not your own new file — e.g.
32
+ * the base branch merged bug-* files without feeding the allowlist (the
33
+ * #947/#948/#950 race after the ratchet landed), or after a rebase. The
34
+ * allowlist is a snapshot artifact: regenerate it AFTER rebasing, never
35
+ * carry a pre-rebase copy through.
36
+ *
37
+ * See docs/TESTING-SUITES.md ("Regression tests") for the placement policy.
38
+ */
39
+
40
+ const fs = require('fs');
41
+ const path = require('path');
42
+ const { assertWithinAllowlist } = require('./lib/allowlist-ratchet.cjs');
43
+ const { ExitError, runMain } = require('./lib/cli-exit.cjs');
44
+
45
+ const ROOT = path.join(__dirname, '..');
46
+ // Env overrides exist for the lint's own tests only (sandbox fixture dirs).
47
+ const TESTS_DIR = process.env.GSD_LINT_REGRESSION_TESTS_DIR || path.join(ROOT, 'tests');
48
+ const ALLOWLIST_PATH =
49
+ process.env.GSD_LINT_REGRESSION_ALLOWLIST ||
50
+ path.join(__dirname, 'lint-regression-test-names.allowlist.json');
51
+
52
+ const BUG_FILE_RE = /^bug-\d+.*\.test\.cjs$/;
53
+
54
+ function main() {
55
+ const args = process.argv.slice(2);
56
+ const update = args.includes('--update');
57
+ const unknown = args.filter(a => a !== '--update');
58
+ if (unknown.length > 0) {
59
+ throw new ExitError(2, `lint-regression-test-names: unknown argument(s): ${unknown.join(', ')}`);
60
+ }
61
+
62
+ const current = fs
63
+ .readdirSync(TESTS_DIR)
64
+ .filter(f => BUG_FILE_RE.test(f))
65
+ .sort();
66
+ const known = JSON.parse(fs.readFileSync(ALLOWLIST_PATH, 'utf8'));
67
+
68
+ if (update) {
69
+ const knownSet = new Set(known);
70
+ const currentSet = new Set(current);
71
+ const added = current.filter(f => !knownSet.has(f));
72
+ const pruned = known.filter(f => !currentSet.has(f));
73
+ if (added.length === 0 && pruned.length === 0) {
74
+ console.log(`lint-regression-test-names --update: allowlist already in sync (${current.length} entries)`);
75
+ return;
76
+ }
77
+ fs.writeFileSync(ALLOWLIST_PATH, JSON.stringify(current, null, 2) + '\n');
78
+ console.log(
79
+ `lint-regression-test-names --update: ${known.length} -> ${current.length} entries` +
80
+ (added.length ? ` | grandfathered: ${added.join(', ')}` : '') +
81
+ (pruned.length ? ` | pruned: ${pruned.join(', ')}` : '')
82
+ );
83
+ console.log('Commit the regenerated allowlist with your change.');
84
+ return;
85
+ }
86
+
87
+ const failures = [];
88
+ const { novel } = assertWithinAllowlist({
89
+ label: 'regression-test-names',
90
+ current,
91
+ known,
92
+ fail: msg => failures.push(msg),
93
+ pruneHint: 'edit scripts/lint-regression-test-names.allowlist.json',
94
+ });
95
+
96
+ if (failures.length > 0) {
97
+ for (const msg of failures) console.error(msg);
98
+ if (novel.length > 0) {
99
+ console.error(
100
+ '\nIf this PR added the file(s) above: new bug-NNNN test files are not ' +
101
+ "accepted — add the regression case to the owning module's test file " +
102
+ "(e.g. a describe('regressions') block in tests/<module>.test.cjs) instead.\n" +
103
+ 'If the file(s) came from the base branch (inherited allowlist drift, ' +
104
+ 'e.g. after a rebase): run ' +
105
+ '`node scripts/lint-regression-test-names.cjs --update` and commit the ' +
106
+ 'regenerated allowlist. See docs/TESTING-SUITES.md.'
107
+ );
108
+ }
109
+ throw new ExitError(1);
110
+ }
111
+
112
+ console.log(
113
+ `ok lint-regression-test-names: ${current.length} grandfathered bug-* file(s), no novel offenders`
114
+ );
115
+ }
116
+
117
+ runMain(main);
@@ -3,6 +3,7 @@
3
3
  "modules": {
4
4
  "audit": {
5
5
  "files": [
6
+ "audit-command-cutover.test.cjs",
6
7
  "audit-fix-command.test.cjs",
7
8
  "bug-2659-audit-open-crash.test.cjs",
8
9
  "bug-2836-audit-open-summary-uat-drift.test.cjs",
@@ -19,14 +20,17 @@
19
20
  "config-field-docs.test.cjs",
20
21
  "config-get-default.test.cjs",
21
22
  "config-schema.property.test.cjs",
22
- "config.test.cjs"
23
+ "config.test.cjs",
24
+ "enh-1055-config-intent-descriptor-drive.test.cjs"
23
25
  ],
24
26
  "issue": "TBD"
25
27
  },
26
28
  "graphify": {
27
29
  "files": [
28
30
  "bug-622-graphify-optional-graph-html.test.cjs",
29
- "graphify-auto-update.test.cjs",
31
+ "bug-974-graphify-budget-missing-value.test.cjs",
32
+ "graphify-auto-update.slow.test.cjs",
33
+ "graphify-command-cutover.test.cjs",
30
34
  "graphify-query.test.cjs",
31
35
  "graphify-visualization.test.cjs",
32
36
  "graphify.test.cjs"
@@ -37,6 +41,7 @@
37
41
  "files": [
38
42
  "bug-2351-intel-kilo-layout.test.cjs",
39
43
  "bug-3290-intel-updater-layout-block.test.cjs",
44
+ "intel-command-cutover.test.cjs",
40
45
  "intel.test.cjs"
41
46
  ],
42
47
  "issue": "TBD"
@@ -44,6 +49,7 @@
44
49
  "milestone": {
45
50
  "files": [
46
51
  "bug-730-milestone-phase-details-scope.test.cjs",
52
+ "bug-978-milestone-complete-force.test.cjs",
47
53
  "milestone-archive.test.cjs",
48
54
  "milestone-helper.test.cjs",
49
55
  "milestone-prefixed-convention.test.cjs",
@@ -74,6 +80,7 @@
74
80
  },
75
81
  "runtime-artifact-layout": {
76
82
  "files": [
83
+ "runtime-artifact-layout-descriptor-drive.test.cjs",
77
84
  "runtime-artifact-layout-install-profiles.test.cjs",
78
85
  "runtime-artifact-layout-surface.test.cjs",
79
86
  "runtime-artifact-layout.test.cjs"
@@ -82,8 +89,8 @@
82
89
  },
83
90
  "security": {
84
91
  "files": [
85
- "security-prompt-injection.test.cjs",
86
- "security-scan.test.cjs",
92
+ "security-prompt-injection.security.test.cjs",
93
+ "security-scan.security.test.cjs",
87
94
  "security.test.cjs"
88
95
  ],
89
96
  "issue": "TBD"
@@ -98,6 +105,7 @@
98
105
  "bug-3454-state-dollar-backreference-growth.test.cjs",
99
106
  "bug-397-state-preserve-executor-authored.test.cjs",
100
107
  "bug-905-state-syncstatefrontmatter-preserve-scalars.test.cjs",
108
+ "bug-948-state-noop-write-guard.test.cjs",
101
109
  "state-acquirestatelock-non-eexist.test.cjs",
102
110
  "state-prune.test.cjs",
103
111
  "state.test.cjs"
@@ -110,6 +118,7 @@
110
118
  "bug-2994-verify-reapply-patches-installed-path.test.cjs",
111
119
  "bug-3381-verify-work-workstream.test.cjs",
112
120
  "bug-3657-verify-reapply-patches-pristine-drift.test.cjs",
121
+ "bug-967-verify-key-links-strict-paths.test.cjs",
113
122
  "verify-health.test.cjs",
114
123
  "verify-mvp-uat.test.cjs",
115
124
  "verify-npm-publish.test.cjs",
@@ -122,8 +131,11 @@
122
131
  "install": {
123
132
  "files": [
124
133
  "bug-410-install-defaults-test-mode-guard.test.cjs",
134
+ "enh-1077-install-hook-events-dialect-drive.test.cjs",
135
+ "enh-1082-install-plan-capstone.test.cjs",
125
136
  "enh-776-install-gemini-hook-events.test.cjs",
126
137
  "install-minimal-hooks.test.cjs",
138
+ "install-nested-layout.test.cjs",
127
139
  "install-path-detection.test.cjs",
128
140
  "install-regressions.test.cjs",
129
141
  "install-runtime-artifacts.test.cjs",
@@ -139,6 +151,15 @@
139
151
  "validate-context.test.cjs"
140
152
  ],
141
153
  "issue": "892"
154
+ },
155
+ "edge-probe": {
156
+ "files": [
157
+ "edge-probe-docs-fixtures.test.cjs",
158
+ "edge-probe-planner-contract.test.cjs",
159
+ "edge-probe-spec-phase-contract.test.cjs",
160
+ "edge-probe.test.cjs"
161
+ ],
162
+ "issue": "550"
142
163
  }
143
164
  }
144
165
  }
@@ -0,0 +1,178 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * lint-windows-test-portability.cjs — flag tests that combine chmod exec-bit
5
+ * with bare sh/bash -c without a platform guard.
6
+ *
7
+ * ## Why
8
+ *
9
+ * Windows Git Bash (msys2) does not honour Node's chmod exec bit for
10
+ * PATH-executing extension-less scripts. A test that (a) makes a fixture
11
+ * executable via chmodSync and (b) runs it with `sh -c`/`bash -c` will pass
12
+ * on Mac/Linux but fail only in the CI `test (windows-latest, *)` /
13
+ * `full test (windows-latest, *)` lanes, producing a hard-to-diagnose
14
+ * false-negative gate. See CONTEXT.md → DEFECT.WINDOWS-TEST-PORTABILITY.
15
+ *
16
+ * ## What this enforces
17
+ *
18
+ * For every file in tests/**\/*.test.cjs (recursive, excluding node_modules):
19
+ * - makesExecutable: contains chmodSync?( with an exec-bit octal literal
20
+ * - shellDashC: contains a sh/bash -c invocation (array form or string literal)
21
+ * - guarded: contains a process.platform / os.platform() / win32 / isWindows guard
22
+ * - optOut: contains the literal `windows-portability-ok`
23
+ * VIOLATION = makesExecutable && shellDashC && !guarded && !optOut
24
+ *
25
+ * ## Remediation
26
+ *
27
+ * Gate the bare-command execution with `if (process.platform !== 'win32')`,
28
+ * or invoke via an explicit interpreter (`sh <path>`), or annotate
29
+ * `// windows-portability-ok: <reason>`.
30
+ *
31
+ * ## Export contract (for unit tests)
32
+ *
33
+ * When required as a module (`require.main !== module`) this file exports
34
+ * `scanContent(source)` → { makesExecutable, shellDashC, guarded, optOut, violation }.
35
+ */
36
+
37
+ const fs = require('fs');
38
+ const path = require('path');
39
+
40
+ // ─── Detection regexes ──────────────────────────────────────────────────────
41
+
42
+ /**
43
+ * Match chmod/chmodSync( calls with an octal mode literal whose exec bits are
44
+ * set, e.g. `fs.chmodSync(p, 0o755)` or `chmod(file, 0o111)`.
45
+ */
46
+ const CHMOD_RE = /chmod(?:Sync)?\s*\([^,;]+,\s*0o([0-7]{3})\b/g;
47
+
48
+ /**
49
+ * Array form: execFileSync/spawnSync/exec* with 'sh' or 'bash' (optionally
50
+ * prefixed) as the first arg and '-c' as an element of the args array.
51
+ * e.g. execFileSync('bash', ['-c', ...]) or spawnSync('/bin/sh', ['-c', ...])
52
+ */
53
+ const SHELL_ARRAY_RE =
54
+ /(?:execFile(?:Sync)?|spawnSync|spawn|exec)\s*\(\s*['"`](?:\/(?:usr\/)?bin\/)?(?:bash|sh)['"`]\s*,\s*\[[^\]]*['"]-c['"]/;
55
+
56
+ /**
57
+ * String-literal form: any string containing `bash -c` or `sh -c`.
58
+ */
59
+ const SHELL_STRING_RE = /['"`][^'"`\n]*(?:bash|sh)\s+-c[^'"`\n]*['"`]/;
60
+
61
+ /** Platform guard presence. */
62
+ const GUARD_RE = /process\.platform|os\.platform\s*\(|\bwin32\b|\bisWindows\b/;
63
+
64
+ /** Opt-out annotation. */
65
+ const OPT_OUT_RE = /windows-portability-ok/;
66
+
67
+ // ─── Pure scanning function (exported for unit tests) ────────────────────────
68
+
69
+ /**
70
+ * Scan a single file's source text and return detection flags.
71
+ *
72
+ * @param {string} source - The file contents as a string.
73
+ * @returns {{ makesExecutable: boolean, shellDashC: boolean, guarded: boolean, optOut: boolean, violation: boolean }}
74
+ */
75
+ function scanContent(source) {
76
+ // Reset stateful regex before use.
77
+ CHMOD_RE.lastIndex = 0;
78
+
79
+ let makesExecutable = false;
80
+ let match;
81
+ while ((match = CHMOD_RE.exec(source)) !== null) {
82
+ const oct = match[1];
83
+ if ((parseInt(oct, 8) & 0o111) !== 0) {
84
+ makesExecutable = true;
85
+ break;
86
+ }
87
+ }
88
+
89
+ const shellDashC = SHELL_ARRAY_RE.test(source) || SHELL_STRING_RE.test(source);
90
+ const guarded = GUARD_RE.test(source);
91
+ const optOut = OPT_OUT_RE.test(source);
92
+ const violation = makesExecutable && shellDashC && !guarded && !optOut;
93
+
94
+ return { makesExecutable, shellDashC, guarded, optOut, violation };
95
+ }
96
+
97
+ // ─── Filesystem walker ───────────────────────────────────────────────────────
98
+
99
+ /**
100
+ * Recursively collect all *.test.cjs files under `dir`, excluding node_modules.
101
+ *
102
+ * @param {string} dir
103
+ * @param {string[]} [acc]
104
+ * @returns {string[]}
105
+ */
106
+ function collectTestFiles(dir, acc) {
107
+ acc = acc || [];
108
+ let entries;
109
+ try {
110
+ entries = fs.readdirSync(dir, { withFileTypes: true });
111
+ } catch {
112
+ return acc;
113
+ }
114
+ for (const entry of entries) {
115
+ if (entry.name === 'node_modules') continue;
116
+ const full = path.join(dir, entry.name);
117
+ if (entry.isDirectory()) {
118
+ collectTestFiles(full, acc);
119
+ } else if (entry.isFile() && entry.name.endsWith('.test.cjs')) {
120
+ acc.push(full);
121
+ }
122
+ }
123
+ return acc;
124
+ }
125
+
126
+ // ─── Main ────────────────────────────────────────────────────────────────────
127
+
128
+ function main() {
129
+ const ROOT = path.join(__dirname, '..');
130
+ const TESTS_DIR = path.join(ROOT, 'tests');
131
+
132
+ const files = collectTestFiles(TESTS_DIR);
133
+ const violations = [];
134
+
135
+ for (const file of files) {
136
+ let source;
137
+ try {
138
+ source = fs.readFileSync(file, 'utf8');
139
+ } catch {
140
+ continue;
141
+ }
142
+ const { violation } = scanContent(source);
143
+ if (violation) {
144
+ const rel = path.relative(ROOT, file).replace(/\\/g, '/');
145
+ violations.push(rel);
146
+ }
147
+ }
148
+
149
+ if (violations.length > 0) {
150
+ for (const rel of violations) {
151
+ process.stderr.write(
152
+ `${rel}: chmod-executable + sh/bash -c with no platform guard\n`,
153
+ );
154
+ }
155
+ process.stderr.write(
156
+ '\nWindows Git Bash does not honor Node\'s chmod exec bit for ' +
157
+ 'PATH-executing extension-less scripts ' +
158
+ '(CONTEXT.md → DEFECT.WINDOWS-TEST-PORTABILITY). ' +
159
+ 'Gate the bare-command execution with ' +
160
+ '`if (process.platform !== \'win32\')`, or invoke via an explicit ' +
161
+ 'interpreter (`sh <path>`), or annotate ' +
162
+ '`// windows-portability-ok: <reason>`.\n',
163
+ );
164
+ process.exitCode = 1;
165
+ } else {
166
+ console.log(
167
+ `ok lint-windows-test-portability: ${files.length} file(s) scanned, no violations`,
168
+ );
169
+ }
170
+ }
171
+
172
+ // ─── Module boundary ─────────────────────────────────────────────────────────
173
+
174
+ if (require.main === module) {
175
+ main();
176
+ } else {
177
+ module.exports = { scanContent };
178
+ }