@opengsd/gsd-core 1.13.0 → 1.14.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 (257) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/agents/gsd-advisor-researcher.compact.md +85 -0
  4. package/agents/gsd-ai-researcher.compact.md +96 -0
  5. package/agents/gsd-assumptions-analyzer.compact.md +81 -0
  6. package/agents/gsd-code-fixer.compact.md +458 -0
  7. package/agents/gsd-code-fixer.md +5 -5
  8. package/agents/gsd-code-reviewer.compact.md +269 -0
  9. package/agents/gsd-code-reviewer.md +15 -3
  10. package/agents/gsd-codebase-mapper.compact.md +760 -0
  11. package/agents/gsd-debug-session-manager.compact.md +345 -0
  12. package/agents/gsd-doc-classifier.compact.md +192 -0
  13. package/agents/gsd-doc-synthesizer.compact.md +200 -0
  14. package/agents/gsd-doc-verifier.compact.md +143 -0
  15. package/agents/gsd-doc-writer.compact.md +440 -0
  16. package/agents/gsd-dom-verifier.compact.md +138 -0
  17. package/agents/gsd-domain-researcher.compact.md +141 -0
  18. package/agents/gsd-eval-auditor.compact.md +160 -0
  19. package/agents/gsd-eval-planner.compact.md +137 -0
  20. package/agents/gsd-framework-selector.compact.md +82 -0
  21. package/agents/gsd-integration-checker.compact.md +245 -0
  22. package/agents/gsd-intel-updater.compact.md +226 -0
  23. package/agents/gsd-mempalace-curator.compact.md +45 -0
  24. package/agents/gsd-nyquist-auditor.compact.md +179 -0
  25. package/agents/gsd-pattern-mapper.compact.md +275 -0
  26. package/agents/gsd-project-researcher.compact.md +587 -0
  27. package/agents/gsd-research-synthesizer.compact.md +212 -0
  28. package/agents/gsd-roadmapper.compact.md +454 -0
  29. package/agents/gsd-roadmapper.md +13 -0
  30. package/agents/gsd-security-auditor.compact.md +162 -0
  31. package/agents/gsd-ui-auditor.compact.md +404 -0
  32. package/agents/gsd-ui-checker.compact.md +277 -0
  33. package/agents/gsd-ui-researcher.compact.md +282 -0
  34. package/agents/gsd-user-profiler.compact.md +108 -0
  35. package/bin/install.js +206 -68
  36. package/commands/gsd/cleanup.md +1 -0
  37. package/commands/gsd/code-review.md +2 -1
  38. package/commands/gsd/complete-milestone.md +1 -0
  39. package/commands/gsd/config.md +1 -0
  40. package/commands/gsd/debug.md +1 -0
  41. package/commands/gsd/graphify.md +1 -0
  42. package/commands/gsd/health.md +1 -0
  43. package/commands/gsd/mempalace-capture.md +1 -0
  44. package/commands/gsd/mempalace-recall.md +1 -0
  45. package/commands/gsd/new-milestone.md +1 -0
  46. package/commands/gsd/new-project.md +1 -0
  47. package/commands/gsd/next.md +1 -0
  48. package/commands/gsd/pause-work.md +1 -0
  49. package/commands/gsd/phase.md +1 -0
  50. package/commands/gsd/pr-branch.md +1 -0
  51. package/commands/gsd/resume-work.md +1 -0
  52. package/commands/gsd/review-backlog.md +1 -0
  53. package/commands/gsd/settings.md +2 -1
  54. package/commands/gsd/stats.md +1 -0
  55. package/commands/gsd/thread.md +1 -0
  56. package/commands/gsd/workspace.md +1 -0
  57. package/commands/gsd/workstreams.md +1 -0
  58. package/gsd-core/bin/check-latest-version.cjs +8 -3
  59. package/gsd-core/bin/gsd-tools.cjs +338 -125
  60. package/gsd-core/bin/lib/adr-parser.cjs +1 -1
  61. package/gsd-core/bin/lib/artifacts.cjs +2 -1
  62. package/gsd-core/bin/lib/audit.cjs +39 -22
  63. package/gsd-core/bin/lib/broken-windows.cjs +168 -49
  64. package/gsd-core/bin/lib/capability-lifecycle.cjs +10 -6
  65. package/gsd-core/bin/lib/capability-loader.cjs +135 -1
  66. package/gsd-core/bin/lib/capability-registry.cjs +79 -67
  67. package/gsd-core/bin/lib/capability-source.cjs +19 -2
  68. package/gsd-core/bin/lib/capability-validator.cjs +14 -1
  69. package/gsd-core/bin/lib/check-command-router.cjs +113 -36
  70. package/gsd-core/bin/lib/code-review-depth.cjs +2 -2
  71. package/gsd-core/bin/lib/commands.cjs +650 -72
  72. package/gsd-core/bin/lib/config-loader.cjs +1 -0
  73. package/gsd-core/bin/lib/config.cjs +153 -38
  74. package/gsd-core/bin/lib/coverage.cjs +1 -1
  75. package/gsd-core/bin/lib/decisions.cjs +137 -34
  76. package/gsd-core/bin/lib/external-descriptor-trust.cjs +29 -14
  77. package/gsd-core/bin/lib/gsd2-import.cjs +1 -2
  78. package/gsd-core/bin/lib/health-diagnostic-rules/state-consistency.cjs +12 -1
  79. package/gsd-core/bin/lib/health-diagnostic-rules/worktree-health.cjs +1 -1
  80. package/gsd-core/bin/lib/init.cjs +409 -47
  81. package/gsd-core/bin/lib/install-engine.cjs +16 -3
  82. package/gsd-core/bin/lib/install-profiles.cjs +14 -0
  83. package/gsd-core/bin/lib/installer-migrations.cjs +33 -4
  84. package/gsd-core/bin/lib/loop-resolver.cjs +50 -31
  85. package/gsd-core/bin/lib/mcp-catalog.cjs +2 -2
  86. package/gsd-core/bin/lib/milestone.cjs +19 -8
  87. package/gsd-core/bin/lib/model-resolver.cjs +101 -10
  88. package/gsd-core/bin/lib/phase-command-router.cjs +7 -1
  89. package/gsd-core/bin/lib/phase-id.cjs +161 -22
  90. package/gsd-core/bin/lib/phase-lifecycle.cjs +61 -0
  91. package/gsd-core/bin/lib/phase.cjs +167 -63
  92. package/gsd-core/bin/lib/planning-inspect.cjs +34 -18
  93. package/gsd-core/bin/lib/planning-snapshot.cjs +61 -12
  94. package/gsd-core/bin/lib/planning-workspace.cjs +50 -1
  95. package/gsd-core/bin/lib/pristine-baseline.cjs +182 -0
  96. package/gsd-core/bin/lib/prohibition-enforcement.cjs +91 -4
  97. package/gsd-core/bin/lib/quick-batch.cjs +1 -1
  98. package/gsd-core/bin/lib/refactor-trigger-command-router.cjs +61 -2
  99. package/gsd-core/bin/lib/research-store.cjs +11 -12
  100. package/gsd-core/bin/lib/review-lane-invocation.cjs +23 -0
  101. package/gsd-core/bin/lib/reviewer-step-dispatch.cjs +337 -0
  102. package/gsd-core/bin/lib/roadmap-parser.cjs +56 -15
  103. package/gsd-core/bin/lib/roadmap.cjs +108 -14
  104. package/gsd-core/bin/lib/runtime-artifact-conversion.cjs +27 -10
  105. package/gsd-core/bin/lib/runtime-artifact-install-plan.cjs +12 -3
  106. package/gsd-core/bin/lib/runtime-artifact-layout.cjs +13 -5
  107. package/gsd-core/bin/lib/runtime-hooks-surface.cjs +193 -4
  108. package/gsd-core/bin/lib/security.cjs +126 -7
  109. package/gsd-core/bin/lib/state-document.cjs +130 -28
  110. package/gsd-core/bin/lib/state-md-schema.cjs +21 -14
  111. package/gsd-core/bin/lib/state-transition.cjs +142 -28
  112. package/gsd-core/bin/lib/state.cjs +223 -27
  113. package/gsd-core/bin/lib/surface.cjs +60 -2
  114. package/gsd-core/bin/lib/task-command-router.cjs +12 -6
  115. package/gsd-core/bin/lib/uat.cjs +1 -1
  116. package/gsd-core/bin/lib/update-context.cjs +30 -24
  117. package/gsd-core/bin/lib/vendor/js-yaml.cjs +11 -3
  118. package/gsd-core/bin/lib/verification.cjs +47 -15
  119. package/gsd-core/bin/lib/verify-command-grounding.cjs +1 -1
  120. package/gsd-core/bin/lib/verify.cjs +188 -23
  121. package/gsd-core/bin/lib/workstream-inventory.cjs +1 -0
  122. package/gsd-core/bin/lib/worktree-safety.cjs +13 -7
  123. package/gsd-core/bin/shared/config-defaults.manifest.json +1 -0
  124. package/gsd-core/bin/shared/config-schema.manifest.json +5 -0
  125. package/gsd-core/bin/verify-reapply-patches.cjs +439 -80
  126. package/gsd-core/references/compact-content-gate.md +66 -0
  127. package/gsd-core/references/loop-hook-dispatch.md +18 -0
  128. package/gsd-core/references/model-profiles.md +12 -3
  129. package/gsd-core/references/planning-config.md +3 -0
  130. package/gsd-core/references/tdd.md +5 -2
  131. package/gsd-core/references/thinking-models-planning.md +18 -2
  132. package/gsd-core/references/verification-patterns.md +17 -4
  133. package/gsd-core/references/worktree-path-safety.md +112 -2
  134. package/gsd-core/templates/README.md +7 -1
  135. package/gsd-core/templates/state.md +6 -3
  136. package/gsd-core/templates/summary.compact.md +212 -0
  137. package/gsd-core/templates/user-setup.compact.md +199 -0
  138. package/gsd-core/templates/user-setup.md +0 -9
  139. package/gsd-core/workflows/add-todo.md +3 -2
  140. package/gsd-core/workflows/autonomous.md +13 -10
  141. package/gsd-core/workflows/check-todos.md +4 -2
  142. package/gsd-core/workflows/cleanup.md +3 -1
  143. package/gsd-core/workflows/code-review/steps/structural-pre-pass.md +7 -0
  144. package/gsd-core/workflows/code-review-fix.md +3 -3
  145. package/gsd-core/workflows/code-review.md +156 -30
  146. package/gsd-core/workflows/complete-milestone/detail/elaboration.md +274 -0
  147. package/gsd-core/workflows/complete-milestone.md +39 -262
  148. package/gsd-core/workflows/docs-update/detail/elaboration.md +179 -0
  149. package/gsd-core/workflows/docs-update.md +14 -155
  150. package/gsd-core/workflows/execute-phase/detail/elaboration.md +124 -0
  151. package/gsd-core/workflows/execute-phase/steps/codebase-drift-gate.md +18 -3
  152. package/gsd-core/workflows/execute-phase/steps/completion-reconciliation.md +56 -0
  153. package/gsd-core/workflows/execute-phase/steps/executor-isolation-dispatch.md +7 -2
  154. package/gsd-core/workflows/execute-phase/steps/executor-progress-policy.md +43 -0
  155. package/gsd-core/workflows/execute-phase/steps/sequential-root-pin.md +35 -0
  156. package/gsd-core/workflows/execute-phase.md +53 -152
  157. package/gsd-core/workflows/execute-plan.md +20 -7
  158. package/gsd-core/workflows/help/modes/full.compact.md +398 -0
  159. package/gsd-core/workflows/help.md +1 -1
  160. package/gsd-core/workflows/map-codebase.md +50 -3
  161. package/gsd-core/workflows/new-milestone.md +54 -12
  162. package/gsd-core/workflows/new-project/detail/elaboration.md +216 -0
  163. package/gsd-core/workflows/new-project.md +32 -202
  164. package/gsd-core/workflows/plan-phase/detail/elaboration.md +209 -0
  165. package/gsd-core/workflows/plan-phase.md +22 -181
  166. package/gsd-core/workflows/pr-branch.md +19 -7
  167. package/gsd-core/workflows/quick.md +8 -1
  168. package/gsd-core/workflows/reapply-patches.md +77 -3
  169. package/gsd-core/workflows/settings.md +18 -5
  170. package/gsd-core/workflows/update.md +7 -5
  171. package/gsd-core/workflows/verify-work/detail/elaboration.md +230 -0
  172. package/gsd-core/workflows/verify-work.md +20 -180
  173. package/hooks/dist/gsd-agent-isolation-guard.js +42 -16
  174. package/hooks/dist/gsd-context-monitor.js +88 -15
  175. package/hooks/dist/gsd-cursor-subagent-start.js +34 -14
  176. package/hooks/dist/gsd-secret-read-guard.js +44 -18
  177. package/hooks/dist/gsd-statusline.js +11 -7
  178. package/hooks/dist/gsd-validate-commit.sh +34 -4
  179. package/hooks/dist/gsd-worktree-path-guard.js +25 -14
  180. package/hooks/dist/gsd-write-guard.js +46 -1
  181. package/hooks/dist/lib/dispatch-identity.js +187 -0
  182. package/hooks/dist/lib/filename-classification.js +64 -0
  183. package/hooks/dist/lib/isolation-deny-reason.js +53 -1
  184. package/hooks/dist/lib/isolation-sentinel.js +58 -19
  185. package/hooks/gsd-agent-isolation-guard.js +42 -16
  186. package/hooks/gsd-context-monitor.js +88 -15
  187. package/hooks/gsd-cursor-subagent-start.js +34 -14
  188. package/hooks/gsd-secret-read-guard.js +44 -18
  189. package/hooks/gsd-statusline.js +11 -7
  190. package/hooks/gsd-validate-commit.sh +34 -4
  191. package/hooks/gsd-worktree-path-guard.js +25 -14
  192. package/hooks/gsd-write-guard.js +46 -1
  193. package/hooks/lib/dispatch-identity.js +187 -0
  194. package/hooks/lib/filename-classification.js +64 -0
  195. package/hooks/lib/isolation-deny-reason.js +53 -1
  196. package/hooks/lib/isolation-sentinel.js +58 -19
  197. package/package.json +10 -6
  198. package/scripts/benchmark-compact-content-variants.cjs +298 -0
  199. package/scripts/benchmark-compact-content.cjs +368 -0
  200. package/scripts/check-contract-drift.cjs +4 -1
  201. package/scripts/check-env.cjs +36 -8
  202. package/scripts/check-glossary-refs.cjs +25 -21
  203. package/scripts/ci-next-health.cjs +271 -0
  204. package/scripts/ci-prepare-test-scope.cjs +7 -7
  205. package/scripts/ci-test-scope.cjs +126 -20
  206. package/scripts/ci-timeout-report.cjs +1 -1
  207. package/scripts/diff-touches-shipped-paths.cjs +1 -1
  208. package/scripts/docs-guard-registry.cjs +7 -2
  209. package/scripts/gen-adr-index.cjs +8 -2
  210. package/scripts/gen-inventory-manifest.cjs +12 -0
  211. package/scripts/gen-platform-conformance-tier.cjs +557 -0
  212. package/scripts/lib/drift-scan.cjs +1 -1
  213. package/scripts/lib/macos-conformance-tier.generated.cjs +210 -0
  214. package/scripts/lib/npm-version-check-diagnosis.cjs +59 -0
  215. package/scripts/lib/platform-conformance-tier.generated.cjs +276 -0
  216. package/scripts/lib/suite-detection.cjs +32 -0
  217. package/scripts/lint-allowed-tools-parity.cjs +221 -0
  218. package/scripts/lint-docs-guard-registration.exempt-baseline.cjs +19 -2
  219. package/scripts/lint-phase-id-drift.cjs +338 -13
  220. package/scripts/lint-response-language-coverage.cjs +9 -3
  221. package/scripts/lint-source-test-name-collision.cjs +1 -1
  222. package/scripts/lint-test-file-count.allowlist.json +1 -0
  223. package/scripts/lint-vendored-deps.cjs +128 -17
  224. package/scripts/lint-workflow-shellcheck-baseline.json +85 -0
  225. package/scripts/prompt-injection-scan.sh +14 -0
  226. package/scripts/workflow-size.cjs +139 -0
  227. package/skills/gsd-cleanup/SKILL.md +1 -0
  228. package/skills/gsd-code-review/SKILL.md +2 -1
  229. package/skills/gsd-complete-milestone/SKILL.md +1 -0
  230. package/skills/gsd-config/SKILL.md +1 -0
  231. package/skills/gsd-debug/SKILL.md +1 -0
  232. package/skills/gsd-graphify/SKILL.md +1 -0
  233. package/skills/gsd-health/SKILL.md +1 -0
  234. package/skills/gsd-mempalace-capture/SKILL.md +1 -0
  235. package/skills/gsd-mempalace-recall/SKILL.md +1 -0
  236. package/skills/gsd-new-milestone/SKILL.md +1 -0
  237. package/skills/gsd-new-project/SKILL.md +1 -0
  238. package/skills/gsd-next/SKILL.md +1 -0
  239. package/skills/gsd-pause-work/SKILL.md +1 -0
  240. package/skills/gsd-phase/SKILL.md +1 -0
  241. package/skills/gsd-pr-branch/SKILL.md +1 -0
  242. package/skills/gsd-resume-work/SKILL.md +1 -0
  243. package/skills/gsd-review-backlog/SKILL.md +1 -0
  244. package/skills/gsd-settings/SKILL.md +2 -1
  245. package/skills/gsd-stats/SKILL.md +1 -0
  246. package/skills/gsd-thread/SKILL.md +1 -0
  247. package/skills/gsd-workspace/SKILL.md +1 -0
  248. package/skills/gsd-workstreams/SKILL.md +1 -0
  249. package/vscode/package.json +1 -1
  250. package/gsd-core/templates/claude-md.md +0 -145
  251. package/gsd-core/templates/codebase/concerns.md +0 -310
  252. package/gsd-core/templates/codebase/conventions.md +0 -307
  253. package/gsd-core/templates/codebase/integrations.md +0 -280
  254. package/gsd-core/templates/codebase/structure.md +0 -285
  255. package/gsd-core/templates/codebase/testing.md +0 -480
  256. package/gsd-core/templates/debug-subagent-prompt.md +0 -91
  257. package/gsd-core/templates/discovery.md +0 -146
@@ -0,0 +1,221 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ /**
5
+ * lint-allowed-tools-parity.cjs — catch `allowed-tools` frontmatter that
6
+ * declares `Bash` but omits `Grep` (#4394, follow-up to #3085).
7
+ *
8
+ * ## Why
9
+ *
10
+ * `gen-plugin-skills.cjs --check` already guarantees `skills/*` /SKILL.md`
11
+ * matches byte-for-byte what `commands/gsd/*.md` generates, so the two trees
12
+ * cannot silently diverge FROM EACH OTHER. Nothing guarded the shape #3085
13
+ * actually found: a command shipping `Bash` without `Grep` purely by
14
+ * omission, identical in both trees and therefore invisible to a parity
15
+ * check that only compares them to each other.
16
+ *
17
+ * That drift ran long enough for 29 of 71 skills to lack a tool most of
18
+ * their siblings already declared, and it surfaced through a manual audit
19
+ * rather than any gate. A command that can shell out but cannot Grep does
20
+ * not fail loudly — it quietly reaches for `Bash` + `grep` instead, which is
21
+ * slower, less structured, and (per `lint-portable-grep.cjs`) a portability
22
+ * hazard of its own on hosts without GNU grep.
23
+ *
24
+ * ## The rule
25
+ *
26
+ * A command whose `allowed-tools` includes `Bash` must also include `Grep`,
27
+ * unless it is on the exemption list below.
28
+ *
29
+ * Detection only. This lint never edits a command's `allowed-tools`.
30
+ *
31
+ * ## Why an exemption list rather than a heuristic
32
+ *
33
+ * The alternative — inferring from a command's body whether it "really"
34
+ * needs Grep — would make the rule's verdict depend on prose that changes
35
+ * constantly, and produce a lint whose failures nobody can predict. A short
36
+ * literal list keeps every exemption a reviewable one-line diff, and the
37
+ * staleness check below stops it becoming a dumping ground: an entry that no
38
+ * longer needs to be there fails just as loudly as a missing tool.
39
+ */
40
+
41
+ const fs = require('fs');
42
+ const path = require('path');
43
+ const { ExitError, runMain } = require('./lib/cli-exit.cjs');
44
+
45
+ const ROOT = path.resolve(__dirname, '..');
46
+ const DEFAULT_ROOT = 'commands/gsd';
47
+
48
+ /**
49
+ * Commands allowed to declare `Bash` without `Grep`.
50
+ *
51
+ * Keyed by command stem (the filename without `.md`), with the reason inline
52
+ * so changing the set is a one-line, reviewable diff.
53
+ *
54
+ * #4394 named eight candidates from the #3085 review: the six `gsd-ns-*`
55
+ * namespace dispatchers, `gsd-help`, and `gsd-surface`. Seven of those turn
56
+ * out not to need an entry at all — they do not declare `Bash` in the first
57
+ * place, so the rule never reaches them:
58
+ *
59
+ * ns-context/ns-ideate/ns-manage/ns-project/ns-review/ns-workflow Read + Skill
60
+ * help Read
61
+ *
62
+ * Listing them anyway would be seven pre-forgiven commands: the day one of
63
+ * them gained `Bash`, the omission it was granted an exemption for would
64
+ * pass silently. The staleness check in `scan()` below is what surfaced
65
+ * this, and it is why the list stays minimal — an exemption is a real
66
+ * suppression, not documentation.
67
+ */
68
+ const EXEMPT = new Map([
69
+ // Toggles which skills are surfaced. Mutates the install tree through the
70
+ // installer's own seams rather than by searching project files, so `Bash`
71
+ // here is not standing in for a search it cannot perform.
72
+ ['surface', 'mutates the install surface via installer seams, not by searching the project'],
73
+ ]);
74
+
75
+ /**
76
+ * Parse an `allowed-tools` frontmatter value.
77
+ *
78
+ * Handles both shapes the corpus uses: a YAML block sequence
79
+ *
80
+ * allowed-tools:
81
+ * - Read
82
+ * - Bash
83
+ *
84
+ * and an inline scalar or flow sequence (`allowed-tools: Read, Bash` /
85
+ * `allowed-tools: [Read, Bash]`). Returns `null` when the key is absent,
86
+ * which is NOT a violation — a command with no `allowed-tools` at all
87
+ * declares no `Bash` either, so this rule has nothing to say about it.
88
+ *
89
+ * Deliberately not a YAML parser: the frontmatter here is a fixed, shallow
90
+ * shape, and pulling in a parser to read one list would be a dependency
91
+ * bought for a single key.
92
+ *
93
+ * @param {string} text full file contents
94
+ * @returns {string[] | null} declared tool names, or null when the key is absent
95
+ */
96
+ function parseAllowedTools(text) {
97
+ const lines = String(text).split(/\r?\n/);
98
+ const keyIndex = lines.findIndex((l) => /^allowed-tools:/.test(l));
99
+ if (keyIndex === -1) return null;
100
+
101
+ const inline = lines[keyIndex].replace(/^allowed-tools:/, '').trim();
102
+ if (inline) {
103
+ return inline
104
+ .replace(/^\[/, '')
105
+ .replace(/\]$/, '')
106
+ .split(',')
107
+ .map((s) => s.trim().replace(/^['"]|['"]$/g, ''))
108
+ .filter(Boolean);
109
+ }
110
+
111
+ const tools = [];
112
+ for (let i = keyIndex + 1; i < lines.length; i += 1) {
113
+ const line = lines[i];
114
+ const item = line.match(/^\s*-\s+(.+?)\s*$/);
115
+ if (item) {
116
+ tools.push(item[1].replace(/^['"]|['"]$/g, ''));
117
+ continue;
118
+ }
119
+ // The first line that is neither a list item nor blank ends the block —
120
+ // the next frontmatter key, or the closing `---`.
121
+ if (line.trim() === '') continue;
122
+ break;
123
+ }
124
+ return tools;
125
+ }
126
+
127
+ /**
128
+ * List command stems under `dir`, sorted.
129
+ *
130
+ * @param {string} dir absolute path to the commands directory
131
+ * @returns {{ stem: string, file: string }[]}
132
+ */
133
+ function listCommands(dir) {
134
+ let entries;
135
+ try {
136
+ entries = fs.readdirSync(dir, { withFileTypes: true });
137
+ } catch {
138
+ return [];
139
+ }
140
+ return entries
141
+ .filter((e) => e.isFile() && e.name.endsWith('.md'))
142
+ .map((e) => ({ stem: e.name.replace(/\.md$/, ''), file: path.join(dir, e.name) }))
143
+ .sort((a, b) => a.stem.localeCompare(b.stem));
144
+ }
145
+
146
+ /**
147
+ * Evaluate the corpus.
148
+ *
149
+ * Reports two independent failure classes, because an exemption list that
150
+ * can only ever grow rots into a list of things nobody re-examined:
151
+ *
152
+ * - `violations`: declares Bash, omits Grep, not exempt.
153
+ * - `staleExemptions`: on the list but no longer needs to be — either the
154
+ * command is gone, or it no longer declares Bash without Grep. Removing
155
+ * the entry is then a one-line diff, and the next omission in that
156
+ * command is caught rather than silently pre-forgiven.
157
+ *
158
+ * @param {string} [root] repo-relative commands directory
159
+ * @returns {{ scanned: number, violations: {stem: string, tools: string[]}[], staleExemptions: {stem: string, reason: string}[] }}
160
+ */
161
+ function scan(root = DEFAULT_ROOT) {
162
+ const abs = path.isAbsolute(root) ? root : path.join(ROOT, root);
163
+ const commands = listCommands(abs);
164
+ const violations = [];
165
+ const seen = new Set();
166
+
167
+ for (const { stem, file } of commands) {
168
+ const tools = parseAllowedTools(fs.readFileSync(file, 'utf8'));
169
+ if (!tools) continue;
170
+ if (!tools.includes('Bash') || tools.includes('Grep')) continue;
171
+ seen.add(stem);
172
+ if (EXEMPT.has(stem)) continue;
173
+ violations.push({ stem, tools });
174
+ }
175
+
176
+ const present = new Set(commands.map((c) => c.stem));
177
+ const staleExemptions = [];
178
+ for (const [stem, reason] of EXEMPT) {
179
+ if (!present.has(stem)) {
180
+ staleExemptions.push({ stem, reason: `no such command (${reason})` });
181
+ } else if (!seen.has(stem)) {
182
+ staleExemptions.push({ stem, reason: `no longer declares Bash without Grep (${reason})` });
183
+ }
184
+ }
185
+
186
+ return { scanned: commands.length, violations, staleExemptions };
187
+ }
188
+
189
+ function main() {
190
+ const root = process.env.GSD_LINT_ALLOWED_TOOLS_ROOT || DEFAULT_ROOT;
191
+ const { scanned, violations, staleExemptions } = scan(root);
192
+
193
+ if (violations.length > 0 || staleExemptions.length > 0) {
194
+ const parts = [];
195
+ if (violations.length > 0) {
196
+ parts.push(
197
+ 'lint-allowed-tools-parity: these commands declare `Bash` but not `Grep` (#4394).\n' +
198
+ 'A command that can shell out but cannot Grep reaches for `Bash` + `grep` instead —\n' +
199
+ 'slower, unstructured, and a portability hazard on hosts without GNU grep. Add `Grep`\n' +
200
+ 'to the frontmatter, or add the command to EXEMPT in this script with a reason:\n' +
201
+ violations.map((v) => ` ${root}/${v.stem}.md [${v.tools.join(', ')}]`).join('\n'),
202
+ );
203
+ }
204
+ if (staleExemptions.length > 0) {
205
+ parts.push(
206
+ 'lint-allowed-tools-parity: these EXEMPT entries are stale — delete them so the next\n' +
207
+ 'omission in those commands is caught rather than silently pre-forgiven:\n' +
208
+ staleExemptions.map((s) => ` ${s.stem}: ${s.reason}`).join('\n'),
209
+ );
210
+ }
211
+ throw new ExitError(1, parts.join('\n\n'));
212
+ }
213
+
214
+ console.log(
215
+ `ok lint-allowed-tools-parity: ${scanned} command(s) checked, ${EXEMPT.size} exemption(s) all still needed`,
216
+ );
217
+ }
218
+
219
+ module.exports = { parseAllowedTools, scan, EXEMPT, DEFAULT_ROOT };
220
+
221
+ if (require.main === module) runMain(main);
@@ -46,6 +46,7 @@ const DOCS_GUARD_EXEMPT_BASELINE = [
46
46
  'codebuddy-upgrades.test.cjs',
47
47
  'commands.test.cjs',
48
48
  'commit-docs-bypass.test.cjs',
49
+ 'compact-content-partition-guard.test.cjs',
49
50
  'complexity-trigger.test.cjs',
50
51
  'concurrency-safety.test.cjs',
51
52
  'cursor-imperative-reference.test.cjs',
@@ -80,6 +81,7 @@ const DOCS_GUARD_EXEMPT_BASELINE = [
80
81
  'repo-invariants.test.cjs',
81
82
  'require-issue-link-policy.test.cjs',
82
83
  'reviewer-manifest-body.test.cjs',
84
+ 'reviewer-step-dispatch.test.cjs',
83
85
  'run-tests-harness.test.cjs',
84
86
  'runtime-name-policy.test.cjs',
85
87
  'security-prompt-injection.security.test.cjs',
@@ -123,8 +125,12 @@ const DOCS_GUARD_EXEMPT_DOCS_PATHS = {
123
125
  'docs/how-to/some-unrelated-guide.md', 'docs/how-to/x.md', 'docs/some-unrelated-file.md',
124
126
  'docs/totally-unrelated.md',
125
127
  ],
128
+ // #4641: cites docs/adr/4641-windows-selector-consolidation.md in an
129
+ // explanatory comment describing why the retired `windows` scope must not
130
+ // be restored; the file never reads that (or any) docs/ file.
126
131
  'ci-test-scope.test.cjs': [
127
- 'docs/a.md', 'docs/adr', 'docs/adr/22-plan-drift-guard.md', 'docs/how-to/configure-model-profiles.md',
132
+ 'docs/a.md', 'docs/adr', 'docs/adr/22-plan-drift-guard.md',
133
+ 'docs/adr/4641-windows-selector-consolidation.md', 'docs/how-to/configure-model-profiles.md',
128
134
  'docs/installer-migrations.md', 'docs/ja-JP', 'docs/ja-JP/USAGE.md', 'docs/usage.md', 'docs/x.md',
129
135
  ],
130
136
  'cline-install.test.cjs': ['docs/guide.md'],
@@ -135,6 +141,10 @@ const DOCS_GUARD_EXEMPT_DOCS_PATHS = {
135
141
  'commit-docs-bypass.test.cjs': [
136
142
  'docs/40-design.md', 'docs/CONFIGURATION.md', 'docs/readme.md', 'docs/tracked-var-mentioning',
137
143
  ],
144
+ // #4403: cites docs/PARTITION-RULES.md as a "see" pointer in the module docstring
145
+ // describing the operational spec this guard implements; the file never reads that
146
+ // (or any) docs/ file.
147
+ 'compact-content-partition-guard.test.cjs': ['docs/PARTITION-RULES.md'],
138
148
  'complexity-trigger.test.cjs': ['docs/readme.md'],
139
149
  // #3884: cites docs/CLI-TOOLS.md:736 in an explanatory comment describing
140
150
  // the real `frontmatter get <file> [--field key]` CLI shape; the file
@@ -187,11 +197,18 @@ const DOCS_GUARD_EXEMPT_DOCS_PATHS = {
187
197
  'repo-invariants.test.cjs': ['docs/FEATURES.md', 'docs/workflows/README'],
188
198
  'require-issue-link-policy.test.cjs': ['docs/-prefixed', 'docs/CONFIGURATION.md', 'docs/a.md', 'docs/b.md', 'docs/guide.md'],
189
199
  'reviewer-manifest-body.test.cjs': ['docs/how-to/ship-a-reviewer-lane.md'],
200
+ // #4209: 'docs/spec.md' is a synthetic, never-read fake path proving dispatchReviewerLanes
201
+ // has no code-review-specific special-casing; 'docs/adr/456-test-rigor-architecture.md' is a
202
+ // prose citation in a code comment justifying the fast-check property tests below (#4209
203
+ // review). Neither is a real filesystem read — the file never reads any docs/ file.
204
+ 'reviewer-step-dispatch.test.cjs': ['docs/adr/456-test-rigor-architecture.md', 'docs/spec.md'],
190
205
  'run-tests-harness.test.cjs': ['docs/TESTING-SUITES.md'],
191
206
  'runtime-name-policy.test.cjs': ['docs/customize/skills'],
192
207
  'security-prompt-injection.security.test.cjs': ['docs/notes.md'],
193
208
  'shipped-reference-cites.test.cjs': [],
194
- 'state.test.cjs': ['docs/CONFIGURATION.md', 'docs/reference/state-md.md'],
209
+ // #4186: the record-session usage-contract test cites the documented
210
+ // signature in docs/CLI-TOOLS.md in its explanatory comment.
211
+ 'state.test.cjs': ['docs/CLI-TOOLS.md', 'docs/CONFIGURATION.md', 'docs/reference/state-md.md'],
195
212
  'worktree-safety.test.cjs': ['docs/SUMMARY.md'],
196
213
  };
197
214