diff --git a/.changes/actionlint/ci-selection-gap-declarations-6457-5aa475bb.md b/.changes/actionlint/ci-selection-gap-declarations-6457-5aa475bb.md new file mode 100644 index 0000000000..c2860db872 --- /dev/null +++ b/.changes/actionlint/ci-selection-gap-declarations-6457-5aa475bb.md @@ -0,0 +1,5 @@ +--- +bump: none +--- + +Test suites only: each declares the files it reads so the pull-request test selection runs it when they change. Nothing the plugin ships changes. diff --git a/.changes/animation/ci-selection-gap-declarations-6457-0346e98c.md b/.changes/animation/ci-selection-gap-declarations-6457-0346e98c.md new file mode 100644 index 0000000000..c2860db872 --- /dev/null +++ b/.changes/animation/ci-selection-gap-declarations-6457-0346e98c.md @@ -0,0 +1,5 @@ +--- +bump: none +--- + +Test suites only: each declares the files it reads so the pull-request test selection runs it when they change. Nothing the plugin ships changes. diff --git a/.changes/architecture/ci-selection-gap-declarations-6457-dfb4213e.md b/.changes/architecture/ci-selection-gap-declarations-6457-dfb4213e.md new file mode 100644 index 0000000000..c2860db872 --- /dev/null +++ b/.changes/architecture/ci-selection-gap-declarations-6457-dfb4213e.md @@ -0,0 +1,5 @@ +--- +bump: none +--- + +Test suites only: each declares the files it reads so the pull-request test selection runs it when they change. Nothing the plugin ships changes. diff --git a/.changes/autonomy/ci-selection-gap-declarations-6457-781d539e.md b/.changes/autonomy/ci-selection-gap-declarations-6457-781d539e.md new file mode 100644 index 0000000000..c2860db872 --- /dev/null +++ b/.changes/autonomy/ci-selection-gap-declarations-6457-781d539e.md @@ -0,0 +1,5 @@ +--- +bump: none +--- + +Test suites only: each declares the files it reads so the pull-request test selection runs it when they change. Nothing the plugin ships changes. diff --git a/.changes/bash-format/ci-selection-gap-declarations-6457-ce48a644.md b/.changes/bash-format/ci-selection-gap-declarations-6457-ce48a644.md new file mode 100644 index 0000000000..c2860db872 --- /dev/null +++ b/.changes/bash-format/ci-selection-gap-declarations-6457-ce48a644.md @@ -0,0 +1,5 @@ +--- +bump: none +--- + +Test suites only: each declares the files it reads so the pull-request test selection runs it when they change. Nothing the plugin ships changes. diff --git a/.changes/biome-format/ci-selection-gap-declarations-6457-38dbc180.md b/.changes/biome-format/ci-selection-gap-declarations-6457-38dbc180.md new file mode 100644 index 0000000000..c2860db872 --- /dev/null +++ b/.changes/biome-format/ci-selection-gap-declarations-6457-38dbc180.md @@ -0,0 +1,5 @@ +--- +bump: none +--- + +Test suites only: each declares the files it reads so the pull-request test selection runs it when they change. Nothing the plugin ships changes. diff --git a/.changes/code-metrics/ci-selection-gap-declarations-6457-eabd0f4b.md b/.changes/code-metrics/ci-selection-gap-declarations-6457-eabd0f4b.md new file mode 100644 index 0000000000..c2860db872 --- /dev/null +++ b/.changes/code-metrics/ci-selection-gap-declarations-6457-eabd0f4b.md @@ -0,0 +1,5 @@ +--- +bump: none +--- + +Test suites only: each declares the files it reads so the pull-request test selection runs it when they change. Nothing the plugin ships changes. diff --git a/.changes/code-tidying/ci-selection-gap-declarations-6457-247cdcbf.md b/.changes/code-tidying/ci-selection-gap-declarations-6457-247cdcbf.md new file mode 100644 index 0000000000..c2860db872 --- /dev/null +++ b/.changes/code-tidying/ci-selection-gap-declarations-6457-247cdcbf.md @@ -0,0 +1,5 @@ +--- +bump: none +--- + +Test suites only: each declares the files it reads so the pull-request test selection runs it when they change. Nothing the plugin ships changes. diff --git a/.changes/context-budget/ci-selection-gap-declarations-6457-87f0e547.md b/.changes/context-budget/ci-selection-gap-declarations-6457-87f0e547.md new file mode 100644 index 0000000000..c2860db872 --- /dev/null +++ b/.changes/context-budget/ci-selection-gap-declarations-6457-87f0e547.md @@ -0,0 +1,5 @@ +--- +bump: none +--- + +Test suites only: each declares the files it reads so the pull-request test selection runs it when they change. Nothing the plugin ships changes. diff --git a/.changes/discovery/ci-selection-gap-declarations-6457-6afe6e97.md b/.changes/discovery/ci-selection-gap-declarations-6457-6afe6e97.md new file mode 100644 index 0000000000..c2860db872 --- /dev/null +++ b/.changes/discovery/ci-selection-gap-declarations-6457-6afe6e97.md @@ -0,0 +1,5 @@ +--- +bump: none +--- + +Test suites only: each declares the files it reads so the pull-request test selection runs it when they change. Nothing the plugin ships changes. diff --git a/.changes/disk-hygiene/ci-selection-gap-declarations-6457-5101169a.md b/.changes/disk-hygiene/ci-selection-gap-declarations-6457-5101169a.md new file mode 100644 index 0000000000..c2860db872 --- /dev/null +++ b/.changes/disk-hygiene/ci-selection-gap-declarations-6457-5101169a.md @@ -0,0 +1,5 @@ +--- +bump: none +--- + +Test suites only: each declares the files it reads so the pull-request test selection runs it when they change. Nothing the plugin ships changes. diff --git a/.changes/docs-hygiene/ci-selection-gap-declarations-6457-03163494.md b/.changes/docs-hygiene/ci-selection-gap-declarations-6457-03163494.md new file mode 100644 index 0000000000..c2860db872 --- /dev/null +++ b/.changes/docs-hygiene/ci-selection-gap-declarations-6457-03163494.md @@ -0,0 +1,5 @@ +--- +bump: none +--- + +Test suites only: each declares the files it reads so the pull-request test selection runs it when they change. Nothing the plugin ships changes. diff --git a/.changes/docs-naming/ci-selection-gap-declarations-6457-fb6bd772.md b/.changes/docs-naming/ci-selection-gap-declarations-6457-fb6bd772.md new file mode 100644 index 0000000000..c2860db872 --- /dev/null +++ b/.changes/docs-naming/ci-selection-gap-declarations-6457-fb6bd772.md @@ -0,0 +1,5 @@ +--- +bump: none +--- + +Test suites only: each declares the files it reads so the pull-request test selection runs it when they change. Nothing the plugin ships changes. diff --git a/.changes/education/ci-selection-gap-declarations-6457-4f2a9a33.md b/.changes/education/ci-selection-gap-declarations-6457-4f2a9a33.md new file mode 100644 index 0000000000..c2860db872 --- /dev/null +++ b/.changes/education/ci-selection-gap-declarations-6457-4f2a9a33.md @@ -0,0 +1,5 @@ +--- +bump: none +--- + +Test suites only: each declares the files it reads so the pull-request test selection runs it when they change. Nothing the plugin ships changes. diff --git a/.changes/eol-normalizer/ci-selection-gap-declarations-6457-a1176566.md b/.changes/eol-normalizer/ci-selection-gap-declarations-6457-a1176566.md new file mode 100644 index 0000000000..c2860db872 --- /dev/null +++ b/.changes/eol-normalizer/ci-selection-gap-declarations-6457-a1176566.md @@ -0,0 +1,5 @@ +--- +bump: none +--- + +Test suites only: each declares the files it reads so the pull-request test selection runs it when they change. Nothing the plugin ships changes. diff --git a/.changes/evals/ci-selection-gap-declarations-6457-09c0ad8c.md b/.changes/evals/ci-selection-gap-declarations-6457-09c0ad8c.md new file mode 100644 index 0000000000..c2860db872 --- /dev/null +++ b/.changes/evals/ci-selection-gap-declarations-6457-09c0ad8c.md @@ -0,0 +1,5 @@ +--- +bump: none +--- + +Test suites only: each declares the files it reads so the pull-request test selection runs it when they change. Nothing the plugin ships changes. diff --git a/.changes/event-storming/ci-selection-gap-declarations-6457-a510fe3f.md b/.changes/event-storming/ci-selection-gap-declarations-6457-a510fe3f.md new file mode 100644 index 0000000000..c2860db872 --- /dev/null +++ b/.changes/event-storming/ci-selection-gap-declarations-6457-a510fe3f.md @@ -0,0 +1,5 @@ +--- +bump: none +--- + +Test suites only: each declares the files it reads so the pull-request test selection runs it when they change. Nothing the plugin ships changes. diff --git a/.changes/explainer-video/ci-selection-gap-declarations-6457-bc898bc0.md b/.changes/explainer-video/ci-selection-gap-declarations-6457-bc898bc0.md new file mode 100644 index 0000000000..c2860db872 --- /dev/null +++ b/.changes/explainer-video/ci-selection-gap-declarations-6457-bc898bc0.md @@ -0,0 +1,5 @@ +--- +bump: none +--- + +Test suites only: each declares the files it reads so the pull-request test selection runs it when they change. Nothing the plugin ships changes. diff --git a/.changes/go-format/ci-selection-gap-declarations-6457-b23cdd27.md b/.changes/go-format/ci-selection-gap-declarations-6457-b23cdd27.md new file mode 100644 index 0000000000..c2860db872 --- /dev/null +++ b/.changes/go-format/ci-selection-gap-declarations-6457-b23cdd27.md @@ -0,0 +1,5 @@ +--- +bump: none +--- + +Test suites only: each declares the files it reads so the pull-request test selection runs it when they change. Nothing the plugin ships changes. diff --git a/.changes/guardrails/ci-selection-gap-declarations-6457-a7b6ee7e.md b/.changes/guardrails/ci-selection-gap-declarations-6457-a7b6ee7e.md new file mode 100644 index 0000000000..c2860db872 --- /dev/null +++ b/.changes/guardrails/ci-selection-gap-declarations-6457-a7b6ee7e.md @@ -0,0 +1,5 @@ +--- +bump: none +--- + +Test suites only: each declares the files it reads so the pull-request test selection runs it when they change. Nothing the plugin ships changes. diff --git a/.changes/harness-config/ci-selection-gap-declarations-6457-7f3b0c8d.md b/.changes/harness-config/ci-selection-gap-declarations-6457-7f3b0c8d.md new file mode 100644 index 0000000000..c2860db872 --- /dev/null +++ b/.changes/harness-config/ci-selection-gap-declarations-6457-7f3b0c8d.md @@ -0,0 +1,5 @@ +--- +bump: none +--- + +Test suites only: each declares the files it reads so the pull-request test selection runs it when they change. Nothing the plugin ships changes. diff --git a/.changes/harness-memory/ci-selection-gap-declarations-6457-cc460d18.md b/.changes/harness-memory/ci-selection-gap-declarations-6457-cc460d18.md new file mode 100644 index 0000000000..c2860db872 --- /dev/null +++ b/.changes/harness-memory/ci-selection-gap-declarations-6457-cc460d18.md @@ -0,0 +1,5 @@ +--- +bump: none +--- + +Test suites only: each declares the files it reads so the pull-request test selection runs it when they change. Nothing the plugin ships changes. diff --git a/.changes/harness-ops/ci-selection-gap-declarations-6457-2f03e55a.md b/.changes/harness-ops/ci-selection-gap-declarations-6457-2f03e55a.md new file mode 100644 index 0000000000..c2860db872 --- /dev/null +++ b/.changes/harness-ops/ci-selection-gap-declarations-6457-2f03e55a.md @@ -0,0 +1,5 @@ +--- +bump: none +--- + +Test suites only: each declares the files it reads so the pull-request test selection runs it when they change. Nothing the plugin ships changes. diff --git a/.changes/knowledge/ci-selection-gap-declarations-6457-deb3d90c.md b/.changes/knowledge/ci-selection-gap-declarations-6457-deb3d90c.md new file mode 100644 index 0000000000..c2860db872 --- /dev/null +++ b/.changes/knowledge/ci-selection-gap-declarations-6457-deb3d90c.md @@ -0,0 +1,5 @@ +--- +bump: none +--- + +Test suites only: each declares the files it reads so the pull-request test selection runs it when they change. Nothing the plugin ships changes. diff --git a/.changes/markdown-format/ci-selection-gap-declarations-6457-0a96a9df.md b/.changes/markdown-format/ci-selection-gap-declarations-6457-0a96a9df.md new file mode 100644 index 0000000000..c2860db872 --- /dev/null +++ b/.changes/markdown-format/ci-selection-gap-declarations-6457-0a96a9df.md @@ -0,0 +1,5 @@ +--- +bump: none +--- + +Test suites only: each declares the files it reads so the pull-request test selection runs it when they change. Nothing the plugin ships changes. diff --git a/.changes/multi-agent/ci-selection-gap-declarations-6457-93566999.md b/.changes/multi-agent/ci-selection-gap-declarations-6457-93566999.md new file mode 100644 index 0000000000..c2860db872 --- /dev/null +++ b/.changes/multi-agent/ci-selection-gap-declarations-6457-93566999.md @@ -0,0 +1,5 @@ +--- +bump: none +--- + +Test suites only: each declares the files it reads so the pull-request test selection runs it when they change. Nothing the plugin ships changes. diff --git a/.changes/pixel-art/ci-selection-gap-declarations-6457-d83fcc9f.md b/.changes/pixel-art/ci-selection-gap-declarations-6457-d83fcc9f.md new file mode 100644 index 0000000000..c2860db872 --- /dev/null +++ b/.changes/pixel-art/ci-selection-gap-declarations-6457-d83fcc9f.md @@ -0,0 +1,5 @@ +--- +bump: none +--- + +Test suites only: each declares the files it reads so the pull-request test selection runs it when they change. Nothing the plugin ships changes. diff --git a/.changes/planning/ci-selection-gap-declarations-6457-6543d37d.md b/.changes/planning/ci-selection-gap-declarations-6457-6543d37d.md new file mode 100644 index 0000000000..c2860db872 --- /dev/null +++ b/.changes/planning/ci-selection-gap-declarations-6457-6543d37d.md @@ -0,0 +1,5 @@ +--- +bump: none +--- + +Test suites only: each declares the files it reads so the pull-request test selection runs it when they change. Nothing the plugin ships changes. diff --git a/.changes/playbooks/ci-selection-gap-declarations-6457-1d127e17.md b/.changes/playbooks/ci-selection-gap-declarations-6457-1d127e17.md new file mode 100644 index 0000000000..c2860db872 --- /dev/null +++ b/.changes/playbooks/ci-selection-gap-declarations-6457-1d127e17.md @@ -0,0 +1,5 @@ +--- +bump: none +--- + +Test suites only: each declares the files it reads so the pull-request test selection runs it when they change. Nothing the plugin ships changes. diff --git a/.changes/powershell-format/ci-selection-gap-declarations-6457-81b1fe2b.md b/.changes/powershell-format/ci-selection-gap-declarations-6457-81b1fe2b.md new file mode 100644 index 0000000000..c2860db872 --- /dev/null +++ b/.changes/powershell-format/ci-selection-gap-declarations-6457-81b1fe2b.md @@ -0,0 +1,5 @@ +--- +bump: none +--- + +Test suites only: each declares the files it reads so the pull-request test selection runs it when they change. Nothing the plugin ships changes. diff --git a/.changes/repo-fleet-hygiene/ci-selection-gap-declarations-6457-01c08b7e.md b/.changes/repo-fleet-hygiene/ci-selection-gap-declarations-6457-01c08b7e.md new file mode 100644 index 0000000000..c2860db872 --- /dev/null +++ b/.changes/repo-fleet-hygiene/ci-selection-gap-declarations-6457-01c08b7e.md @@ -0,0 +1,5 @@ +--- +bump: none +--- + +Test suites only: each declares the files it reads so the pull-request test selection runs it when they change. Nothing the plugin ships changes. diff --git a/.changes/retro-audio/ci-selection-gap-declarations-6457-8832a509.md b/.changes/retro-audio/ci-selection-gap-declarations-6457-8832a509.md new file mode 100644 index 0000000000..c2860db872 --- /dev/null +++ b/.changes/retro-audio/ci-selection-gap-declarations-6457-8832a509.md @@ -0,0 +1,5 @@ +--- +bump: none +--- + +Test suites only: each declares the files it reads so the pull-request test selection runs it when they change. Nothing the plugin ships changes. diff --git a/.changes/review/ci-selection-gap-declarations-6457-f5679709.md b/.changes/review/ci-selection-gap-declarations-6457-f5679709.md new file mode 100644 index 0000000000..c2860db872 --- /dev/null +++ b/.changes/review/ci-selection-gap-declarations-6457-f5679709.md @@ -0,0 +1,5 @@ +--- +bump: none +--- + +Test suites only: each declares the files it reads so the pull-request test selection runs it when they change. Nothing the plugin ships changes. diff --git a/.changes/ruff-format/ci-selection-gap-declarations-6457-584cdfd1.md b/.changes/ruff-format/ci-selection-gap-declarations-6457-584cdfd1.md new file mode 100644 index 0000000000..c2860db872 --- /dev/null +++ b/.changes/ruff-format/ci-selection-gap-declarations-6457-584cdfd1.md @@ -0,0 +1,5 @@ +--- +bump: none +--- + +Test suites only: each declares the files it reads so the pull-request test selection runs it when they change. Nothing the plugin ships changes. diff --git a/.changes/session-flow/ci-selection-gap-declarations-6457-779c70a2.md b/.changes/session-flow/ci-selection-gap-declarations-6457-779c70a2.md new file mode 100644 index 0000000000..c2860db872 --- /dev/null +++ b/.changes/session-flow/ci-selection-gap-declarations-6457-779c70a2.md @@ -0,0 +1,5 @@ +--- +bump: none +--- + +Test suites only: each declares the files it reads so the pull-request test selection runs it when they change. Nothing the plugin ships changes. diff --git a/.changes/source-control/ci-selection-gap-declarations-6457-77ef8a3f.md b/.changes/source-control/ci-selection-gap-declarations-6457-77ef8a3f.md new file mode 100644 index 0000000000..c2860db872 --- /dev/null +++ b/.changes/source-control/ci-selection-gap-declarations-6457-77ef8a3f.md @@ -0,0 +1,5 @@ +--- +bump: none +--- + +Test suites only: each declares the files it reads so the pull-request test selection runs it when they change. Nothing the plugin ships changes. diff --git a/.changes/speech/ci-selection-gap-declarations-6457-0139c624.md b/.changes/speech/ci-selection-gap-declarations-6457-0139c624.md new file mode 100644 index 0000000000..c2860db872 --- /dev/null +++ b/.changes/speech/ci-selection-gap-declarations-6457-0139c624.md @@ -0,0 +1,5 @@ +--- +bump: none +--- + +Test suites only: each declares the files it reads so the pull-request test selection runs it when they change. Nothing the plugin ships changes. diff --git a/.changes/testing/ci-selection-gap-declarations-6457-0c46e032.md b/.changes/testing/ci-selection-gap-declarations-6457-0c46e032.md new file mode 100644 index 0000000000..c2860db872 --- /dev/null +++ b/.changes/testing/ci-selection-gap-declarations-6457-0c46e032.md @@ -0,0 +1,5 @@ +--- +bump: none +--- + +Test suites only: each declares the files it reads so the pull-request test selection runs it when they change. Nothing the plugin ships changes. diff --git a/.changes/typos-format/ci-selection-gap-declarations-6457-3b66afaa.md b/.changes/typos-format/ci-selection-gap-declarations-6457-3b66afaa.md new file mode 100644 index 0000000000..c2860db872 --- /dev/null +++ b/.changes/typos-format/ci-selection-gap-declarations-6457-3b66afaa.md @@ -0,0 +1,5 @@ +--- +bump: none +--- + +Test suites only: each declares the files it reads so the pull-request test selection runs it when they change. Nothing the plugin ships changes. diff --git a/.changes/user-interface/ci-selection-gap-declarations-6457-a6a4d9bb.md b/.changes/user-interface/ci-selection-gap-declarations-6457-a6a4d9bb.md new file mode 100644 index 0000000000..c2860db872 --- /dev/null +++ b/.changes/user-interface/ci-selection-gap-declarations-6457-a6a4d9bb.md @@ -0,0 +1,5 @@ +--- +bump: none +--- + +Test suites only: each declares the files it reads so the pull-request test selection runs it when they change. Nothing the plugin ships changes. diff --git a/.changes/visualization/ci-selection-gap-declarations-6457-f63abe2d.md b/.changes/visualization/ci-selection-gap-declarations-6457-f63abe2d.md new file mode 100644 index 0000000000..c2860db872 --- /dev/null +++ b/.changes/visualization/ci-selection-gap-declarations-6457-f63abe2d.md @@ -0,0 +1,5 @@ +--- +bump: none +--- + +Test suites only: each declares the files it reads so the pull-request test selection runs it when they change. Nothing the plugin ships changes. diff --git a/.changes/wizard/ci-selection-gap-declarations-6457-97fac2b4.md b/.changes/wizard/ci-selection-gap-declarations-6457-97fac2b4.md new file mode 100644 index 0000000000..c2860db872 --- /dev/null +++ b/.changes/wizard/ci-selection-gap-declarations-6457-97fac2b4.md @@ -0,0 +1,5 @@ +--- +bump: none +--- + +Test suites only: each declares the files it reads so the pull-request test selection runs it when they change. Nothing the plugin ships changes. diff --git a/.changes/work-items/ci-selection-gap-declarations-6457-f8839f57.md b/.changes/work-items/ci-selection-gap-declarations-6457-f8839f57.md new file mode 100644 index 0000000000..c2860db872 --- /dev/null +++ b/.changes/work-items/ci-selection-gap-declarations-6457-f8839f57.md @@ -0,0 +1,5 @@ +--- +bump: none +--- + +Test suites only: each declares the files it reads so the pull-request test selection runs it when they change. Nothing the plugin ships changes. diff --git a/.github/workflows/maintenance-audit-test-selection.yml b/.github/workflows/maintenance-audit-test-selection.yml index c840ab998b..e6b1cda5c9 100644 --- a/.github/workflows/maintenance-audit-test-selection.yml +++ b/.github/workflows/maintenance-audit-test-selection.yml @@ -10,7 +10,7 @@ name: maintenance-audit-test-selection # read whose one-file diff would not select that suite. # The seventh rotates with the day, so every suite is # traced within a week; the whole corpus under strace -# would not fit the 16-minute job. +# would not fit the 35-minute job. # audit-failed-suite-selection # when a pr-require-checks run on main fails: for each # failing suite, replays the selector on every commit @@ -46,7 +46,7 @@ jobs: audit-test-selection: if: github.event_name != 'workflow_run' runs-on: ubuntu-24.04 - timeout-minutes: 16 + timeout-minutes: 35 permissions: contents: read concurrency: @@ -57,6 +57,9 @@ jobs: uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false + # Full history, as test-bash fetches it: a suite that proves against an + # old commit fails in a shallow clone, and a failed suite reads less. + fetch-depth: 0 # The suites read what they read only when their tools are present, so # the toolchains match pr-require-checks.yml's test-bash and test-python lanes (Node, # Python, ShellCheck, shfmt, the inventory parser packages, DuckDB); a suite that skips for a @@ -93,6 +96,8 @@ jobs: - name: Install locked test toolchains run: | npm ci + # The yaml package scripts/check-read-caller-keys.test.sh parses with. + npm ci --prefix .github/standards/runner-policy python -m pip install --user --only-binary=:all: --require-hashes \ --requirement .github/requirements-ci.txt \ --requirement .github/requirements-ci-animation.txt @@ -110,8 +115,8 @@ jobs: chmod +x "$RUNNER_TEMP/duckdb/duckdb" - name: Test the selection audit run: bash scripts/selection-audit.test.sh - # Budget 600 s: suites start for the first 360 s, selector queries stop - # at 600 s, and anything cut off is counted in the report, not dropped. + # Budget 1500 s: suites start for the first 900 s, selector queries stop + # at 1500 s, and anything cut off is counted in the report, not dropped. # PATH is set here rather than through GITHUB_PATH, which zizmor rejects # in a workflow that also has a workflow_run trigger. - name: Trace suites and report selection gaps @@ -126,7 +131,7 @@ jobs: export PATH="$GITHUB_WORKSPACE/node_modules/.bin:$HOME/.local/bin:$RUNNER_TEMP/duckdb:$PATH" shard="${SHARD_INPUT:-$(($(date -u +%s) / 86400 % 7))/7}" read -ra suites <<<"${SUITES_INPUT:-}" - scripts/selection-audit.sh trace --shard "$shard" --jobs 4 --budget 600 \ + scripts/selection-audit.sh trace --shard "$shard" --jobs 4 --budget 1500 \ --out "$RUNNER_TEMP/selection-audit" ${suites[@]+"${suites[@]}"} - name: Upload the trace report if: ${{ !cancelled() }} diff --git a/.github/workflows/pr-require-checks.yml b/.github/workflows/pr-require-checks.yml index ea76363c50..ed734ad6e8 100644 --- a/.github/workflows/pr-require-checks.yml +++ b/.github/workflows/pr-require-checks.yml @@ -760,6 +760,15 @@ jobs: { echo "::error::a Node suite runs through neither a wrapper nor a Node package"; rc=1; } exit "$rc" + # The test selector's cases against the live tree: the derived shared-lib + # copy sets and real fan-outs. Each runs the selector over the whole tree, + # so no test selection can say when it must run, and it runs here on every + # pull request rather than as a case of scripts/affected-tests.test.sh. + - name: Check the test selector against the live tree + id: selector_live + continue-on-error: true + run: bash scripts/affected-tests-live.sh + # A DIFF LINTS THE MARKDOWN IT CHANGED. markdownlint's rules read one file # at a time, so an unchanged file's verdict is the one its last green run # gave. The whole tree when there is no diff base (a schedule or dispatch @@ -1088,6 +1097,7 @@ jobs: docs-only-contract=${{ steps.docs_only_contract.outcome }} lane-coverage=${{ steps.lane_coverage.outcome }} test-plan-corpus=${{ steps.test_plan_corpus.outcome }} + selector-live=${{ steps.selector_live.outcome }} markdown=${{ steps.markdown.outcome }} typos=${{ steps.typos.outcome }} gitleaks=${{ steps.gitleaks.outcome }} diff --git a/lib/docs-fetcher-gate.test.sh b/lib/docs-fetcher-gate.test.sh index 747cdf8f13..3b3bac8b8c 100755 --- a/lib/docs-fetcher-gate.test.sh +++ b/lib/docs-fetcher-gate.test.sh @@ -1,4 +1,5 @@ #!/usr/bin/env bash +# test-scope: plugins/discovery/agents/docs-fetcher.md plugins/discovery/hooks/hooks.json plugins/multi-agent/agents/docs-fetcher.md plugins/multi-agent/hooks/hooks.json # Owns docs-fetcher-gate.test.mjs; run-plugin-tests.sh discovers only .test.sh files. set -euo pipefail exec node --test "$(dirname "${BASH_SOURCE[0]}")/docs-fetcher-gate.test.mjs" diff --git a/lib/prerequisites.test.sh b/lib/prerequisites.test.sh index 3759153c51..2ceb525172 100755 --- a/lib/prerequisites.test.sh +++ b/lib/prerequisites.test.sh @@ -3,6 +3,7 @@ # pwsh stubs: with node absent each prints one fixed line and exits 1; with node # present each hands its arguments and exit code to prerequisites.mjs. # scripts/run-outside-node-suites.sh treats a sibling .test.sh as the runner. +# test-scope: plugins/*/prerequisites.json plugins/*/.claude-plugin/plugin.json set -uo pipefail LIB="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" diff --git a/lib/session-bridge/test_session_bridge.py b/lib/session-bridge/test_session_bridge.py index 1a321feb1e..22c84ea4e3 100644 --- a/lib/session-bridge/test_session_bridge.py +++ b/lib/session-bridge/test_session_bridge.py @@ -1,3 +1,4 @@ +# test-scope: lib/session-bridge/watch.sh lib/session-bridge/wake.sh """Tests for session-bridge: the Transport port, the loopback adapter's server and client halves, and watch.sh and wake.sh against a toy app built on the bridge. diff --git a/plugins/actionlint/hooks/actionlint-check.test.sh b/plugins/actionlint/hooks/actionlint-check.test.sh index 9ff8f7451b..e4ad203629 100755 --- a/plugins/actionlint/hooks/actionlint-check.test.sh +++ b/plugins/actionlint/hooks/actionlint-check.test.sh @@ -12,7 +12,7 @@ # the caller's working directory would surface (the tool is file-anchored, so a # correct hook needs no cd). actionlint is required to drive the violation # assertions; without it the suite skips (the hook itself no-ops silently). -# test-scope: plugins/actionlint/hooks/* plugins/actionlint/.claude-plugin/plugin.json +# test-scope: plugins/actionlint/hooks/* plugins/actionlint/.claude-plugin/plugin.json plugins/actionlint/lib/prerequisites.mjs plugins/actionlint/prerequisites.json set -uo pipefail diff --git a/plugins/animation/scripts/animation.test.sh b/plugins/animation/scripts/animation.test.sh index 5bf66ee36d..53ab81d10e 100755 --- a/plugins/animation/scripts/animation.test.sh +++ b/plugins/animation/scripts/animation.test.sh @@ -5,7 +5,8 @@ # ../requirements.txt hash-locks them) and skip without them, so ANIMATION_REQUIRE_DEPS=1 fails the run # instead: a lane that provisions the pinned requirements sets it, and missing dependencies then read as a # broken environment, not as passing coverage. -# test-scope: plugins/animation/skills/*/SKILL.md plugins/animation/skills/*/scripts/* plugins/animation/hooks/*.sh +# test-scope: plugins/animation/skills/*/SKILL.md plugins/animation/skills/*/scripts/* plugins/animation/hooks/*.sh plugins/animation/scripts/* +# test-scope: plugins/animation/requirements.in plugins/animation/styles/* set -uo pipefail cd "$(dirname "${BASH_SOURCE[0]}")" || exit 1 diff --git a/plugins/animation/scripts/test_inkstats.py b/plugins/animation/scripts/test_inkstats.py index a307558eb8..9aa3c59a2f 100644 --- a/plugins/animation/scripts/test_inkstats.py +++ b/plugins/animation/scripts/test_inkstats.py @@ -1,3 +1,4 @@ +# test-scope: plugins/animation/requirements.in """inkstats hold timing: a rotoscope work dir's last drawing uses d/index.json t1, not one frame. Standard library only at module level so collection works without numpy; the tests skip, naming what is diff --git a/plugins/animation/scripts/test_pydeps.py b/plugins/animation/scripts/test_pydeps.py index 79f322509c..6cfc23a27a 100644 --- a/plugins/animation/scripts/test_pydeps.py +++ b/plugins/animation/scripts/test_pydeps.py @@ -1,3 +1,4 @@ +# test-scope: plugins/animation/skills/*/SKILL.md plugins/animation/skills/*/scripts/* plugins/animation/scripts/* plugins/animation/hooks/*.sh """pydeps.py: the hash-locked on-demand install (first install, no-op rerun, a failed install) and the run launcher. The lock is a local wheel that provides the numpy and cv2 modules, installed with pip's own PIP_NO_INDEX and diff --git a/plugins/animation/scripts/test_woodcut_marks.py b/plugins/animation/scripts/test_woodcut_marks.py index be64c5ad1e..bdfedf00fb 100644 --- a/plugins/animation/scripts/test_woodcut_marks.py +++ b/plugins/animation/scripts/test_woodcut_marks.py @@ -1,3 +1,4 @@ +# test-scope: plugins/animation/requirements.in """The woodcut residuals that are authoring, not a retuned check.""" import json import sys diff --git a/plugins/animation/skills/rotoscope/scripts/test_regress.py b/plugins/animation/skills/rotoscope/scripts/test_regress.py index 149f10a5b9..1a2eb60387 100644 --- a/plugins/animation/skills/rotoscope/scripts/test_regress.py +++ b/plugins/animation/skills/rotoscope/scripts/test_regress.py @@ -1,3 +1,5 @@ +# test-scope: plugins/animation/requirements.in +# test-scope: plugins/animation/.claude-plugin/plugin.json plugins/animation/skills/rotoscope/scripts/roto.js """regress.py --synthetic as a test: the render, encode and decode contracts and the regression cases. Standard library only at module level, so collection works without numpy; the test skips, naming what is missing, diff --git a/plugins/architecture/lib/family-records.test.sh b/plugins/architecture/lib/family-records.test.sh index 484a9143c4..61ef5ebddb 100755 --- a/plugins/architecture/lib/family-records.test.sh +++ b/plugins/architecture/lib/family-records.test.sh @@ -1,6 +1,7 @@ #!/usr/bin/env bash # Tests for is_family_record: every name the map-* renderers and skills write # is recognized, and configuration names are not. +# test-scope: plugins/architecture/skills/*/scripts/render-*.sh plugins/architecture/skills/*/SKILL.md set -uo pipefail unset GIT_DIR GIT_WORK_TREE GIT_CONFIG diff --git a/plugins/architecture/skills/map-landscape/scripts/render-landscape.test.sh b/plugins/architecture/skills/map-landscape/scripts/render-landscape.test.sh index 8b91adc91b..229b9a7e62 100755 --- a/plugins/architecture/skills/map-landscape/scripts/render-landscape.test.sh +++ b/plugins/architecture/skills/map-landscape/scripts/render-landscape.test.sh @@ -5,6 +5,7 @@ # # The fixture record is written here rather than collected, so the renderer is # tested against a fixed input and every assertion is about rendering alone. +# test-scope: .markdownlint-cli2.jsonc set -uo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" diff --git a/plugins/autonomy/hooks/lane-stop-gate.test.sh b/plugins/autonomy/hooks/lane-stop-gate.test.sh index 80726b703a..ab81c6eaa6 100755 --- a/plugins/autonomy/hooks/lane-stop-gate.test.sh +++ b/plugins/autonomy/hooks/lane-stop-gate.test.sh @@ -23,6 +23,7 @@ # so this defines its own assertions and builds throwaway inputs with jq. The OS # toast is never exercised here (notification channels are disabled on the paths # that would fire one) — lane-notify.test.sh covers the notifier. +# test-scope: plugins/autonomy/hooks/*.sh set -uo pipefail diff --git a/plugins/bash-format/hooks/bash-format.test.sh b/plugins/bash-format/hooks/bash-format.test.sh index d87384a415..3392765668 100755 --- a/plugins/bash-format/hooks/bash-format.test.sh +++ b/plugins/bash-format/hooks/bash-format.test.sh @@ -13,6 +13,7 @@ # a correct hook needs no cd). shellcheck is required; without it the lint # branch -- which drives the findings assertions -- cannot fire, so the suite # skips. shfmt-gated cases skip when shfmt is absent. +# test-scope: plugins/bash-format/.claude-plugin/plugin.json plugins/bash-format/hooks/hooks.json plugins/bash-format/prerequisites.json set -uo pipefail diff --git a/plugins/biome-format/hooks/biome-format.test.sh b/plugins/biome-format/hooks/biome-format.test.sh index d5a80b2186..559f296ef5 100755 --- a/plugins/biome-format/hooks/biome-format.test.sh +++ b/plugins/biome-format/hooks/biome-format.test.sh @@ -16,6 +16,7 @@ # # Requires a real Biome binary: $BIOME_TEST_BIN if set, else `biome` on PATH. # Without one the behavioral assertions cannot run, so the suite skips. +# test-scope: plugins/biome-format/.claude-plugin/plugin.json plugins/biome-format/hooks/hooks.json plugins/biome-format/prerequisites.json set -uo pipefail diff --git a/plugins/code-metrics/skills/audit-size/scripts/audit-size.test.sh b/plugins/code-metrics/skills/audit-size/scripts/audit-size.test.sh index d061f8af16..6f20ed3b8d 100755 --- a/plugins/code-metrics/skills/audit-size/scripts/audit-size.test.sh +++ b/plugins/code-metrics/skills/audit-size/scripts/audit-size.test.sh @@ -1,7 +1,7 @@ #!/usr/bin/env bash # Regression tests for the audit-size entry point (audit-size.sh): option parsing, # JSON versus markdown output, and exit-code passthrough from dispatch.sh. -# test-scope: plugins/code-metrics/scripts/collectors/*.py plugins/code-metrics/scripts/fixtures/* +# test-scope: plugins/code-metrics/scripts/collectors/*.py plugins/code-metrics/scripts/fixtures/* plugins/code-metrics/skills/audit-size/SKILL.md set -uo pipefail unset GIT_DIR GIT_WORK_TREE GIT_CONFIG diff --git a/plugins/code-metrics/skills/setup/scripts/setup-check.test.sh b/plugins/code-metrics/skills/setup/scripts/setup-check.test.sh index b3318355a2..fbbcb12585 100755 --- a/plugins/code-metrics/skills/setup/scripts/setup-check.test.sh +++ b/plugins/code-metrics/skills/setup/scripts/setup-check.test.sh @@ -1,7 +1,7 @@ #!/usr/bin/env bash # Regression tests for setup-check.sh: the layer rows, the tracked-file guard, # the resolved references, and one row per collector adapter. -# test-scope: plugins/code-metrics/scripts/collectors/*.py +# test-scope: plugins/code-metrics/scripts/* set -uo pipefail unset GIT_DIR GIT_WORK_TREE GIT_CONFIG diff --git a/plugins/code-tidying/skills/audit-comment-residue/scripts/detect.test.sh b/plugins/code-tidying/skills/audit-comment-residue/scripts/detect.test.sh index d6298535f3..54c8b97d1b 100755 --- a/plugins/code-tidying/skills/audit-comment-residue/scripts/detect.test.sh +++ b/plugins/code-tidying/skills/audit-comment-residue/scripts/detect.test.sh @@ -1,6 +1,7 @@ #!/usr/bin/env bash # Self-contained tests for detect.sh (no external test lib — ships with the # plugin; fixtures are built inline in a tmpdir). +# test-scope: plugins/code-tidying/skills/audit-comment-residue/SKILL.md set -uo pipefail # Fixture git isolation: an inherited GIT_DIR/GIT_WORK_TREE/GIT_CONFIG would diff --git a/plugins/context-budget/hooks/settings-write-ask.test.sh b/plugins/context-budget/hooks/settings-write-ask.test.sh index 6a62ffcb75..22eed38b36 100755 --- a/plugins/context-budget/hooks/settings-write-ask.test.sh +++ b/plugins/context-budget/hooks/settings-write-ask.test.sh @@ -14,6 +14,7 @@ # # Self-contained: defines its own assertion helpers — installed plugins are # cache-isolated with no shared test lib. +# test-scope: plugins/context-budget/hooks/hooks.json set -uo pipefail diff --git a/plugins/discovery/hooks/webfetch-truncation.test.sh b/plugins/discovery/hooks/webfetch-truncation.test.sh index 92b8c911a5..bff59de8f2 100755 --- a/plugins/discovery/hooks/webfetch-truncation.test.sh +++ b/plugins/discovery/hooks/webfetch-truncation.test.sh @@ -1,4 +1,5 @@ #!/usr/bin/env bash +# test-scope: plugins/discovery/hooks/fixtures/* plugins/discovery/hooks/hooks.json # Discovery wrapper: scripts/run-plugin-tests.sh finds plugins/**/*.test.sh, so # this hands off to the Node suite. SKIPs (exit 0) when Node is unavailable. set -uo pipefail diff --git a/plugins/discovery/skills/check/scripts/check.test.sh b/plugins/discovery/skills/check/scripts/check.test.sh index e9aab7032c..f7d1dc2a01 100755 --- a/plugins/discovery/skills/check/scripts/check.test.sh +++ b/plugins/discovery/skills/check/scripts/check.test.sh @@ -1,4 +1,5 @@ #!/usr/bin/env bash +# test-scope: plugins/discovery/hooks/hooks.json # Tests for check.sh: a healthy plugin passes every row; a PATH without node # fails the node and hook rows but still reports registration; a hooks.json # without the hook fails registration. diff --git a/plugins/discovery/tests/research-sweep.test.sh b/plugins/discovery/tests/research-sweep.test.sh index f2eed61d2c..c3541b6b62 100755 --- a/plugins/discovery/tests/research-sweep.test.sh +++ b/plugins/discovery/tests/research-sweep.test.sh @@ -1,4 +1,5 @@ #!/usr/bin/env bash +# test-scope: plugins/discovery/agents/*.md # Discovery wrapper: scripts/run-plugin-tests.sh finds plugins/**/*.test.sh, so # this hands off to the Node suite. SKIPs (exit 0) when Node is unavailable. set -uo pipefail diff --git a/plugins/disk-hygiene/hooks/run-python-hook.test.sh b/plugins/disk-hygiene/hooks/run-python-hook.test.sh index 88a623a6c4..7b63e49f12 100755 --- a/plugins/disk-hygiene/hooks/run-python-hook.test.sh +++ b/plugins/disk-hygiene/hooks/run-python-hook.test.sh @@ -1,5 +1,6 @@ #!/usr/bin/env bash # Contract tests for the disk-hygiene Python hook launcher (#1504). +# test-scope: plugins/disk-hygiene/hooks/hooks.json set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" diff --git a/plugins/disk-hygiene/skills/clean/scripts/engine_context.test.sh b/plugins/disk-hygiene/skills/clean/scripts/engine_context.test.sh index 7525531b80..140e846f33 100755 --- a/plugins/disk-hygiene/skills/clean/scripts/engine_context.test.sh +++ b/plugins/disk-hygiene/skills/clean/scripts/engine_context.test.sh @@ -1,5 +1,6 @@ #!/usr/bin/env bash # Cross-platform contract wrapper for the engine-context test suite. +# test-scope: plugins/disk-hygiene/hooks/hooks.json set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" diff --git a/plugins/disk-hygiene/skills/clean/scripts/owner_registry.test.sh b/plugins/disk-hygiene/skills/clean/scripts/owner_registry.test.sh index fa48f6419f..31f43ed36e 100755 --- a/plugins/disk-hygiene/skills/clean/scripts/owner_registry.test.sh +++ b/plugins/disk-hygiene/skills/clean/scripts/owner_registry.test.sh @@ -1,6 +1,6 @@ #!/usr/bin/env bash # Cross-platform contract wrapper for the owner-registry test suite. -# test-scope: plugins/disk-hygiene/*.py plugins/disk-hygiene/*.sh plugins/disk-hygiene/*.mjs plugins/disk-hygiene/*.json +# test-scope: plugins/disk-hygiene/*.py plugins/disk-hygiene/*.sh plugins/disk-hygiene/*.mjs plugins/disk-hygiene/*.json plugins/disk-hygiene/*.ps1 set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" diff --git a/plugins/disk-hygiene/skills/clean/scripts/test_engine_context.py b/plugins/disk-hygiene/skills/clean/scripts/test_engine_context.py index 0e6a67a53d..87c1274f22 100755 --- a/plugins/disk-hygiene/skills/clean/scripts/test_engine_context.py +++ b/plugins/disk-hygiene/skills/clean/scripts/test_engine_context.py @@ -1,4 +1,5 @@ #!/usr/bin/env python3 +# test-scope: plugins/disk-hygiene/hooks/hooks.json """Tests for the ``UserPromptExpansion`` context hook of ``/disk-hygiene:clean`` (#4215). The contract under test: the values the hook hands the skill are values the diff --git a/plugins/disk-hygiene/skills/clean/scripts/test_hygiene.py b/plugins/disk-hygiene/skills/clean/scripts/test_hygiene.py index c3cbc5675b..6859508c3f 100755 --- a/plugins/disk-hygiene/skills/clean/scripts/test_hygiene.py +++ b/plugins/disk-hygiene/skills/clean/scripts/test_hygiene.py @@ -1,5 +1,5 @@ #!/usr/bin/env python3 -# test-scope: plugins/disk-hygiene/skills/clean/SKILL.md +# test-scope: plugins/disk-hygiene/skills/clean/SKILL.md plugins/disk-hygiene/hooks/hooks.json """Behavioral tests for the disk-hygiene safety engine and scoped guard.""" from __future__ import annotations diff --git a/plugins/disk-hygiene/skills/clean/scripts/test_owner_registry.py b/plugins/disk-hygiene/skills/clean/scripts/test_owner_registry.py index 068d179399..a8c1d962ca 100755 --- a/plugins/disk-hygiene/skills/clean/scripts/test_owner_registry.py +++ b/plugins/disk-hygiene/skills/clean/scripts/test_owner_registry.py @@ -1,4 +1,5 @@ #!/usr/bin/env python3 +# test-scope: plugins/disk-hygiene/*.py plugins/disk-hygiene/*.sh plugins/disk-hygiene/*.mjs plugins/disk-hygiene/*.ps1 plugins/disk-hygiene/hooks/hooks.json """Tests for the bundled managed-state owner registry.""" from __future__ import annotations diff --git a/plugins/disk-hygiene/skills/setup/scripts/kill_switch_probe.test.sh b/plugins/disk-hygiene/skills/setup/scripts/kill_switch_probe.test.sh index a62aacd461..b25af7edda 100755 --- a/plugins/disk-hygiene/skills/setup/scripts/kill_switch_probe.test.sh +++ b/plugins/disk-hygiene/skills/setup/scripts/kill_switch_probe.test.sh @@ -1,6 +1,6 @@ #!/usr/bin/env bash # Cross-platform contract wrapper for the kill-switch probe test suite. -# test-scope: plugins/disk-hygiene/*.py plugins/disk-hygiene/*.sh plugins/disk-hygiene/*.mjs plugins/disk-hygiene/*.json +# test-scope: plugins/disk-hygiene/* set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" diff --git a/plugins/disk-hygiene/skills/setup/scripts/test_kill_switch_probe.py b/plugins/disk-hygiene/skills/setup/scripts/test_kill_switch_probe.py index 78b26728c5..7f08c0dca1 100755 --- a/plugins/disk-hygiene/skills/setup/scripts/test_kill_switch_probe.py +++ b/plugins/disk-hygiene/skills/setup/scripts/test_kill_switch_probe.py @@ -1,4 +1,5 @@ #!/usr/bin/env python3 +# test-scope: plugins/disk-hygiene/* """Behavioral tests for the deterministic kill-switch probe. The probe exists so ``/disk-hygiene:setup check`` (and the clean skill when its diff --git a/plugins/docs-hygiene/skills/audit-noise/scripts/detect.test.sh b/plugins/docs-hygiene/skills/audit-noise/scripts/detect.test.sh index 91a3ec3b98..afd180aaa8 100755 --- a/plugins/docs-hygiene/skills/audit-noise/scripts/detect.test.sh +++ b/plugins/docs-hygiene/skills/audit-noise/scripts/detect.test.sh @@ -1,6 +1,7 @@ #!/usr/bin/env bash # Self-contained tests for detect.sh (no external test lib — ships with the # plugin; fixtures are built inline in a tmpdir). +# test-scope: plugins/docs-hygiene/skills/audit-noise/SKILL.md set -uo pipefail # Fixture git isolation: an inherited GIT_DIR/GIT_WORK_TREE/GIT_CONFIG would diff --git a/plugins/docs-naming/skills/audit-file-names/scripts/emit-findings.test.sh b/plugins/docs-naming/skills/audit-file-names/scripts/emit-findings.test.sh index ead168d234..198ad918b9 100755 --- a/plugins/docs-naming/skills/audit-file-names/scripts/emit-findings.test.sh +++ b/plugins/docs-naming/skills/audit-file-names/scripts/emit-findings.test.sh @@ -4,6 +4,7 @@ # # Fixture git isolation: an inherited GIT_DIR/GIT_WORK_TREE/GIT_CONFIG would # redirect `git init` / `git config` into the caller's repository. +# test-scope: plugins/docs-naming/skills/audit-file-names/scripts/fixtures/tree/* set -uo pipefail unset GIT_DIR GIT_WORK_TREE GIT_CONFIG diff --git a/plugins/docs-naming/skills/audit-file-names/scripts/inventory.test.sh b/plugins/docs-naming/skills/audit-file-names/scripts/inventory.test.sh index a0c95de73e..a87a10161d 100755 --- a/plugins/docs-naming/skills/audit-file-names/scripts/inventory.test.sh +++ b/plugins/docs-naming/skills/audit-file-names/scripts/inventory.test.sh @@ -4,6 +4,7 @@ # # Fixture git isolation: an inherited GIT_DIR/GIT_WORK_TREE/GIT_CONFIG would # redirect `git init` / `git config` into the caller's repository. +# test-scope: plugins/docs-naming/skills/audit-file-names/scripts/fixtures/tree/* set -uo pipefail unset GIT_DIR GIT_WORK_TREE GIT_CONFIG diff --git a/plugins/docs-naming/skills/audit-file-names/scripts/sweep.test.sh b/plugins/docs-naming/skills/audit-file-names/scripts/sweep.test.sh index 5e6a052db6..6011080465 100755 --- a/plugins/docs-naming/skills/audit-file-names/scripts/sweep.test.sh +++ b/plugins/docs-naming/skills/audit-file-names/scripts/sweep.test.sh @@ -4,6 +4,7 @@ # # Fixture git isolation: an inherited GIT_DIR/GIT_WORK_TREE/GIT_CONFIG would # redirect `git init` / `git config` into the caller's repository. +# test-scope: plugins/docs-naming/skills/audit-file-names/scripts/fixtures/tree/* set -uo pipefail unset GIT_DIR GIT_WORK_TREE GIT_CONFIG diff --git a/plugins/docs-naming/skills/generate-file-name-gate/scripts/emit-gate.test.sh b/plugins/docs-naming/skills/generate-file-name-gate/scripts/emit-gate.test.sh index 7f27482eb0..e865930c35 100755 --- a/plugins/docs-naming/skills/generate-file-name-gate/scripts/emit-gate.test.sh +++ b/plugins/docs-naming/skills/generate-file-name-gate/scripts/emit-gate.test.sh @@ -17,6 +17,7 @@ # # Fixture git isolation: an inherited GIT_DIR/GIT_WORK_TREE/GIT_CONFIG would # redirect `git init` / `git config` into the caller's repository. +# test-scope: plugins/docs-naming/skills/audit-file-names/scripts/fixtures/tree/* set -uo pipefail unset GIT_DIR GIT_WORK_TREE GIT_CONFIG diff --git a/plugins/docs-naming/skills/realign-file-names/scripts/apply-rename.test.sh b/plugins/docs-naming/skills/realign-file-names/scripts/apply-rename.test.sh index 75e5715205..47fc267fb8 100755 --- a/plugins/docs-naming/skills/realign-file-names/scripts/apply-rename.test.sh +++ b/plugins/docs-naming/skills/realign-file-names/scripts/apply-rename.test.sh @@ -14,6 +14,7 @@ # list at the bottom is the only place a group is scheduled, and the suite # refuses to start when a defined group is missing from it, so the two halves # always add up to the whole suite. +# test-scope: plugins/docs-naming/skills/audit-file-names/scripts/fixtures/tree/* plugins/docs-naming/skills/audit-file-names/scripts/inventory.sh plugins/docs-naming/skills/audit-file-names/scripts/emit-findings.sh set -uo pipefail unset GIT_DIR GIT_WORK_TREE GIT_CONFIG diff --git a/plugins/education/skills/illustrate/scripts/build-explainer.test.sh b/plugins/education/skills/illustrate/scripts/build-explainer.test.sh index 34bc777fab..fa67809b69 100755 --- a/plugins/education/skills/illustrate/scripts/build-explainer.test.sh +++ b/plugins/education/skills/illustrate/scripts/build-explainer.test.sh @@ -6,6 +6,7 @@ # bash plugins/education/skills/illustrate/scripts/build-explainer.test.sh # # Exit 0 clean, 1 findings, 2 environment (node missing). +# test-scope: plugins/education/skills/illustrate/SKILL.md set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" || exit 2 diff --git a/plugins/education/skills/quiz-me/scripts/build-report.test.sh b/plugins/education/skills/quiz-me/scripts/build-report.test.sh index 861dbe24ae..ca672ff14c 100755 --- a/plugins/education/skills/quiz-me/scripts/build-report.test.sh +++ b/plugins/education/skills/quiz-me/scripts/build-report.test.sh @@ -5,6 +5,7 @@ # bash plugins/education/skills/quiz-me/scripts/build-report.test.sh # # Exit 0 clean, 1 findings, 2 environment (node missing). +# test-scope: plugins/education/skills/quiz-me/SKILL.md set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" || exit 2 diff --git a/plugins/eol-normalizer/hooks/eol-normalizer.test.sh b/plugins/eol-normalizer/hooks/eol-normalizer.test.sh index 0dd2a66c54..51d0df1360 100755 --- a/plugins/eol-normalizer/hooks/eol-normalizer.test.sh +++ b/plugins/eol-normalizer/hooks/eol-normalizer.test.sh @@ -13,6 +13,7 @@ # any reliance on the caller's working directory would surface (the tools are # file-anchored, so a correct hook needs no cd). git is required; without it the # hook is a no-op and these assertions cannot fire, so the suite skips. +# test-scope: plugins/eol-normalizer/hooks/hooks.json set -uo pipefail diff --git a/plugins/evals/skills/plugin-eval/scripts/test_calibrate_judge.py b/plugins/evals/skills/plugin-eval/scripts/test_calibrate_judge.py index 1d6ec4277c..f146614c17 100755 --- a/plugins/evals/skills/plugin-eval/scripts/test_calibrate_judge.py +++ b/plugins/evals/skills/plugin-eval/scripts/test_calibrate_judge.py @@ -1,4 +1,6 @@ #!/usr/bin/env python3 +# test-scope: plugins/evals/evals/* +# test-scope: plugins/evals/skills/plugin-eval/scripts/fixtures/calibrate-judge/* """Fixture suite for calibrate-judge.py. fixtures/calibrate-judge/ holds: diff --git a/plugins/evals/skills/plugin-eval/scripts/test_run_validity.py b/plugins/evals/skills/plugin-eval/scripts/test_run_validity.py index 6329aa137b..e7919f3807 100755 --- a/plugins/evals/skills/plugin-eval/scripts/test_run_validity.py +++ b/plugins/evals/skills/plugin-eval/scripts/test_run_validity.py @@ -1,4 +1,5 @@ #!/usr/bin/env python3 +# test-scope: plugins/evals/skills/plugin-eval/scripts/fixtures/run-validity/traces/* """Fixture suite for run-validity.py. fixtures/run-validity/ holds two result files trimmed from one real diff --git a/plugins/evals/skills/validate/scripts/test_validate_cases.py b/plugins/evals/skills/validate/scripts/test_validate_cases.py index 70fcce0953..6c58dfef9a 100755 --- a/plugins/evals/skills/validate/scripts/test_validate_cases.py +++ b/plugins/evals/skills/validate/scripts/test_validate_cases.py @@ -1,4 +1,5 @@ #!/usr/bin/env python3 +# test-scope: plugins/evals/evals/* """Fixture suite for validate-cases.py. Every fixture is built in a temporary directory from the inline strings here, so diff --git a/plugins/event-storming/skills/simulation/scripts/build-bc-view.test.sh b/plugins/event-storming/skills/simulation/scripts/build-bc-view.test.sh index a698d2793b..36ac93af31 100755 --- a/plugins/event-storming/skills/simulation/scripts/build-bc-view.test.sh +++ b/plugins/event-storming/skills/simulation/scripts/build-bc-view.test.sh @@ -6,6 +6,7 @@ # bash plugins/event-storming/skills/simulation/scripts/build-bc-view.test.sh # # Exit 0 clean, 1 findings, 2 environment (node missing). +# test-scope: plugins/event-storming/skills/simulation/SKILL.md set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" || exit 2 diff --git a/plugins/explainer-video/scripts/explainer-video.test.sh b/plugins/explainer-video/scripts/explainer-video.test.sh index abe36eadc7..26cca5d420 100755 --- a/plugins/explainer-video/scripts/explainer-video.test.sh +++ b/plugins/explainer-video/scripts/explainer-video.test.sh @@ -3,6 +3,7 @@ # standard library and always run; the mux test needs ffmpeg and ffprobe. The real renders need ManimCE, ffmpeg and ffprobe and skip without them, so # EXPLAINER_VIDEO_REQUIRE_DEPS=1 fails the run instead: run it through the launcher, which puts the installed set # on the path, e.g. `python3 pydeps.py run -- -m unittest test_explainer_video_render` from this directory. +# test-scope: plugins/explainer-video/skills/*/SKILL.md plugins/explainer-video/skills/*.py plugins/explainer-video/scripts/* plugins/explainer-video/hooks/*.sh plugins/explainer-video/requirements.txt set -uo pipefail cd "$(dirname "${BASH_SOURCE[0]}")" || exit 1 diff --git a/plugins/explainer-video/scripts/test_explainer_video_pydeps.py b/plugins/explainer-video/scripts/test_explainer_video_pydeps.py index 2faf4465a9..10f9fae172 100644 --- a/plugins/explainer-video/scripts/test_explainer_video_pydeps.py +++ b/plugins/explainer-video/scripts/test_explainer_video_pydeps.py @@ -1,3 +1,4 @@ +# test-scope: plugins/explainer-video/skills/*/SKILL.md plugins/explainer-video/skills/*.py plugins/explainer-video/scripts/* plugins/explainer-video/hooks/*.sh plugins/explainer-video/requirements.txt """pydeps.py: the hash-locked on-demand install (first install, no-op rerun, a failed install), the run launcher, and the hand-over to a supported interpreter. diff --git a/plugins/go-format/hooks/go-format.test.sh b/plugins/go-format/hooks/go-format.test.sh index fe6f8856fc..1dab995066 100755 --- a/plugins/go-format/hooks/go-format.test.sh +++ b/plugins/go-format/hooks/go-format.test.sh @@ -15,6 +15,7 @@ # Requires a real goimports binary: $GOIMPORTS_TEST_BIN if set, else # `goimports` on PATH. Without one the behavioral assertions cannot run, so # the suite skips. +# test-scope: plugins/go-format/.claude-plugin/plugin.json plugins/go-format/hooks/hooks.json plugins/go-format/prerequisites.json set -uo pipefail diff --git a/plugins/guardrails/hooks/abort-boundary.test.sh b/plugins/guardrails/hooks/abort-boundary.test.sh index 755a20aa8b..4626e029b4 100755 --- a/plugins/guardrails/hooks/abort-boundary.test.sh +++ b/plugins/guardrails/hooks/abort-boundary.test.sh @@ -6,7 +6,7 @@ # code, stderr, and the stdout document. The registered set is read from # hooks.json, never enumerated here, so a hook added without the boundary # fails this suite. Self-contained; no host-repo assertion library. -# test-scope: plugins/guardrails/hooks/* plugins/guardrails/lib/* +# test-scope: plugins/guardrails/* set -uo pipefail diff --git a/plugins/guardrails/hooks/block-hook-bypass.test.sh b/plugins/guardrails/hooks/block-hook-bypass.test.sh index 33c8c065ee..c95fb35195 100755 --- a/plugins/guardrails/hooks/block-hook-bypass.test.sh +++ b/plugins/guardrails/hooks/block-hook-bypass.test.sh @@ -4,6 +4,7 @@ # Black-box: invokes the hook as a subprocess, pipes PreToolUse Bash JSON on # stdin, asserts on exit code (2 = blocked, 0 = allowed). Self-contained — no # host-repo assertion library. +# test-scope: plugins/guardrails/README.md set -uo pipefail diff --git a/plugins/guardrails/hooks/block-windows-drive-tmp.test.sh b/plugins/guardrails/hooks/block-windows-drive-tmp.test.sh index bde90815a0..b6e3c1c646 100755 --- a/plugins/guardrails/hooks/block-windows-drive-tmp.test.sh +++ b/plugins/guardrails/hooks/block-windows-drive-tmp.test.sh @@ -5,6 +5,7 @@ # JSON on stdin, asserts on exit code (2 = blocked, 0 = allowed). The Windows # lane is forced via OSTYPE=msys so Linux CI exercises the same matcher the # Git Bash host would. Self-contained — no host-repo assertion library. +# test-scope: plugins/guardrails/hooks/hooks.json set -uo pipefail diff --git a/plugins/guardrails/hooks/check-bash-file-changes.test.sh b/plugins/guardrails/hooks/check-bash-file-changes.test.sh index 4f5a250da4..00f2073bc0 100755 --- a/plugins/guardrails/hooks/check-bash-file-changes.test.sh +++ b/plugins/guardrails/hooks/check-bash-file-changes.test.sh @@ -1,4 +1,5 @@ #!/usr/bin/env bash +# test-scope: plugins/guardrails/hooks/hooks.json # Contract test for check-bash-file-changes.mjs (guardrails plugin). # # Black-box: fires the snapshot mode with a PreToolUse Bash payload, runs a real diff --git a/plugins/guardrails/hooks/coverage-manifest.test.sh b/plugins/guardrails/hooks/coverage-manifest.test.sh index c657542883..7c84d0d2f1 100755 --- a/plugins/guardrails/hooks/coverage-manifest.test.sh +++ b/plugins/guardrails/hooks/coverage-manifest.test.sh @@ -6,7 +6,7 @@ # is registered, every event is one hooks.json declares, every family and # pattern is one the harness-config audit baseline actually lists, and every # lever is a documented option. -# test-scope: plugins/guardrails/README.md +# test-scope: plugins/guardrails/README.md plugins/guardrails/hooks/hooks.json set -uo pipefail HOOK_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" diff --git a/plugins/guardrails/hooks/exec-bash.test.sh b/plugins/guardrails/hooks/exec-bash.test.sh index f60ba1889a..7f94abecfb 100755 --- a/plugins/guardrails/hooks/exec-bash.test.sh +++ b/plugins/guardrails/hooks/exec-bash.test.sh @@ -1,6 +1,7 @@ #!/usr/bin/env bash # exec-bash.mjs is the exec-form entry for run-guards.sh: command is node, # this file is args[0], and bash is a child. Bare bash is not the hook command. +# test-scope: plugins/guardrails/hooks/hooks.json set -uo pipefail HOOK_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" diff --git a/plugins/guardrails/hooks/require-jq-notice-isolation.test.sh b/plugins/guardrails/hooks/require-jq-notice-isolation.test.sh index 93fc41ace1..2cbf1123a4 100755 --- a/plugins/guardrails/hooks/require-jq-notice-isolation.test.sh +++ b/plugins/guardrails/hooks/require-jq-notice-isolation.test.sh @@ -12,7 +12,7 @@ # # jq is hidden by the BASH_ENV shim require-jq-posture.test.sh documents: the # LOOKUP of jq fails, PATH is untouched. -# test-scope: plugins/guardrails/hooks/*.sh +# test-scope: plugins/guardrails/hooks/*.sh plugins/guardrails/.claude-plugin/plugin.json plugins/guardrails/prerequisites.json set -uo pipefail HOOK_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" diff --git a/plugins/guardrails/hooks/require-jq-posture.test.sh b/plugins/guardrails/hooks/require-jq-posture.test.sh index 2af44515bc..d86854c65d 100755 --- a/plugins/guardrails/hooks/require-jq-posture.test.sh +++ b/plugins/guardrails/hooks/require-jq-posture.test.sh @@ -39,7 +39,7 @@ # that real jq-removal "is not portably simulable" via an isolated bin dir # (which cannot host bash + coreutils across Git Bash and Linux). That is true # of the bin-dir approach, and is exactly why this one overrides the lookup. -# test-scope: plugins/guardrails/hooks/*.sh +# test-scope: plugins/guardrails/hooks/*.sh plugins/guardrails/.claude-plugin/plugin.json plugins/guardrails/hooks/hooks.json plugins/guardrails/prerequisites.json set -uo pipefail diff --git a/plugins/guardrails/hooks/run-guards.test.sh b/plugins/guardrails/hooks/run-guards.test.sh index c55318286b..8227f93787 100755 --- a/plugins/guardrails/hooks/run-guards.test.sh +++ b/plugins/guardrails/hooks/run-guards.test.sh @@ -15,7 +15,7 @@ # # The stub guard bodies below are single-quoted on purpose: they are written # verbatim into stub scripts, so their `$` must not expand here. -# test-scope: plugins/guardrails/hooks/*.sh +# test-scope: plugins/guardrails/hooks/*.sh plugins/guardrails/.claude-plugin/plugin.json plugins/guardrails/hooks/hooks.json plugins/guardrails/prerequisites.json # shellcheck disable=SC2016 set -uo pipefail diff --git a/plugins/guardrails/hooks/secret-pattern-detection.test.sh b/plugins/guardrails/hooks/secret-pattern-detection.test.sh index b23695246d..59a38486c3 100755 --- a/plugins/guardrails/hooks/secret-pattern-detection.test.sh +++ b/plugins/guardrails/hooks/secret-pattern-detection.test.sh @@ -7,6 +7,7 @@ # Token construction discipline: every real-shape token is assembled at runtime # from concatenated parts, so the literal joined string never appears in this # file's source bytes — no secret scanner (gitleaks etc.) sees a committed key. +# test-scope: plugins/guardrails/hooks/hooks.json set -uo pipefail diff --git a/plugins/harness-config/skills/audit-automation-gaps/scripts/inventory.test.sh b/plugins/harness-config/skills/audit-automation-gaps/scripts/inventory.test.sh index fcd6d104d0..ccc5d4f62f 100755 --- a/plugins/harness-config/skills/audit-automation-gaps/scripts/inventory.test.sh +++ b/plugins/harness-config/skills/audit-automation-gaps/scripts/inventory.test.sh @@ -47,9 +47,11 @@ assert_not_contains() { *) pass "$1" ;; esac } -# Asserts one line of output verbatim, which pins the numbers inside it. +# Asserts one line of output verbatim, which pins the numbers inside it. A +# here-string, not a pipe: grep -q exits on its first match, and under pipefail +# the writer's SIGPIPE would fail the assertion. assert_line() { - if printf '%s\n' "$2" | grep -Fxq -- "$3"; then + if grep -Fxq -- "$3" <<<"$2"; then pass "$1" else fail "$1" "no line exactly equal to: $3" @@ -600,14 +602,15 @@ assert_eq "a plugin-free repo reports the plugin row absent, not 0 handlers" \ assert_line "a plugin-free repo reports 0 plugin roots" "$empty_out" " plugin roots 0" assert_contains "a plugin-free repo still emits components" "$empty_out" "Components in this repository" -# --- The real repository this script ships in --------------------------------- +# --- The default project root ------------------------------------------------ # -# A smoke pass over a live checkout, which is the shape the fixtures cannot -# supply: many plugins, many skills, real settings. The bounds are deliberately -# one-sided, because the tree grows; the exact numbers are the fixtures' job. +# A run with no INVENTORY_PROJECT_DIR takes the git toplevel, or the working +# directory outside a repository, and still emits the whole table. It runs in +# the golden fixture: a run from this checkout walked every plugin of the live +# tree, files no change to them would select this suite for. rc=0 -out="$(bash "$INVENTORY" 2>/dev/null)" || rc=$? +out="$(cd "$GOLD" && env -u CLAUDE_PROJECT_DIR bash "$INVENTORY" 2>/dev/null)" || rc=$? assert_exit "default run exits 0" 0 "$rc" assert_contains "table header present" "$out" "LOCATION" assert_contains "table header carries PROBED" "$out" "PROBED" @@ -626,9 +629,9 @@ assert_contains "subagent frontmatter is conditional" "$(row_for subagent-frontm line_count="$(printf '%s\n' "$out" | wc -l | tr -d ' ')" if [[ "$line_count" -ge 30 && "$line_count" -le 45 ]]; then - pass "real-repo output stays a count table ($line_count lines)" + pass "default-root output stays a count table ($line_count lines)" else - fail "real-repo output stays a count table" "expected 30 to 45 lines, got $line_count" + fail "default-root output stays a count table" "expected 30 to 45 lines, got $line_count" fi if [[ "$FAILED" -eq 0 ]]; then diff --git a/plugins/harness-config/skills/audit-instructions/scripts/finding-ids.test.sh b/plugins/harness-config/skills/audit-instructions/scripts/finding-ids.test.sh index d91d1e4cb3..271942563d 100755 --- a/plugins/harness-config/skills/audit-instructions/scripts/finding-ids.test.sh +++ b/plugins/harness-config/skills/audit-instructions/scripts/finding-ids.test.sh @@ -250,7 +250,7 @@ catalog_ids="$(sed -n 's/^### \(I[0-9][0-9]*\):.*/\1/p' "$SKILL_DIR/reference/cr missing="" while IFS= read -r cid; do [[ -n "$cid" ]] || continue - printf '%s\n' "$script_table" | grep -q "^$cid " || missing="$missing $cid" + grep -q "^$cid " <<<"$script_table" || missing="$missing $cid" done <<<"$catalog_ids" assert_eq "every catalog check from I6 on has a claim template" "" "$missing" while IFS=' ' read -r cid claim; do diff --git a/plugins/harness-memory/scripts/spoke-script-paths.test.sh b/plugins/harness-memory/scripts/spoke-script-paths.test.sh index 247869bc45..b2d320bb1f 100755 --- a/plugins/harness-memory/scripts/spoke-script-paths.test.sh +++ b/plugins/harness-memory/scripts/spoke-script-paths.test.sh @@ -3,6 +3,7 @@ # skills/*/reference are read by the Read tool, which substitutes no variables, so they name # bundled scripts as /scripts/.sh and SKILL.md renders the placeholder. # shellcheck disable=SC2016,SC2013 # the dollar-brace tokens are literal text; script names hold no spaces +# test-scope: plugins/harness-memory/skills/* set -uo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" diff --git a/plugins/harness-ops/hooks/hook-failure-audit.test.sh b/plugins/harness-ops/hooks/hook-failure-audit.test.sh index f6530394f6..3dfe6b773f 100755 --- a/plugins/harness-ops/hooks/hook-failure-audit.test.sh +++ b/plugins/harness-ops/hooks/hook-failure-audit.test.sh @@ -8,6 +8,7 @@ # (a `hook_success` quoting an error in stdout, and a message record quoting a # failure record as a string) are the exact traps hit while mining the incident # transcripts — see #2577. +# test-scope: plugins/harness-ops/hooks/hooks.json set -uo pipefail HOOK_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" diff --git a/plugins/harness-ops/lib/spawn_noise.test.sh b/plugins/harness-ops/lib/spawn_noise.test.sh index 279db98bd9..ce8a415355 100755 --- a/plugins/harness-ops/lib/spawn_noise.test.sh +++ b/plugins/harness-ops/lib/spawn_noise.test.sh @@ -4,6 +4,7 @@ # The lib is imported by the audit-performance engine and by the # performance plugin, so its tests live beside the canonical copy rather than # inside either consumer. +# test-scope: plugins/harness-ops/lib/test_spawn_noise.py set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" diff --git a/plugins/harness-ops/skills/audit-install-state/scripts/install_state.test.sh b/plugins/harness-ops/skills/audit-install-state/scripts/install_state.test.sh index af6feb740e..cdbffe89bc 100755 --- a/plugins/harness-ops/skills/audit-install-state/scripts/install_state.test.sh +++ b/plugins/harness-ops/skills/audit-install-state/scripts/install_state.test.sh @@ -1,5 +1,6 @@ #!/usr/bin/env bash # Cross-platform contract wrapper for the install-state engine test suite. +# test-scope: plugins/harness-ops/.claude-plugin/plugin.json set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" diff --git a/plugins/harness-ops/skills/audit-install-state/scripts/test_install_state.py b/plugins/harness-ops/skills/audit-install-state/scripts/test_install_state.py index 7cb9400a13..2eef10fbe5 100755 --- a/plugins/harness-ops/skills/audit-install-state/scripts/test_install_state.py +++ b/plugins/harness-ops/skills/audit-install-state/scripts/test_install_state.py @@ -1,4 +1,5 @@ #!/usr/bin/env python3 +# test-scope: plugins/harness-ops/.claude-plugin/plugin.json """Contract tests for the install-state engine. Every test builds its own synthetic tree. Nothing here reads the author's real diff --git a/plugins/harness-ops/skills/audit-performance/scripts/audit_performance.test.sh b/plugins/harness-ops/skills/audit-performance/scripts/audit_performance.test.sh index cc5748440c..0dd9654ae8 100755 --- a/plugins/harness-ops/skills/audit-performance/scripts/audit_performance.test.sh +++ b/plugins/harness-ops/skills/audit-performance/scripts/audit_performance.test.sh @@ -1,5 +1,6 @@ #!/usr/bin/env bash # Cross-platform contract wrapper for the audit-performance engine test suite. +# test-scope: plugins/harness-ops/skills/audit-performance/scripts/test_audit_performance.py set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" diff --git a/plugins/harness-ops/skills/audit-skill-visibility/scripts/audit_skill_visibility.test.sh b/plugins/harness-ops/skills/audit-skill-visibility/scripts/audit_skill_visibility.test.sh index 973512be59..1f270d0340 100755 --- a/plugins/harness-ops/skills/audit-skill-visibility/scripts/audit_skill_visibility.test.sh +++ b/plugins/harness-ops/skills/audit-skill-visibility/scripts/audit_skill_visibility.test.sh @@ -1,5 +1,6 @@ #!/usr/bin/env bash # Cross-platform contract wrapper for the skill-starvation engine test suite. +# test-scope: plugins/harness-ops/skills/audit-skill-visibility/scripts/test_audit_skill_visibility.py plugins/harness-ops/skills/audit-skill-visibility/tests/fixtures/* set -euo pipefail # ISOLATION (#2840). The churn tests build throwaway git repositories, and the diff --git a/plugins/harness-ops/skills/behavior-probes/scripts/test_probe.py b/plugins/harness-ops/skills/behavior-probes/scripts/test_probe.py index 357ba77896..cdc39e4e04 100644 --- a/plugins/harness-ops/skills/behavior-probes/scripts/test_probe.py +++ b/plugins/harness-ops/skills/behavior-probes/scripts/test_probe.py @@ -1,3 +1,4 @@ +# test-scope: plugins/harness-ops/skills/behavior-probes/cases/* """Tests for probe.py. Nothing here starts `claude`: every run uses --dry-run or a stub runner.""" from __future__ import annotations diff --git a/plugins/harness-ops/skills/changelog/scripts/discover-surfaces.test.sh b/plugins/harness-ops/skills/changelog/scripts/discover-surfaces.test.sh index 8fe9ce0a44..26746c47aa 100755 --- a/plugins/harness-ops/skills/changelog/scripts/discover-surfaces.test.sh +++ b/plugins/harness-ops/skills/changelog/scripts/discover-surfaces.test.sh @@ -14,6 +14,7 @@ # # Uses the fixture under ../evals/fixtures/consumer-repo and needs no git, network # or installed CLI. +# test-scope: plugins/harness-ops/skills/changelog/evals/fixtures/consumer-repo/* set -uo pipefail diff --git a/plugins/harness-ops/skills/inventory/scripts/test_fixture_parse.py b/plugins/harness-ops/skills/inventory/scripts/test_fixture_parse.py index 67c7a1049b..4c6c0f7625 100755 --- a/plugins/harness-ops/skills/inventory/scripts/test_fixture_parse.py +++ b/plugins/harness-ops/skills/inventory/scripts/test_fixture_parse.py @@ -1,4 +1,6 @@ #!/usr/bin/env python3 +# test-scope: plugins/harness-ops/skills/inventory/scripts/js/package-lock.json +# test-scope: plugins/harness-ops/scripts/docs-cache.sh plugins/harness-ops/scripts/fetch-docs.sh """Every JavaScript fixture test_inventory.py feeds the reader in inventory.py parses as a module under acorn. Naming inventory.py here is what makes scripts/affected-tests.sh select this suite when it changes. diff --git a/plugins/harness-ops/skills/inventory/scripts/test_inventory.py b/plugins/harness-ops/skills/inventory/scripts/test_inventory.py index 41e97d398a..ab0f237127 100755 --- a/plugins/harness-ops/skills/inventory/scripts/test_inventory.py +++ b/plugins/harness-ops/skills/inventory/scripts/test_inventory.py @@ -1,4 +1,5 @@ #!/usr/bin/env python3 +# test-scope: plugins/harness-ops/scripts/docs-cache.sh plugins/harness-ops/scripts/fetch-docs.sh """Deterministic tests for the inventory extractor. Every case runs against a synthetic minified fragment rather than a real diff --git a/plugins/harness-ops/skills/inventory/scripts/test_inventory_under_parser.py b/plugins/harness-ops/skills/inventory/scripts/test_inventory_under_parser.py index 6aaed7cf7e..c8a5402ab8 100755 --- a/plugins/harness-ops/skills/inventory/scripts/test_inventory_under_parser.py +++ b/plugins/harness-ops/skills/inventory/scripts/test_inventory_under_parser.py @@ -1,4 +1,6 @@ #!/usr/bin/env python3 +# test-scope: plugins/harness-ops/skills/inventory/scripts/js/package-lock.json +# test-scope: plugins/harness-ops/scripts/docs-cache.sh plugins/harness-ops/scripts/fetch-docs.sh plugins/harness-ops/skills/inventory/scripts/js/package.json """Every test_inventory.py case again, with inventory.py's binding lookups answered by the parser reader (parser_reader.py driving js/parser_helper.cjs) instead of the regex reader. Naming those three files diff --git a/plugins/harness-ops/skills/inventory/scripts/test_parser_reader.py b/plugins/harness-ops/skills/inventory/scripts/test_parser_reader.py index 4bbcff1c77..eee19f86dd 100755 --- a/plugins/harness-ops/skills/inventory/scripts/test_parser_reader.py +++ b/plugins/harness-ops/skills/inventory/scripts/test_parser_reader.py @@ -1,4 +1,5 @@ #!/usr/bin/env python3 +# test-scope: plugins/harness-ops/skills/inventory/scripts/js/package-lock.json plugins/harness-ops/skills/inventory/scripts/js/package.json """Tests for parser_reader.py, the js/parser_helper.cjs process it drives, and inventory.py's --reader=parser|compare wiring. diff --git a/plugins/harness-ops/skills/inventory/scripts/test_reader_findings.py b/plugins/harness-ops/skills/inventory/scripts/test_reader_findings.py index 120452e3a7..c15566efc6 100755 --- a/plugins/harness-ops/skills/inventory/scripts/test_reader_findings.py +++ b/plugins/harness-ops/skills/inventory/scripts/test_reader_findings.py @@ -1,4 +1,6 @@ #!/usr/bin/env python3 +# test-scope: plugins/harness-ops/skills/inventory/scripts/js/package-lock.json +# test-scope: plugins/harness-ops/skills/inventory/scripts/js/package.json """The open wrong-value and unresolved-only findings on #5640 in the bundle reader of inventory.py, pinned under both readers. Naming inventory.py, parser_reader.py and js/parser_helper.cjs here is what makes diff --git a/plugins/harness-ops/skills/observability/scripts/build-dashboard.test.sh b/plugins/harness-ops/skills/observability/scripts/build-dashboard.test.sh index 34e82aa09d..99bd024879 100755 --- a/plugins/harness-ops/skills/observability/scripts/build-dashboard.test.sh +++ b/plugins/harness-ops/skills/observability/scripts/build-dashboard.test.sh @@ -6,6 +6,7 @@ # bash plugins/harness-ops/skills/observability/scripts/build-dashboard.test.sh # # Exit 0 clean, 1 findings, 2 environment (node missing). +# test-scope: plugins/harness-ops/skills/observability/SKILL.md set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" || exit 2 diff --git a/plugins/harness-ops/skills/observability/scripts/probe-observability-state.test.sh b/plugins/harness-ops/skills/observability/scripts/probe-observability-state.test.sh index f327580cd3..3973c948c9 100755 --- a/plugins/harness-ops/skills/observability/scripts/probe-observability-state.test.sh +++ b/plugins/harness-ops/skills/observability/scripts/probe-observability-state.test.sh @@ -43,6 +43,7 @@ # subshell increments a copy of the failure counter and the run would report # green with a failing case in it. Environment scoping is therefore explicit # set/unset around each case rather than subshell containment. +# test-scope: plugins/harness-ops/skills/observability/SKILL.md set -uo pipefail diff --git a/plugins/harness-ops/skills/plugins/scripts/cache-content-check.test.sh b/plugins/harness-ops/skills/plugins/scripts/cache-content-check.test.sh index f6b6e51c10..88940428d9 100755 --- a/plugins/harness-ops/skills/plugins/scripts/cache-content-check.test.sh +++ b/plugins/harness-ops/skills/plugins/scripts/cache-content-check.test.sh @@ -4,6 +4,7 @@ # repo standing in for a marketplace's installLocation at two commits, a fake # cache directory, and CACHE_CONTENT_* state files pointing at both. Mirrors # fleet-state.test.sh's structure. +# test-scope: .gitattributes set -uo pipefail # Fixture git isolation: an inherited GIT_DIR/GIT_WORK_TREE/GIT_CONFIG would diff --git a/plugins/harness-ops/skills/plugins/scripts/sync-run.test.sh b/plugins/harness-ops/skills/plugins/scripts/sync-run.test.sh index c98adbe1ed..fc2b5b08e3 100755 --- a/plugins/harness-ops/skills/plugins/scripts/sync-run.test.sh +++ b/plugins/harness-ops/skills/plugins/scripts/sync-run.test.sh @@ -5,7 +5,7 @@ # and its exit status is the production one. Only the two siblings whose real work # needs a machine — the `claude` CLI and cache-content-check.sh's git compare — are # stubbed, through sync-run.sh's own SYNC_RUN_* overrides. -# test-scope: .claude/settings.json +# test-scope: .claude/settings.json plugins/harness-ops/.claude-plugin/plugin.json set -uo pipefail # Fixture git isolation: an inherited GIT_DIR/GIT_WORK_TREE/GIT_CONFIG would diff --git a/plugins/knowledge/skills/docpage-digest/scripts/check-fences-exact.test.sh b/plugins/knowledge/skills/docpage-digest/scripts/check-fences-exact.test.sh index 94b23e54fd..13a0e921d8 100755 --- a/plugins/knowledge/skills/docpage-digest/scripts/check-fences-exact.test.sh +++ b/plugins/knowledge/skills/docpage-digest/scripts/check-fences-exact.test.sh @@ -1,6 +1,8 @@ #!/usr/bin/env bash # Cross-platform wrapper so plugin-gate (plugins/**/*.test.sh) runs the # check-fences-exact negative-control suite. +# test-scope: plugins/knowledge/skills/docpage-digest/scripts/test_check_fences_exact.py +# test-scope: plugins/knowledge/skills/docpage-digest/scripts/gate_harness.py set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" diff --git a/plugins/knowledge/skills/docpage-digest/scripts/check-html-rows.test.sh b/plugins/knowledge/skills/docpage-digest/scripts/check-html-rows.test.sh index 7be581d55f..c0087b0e43 100755 --- a/plugins/knowledge/skills/docpage-digest/scripts/check-html-rows.test.sh +++ b/plugins/knowledge/skills/docpage-digest/scripts/check-html-rows.test.sh @@ -2,6 +2,7 @@ # Cross-platform wrapper so plugin-gate (plugins/**/*.test.sh) runs the # check-html-rows negative-control suite. # test-scope: plugins/knowledge/skills/docpage-digest/scripts/fixtures/html-rows/* +# test-scope: plugins/knowledge/skills/docpage-digest/scripts/test_check_html_rows.py set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" diff --git a/plugins/knowledge/skills/docpage-digest/scripts/check-snippets.test.sh b/plugins/knowledge/skills/docpage-digest/scripts/check-snippets.test.sh index e240bde3b0..ed88f6237a 100755 --- a/plugins/knowledge/skills/docpage-digest/scripts/check-snippets.test.sh +++ b/plugins/knowledge/skills/docpage-digest/scripts/check-snippets.test.sh @@ -1,6 +1,8 @@ #!/usr/bin/env bash # Cross-platform wrapper so plugin-gate (plugins/**/*.test.sh) runs the # check-snippets negative-control suite. +# test-scope: plugins/knowledge/skills/docpage-digest/scripts/test_check_snippets.py +# test-scope: plugins/knowledge/skills/docpage-digest/scripts/gate_harness.py set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" diff --git a/plugins/knowledge/skills/docpage-digest/scripts/extract_blog_body.test.sh b/plugins/knowledge/skills/docpage-digest/scripts/extract_blog_body.test.sh index bc4ef87fc4..f9ded99e5d 100755 --- a/plugins/knowledge/skills/docpage-digest/scripts/extract_blog_body.test.sh +++ b/plugins/knowledge/skills/docpage-digest/scripts/extract_blog_body.test.sh @@ -2,6 +2,7 @@ # Cross-platform wrapper so plugin-gate (plugins/**/*.test.sh) runs the # extract_blog_body fixture suite. # test-scope: plugins/knowledge/skills/docpage-digest/scripts/fixtures/blog-body.html +# test-scope: plugins/knowledge/skills/docpage-digest/scripts/test_extract_blog_body.py set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" diff --git a/plugins/knowledge/skills/docpage-digest/scripts/pin-manifest.test.sh b/plugins/knowledge/skills/docpage-digest/scripts/pin-manifest.test.sh index da35f84690..8634f2285e 100755 --- a/plugins/knowledge/skills/docpage-digest/scripts/pin-manifest.test.sh +++ b/plugins/knowledge/skills/docpage-digest/scripts/pin-manifest.test.sh @@ -1,6 +1,7 @@ #!/usr/bin/env bash # Cross-platform wrapper so plugin-gate (plugins/**/*.test.sh) runs the # pin-manifest suite. +# test-scope: plugins/knowledge/skills/docpage-digest/scripts/test_pin_manifest.py set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" diff --git a/plugins/markdown-format/hooks/markdown-format.test.sh b/plugins/markdown-format/hooks/markdown-format.test.sh index 962f8f7df8..62b04748a7 100755 --- a/plugins/markdown-format/hooks/markdown-format.test.sh +++ b/plugins/markdown-format/hooks/markdown-format.test.sh @@ -12,6 +12,7 @@ # is invoked as a subprocess from an UNRELATED cwd so the `cd repo-root` config # discovery is genuinely exercised (running from the repo root would false-pass # a hook that skips the cd). +# test-scope: plugins/markdown-format/.claude-plugin/plugin.json plugins/markdown-format/hooks/hooks.json plugins/markdown-format/prerequisites.json set -uo pipefail diff --git a/plugins/multi-agent/hooks/drift-checker-fetch-gate.test.sh b/plugins/multi-agent/hooks/drift-checker-fetch-gate.test.sh index ce83ab3fe3..cc8894198f 100755 --- a/plugins/multi-agent/hooks/drift-checker-fetch-gate.test.sh +++ b/plugins/multi-agent/hooks/drift-checker-fetch-gate.test.sh @@ -1,6 +1,7 @@ #!/usr/bin/env bash # Discovery wrapper: scripts/run-plugin-tests.sh finds plugins/**/*.test.sh, so # this hands off to the Node suite. SKIPs (exit 0) when Node is unavailable. +# test-scope: plugins/multi-agent/hooks/hooks.json set -uo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" diff --git a/plugins/multi-agent/skills/check/scripts/check.test.sh b/plugins/multi-agent/skills/check/scripts/check.test.sh index 2e96f0e43e..35df6d915d 100755 --- a/plugins/multi-agent/skills/check/scripts/check.test.sh +++ b/plugins/multi-agent/skills/check/scripts/check.test.sh @@ -2,6 +2,7 @@ # Tests for check.sh: a healthy plugin passes every row; a PATH without node # fails the node and gate rows but still reports registration; a hooks.json # without the gate fails registration. +# test-scope: plugins/multi-agent/hooks/hooks.json set -uo pipefail HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" diff --git a/plugins/pixel-art/scripts/backends.test.sh b/plugins/pixel-art/scripts/backends.test.sh index fe758f3557..66f46064cf 100755 --- a/plugins/pixel-art/scripts/backends.test.sh +++ b/plugins/pixel-art/scripts/backends.test.sh @@ -1,6 +1,8 @@ #!/usr/bin/env bash # Runs every pixel-art test suite (test_*.py). # test-scope: plugins/pixel-art/examples/* plugins/pixel-art/palettes/* +# test-scope: plugins/pixel-art/scripts/test_*.py plugins/pixel-art/scripts/gallery.py +# test-scope: plugins/pixel-art/scripts/*.py set -uo pipefail cd "$(dirname "${BASH_SOURCE[0]}")" || exit 1 diff --git a/plugins/pixel-art/scripts/test_palette.py b/plugins/pixel-art/scripts/test_palette.py index 6db24e32e7..70c68edd66 100644 --- a/plugins/pixel-art/scripts/test_palette.py +++ b/plugins/pixel-art/scripts/test_palette.py @@ -1,3 +1,4 @@ +# test-scope: plugins/pixel-art/palettes/*.json """Preset resolution and palette snapping.""" import json import pathlib diff --git a/plugins/planning/scripts/eval-scaffold.test.sh b/plugins/planning/scripts/eval-scaffold.test.sh index b45055d940..87c3e11b41 100755 --- a/plugins/planning/scripts/eval-scaffold.test.sh +++ b/plugins/planning/scripts/eval-scaffold.test.sh @@ -3,6 +3,7 @@ # so the eval cases that read them have a workspace: the nightly-etl # repository for the brainstorm cases, and the partner-feed tracker export for # the wayfind work-mode cases. +# test-scope: plugins/planning/evals/observed-fact-evidence-bar/scaffold.sh plugins/planning/evals/work-*/scaffold.sh set -euo pipefail here="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" diff --git a/plugins/planning/surface/surface.test.sh b/plugins/planning/surface/surface.test.sh index 9ed77f8f04..c2cf97df9e 100755 --- a/plugins/planning/surface/surface.test.sh +++ b/plugins/planning/surface/surface.test.sh @@ -8,6 +8,7 @@ # The browser suites run only where playwright-cli resolves; elsewhere they print a SKIP with # the number of checks not run, never a pass. # test-scope: plugins/planning/surface/schema/* plugins/planning/surface/tests/fixtures/* +# test-scope: plugins/planning/.claude-plugin/plugin.json set -u here=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) root=$(cd "$here/../../.." && pwd) diff --git a/plugins/planning/surface/test_exporters.py b/plugins/planning/surface/test_exporters.py index e07cae1e83..5c98acc151 100644 --- a/plugins/planning/surface/test_exporters.py +++ b/plugins/planning/surface/test_exporters.py @@ -1,4 +1,5 @@ # test-scope: plugins/planning/surface/tests/fixtures/ledger-legacy/* +# test-scope: plugins/planning/surface/schema/*.schema.json plugins/planning/.claude-plugin/plugin.json """Tests for the exporters and import-ledger (AC27 to AC29), driven through round.py's CLI. Sessions are built in temporary data dirs: questions.json written directly, responses.json diff --git a/plugins/planning/surface/test_round.py b/plugins/planning/surface/test_round.py index 37246550f6..bc34c4b60e 100644 --- a/plugins/planning/surface/test_round.py +++ b/plugins/planning/surface/test_round.py @@ -1,4 +1,5 @@ # test-scope: plugins/planning/surface/tests/fixtures/*.json +# test-scope: plugins/planning/surface/schema/*.schema.json plugins/planning/.claude-plugin/plugin.json """Tests for round.py V1: schema validation, refusals and warnings, --affects, apply, archive, status --latency, the sidecar lock and the rebuild check. diff --git a/plugins/planning/surface/test_schema.py b/plugins/planning/surface/test_schema.py index 4233502a4c..11e958a30b 100644 --- a/plugins/planning/surface/test_schema.py +++ b/plugins/planning/surface/test_schema.py @@ -1,4 +1,5 @@ # test-scope: plugins/planning/surface/tests/fixtures/*.json +# test-scope: plugins/planning/surface/schema/*.schema.json """Tests for schema.py, the stdlib JSON Schema subset round.py validates with.""" from __future__ import annotations diff --git a/plugins/planning/surface/test_server.py b/plugins/planning/surface/test_server.py index dce77257e7..a5cdc0af10 100644 --- a/plugins/planning/surface/test_server.py +++ b/plugins/planning/surface/test_server.py @@ -1,4 +1,5 @@ # test-scope: plugins/planning/surface/tests/fixtures/*.json +# test-scope: plugins/planning/surface/schema/*.schema.json plugins/planning/.claude-plugin/plugin.json """Tests for the interview surface server and its lifecycle commands. Every class starts its own server through `round.py ensure-running --port 0` in a diff --git a/plugins/planning/surface/watch.test.sh b/plugins/planning/surface/watch.test.sh index fbfdc84663..ecd88cdb75 100755 --- a/plugins/planning/surface/watch.test.sh +++ b/plugins/planning/surface/watch.test.sh @@ -10,6 +10,7 @@ # lease is reclaimed after leaseTimeout), the fallback watcher id and a release mid-wait, and # round.sh stop ending the data dir's watch.sh (the lease records its pid). # test-scope: plugins/planning/surface/schema/* +# test-scope: plugins/planning/.claude-plugin/plugin.json set -u # Every watcher in the suite is one session unless a case names another. export WATCH_ID=suite diff --git a/plugins/playbooks/skills/repo-sweep/scripts/catalog.test.sh b/plugins/playbooks/skills/repo-sweep/scripts/catalog.test.sh index a7a67ff926..b905dc1df8 100755 --- a/plugins/playbooks/skills/repo-sweep/scripts/catalog.test.sh +++ b/plugins/playbooks/skills/repo-sweep/scripts/catalog.test.sh @@ -1,5 +1,6 @@ #!/usr/bin/env bash # Tests for catalog.sh against fixture catalogs written into a temp dir. +# test-scope: plugins/playbooks/skills/repo-sweep/catalogs/*.md set -uo pipefail SCRIPT="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/catalog.sh" diff --git a/plugins/powershell-format/hooks/powershell-format.test.sh b/plugins/powershell-format/hooks/powershell-format.test.sh index 73822e5596..fb2c8972ad 100755 --- a/plugins/powershell-format/hooks/powershell-format.test.sh +++ b/plugins/powershell-format/hooks/powershell-format.test.sh @@ -17,6 +17,7 @@ # suite skips; the pwsh-absent and module-absent GRACEFUL-DEGRADE paths are # exercised separately by simulating their absence, so they run even where a real # pwsh + module is present. +# test-scope: plugins/powershell-format/.claude-plugin/plugin.json plugins/powershell-format/hooks/hooks.json plugins/powershell-format/prerequisites.json set -uo pipefail diff --git a/plugins/repo-fleet-hygiene/skills/sync/scripts/sync-fleet.test.sh b/plugins/repo-fleet-hygiene/skills/sync/scripts/sync-fleet.test.sh index 047d4e3520..d46cd23063 100755 --- a/plugins/repo-fleet-hygiene/skills/sync/scripts/sync-fleet.test.sh +++ b/plugins/repo-fleet-hygiene/skills/sync/scripts/sync-fleet.test.sh @@ -254,7 +254,10 @@ done # The source-control plugin next door is never probed: a decoy in its place stays unread. layout="$TMP/layout" mkdir -p "$layout/source-control/scripts" -cp -R "$SCRIPT_DIR/../../.." "$layout/repo-fleet-hygiene" +# The copy holds only what sync-fleet.sh sources, under the plugin's own layout. +mkdir -p "$layout/repo-fleet-hygiene/skills/sync/scripts" "$layout/repo-fleet-hygiene/scripts" +cp "$SCRIPT" "$layout/repo-fleet-hygiene/skills/sync/scripts/" +cp "$SCRIPT_DIR/../../../scripts/scope-resolve.sh" "$SCRIPT_DIR/../../../scripts/fleet-discovery.sh" "$layout/repo-fleet-hygiene/scripts/" printf '#!/usr/bin/env bash\ntouch "%s"\necho "%s"\n' "$TMP/decoy.ran" "$TMP/decoy-worktree" >"$layout/source-control/scripts/worktree-create.sh" for sibling in "a decoy" no; do [[ "$sibling" != no ]] || rm -rf "$layout/source-control" diff --git a/plugins/retro-audio/scripts/audio.test.sh b/plugins/retro-audio/scripts/audio.test.sh index 8ec1d82b64..d50169eacc 100755 --- a/plugins/retro-audio/scripts/audio.test.sh +++ b/plugins/retro-audio/scripts/audio.test.sh @@ -1,6 +1,7 @@ #!/usr/bin/env bash # Contract tests for retro-audio WAV rendering. # test-scope: plugins/retro-audio/examples/* +# test-scope: plugins/retro-audio/scripts/*.py set -uo pipefail cd "$(dirname "${BASH_SOURCE[0]}")" || exit 1 diff --git a/plugins/review/tests/explain-change.test.sh b/plugins/review/tests/explain-change.test.sh index 3d13dd4ea4..d1f3eeb873 100755 --- a/plugins/review/tests/explain-change.test.sh +++ b/plugins/review/tests/explain-change.test.sh @@ -1,6 +1,7 @@ #!/usr/bin/env bash # Discovery wrapper: scripts/run-plugin-tests.sh finds plugins/**/*.test.sh, so # this hands off to the Node suite. SKIPs (exit 0) when Node is unavailable. +# test-scope: plugins/review/skills/explain-change/SKILL.md set -uo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" diff --git a/plugins/review/tests/fanout-sweep.test.sh b/plugins/review/tests/fanout-sweep.test.sh index 86012b9131..2c9d6ab7c6 100755 --- a/plugins/review/tests/fanout-sweep.test.sh +++ b/plugins/review/tests/fanout-sweep.test.sh @@ -1,6 +1,7 @@ #!/usr/bin/env bash # Discovery wrapper: scripts/run-plugin-tests.sh finds plugins/**/*.test.sh, so # this hands off to the Node suite. SKIPs (exit 0) when Node is unavailable. +# test-scope: plugins/review/agents/*.md set -uo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" diff --git a/plugins/ruff-format/hooks/ruff-format.test.sh b/plugins/ruff-format/hooks/ruff-format.test.sh index f386b76ac6..509f7bfd3f 100755 --- a/plugins/ruff-format/hooks/ruff-format.test.sh +++ b/plugins/ruff-format/hooks/ruff-format.test.sh @@ -18,6 +18,7 @@ # # Requires a real Ruff binary: $RUFF_TEST_BIN if set, else `ruff` on PATH. # Without one the behavioral assertions cannot run, so the suite skips. +# test-scope: plugins/ruff-format/.claude-plugin/plugin.json plugins/ruff-format/hooks/hooks.json plugins/ruff-format/prerequisites.json set -uo pipefail diff --git a/plugins/session-flow/scripts/tests/test_save_point.py b/plugins/session-flow/scripts/tests/test_save_point.py index d423180543..239335fb0b 100644 --- a/plugins/session-flow/scripts/tests/test_save_point.py +++ b/plugins/session-flow/scripts/tests/test_save_point.py @@ -1259,15 +1259,20 @@ def test_new_without_memory_dir_outside_git_uses_plugin_data_env(tmp_path): assert not (cwd / ".work").exists() +def _install_in_cache(version_dir): + # save_point.py and io_streams.py, the one module it imports, under the + # plugin cache layout. + scripts = version_dir / "scripts" + scripts.mkdir(parents=True) + for name in ("save_point.py", "io_streams.py"): + shutil.copy2(SCRIPT.parent / name, scripts / name) + + def test_new_without_memory_dir_outside_git_derives_data_dir_from_cache(tmp_path): cwd = _outside_git(tmp_path) config = tmp_path / "config" version_dir = config / "plugins" / "cache" / "my.market" / "session-flow" / "1.2.3" - shutil.copytree( - SCRIPT.parent, - version_dir / "scripts", - ignore=shutil.ignore_patterns("tests", "__pycache__"), - ) + _install_in_cache(version_dir) memory = config / "plugins" / "data" / "session-flow-my-market" / "artifacts" memory.mkdir(parents=True) (memory / ".gitignore").write_text("*\n", encoding="utf-8") @@ -1294,11 +1299,7 @@ def test_new_without_memory_dir_ignores_another_plugins_data_env(tmp_path): cwd = _outside_git(tmp_path) config = tmp_path / "config" version_dir = config / "plugins" / "cache" / "my.market" / "session-flow" / "1.2.3" - shutil.copytree( - SCRIPT.parent, - version_dir / "scripts", - ignore=shutil.ignore_patterns("tests", "__pycache__"), - ) + _install_in_cache(version_dir) memory = config / "plugins" / "data" / "session-flow-my-market" / "artifacts" memory.mkdir(parents=True) (memory / ".gitignore").write_text("*\n", encoding="utf-8") diff --git a/plugins/session-flow/skills/audit-friction/scripts/audit-friction.test.sh b/plugins/session-flow/skills/audit-friction/scripts/audit-friction.test.sh index 85656cf6db..fe81a7ff48 100755 --- a/plugins/session-flow/skills/audit-friction/scripts/audit-friction.test.sh +++ b/plugins/session-flow/skills/audit-friction/scripts/audit-friction.test.sh @@ -7,6 +7,8 @@ # drives the audit-sessions collector, so its scripts are in scope too. # test-scope: plugins/session-flow/skills/audit-friction/scripts/tests/fixtures/* # test-scope: plugins/session-flow/skills/audit-sessions/scripts/*.py plugins/session-flow/scripts/transcript_reader.py +# test-scope: plugins/session-flow/.claude-plugin/plugin.json plugins/session-flow/lib/state-key.sh plugins/session-flow/skills/audit-friction/scripts/* +# test-scope: plugins/session-flow/skills/audit-sessions/vendor/gitleaks/gitleaks-rules.json set -uo pipefail cd "$(dirname "${BASH_SOURCE[0]}")" || exit 1 diff --git a/plugins/session-flow/skills/audit-friction/scripts/tests/test_friction.py b/plugins/session-flow/skills/audit-friction/scripts/tests/test_friction.py index c0bb7ca991..301892f4fa 100644 --- a/plugins/session-flow/skills/audit-friction/scripts/tests/test_friction.py +++ b/plugins/session-flow/skills/audit-friction/scripts/tests/test_friction.py @@ -1,3 +1,4 @@ +# test-scope: plugins/session-flow/.claude-plugin/plugin.json plugins/session-flow/skills/audit-friction/scripts/tests/fixtures/* """Contract tests for friction.py, and for the `friction` block collect.py writes, by subprocess. Fixture `fixtures/friction/` is synthetic. `proj-f/sess-f1` (interactive, 2026-09-10) holds, in its diff --git a/plugins/session-flow/skills/audit-sessions/scripts/audit-sessions.test.sh b/plugins/session-flow/skills/audit-sessions/scripts/audit-sessions.test.sh index 60ab45d538..d0f32f1c45 100755 --- a/plugins/session-flow/skills/audit-sessions/scripts/audit-sessions.test.sh +++ b/plugins/session-flow/skills/audit-sessions/scripts/audit-sessions.test.sh @@ -5,6 +5,8 @@ # repo test-runner convention for optional toolchains. -p no:cacheprovider keeps # a .pytest_cache/README.md out of the plugin-wide markdownlint glob. # test-scope: plugins/session-flow/skills/audit-sessions/scripts/tests/fixtures/* +# test-scope: plugins/session-flow/skills/audit-sessions/scripts/*.py plugins/session-flow/skills/audit-sessions/reference/*.json plugins/session-flow/skills/audit-sessions/vendor/gitleaks/gitleaks-rules.json +# test-scope: plugins/session-flow/.claude-plugin/plugin.json plugins/session-flow/lib/state-key.sh plugins/session-flow/scripts/transcript_reader.py plugins/session-flow/skills/retro/scripts/parse_transcript.py set -uo pipefail cd "$(dirname "${BASH_SOURCE[0]}")" || exit 1 diff --git a/plugins/session-flow/skills/audit-sessions/scripts/tests/test_collect.py b/plugins/session-flow/skills/audit-sessions/scripts/tests/test_collect.py index 34e19c7820..e3ccd19068 100644 --- a/plugins/session-flow/skills/audit-sessions/scripts/tests/test_collect.py +++ b/plugins/session-flow/skills/audit-sessions/scripts/tests/test_collect.py @@ -1,3 +1,4 @@ +# test-scope: plugins/session-flow/.claude-plugin/plugin.json plugins/session-flow/skills/audit-sessions/scripts/tests/fixtures/multi/* """Contract tests for collect.py: the CLI envelope and the store records it writes. Runs the scripts by subprocess, testing the CLI interface, not internals. The tracer test also diff --git a/plugins/session-flow/skills/audit-sessions/scripts/tests/test_drift.py b/plugins/session-flow/skills/audit-sessions/scripts/tests/test_drift.py index da7280e175..a0b8d9d71c 100644 --- a/plugins/session-flow/skills/audit-sessions/scripts/tests/test_drift.py +++ b/plugins/session-flow/skills/audit-sessions/scripts/tests/test_drift.py @@ -1,3 +1,4 @@ +# test-scope: plugins/session-flow/.claude-plugin/plugin.json """Drift guard tests: census.py's aggregate and classifier, and the `census`/`drift` subcommands. Classifier cases build store records in memory; the CLI cases read the synthetic store diff --git a/plugins/session-flow/skills/audit-sessions/scripts/tests/test_parse_transcript_golden.py b/plugins/session-flow/skills/audit-sessions/scripts/tests/test_parse_transcript_golden.py index 7cf5e3683f..991cc523f5 100644 --- a/plugins/session-flow/skills/audit-sessions/scripts/tests/test_parse_transcript_golden.py +++ b/plugins/session-flow/skills/audit-sessions/scripts/tests/test_parse_transcript_golden.py @@ -1,3 +1,4 @@ +# test-scope: plugins/session-flow/skills/audit-sessions/scripts/tests/fixtures/golden/* """Golden-output seam for retro's parse_transcript.py on the shared transcript reader. `fixtures/golden/expected/*.json` hold the parser's output over `fixtures/golden/proj/` captured diff --git a/plugins/session-flow/skills/audit-sessions/scripts/tests/test_transcript_reader.py b/plugins/session-flow/skills/audit-sessions/scripts/tests/test_transcript_reader.py index 5ad67eb727..ddd9f476eb 100644 --- a/plugins/session-flow/skills/audit-sessions/scripts/tests/test_transcript_reader.py +++ b/plugins/session-flow/skills/audit-sessions/scripts/tests/test_transcript_reader.py @@ -1,3 +1,4 @@ +# test-scope: plugins/session-flow/skills/audit-sessions/scripts/tests/fixtures/reader/subagents/* """Unit tests for transcript_reader.py, the one reader of Claude Code transcript JSONL. Drives the public interface only: `iter_records`, `record_kind`, `UsageLedger`, `is_typed_turn` diff --git a/plugins/session-flow/skills/retro/scripts/parse-transcript.test.sh b/plugins/session-flow/skills/retro/scripts/parse-transcript.test.sh index befee2a60e..bc7bd386a4 100755 --- a/plugins/session-flow/skills/retro/scripts/parse-transcript.test.sh +++ b/plugins/session-flow/skills/retro/scripts/parse-transcript.test.sh @@ -3,6 +3,8 @@ # # SKIPs (exit 0) when Python 3.10+ or pytest is unavailable, matching the # repo test-runner convention for optional toolchains. +# test-scope: plugins/session-flow/skills/retro/scripts/parse_transcript.py +# test-scope: plugins/session-flow/scripts/transcript_reader.py set -uo pipefail cd "$(dirname "${BASH_SOURCE[0]}")" || exit 1 diff --git a/plugins/session-flow/skills/running-retro/scripts/observer.test.sh b/plugins/session-flow/skills/running-retro/scripts/observer.test.sh index f37fde78ea..4b6bd0286c 100755 --- a/plugins/session-flow/skills/running-retro/scripts/observer.test.sh +++ b/plugins/session-flow/skills/running-retro/scripts/observer.test.sh @@ -8,6 +8,7 @@ # call -- never gate CI. SKIPs (exit 0) when Python 3.10+ is unavailable, # matching the floor this skill's other launchers (observer-arm.sh, SKILL.md) # already enforce. +# test-scope: plugins/session-flow/skills/running-retro/scripts/arm_observer.py set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" diff --git a/plugins/source-control/hooks/pr-linkage-mcp-gate.test.sh b/plugins/source-control/hooks/pr-linkage-mcp-gate.test.sh index 36535d0dac..c3c3b05224 100755 --- a/plugins/source-control/hooks/pr-linkage-mcp-gate.test.sh +++ b/plugins/source-control/hooks/pr-linkage-mcp-gate.test.sh @@ -10,6 +10,7 @@ # # Self-contained: builds throwaway git repos with runtime-generated fixtures # and invokes the hook as a subprocess. +# test-scope: plugins/source-control/hooks/hooks.json set -uo pipefail diff --git a/plugins/source-control/scripts/babysit-wrapper-help.test.sh b/plugins/source-control/scripts/babysit-wrapper-help.test.sh index 2cd027dcda..7f46cc8aa7 100755 --- a/plugins/source-control/scripts/babysit-wrapper-help.test.sh +++ b/plugins/source-control/scripts/babysit-wrapper-help.test.sh @@ -9,6 +9,7 @@ # regressed to a live call, a read-only `check` run would start touching the # fleet it was asked to inspect. So the canary's harmlessness is pinned here. # test-scope: plugins/source-control/scripts/* plugins/source-control/skills/babysit-prs/scripts/babysit_*.py +# test-scope: plugins/source-control/skills/babysit-prs/scripts/babysit-python.sh plugins/source-control/skills/babysit-prs/scripts/pr_review_threads.py set -uo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" diff --git a/plugins/source-control/scripts/convert-bump-to-fragment.test.sh b/plugins/source-control/scripts/convert-bump-to-fragment.test.sh index aa6c7237a9..ad97ec9156 100755 --- a/plugins/source-control/scripts/convert-bump-to-fragment.test.sh +++ b/plugins/source-control/scripts/convert-bump-to-fragment.test.sh @@ -1,4 +1,5 @@ #!/usr/bin/env bash +# test-scope: scripts/lib/* # Regression tests for convert-bump-to-fragment.sh against throwaway git # fixtures: a plugin at 1.2.0 listed in scripts/fragment-plugins.txt, a `pr` # branch that bumps it by hand, and a stub scripts/new-changelog-fragment.sh diff --git a/plugins/source-control/scripts/lib/worktree-facts.test.sh b/plugins/source-control/scripts/lib/worktree-facts.test.sh index 5d1a0c2f1d..4c9bcc43cf 100755 --- a/plugins/source-control/scripts/lib/worktree-facts.test.sh +++ b/plugins/source-control/scripts/lib/worktree-facts.test.sh @@ -1,5 +1,6 @@ #!/usr/bin/env bash # Fact-record tests. Assertion helpers are local. +# test-scope: plugins/source-control/scripts/*.sh set -uo pipefail unset GIT_DIR GIT_WORK_TREE GIT_CONFIG GIT_COMMON_DIR diff --git a/plugins/source-control/skills/babysit-prs/scripts/engine.test.sh b/plugins/source-control/skills/babysit-prs/scripts/engine.test.sh index 359440834b..166d2a0d20 100755 --- a/plugins/source-control/skills/babysit-prs/scripts/engine.test.sh +++ b/plugins/source-control/skills/babysit-prs/scripts/engine.test.sh @@ -6,6 +6,7 @@ # concern, not a Python one). SKIPs (exit 0) when Python 3.11+ is unavailable, matching the # repo test-runner convention for optional toolchains. # test-scope: plugins/source-control/*.md plugins/source-control/scripts/babysit-*.sh +# test-scope: plugins/source-control/skills/babysit-prs/scripts/*.py plugins/source-control/skills/babysit-prs/scripts/babysit-python.sh ruff.toml set -uo pipefail # Resolve before the cd: BASH_SOURCE is the path as invoked, so re-deriving it diff --git a/plugins/source-control/skills/babysit-prs/scripts/tests/test_guards.py b/plugins/source-control/skills/babysit-prs/scripts/tests/test_guards.py index c2cf2b10cc..a222369a3e 100644 --- a/plugins/source-control/skills/babysit-prs/scripts/tests/test_guards.py +++ b/plugins/source-control/skills/babysit-prs/scripts/tests/test_guards.py @@ -1,4 +1,4 @@ -# test-scope: plugins/source-control/skills/*/SKILL.md +# test-scope: plugins/source-control/*.md """Executes the guard contract in `guard_contract.py` against the real entry points. Every assertion here exercises a fact a consumer is told to rely on, and fails diff --git a/plugins/source-control/skills/babysit-prs/scripts/tests/test_skill_contract.py b/plugins/source-control/skills/babysit-prs/scripts/tests/test_skill_contract.py index c96ce81981..0de4db6c4e 100644 --- a/plugins/source-control/skills/babysit-prs/scripts/tests/test_skill_contract.py +++ b/plugins/source-control/skills/babysit-prs/scripts/tests/test_skill_contract.py @@ -1,3 +1,4 @@ +# test-scope: plugins/source-control/skills/babysit-prs/SKILL.md plugins/source-control/skills/babysit-loop/SKILL.md """SKILL.md prose contract assertions. Guards the merge-opt-in mode table, the zero-blocker check-only default, the diff --git a/plugins/source-control/skills/worktree/nesting-invariant-ssot.test.sh b/plugins/source-control/skills/worktree/nesting-invariant-ssot.test.sh index 12b52489e3..6bc299a816 100755 --- a/plugins/source-control/skills/worktree/nesting-invariant-ssot.test.sh +++ b/plugins/source-control/skills/worktree/nesting-invariant-ssot.test.sh @@ -18,6 +18,7 @@ # so the result does not depend on where it runs; unset skips that arm with a # counted skip. # test-scope: plugins/source-control/* +# test-scope: .gitattributes set -uo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" diff --git a/plugins/speech/scripts/speech.test.sh b/plugins/speech/scripts/speech.test.sh index 4f877c4b61..e019021743 100755 --- a/plugins/speech/scripts/speech.test.sh +++ b/plugins/speech/scripts/speech.test.sh @@ -6,6 +6,8 @@ # pinned requirements sets it, and a missing numpy then reads as a broken environment, not as passing coverage. # SPEECH_E2E_DATA_DIR opts into the real end-to-end narration (see test_narrate.py). # test-scope: plugins/speech/skills/*/SKILL.md plugins/speech/hooks/*.sh plugins/speech/scripts/*.json +# test-scope: plugins/speech/scripts/*.py plugins/speech/prerequisites.json plugins/speech/requirements.txt +# test-scope: plugins/speech/.claude-plugin/plugin.json set -uo pipefail cd "$(dirname "${BASH_SOURCE[0]}")" || exit 1 diff --git a/plugins/speech/scripts/test_assets.py b/plugins/speech/scripts/test_assets.py index f8f3f48a2a..e61d17c821 100644 --- a/plugins/speech/scripts/test_assets.py +++ b/plugins/speech/scripts/test_assets.py @@ -1,3 +1,4 @@ +# test-scope: plugins/speech/prerequisites.json plugins/speech/requirements.txt """assets.py and check.py: fetching pinned files with their hashes checked, and the read-only prerequisite report. Fetch tests serve a fixture manifest from a file:// source, so nothing reaches the network. diff --git a/plugins/speech/scripts/test_elevenlabs.py b/plugins/speech/scripts/test_elevenlabs.py index 92da2d90fe..5da40d0732 100644 --- a/plugins/speech/scripts/test_elevenlabs.py +++ b/plugins/speech/scripts/test_elevenlabs.py @@ -1,3 +1,4 @@ +# test-scope: plugins/speech/prerequisites.json plugins/speech/.claude-plugin/plugin.json """elevenlabs.py: the statement and quota gate before every call, the proceed gate, the egress floor, the cache, the retry policy, key delivery through vault-exec, the key never leaking, and the mapping from character alignment to word timings. No test reaches the network: the transport is injected, or urlopen is replaced.""" diff --git a/plugins/speech/scripts/test_speech_pydeps.py b/plugins/speech/scripts/test_speech_pydeps.py index f118a1285a..a652c9ae3a 100644 --- a/plugins/speech/scripts/test_speech_pydeps.py +++ b/plugins/speech/scripts/test_speech_pydeps.py @@ -1,3 +1,4 @@ +# test-scope: plugins/speech/skills/*/SKILL.md plugins/speech/scripts/* plugins/speech/hooks/*.sh """pydeps.py: the hash-locked on-demand install (first install, no-op rerun, a failed install) and the run launcher. The lock is a local wheel that provides the numpy and onnxruntime modules, installed with pip's own PIP_NO_INDEX and diff --git a/plugins/testing/evals/ui-defects/build-variants.test.sh b/plugins/testing/evals/ui-defects/build-variants.test.sh index 00312e0486..acd1b7031b 100755 --- a/plugins/testing/evals/ui-defects/build-variants.test.sh +++ b/plugins/testing/evals/ui-defects/build-variants.test.sh @@ -1,4 +1,5 @@ #!/usr/bin/env bash +# test-scope: plugins/testing/evals/vision-*/scaffold.sh # Contract test for build-variants.py. Expected values come from the defect # catalog (D1-D9, C0, C1 and their injections), not from the builder's output. set -euo pipefail diff --git a/plugins/testing/hooks/test-judge-bg.test.sh b/plugins/testing/hooks/test-judge-bg.test.sh index b7864e229b..d7f2908d82 100755 --- a/plugins/testing/hooks/test-judge-bg.test.sh +++ b/plugins/testing/hooks/test-judge-bg.test.sh @@ -2,6 +2,7 @@ # Contract test for test-judge-bg.sh, the task-end judge's PostToolUse job # (async): it judges the test blocks a write created or changed and writes # their verdicts to the ledger. The judge is the stub behind TEST_JUDGE_CMD. +# test-scope: plugins/testing/skills/audit/adapters/*.yaml # shellcheck disable=SC2016,SC2034 # check() evals its single-quoted condition, which reads these # shellcheck source=judge-test-helpers.sh diff --git a/plugins/testing/hooks/test-judge.test.sh b/plugins/testing/hooks/test-judge.test.sh index 5c40448ece..6904a206d7 100755 --- a/plugins/testing/hooks/test-judge.test.sh +++ b/plugins/testing/hooks/test-judge.test.sh @@ -2,6 +2,7 @@ # Contract test for test-judge.sh, the task-end judge's Stop hook: it waits for # jobs still running, judges what has no verdict, and relays the verdicts # once from a fixed template. The judge is the stub behind TEST_JUDGE_CMD. +# test-scope: plugins/testing/hooks/hooks.json plugins/testing/skills/audit/adapters/*.yaml # shellcheck disable=SC2016,SC2034 # check() evals its single-quoted condition, which reads these # shellcheck source=judge-test-helpers.sh @@ -361,7 +362,7 @@ wait cat >"$TMP/term-stub.sh" <<'EOF' #!/usr/bin/env bash trap '' TERM -printf x >"$STUB_DIR/call-term-$$.args" +printf x >"$STUB_DIR/term-$$.args" end=$((SECONDS + 8)) while ((SECONDS < end)); do sleep 0.2; done echo '{"type":"result","subtype":"success","is_error":false,"result":"{\"verdicts\":[]}"}' @@ -380,7 +381,9 @@ p="$(find "$DATA/pending/$PKEY/s8c" -type f | head -1)" check "its key gets a pending/ marker and a live background job" '[[ -n "$p" ]] && kill -0 "$(head -1 "$p" | cut -d" " -f1)" 2>/dev/null' assert_contains "the message counts the key not judged in time" "$(field .systemMessage)" "1 more test is judged in the background" stop_jobs s8c -tmp_kill TERM "$TMP/term-stub.sh" +# KILL: the stub ignores TERM. Its marker is not a call-*.args file, so a +# background run that starts late is never counted as a later case's call. +tmp_kill KILL "$TMP/term-stub.sh" wait # Two failed attempts: "judge not run", and no third attempt. diff --git a/plugins/testing/hooks/test-scan.test.sh b/plugins/testing/hooks/test-scan.test.sh index 02dfe4565e..d4588f912e 100755 --- a/plugins/testing/hooks/test-scan.test.sh +++ b/plugins/testing/hooks/test-scan.test.sh @@ -10,6 +10,7 @@ # can fail, the marker prune, the per-call dedup that keeps two # overlapping `if` rows from reporting twice, and the per-write session state # (blocks, project key, prune) the task-end judge reads. +# test-scope: plugins/testing/hooks/hooks.json plugins/testing/skills/audit/adapters/*.yaml # shellcheck disable=SC2016 # fence lines in fixtures are literal text set -uo pipefail diff --git a/plugins/testing/hooks/test-weaken.test.sh b/plugins/testing/hooks/test-weaken.test.sh index 49a7bb194d..c7d26e2769 100755 --- a/plugins/testing/hooks/test-weaken.test.sh +++ b/plugins/testing/hooks/test-weaken.test.sh @@ -9,6 +9,7 @@ # scanner error and a hang, silence on a pure addition, a create, a # gitignored or excluded path and an unjudgeable fragment, the per-call dedup, # and that test-scan still reports for a call test-weaken already saw. +# test-scope: plugins/testing/skills/audit/adapters/*.yaml set -uo pipefail unset GIT_DIR GIT_WORK_TREE GIT_CONFIG diff --git a/plugins/testing/scripts/gen-hook-filters.test.sh b/plugins/testing/scripts/gen-hook-filters.test.sh index 7113df7f16..fc98eae31a 100755 --- a/plugins/testing/scripts/gen-hook-filters.test.sh +++ b/plugins/testing/scripts/gen-hook-filters.test.sh @@ -3,6 +3,7 @@ # adapters, every row is gated by an `if`, no row matches a non-test path, no # glob repeats, and --check catches drift. # test-scope: plugins/testing/skills/audit/adapters/*.yaml +# test-scope: plugins/testing/hooks/hooks.json # shellcheck disable=SC2016 # check() evals its single-quoted condition set -uo pipefail diff --git a/plugins/testing/scripts/resolve-config.test.sh b/plugins/testing/scripts/resolve-config.test.sh index 15aa1b0131..065977fb24 100755 --- a/plugins/testing/scripts/resolve-config.test.sh +++ b/plugins/testing/scripts/resolve-config.test.sh @@ -1,6 +1,7 @@ #!/usr/bin/env bash # Tests for resolve-config.sh: layer order, list concatenation, scalar # override, validation exits, hook coverage, and the one path-glob matcher. +# test-scope: plugins/testing/hooks/hooks.json plugins/testing/skills/audit/adapters/*.yaml # shellcheck disable=SC2016 # fence lines in fixtures are literal text set -uo pipefail unset GIT_DIR GIT_WORK_TREE GIT_CONFIG CLAUDE_PROJECT_DIR diff --git a/plugins/testing/skills/audit/evals/judge-calibration/metrics.test.sh b/plugins/testing/skills/audit/evals/judge-calibration/metrics.test.sh index 7b57cf7c65..da375c9a9f 100755 --- a/plugins/testing/skills/audit/evals/judge-calibration/metrics.test.sh +++ b/plugins/testing/skills/audit/evals/judge-calibration/metrics.test.sh @@ -11,6 +11,7 @@ # p = min(1, 2 * sum(comb(n, k) for k <= min(b, c)) / 2^n)), never with # these scripts. # Every interval prints to 4 decimals with z = 1.959964. +# test-scope: plugins/testing/skills/audit/adapters/*.yaml HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" # The judge hooks' fixtures: a temp dir, a `claude` on PATH that records any # real call, the stub judge behind TEST_JUDGE_CMD, and the ok/fail counters. diff --git a/plugins/testing/skills/audit/scripts/cant-fail-scan.test.sh b/plugins/testing/skills/audit/scripts/cant-fail-scan.test.sh index 7cfa073b73..8440fda914 100755 --- a/plugins/testing/skills/audit/scripts/cant-fail-scan.test.sh +++ b/plugins/testing/skills/audit/scripts/cant-fail-scan.test.sh @@ -4,6 +4,8 @@ # explicitly, so each is consumed by a grader (check-orphaned-fixtures.sh's # contract), and every script the driver loads is named as a whole token so the # affected-tests mapping reaches this suite from any of them. +# test-scope: plugins/testing/skills/audit/evals/fixtures/* +# test-scope: plugins/testing/hooks/hooks.json # shellcheck disable=SC2016 # single-quoted fixture text is literal shell source set -uo pipefail @@ -48,7 +50,7 @@ assert_matches() { # about the SHAPE of a field's value: a substring check on a prefix of that # value passes for every malformed value sharing the prefix, which is the # can't-fail shape this scanner exists to find. - if printf '%s\n' "$2" | LC_ALL=C grep -qE "$3"; then + if LC_ALL=C grep -qE "$3" <<<"$2"; then pass "$1" else fail "$1" "expected output to match ERE: $3" diff --git a/plugins/testing/skills/setup/scripts/setup.test.sh b/plugins/testing/skills/setup/scripts/setup.test.sh index ae331f3f2e..5f07605b2b 100755 --- a/plugins/testing/skills/setup/scripts/setup.test.sh +++ b/plugins/testing/skills/setup/scripts/setup.test.sh @@ -2,6 +2,7 @@ # Tests for setup.sh: check's four sections, lint findings, the consumer hook # entry, and an apply that writes only the docs convention file or .claude/testing.yaml. # test-scope: plugins/testing/skills/audit/adapters/*.yaml +# test-scope: plugins/testing/hooks/hooks.json # shellcheck disable=SC2016 # fence lines in fixtures are literal text set -uo pipefail unset GIT_DIR GIT_WORK_TREE GIT_CONFIG CLAUDE_PROJECT_DIR diff --git a/plugins/typos-format/hooks/typos-format.test.sh b/plugins/typos-format/hooks/typos-format.test.sh index 14ce3af5ae..9860ff5eb8 100755 --- a/plugins/typos-format/hooks/typos-format.test.sh +++ b/plugins/typos-format/hooks/typos-format.test.sh @@ -20,6 +20,7 @@ # # Requires a real typos binary: $TYPOS_TEST_BIN if set, else `typos` on PATH. # Without one the behavioral assertions cannot run, so the suite skips. +# test-scope: plugins/typos-format/.claude-plugin/plugin.json plugins/typos-format/hooks/hooks.json plugins/typos-format/prerequisites.json set -uo pipefail diff --git a/plugins/user-experience/.claude-plugin/plugin.json b/plugins/user-experience/.claude-plugin/plugin.json index e2defc06fd..53379ca18a 100644 --- a/plugins/user-experience/.claude-plugin/plugin.json +++ b/plugins/user-experience/.claude-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "user-experience", - "version": "0.1.0", + "version": "0.1.1", "description": "Front door for user experience on the app being built, at any stage from idea to legacy: shape detects the app's stage and the project's own research, personas and analytics, then chains user research planning, synthesis, flows and IA, and evaluation, routing to the tools installed, with every deliverable labeled evidence-based, assumption-based or mixed.", "author": { "name": "Melodic Software", diff --git a/plugins/user-experience/CHANGELOG.md b/plugins/user-experience/CHANGELOG.md index cbabbd2e7f..44e4122edc 100644 --- a/plugins/user-experience/CHANGELOG.md +++ b/plugins/user-experience/CHANGELOG.md @@ -3,6 +3,13 @@ All notable changes to the `user-experience` plugin are documented here. Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/); this plugin uses semantic versioning. +## [0.1.1] - 2026-10-10 + +### Changed + +- The `routing` and `detect` test suites declare the manifests and fixtures they read, so the + pull-request test selection runs them when those files change. Nothing the plugin ships changes. + ## [0.1.0] - 2026-10-09 ### Added diff --git a/plugins/user-experience/tests/detect.test.sh b/plugins/user-experience/tests/detect.test.sh index 94b7e9ef5c..1dda7ed164 100755 --- a/plugins/user-experience/tests/detect.test.sh +++ b/plugins/user-experience/tests/detect.test.sh @@ -1,4 +1,5 @@ #!/usr/bin/env bash +# test-scope: plugins/user-experience/tests/fixtures/* # Discovery wrapper for the user-experience detect suite: scripts/run-plugin-tests.sh # finds plugins/**/*.test.sh, so this runs detect.test.mjs. SKIPs (exit 0) without Node. set -uo pipefail diff --git a/plugins/user-experience/tests/routing.test.sh b/plugins/user-experience/tests/routing.test.sh index 9b33bb2e6a..438c3762db 100755 --- a/plugins/user-experience/tests/routing.test.sh +++ b/plugins/user-experience/tests/routing.test.sh @@ -1,4 +1,5 @@ #!/usr/bin/env bash +# test-scope: .claude-plugin/marketplace.json plugins/*/.claude-plugin/plugin.json # Discovery wrapper for the user-experience route-row suite: scripts/run-plugin-tests.sh # finds plugins/**/*.test.sh, so this runs routing.test.mjs. SKIPs (exit 0) without Node. set -uo pipefail diff --git a/plugins/user-interface/tests/detect.test.sh b/plugins/user-interface/tests/detect.test.sh index 468c0080ef..3e29739117 100755 --- a/plugins/user-interface/tests/detect.test.sh +++ b/plugins/user-interface/tests/detect.test.sh @@ -1,6 +1,7 @@ #!/usr/bin/env bash # Discovery wrapper: scripts/run-plugin-tests.sh finds plugins/**/*.test.sh, so # this hands off to the Node suite. SKIPs (exit 0) when Node is unavailable. +# test-scope: plugins/user-interface/tests/fixtures/*/package.json set -uo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" diff --git a/plugins/user-interface/tests/routing.test.sh b/plugins/user-interface/tests/routing.test.sh index c5204ca263..f7e5c5434f 100755 --- a/plugins/user-interface/tests/routing.test.sh +++ b/plugins/user-interface/tests/routing.test.sh @@ -1,4 +1,5 @@ #!/usr/bin/env bash +# test-scope: .claude-plugin/marketplace.json plugins/*/.claude-plugin/plugin.json # Discovery wrapper: scripts/run-plugin-tests.sh finds plugins/**/*.test.sh, so # this hands off to the Node suite. SKIPs (exit 0) when Node is unavailable. set -uo pipefail diff --git a/plugins/visualization/tests/present.test.sh b/plugins/visualization/tests/present.test.sh index 9d57a9d0a7..f00f551270 100755 --- a/plugins/visualization/tests/present.test.sh +++ b/plugins/visualization/tests/present.test.sh @@ -1,6 +1,7 @@ #!/usr/bin/env bash # Discovery wrapper: scripts/run-plugin-tests.sh finds plugins/**/*.test.sh, so # this hands off to the Node suite. SKIPs (exit 0) when Node is unavailable. +# test-scope: plugins/visualization/skills/present/SKILL.md set -uo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" diff --git a/plugins/wizard/skills/unattended/template.test.sh b/plugins/wizard/skills/unattended/template.test.sh index 343c48824f..ac5290a84c 100755 --- a/plugins/wizard/skills/unattended/template.test.sh +++ b/plugins/wizard/skills/unattended/template.test.sh @@ -1,5 +1,6 @@ #!/usr/bin/env bash # Behavioral tests for the unattended PowerShell library. +# test-scope: plugins/wizard/README.md set -uo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" diff --git a/plugins/work-items/skills/onboard-adapter/scripts/generate-adapter.test.sh b/plugins/work-items/skills/onboard-adapter/scripts/generate-adapter.test.sh index c0d0cbd7e0..3556978dae 100755 --- a/plugins/work-items/skills/onboard-adapter/scripts/generate-adapter.test.sh +++ b/plugins/work-items/skills/onboard-adapter/scripts/generate-adapter.test.sh @@ -1,4 +1,5 @@ #!/usr/bin/env bash +# test-scope: plugins/work-items/tools/work-item-tracker/adapters/* # shellcheck disable=SC2154 # FAILED/CASE_NUM initialized by the sourced lib # Contract tests for generate-adapter.sh — the deterministic half of # /work-items:onboard-adapter. diff --git a/plugins/work-items/tools/work-item-tracker/adapters/github/common.test.sh b/plugins/work-items/tools/work-item-tracker/adapters/github/common.test.sh index f5226428f8..ea47d08181 100755 --- a/plugins/work-items/tools/work-item-tracker/adapters/github/common.test.sh +++ b/plugins/work-items/tools/work-item-tracker/adapters/github/common.test.sh @@ -1,4 +1,5 @@ #!/usr/bin/env bash +# test-scope: .work-item-tracker.json # shellcheck disable=SC2154 # FAILED/CASE_NUM initialized by the sourced lib # common.sh is a sourceable contract lib — assert it sources cleanly and exposes # its public helpers (no --help contract; it is sourced, never invoked). diff --git a/plugins/work-items/tools/work-item-tracker/adapters/github/list-sub-items.test.sh b/plugins/work-items/tools/work-item-tracker/adapters/github/list-sub-items.test.sh index 9d218e4ff0..a1962fe12d 100755 --- a/plugins/work-items/tools/work-item-tracker/adapters/github/list-sub-items.test.sh +++ b/plugins/work-items/tools/work-item-tracker/adapters/github/list-sub-items.test.sh @@ -1,4 +1,5 @@ #!/usr/bin/env bash +# test-scope: .work-item-tracker.json # shellcheck disable=SC2154 # FAILED/CASE_NUM initialized by the sourced helper # Offline: the skill-script contract (--help), the pre-I/O usage-error paths, and # the subIssues + intersect path against a gh stub that reproduces gh's real diff --git a/plugins/work-items/tools/work-item-tracker/conformance/bindings/jira.test.sh b/plugins/work-items/tools/work-item-tracker/conformance/bindings/jira.test.sh index 26981e6765..6ec0b4bb03 100755 --- a/plugins/work-items/tools/work-item-tracker/conformance/bindings/jira.test.sh +++ b/plugins/work-items/tools/work-item-tracker/conformance/bindings/jira.test.sh @@ -3,6 +3,7 @@ # RUNS the full abstract suite against the consume-only jira adapter, once normally and # once under a PATH shim that makes gh/curl fail: every exercised path is pre-network. # test-scope: plugins/work-items/tools/work-item-tracker/adapters/jira/* +# test-scope: plugins/work-items/tools/work-item-tracker/lib/id.sh set -uo pipefail TMP_ROOT="$(mktemp -d)" diff --git a/plugins/work-items/tools/work-item-tracker/conformance/bindings/local-markdown.test.sh b/plugins/work-items/tools/work-item-tracker/conformance/bindings/local-markdown.test.sh index 34037bafc5..7f2831d240 100755 --- a/plugins/work-items/tools/work-item-tracker/conformance/bindings/local-markdown.test.sh +++ b/plugins/work-items/tools/work-item-tracker/conformance/bindings/local-markdown.test.sh @@ -3,6 +3,7 @@ # RUNS the full abstract conformance suite offline against the local-markdown adapter, # once normally and once under a PATH shim that makes gh/curl fail. # test-scope: plugins/work-items/tools/work-item-tracker/adapters/local-markdown/* +# test-scope: plugins/work-items/tools/work-item-tracker/lib/id.sh plugins/work-items/tools/work-item-tracker/lib/lease.sh set -uo pipefail TMP_ROOT="$(mktemp -d)" diff --git a/plugins/work-items/tools/work-item-tracker/work-item-tracker.test.sh b/plugins/work-items/tools/work-item-tracker/work-item-tracker.test.sh index c731173825..885281c3be 100755 --- a/plugins/work-items/tools/work-item-tracker/work-item-tracker.test.sh +++ b/plugins/work-items/tools/work-item-tracker/work-item-tracker.test.sh @@ -2,6 +2,7 @@ # Tests for the core dispatcher: usage, binding resolution, capability gating, and # list-frontier derivation — all against a fake adapter (no network, no gh). # test-scope: plugins/work-items/tools/work-item-tracker/adapters/local-markdown/* +# test-scope: plugins/work-items/tools/work-item-tracker/lib/id.sh plugins/work-items/tools/work-item-tracker/lib/lease.sh set -uo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" diff --git a/scripts/affected-tests-live.sh b/scripts/affected-tests-live.sh new file mode 100755 index 0000000000..0fe564b234 --- /dev/null +++ b/scripts/affected-tests-live.sh @@ -0,0 +1,229 @@ +#!/usr/bin/env bash +# The cases of scripts/affected-tests.test.sh that must run against the LIVE +# repository: the derived shared-lib copy sets, real fan-outs, and the probes +# discovered in the tree. Each runs the selector over the whole tree, so no test +# selection can say when it must run; the lint-repo job of +# .github/workflows/pr-require-checks.yml runs this on every pull request. +# +# Usage: scripts/affected-tests-live.sh (from anywhere; exit 0 all pass, 1 a failure) +set -uo pipefail + +SELF_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +REPO_ROOT="$(cd "$SELF_DIR/.." && pwd)" +# shellcheck source=lib/test-harness.sh +. "$SELF_DIR/lib/test-harness.sh" + +# Captured output is matched in-shell, never piped into an early-exit reader. +has_line() { [[ $'\n'$1$'\n' == *$'\n'"$2"$'\n'* ]]; } +contains() { [[ $1 == *"$2"* ]]; } + +contract_suite=scripts/validate-plugin-contracts.test.sh + +# --- LIVE repo: the derived copy set equals the published manifest today --- +# The synthetic cases above prove the mechanism; this one proves it is still +# wired to THIS repository, which is what rots. The oracle is each script's +# --print-manifest surface (not a transcription of src=/copies=( scraping), so +# renaming those variables cannot make this assertion go green for the wrong +# reason. +for src in lib/hook-utils.sh lib/parse-concern-value.sh docs/conventions/standards/README.md; do + derived="$(cd "$REPO_ROOT" && bash scripts/affected-tests.sh --print-fanout "$src" 2>/dev/null | sort)" + manifest="scripts/sync-shared-copies.sh" + expected="$(cd "$REPO_ROOT" && bash "$manifest" --print-manifest | awk -F '\t' -v s="$src" '$1=="src"{on=($2==s)} on && $1=="copy" && $2!=""{print $2}' | while IFS= read -r pat; do + if [[ -e "$pat" ]]; then + printf '%s\n' "$pat" + else + # shellcheck disable=SC2086 # a published entry may still be a glob. + for m in $pat; do [[ -e "$m" ]] && printf '%s\n' "$m"; done + fi + done | sort)" + if [[ -n "$derived" && "$derived" == "$expected" ]]; then + ok "live derivation for $src matches $manifest ($(printf '%s\n' "$derived" | wc -l | tr -d ' ') copies)" + else + fail "live derivation drifted for $src: derived=[$derived] expected=[$expected]" + fi +done + +# Every live sync-*.sh (except the test helper) must implement the surface. +# The publisher's stdout is captured, then matched in-shell with no pipe: an +# early-exit reader on a pipe can kill a still-writing producer with SIGPIPE, +# which this script's pipefail then reports as a failed assertion. +live_missing=0 +for manifest in "$REPO_ROOT"/scripts/sync-*.sh; do + case "$manifest" in + *.test.sh) continue ;; + *) ;; + esac + live_out="" + live_out="$(bash "$manifest" --print-manifest)" || { + fail "live $manifest --print-manifest exited non-zero" + live_missing=1 + continue + } + if [[ $'\n'$live_out != *$'\n'src$'\t'* ]]; then + fail "live $manifest --print-manifest did not emit a src line" + live_missing=1 + fi +done +if [[ "$live_missing" -eq 0 ]]; then + ok "every live scripts/sync-*.sh implements --print-manifest" +fi + +# --- LIVE repo: a real shared-lib change reaches a real carrying plugin ---- +out="$(cd "$REPO_ROOT" && bash scripts/affected-tests.sh lib/hook-utils.sh 2>/dev/null)" +RC=$? +if [[ "$RC" -eq 0 ]] && + has_line "$out" lib/hook-utils.test.sh && + has_line "$out" plugins/guardrails/hooks/block-no-verify.test.sh && + has_line "$out" plugins/eol-normalizer/hooks/eol-normalizer.test.sh; then + ok "live shared-lib change reaches suites in more than one carrying plugin" +else + fail "live hook-utils fan-out (rc=$RC): $out" +fi + +# --- LIVE repo: a STRUCTURAL basename that is also a sync src ------------- +# docs/conventions/standards/README.md is both. R3/R4 must ignore the basename +# (every plugin has a README.md, so the match carries no signal) while R5 must +# still fan the change out to the differently-named copies the manifest +# declares — plugins/*/reference/standards-contract.md — and on to the suites +# that bind against them. Neither the synthetic fixture nor the copy-set +# assertions above isolate that interaction. +out="$(cd "$REPO_ROOT" && bash scripts/affected-tests.sh docs/conventions/standards/README.md 2>/dev/null)" +RC=$? +if [[ "$RC" -eq 0 ]] && + has_line "$out" plugins/planning/tests/standards-binding.test.sh && + has_line "$out" plugins/review/tests/standards-binding.test.sh; then + ok "a structural basename that is also a sync src still fans out" +else + fail "standards-contract fan-out (rc=$RC): $out" +fi + +# ... while YAML under .github/, which actionlint/zizmor/check-jsonschema DO +# read, stays covered by the .github/* entry. Without this, the cases above +# would pass just as well if someone deleted .github/* too — and since the +# probe here is itself a *.yaml, it is the direct evidence that dropping the +# bare *.yaml entry did not orphan the one YAML that had a real lane. +# +# Asserted as a SILENT no-suite exit (rc 0 AND an empty selection), not merely +# rc 0: a path that some suite happens to name is selected by R3 long before the +# no-suite list is consulted, which would pass a bare rc-0 check while proving +# nothing about .github/*. That is not hypothetical — .github/workflows/pr-require-checks.yml +# is named by two suites and reaches exit 0 through R3, so it cannot serve as +# this probe. Discovered by glob, never spelled: a path spelled here would make +# this script name it, and R3 would select through it the same way. +# +# The candidate is additionally FILTERED to one that no grepped-language file +# names at all, rather than assuming the sole `.github/*.yaml` qualifies: a +# file some suite reaches through R3 reaches exit 0 the way +# pr-require-checks.yml does, so it cannot serve as this probe either. +# +# The filter is an INDEPENDENT oracle (a direct git grep for the basename), not +# a call to affected-tests.sh: picking the probe with the tool under test would +# make the assertion below tautological. One incoming reference anywhere is +# enough to disqualify a candidate, because R3's first level would then have +# somewhere to go. +mapfile -t wf_candidates < <(cd "$REPO_ROOT" && git ls-files '.github/*.yaml' '.github/*.yml') +wf_yaml=() +for c in ${wf_candidates[@]+"${wf_candidates[@]}"}; do + # scripts/workflow-self-paths.test.sh declares `.github/workflows/*` (R8), so + # a workflow file selects that suite and cannot be the probe. + [[ "$c" == .github/workflows/* ]] && continue + if ! (cd "$REPO_ROOT" && git grep -q -F -- "${c##*/}" \ + -- '*.sh' '*.bash' '*.js' '*.mjs' '*.cjs' '*.py' '*.ps1' '*.psm1') 2>/dev/null; then + wf_yaml=("$c") + break + fi +done +if [[ ${#wf_yaml[@]} -eq 0 ]]; then + fail "no unreferenced .github YAML found to probe — the case below would be vacuous" +fi +for y in ${wf_yaml[@]+"${wf_yaml[@]}"}; do + out="$(cd "$REPO_ROOT" && bash scripts/affected-tests.sh "$y" 2>/dev/null)" + RC=$? + if [[ "$RC" -eq 0 && -z "$out" ]]; then + ok ".github YAML stays a recorded no-suite class via .github/*: $y" + else + fail ".github YAML should exit 0 with no suites (rc=$RC): $out" + fi +done + +# --- LIVE repo: the false coverage that masked a real suite ---------------- +# babysit_lease.py's suite is scripts/tests/test_babysit_lease.py, and nothing +# in the corpus spells that filename: every consumer says `import +# babysit_lease`. Under the old substring lookup the file still came back +# MAPPED, at exit 0, with five suites — none of them its own — purely because +# `babysit_lease.py` is a substring of `manage_babysit_lease.py`. That is the +# masked-zero-coverage shape the boundary rule exists to expose, and exposing it +# is only half the fix: the co-located rule then has to find the real suite by +# path, or a covered file reports UNMAPPED. +out="$(cd "$REPO_ROOT" && bash scripts/affected-tests.sh plugins/source-control/skills/babysit-prs/scripts/babysit_lease.py 2>/dev/null)" +RC=$? +if [[ "$RC" -eq 0 ]] && + has_line "$out" plugins/source-control/skills/babysit-prs/scripts/tests/test_babysit_lease.py; then + ok "live: a .py whose only suite sits in tests/ maps to that suite" +else + fail "live tests/ subdirectory suite (rc=$RC): $out" +fi + +# --- LIVE repo: a co-located change stays narrow --------------------------- +out="$(cd "$REPO_ROOT" && bash scripts/affected-tests.sh plugins/eol-normalizer/hooks/eol-normalizer.sh 2>/dev/null)" +RC=$? +count="$(printf '%s\n' "$out" | grep -c '.')" +if [[ "$RC" -eq 0 ]] && has_line "$out" plugins/eol-normalizer/hooks/eol-normalizer.test.sh && [[ "$count" -lt 10 ]]; then + ok "live co-located change selects a narrow set ($count suites)" +else + fail "live co-located selection too wide or wrong (rc=$RC, count=$count): $out" +fi + +# --- LIVE repo: the false-UNMAPPED regression this all exists to fix ------- +# hook_telemetry.py sits directly beside test_hook_telemetry.py and was still +# reported UNMAPPED. Asserted against the LIVE repo, not a fixture: the point is +# that the tool tracks THIS corpus's real conventions. +out="$(cd "$REPO_ROOT" && bash scripts/affected-tests.sh plugins/disk-hygiene/lib/hook_telemetry.py 2>/dev/null)" +RC=$? +if [[ "$RC" -eq 0 ]] && has_line "$out" plugins/disk-hygiene/lib/test_hook_telemetry.py; then + ok "live: a .py beside its test_.py is no longer UNMAPPED" +else + fail "live python co-located selection (rc=$RC): $out" +fi + +# --- LIVE repo: a real autonomy reference doc selects the contract suite ----- +# The probe doc is discovered, never spelled: a basename written here would make +# this script name that file, and R3 would then cover it without R7. The suite +# path above IS spelled on purpose: renaming the suite fails this case instead +# of R7 silently selecting nothing. +# The assertion is on the R7 reason, not on bare selection: a live doc can also +# reach the suite through R4 fan-out (a hook naming it), which would pass without +# R7. The seed is walked first, so R7's reason is the one recorded. +# The exit status of this head pipe is never read, so its early exit is harmless. +live_ref="$(cd "$REPO_ROOT" && git ls-files 'plugins/autonomy/reference/*.md' | head -n 1)" +if [[ -z "$live_ref" ]]; then + fail "LIVE R7: no tracked plugins/autonomy/reference/*.md to probe" +else + out="$(cd "$REPO_ROOT" && bash scripts/affected-tests.sh --explain "$live_ref" 2>&1)" + RC=$? + if [[ "$RC" -eq 0 ]] && contains "$out" "select: $contract_suite (path class:"; then + ok "LIVE R7: $live_ref selects the plugin-contract suite through R7" + else + fail "LIVE R7: $live_ref did not select the plugin-contract suite through R7 (rc=$RC): $out" + fi +fi + +# --- LIVE repo: the two suite breaks only a full main run caught -------------- +# Both were a skill body edit breaking a suite that never spells the body's +# path the plain way: one scans its plugin's markdown (R8 declares it), the +# other spells the body relative to its plugin (AMBIGUOUS NAMES resolves it). +# The probe bodies are discovered by glob, so renaming or deleting either fails +# this case. +for probe in 'plugins/github/skills/advise/S*.md|plugins/github/github.test.sh' \ + 'plugins/planning/skills/interview/S*.md|plugins/planning/tests/interview-defenses.test.sh'; do + body="$(cd "$REPO_ROOT" && git ls-files "${probe%%|*}")" + out="$(cd "$REPO_ROOT" && bash scripts/affected-tests.sh "$body" 2>/dev/null)" + RC=$? + if [[ -n "$body" && "$RC" -eq 0 ]] && has_line "$out" "${probe#*|}"; then + ok "LIVE: $body selects ${probe#*|}" + else + fail "LIVE: '$body' did not select ${probe#*|} (rc=$RC): $out" + fi +done + +test_harness::report diff --git a/scripts/affected-tests.test.sh b/scripts/affected-tests.test.sh index 7d86923b97..c5c2fe2bc2 100755 --- a/scripts/affected-tests.test.sh +++ b/scripts/affected-tests.test.sh @@ -4,13 +4,11 @@ # # Most scenarios build a throwaway git repo carrying a MINIATURE version of this # repo's own shape: a shared lib with a sync manifest, two carrying plugins, and -# co-located suites. The exceptions are the two cases that must be proved -# against the LIVE repo — the derived shared-lib copy set and the real no-suite -# list — because a synthetic fixture cannot show that the derivation still -# tracks reality, which is the whole failure mode this tool exists to avoid. -# test-scope: scripts/affected-tests* scripts/sync-*.sh .github/workflows/pr-require-checks.yml -# test-scope: plugins/github/skills/advise/S*.md plugins/planning/skills/interview/S*.md -# test-scope: plugins/autonomy/reference/*.md +# co-located suites. The cases that must be proved against the LIVE repository +# (the derived shared-lib copy sets, real fan-outs and discovered probes) run the +# selector over the whole tree, so they live in scripts/affected-tests-live.sh, +# which the lint-repo job runs on every pull request. +# test-scope: scripts/affected-tests* .github/workflows/pr-require-checks.yml set -uo pipefail TMP_ROOT="$(mktemp -d)" @@ -688,55 +686,6 @@ else fi rm -rf "$repo" "$marker" -# --- LIVE repo: the derived copy set equals the published manifest today --- -# The synthetic cases above prove the mechanism; this one proves it is still -# wired to THIS repository, which is what rots. The oracle is each script's -# --print-manifest surface (not a transcription of src=/copies=( scraping), so -# renaming those variables cannot make this assertion go green for the wrong -# reason. -for src in lib/hook-utils.sh lib/parse-concern-value.sh docs/conventions/standards/README.md; do - derived="$(cd "$REPO_ROOT" && bash scripts/affected-tests.sh --print-fanout "$src" 2>/dev/null | sort)" - manifest="scripts/sync-shared-copies.sh" - expected="$(cd "$REPO_ROOT" && bash "$manifest" --print-manifest | awk -F '\t' -v s="$src" '$1=="src"{on=($2==s)} on && $1=="copy" && $2!=""{print $2}' | while IFS= read -r pat; do - if [[ -e "$pat" ]]; then - printf '%s\n' "$pat" - else - # shellcheck disable=SC2086 # a published entry may still be a glob. - for m in $pat; do [[ -e "$m" ]] && printf '%s\n' "$m"; done - fi - done | sort)" - if [[ -n "$derived" && "$derived" == "$expected" ]]; then - ok "live derivation for $src matches $manifest ($(printf '%s\n' "$derived" | wc -l | tr -d ' ') copies)" - else - fail "live derivation drifted for $src: derived=[$derived] expected=[$expected]" - fi -done - -# Every live sync-*.sh (except the test helper) must implement the surface. -# The publisher's stdout is captured, then matched in-shell with no pipe: an -# early-exit reader on a pipe can kill a still-writing producer with SIGPIPE, -# which this suite's pipefail then reports as a failed assertion. -live_missing=0 -for manifest in "$REPO_ROOT"/scripts/sync-*.sh; do - case "$manifest" in - *.test.sh) continue ;; - *) ;; - esac - live_out="" - live_out="$(bash "$manifest" --print-manifest)" || { - fail "live $manifest --print-manifest exited non-zero" - live_missing=1 - continue - } - if [[ $'\n'$live_out != *$'\n'src$'\t'* ]]; then - fail "live $manifest --print-manifest did not emit a src line" - live_missing=1 - fi -done -if [[ "$live_missing" -eq 0 ]]; then - ok "every live scripts/sync-*.sh implements --print-manifest" -fi - # --- renaming src/copies in the publisher does not change fan-out ---------- # The old consumer scraped those spellings out of source text. A publisher # that never uses them, but still implements --print-manifest, must fan out @@ -795,35 +744,6 @@ else ok "affected-tests.sh no longer scrapes src=/copies=( source text" fi -# --- LIVE repo: a real shared-lib change reaches a real carrying plugin ---- -out="$(cd "$REPO_ROOT" && bash scripts/affected-tests.sh lib/hook-utils.sh 2>/dev/null)" -RC=$? -if [[ "$RC" -eq 0 ]] && - has_line "$out" lib/hook-utils.test.sh && - has_line "$out" plugins/guardrails/hooks/block-no-verify.test.sh && - has_line "$out" plugins/eol-normalizer/hooks/eol-normalizer.test.sh; then - ok "live shared-lib change reaches suites in more than one carrying plugin" -else - fail "live hook-utils fan-out (rc=$RC): $out" -fi - -# --- LIVE repo: a STRUCTURAL basename that is also a sync src ------------- -# docs/conventions/standards/README.md is both. R3/R4 must ignore the basename -# (every plugin has a README.md, so the match carries no signal) while R5 must -# still fan the change out to the differently-named copies the manifest -# declares — plugins/*/reference/standards-contract.md — and on to the suites -# that bind against them. Neither the synthetic fixture nor the copy-set -# assertions above isolate that interaction. -out="$(cd "$REPO_ROOT" && bash scripts/affected-tests.sh docs/conventions/standards/README.md 2>/dev/null)" -RC=$? -if [[ "$RC" -eq 0 ]] && - has_line "$out" plugins/planning/tests/standards-binding.test.sh && - has_line "$out" plugins/review/tests/standards-binding.test.sh; then - ok "a structural basename that is also a sync src still fans out" -else - fail "standards-contract fan-out (rc=$RC): $out" -fi - # --- LIVE no-suite list: reference YAML with no lane is UNMAPPED ------------- # The reference YAML under plugins/toolchain/ and docs/conventions/ # ecosystem-commands/ is read by NO lane: no yamllint step exists, every @@ -863,83 +783,6 @@ for y in "${eco_yaml[@]}"; do done rm -rf "$repo" -# ... while YAML under .github/, which actionlint/zizmor/check-jsonschema DO -# read, stays covered by the .github/* entry. Without this, the cases above -# would pass just as well if someone deleted .github/* too — and since the -# probe here is itself a *.yaml, it is the direct evidence that dropping the -# bare *.yaml entry did not orphan the one YAML that had a real lane. -# -# Asserted as a SILENT no-suite exit (rc 0 AND an empty selection), not merely -# rc 0: a path that some suite happens to name is selected by R3 long before the -# no-suite list is consulted, which would pass a bare rc-0 check while proving -# nothing about .github/*. That is not hypothetical — .github/workflows/pr-require-checks.yml -# is named by two suites and reaches exit 0 through R3, so it cannot serve as -# this probe. Discovered by glob, never spelled: a path spelled here would make -# this suite name it, and R3 would select this suite the same way. -# -# The candidate is additionally FILTERED to one that no grepped-language file -# names at all, rather than assuming the sole `.github/*.yaml` qualifies: a -# file some suite reaches through R3 reaches exit 0 the way -# pr-require-checks.yml does, so it cannot serve as this probe either. -# -# The filter is an INDEPENDENT oracle (a direct git grep for the basename), not -# a call to affected-tests.sh: picking the probe with the tool under test would -# make the assertion below tautological. One incoming reference anywhere is -# enough to disqualify a candidate, because R3's first level would then have -# somewhere to go. -mapfile -t wf_candidates < <(cd "$REPO_ROOT" && git ls-files '.github/*.yaml' '.github/*.yml') -wf_yaml=() -for c in ${wf_candidates[@]+"${wf_candidates[@]}"}; do - # scripts/workflow-self-paths.test.sh declares `.github/workflows/*` (R8), so - # a workflow file selects that suite and cannot be the probe. - [[ "$c" == .github/workflows/* ]] && continue - if ! (cd "$REPO_ROOT" && git grep -q -F -- "${c##*/}" \ - -- '*.sh' '*.bash' '*.js' '*.mjs' '*.cjs' '*.py' '*.ps1' '*.psm1') 2>/dev/null; then - wf_yaml=("$c") - break - fi -done -if [[ ${#wf_yaml[@]} -eq 0 ]]; then - fail "no unreferenced .github YAML found to probe — the case below would be vacuous" -fi -for y in ${wf_yaml[@]+"${wf_yaml[@]}"}; do - out="$(cd "$REPO_ROOT" && bash scripts/affected-tests.sh "$y" 2>/dev/null)" - RC=$? - if [[ "$RC" -eq 0 && -z "$out" ]]; then - ok ".github YAML stays a recorded no-suite class via .github/*: $y" - else - fail ".github YAML should exit 0 with no suites (rc=$RC): $out" - fi -done - -# --- LIVE repo: the false coverage that masked a real suite ---------------- -# babysit_lease.py's suite is scripts/tests/test_babysit_lease.py, and nothing -# in the corpus spells that filename: every consumer says `import -# babysit_lease`. Under the old substring lookup the file still came back -# MAPPED, at exit 0, with five suites — none of them its own — purely because -# `babysit_lease.py` is a substring of `manage_babysit_lease.py`. That is the -# masked-zero-coverage shape the boundary rule exists to expose, and exposing it -# is only half the fix: the co-located rule then has to find the real suite by -# path, or a covered file reports UNMAPPED. -out="$(cd "$REPO_ROOT" && bash scripts/affected-tests.sh plugins/source-control/skills/babysit-prs/scripts/babysit_lease.py 2>/dev/null)" -RC=$? -if [[ "$RC" -eq 0 ]] && - has_line "$out" plugins/source-control/skills/babysit-prs/scripts/tests/test_babysit_lease.py; then - ok "live: a .py whose only suite sits in tests/ maps to that suite" -else - fail "live tests/ subdirectory suite (rc=$RC): $out" -fi - -# --- LIVE repo: a co-located change stays narrow --------------------------- -out="$(cd "$REPO_ROOT" && bash scripts/affected-tests.sh plugins/eol-normalizer/hooks/eol-normalizer.sh 2>/dev/null)" -RC=$? -count="$(printf '%s\n' "$out" | grep -c '.')" -if [[ "$RC" -eq 0 ]] && has_line "$out" plugins/eol-normalizer/hooks/eol-normalizer.test.sh && [[ "$count" -lt 10 ]]; then - ok "live co-located change selects a narrow set ($count suites)" -else - fail "live co-located selection too wide or wrong (rc=$RC, count=$count): $out" -fi - # --- per-ecosystem suite naming -------------------------------------------- # The selector was shell-only: R2 looked for .test.sh and nothing else, # and the reverse lookup searched `-- '*.sh'`. Every one of the ~180 non-shell @@ -1223,18 +1066,6 @@ else fail "--run should exit 3 naming the unrun suite (rc=$RC): $OUT" fi -# --- LIVE repo: the false-UNMAPPED regression this all exists to fix ------- -# hook_telemetry.py sits directly beside test_hook_telemetry.py and was still -# reported UNMAPPED. Asserted against the LIVE repo, not a fixture: the point is -# that the tool tracks THIS corpus's real conventions. -out="$(cd "$REPO_ROOT" && bash scripts/affected-tests.sh plugins/disk-hygiene/lib/hook_telemetry.py 2>/dev/null)" -RC=$? -if [[ "$RC" -eq 0 ]] && has_line "$out" plugins/disk-hygiene/lib/test_hook_telemetry.py; then - ok "live: a .py beside its test_.py is no longer UNMAPPED" -else - fail "live python co-located selection (rc=$RC): $out" -fi - # --- crossing once does not stop the walk in the destination language ------ # The cap is one language TRANSITION per path, not one hop. A weaker # "stop dead after crossing" rule looks equivalent and is not: helper.py -> @@ -1593,30 +1424,6 @@ for p in plugins/beta/reference/other-doc.md plugins/autonomy/README.md; do done rm -rf "$repo" -# --- LIVE repo: a real autonomy reference doc selects the contract suite ----- -# The probe doc is discovered, never spelled: a basename written here would make -# this suite name that file, and R3 would then cover it without R7. The glob is -# declared in this suite's test-scope header, so deleting the last doc selects -# this suite. The suite path above IS spelled on purpose: renaming the suite -# selects this file, and this case then fails instead of R7 silently selecting -# nothing. -# The assertion is on the R7 reason, not on bare selection: a live doc can also -# reach the suite through R4 fan-out (a hook naming it), which would pass without -# R7. The seed is walked first, so R7's reason is the one recorded. -# The exit status of this head pipe is never read, so its early exit is harmless. -live_ref="$(cd "$REPO_ROOT" && git ls-files 'plugins/autonomy/reference/*.md' | head -n 1)" -if [[ -z "$live_ref" ]]; then - fail "LIVE R7: no tracked plugins/autonomy/reference/*.md to probe" -else - out="$(cd "$REPO_ROOT" && bash scripts/affected-tests.sh --explain "$live_ref" 2>&1)" - RC=$? - if [[ "$RC" -eq 0 ]] && contains "$out" "select: $contract_suite (path class:"; then - ok "LIVE R7: $live_ref selects the plugin-contract suite through R7" - else - fail "LIVE R7: $live_ref did not select the plugin-contract suite through R7 (rc=$RC): $out" - fi -fi - # --- R8: a declared scope selects the suite that scans a directory ----------- # A suite that greps or globs a directory never spells the files it reads, so # it declares them in `# test-scope:` lines of its leading comment block, and a @@ -1761,24 +1568,6 @@ else fi rm -rf "$repo" -# --- LIVE repo: the two suite breaks only a full main run caught -------------- -# Both were a skill body edit breaking a suite that never spells the body's -# path the plain way: one scans its plugin's markdown (R8 declares it), the -# other spells the body relative to its plugin (AMBIGUOUS NAMES resolves it). -# The probe bodies are discovered by glob and declared in this suite's -# test-scope header, so renaming or deleting either selects this suite. -for probe in 'plugins/github/skills/advise/S*.md|plugins/github/github.test.sh' \ - 'plugins/planning/skills/interview/S*.md|plugins/planning/tests/interview-defenses.test.sh'; do - body="$(cd "$REPO_ROOT" && git ls-files "${probe%%|*}")" - out="$(cd "$REPO_ROOT" && bash scripts/affected-tests.sh "$body" 2>/dev/null)" - RC=$? - if [[ -n "$body" && "$RC" -eq 0 ]] && has_line "$out" "${probe#*|}"; then - ok "LIVE: $body selects ${probe#*|}" - else - fail "LIVE: '$body' did not select ${probe#*|} (rc=$RC): $out" - fi -done - # --- ambiguous names: a shared basename counts only where it resolves ------ # Skills reuse reference names freely, so a bare `probe-doc.md` says nothing # about which one. A mention names a file only when it resolves to it: a path diff --git a/scripts/check-detector-eval-coverage.test.sh b/scripts/check-detector-eval-coverage.test.sh index a1e06dbf7e..e10cc15f35 100755 --- a/scripts/check-detector-eval-coverage.test.sh +++ b/scripts/check-detector-eval-coverage.test.sh @@ -1562,27 +1562,9 @@ else fi rm -rf "$root" -# ====================== the built-in registry resolves ===================== -# The shipped rows are paths into this repository, and a renamed or moved -# surface must not sit undetected behind an env override every test uses. This -# asserts only that the rows RESOLVE (exit 0 or 1), never what they report: -# the coverage verdict belongs to the tree, and asserting it here would make -# this suite red for a backfill in flight. -out="$(cd "$SELF_DIR/.." && bash scripts/check-detector-eval-coverage.sh --check 2>&1)" -rc=$? -if [[ $rc -eq 0 || $rc -eq 1 ]]; then - ok "the built-in registry rows resolve against this tree (exit $rc, not an environment answer)" -else - fail "built-in registry does not resolve: rc=$rc out='$out'" -fi - -# The live tree's own qualifying pairs are all registered, so the stopping rule -# reports nothing against it. This is the assertion that turns "the registry is -# complete today" into a standing fact rather than a one-time audit. -if [[ "$out" != *"UNREGISTERED PAIR"* ]]; then - ok "no qualifying detector-plus-evals skill in this tree is missing a registry row" -else - fail "an unregistered qualifying pair exists in this tree: $out" -fi +# The built-in registry is checked against the live tree by the +# detector_eval_coverage gate step of pr-require-checks.yml, which runs --check +# whenever a skill script or eval set changes and fails on an unresolved row or +# an unregistered pair. Run here, that check reads every skill script of the tree. test_harness::report diff --git a/scripts/check-fixture-git-isolation.test.sh b/scripts/check-fixture-git-isolation.test.sh index 76d2508990..f770827ce7 100755 --- a/scripts/check-fixture-git-isolation.test.sh +++ b/scripts/check-fixture-git-isolation.test.sh @@ -1121,13 +1121,8 @@ else fail "heredoc fixture-only: rc=$rc out='$out'" fi -# --- the LIVE corpus is clean against its own baseline ------------------------ -out="$(cd "$ROOT" && bash scripts/check-fixture-git-isolation.sh 2>&1)" -rc=$? -if [[ $rc -eq 0 ]]; then - ok "live corpus passes against scripts/$BASELINE_NAME" -else - fail "live corpus: rc=$rc out='$out'" -fi +# The live corpus against its baseline is the "Check fixture git isolation" +# step of lint-shell, which runs whenever a shell or Python file changes; run +# here, it read every shell and Python file of the tree. test_harness::report diff --git a/scripts/check-guardrails-ps-differential.test.sh b/scripts/check-guardrails-ps-differential.test.sh index b7d07bc5a2..120d7d5c72 100755 --- a/scripts/check-guardrails-ps-differential.test.sh +++ b/scripts/check-guardrails-ps-differential.test.sh @@ -5,6 +5,7 @@ # refuses must fail the check wherever it was planted (a consumer, the shared # classifier library, the Bash row's argv, or a token-only path), and a # branch-only refusal must not. +# test-scope: plugins/guardrails/* # shellcheck disable=SC2016 # planted source lines are literal shell set -uo pipefail diff --git a/scripts/check-hooks-description.test.sh b/scripts/check-hooks-description.test.sh index cbb0c358d0..ebeffdba88 100755 --- a/scripts/check-hooks-description.test.sh +++ b/scripts/check-hooks-description.test.sh @@ -1,6 +1,7 @@ #!/usr/bin/env bash # Tests for scripts/check-hooks-description.sh: a fixture tree per case so the -# verdict is on the shape under test and nothing else, then the live tree. +# verdict is on the shape under test and nothing else. The live tree is the +# gate's own CI step (pr-require-checks.yml, step id hooks_description). set -uo pipefail SELF_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" @@ -151,11 +152,4 @@ else fi rm -rf "$f" -# --- the live tree ---------------------------------------------------------- -if out="$(cd "$SELF_DIR/.." && bash scripts/check-hooks-description.sh 2>&1)"; then - ok "the repository's own hooks.json files all carry a description" -else - fail "the live tree fails the gate: $out" -fi - test_harness::report diff --git a/scripts/check-lane-coverage.test.sh b/scripts/check-lane-coverage.test.sh index 75cb357def..faec4f18b0 100755 --- a/scripts/check-lane-coverage.test.sh +++ b/scripts/check-lane-coverage.test.sh @@ -11,7 +11,8 @@ # names steps of the real pr-require-checks.yml, and an entry naming a step no fixture defines # is a stale opt-out by construction — so a fixture checked against it would fail # for a reason that has nothing to do with the case under test. -# test-scope: .github/workflows/pr-require-checks.yml +# The real workflow is the gate's own CI step (pr-require-checks.yml, step id +# lane_coverage), which runs on every pull request. set -uo pipefail ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" @@ -474,11 +475,4 @@ expect "a check name a peer workflow also carries fails and names it" 1 \ expect "a missing peer directory exits 2" 2 "peer workflow directory not found" \ --check "$peers/ci.yml" ci-status "$NONE" "$scratch/no-such-dir" -# --- the real workflow ------------------------------------------------------ - -expect "the repository's own pr-require-checks.yml is fully covered" 0 "reachable from ci-status.needs" --check -expect "no pr-require-checks.yml check name is shared with another workflow here" 0 "no check name shared with" --check -expect "every gate step in the repository's own pr-require-checks.yml is fed or opted out" 0 \ - "gate step(s) fed to the aggregator" --check - test_harness::report diff --git a/scripts/check-loop-lane-floor-drift.test.sh b/scripts/check-loop-lane-floor-drift.test.sh index fa0efd0af8..d9defc408c 100755 --- a/scripts/check-loop-lane-floor-drift.test.sh +++ b/scripts/check-loop-lane-floor-drift.test.sh @@ -67,12 +67,14 @@ VALUES_CONSUMERS=( # the fixture tree has to satisfy both registries. The paths are read back out # of the SUT rather than copied here: a second hand-maintained list would be # free to disagree with the one under test, which is the defect class this -# whole gate is about. -DATA_CARRIER_PATHS=() -while read -r _kind _tag dc_path _rest; do - [[ -n "${dc_path:-}" ]] || continue - DATA_CARRIER_PATHS+=("$dc_path") -done <<<"$(bash "$SUT" --list 2>/dev/null | grep '^carrier exempt' || true)" +# whole gate is about. They come from the SUT's DATA_CARRIERS array, not from +# its --list, which scans every file of the live tree to find the carriers. +mapfile -t DATA_CARRIER_PATHS < <(awk ' + /^DATA_CARRIERS=\(/ { f = 1; next } + f && /^\)/ { exit } + f { sub(/^[[:blank:]]*"/, ""); sub(/ .*/, ""); if ($0 != "") print } +' "$SUT") +((${#DATA_CARRIER_PATHS[@]})) || fail "no DATA_CARRIERS entry read from $SUT" # The floor block as the fixtures carry it. Short stand-ins for the real # bullets: this suite tests the gate's comparison, not the guard's values. @@ -354,23 +356,10 @@ else fail "missing source should exit 2 (rc=$rc): $out" fi -# --- 16. Every registered path exists in the live repository --------------- -# The registry is hand-maintained, so the live tree is where a rename shows up. -# This is the same stale-guard idiom the sibling list-backed gates use, run -# against the real repo rather than a fixture. - -live="$(bash "$SUT" --list 2>&1)" -rc=$? -missing=() -while read -r kind _mode path; do - [[ "$kind" == consumer ]] || continue - [[ -r "$SCRIPT_DIR/../$path" ]] || missing+=("$path") -done <<<"$live" -if ((rc == 0)) && ((${#missing[@]} == 0)); then - ok "every registered consumer exists in this checkout" -else - fail "registered consumer(s) missing from the checkout: ${missing[*]-} (rc=$rc)" -fi +# Cases 16, 26 and 28 checked the live registry for stale consumers and data +# carriers and the live tree for an unregistered copy. `--check` fails on all +# three, and lint-repo runs it on every pull request; run here, they read every +# file of the live tree. # --- 17. An unregistered file carrying the floor fails --------------------- # The registry alone only ever looks where it is told, so a seventh consumer @@ -492,25 +481,6 @@ else fail "drifted self-exempting copy must fail (rc=$rc): $out" fi -# --- 26. A data-carrier listing that no longer carries the floor is stale -- -# Same stale-guard as the consumer registry: an exemption must not outlive what -# it excuses. Asserted against the live registry, since DATA_CARRIERS lives in -# the gate and the fixture tree cannot hold an entry for a path of its own. - -live="$(bash "$SUT" --list 2>&1)" -rc=$? -listed="$(grep -c '^carrier exempt' <<<"$live" || true)" -stale=0 -while read -r _c _e path _rest; do - [[ -n "${path:-}" ]] || continue - [[ -r "$SCRIPT_DIR/../$path" ]] || stale=$((stale + 1)) -done <<<"$(grep '^carrier exempt' <<<"$live")" -if ((rc == 0)) && ((listed >= 1)) && ((stale == 0)); then - ok "every listed data carrier still exists in this checkout" -else - fail "data-carrier listing is stale or empty (rc=$rc listed=$listed stale=$stale): $live" -fi - # --- 27. The discovery self-proof is load-bearing -------------------------- # If git cannot see the source, the scan found nothing because it was broken, # not because the corpus is clean. Staging everything EXCEPT the source makes @@ -529,18 +499,6 @@ else fail "discovery self-proof should exit 2 (rc=$rc): $out" fi -# --- 28. The live repository carries no unregistered copy ------------------ -# Runs the scan against the real tree, which is where a new copy actually -# appears. Case 17 proves the mechanism; this proves today's corpus is closed. - -live="$(bash "$SUT" --check 2>&1)" -rc=$? -if ((rc == 0)) && ! grep -q 'UNREGISTERED' <<<"$live"; then - ok "no unregistered copy of the floor exists in this checkout" -else - fail "live repository carries an unregistered floor copy (rc=$rc): $live" -fi - # --- 29. Weakening the Account switch bullet in one lane body fails -------- # The newest bullet is the one a lane would silently soften first (MUST to may), # so it gets its own drift case rather than riding on the older bullets' cases. diff --git a/scripts/check-read-caller-keys.test.sh b/scripts/check-read-caller-keys.test.sh index d3564fbff2..686aaddfdf 100755 --- a/scripts/check-read-caller-keys.test.sh +++ b/scripts/check-read-caller-keys.test.sh @@ -1,4 +1,5 @@ #!/usr/bin/env bash +# test-scope: .github/standards/runner-policy/package.json # Black-box contract test for check-read-caller-keys.sh. # # Self-contained and cwd-independent: builds a throwaway tree with fixture diff --git a/scripts/check-summary-reader-parity.test.sh b/scripts/check-summary-reader-parity.test.sh index 769a77b34c..d896962644 100755 --- a/scripts/check-summary-reader-parity.test.sh +++ b/scripts/check-summary-reader-parity.test.sh @@ -28,6 +28,7 @@ # 2. every SKILL.md in the tree, proving the TREE # # Exit 0 = parity holds; 1 = a reader diverged; 2 = the gate could not run. +# test-scope: plugins/*/skills/*/SKILL.md set -uo pipefail cd "$(dirname "${BASH_SOURCE[0]}")/.." || exit 2 diff --git a/scripts/gen-hook-event-registry.test.sh b/scripts/gen-hook-event-registry.test.sh index 4fb9bdffa2..f76e49acbd 100755 --- a/scripts/gen-hook-event-registry.test.sh +++ b/scripts/gen-hook-event-registry.test.sh @@ -3,6 +3,7 @@ # (a plugins/harness-ops/hooks with a copy of the real hooks.json and the real # session-log-lib.sh) and runs the generator against a saved copy of the # Hooks reference lifecycle table, so nothing here touches the network. +# test-scope: plugins/harness-ops/hooks/hooks.json set -uo pipefail TMP_ROOT="$(mktemp -d)" diff --git a/scripts/lib/fixture-tree.test.sh b/scripts/lib/fixture-tree.test.sh index 151f17a3ff..f37af826d9 100755 --- a/scripts/lib/fixture-tree.test.sh +++ b/scripts/lib/fixture-tree.test.sh @@ -15,6 +15,7 @@ # # fixture-isolation-scope: exports GIT_DIR, GIT_WORK_TREE and GIT_CONFIG into a # child on purpose, to prove the builder clears what it inherits. +# test-scope: scripts/lib/* set -uo pipefail SELF_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" diff --git a/scripts/node-notice-rows.test.sh b/scripts/node-notice-rows.test.sh index ede9f3656b..994733e59a 100755 --- a/scripts/node-notice-rows.test.sh +++ b/scripts/node-notice-rows.test.sh @@ -8,6 +8,7 @@ # A POSIX shell (bash, or dash as /bin/sh on Debian and Ubuntu) runs the first and leaves at # `${PPID:+exit}`, since every POSIX shell sets PPID; PowerShell finds no sh, reads # that token as a missing drive, and runs the second. Both share one latch per session. +# test-scope: plugins/*/hooks/hooks.json plugins/*/.claude-plugin/plugin.json set -uo pipefail SELF_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" diff --git a/scripts/pipefail-grep-q-baseline.txt b/scripts/pipefail-grep-q-baseline.txt index eaa4874320..73f13a0eb9 100644 --- a/scripts/pipefail-grep-q-baseline.txt +++ b/scripts/pipefail-grep-q-baseline.txt @@ -44,9 +44,7 @@ plugins/guardrails/lib/verification/verify-cli-flag.sh plugins/harness-config/lib/check-retirements.sh plugins/harness-config/lib/permission-patterns.test.sh plugins/harness-config/lib/state-key.sh -plugins/harness-config/skills/audit-automation-gaps/scripts/inventory.test.sh plugins/harness-config/skills/audit-instructions/scripts/emit-findings.test.sh -plugins/harness-config/skills/audit-instructions/scripts/finding-ids.test.sh plugins/harness-config/skills/audit-pass/scripts/state-digest.test.sh plugins/harness-config/skills/audit-permission-state/scripts/automode-entry-diff.sh plugins/harness-config/skills/audit-permission-state/scripts/managed-conformance.test.sh diff --git a/scripts/run-outside-node-suites.test.sh b/scripts/run-outside-node-suites.test.sh index 484c0dda27..4f7ee21948 100755 --- a/scripts/run-outside-node-suites.test.sh +++ b/scripts/run-outside-node-suites.test.sh @@ -1,6 +1,6 @@ #!/usr/bin/env bash -# Classifies declined suites without guessing a runner, and runs the registered -# packages' own npm test. +# Classifies declined suites without guessing a runner, and runs a fixture +# registry's packages through their own npm test. set -euo pipefail cd "$(dirname "${BASH_SOURCE[0]}")/.." @@ -40,10 +40,27 @@ else fail "wrapped node suite was not owned: $out" fi -if bash "$RUNNER"; then - ok "registered packages pass their own npm test" +# The no-argument mode against a fixture registry. Each registered package's +# own suites run in test-node when the plan names that package. +# shellcheck source=lib/fixture-tree.sh +. scripts/lib/fixture-tree.sh +fixture="" +fixture_tree::build fixture --sut run-outside-node-suites.sh --label outside-node +mkdir -p "$fixture/pkgs/pass" "$fixture/pkgs/fail" +printf '{"private":true,"scripts":{"test":"exit 0"}}\n' >"$fixture/pkgs/pass/package.json" +printf '{"private":true,"scripts":{"test":"exit 3"}}\n' >"$fixture/pkgs/fail/package.json" +: >"$fixture/scripts/outside-node-exclusions.txt" +printf 'pkgs/pass\n' >"$fixture/scripts/outside-node-packages.txt" +if out="$(bash "$fixture/$RUNNER" 2>&1)" && grep -q "npm test in pkgs/pass" <<<"$out"; then + ok "with no arguments every registered package runs its own npm test" else - fail "registered packages failed npm test" + fail "the registered package did not run and pass: $out" +fi +printf 'pkgs/pass\npkgs/fail\n' >"$fixture/scripts/outside-node-packages.txt" +if bash "$fixture/$RUNNER" >/dev/null 2>&1; then + fail "a registered package whose npm test fails should fail the run" +else + ok "a failing registered package fails the run" fi printf '%s\n' "plugins/knowledge/vendor/not-a-suite/missing.test.js" >"$list" diff --git a/scripts/selection-audit.sh b/scripts/selection-audit.sh index 807ffa8c74..4cc3a3bf92 100755 --- a/scripts/selection-audit.sh +++ b/scripts/selection-audit.sh @@ -9,8 +9,10 @@ # selector would not take is a GAP: a change to that file could break the # suite on main while the pull request's affected run never started it. # Default corpus: every tracked *.test.sh, and every test_*.py outside -# /evals/fixtures/, which holds data no lane runs; --shard keeps -# suites I, I+N, I+2N, ... of the sorted list. +# /evals/fixtures/, which holds data no lane runs, but the two suites +# that run strace themselves; --shard keeps suites I, I+N, I+2N, ... +# of the sorted list. Each suite gets 600 s under strace, and the +# slowest start first. # # scripts/selection-audit.sh replay --suite S --good SHA --bad SHA # For each first-parent commit in GOOD..BAD, run that commit's own @@ -42,6 +44,11 @@ # through git objects (`git show HEAD:path`) and existence probes (stat, `-f`) # are not opens, so the trace cannot see them. # +# Three root files are read by almost every suite through its tools, not its +# logic: node reads package.json for the module type, the version manager reads +# .node-version, and git reads .gitignore. A read of one is no gap; a change to +# one is the toolchain's to gate, and as a scope it would select every suite. +# # Writes report.md, gaps.tsv and suites.tsv (trace) or report.md (replay, # red-replay) under --out, and appends report.md to $GITHUB_STEP_SUMMARY when # set. Never opens or edits an issue. @@ -130,9 +137,10 @@ trace_one() { *) runner=(bash "$suite") ;; esac start="$(date +%s)" - timeout 240 strace -f -y -z -qq --seccomp-bpf -e trace=open,openat -e signal=none \ + # -k: a daemon a suite leaves behind stays traced, and strace outlives a TERM waiting for it. + timeout -k 10 600 strace -f -y -z -qq --seccomp-bpf -e trace=open,openat -e signal=none \ -o "$out/strace.$key" "${runner[@]}" >"$out/logs/$key.log" 2>&1 || rc=$? - reads "$out/strace.$key" "$ROOT" | grep -vxF -- "$suite" | + reads "$out/strace.$key" "$ROOT" | grep -vxF -e "$suite" -e package.json -e .node-version -e .gitignore | awk 'NR == FNR { t[$0] = 1; next } t[$0]' "$out/tracked" - >"$out/reads/$key" rm -f "$out/strace.$key" printf '%s\t%s\t%s\t%s\n' "$suite" "$rc" "$(($(date +%s) - start))" "$(wc -l <"$out/reads/$key")" >"$out/suites/$key" @@ -183,10 +191,18 @@ cmd_trace() { if ((${#suites[@]} == 0)); then # A Python eval fixture is data no lane runs (scripts/plan-test-lanes.sh); # a *.test.sh there would still run in the bash lane, so it stays traced. + # These suites run strace themselves, which fails under the audit's own strace. mapfile -t suites < <(grep -E '(\.test\.sh|(^|/)test_[^/]*\.py)$' "$out/tracked" | grep -vE '/evals/fixtures/(.*/)?test_[^/]*\.py$' | - awk -v i="$i" -v n="$n" '(NR - 1) % n == i') + grep -vxF -e scripts/selection-audit.test.sh -e scripts/hook-census.test.sh | awk -v i="$i" -v n="$n" '(NR - 1) % n == i') fi ((${#suites[@]})) || die "no suites to trace" + # Slowest first by scripts/suite-seconds.txt, so a long suite starts while the + # budget still has room for it to finish and for its reads to be checked. + if [[ -f scripts/suite-seconds.txt ]]; then + mapfile -t suites < <(printf '%s\n' "${suites[@]}" | + awk 'NR == FNR { if (!/^#/) s[$1] = $2; next } { print ($0 in s ? s[$0] : 0) "\t" $0 }' scripts/suite-seconds.txt - | + sort -s -t $'\t' -k1,1nr | cut -f2-) + fi AUDIT_UNMAPPED_FLAG="$(unmapped_flag)" export AUDIT_DEADLINE=0 AUDIT_SELECT_GRACE=$((budget * 2 / 5)) AUDIT_UNMAPPED_FLAG diff --git a/scripts/selection-audit.test.sh b/scripts/selection-audit.test.sh index a5ad00c595..e0e630c8cf 100755 --- a/scripts/selection-audit.test.sh +++ b/scripts/selection-audit.test.sh @@ -72,7 +72,12 @@ else echo named >"$t/data/named.txt" echo odd >"$t/data/unmapped.txt" echo broken >"$t/data/broken.txt" - printf '%s\n' '#!/usr/bin/env bash' 'cat data/hidden.txt data/named.txt data/unmapped.txt data/untracked.txt data/broken.txt' >"$t/suites/reader.test.sh" + # The root toolchain files every suite reads are no gap. + echo '{}' >"$t/package.json" + echo 22 >"$t/.node-version" + echo '*.log' >"$t/.gitignore" + printf '%s\n' '#!/usr/bin/env bash' 'cat data/hidden.txt data/named.txt data/unmapped.txt data/untracked.txt data/broken.txt' \ + 'cat package.json .node-version .gitignore' >"$t/suites/reader.test.sh" printf '%s\n' '#!/usr/bin/env bash' 'true' >"$t/suites/quiet.test.sh" # Reads the unmapped file too, but the fallback corpus does not run it. printf '%s\n' '#!/usr/bin/env bash' 'cat data/unmapped.txt' >"$t/suites/zz-outside.test.sh" @@ -80,6 +85,8 @@ else mkdir -p "$t/plugins/x/evals/fixtures" printf '%s\n' 'open("data/hidden.txt").read()' >"$t/plugins/x/evals/fixtures/test_fake.py" ci_fallback "$t" suites/reader.test.sh suites/quiet.test.sh + # The audit's own suite runs strace, which fails under the audit's strace. + printf '%s\n' '#!/usr/bin/env bash' 'cat data/hidden.txt' >"$t/scripts/selection-audit.test.sh" git -C "$t" add -A && git -C "$t" commit -qm base echo untracked >"$t/data/untracked.txt" cat >"$TMP_ROOT/stub-selector.sh" <<'EOF' @@ -104,7 +111,7 @@ EOF fail "trace: exit $rc, want 1: $(cat "$TMP_ROOT/trace.log")" fi if [[ "$gaps" == "$want" ]]; then - ok "trace: gaps are the unmapped-to-suite read and the unmapped read of a suite the fallback does not run (mapped, fallback-run, unchecked, untracked and self are not)" + ok "trace: gaps are the unmapped-to-suite read and the unmapped read of a suite the fallback does not run (mapped, fallback-run, unchecked, untracked, self and root toolchain files are not)" else fail "trace: gaps.tsv [$gaps] want [$want]" fi @@ -120,7 +127,7 @@ EOF fail "trace: verdicts.tsv [$(cat "$TMP_ROOT/out/verdicts.tsv" 2>/dev/null)]" fi if [[ "$(cut -f1,2 "$TMP_ROOT/out/suites.tsv")" == $'suites/quiet.test.sh\t0\nsuites/reader.test.sh\t0\nsuites/zz-outside.test.sh\t0' ]]; then - ok "trace: suites.tsv records every traced suite and its exit, and no eval fixture" + ok "trace: suites.tsv records every traced suite and its exit, and no eval fixture or audit suite" else fail "trace: suites.tsv [$(cat "$TMP_ROOT/out/suites.tsv")]" fi diff --git a/scripts/suite-seconds.txt b/scripts/suite-seconds.txt index cb52e0ff23..44e85bba5e 100644 --- a/scripts/suite-seconds.txt +++ b/scripts/suite-seconds.txt @@ -405,7 +405,7 @@ plugins/planning/scripts/check-open-questions.test.sh 2 plugins/planning/scripts/check-plan-outcome.test.sh 1 plugins/planning/scripts/eval-scaffold.test.sh 1 plugins/planning/scripts/goal-condition-length.test.sh 1 -plugins/planning/surface/surface.test.sh 1 +plugins/planning/surface/surface.test.sh 390 plugins/planning/surface/test_exporters.py 92 plugins/planning/surface/test_round.py 49 plugins/planning/surface/test_schema.py 1 diff --git a/scripts/validate-plugin-contracts.test.sh b/scripts/validate-plugin-contracts.test.sh index 302031d337..4b46b991f2 100755 --- a/scripts/validate-plugin-contracts.test.sh +++ b/scripts/validate-plugin-contracts.test.sh @@ -27,7 +27,6 @@ set -uo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" SUT="$SCRIPT_DIR/validate-plugin-contracts.mjs" if ! command -v node >/dev/null 2>&1; then @@ -1708,18 +1707,8 @@ else fail "skill limits should fail per file: $out" fi -# --- 9. Real corpus: every shipping setup skill still conforms. ------------- -out="$( (cd "$REPO_ROOT" && node "$SUT" 2>&1))" -rc=$? -if [[ $rc -eq 0 ]]; then - ok "the shipping plugins/ tree still validates end to end" -else - fail "the shipping tree should stay green (rc=$rc): $out" -fi -if grep -q '^warning: ' <<<"$out"; then - fail "the shipping tree should draw zero warnings: $out" -else - ok "the shipping tree draws zero warnings" -fi +# The shipping tree itself, clean and with zero warnings, is the job of +# scripts/validate-plugins.sh in check-plugins, which runs this validator over +# the whole tree on every pull request; run here, it read every plugin file. test_harness::report diff --git a/scripts/vendor-gitleaks-rules.test.sh b/scripts/vendor-gitleaks-rules.test.sh index 4c4c0f4c0c..4088979542 100755 --- a/scripts/vendor-gitleaks-rules.test.sh +++ b/scripts/vendor-gitleaks-rules.test.sh @@ -4,6 +4,7 @@ # SKIPs (exit 0) when no interpreter meets the script's MIN_PYTHON (3.11, for # tomllib) or pytest is missing. -p no:cacheprovider keeps a .pytest_cache out # of the tree. +# test-scope: scripts/fixtures/vendor-gitleaks-rules/* set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"