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

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 (69) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/agents/gsd-mempalace-curator.md +47 -0
  3. package/bin/install.js +25 -7
  4. package/commands/gsd/mempalace-capture.md +71 -0
  5. package/commands/gsd/mempalace-recall.md +102 -0
  6. package/commands/gsd/ns-context.md +4 -2
  7. package/gemini-extension.json +1 -1
  8. package/gsd-core/bin/gsd-tools.cjs +110 -92
  9. package/gsd-core/bin/lib/capability-activation.cjs +86 -0
  10. package/gsd-core/bin/lib/capability-registry.cjs +1468 -11
  11. package/gsd-core/bin/lib/capability-state.cjs +31 -18
  12. package/gsd-core/bin/lib/check-command-router.cjs +328 -1
  13. package/gsd-core/bin/lib/clusters.cjs +2 -0
  14. package/gsd-core/bin/lib/command-roster.cjs +19 -0
  15. package/gsd-core/bin/lib/commands.cjs +33 -10
  16. package/gsd-core/bin/lib/config-loader.cjs +7 -8
  17. package/gsd-core/bin/lib/config-schema.cjs +32 -3
  18. package/gsd-core/bin/lib/config.cjs +1 -2
  19. package/gsd-core/bin/lib/core.cjs +5 -2
  20. package/gsd-core/bin/lib/init.cjs +8 -5
  21. package/gsd-core/bin/lib/install-profiles.cjs +2 -1
  22. package/gsd-core/bin/lib/loop-resolver.cjs +157 -16
  23. package/gsd-core/bin/lib/model-resolver.cjs +47 -5
  24. package/gsd-core/bin/lib/phase.cjs +71 -11
  25. package/gsd-core/bin/lib/profile-output.cjs +45 -4
  26. package/gsd-core/bin/lib/profile-pipeline-command-router.cjs +138 -0
  27. package/gsd-core/bin/lib/roadmap.cjs +97 -7
  28. package/gsd-core/bin/lib/runtime-artifact-conversion.cjs +1700 -0
  29. package/gsd-core/bin/lib/runtime-artifact-layout.cjs +20 -28
  30. package/gsd-core/bin/lib/runtime-config-adapter-registry.cjs +27 -19
  31. package/gsd-core/bin/lib/runtime-homes.cjs +26 -20
  32. package/gsd-core/bin/lib/state-command-router.cjs +15 -3
  33. package/gsd-core/bin/lib/state-document.cjs +46 -1
  34. package/gsd-core/bin/lib/state.cjs +221 -35
  35. package/gsd-core/bin/lib/verify.cjs +19 -4
  36. package/gsd-core/bin/shared/config-defaults.manifest.json +1 -2
  37. package/gsd-core/bin/shared/config-schema.manifest.json +0 -18
  38. package/gsd-core/bin/shared/model-catalog.json +1 -0
  39. package/gsd-core/references/loop-hook-dispatch.md +61 -0
  40. package/gsd-core/templates/config.json +1 -1
  41. package/gsd-core/workflows/audit-milestone.md +5 -3
  42. package/gsd-core/workflows/autonomous.md +10 -5
  43. package/gsd-core/workflows/code-review-fix.md +9 -7
  44. package/gsd-core/workflows/code-review.md +8 -6
  45. package/gsd-core/workflows/discuss-phase.md +14 -0
  46. package/gsd-core/workflows/execute-phase.md +82 -143
  47. package/gsd-core/workflows/help/modes/full.md +3 -1
  48. package/gsd-core/workflows/new-project.md +3 -3
  49. package/gsd-core/workflows/plan-phase.md +89 -221
  50. package/gsd-core/workflows/profile-user.md +3 -1
  51. package/gsd-core/workflows/progress.md +58 -1
  52. package/gsd-core/workflows/quick.md +9 -6
  53. package/gsd-core/workflows/review.md +19 -2
  54. package/gsd-core/workflows/secure-phase.md +4 -2
  55. package/gsd-core/workflows/settings-advanced.md +2 -0
  56. package/gsd-core/workflows/ship.md +57 -0
  57. package/gsd-core/workflows/validate-phase.md +4 -2
  58. package/gsd-core/workflows/verify-work.md +27 -11
  59. package/package.json +3 -2
  60. package/scripts/affected-tests-lib.cjs +16 -4
  61. package/scripts/gen-capability-registry.cjs +359 -48
  62. package/scripts/gen-inventory-manifest.cjs +1 -4
  63. package/scripts/gen-loop-host-contract.cjs +55 -0
  64. package/scripts/issue-version-gate.cjs +140 -0
  65. package/scripts/lint-allow-test-rule-refs.allowlist.json +326 -0
  66. package/scripts/lint-allow-test-rule-refs.cjs +162 -0
  67. package/scripts/lint-test-file-count.allowlist.json +8 -0
  68. package/scripts/release-tarball-smoke.cjs +7 -1
  69. package/scripts/run-tests.cjs +74 -14
@@ -0,0 +1,140 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ const { PACKAGE_NAME } = require('../gsd-core/bin/lib/package-identity.cjs');
5
+
6
+ /**
7
+ * Version gate for bug-report issues.
8
+ *
9
+ * GitHub Issue Forms enforce `required: true` only in the web form; issues
10
+ * filed via the REST API, `gh issue create`, or automated/AI reporters can
11
+ * omit the GSD Version field entirely. This module provides the pure logic
12
+ * for detecting bug reports missing a usable version so a workflow can
13
+ * auto-close them. See .github/workflows/version-gate.yml.
14
+ */
15
+
16
+ const BUG_LABEL = 'bug';
17
+ const NEEDS_VERSION_LABEL = 'needs-version';
18
+ const VERSION_GATE_MARKER = '<!-- gsd-version-gate -->';
19
+
20
+ // Labels that opt an issue out of the version gate.
21
+ const EXEMPT_LABELS = ['version-exempt'];
22
+
23
+ // Heading GitHub renders for the bug template's `label: GSD Version` field.
24
+ // Issue Forms render input labels as `### <label>`; matches the exact heading
25
+ // `### GSD Version` (any heading level) — bare `### Version` is NOT matched.
26
+ const VERSION_HEADING_RE = /^#{1,6}\s*GSD\s+Version\s*$/i;
27
+
28
+ // GitHub's placeholder for an empty optional form field.
29
+ const NO_RESPONSE_RE = /^_no response_$/i;
30
+
31
+ // A value "looks like a version" if it contains a semver-ish token
32
+ // (1.18, v1.4.1, 1.18.0-dev) or a git commit SHA (7-40 hex chars).
33
+ const SEMVER_TOKEN_RE = /\bv?\d+\.\d+(?:\.\d+)?(?:[-+.][0-9A-Za-z.-]+)?\b/;
34
+ const SHA_TOKEN_RE = /\b[0-9a-f]{7,40}\b/i;
35
+
36
+ function normalizeLabels(labels) {
37
+ if (!Array.isArray(labels)) return [];
38
+ return labels
39
+ .map((l) => (typeof l === 'string' ? l : l && l.name))
40
+ .filter(Boolean)
41
+ .map((l) => String(l).toLowerCase());
42
+ }
43
+
44
+ function hasExemptLabel(labels) {
45
+ const names = normalizeLabels(labels);
46
+ return names.some((l) => EXEMPT_LABELS.includes(l));
47
+ }
48
+
49
+ function isBugReport({ labels, body } = {}) {
50
+ const names = normalizeLabels(labels);
51
+ if (names.includes(BUG_LABEL)) return true;
52
+ // Labels are authoritative: if any label was applied and it isn't `bug`,
53
+ // this is not a bug report (e.g. an `enhancement`-labeled issue that happens
54
+ // to contain a version heading must not be gated). Only fall back to the
55
+ // bug-template's `### GSD Version` heading for fully unlabeled submissions
56
+ // (bare REST API / `gh issue create`).
57
+ if (names.length > 0) return false;
58
+ return hasVersionHeading(body);
59
+ }
60
+
61
+ function hasVersionHeading(body) {
62
+ if (!body) return false;
63
+ return String(body)
64
+ .split(/\r?\n/)
65
+ .some((line) => VERSION_HEADING_RE.test(line));
66
+ }
67
+
68
+ /**
69
+ * Extract the value beneath the "GSD Version" heading. Returns null when the
70
+ * section is absent, '' when the section is present but empty.
71
+ */
72
+ function extractVersion(body) {
73
+ if (!body) return null;
74
+ const lines = String(body).split(/\r?\n/);
75
+ let start = -1;
76
+ for (let i = 0; i < lines.length; i++) {
77
+ if (VERSION_HEADING_RE.test(lines[i])) {
78
+ start = i;
79
+ break;
80
+ }
81
+ }
82
+ if (start === -1) return null;
83
+ const collected = [];
84
+ for (let i = start + 1; i < lines.length; i++) {
85
+ if (/^#{1,6}\s/.test(lines[i])) break; // next section heading
86
+ collected.push(lines[i]);
87
+ }
88
+ return collected.join('\n').trim();
89
+ }
90
+
91
+ function isValidVersion(value) {
92
+ if (value == null) return false;
93
+ const v = String(value).trim();
94
+ if (!v) return false;
95
+ if (NO_RESPONSE_RE.test(v)) return false;
96
+ return SEMVER_TOKEN_RE.test(v) || SHA_TOKEN_RE.test(v);
97
+ }
98
+
99
+ /**
100
+ * Decide what to do with an issue. Returns { action: 'skip'|'close', reason }.
101
+ */
102
+ function evaluateVersionGate({ labels, body } = {}) {
103
+ if (hasExemptLabel(labels)) return { action: 'skip', reason: 'exempt-label' };
104
+ if (!isBugReport({ labels, body })) return { action: 'skip', reason: 'not-a-bug' };
105
+ const version = extractVersion(body);
106
+ if (isValidVersion(version)) return { action: 'skip', reason: 'valid-version' };
107
+ const reason = version == null ? 'missing-version' : 'invalid-version';
108
+ return { action: 'close', reason };
109
+ }
110
+
111
+ function renderCloseComment() {
112
+ return [
113
+ VERSION_GATE_MARKER,
114
+ 'Closing automatically — this bug report does not include a valid **GSD Version**.',
115
+ '',
116
+ 'A version is required to reproduce and triage bugs. Grab it with one of:',
117
+ '',
118
+ '```',
119
+ `npm list -g ${PACKAGE_NAME}`,
120
+ `npx ${PACKAGE_NAME} --version`,
121
+ '```',
122
+ '',
123
+ 'Then **edit this issue to add the version (e.g. `1.18.0`) and reopen it**, or comment with the version and a maintainer will reopen it. If a version genuinely does not apply, a maintainer can add the `version-exempt` label.',
124
+ ].join('\n');
125
+ }
126
+
127
+ module.exports = {
128
+ BUG_LABEL,
129
+ NEEDS_VERSION_LABEL,
130
+ VERSION_GATE_MARKER,
131
+ EXEMPT_LABELS,
132
+ normalizeLabels,
133
+ hasExemptLabel,
134
+ isBugReport,
135
+ hasVersionHeading,
136
+ extractVersion,
137
+ isValidVersion,
138
+ evaluateVersionGate,
139
+ renderCloseComment,
140
+ };
@@ -0,0 +1,326 @@
1
+ [
2
+ "tests/agent-classification-parity.test.cjs :: runtime-contract-is-the-product — docs/AGENTS.md section layout + docs/INVENTORY.md table ARE the classification surface being validated",
3
+ "tests/agent-frontmatter.test.cjs :: source-text-is-the-product",
4
+ "tests/agent-required-reading-consistency.test.cjs :: source-text-is-the-product",
5
+ "tests/agent-size-budget.test.cjs :: source-text-is-the-product",
6
+ "tests/agent-skills-awareness.test.cjs :: source-text-is-the-product",
7
+ "tests/ai-evals.test.cjs :: source-text-is-the-product",
8
+ "tests/analyze-dependencies.test.cjs :: source-text-is-the-product",
9
+ "tests/anti-pattern-enforcement.test.cjs :: source-text-is-the-product",
10
+ "tests/ask-user-questions-fallback.test.cjs :: source-text-is-the-product",
11
+ "tests/audit-fix-command.test.cjs :: source-text-is-the-product",
12
+ "tests/autonomous-allowed-tools.test.cjs :: source-text-is-the-product",
13
+ "tests/autonomous-converge.test.cjs :: source-text-is-the-product",
14
+ "tests/autonomous-decomposition.test.cjs :: source-text-is-the-product",
15
+ "tests/autonomous-interactive.test.cjs :: source-text-is-the-product",
16
+ "tests/autonomous-to-flag.test.cjs :: source-text-is-the-product",
17
+ "tests/bug-131-release-tarball-smoke-explicit-home.test.cjs :: integration-test-input",
18
+ "tests/bug-14-progress-auto-flag-dropped.test.cjs :: source-text-is-the-product",
19
+ "tests/bug-17-askuserquestion-option-cap.test.cjs :: source-text-is-the-product",
20
+ "tests/bug-170-workflow-fallback-install-hint.test.cjs :: workflow markdown is shipped product text; this test validates fallback hint literals across all workflow files",
21
+ "tests/bug-1891-file-resolution.test.cjs :: structural-implementation-guard",
22
+ "tests/bug-1924-preserve-user-artifacts.test.cjs :: source-text-is-the-product",
23
+ "tests/bug-1967-cache-invalidation.test.cjs :: source-text-is-the-product",
24
+ "tests/bug-211-launcher-home-fallback.test.cjs :: structural/behavioral regression for the ~/.claude fallback arm in",
25
+ "tests/bug-2136-sh-hook-version.test.cjs :: structural-regression-guard",
26
+ "tests/bug-214-phase-researcher-write-truncation-contract.test.cjs :: source-text-is-the-product",
27
+ "tests/bug-214-writer-agents-write-truncation-contract.test.cjs :: source-text-is-the-product",
28
+ "tests/bug-222-research-synthesizer-write-contract.test.cjs :: source-text-is-the-product",
29
+ "tests/bug-224-pick-stdout-capture.test.cjs :: structural-implementation-guard",
30
+ "tests/bug-2346-agent-read-loop-guards.test.cjs :: source-text-is-the-product",
31
+ "tests/bug-2388-plan-phase-no-branch-rename.test.cjs :: source-text-is-the-product",
32
+ "tests/bug-2396-makefile-test-priority.test.cjs :: source-text-is-the-product",
33
+ "tests/bug-2399-commit-docs-plan-phase.test.cjs :: source-text-is-the-product",
34
+ "tests/bug-2410-stream-checkpoint-heartbeats.test.cjs :: source-text-is-the-product",
35
+ "tests/bug-2419-project-researcher-agent.test.cjs :: source-text-is-the-product",
36
+ "tests/bug-2421-planner-grep-gate-hygiene.test.cjs :: source-text-is-the-product",
37
+ "tests/bug-2424-reapply-patches-baseline-detection.test.cjs :: source-text-is-the-product",
38
+ "tests/bug-2470-update-md-claude-path.test.cjs :: source-text-is-the-product",
39
+ "tests/bug-2492-context-coverage-gate.test.cjs :: source-text-is-the-product",
40
+ "tests/bug-2502-insert-phase-state-update.test.cjs :: source-text-is-the-product",
41
+ "tests/bug-2516-inherit-model-execute-phase.test.cjs :: source-text-is-the-product",
42
+ "tests/bug-2543-gsd-slash-namespace.test.cjs :: structural-regression-guard",
43
+ "tests/bug-2549-2550-2552-discuss-phase-context.test.cjs :: source-text-is-the-product",
44
+ "tests/bug-2559-stale-search-year.test.cjs :: source-text-is-the-product",
45
+ "tests/bug-2661-roadmap-sync-parallel.test.cjs :: source-text-is-the-product",
46
+ "tests/bug-2686-review-fix-worktree.test.cjs :: source-text-is-the-product",
47
+ "tests/bug-2698-crlf-install.test.cjs :: source-text-is-the-product",
48
+ "tests/bug-2770-annotate-deps-int-coerce.test.cjs :: source-text-is-the-product",
49
+ "tests/bug-2772-gitmodules-path-intersection.test.cjs :: source-text-is-the-product",
50
+ "tests/bug-2784-update-cache-clear-path.test.cjs :: structural-regression-guard",
51
+ "tests/bug-279-codex-agent-mapping.test.cjs :: source-text-is-the-product [adapter header contract in bin/install.js]",
52
+ "tests/bug-2808-skill-hyphen-name.test.cjs :: source-text-is-the-product",
53
+ "tests/bug-2831-opencode-home-path-prefix.test.cjs :: source-text-is-the-product",
54
+ "tests/bug-2839-review-fix-transactional-cleanup.test.cjs :: source-text-is-the-product",
55
+ "tests/bug-2948-spike-wrap-up-dispatch.test.cjs :: source-text-is-the-product",
56
+ "tests/bug-2949-sketch-wrap-up-dispatch.test.cjs :: source-text-is-the-product",
57
+ "tests/bug-2954-help-md-slash-command-stubs.test.cjs :: source-text-is-the-product",
58
+ "tests/bug-2973-profile-user-skills-path.test.cjs :: source-text-is-the-product. profile-user.md IS the",
59
+ "tests/bug-2990-code-fixer-worktree-branch.test.cjs :: source-text-is-the-product",
60
+ "tests/bug-3086-git-create-tag-config-gate.test.cjs :: workflow-markdown-is-the-runtime-contract",
61
+ "tests/bug-3096-ai-integration-phase-parallel-race.test.cjs :: reads product workflow markdown (ai-integration-phase.md) to verify structural ordering contract — not a source-grep test",
62
+ "tests/bug-3097-3099-executor-worktree-path-safety.test.cjs :: reads markdown product files (gsd-executor.md, worktree-path-safety.md) to verify structural protocol — not source-grep",
63
+ "tests/bug-3120-secure-phase-empty-register.test.cjs :: reads product workflow markdown (secure-phase.md) to verify structural guard contract — not a source-grep test",
64
+ "tests/bug-3126-global-skills-base-runtime-path.test.cjs :: last three tests read init.cjs source to verify delegation contract to runtime-homes.cjs — structural guard, no behavioral IR exposed",
65
+ "tests/bug-3127-state-begin-phase-idempotent.test.cjs :: reads runtime STATE.md written to temp dir — behavioral output test, not source-grep",
66
+ "tests/bug-3128-roadmap-plan-count-slug-layout.test.cjs :: reads roadmap.cjs source to verify isPlanFile pattern was adopted — structural contract prevents silent regression to old filter",
67
+ "tests/bug-3129-validate-commit-git-bypass.test.cjs :: reads hook shell script to verify delegation pattern — structural contract test, not source-grep",
68
+ "tests/bug-3130-update-npx-robust-invocation.test.cjs :: reads product workflow markdown (update.md) to verify structural invocation contract — not a source-grep test",
69
+ "tests/bug-3135-capture-backlog-workflow.test.cjs :: source-text-is-the-product — workflow and command .md files",
70
+ "tests/bug-3156-plan-phase-opencode-dispatch.test.cjs :: source-text-is-the-product",
71
+ "tests/bug-3168-task-to-agent-rename.test.cjs :: source-text-is-the-product",
72
+ "tests/bug-3236-capture-seed-one-shot.test.cjs :: source-text-is-the-product — workflow and command .md files",
73
+ "tests/bug-3258-no-stale-gsd-intel-references.test.cjs :: source-text-is-the-product — workflow, reference, and docs .md files",
74
+ "tests/bug-3290-intel-updater-layout-block.test.cjs :: source-text-is-the-product — agents/gsd-intel-updater.md IS",
75
+ "tests/bug-33-settings-model-profile-adaptive.test.cjs :: source-text-is-the-product",
76
+ "tests/bug-3381-verify-work-workstream.test.cjs :: source-text-is-the-product — verify-work.md is a runtime workflow contract.",
77
+ "tests/bug-3384-secondary-defects.test.cjs :: source-text-is-the-product",
78
+ "tests/bug-3418-progress-flag-routing.test.cjs :: source-text-is-the-product",
79
+ "tests/bug-3430-planner-phase-contract.test.cjs :: source-text-is-the-product",
80
+ "tests/bug-3431-debug-command-yaml.test.cjs :: source-text-is-the-product",
81
+ "tests/bug-3446-resume-continue-here-discovery.test.cjs :: source-text-is-the-product",
82
+ "tests/bug-3489-complete-phase-idempotent.test.cjs :: source-text-is-the-product",
83
+ "tests/bug-3491-nested-git-worktree.test.cjs :: source-text-is-the-product",
84
+ "tests/bug-3516-reapply-patches-gsd-update-filter.test.cjs :: source-text-is-the-product",
85
+ "tests/bug-3521-quick-cleanup-cwd-pin.test.cjs :: source-text-is-the-product",
86
+ "tests/bug-3523-cjs-loadconfig-branching-strategy-warning.test.cjs :: validates runtime CLI stdout/stderr warning behavior, not source grep",
87
+ "tests/bug-3542-executor-git-stash-prohibition.test.cjs :: source-text-is-the-product",
88
+ "tests/bug-3582-codex-skills-materialized.test.cjs :: source-text-is-the-product",
89
+ "tests/bug-3605-stale-research-insert-phase-agent-refs.test.cjs :: source-text-is-the-product",
90
+ "tests/bug-3628-bundled-hook-classifier-whitelist.test.cjs :: architectural-invariant",
91
+ "tests/bug-3657-verify-reapply-patches-pristine-drift.test.cjs :: source-text-is-the-product.",
92
+ "tests/bug-3657-verify-reapply-patches-pristine-drift.test.cjs :: source-text-is-the-product — Finding 2 reads reapply-patches.md to",
93
+ "tests/bug-3677-agent-colon-namespace-leak.test.cjs :: source-text-is-the-product",
94
+ "tests/bug-3678-executor-commit-docs-respect.test.cjs :: source-text-is-the-product",
95
+ "tests/bug-3683-command-colon-namespace-leak.test.cjs :: source-text-is-the-product",
96
+ "tests/bug-3683-command-cross-reference-invariant.test.cjs :: source-text-is-the-product",
97
+ "tests/bug-3683-workflow-colon-namespace-leak.test.cjs :: source-text-is-the-product",
98
+ "tests/bug-3689-resume-glob-nomatch.test.cjs :: source-text-is-the-product",
99
+ "tests/bug-3691-annotate-deps-plans-block-variants.test.cjs :: source-text-is-the-product",
100
+ "tests/bug-3706-ui-safety-gate-false-positives.test.cjs :: source-text-is-the-product",
101
+ "tests/bug-3707-locked-worktree-cleanup.test.cjs :: source-text-is-the-product",
102
+ "tests/bug-3727-code-review-fix-flag-dispatch.test.cjs :: source-text-is-the-product",
103
+ "tests/bug-378-update-check-scoped-name.test.cjs :: structural assertion on hook delegation; the behavior being",
104
+ "tests/bug-3784-gsd-settings-model-profile-ui-omits-adaptive.test.cjs :: source-text-is-the-product",
105
+ "tests/bug-3805-fast-md-log-to-state-schema.test.cjs :: source-text-is-the-product",
106
+ "tests/bug-3810-no-gsd-sdk-runtime-refs.test.cjs :: source-text-is-the-product",
107
+ "tests/bug-397-state-preserve-executor-authored.test.cjs :: reads runtime STATE.md written to temp dir — behavioral output test, not source-grep",
108
+ "tests/bug-444-resolver-local-claude-install.test.cjs :: structural/behavioral regression for the repo-local .claude/ install",
109
+ "tests/bug-474-clock-seam-date-determinism.test.cjs :: source-text-is-the-product",
110
+ "tests/bug-503-update-agent-antigravity-detection.test.cjs :: source-text-is-the-product",
111
+ "tests/bug-570-codex-leak-scanner.test.cjs :: source-text-is-the-product",
112
+ "tests/bug-571-doc-writer-fix-mode-edit-only.test.cjs :: source-text-is-the-product",
113
+ "tests/bug-619-codebase-drift-gate-shim.test.cjs :: source-text-is-the-product",
114
+ "tests/bug-621-plan-phase-gap-analysis-gsd-run.test.cjs :: source-text-is-the-product",
115
+ "tests/bug-622-graphify-optional-graph-html.test.cjs :: source-text-is-the-product",
116
+ "tests/bug-630-wave-cleanup-orchestrator-root.test.cjs :: source-text-is-the-product",
117
+ "tests/bug-637-workflow-no-hardcoded-home-tool.test.cjs :: source-text-is-the-product",
118
+ "tests/bug-685-windowshide-spawn.test.cjs :: source-text-is-the-product",
119
+ "tests/bug-687-agy-timeout.test.cjs :: source-text-is-the-product",
120
+ "tests/bug-704-codex-launcher-path-corruption.test.cjs :: source-text-is-the-product",
121
+ "tests/bug-851-codex-quick-adapter-agent-type-fallback.test.cjs :: source-text-is-the-product",
122
+ "tests/bug-891-non-claude-runtime-home-fallback.test.cjs :: structural/behavioral regression for non-Claude runtime-home",
123
+ "tests/bug-924-claude-flat-skill-layout.test.cjs :: source-text-is-the-product",
124
+ "tests/bug-936-no-nested-spawner-wrap.test.cjs :: source-text-is-the-product",
125
+ "tests/bug-947-hermes-gsd-prefix.test.cjs :: source-text-is-the-product",
126
+ "tests/bug-950-quick-summary-status-complete.test.cjs :: source-text-is-the-product",
127
+ "tests/bug-967-verify-key-links-strict-paths.test.cjs :: <runtime-contract-is-the-product> the plan-md.md reference",
128
+ "tests/bug-967-verify-key-links-strict-paths.test.cjs :: <runtime-contract-is-the-product> the plan-md.md reference example IS the documented authoring surface for key_links; asserting it uses a file path (not an endpoint) directly tests the documented contract.",
129
+ "tests/bug-983-trae-windsurf-claude-path-leak.test.cjs :: source-text-is-the-product",
130
+ "tests/bug-patterns-reference.test.cjs :: source-text-is-the-product",
131
+ "tests/chain-flag-plan-phase.test.cjs :: source-text-is-the-product",
132
+ "tests/changeset-cli.test.cjs :: reads a product workflow .md file (not CJS source) to verify",
133
+ "tests/check-update-config-dir.test.cjs :: structural-regression-guard",
134
+ "tests/ci-test-scope.test.cjs :: CLI usage banner presence is a user-facing contract.",
135
+ "tests/ci-test-scope.test.cjs :: CLI usage failure text is user-facing contract for this parser guard.",
136
+ "tests/claude-md.test.cjs :: source-text-is-the-product",
137
+ "tests/claude-skills-migration.test.cjs :: source-text-is-the-product",
138
+ "tests/cleanup-branch-pruning.test.cjs :: source-text-is-the-product",
139
+ "tests/cline-install.test.cjs :: source-text-is-the-product",
140
+ "tests/cline-support.test.cjs :: source-text-is-the-product",
141
+ "tests/clock-seam.test.cjs :: line 159 reads the STATE.md temp file written by readModifyWriteStateMd — this is a runtime output file assertion, not a source-grep; the API returns void so a file read-back is the only way to verify the transform was applied",
142
+ "tests/code-review-agent-skills.test.cjs :: source-text-is-the-product",
143
+ "tests/code-review-command.test.cjs :: source-text-is-the-product",
144
+ "tests/code-review-pipeline-regression.test.cjs :: source-text-is-the-product",
145
+ "tests/code-review.test.cjs :: source-text-is-the-product",
146
+ "tests/codebuddy-install.test.cjs :: source-text-is-the-product",
147
+ "tests/codex-config.test.cjs :: source-text-is-the-product",
148
+ "tests/command-contract.test.cjs :: source-text-is-the-product — commands/gsd/*.md files ARE the",
149
+ "tests/commands.test.cjs :: source-text-is-the-product",
150
+ "tests/concurrency-safety.test.cjs :: source-text-is-the-product",
151
+ "tests/config-field-docs.test.cjs :: docs-parity",
152
+ "tests/context-enrichment.test.cjs :: source-text-is-the-product",
153
+ "tests/contributor-standards.test.cjs :: source-text-is-the-product",
154
+ "tests/copilot-install.test.cjs :: integration-test-input",
155
+ "tests/core.test.cjs :: architectural-invariant",
156
+ "tests/core.test.cjs :: structural-regression-guard",
157
+ "tests/cursor-hooks.test.cjs :: source-text-is-the-product",
158
+ "tests/cursor-reviewer.test.cjs :: source-text-is-the-product",
159
+ "tests/debug-session-management.test.cjs :: source-text-is-the-product",
160
+ "tests/discuss-all-flag.test.cjs :: source-text-is-the-product",
161
+ "tests/discuss-checkpoint.test.cjs :: source-text-is-the-product",
162
+ "tests/discuss-mode.test.cjs :: structural-implementation-guard",
163
+ "tests/discuss-phase-power.test.cjs :: source-text-is-the-product",
164
+ "tests/docs-parity-live-registry.test.cjs :: source-text-is-the-product",
165
+ "tests/drift-detection.test.cjs :: source-text-is-the-product",
166
+ "tests/edge-probe-docs-fixtures.test.cjs :: runtime-contract-is-the-product — the rendered reference/SPEC/ADR vocab surfaces are the runtime contract; this pins their bijection to the code (docs-parity)",
167
+ "tests/edge-probe-planner-contract.test.cjs :: runtime-contract-is-the-product — plan-phase.md's planner prompt is the deployed runtime contract under assertion",
168
+ "tests/edge-probe-spec-phase-contract.test.cjs :: runtime-contract-is-the-product — spec-phase.md Step 5.5 is the deployed workflow runtime contract under assertion",
169
+ "tests/edit-phase.test.cjs :: source-text-is-the-product",
170
+ "tests/enh-2310-chunked-plan-phase.test.cjs :: source-text-is-the-product",
171
+ "tests/enh-2380-sync-skills.test.cjs :: source-text-is-the-product",
172
+ "tests/enh-2415-claude-md-link-mode.test.cjs :: source-text-is-the-product",
173
+ "tests/enh-2430-learnings-consumption.test.cjs :: source-text-is-the-product",
174
+ "tests/enh-2433-todo-phase-linking.test.cjs :: source-text-is-the-product",
175
+ "tests/enh-2446-milestones-drift.test.cjs :: source-text-is-the-product",
176
+ "tests/enh-2447-roadmap-wave-deps.test.cjs :: source-text-is-the-product",
177
+ "tests/enh-2448-artifact-registry.test.cjs :: source-text-is-the-product",
178
+ "tests/enh-2500-codebase-mapper-arch-rich-format.test.cjs :: source-text-is-the-product",
179
+ "tests/enh-2789-description-budget.test.cjs :: source-text-is-the-product",
180
+ "tests/enh-2790-skill-consolidation.test.cjs :: source-text-is-the-product",
181
+ "tests/enh-2792-namespace-skills.test.cjs :: source-text-is-the-product",
182
+ "tests/enh-3209-plan-phase-ingest-adr.test.cjs :: source-text-is-the-product",
183
+ "tests/enh-48-cwd-drift-guard-e2e.test.cjs :: integration-test-input",
184
+ "tests/enh-72-business-context.test.cjs :: source-text-is-the-product",
185
+ "tests/enh-769-context-fork-effort.install.test.cjs :: integration-test-input",
186
+ "tests/enh-770-claude-hook-events.test.cjs :: runtime-contract-is-the-product — hooks.json IS the",
187
+ "tests/enh-770-claude-hook-events.test.cjs :: runtime-contract-is-the-product — the hookEventName is",
188
+ "tests/enh-770-claude-hook-events.test.cjs :: runtime-contract-is-the-product — the stamp template token",
189
+ "tests/enh-770-claude-hook-events.test.cjs :: runtime-contract-is-the-product — the stdin-read and",
190
+ "tests/enh-773-codex-exec-automation-flags.test.cjs :: source-text-is-the-product",
191
+ "tests/enh-778-cross-runtime-command-enrichment.test.cjs :: source-text-is-the-product",
192
+ "tests/enh-789-codebuddy-commands.test.cjs :: source-text-is-the-product",
193
+ "tests/eslint-rules.test.cjs :: <source-grep reason> must still error",
194
+ "tests/eslint-rules.test.cjs :: pending migration",
195
+ "tests/eslint-rules.test.cjs :: source-text-is-the-product",
196
+ "tests/execute-phase-active-flags.test.cjs :: source-text-is-the-product",
197
+ "tests/execute-phase-step-5-5-deviation-doc.test.cjs :: source-text-is-the-product",
198
+ "tests/execute-phase-wave.test.cjs :: behavioral — calls gsd-tools and asserts structured output",
199
+ "tests/execute-phase-wave.test.cjs :: behavioral — exercises config-set validation, not source text",
200
+ "tests/execute-phase-wave.test.cjs :: behavioral — exercises gsd-tools wave-defaulting logic",
201
+ "tests/execute-phase-wave.test.cjs :: source-text-is-the-product",
202
+ "tests/execute-phase-worktree-artifacts.test.cjs :: source-text-is-the-product",
203
+ "tests/explore-command.test.cjs :: source-text-is-the-product",
204
+ "tests/extract-learnings.test.cjs :: source-text-is-the-product",
205
+ "tests/feat-22-surfacing-docs.test.cjs :: docs-parity",
206
+ "tests/feat-2840-issue-driven-orchestration-guide.test.cjs :: structural-IR parser for a docs guide. The .includes()",
207
+ "tests/feat-3039-help-tiered.test.cjs :: source-text-is-the-product",
208
+ "tests/feat-3167-ship-pr-body-sections.test.cjs :: source-text-is-the-product",
209
+ "tests/feat-3210-fallow-integration.test.cjs :: source-text-is-the-product",
210
+ "tests/feat-3210-fallow-integration.test.cjs :: source-text-is-the-product — code-review.md IS the workflow the orchestrator",
211
+ "tests/feat-3309-human-verify-mode.test.cjs :: source-text-is-the-product",
212
+ "tests/feat-443-effort-install-wiring.install.test.cjs :: integration-test-input",
213
+ "tests/feat-488-effort-sync.test.cjs :: structural-regression-guard — readFileSync asserts on installed agent .md files (the product under mutation) to verify dry-run safety and apply correctness; stderr.includes guards the CLI argument-rejection contract.",
214
+ "tests/fix-3722-execute-phase-human-needed-checkpoint.test.cjs :: source-text-is-the-product",
215
+ "tests/forensics.test.cjs :: source-text-is-the-product",
216
+ "tests/frontmatter-cli.test.cjs :: source-text-is-the-product",
217
+ "tests/gates-taxonomy.test.cjs :: source-text-is-the-product",
218
+ "tests/gsd-check-update-worker-platform-gate.test.cjs :: structural assertion on spawn-options shape; the behavior",
219
+ "tests/gsd-researcher-app-aware.test.cjs :: source-text-is-the-product",
220
+ "tests/gsd-researcher-flow-diagram.test.cjs :: source-text-is-the-product",
221
+ "tests/gsd-settings-advanced.test.cjs :: source-text-is-the-product",
222
+ "tests/gsd2-import.test.cjs :: source-text-is-the-product",
223
+ "tests/hermes-skills-migration.test.cjs :: source-text-is-the-product",
224
+ "tests/import-command.test.cjs :: source-text-is-the-product",
225
+ "tests/ingest-docs.test.cjs :: source-text-is-the-product",
226
+ "tests/inline-plan-threshold.test.cjs :: source-text-is-the-product",
227
+ "tests/install-minimal-hooks.test.cjs :: source-text-is-the-product",
228
+ "tests/install-nested-layout.test.cjs :: source-text-is-the-product",
229
+ "tests/install-runtime-artifacts.test.cjs :: source-text-is-the-product",
230
+ "tests/install.test.cjs :: runtime-contract-is-the-product",
231
+ "tests/install.test.cjs :: source-text-is-the-product",
232
+ "tests/intel.test.cjs :: source-text-is-the-product — agents/gsd-intel-updater.md IS the",
233
+ "tests/intel.test.cjs :: source-text-is-the-product — readFileSync assertions target API-SURFACE.md, which is the generated product of intelApiSurface; asserting on its text content is the only way to verify correct generation.",
234
+ "tests/inventory-headings-countfree.test.cjs :: runtime-contract-is-the-product — INVENTORY.md heading format is the shipped doc surface being locked",
235
+ "tests/ios-scaffold-safety.test.cjs :: source-text-is-the-product",
236
+ "tests/issue-2639-codex-toml-neutralization.test.cjs :: source-text-is-the-product",
237
+ "tests/issue-429-comment-text-gate.test.cjs :: source-text-is-the-product",
238
+ "tests/issue-498-package-identity.test.cjs :: architectural-invariant",
239
+ "tests/issue-498-update-backup-runtime-dir.test.cjs :: structural assertion on the deployed update.md backup bash;",
240
+ "tests/issue-57-runtime-install-no-drift.test.cjs :: delegation-presence guard. Catches wholesale removal of the registry",
241
+ "tests/issue-57-runtime-install-no-drift.test.cjs :: structural guard over bin/install.js source. Behavioral assertions",
242
+ "tests/issue-607-installer-dry-run.install.test.cjs :: integration-test-input",
243
+ "tests/issue-607-legacy-cleanup.test.cjs :: integration-test-input",
244
+ "tests/issue-787-cline-hooks-agents.test.cjs :: source-text-is-the-product",
245
+ "tests/issue-815-update-next-channel.test.cjs :: reads product workflow/command markdown to verify the --next RC channel contract — not a source-grep test",
246
+ "tests/locking-bugs-1909-1916-1925-1927.test.cjs :: architectural-invariant",
247
+ "tests/mcp-tool-inheritance.test.cjs :: source-text-is-the-product",
248
+ "tests/milestone-summary.test.cjs :: source-text-is-the-product",
249
+ "tests/milestone.test.cjs :: source-text-is-the-product",
250
+ "tests/milestone.test.cjs :: structural-regression-guard",
251
+ "tests/model-catalog-runtime-defaults.test.cjs :: source-text-is-the-product",
252
+ "tests/next-safety-gates.test.cjs :: source-text-is-the-product",
253
+ "tests/next-up-clear-order.test.cjs :: source-text-is-the-product",
254
+ "tests/no-hardcoded-home-gsd-tools.test.cjs :: source-text-is-the-product",
255
+ "tests/opencode-permissions.test.cjs :: architectural-invariant",
256
+ "tests/orphan-worktree-detection.test.cjs :: architectural-invariant",
257
+ "tests/orphaned-hooks.test.cjs :: structural-regression-guard",
258
+ "tests/package-legitimacy-gate.test.cjs :: source-text-is-the-product",
259
+ "tests/parallel-dependent-plans.test.cjs :: source-text-is-the-product",
260
+ "tests/path-replacement.test.cjs :: source-text-is-the-product",
261
+ "tests/phase-dependency-levels.test.cjs :: source-text-is-the-product",
262
+ "tests/phase.test.cjs :: source-text-is-the-product",
263
+ "tests/phase.test.cjs :: state-md-is-the-runtime-contract — regression tests for",
264
+ "tests/phase6-capability-docs.test.cjs :: source-text-is-the-product",
265
+ "tests/phase6-capstone-conformance.test.cjs :: source-text-is-the-product",
266
+ "tests/phase6-planning-capabilities.test.cjs :: source-text-is-the-product",
267
+ "tests/plan-bounce.test.cjs :: source-text-is-the-product",
268
+ "tests/plan-phase-drift-guard.test.cjs :: source-text-is-the-product",
269
+ "tests/plan-phase-ui-redirect.test.cjs :: source-text-is-the-product",
270
+ "tests/plan-review-convergence.test.cjs :: source-text-is-the-product",
271
+ "tests/planner-decomposition.test.cjs :: source-text-is-the-product",
272
+ "tests/planner-language-regression.test.cjs :: source-text-is-the-product",
273
+ "tests/playwright-ui-verify.test.cjs :: source-text-is-the-product",
274
+ "tests/policy-160-route0-resume.test.cjs :: source-text-is-the-product",
275
+ "tests/policy-release-no-npm-self-upgrade.test.cjs :: source-text-is-the-product",
276
+ "tests/product-name-purity.test.cjs :: source-text-is-the-product",
277
+ "tests/profile-output.test.cjs :: source-text-is-the-product",
278
+ "tests/progress-forensic.test.cjs :: source-text-is-the-product",
279
+ "tests/prompt-budget-cli.test.cjs :: prompt-content-is-the-product",
280
+ "tests/prompt-thinning.test.cjs :: source-text-is-the-product",
281
+ "tests/quick-session-management.test.cjs :: source-text-is-the-product",
282
+ "tests/qwen-skills-migration.test.cjs :: source-text-is-the-product",
283
+ "tests/read-guard.test.cjs :: source-text-is-the-product",
284
+ "tests/reapply-patches.test.cjs :: source-text-is-the-product",
285
+ "tests/reapply-verify-hunks.test.cjs :: source-text-is-the-product",
286
+ "tests/release-coverage-scope.test.cjs :: source-text-is-the-product",
287
+ "tests/release-tarball-smoke-workflow.test.cjs :: source-text-is-the-product",
288
+ "tests/release-tarball-smoke.install.test.cjs :: integration-test-input",
289
+ "tests/research-agent-profiles.test.cjs :: <runtime-contract-is-the-product> research agent .md content is the governed surface",
290
+ "tests/review-default-reviewers-workflow.test.cjs :: source-text-is-the-product",
291
+ "tests/roadmap.test.cjs :: source-text-is-the-product",
292
+ "tests/roadmapper-granularity.test.cjs :: source-text-is-the-product",
293
+ "tests/run-tests-harness.test.cjs :: run-tests.cjs is a CLI test harness whose only IR is its",
294
+ "tests/runtime-launcher-parity.test.cjs :: structural parity/drift guard — asserts literal presence/absence of the canonical gsd_run launcher and the retired $GSD_SDK / `/gsd-tools` tokens across workflow markdown; there is no typed IR for \"this source file does not contain substring X\".",
295
+ "tests/runtime-name-policy.test.cjs :: runtime-contract-is-the-product — FALLBACK_ALIASES source text IS the",
296
+ "tests/scan-command.test.cjs :: source-text-is-the-product",
297
+ "tests/secret-scan-lint.security.test.cjs :: source-text-is-the-product",
298
+ "tests/secure-phase.test.cjs :: source-text-is-the-product",
299
+ "tests/security-prompt-injection.security.test.cjs :: structural-regression-guard",
300
+ "tests/security-scan.security.test.cjs :: source-text-is-the-product",
301
+ "tests/seed-scan-new-milestone.test.cjs :: source-text-is-the-product",
302
+ "tests/settings-integrations.test.cjs :: source-text-is-the-product",
303
+ "tests/settings-jsonc.test.cjs :: structural-regression-guard",
304
+ "tests/skill-frontmatter-contract.test.cjs :: source-text-is-the-product",
305
+ "tests/spawn-liveness-banner.test.cjs :: source-text-is-the-product",
306
+ "tests/state-acquirestatelock-non-eexist.test.cjs :: architectural-invariant",
307
+ "tests/state.test.cjs :: source-text-is-the-product",
308
+ "tests/subagent-timeout.test.cjs :: source-text-is-the-product",
309
+ "tests/template.test.cjs :: source-text-is-the-product",
310
+ "tests/thinking-partner.test.cjs :: source-text-is-the-product",
311
+ "tests/thread-session-management.test.cjs :: source-text-is-the-product",
312
+ "tests/ultraplan-phase.test.cjs :: source-text-is-the-product",
313
+ "tests/verify-health.test.cjs :: source-text-is-the-product",
314
+ "tests/verify-test-quality.test.cjs :: source-text-is-the-product",
315
+ "tests/verify-work-auto-transition.test.cjs :: source-text-is-the-product",
316
+ "tests/windows-robustness.test.cjs :: source-text-is-the-product",
317
+ "tests/windows-test-parity-guard.test.cjs :: structural-regression-guard",
318
+ "tests/workflow-compat.test.cjs :: source-text-is-the-product",
319
+ "tests/workflow-guard-registration.test.cjs :: structural-regression-guard",
320
+ "tests/workflow-maintainer-skip.test.cjs :: source-text-is-the-product",
321
+ "tests/workflow-shell-pinning.test.cjs :: file-scope prefilter, not a test assertion — we need to",
322
+ "tests/workflow-size-budget.test.cjs :: source-text-is-the-product",
323
+ "tests/workspace.test.cjs :: source-text-is-the-product",
324
+ "tests/worktree-cleanup.test.cjs :: source-text-is-the-product",
325
+ "tests/worktree.test.cjs :: source-text-is-the-product"
326
+ ]
@@ -0,0 +1,162 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ /**
5
+ * lint-allow-test-rule-refs.cjs — enforce that NEW `allow-test-rule:` exemption
6
+ * comments carry a tracking-issue reference.
7
+ *
8
+ * ## Why
9
+ *
10
+ * `allow-test-rule:` is an inline comment that disables the `no-source-grep`
11
+ * ESLint rule for a whole test file. Today many such comments exist with no
12
+ * issue reference, making it impossible to audit or revisit them. Per ADR-456
13
+ * (docs/adr/456-test-rigor-architecture.md) every NEW exemption must carry a
14
+ * `#NNN` issue reference or an https:// URL so the decision is traceable.
15
+ *
16
+ * ## What "compliant" means
17
+ *
18
+ * A compliant `allow-test-rule:` comment is one whose reason text (everything
19
+ * after the colon) contains either:
20
+ * - a `#\d+` token (e.g. `// allow-test-rule: see #1234`)
21
+ * - an https?:// URL
22
+ *
23
+ * Any other comment is an OFFENDER.
24
+ *
25
+ * ## Grandfathering
26
+ *
27
+ * All pre-existing untracked exemptions are recorded in
28
+ * scripts/lint-allow-test-rule-refs.allowlist.json (seeded at gate introduction
29
+ * time). The identity ratchet (scripts/lib/allowlist-ratchet.cjs) means:
30
+ * - A NEW non-compliant comment not in the allowlist → gate fails.
31
+ * - A previously-offending comment that is now compliant → allowlist entry is
32
+ * STALE and must be pruned (ratchet-down; the baseline only ever shrinks).
33
+ *
34
+ * ## Offender identifiers
35
+ *
36
+ * Identifiers are stable cross-rename-safe strings of the form:
37
+ * `<repo-relative-path> :: <trimmed-reason>`
38
+ *
39
+ * e.g. `tests/foo.test.cjs :: source-text-is-the-product`
40
+ *
41
+ * If a file has multiple non-compliant comments with the SAME reason text, only
42
+ * one identifier is recorded (deduped via Set).
43
+ *
44
+ * See docs/adr/456-test-rigor-architecture.md for the full policy.
45
+ */
46
+
47
+ const fs = require('fs');
48
+ const path = require('path');
49
+ const { assertWithinAllowlist } = require('./lib/allowlist-ratchet.cjs');
50
+ const { ExitError, runMain } = require('./lib/cli-exit.cjs');
51
+
52
+ const ROOT = path.join(__dirname, '..');
53
+ const TESTS_DIR = process.env.GSD_LINT_ALLOW_TEST_RULE_TESTS_DIR || path.join(ROOT, 'tests');
54
+ const ALLOWLIST_PATH =
55
+ process.env.GSD_LINT_ALLOW_TEST_RULE_ALLOWLIST ||
56
+ path.join(__dirname, 'lint-allow-test-rule-refs.allowlist.json');
57
+
58
+ /**
59
+ * Extracts the reason text after `allow-test-rule:` from a single line of source
60
+ * text in any comment form that the no-source-grep ESLint rule honours.
61
+ *
62
+ * The ESLint rule tests `c.value` (AST comment node value, delimiters stripped)
63
+ * with /allow-test-rule:\s*\S/, which fires on BOTH:
64
+ * // allow-test-rule: <reason> (line comment)
65
+ * /* allow-test-rule: <reason> * / (block comment, single-line)
66
+ *
67
+ * By scanning line-by-line and extracting everything after `allow-test-rule:` on
68
+ * each line, we cover both forms without a cross-line regex (which was previously
69
+ * matching arbitrary `/* ... * /` pairs spanning hundreds of lines, causing false
70
+ * positives).
71
+ *
72
+ * The trailing `*\/` and whitespace are stripped so block-comment closers don't
73
+ * bleed into the extracted reason.
74
+ */
75
+ const ALLOW_TEST_RULE_LINE_RE = /allow-test-rule:\s*(.+)/;
76
+ /** Matches a compliant issue reference or URL */
77
+ const ISSUE_REF_RE = /#\d+|https?:\/\//;
78
+
79
+ /**
80
+ * Recursively collect offender identifiers from all *.test.cjs files under dir.
81
+ *
82
+ * @param {string} dir absolute path to scan
83
+ * @returns {string[]} sorted, deduped list of `<relpath> :: <reason>` strings
84
+ */
85
+ function collectOffenders(dir) {
86
+ const offenders = new Set();
87
+
88
+ function scan(current) {
89
+ for (const entry of fs.readdirSync(current, { withFileTypes: true })) {
90
+ const full = path.join(current, entry.name);
91
+ if (entry.isDirectory()) {
92
+ scan(full);
93
+ } else if (entry.isFile() && entry.name.endsWith('.test.cjs')) {
94
+ const relpath = path.relative(ROOT, full).split(path.sep).join('/');
95
+ let content;
96
+ try {
97
+ content = fs.readFileSync(full, 'utf8');
98
+ } catch {
99
+ // skip unreadable files (e.g. binary)
100
+ continue;
101
+ }
102
+ // Scan line-by-line. By testing each line for `allow-test-rule:` we
103
+ // cover BOTH comment forms without a cross-line regex:
104
+ // // allow-test-rule: <reason> ← line comment
105
+ // /* allow-test-rule: <reason> */ ← single-line block comment
106
+ //
107
+ // For each matching line we extract the reason (everything after the
108
+ // colon), then strip any trailing block-comment closer `*/` and
109
+ // whitespace so the identifier stays clean.
110
+ for (const line of content.split('\n')) {
111
+ const m = ALLOW_TEST_RULE_LINE_RE.exec(line);
112
+ if (!m) continue;
113
+ // Strip trailing block-comment closer and whitespace if present
114
+ const reason = m[1].replace(/\s*\*\/\s*$/, '').trim();
115
+ if (!reason) continue;
116
+ if (ISSUE_REF_RE.test(reason)) continue; // compliant — skip
117
+ offenders.add(`${relpath} :: ${reason}`);
118
+ }
119
+ }
120
+ }
121
+ }
122
+
123
+ scan(dir);
124
+ return [...offenders].sort();
125
+ }
126
+
127
+ function main() {
128
+ const args = process.argv.slice(2);
129
+ const unknown = args.filter((a) => a !== '--help');
130
+ if (unknown.length > 0) {
131
+ throw new ExitError(2, `lint-allow-test-rule-refs: unknown argument(s): ${unknown.join(', ')}`);
132
+ }
133
+
134
+ const current = collectOffenders(TESTS_DIR);
135
+ const known = JSON.parse(fs.readFileSync(ALLOWLIST_PATH, 'utf8'));
136
+
137
+ const failures = [];
138
+ const { novel } = assertWithinAllowlist({
139
+ label: 'allow-test-rule-refs',
140
+ current,
141
+ known,
142
+ fail: (msg) => failures.push(msg),
143
+ pruneHint: 'edit scripts/lint-allow-test-rule-refs.allowlist.json',
144
+ });
145
+
146
+ if (failures.length > 0) {
147
+ for (const msg of failures) process.stderr.write(`${msg}\n`);
148
+ if (novel.length > 0) {
149
+ process.stderr.write(
150
+ '\nNew allow-test-rule exemption without an issue ref — add `see #NNN` per ADR-456' +
151
+ ' (docs/adr/456-test-rigor-architecture.md).\n'
152
+ );
153
+ }
154
+ throw new ExitError(1);
155
+ }
156
+
157
+ console.log(
158
+ `ok lint-allow-test-rule-refs: ${current.length} grandfathered exemption(s) tracked, no novel untracked offenders`
159
+ );
160
+ }
161
+
162
+ runMain(main);
@@ -160,6 +160,14 @@
160
160
  "edge-probe.test.cjs"
161
161
  ],
162
162
  "issue": "550"
163
+ },
164
+ "federated-config": {
165
+ "files": [
166
+ "federated-config.test.cjs",
167
+ "federated-config-loadconfig.test.cjs",
168
+ "federated-config-key-removal.test.cjs"
169
+ ],
170
+ "issue": "TBD"
163
171
  }
164
172
  }
165
173
  }