From 622e4be6d825b7d5fa09c9fd19bce38126b804fe Mon Sep 17 00:00:00 2001 From: Colin Date: Tue, 9 Jun 2026 23:03:14 -0400 Subject: [PATCH] test(ratchet): ban new top-level bug-NNNN test files via identity allowlist 244 one-off bug-* files (~38% of the suite) are grandfathered in lint-regression-test-names.allowlist.json; new ones fail lint with fold-into-module guidance, and deletions force allowlist pruning so the baseline only shrinks. Wired into npm run lint:ci (new single entry point for every CI lint). Policy documented in docs/TESTING-SUITES.md. Co-Authored-By: Claude Fable 5 --- package.json | 2 + .../lint-regression-test-names.allowlist.json | 246 ++++++++++++++++++ scripts/lint-regression-test-names.cjs | 78 ++++++ 3 files changed, 326 insertions(+) create mode 100644 scripts/lint-regression-test-names.allowlist.json create mode 100644 scripts/lint-regression-test-names.cjs diff --git a/package.json b/package.json index 050571a0e..57f6ce2ad 100644 --- a/package.json +++ b/package.json @@ -91,6 +91,8 @@ "pretest:coverage": "npm run build:lib && npm run lint:skill-deps", "lint": "eslint . --cache --cache-location node_modules/.cache/eslint/", "lint:fix": "eslint . --fix", + "lint:ci": "eslint . && npm run lint:skill-deps && node scripts/lint-test-file-count.cjs && node scripts/lint-command-contract.cjs && node scripts/lint-pr-check-project-dir.cjs && npm run lint:legacy-name && node scripts/lint-regression-test-names.cjs", + "lint:regression-names": "node scripts/lint-regression-test-names.cjs", "lint:descriptions": "node scripts/lint-descriptions.cjs", "lint:skill-deps": "node scripts/lint-skill-deps.cjs", "lint:test-file-count": "node scripts/lint-test-file-count.cjs", diff --git a/scripts/lint-regression-test-names.allowlist.json b/scripts/lint-regression-test-names.allowlist.json new file mode 100644 index 000000000..f99f80a62 --- /dev/null +++ b/scripts/lint-regression-test-names.allowlist.json @@ -0,0 +1,246 @@ +[ + "bug-10-semver-policy-consolidation.test.cjs", + "bug-130-finishinstall-opencode-testmode.test.cjs", + "bug-131-release-tarball-smoke-explicit-home.test.cjs", + "bug-14-progress-auto-flag-dropped.test.cjs", + "bug-167-query-meta-command.test.cjs", + "bug-17-askuserquestion-option-cap.test.cjs", + "bug-170-workflow-fallback-install-hint.test.cjs", + "bug-1736-local-install-commands.test.cjs", + "bug-1754-js-hook-guard.test.cjs", + "bug-1817-sh-hook-guard.test.cjs", + "bug-1818-unknown-flags.test.cjs", + "bug-1826-phases-clear-confirm.test.cjs", + "bug-1829-inherit-model-profile.test.cjs", + "bug-1834-sh-hooks-installed.test.cjs", + "bug-1891-file-resolution.test.cjs", + "bug-190-bridge-collapse.test.cjs", + "bug-1906-hook-relative-paths.test.cjs", + "bug-1908-uninstall-manifest.test.cjs", + "bug-1924-preserve-user-artifacts.test.cjs", + "bug-1967-cache-invalidation.test.cjs", + "bug-1974-context-exhaustion-record.test.cjs", + "bug-2002-offer-next-context.test.cjs", + "bug-2004-pr-branch-milestone.test.cjs", + "bug-21-state-md-template-frontmatter.test.cjs", + "bug-211-launcher-home-fallback.test.cjs", + "bug-2136-sh-hook-version.test.cjs", + "bug-214-phase-researcher-write-truncation-contract.test.cjs", + "bug-214-writer-agents-write-truncation-contract.test.cjs", + "bug-222-research-synthesizer-write-contract.test.cjs", + "bug-224-pick-stdout-capture.test.cjs", + "bug-2248-local-install-statusline.test.cjs", + "bug-2256-model-overrides-transport.test.cjs", + "bug-2268-parallel-discuss.test.cjs", + "bug-2344-read-guard-claudecode-env.test.cjs", + "bug-2346-agent-read-loop-guards.test.cjs", + "bug-2351-intel-kilo-layout.test.cjs", + "bug-2376-opencode-windows-home-path.test.cjs", + "bug-2384-post-merge-deletion-audit.test.cjs", + "bug-2388-plan-phase-no-branch-rename.test.cjs", + "bug-2396-makefile-test-priority.test.cjs", + "bug-2399-commit-docs-plan-phase.test.cjs", + "bug-2410-stream-checkpoint-heartbeats.test.cjs", + "bug-2418-antigravity-bare-path.test.cjs", + "bug-2419-project-researcher-agent.test.cjs", + "bug-2421-planner-grep-gate-hygiene.test.cjs", + "bug-2424-reapply-patches-baseline-detection.test.cjs", + "bug-2432-quick-plan-predispatch-commit.test.cjs", + "bug-2451-context-monitor-over-report.test.cjs", + "bug-2470-update-md-claude-path.test.cjs", + "bug-2492-context-coverage-gate.test.cjs", + "bug-2501-resurrection-detection.test.cjs", + "bug-2502-insert-phase-state-update.test.cjs", + "bug-2504-uat-foundation-phases.test.cjs", + "bug-2506-settings-profile-nonclaude-warning.test.cjs", + "bug-2516-inherit-model-execute-phase.test.cjs", + "bug-2520-read-guard-hook-subprocess-env.test.cjs", + "bug-2523-quick-deferred-items.test.cjs", + "bug-2530-valid-config-keys.test.cjs", + "bug-2543-gsd-slash-namespace.test.cjs", + "bug-2545-copilot-unreplaced-paths.test.cjs", + "bug-2549-2550-2552-discuss-phase-context.test.cjs", + "bug-2554-decimal-phase-filter.test.cjs", + "bug-2557-gemini-local-hook-paths.test.cjs", + "bug-2559-stale-search-year.test.cjs", + "bug-260-worktree-path-guard.test.cjs", + "bug-2601-inherit-model-profile.test.cjs", + "bug-261-worktree-force-add-guard.test.cjs", + "bug-2630-state-frontmatter-milestone-switch.test.cjs", + "bug-2638-sub-repos-canonical-location.test.cjs", + "bug-2643-skill-frontmatter-name.test.cjs", + "bug-2659-audit-open-crash.test.cjs", + "bug-2660-one-liner-extraction.test.cjs", + "bug-2661-roadmap-sync-parallel.test.cjs", + "bug-2686-review-fix-worktree.test.cjs", + "bug-2698-crlf-install.test.cjs", + "bug-2760-codex-install-defensive.test.cjs", + "bug-2769-requirements-header-variants.test.cjs", + "bug-2770-annotate-deps-int-coerce.test.cjs", + "bug-2771-user-profile-manifest.test.cjs", + "bug-2772-gitmodules-path-intersection.test.cjs", + "bug-2784-update-cache-clear-path.test.cjs", + "bug-279-codex-agent-mapping.test.cjs", + "bug-2794-opencode-model-profile-overrides.test.cjs", + "bug-2798-context-window-config-key.test.cjs", + "bug-2801-ingest-docs-handler.test.cjs", + "bug-2808-skill-hyphen-name.test.cjs", + "bug-2831-opencode-home-path-prefix.test.cjs", + "bug-2836-audit-open-summary-uat-drift.test.cjs", + "bug-2838-summary-rescue-gitignored-planning.test.cjs", + "bug-2839-review-fix-transactional-cleanup.test.cjs", + "bug-2851-workflow-bare-gsd-tools.test.cjs", + "bug-2866-codex-strip-no-trailing-newline.test.cjs", + "bug-2876-skill-frontmatter-quote.test.cjs", + "bug-2911-audit-open-output-shape.test.cjs", + "bug-2912-progress-context-authority.test.cjs", + "bug-2916-handle-branching-default-base.test.cjs", + "bug-2942-detect-custom-skills.test.cjs", + "bug-2943-config-get-context-window-default.test.cjs", + "bug-2948-spike-wrap-up-dispatch.test.cjs", + "bug-2949-sketch-wrap-up-dispatch.test.cjs", + "bug-2950-stale-command-refs.test.cjs", + "bug-2954-help-md-slash-command-stubs.test.cjs", + "bug-2957-claude-global-postinstall-message.test.cjs", + "bug-2969-verify-reapply-patches.test.cjs", + "bug-2973-profile-user-skills-path.test.cjs", + "bug-2979-hook-absolute-node.test.cjs", + "bug-2986-config-schema-mutation-killers.test.cjs", + "bug-2990-code-fixer-worktree-branch.test.cjs", + "bug-2992-check-latest-version.test.cjs", + "bug-2994-verify-reapply-patches-installed-path.test.cjs", + "bug-2995-post-install-script-paths.test.cjs", + "bug-2998-pristine-dir-populated.test.cjs", + "bug-3017-codex-hook-absolute-node.test.cjs", + "bug-3018-codex-discuss-fallback.test.cjs", + "bug-3019-help-passthrough.test.cjs", + "bug-3037-gemini-duplicate-commands.test.cjs", + "bug-3050-update-backup-eacces-nonfatal.test.cjs", + "bug-3054-stale-gsd-next-references.test.cjs", + "bug-3072-optional-sketch-findings-guard.test.cjs", + "bug-3083-resume-route-clear.test.cjs", + "bug-3086-git-create-tag-config-gate.test.cjs", + "bug-3087-planner-directive-language.test.cjs", + "bug-3096-ai-integration-phase-parallel-race.test.cjs", + "bug-3097-3099-executor-worktree-path-safety.test.cjs", + "bug-3120-secure-phase-empty-register.test.cjs", + "bug-3126-global-skills-base-runtime-path.test.cjs", + "bug-3127-state-begin-phase-idempotent.test.cjs", + "bug-3128-roadmap-plan-count-slug-layout.test.cjs", + "bug-3129-validate-commit-git-bypass.test.cjs", + "bug-3130-update-npx-robust-invocation.test.cjs", + "bug-3135-capture-backlog-workflow.test.cjs", + "bug-3150-stats-json-decimal-phase-gaps.test.cjs", + "bug-3156-plan-phase-opencode-dispatch.test.cjs", + "bug-3163-codex-agents-md.test.cjs", + "bug-3168-task-to-agent-rename.test.cjs", + "bug-3181-node-cellar-path.test.cjs", + "bug-3195-quick-resurrection-guard.test.cjs", + "bug-3197-gsd-tools-config-whitelist.test.cjs", + "bug-321-config-defaults-clone-strategy.test.cjs", + "bug-3212-execute-phase-stall-safe-resume.test.cjs", + "bug-3227-config-set-model-overrides.test.cjs", + "bug-3236-capture-seed-one-shot.test.cjs", + "bug-3242-state-update-progress-trample.test.cjs", + "bug-3243-dotted-command-form.test.cjs", + "bug-3245-codex-toml-floats.test.cjs", + "bug-3257-nested-plans-undercount.test.cjs", + "bug-3258-no-stale-gsd-intel-references.test.cjs", + "bug-3275-fmstr-non-string-scalars.test.cjs", + "bug-3285-codex-hooks-state-allowed.test.cjs", + "bug-3286-state-write-routing.test.cjs", + "bug-3288-model-catalog-install-path.test.cjs", + "bug-3290-intel-updater-layout-block.test.cjs", + "bug-33-settings-model-profile-adaptive.test.cjs", + "bug-3320-planner-deep-work-rules.test.cjs", + "bug-3321-verifier-runs-probes.test.cjs", + "bug-3346-codex-aot-toml-key.test.cjs", + "bug-3357-codex-legacy-hooks-json-migration.test.cjs", + "bug-3360-codex-execute-phase-worktrees.test.cjs", + "bug-338-local-install-settings-local-json.test.cjs", + "bug-3381-verify-work-workstream.test.cjs", + "bug-3384-secondary-defects.test.cjs", + "bug-3407-pristine-stale-content.test.cjs", + "bug-3413-shell-command-projection.test.cjs", + "bug-3418-progress-flag-routing.test.cjs", + "bug-3426-codex-windows-hooks.test.cjs", + "bug-3427-3433-codex-install-shape.test.cjs", + "bug-3430-planner-phase-contract.test.cjs", + "bug-3431-debug-command-yaml.test.cjs", + "bug-3441-path-action-projection.test.cjs", + "bug-3442-codex-legacy-hooks-json-migration.test.cjs", + "bug-3442-shim-projection-drift-guard.test.cjs", + "bug-3446-resume-continue-here-discovery.test.cjs", + "bug-3454-state-dollar-backreference-growth.test.cjs", + "bug-3489-complete-phase-idempotent.test.cjs", + "bug-3491-nested-git-worktree.test.cjs", + "bug-3509-path-spaces.test.cjs", + "bug-3516-reapply-patches-gsd-update-filter.test.cjs", + "bug-3521-quick-cleanup-cwd-pin.test.cjs", + "bug-3523-cjs-loadconfig-branching-strategy-warning.test.cjs", + "bug-3537-padded-id-against-unpadded-roadmap.test.cjs", + "bug-3541-installer-migration-prompt-user-resolution.test.cjs", + "bug-3542-executor-git-stash-prohibition.test.cjs", + "bug-3562-codex-install-skill-surface.test.cjs", + "bug-3566-codex-hooks-feature-canonical-key.test.cjs", + "bug-3571-configuration-manifest-install-path.test.cjs", + "bug-3582-codex-skills-materialized.test.cjs", + "bug-3584-runtime-slash-emitters.test.cjs", + "bug-3584-runtime-slash-formatter.test.cjs", + "bug-3588-npm-audit-clean.test.cjs", + "bug-3599-roadmap-get-phase-project-code-prefix.test.cjs", + "bug-3605-stale-research-insert-phase-agent-refs.test.cjs", + "bug-3608-antigravity-update-runtime-classification.test.cjs", + "bug-3610-installer-migration-bundled-hooks-classification.test.cjs", + "bug-3628-bundled-hook-classifier-whitelist.test.cjs", + "bug-3631-router-raw-flag.test.cjs", + "bug-3657-verify-reapply-patches-pristine-drift.test.cjs", + "bug-3659-applysurface-prune-skill-dirs.test.cjs", + "bug-3668-workflow-runtime-resolution.test.cjs", + "bug-3670-cursor-local-install-migration-lock.test.cjs", + "bug-3677-agent-colon-namespace-leak.test.cjs", + "bug-3678-executor-commit-docs-respect.test.cjs", + "bug-3683-command-colon-namespace-leak.test.cjs", + "bug-3683-command-cross-reference-invariant.test.cjs", + "bug-3683-workflow-colon-namespace-leak.test.cjs", + "bug-3689-resume-glob-nomatch.test.cjs", + "bug-3691-annotate-deps-plans-block-variants.test.cjs", + "bug-3706-ui-safety-gate-false-positives.test.cjs", + "bug-3707-locked-worktree-cleanup.test.cjs", + "bug-3727-code-review-fix-flag-dispatch.test.cjs", + "bug-3735-profiles-core-includes-surface.test.cjs", + "bug-3739-gap-checker-padded-prefix-context.test.cjs", + "bug-376-claude-js-hook-gsd-rewriter.test.cjs", + "bug-378-update-check-scoped-name.test.cjs", + "bug-3784-gsd-settings-model-profile-ui-omits-adaptive.test.cjs", + "bug-3805-fast-md-log-to-state-schema.test.cjs", + "bug-3808-codex-adapter-text-mode-fallback.test.cjs", + "bug-384-agents-runtime-aware.test.cjs", + "bug-397-state-preserve-executor-authored.test.cjs", + "bug-410-install-defaults-test-mode-guard.test.cjs", + "bug-416-archive-dir-null.test.cjs", + "bug-442-config-dir-equals-in-path.test.cjs", + "bug-444-resolver-local-claude-install.test.cjs", + "bug-447-gap-analysis-phase-req-ids.test.cjs", + "bug-474-clock-seam-date-determinism.test.cjs", + "bug-492-effort-manifest-fallback.test.cjs", + "bug-500-planned-phase-progress-corruption.test.cjs", + "bug-501-flat-phase-details-milestone-leak.test.cjs", + "bug-503-update-agent-antigravity-detection.test.cjs", + "bug-505-remove-dead-sdk-verification.test.cjs", + "bug-549-total-phases-overcounts-with-phase-section-heading.test.cjs", + "bug-557-details-summary-milestone-strip.test.cjs", + "bug-570-codex-leak-scanner.test.cjs", + "bug-571-doc-writer-fix-mode-edit-only.test.cjs", + "bug-580-local-sh-hook-bash-wrapper.test.cjs", + "bug-619-codebase-drift-gate-shim.test.cjs", + "bug-621-plan-phase-gap-analysis-gsd-run.test.cjs", + "bug-622-graphify-optional-graph-html.test.cjs", + "bug-630-wave-cleanup-orchestrator-root.test.cjs", + "bug-637-workflow-no-hardcoded-home-tool.test.cjs", + "bug-641-files-from-suite-token.test.cjs", + "bug-663-redos-roadmap-phase-parsing.test.cjs", + "bug-685-windowshide-spawn.test.cjs", + "bug-687-agy-timeout.test.cjs", + "bug-704-codex-launcher-path-corruption.test.cjs" +] diff --git a/scripts/lint-regression-test-names.cjs b/scripts/lint-regression-test-names.cjs new file mode 100644 index 000000000..724f81b52 --- /dev/null +++ b/scripts/lint-regression-test-names.cjs @@ -0,0 +1,78 @@ +#!/usr/bin/env node +'use strict'; + +/** + * lint-regression-test-names.cjs — ban NEW top-level bug-NNNN test files. + * + * ## Why + * + * The 2026-06 CI audit found 244 one-off `tests/bug-NNNN-*.test.cjs` files — + * ~38% of the suite. `node --test` spawns one child process per FILE, so file + * count (not test count) is the unit of CI overhead, and it is worst on the + * Windows lanes where every spawn is Defender-scanned. Each regression test + * belongs in the owning module's main test file as a regression case (e.g. a + * `describe('regressions')` block in `tests/.test.cjs`), where it + * costs zero additional processes. + * + * ## What this enforces + * + * Identity ratchet (scripts/lib/allowlist-ratchet.cjs) over basenames matching + * /^bug-\d+.*\.test\.cjs$/ in tests/: + * - A NEW bug-* file (not in the allowlist) fails: fold the regression into + * the owning module's test file instead. + * - A REMOVED bug-* file with a stale allowlist entry also fails: prune the + * entry from scripts/lint-regression-test-names.allowlist.json so the + * baseline only ever shrinks. + * + * See docs/TESTING-SUITES.md ("Regression tests") for the placement policy. + */ + +const fs = require('fs'); +const path = require('path'); +const { assertWithinAllowlist } = require('./lib/allowlist-ratchet.cjs'); +const { ExitError, runMain } = require('./lib/cli-exit.cjs'); + +const ROOT = path.join(__dirname, '..'); +// Env overrides exist for the lint's own tests only (sandbox fixture dirs). +const TESTS_DIR = process.env.GSD_LINT_REGRESSION_TESTS_DIR || path.join(ROOT, 'tests'); +const ALLOWLIST_PATH = + process.env.GSD_LINT_REGRESSION_ALLOWLIST || + path.join(__dirname, 'lint-regression-test-names.allowlist.json'); + +const BUG_FILE_RE = /^bug-\d+.*\.test\.cjs$/; + +function main() { + const current = fs + .readdirSync(TESTS_DIR) + .filter(f => BUG_FILE_RE.test(f)) + .sort(); + const known = JSON.parse(fs.readFileSync(ALLOWLIST_PATH, 'utf8')); + + const failures = []; + const { novel } = assertWithinAllowlist({ + label: 'regression-test-names', + current, + known, + fail: msg => failures.push(msg), + pruneHint: 'edit scripts/lint-regression-test-names.allowlist.json', + }); + + if (failures.length > 0) { + for (const msg of failures) console.error(msg); + if (novel.length > 0) { + console.error( + '\nNew bug-NNNN test files are no longer accepted. Add the regression ' + + "case to the owning module's test file (e.g. a describe('regressions') " + + 'block in tests/.test.cjs) instead of creating a new file. ' + + 'See docs/TESTING-SUITES.md.' + ); + } + throw new ExitError(1); + } + + console.log( + `ok lint-regression-test-names: ${current.length} grandfathered bug-* file(s), no novel offenders` + ); +} + +runMain(main);