From 3c4151dd0f18b5f874e6cd4eee2072db3ecbd806 Mon Sep 17 00:00:00 2001 From: Kyle Sexton <153232337+kyle-sexton@users.noreply.github.com> Date: Sun, 4 Oct 2026 20:34:54 -0400 Subject: [PATCH 01/22] wip: test-scope declarations (F3, F4, F5) --- .../skills/setup/scripts/setup-check.test.sh | 2 +- .../validate/scripts/test_validate_cases.py | 1 + .../scripts/inventory.test.sh | 15 ++++++++------- 3 files changed, 10 insertions(+), 8 deletions(-) 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/evals/skills/validate/scripts/test_validate_cases.py b/plugins/evals/skills/validate/scripts/test_validate_cases.py index 94b3946687..1dc3c08469 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/harness-config/skills/audit-automation-gaps/scripts/inventory.test.sh b/plugins/harness-config/skills/audit-automation-gaps/scripts/inventory.test.sh index fcd6d104d0..57a750734b 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 @@ -600,14 +600,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 +627,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 From 18044abdb8c25c8e04209459892910e119cedcea Mon Sep 17 00:00:00 2001 From: Kyle Sexton <153232337+kyle-sexton@users.noreply.github.com> Date: Sun, 4 Oct 2026 22:24:31 -0400 Subject: [PATCH 02/22] wip: drop live re-runs of gate checks --- scripts/check-fixture-git-isolation.test.sh | 11 ++---- scripts/check-loop-lane-floor-drift.test.sh | 39 ++------------------- scripts/validate-plugin-contracts.test.sh | 17 ++------- 3 files changed, 9 insertions(+), 58 deletions(-) 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-loop-lane-floor-drift.test.sh b/scripts/check-loop-lane-floor-drift.test.sh index fa0efd0af8..5ffd608efb 100755 --- a/scripts/check-loop-lane-floor-drift.test.sh +++ b/scripts/check-loop-lane-floor-drift.test.sh @@ -354,23 +354,9 @@ 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 and 26 checked the live registry for stale consumers and data +# carriers. `--check` fails on both, 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 +478,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 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 From 91c6395d55f1093baf40beb910c58ccd2bfcbc0b Mon Sep 17 00:00:00 2001 From: Kyle Sexton <153232337+kyle-sexton@users.noreply.github.com> Date: Sun, 4 Oct 2026 23:14:43 -0400 Subject: [PATCH 03/22] ci: declare or remove the suite reads the test selection cannot see (WIP) Work in progress, unverified as a whole. Per-suite `# test-scope:` declarations and removals of live re-runs of gate checks, closing gaps the full selection-audit trace found (F3, F4, F5, F8 and the shards never traced before). Three workers were stopped mid-run for a machine restart, so some suites are partly done; plugin version bumps and CHANGELOG entries are not added yet. Co-Authored-By: Claude Opus 5.5 (1M context) --- lib/prerequisites.test.sh | 1 + lib/session-bridge/test_session_bridge.py | 1 + .../actionlint/hooks/actionlint-check.test.sh | 2 +- plugins/animation/scripts/animation.test.sh | 2 +- plugins/animation/scripts/test_pydeps.py | 1 + .../skills/rotoscope/scripts/test_regress.py | 1 + .../architecture/lib/family-records.test.sh | 1 + .../scripts/render-landscape.test.sh | 1 + plugins/autonomy/hooks/lane-stop-gate.test.sh | 1 + plugins/bash-format/hooks/bash-format.test.sh | 1 + .../biome-format/hooks/biome-format.test.sh | 1 + .../audit-size/scripts/audit-size.test.sh | 2 +- .../scripts/detect.test.sh | 1 + .../hooks/settings-write-ask.test.sh | 1 + .../hooks/run-python-hook.test.sh | 1 + .../clean/scripts/engine_context.test.sh | 1 + .../clean/scripts/owner_registry.test.sh | 2 +- .../clean/scripts/test_engine_context.py | 1 + .../skills/clean/scripts/test_hygiene.py | 2 +- .../clean/scripts/test_owner_registry.py | 1 + .../setup/scripts/kill_switch_probe.test.sh | 2 +- .../setup/scripts/test_kill_switch_probe.py | 1 + .../skills/audit-noise/scripts/detect.test.sh | 1 + .../scripts/emit-findings.test.sh | 1 + .../scripts/inventory.test.sh | 1 + .../audit-file-names/scripts/sweep.test.sh | 1 + .../scripts/emit-gate.test.sh | 1 + .../scripts/apply-rename.test.sh | 1 + .../scripts/build-explainer.test.sh | 1 + .../quiz-me/scripts/build-report.test.sh | 1 + .../hooks/eol-normalizer.test.sh | 1 + .../scripts/test_calibrate_judge.py | 1 + .../plugin-eval/scripts/test_run_validity.py | 1 + .../simulation/scripts/build-bc-view.test.sh | 1 + .../scripts/explainer-video.test.sh | 1 + .../scripts/test_explainer_video_pydeps.py | 1 + plugins/go-format/hooks/go-format.test.sh | 1 + .../guardrails/hooks/abort-boundary.test.sh | 2 +- .../hooks/block-hook-bypass.test.sh | 1 + .../hooks/block-windows-drive-tmp.test.sh | 1 + .../hooks/coverage-manifest.test.sh | 2 +- plugins/guardrails/hooks/exec-bash.test.sh | 1 + .../hooks/require-jq-notice-isolation.test.sh | 2 +- .../hooks/require-jq-posture.test.sh | 2 +- plugins/guardrails/hooks/run-guards.test.sh | 2 +- .../hooks/secret-pattern-detection.test.sh | 1 + .../scripts/spoke-script-paths.test.sh | 1 + .../hooks/hook-failure-audit.test.sh | 1 + plugins/harness-ops/lib/spawn_noise.test.sh | 1 + .../scripts/install_state.test.sh | 1 + .../scripts/test_install_state.py | 1 + .../scripts/audit_performance.test.sh | 1 + .../scripts/audit_skill_visibility.test.sh | 1 + .../scripts/discover-surfaces.test.sh | 1 + .../inventory/scripts/test_fixture_parse.py | 1 + .../scripts/test_inventory_under_parser.py | 1 + .../inventory/scripts/test_parser_reader.py | 1 + .../inventory/scripts/test_reader_findings.py | 1 + .../scripts/build-dashboard.test.sh | 1 + .../scripts/probe-observability-state.test.sh | 1 + .../scripts/cache-content-check.test.sh | 1 + .../skills/plugins/scripts/sync-run.test.sh | 2 +- .../scripts/check-fences-exact.test.sh | 1 + .../scripts/check-html-rows.test.sh | 1 + .../scripts/check-snippets.test.sh | 1 + .../scripts/extract_blog_body.test.sh | 1 + .../scripts/pin-manifest.test.sh | 1 + .../hooks/markdown-format.test.sh | 1 + .../hooks/drift-checker-fetch-gate.test.sh | 1 + .../skills/check/scripts/check.test.sh | 1 + plugins/pixel-art/scripts/backends.test.sh | 1 + plugins/pixel-art/scripts/test_palette.py | 1 + .../planning/scripts/eval-scaffold.test.sh | 1 + plugins/planning/surface/test_exporters.py | 1 + plugins/planning/surface/test_round.py | 1 + plugins/planning/surface/test_schema.py | 1 + plugins/planning/surface/test_server.py | 1 + plugins/planning/surface/watch.test.sh | 1 + .../skills/repo-sweep/scripts/catalog.test.sh | 1 + .../hooks/powershell-format.test.sh | 1 + .../skills/sync/scripts/sync-fleet.test.sh | 5 +++- plugins/retro-audio/scripts/audio.test.sh | 1 + plugins/review/tests/explain-change.test.sh | 1 + plugins/review/tests/fanout-sweep.test.sh | 1 + plugins/ruff-format/hooks/ruff-format.test.sh | 1 + .../scripts/tests/test_save_point.py | 21 ++++++++------- .../scripts/audit-sessions.test.sh | 2 ++ .../scripts/tests/test_collect.py | 1 + .../scripts/tests/test_drift.py | 1 + .../tests/test_parse_transcript_golden.py | 1 + .../scripts/tests/test_transcript_reader.py | 1 + .../retro/scripts/parse-transcript.test.sh | 1 + .../running-retro/scripts/observer.test.sh | 1 + .../hooks/pr-linkage-mcp-gate.test.sh | 1 + .../scripts/babysit-wrapper-help.test.sh | 1 + .../scripts/lib/worktree-facts.test.sh | 1 + .../skills/babysit-prs/scripts/engine.test.sh | 1 + .../babysit-prs/scripts/tests/test_guards.py | 2 +- .../scripts/tests/test_skill_contract.py | 1 + .../worktree/nesting-invariant-ssot.test.sh | 1 + plugins/speech/scripts/speech.test.sh | 1 + plugins/speech/scripts/test_assets.py | 1 + plugins/speech/scripts/test_elevenlabs.py | 1 + plugins/speech/scripts/test_speech_pydeps.py | 1 + plugins/testing/hooks/test-judge-bg.test.sh | 1 + plugins/testing/hooks/test-judge.test.sh | 1 + plugins/testing/hooks/test-scan.test.sh | 1 + plugins/testing/hooks/test-weaken.test.sh | 1 + .../testing/scripts/gen-hook-filters.test.sh | 1 + .../testing/scripts/resolve-config.test.sh | 1 + .../evals/judge-calibration/metrics.test.sh | 1 + .../audit/scripts/cant-fail-scan.test.sh | 1 + .../skills/setup/scripts/setup.test.sh | 1 + .../typos-format/hooks/typos-format.test.sh | 1 + plugins/user-interface/tests/detect.test.sh | 1 + plugins/visualization/tests/present.test.sh | 1 + .../wizard/skills/unattended/template.test.sh | 1 + .../conformance/bindings/jira.test.sh | 1 + .../bindings/local-markdown.test.sh | 1 + .../work-item-tracker.test.sh | 1 + .../check-guardrails-ps-differential.test.sh | 1 + scripts/check-hooks-description.test.sh | 10 ++----- scripts/check-lane-coverage.test.sh | 10 ++----- scripts/check-summary-reader-parity.test.sh | 1 + scripts/gen-hook-event-registry.test.sh | 1 + scripts/lib/fixture-tree.test.sh | 1 + scripts/node-notice-rows.test.sh | 1 + scripts/run-outside-node-suites.test.sh | 27 +++++++++++++++---- scripts/vendor-gitleaks-rules.test.sh | 1 + 129 files changed, 166 insertions(+), 45 deletions(-) mode change 100755 => 100644 lib/prerequisites.test.sh mode change 100755 => 100644 plugins/repo-fleet-hygiene/skills/sync/scripts/sync-fleet.test.sh mode change 100755 => 100644 scripts/check-guardrails-ps-differential.test.sh mode change 100755 => 100644 scripts/check-hooks-description.test.sh mode change 100755 => 100644 scripts/check-lane-coverage.test.sh mode change 100755 => 100644 scripts/check-summary-reader-parity.test.sh mode change 100755 => 100644 scripts/gen-hook-event-registry.test.sh mode change 100755 => 100644 scripts/lib/fixture-tree.test.sh mode change 100755 => 100644 scripts/node-notice-rows.test.sh mode change 100755 => 100644 scripts/run-outside-node-suites.test.sh mode change 100755 => 100644 scripts/vendor-gitleaks-rules.test.sh diff --git a/lib/prerequisites.test.sh b/lib/prerequisites.test.sh old mode 100755 new mode 100644 index 3759153c51..2ceb525172 --- 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 bc20f19187..cf9b962b27 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..37bd60bb30 100755 --- a/plugins/animation/scripts/animation.test.sh +++ b/plugins/animation/scripts/animation.test.sh @@ -5,7 +5,7 @@ # ../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/* set -uo pipefail cd "$(dirname "${BASH_SOURCE[0]}")" || exit 1 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/skills/rotoscope/scripts/test_regress.py b/plugins/animation/skills/rotoscope/scripts/test_regress.py index 149f10a5b9..ddbef16463 100644 --- a/plugins/animation/skills/rotoscope/scripts/test_regress.py +++ b/plugins/animation/skills/rotoscope/scripts/test_regress.py @@ -1,3 +1,4 @@ +# test-scope: plugins/animation/requirements.in """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 d3b6fe3e1e..7ce9305d2b 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 902dcc0a82..1098a4039b 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-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 a30edf9748..80b28edd48 100755 --- a/plugins/context-budget/hooks/settings-write-ask.test.sh +++ b/plugins/context-budget/hooks/settings-write-ask.test.sh @@ -11,6 +11,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/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 b55c5dfe68..08f1ed2ec1 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 e93f94c2b5..1c61eea755 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 f7cdcd1a3e..81ca238854 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..594fe71069 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,5 @@ #!/usr/bin/env python3 +# test-scope: plugins/evals/evals/* """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 89aac8a05e..3ea07b1104 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/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 b9be1f9169..062a943ec7 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/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 8c88da7285..569b4fceb5 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 cfa527fc81..2f475799ab 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-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 3447288d5c..902ae0a450 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/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..0045c0e9f9 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,5 @@ #!/usr/bin/env python3 +# test-scope: plugins/harness-ops/skills/inventory/scripts/js/package-lock.json """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_under_parser.py b/plugins/harness-ops/skills/inventory/scripts/test_inventory_under_parser.py index 6aaed7cf7e..ada81cc8e7 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,5 @@ #!/usr/bin/env python3 +# test-scope: plugins/harness-ops/skills/inventory/scripts/js/package-lock.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..2b932629e5 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,5 @@ #!/usr/bin/env python3 +# test-scope: plugins/harness-ops/skills/inventory/scripts/js/package-lock.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 bba0250c4c..a1faeded2e 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 e858fd05c7..2f1ffdf234 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..286316a934 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,7 @@ #!/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 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..04dafdb270 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,7 @@ #!/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 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 9b29f9e0f6..8c6bf47654 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..9f08cfe974 100755 --- a/plugins/pixel-art/scripts/backends.test.sh +++ b/plugins/pixel-art/scripts/backends.test.sh @@ -1,6 +1,7 @@ #!/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 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/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 4c273f748e..a6445096e1 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 old mode 100755 new mode 100644 index 047d4e3520..d46cd23063 --- 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 36adc54396..cf4c8fc89d 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-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 3004463465..3f60abc331 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..fd7ac7f4f5 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,7 @@ # # 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 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/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..5a66176a9f 100755 --- a/plugins/speech/scripts/speech.test.sh +++ b/plugins/speech/scripts/speech.test.sh @@ -6,6 +6,7 @@ # 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 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 0392b310a0..609703f9ec 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 c9250b6e54..03f47e3424 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 """elevenlabs.py: the statement before every call, the proceed gate, the egress floor, 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/hooks/test-judge-bg.test.sh b/plugins/testing/hooks/test-judge-bg.test.sh index ed86ecc8f2..58be82efe9 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 366c5fc460..c2662e0649 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 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 9eb4ecadb1..77697c6133 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 b01357cc47..e4b5e7d974 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,7 @@ # 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/* # shellcheck disable=SC2016 # single-quoted fixture text is literal shell source set -uo pipefail 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 9f1e54a8d6..dbc7e59c03 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-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/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/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 19a1fbe21b..290cb3fb80 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/check-guardrails-ps-differential.test.sh b/scripts/check-guardrails-ps-differential.test.sh old mode 100755 new mode 100644 index b7d07bc5a2..120d7d5c72 --- 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 old mode 100755 new mode 100644 index cbb0c358d0..ebeffdba88 --- 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 old mode 100755 new mode 100644 index 75cb357def..faec4f18b0 --- 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-summary-reader-parity.test.sh b/scripts/check-summary-reader-parity.test.sh old mode 100755 new mode 100644 index 769a77b34c..d896962644 --- 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 old mode 100755 new mode 100644 index fd89e29c08..15af577145 --- 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 old mode 100755 new mode 100644 index 151f17a3ff..f37af826d9 --- 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 old mode 100755 new mode 100644 index ede9f3656b..994733e59a --- 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/run-outside-node-suites.test.sh b/scripts/run-outside-node-suites.test.sh old mode 100755 new mode 100644 index 484c0dda27..4f7ee21948 --- 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/vendor-gitleaks-rules.test.sh b/scripts/vendor-gitleaks-rules.test.sh old mode 100755 new mode 100644 index 4c4c0f4c0c..4088979542 --- 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)" From c888d3131ba6d4216799e8544b3b6bb06e1aac08 Mon Sep 17 00:00:00 2001 From: Kyle Sexton <153232337+kyle-sexton@users.noreply.github.com> Date: Sat, 10 Oct 2026 10:57:58 -0400 Subject: [PATCH 04/22] ci: give slow suites room in the selection audit and drop harmless root reads The audit traced each suite for at most 240 s, and four suites ran past it under strace, so their reads were cut short. Each suite now gets 600 s, the slowest start first by scripts/suite-seconds.txt, and the job's budget and timeout grow to fit. The audit no longer traces its own suite, which runs strace and fails under the audit's strace. Reads of package.json, .node-version and .gitignore, which almost every suite opens through its tools, are no gap. Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_01TRhsMPSTMFo99ik7gkdgQt --- .../maintenance-audit-test-selection.yml | 10 ++++---- scripts/selection-audit.sh | 24 +++++++++++++++---- scripts/selection-audit.test.sh | 13 +++++++--- 3 files changed, 34 insertions(+), 13 deletions(-) diff --git a/.github/workflows/maintenance-audit-test-selection.yml b/.github/workflows/maintenance-audit-test-selection.yml index c840ab998b..b4a89f3033 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 30-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: 30 permissions: contents: read concurrency: @@ -110,8 +110,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 1200 s: suites start for the first 720 s, selector queries stop + # at 1200 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 +126,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 1200 \ --out "$RUNNER_TEMP/selection-audit" ${suites[@]+"${suites[@]}"} - name: Upload the trace report if: ${{ !cancelled() }} diff --git a/scripts/selection-audit.sh b/scripts/selection-audit.sh index 807ffa8c74..7a75b6d974 100755 --- a/scripts/selection-audit.sh +++ b/scripts/selection-audit.sh @@ -9,8 +9,9 @@ # 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 this audit's own +# suite; --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 +43,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 +136,9 @@ 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 \ + timeout 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 +189,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. + # This suite runs strace itself, 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 scripts/selection-audit.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 From b5e3f7ad37fdd9f60c83eb37d1b1df445cd30588 Mon Sep 17 00:00:00 2001 From: Kyle Sexton <153232337+kyle-sexton@users.noreply.github.com> Date: Sat, 10 Oct 2026 11:00:58 -0400 Subject: [PATCH 05/22] ci: check the selector against the live tree in lint-repo, not in its suite The live cases of scripts/affected-tests.test.sh ran the selector over the whole tree, so the suite read about 2,400 files no test selection could map back to it. They move unchanged to scripts/affected-tests-live.sh, which a new selector-live gate step in lint-repo runs on every pull request and feeds to the ci-status aggregate. The suite's declarations that only those cases needed go with them. Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_01TRhsMPSTMFo99ik7gkdgQt --- .github/workflows/pr-require-checks.yml | 10 ++ scripts/affected-tests-live.sh | 229 ++++++++++++++++++++++++ scripts/affected-tests.test.sh | 221 +---------------------- 3 files changed, 244 insertions(+), 216 deletions(-) create mode 100755 scripts/affected-tests-live.sh diff --git a/.github/workflows/pr-require-checks.yml b/.github/workflows/pr-require-checks.yml index 3e16ae9b0d..dc14568dee 100644 --- a/.github/workflows/pr-require-checks.yml +++ b/.github/workflows/pr-require-checks.yml @@ -749,6 +749,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 @@ -1076,6 +1085,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/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 From 6ce953bfcffe250253e75ef4812b1cce2e256497 Mon Sep 17 00:00:00 2001 From: Kyle Sexton <153232337+kyle-sexton@users.noreply.github.com> Date: Sat, 10 Oct 2026 11:53:03 -0400 Subject: [PATCH 06/22] ci: declare the reads the first full trace of the merged tree found A whole-corpus trace of this branch merged with main left gaps in 25 suites beyond the ones the earlier commits fixed. Each now declares what it reads in its test-scope header: fixture and case directories, the hooks.json and agent files a suite parses, the marketplace and plugin manifests the routing suites check, and scripts/lib/*, which convert-bump-to-fragment.test.sh copies whole. check-loop-lane-floor-drift.test.sh drops case 28, the live --check that lint-repo already runs on every pull request. Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_01TRhsMPSTMFo99ik7gkdgQt --- lib/docs-fetcher-gate.test.sh | 1 + plugins/animation/scripts/animation.test.sh | 1 + plugins/animation/scripts/test_inkstats.py | 1 + .../animation/scripts/test_woodcut_marks.py | 1 + .../skills/rotoscope/scripts/test_regress.py | 1 + .../hooks/webfetch-truncation.test.sh | 1 + .../skills/check/scripts/check.test.sh | 1 + .../discovery/tests/research-sweep.test.sh | 1 + .../scripts/test_calibrate_judge.py | 1 + .../hooks/check-bash-file-changes.test.sh | 1 + .../behavior-probes/scripts/test_probe.py | 1 + .../inventory/scripts/test_fixture_parse.py | 1 + .../inventory/scripts/test_inventory.py | 1 + .../scripts/test_inventory_under_parser.py | 1 + .../inventory/scripts/test_reader_findings.py | 1 + .../scripts/check-fences-exact.test.sh | 1 + plugins/pixel-art/scripts/backends.test.sh | 1 + .../scripts/audit-friction.test.sh | 2 ++ .../scripts/tests/test_friction.py | 1 + .../scripts/convert-bump-to-fragment.test.sh | 1 + plugins/user-experience/tests/detect.test.sh | 1 + plugins/user-experience/tests/routing.test.sh | 1 + plugins/user-interface/tests/routing.test.sh | 1 + scripts/check-loop-lane-floor-drift.test.sh | 19 ++++--------------- scripts/check-read-caller-keys.test.sh | 1 + scripts/selection-audit.sh | 3 ++- 26 files changed, 31 insertions(+), 16 deletions(-) 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/plugins/animation/scripts/animation.test.sh b/plugins/animation/scripts/animation.test.sh index 37bd60bb30..53ab81d10e 100755 --- a/plugins/animation/scripts/animation.test.sh +++ b/plugins/animation/scripts/animation.test.sh @@ -6,6 +6,7 @@ # 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 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_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 ddbef16463..1a2eb60387 100644 --- a/plugins/animation/skills/rotoscope/scripts/test_regress.py +++ b/plugins/animation/skills/rotoscope/scripts/test_regress.py @@ -1,4 +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/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/evals/skills/plugin-eval/scripts/test_calibrate_judge.py b/plugins/evals/skills/plugin-eval/scripts/test_calibrate_judge.py index 594fe71069..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,5 +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/guardrails/hooks/check-bash-file-changes.test.sh b/plugins/guardrails/hooks/check-bash-file-changes.test.sh index ca7464913f..686b0059b5 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/harness-ops/skills/behavior-probes/scripts/test_probe.py b/plugins/harness-ops/skills/behavior-probes/scripts/test_probe.py index 95688ce503..03c8b0c7aa 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/inventory/scripts/test_fixture_parse.py b/plugins/harness-ops/skills/inventory/scripts/test_fixture_parse.py index 0045c0e9f9..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,5 +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 ada81cc8e7..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,5 +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_reader_findings.py b/plugins/harness-ops/skills/inventory/scripts/test_reader_findings.py index 2b932629e5..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,5 +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/knowledge/skills/docpage-digest/scripts/check-fences-exact.test.sh b/plugins/knowledge/skills/docpage-digest/scripts/check-fences-exact.test.sh index 286316a934..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 @@ -2,6 +2,7 @@ # 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/pixel-art/scripts/backends.test.sh b/plugins/pixel-art/scripts/backends.test.sh index 9f08cfe974..66f46064cf 100755 --- a/plugins/pixel-art/scripts/backends.test.sh +++ b/plugins/pixel-art/scripts/backends.test.sh @@ -2,6 +2,7 @@ # 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/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/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/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/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/scripts/check-loop-lane-floor-drift.test.sh b/scripts/check-loop-lane-floor-drift.test.sh index 5ffd608efb..6e432c998b 100755 --- a/scripts/check-loop-lane-floor-drift.test.sh +++ b/scripts/check-loop-lane-floor-drift.test.sh @@ -354,9 +354,10 @@ else fail "missing source should exit 2 (rc=$rc): $out" fi -# Cases 16 and 26 checked the live registry for stale consumers and data -# carriers. `--check` fails on both, and lint-repo runs it on every pull -# request; run here, they read every file of the live tree. +# 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 @@ -496,18 +497,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/selection-audit.sh b/scripts/selection-audit.sh index 7a75b6d974..5c65daa153 100755 --- a/scripts/selection-audit.sh +++ b/scripts/selection-audit.sh @@ -136,7 +136,8 @@ trace_one() { *) runner=(bash "$suite") ;; esac start="$(date +%s)" - timeout 600 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 -e "$suite" -e package.json -e .node-version -e .gitignore | awk 'NR == FNR { t[$0] = 1; next } t[$0]' "$out/tracked" - >"$out/reads/$key" From 8e9d51279c4844426916f410e449cb8e8d8784da Mon Sep 17 00:00:00 2001 From: Kyle Sexton <153232337+kyle-sexton@users.noreply.github.com> Date: Sat, 10 Oct 2026 11:56:15 -0400 Subject: [PATCH 07/22] chore: add bump-none changelog fragments for the test-only plugin changes Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_01TRhsMPSTMFo99ik7gkdgQt --- .../ci-selection-gap-declarations-6457-5aa475bb.md | 5 +++++ .../animation/ci-selection-gap-declarations-6457-0346e98c.md | 5 +++++ .../ci-selection-gap-declarations-6457-dfb4213e.md | 5 +++++ .../autonomy/ci-selection-gap-declarations-6457-781d539e.md | 5 +++++ .../ci-selection-gap-declarations-6457-ce48a644.md | 5 +++++ .../ci-selection-gap-declarations-6457-38dbc180.md | 5 +++++ .../ci-selection-gap-declarations-6457-eabd0f4b.md | 5 +++++ .../ci-selection-gap-declarations-6457-247cdcbf.md | 5 +++++ .../ci-selection-gap-declarations-6457-87f0e547.md | 5 +++++ .../discovery/ci-selection-gap-declarations-6457-6afe6e97.md | 5 +++++ .../ci-selection-gap-declarations-6457-5101169a.md | 5 +++++ .../ci-selection-gap-declarations-6457-03163494.md | 5 +++++ .../ci-selection-gap-declarations-6457-fb6bd772.md | 5 +++++ .../education/ci-selection-gap-declarations-6457-4f2a9a33.md | 5 +++++ .../ci-selection-gap-declarations-6457-a1176566.md | 5 +++++ .../evals/ci-selection-gap-declarations-6457-09c0ad8c.md | 5 +++++ .../ci-selection-gap-declarations-6457-a510fe3f.md | 5 +++++ .../ci-selection-gap-declarations-6457-bc898bc0.md | 5 +++++ .../go-format/ci-selection-gap-declarations-6457-b23cdd27.md | 5 +++++ .../ci-selection-gap-declarations-6457-a7b6ee7e.md | 5 +++++ .../ci-selection-gap-declarations-6457-7f3b0c8d.md | 5 +++++ .../ci-selection-gap-declarations-6457-cc460d18.md | 5 +++++ .../ci-selection-gap-declarations-6457-2f03e55a.md | 5 +++++ .../knowledge/ci-selection-gap-declarations-6457-deb3d90c.md | 5 +++++ .../ci-selection-gap-declarations-6457-0a96a9df.md | 5 +++++ .../ci-selection-gap-declarations-6457-93566999.md | 5 +++++ .../pixel-art/ci-selection-gap-declarations-6457-d83fcc9f.md | 5 +++++ .../planning/ci-selection-gap-declarations-6457-6543d37d.md | 5 +++++ .../playbooks/ci-selection-gap-declarations-6457-1d127e17.md | 5 +++++ .../ci-selection-gap-declarations-6457-81b1fe2b.md | 5 +++++ .../ci-selection-gap-declarations-6457-01c08b7e.md | 5 +++++ .../ci-selection-gap-declarations-6457-8832a509.md | 5 +++++ .../review/ci-selection-gap-declarations-6457-f5679709.md | 5 +++++ .../ci-selection-gap-declarations-6457-584cdfd1.md | 5 +++++ .../ci-selection-gap-declarations-6457-779c70a2.md | 5 +++++ .../ci-selection-gap-declarations-6457-77ef8a3f.md | 5 +++++ .../speech/ci-selection-gap-declarations-6457-0139c624.md | 5 +++++ .../testing/ci-selection-gap-declarations-6457-0c46e032.md | 5 +++++ .../ci-selection-gap-declarations-6457-3b66afaa.md | 5 +++++ .../ci-selection-gap-declarations-6457-a6a4d9bb.md | 5 +++++ .../ci-selection-gap-declarations-6457-f63abe2d.md | 5 +++++ .../wizard/ci-selection-gap-declarations-6457-97fac2b4.md | 5 +++++ .../ci-selection-gap-declarations-6457-f8839f57.md | 5 +++++ 43 files changed, 215 insertions(+) create mode 100644 .changes/actionlint/ci-selection-gap-declarations-6457-5aa475bb.md create mode 100644 .changes/animation/ci-selection-gap-declarations-6457-0346e98c.md create mode 100644 .changes/architecture/ci-selection-gap-declarations-6457-dfb4213e.md create mode 100644 .changes/autonomy/ci-selection-gap-declarations-6457-781d539e.md create mode 100644 .changes/bash-format/ci-selection-gap-declarations-6457-ce48a644.md create mode 100644 .changes/biome-format/ci-selection-gap-declarations-6457-38dbc180.md create mode 100644 .changes/code-metrics/ci-selection-gap-declarations-6457-eabd0f4b.md create mode 100644 .changes/code-tidying/ci-selection-gap-declarations-6457-247cdcbf.md create mode 100644 .changes/context-budget/ci-selection-gap-declarations-6457-87f0e547.md create mode 100644 .changes/discovery/ci-selection-gap-declarations-6457-6afe6e97.md create mode 100644 .changes/disk-hygiene/ci-selection-gap-declarations-6457-5101169a.md create mode 100644 .changes/docs-hygiene/ci-selection-gap-declarations-6457-03163494.md create mode 100644 .changes/docs-naming/ci-selection-gap-declarations-6457-fb6bd772.md create mode 100644 .changes/education/ci-selection-gap-declarations-6457-4f2a9a33.md create mode 100644 .changes/eol-normalizer/ci-selection-gap-declarations-6457-a1176566.md create mode 100644 .changes/evals/ci-selection-gap-declarations-6457-09c0ad8c.md create mode 100644 .changes/event-storming/ci-selection-gap-declarations-6457-a510fe3f.md create mode 100644 .changes/explainer-video/ci-selection-gap-declarations-6457-bc898bc0.md create mode 100644 .changes/go-format/ci-selection-gap-declarations-6457-b23cdd27.md create mode 100644 .changes/guardrails/ci-selection-gap-declarations-6457-a7b6ee7e.md create mode 100644 .changes/harness-config/ci-selection-gap-declarations-6457-7f3b0c8d.md create mode 100644 .changes/harness-memory/ci-selection-gap-declarations-6457-cc460d18.md create mode 100644 .changes/harness-ops/ci-selection-gap-declarations-6457-2f03e55a.md create mode 100644 .changes/knowledge/ci-selection-gap-declarations-6457-deb3d90c.md create mode 100644 .changes/markdown-format/ci-selection-gap-declarations-6457-0a96a9df.md create mode 100644 .changes/multi-agent/ci-selection-gap-declarations-6457-93566999.md create mode 100644 .changes/pixel-art/ci-selection-gap-declarations-6457-d83fcc9f.md create mode 100644 .changes/planning/ci-selection-gap-declarations-6457-6543d37d.md create mode 100644 .changes/playbooks/ci-selection-gap-declarations-6457-1d127e17.md create mode 100644 .changes/powershell-format/ci-selection-gap-declarations-6457-81b1fe2b.md create mode 100644 .changes/repo-fleet-hygiene/ci-selection-gap-declarations-6457-01c08b7e.md create mode 100644 .changes/retro-audio/ci-selection-gap-declarations-6457-8832a509.md create mode 100644 .changes/review/ci-selection-gap-declarations-6457-f5679709.md create mode 100644 .changes/ruff-format/ci-selection-gap-declarations-6457-584cdfd1.md create mode 100644 .changes/session-flow/ci-selection-gap-declarations-6457-779c70a2.md create mode 100644 .changes/source-control/ci-selection-gap-declarations-6457-77ef8a3f.md create mode 100644 .changes/speech/ci-selection-gap-declarations-6457-0139c624.md create mode 100644 .changes/testing/ci-selection-gap-declarations-6457-0c46e032.md create mode 100644 .changes/typos-format/ci-selection-gap-declarations-6457-3b66afaa.md create mode 100644 .changes/user-interface/ci-selection-gap-declarations-6457-a6a4d9bb.md create mode 100644 .changes/visualization/ci-selection-gap-declarations-6457-f63abe2d.md create mode 100644 .changes/wizard/ci-selection-gap-declarations-6457-97fac2b4.md create mode 100644 .changes/work-items/ci-selection-gap-declarations-6457-f8839f57.md 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. From e9e8457ed93cb9c756e9fbf921548d65fe0f7694 Mon Sep 17 00:00:00 2001 From: Kyle Sexton <153232337+kyle-sexton@users.noreply.github.com> Date: Sat, 10 Oct 2026 12:24:13 -0400 Subject: [PATCH 08/22] ci: run the selection audit's suites in the environment test-bash gives them Two suites failed under the audit and so read less than they do in CI: check-fleet-audit-doc-grammar.test.sh proves against an old commit a shallow clone lacks, and check-read-caller-keys.test.sh needs the runner-policy yaml package. The audit now checks out full history and installs that package, as test-bash does. Its timeout also kills strace when a TERM leaves it waiting on a daemon a suite left behind. Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_01TRhsMPSTMFo99ik7gkdgQt --- .github/workflows/maintenance-audit-test-selection.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.github/workflows/maintenance-audit-test-selection.yml b/.github/workflows/maintenance-audit-test-selection.yml index b4a89f3033..2961f2bd16 100644 --- a/.github/workflows/maintenance-audit-test-selection.yml +++ b/.github/workflows/maintenance-audit-test-selection.yml @@ -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 From aab3bdabbcaf8a00170d25a37c99b13c42584945 Mon Sep 17 00:00:00 2001 From: Kyle Sexton <153232337+kyle-sexton@users.noreply.github.com> Date: Sat, 10 Oct 2026 12:41:05 -0400 Subject: [PATCH 09/22] ci: declare two reads the shard 2 trace found check-snippets.test.sh runs gate_harness.py through its pytest suite, and cant-fail-scan.test.sh reads the testing plugin's hooks.json. Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_01TRhsMPSTMFo99ik7gkdgQt --- .../skills/docpage-digest/scripts/check-snippets.test.sh | 1 + plugins/testing/skills/audit/scripts/cant-fail-scan.test.sh | 1 + 2 files changed, 2 insertions(+) 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 04dafdb270..ed88f6237a 100755 --- a/plugins/knowledge/skills/docpage-digest/scripts/check-snippets.test.sh +++ b/plugins/knowledge/skills/docpage-digest/scripts/check-snippets.test.sh @@ -2,6 +2,7 @@ # 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/testing/skills/audit/scripts/cant-fail-scan.test.sh b/plugins/testing/skills/audit/scripts/cant-fail-scan.test.sh index df36cc9c9f..51e5a0ce78 100755 --- a/plugins/testing/skills/audit/scripts/cant-fail-scan.test.sh +++ b/plugins/testing/skills/audit/scripts/cant-fail-scan.test.sh @@ -5,6 +5,7 @@ # 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 From 61c884cc5f6fcdb3b3a317a7b187dc8ae00d7f27 Mon Sep 17 00:00:00 2001 From: Kyle Sexton <153232337+kyle-sexton@users.noreply.github.com> Date: Sat, 10 Oct 2026 13:09:18 -0400 Subject: [PATCH 10/22] ci: stop two repo suites reading the live tree and declare two more reads check-loop-lane-floor-drift.test.sh read the data-carrier paths from the gate's --list, which scans every tracked file; it now reads them from the gate's DATA_CARRIERS array. check-detector-eval-coverage.test.sh drops its live --check case, which read every skill script: the detector_eval_coverage gate step runs the same --check whenever one of those files changes. generate-adapter.test.sh declares the adapter specs it reads, and surface.test.sh the planning manifest. Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_01TRhsMPSTMFo99ik7gkdgQt --- plugins/planning/surface/surface.test.sh | 1 + .../scripts/generate-adapter.test.sh | 1 + scripts/check-detector-eval-coverage.test.sh | 26 +++---------------- scripts/check-loop-lane-floor-drift.test.sh | 14 +++++----- 4 files changed, 14 insertions(+), 28 deletions(-) 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/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/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-loop-lane-floor-drift.test.sh b/scripts/check-loop-lane-floor-drift.test.sh index 6e432c998b..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. From 2a0dd330fe3aee122772e89cb4f346934c58ed0a Mon Sep 17 00:00:00 2001 From: Kyle Sexton <153232337+kyle-sexton@users.noreply.github.com> Date: Sat, 10 Oct 2026 13:13:41 -0400 Subject: [PATCH 11/22] ci: leave hook-census.test.sh untraced and declare one more read hook-census.test.sh runs strace itself, which fails under the audit's strace as selection-audit.test.sh does, so the audit leaves both untraced. parse-transcript.test.sh declares transcript_reader.py, which its parser imports. Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_01TRhsMPSTMFo99ik7gkdgQt --- .../skills/retro/scripts/parse-transcript.test.sh | 1 + scripts/selection-audit.sh | 11 ++++++----- 2 files changed, 7 insertions(+), 5 deletions(-) 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 fd7ac7f4f5..bc7bd386a4 100755 --- a/plugins/session-flow/skills/retro/scripts/parse-transcript.test.sh +++ b/plugins/session-flow/skills/retro/scripts/parse-transcript.test.sh @@ -4,6 +4,7 @@ # 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/scripts/selection-audit.sh b/scripts/selection-audit.sh index 5c65daa153..4cc3a3bf92 100755 --- a/scripts/selection-audit.sh +++ b/scripts/selection-audit.sh @@ -9,9 +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, but this audit's own -# suite; --shard keeps suites I, I+N, I+2N, ... of the sorted list. Each -# suite gets 600 s under strace, and the slowest start first. +# /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 @@ -190,9 +191,9 @@ 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. - # This suite runs strace itself, which fails under the audit's own strace. + # 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$' | - grep -vxF scripts/selection-audit.test.sh | 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 From cb274edf69e8414e3e6460b58efc2a215a9044ec Mon Sep 17 00:00:00 2001 From: Kyle Sexton <153232337+kyle-sexton@users.noreply.github.com> Date: Sat, 10 Oct 2026 15:11:41 -0400 Subject: [PATCH 12/22] ci: run the review lanes on the ready head Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_01TRhsMPSTMFo99ik7gkdgQt From 6330028a301456ab054ad93e1781a96249a68da3 Mon Sep 17 00:00:00 2001 From: Kyle Sexton <153232337+kyle-sexton@users.noreply.github.com> Date: Sat, 10 Oct 2026 15:41:51 -0400 Subject: [PATCH 13/22] fix: restore suite exec bits and release user-experience 0.1.1 Eleven suites lost their executable bit in an earlier commit of this branch. user-experience is not in fragment mode, so its suite declarations ship as a 0.1.1 patch release. Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_01TRhsMPSTMFo99ik7gkdgQt --- lib/prerequisites.test.sh | 0 .../skills/sync/scripts/sync-fleet.test.sh | 0 plugins/user-experience/.claude-plugin/plugin.json | 2 +- plugins/user-experience/CHANGELOG.md | 7 +++++++ scripts/check-guardrails-ps-differential.test.sh | 0 scripts/check-hooks-description.test.sh | 0 scripts/check-lane-coverage.test.sh | 0 scripts/check-summary-reader-parity.test.sh | 0 scripts/gen-hook-event-registry.test.sh | 0 scripts/lib/fixture-tree.test.sh | 0 scripts/node-notice-rows.test.sh | 0 scripts/run-outside-node-suites.test.sh | 0 scripts/vendor-gitleaks-rules.test.sh | 0 13 files changed, 8 insertions(+), 1 deletion(-) mode change 100644 => 100755 lib/prerequisites.test.sh mode change 100644 => 100755 plugins/repo-fleet-hygiene/skills/sync/scripts/sync-fleet.test.sh mode change 100644 => 100755 scripts/check-guardrails-ps-differential.test.sh mode change 100644 => 100755 scripts/check-hooks-description.test.sh mode change 100644 => 100755 scripts/check-lane-coverage.test.sh mode change 100644 => 100755 scripts/check-summary-reader-parity.test.sh mode change 100644 => 100755 scripts/gen-hook-event-registry.test.sh mode change 100644 => 100755 scripts/lib/fixture-tree.test.sh mode change 100644 => 100755 scripts/node-notice-rows.test.sh mode change 100644 => 100755 scripts/run-outside-node-suites.test.sh mode change 100644 => 100755 scripts/vendor-gitleaks-rules.test.sh diff --git a/lib/prerequisites.test.sh b/lib/prerequisites.test.sh old mode 100644 new mode 100755 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 old mode 100644 new mode 100755 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/scripts/check-guardrails-ps-differential.test.sh b/scripts/check-guardrails-ps-differential.test.sh old mode 100644 new mode 100755 diff --git a/scripts/check-hooks-description.test.sh b/scripts/check-hooks-description.test.sh old mode 100644 new mode 100755 diff --git a/scripts/check-lane-coverage.test.sh b/scripts/check-lane-coverage.test.sh old mode 100644 new mode 100755 diff --git a/scripts/check-summary-reader-parity.test.sh b/scripts/check-summary-reader-parity.test.sh old mode 100644 new mode 100755 diff --git a/scripts/gen-hook-event-registry.test.sh b/scripts/gen-hook-event-registry.test.sh old mode 100644 new mode 100755 diff --git a/scripts/lib/fixture-tree.test.sh b/scripts/lib/fixture-tree.test.sh old mode 100644 new mode 100755 diff --git a/scripts/node-notice-rows.test.sh b/scripts/node-notice-rows.test.sh old mode 100644 new mode 100755 diff --git a/scripts/run-outside-node-suites.test.sh b/scripts/run-outside-node-suites.test.sh old mode 100644 new mode 100755 diff --git a/scripts/vendor-gitleaks-rules.test.sh b/scripts/vendor-gitleaks-rules.test.sh old mode 100644 new mode 100755 From c7bba483acba828b595dfc23e6492c60763693bb Mon Sep 17 00:00:00 2001 From: Kyle Sexton <153232337+kyle-sexton@users.noreply.github.com> Date: Sat, 10 Oct 2026 16:03:40 -0400 Subject: [PATCH 14/22] ci: declare the speech manifest test_elevenlabs.py reads Main's hardened ElevenLabs backend reads the plugin manifest for its user agent, which the shard 1 trace of the merged tree found. Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_01TRhsMPSTMFo99ik7gkdgQt --- plugins/speech/scripts/test_elevenlabs.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/speech/scripts/test_elevenlabs.py b/plugins/speech/scripts/test_elevenlabs.py index 112eabaa8d..5da40d0732 100644 --- a/plugins/speech/scripts/test_elevenlabs.py +++ b/plugins/speech/scripts/test_elevenlabs.py @@ -1,4 +1,4 @@ -# test-scope: plugins/speech/prerequisites.json +# 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.""" From 093d167440fb90bb524f625b8ee7ea28cb2f38e4 Mon Sep 17 00:00:00 2001 From: Kyle Sexton <153232337+kyle-sexton@users.noreply.github.com> Date: Sat, 10 Oct 2026 16:26:41 -0400 Subject: [PATCH 15/22] ci: declare four reads a full trace of the merged tree found speech.test.sh runs test_elevenlabs.py, which reads the speech manifest; build-variants.test.sh reads the vision eval scaffolds; and two GitHub adapter suites read the repository's .work-item-tracker.json through the adapter's config lookup. Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_01TRhsMPSTMFo99ik7gkdgQt --- plugins/speech/scripts/speech.test.sh | 1 + plugins/testing/evals/ui-defects/build-variants.test.sh | 1 + .../tools/work-item-tracker/adapters/github/common.test.sh | 1 + .../work-item-tracker/adapters/github/list-sub-items.test.sh | 1 + 4 files changed, 4 insertions(+) diff --git a/plugins/speech/scripts/speech.test.sh b/plugins/speech/scripts/speech.test.sh index 5a66176a9f..e019021743 100755 --- a/plugins/speech/scripts/speech.test.sh +++ b/plugins/speech/scripts/speech.test.sh @@ -7,6 +7,7 @@ # 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/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/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 From dc048ad882da8d22369ba51b155ee6368c0d7983 Mon Sep 17 00:00:00 2001 From: Kyle Sexton <153232337+kyle-sexton@users.noreply.github.com> Date: Sat, 10 Oct 2026 17:34:48 -0400 Subject: [PATCH 16/22] ci: give the selection audit's selector queries room for a wide shard Shard 5 reads about 1,600 distinct files, and its selector queries ran past the 1200 s budget, leaving 17 files unchecked. The budget grows to 1500 s and the job timeout to 35 minutes. Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_01TRhsMPSTMFo99ik7gkdgQt --- .github/workflows/maintenance-audit-test-selection.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/maintenance-audit-test-selection.yml b/.github/workflows/maintenance-audit-test-selection.yml index 2961f2bd16..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 30-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: 30 + timeout-minutes: 35 permissions: contents: read concurrency: @@ -115,8 +115,8 @@ jobs: chmod +x "$RUNNER_TEMP/duckdb/duckdb" - name: Test the selection audit run: bash scripts/selection-audit.test.sh - # Budget 1200 s: suites start for the first 720 s, selector queries stop - # at 1200 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 @@ -131,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 1200 \ + 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() }} From c67cc53a0d5e0aa7b3bdb74a0329287e701b87f9 Mon Sep 17 00:00:00 2001 From: Kyle Sexton <153232337+kyle-sexton@users.noreply.github.com> Date: Sat, 10 Oct 2026 18:06:28 -0400 Subject: [PATCH 17/22] test(harness-config): match inventory lines without a SIGPIPE-prone pipe assert_line piped the output into grep -q, which exits on its first match; under pipefail the writer's broken pipe failed the assertion now and then, as the shard 6 selection-audit trace showed. Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_01TRhsMPSTMFo99ik7gkdgQt --- .../skills/audit-automation-gaps/scripts/inventory.test.sh | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) 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 57a750734b..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" From 5a9e268a05f7f02d438fac4b6c5433cc3b13bc50 Mon Sep 17 00:00:00 2001 From: Kyle Sexton <153232337+kyle-sexton@users.noreply.github.com> Date: Sat, 10 Oct 2026 18:16:54 -0400 Subject: [PATCH 18/22] ci: drop the inventory suite from the piped early-exit grep baseline Its last piped grep -q is gone, so the gate reports the baseline line stale. Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_01TRhsMPSTMFo99ik7gkdgQt --- scripts/pipefail-grep-q-baseline.txt | 1 - 1 file changed, 1 deletion(-) diff --git a/scripts/pipefail-grep-q-baseline.txt b/scripts/pipefail-grep-q-baseline.txt index eaa4874320..afbfcb52b9 100644 --- a/scripts/pipefail-grep-q-baseline.txt +++ b/scripts/pipefail-grep-q-baseline.txt @@ -44,7 +44,6 @@ 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 From 9bf6335f7e5f407561453c5daa38cdbbe767dd47 Mon Sep 17 00:00:00 2001 From: Kyle Sexton <153232337+kyle-sexton@users.noreply.github.com> Date: Sat, 10 Oct 2026 19:47:20 -0400 Subject: [PATCH 19/22] test(harness-config): match claim templates without a SIGPIPE-prone pipe finding-ids.test.sh piped the claim table into grep -q, which exits on its first match; under pipefail the writer's broken pipe failed the check now and then, as the shard 2 selection-audit trace showed. Its baseline line in scripts/pipefail-grep-q-baseline.txt goes with it. Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_01TRhsMPSTMFo99ik7gkdgQt --- .../skills/audit-instructions/scripts/finding-ids.test.sh | 2 +- scripts/pipefail-grep-q-baseline.txt | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) 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/scripts/pipefail-grep-q-baseline.txt b/scripts/pipefail-grep-q-baseline.txt index afbfcb52b9..73f13a0eb9 100644 --- a/scripts/pipefail-grep-q-baseline.txt +++ b/scripts/pipefail-grep-q-baseline.txt @@ -45,7 +45,6 @@ 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-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 From 737709990ef6e015b0a595b377962ce5f03b06e7 Mon Sep 17 00:00:00 2001 From: Kyle Sexton <153232337+kyle-sexton@users.noreply.github.com> Date: Sat, 10 Oct 2026 20:36:55 -0400 Subject: [PATCH 20/22] test(testing): keep the TERM-deaf judge stub out of later call counts The TERM-deaf stub wrote a call-*.args marker and ignored the TERM meant to stop it, so a background run that started late counted as a call of the next case, and "no third attempt" failed now and then under the selection audit's strace. Its marker no longer matches the call count, and it is stopped with KILL. Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_01TRhsMPSTMFo99ik7gkdgQt --- plugins/testing/hooks/test-judge.test.sh | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/plugins/testing/hooks/test-judge.test.sh b/plugins/testing/hooks/test-judge.test.sh index 51c659b84a..96a5ee6c84 100755 --- a/plugins/testing/hooks/test-judge.test.sh +++ b/plugins/testing/hooks/test-judge.test.sh @@ -350,7 +350,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\":[]}"}' @@ -369,7 +369,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. From 08ce85a576a8efba71aeb458c3406d763f61a17c Mon Sep 17 00:00:00 2001 From: Kyle Sexton <153232337+kyle-sexton@users.noreply.github.com> Date: Sat, 10 Oct 2026 22:32:42 -0400 Subject: [PATCH 21/22] test(testing): match cant-fail-scan output without a SIGPIPE-prone pipe assert_matches piped the output into grep -q, which exits on its first match; under pipefail the writer's broken pipe failed the assertion now and then, as the shard 0 selection-audit trace showed. Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_01TRhsMPSTMFo99ik7gkdgQt --- plugins/testing/skills/audit/scripts/cant-fail-scan.test.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 f79c9dfb1c..8440fda914 100755 --- a/plugins/testing/skills/audit/scripts/cant-fail-scan.test.sh +++ b/plugins/testing/skills/audit/scripts/cant-fail-scan.test.sh @@ -50,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" From 2c64437065787f9d22c5549e724816a34caf69e2 Mon Sep 17 00:00:00 2001 From: Kyle Sexton <153232337+kyle-sexton@users.noreply.github.com> Date: Sat, 10 Oct 2026 23:22:30 -0400 Subject: [PATCH 22/22] ci: record the planning surface suite's real duration for leg balance #6836 put playwright-cli on test-bash's PATH, so surface.test.sh now runs its browser checks: 481 checks in about 390 s locally, against the 1 s scripts/suite-seconds.txt recorded when they skipped. Planned at 1 s it shared a leg with a hundred other suites and ran past the leg's 9-minute timeout. Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_01TRhsMPSTMFo99ik7gkdgQt --- scripts/suite-seconds.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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