diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 25e7afef8c..35bfc6b744 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1866,7 +1866,7 @@ jobs: py=$(printf '%s\n' "$notrun" | grep -E '\.py$' || true) rest=$(printf '%s\n' "$notrun" | grep -Ev '(^$|\.py$)' || true) if [ -n "$py" ]; then - printf '%s\n' "$py" | tr '\n' '\0' | xargs -0 python -m pytest -q -- + printf '%s\n' "$py" | tr '\n' '\0' | xargs -0 python -m pytest -q -o tmp_path_retention_policy=none -- fi if [ -n "$rest" ]; then printf '%s\n' "$rest" >"$RUNNER_TEMP/outside-node-paths.txt" diff --git a/lib/hook-utils.test.sh b/lib/hook-utils.test.sh index cebc6bf6c7..33e991eb2c 100755 --- a/lib/hook-utils.test.sh +++ b/lib/hook-utils.test.sh @@ -1538,7 +1538,7 @@ fi FAKEBIN17="$(make_stub_bin)" run17b() { local args="$1" - CLAUDE_PLUGIN_DATA="$(mktemp -d)" "$BASH" -c ' + CLAUDE_PLUGIN_DATA="$(mktemp -d "$WORK/data17b.XXXXXX")" "$BASH" -c ' PATH="'"$FAKEBIN17"'" source "'"$HOOK_DIR"'/hook-utils.sh" hook::require_jq_blocking '"$args"' diff --git a/plugins/actionlint/.claude-plugin/plugin.json b/plugins/actionlint/.claude-plugin/plugin.json index 17a23e60fd..2ea228e7aa 100644 --- a/plugins/actionlint/.claude-plugin/plugin.json +++ b/plugins/actionlint/.claude-plugin/plugin.json @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/claude-code-plugin-manifest.json", "name": "actionlint", - "version": "0.11.3", + "version": "0.11.4", "description": "Lint GitHub Actions workflow files on edit via actionlint, surfacing findings as advisory context.", "author": { "name": "Melodic Software", diff --git a/plugins/actionlint/CHANGELOG.md b/plugins/actionlint/CHANGELOG.md index 3c76382c39..2434cb8a5f 100644 --- a/plugins/actionlint/CHANGELOG.md +++ b/plugins/actionlint/CHANGELOG.md @@ -3,6 +3,12 @@ All notable changes to the `actionlint` plugin are documented here. Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/); this plugin uses semantic versioning. +## [0.11.4] - 2026-09-30 + +### Changed + +- Test-only: the suites remove their temporary directories on exit. No behavior change. + ## [0.11.3] - 2026-09-30 ### Changed diff --git a/plugins/actionlint/hooks/actionlint-check.test.sh b/plugins/actionlint/hooks/actionlint-check.test.sh index 90e3dfe8ad..49f4f00543 100755 --- a/plugins/actionlint/hooks/actionlint-check.test.sh +++ b/plugins/actionlint/hooks/actionlint-check.test.sh @@ -233,7 +233,7 @@ ERRBIN="$(mktemp -d "$WORK/errbin.XXXXXX")" wrap_real_tools "$ERRBIN" printf '#!/bin/sh\necho "fatal: simulated actionlint failure" >&2\nexit 3\n' >"$ERRBIN/actionlint" chmod +x "$ERRBIN/actionlint" -ERR_TEL="$(mktemp)" +ERR_TEL="$(mktemp "$WORK/tel.XXXXXX")" ERR_SINK="$(make_sink "cat >\"$ERR_TEL\"")" OUT_ERR=$( cd "$UNRELATED" || exit 1 @@ -273,7 +273,7 @@ else fi # --- Stub sink + violation -> envelope status ok with findings -------------- -TEL="$(mktemp)" +TEL="$(mktemp "$WORK/tel.XXXXXX")" SINK="$(make_sink "cat >\"$TEL\"")" run_hook_env "$REPO/.github/workflows/violation.yml" CLAUDE_PLUGIN_OPTION_ACTIONLINT_ENABLED=true HOOK_TELEMETRY_SINK="$SINK" >/dev/null wait_for_sink "$TEL" @@ -293,7 +293,7 @@ fi rm -f "$TEL" # --- Stub sink + clean file -> status ok, findings [] ----------------------- -TELC="$(mktemp)" +TELC="$(mktemp "$WORK/tel.XXXXXX")" SINKC="$(make_sink "cat >\"$TELC\"")" run_hook_env "$REPO/.github/workflows/clean.yml" CLAUDE_PLUGIN_OPTION_ACTIONLINT_ENABLED=true HOOK_TELEMETRY_SINK="$SINKC" >/dev/null wait_for_sink "$TELC" @@ -309,7 +309,7 @@ rm -f "$TELC" # The fake-bin dir shadows actionlint. First run must emit the skip notice on # both channels; a second run in the same session (same CLAUDE_PLUGIN_DATA + # session_id) must be silent; telemetry still records status "skipped". -ABSENT_TEL="$(mktemp)" +ABSENT_TEL="$(mktemp "$WORK/tel.XXXXXX")" ABSENT_SINK="$(make_sink "cat >\"$ABSENT_TEL\"")" FAKEBIN="$(mktemp -d "$WORK/fakebin.XXXXXX")" wrap_real_tools "$FAKEBIN" diff --git a/plugins/ai-briefing/.claude-plugin/plugin.json b/plugins/ai-briefing/.claude-plugin/plugin.json index e67f629175..25a0c06746 100644 --- a/plugins/ai-briefing/.claude-plugin/plugin.json +++ b/plugins/ai-briefing/.claude-plugin/plugin.json @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/claude-code-plugin-manifest.json", "name": "ai-briefing", - "version": "0.7.47", + "version": "0.7.48", "description": "Build source-backed AI-industry briefings from official vendor publications, configured RSS/Atom feeds, GitHub releases, reputable secondary reporting, and user-supplied URLs. Deduplicate, rank, and present results as markdown or optional HTML/PPTX decks, with repository-owned profile, audience, and brand configuration. Automated X/Twitter collection is disabled; Playwright is used only for deterministic local rendering.", "author": { "name": "Melodic Software", diff --git a/plugins/ai-briefing/CHANGELOG.md b/plugins/ai-briefing/CHANGELOG.md index 7d6e4ec886..0bb5db640f 100644 --- a/plugins/ai-briefing/CHANGELOG.md +++ b/plugins/ai-briefing/CHANGELOG.md @@ -3,6 +3,12 @@ All notable changes to the `ai-briefing` plugin are documented here. Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/); this plugin uses semantic versioning. +## [0.7.48] - 2026-09-30 + +### Changed + +- Test-only: the suites remove their temporary directories on exit. No behavior change. + ## [0.7.47] - 2026-09-29 ### Fixed diff --git a/plugins/ai-briefing/skills/generate/output/build/test/cli-entry.test.js b/plugins/ai-briefing/skills/generate/output/build/test/cli-entry.test.js index 5480814ceb..25b0b6ea4f 100644 --- a/plugins/ai-briefing/skills/generate/output/build/test/cli-entry.test.js +++ b/plugins/ai-briefing/skills/generate/output/build/test/cli-entry.test.js @@ -1,14 +1,25 @@ import assert from "node:assert/strict"; import { spawnSync } from "node:child_process"; -import { mkdtempSync, readFileSync, symlinkSync, writeFileSync } from "node:fs"; +import { mkdtempSync, readFileSync, rmSync, symlinkSync, writeFileSync } from "node:fs"; import { tmpdir } from "node:os"; import path from "node:path"; -import test from "node:test"; +import test, { after } from "node:test"; import { pathToFileURL } from "node:url"; const buildDir = path.resolve(path.dirname(new URL(import.meta.url).pathname), ".."); const script = path.join(buildDir, "emit-slides-data.js"); +const scratchDirs = []; +after(() => { + for (const dir of scratchDirs) rmSync(dir, { recursive: true, force: true }); +}); + +function scratch(prefix) { + const dir = mkdtempSync(path.join(tmpdir(), prefix)); + scratchDirs.push(dir); + return dir; +} + function run(args, env = {}) { return spawnSync(process.execPath, [script, ...args], { encoding: "utf8", @@ -38,7 +49,7 @@ test("a missing briefing exits 1", () => { }); test("argv writes slides-data.js and formats an ASCII-arrow window", () => { - const root = mkdtempSync(path.join(tmpdir(), "slides-")); + const root = scratch("slides-"); const briefing = path.join(root, "briefing.md"); const out = path.join(root, "slides-data.js"); writeFileSync( @@ -68,7 +79,7 @@ test("argv writes slides-data.js and formats an ASCII-arrow window", () => { }); test("runs as the entrypoint through a symlinked build directory", () => { - const link = path.join(mkdtempSync(path.join(tmpdir(), "emit-link-")), "build"); + const link = path.join(scratch("emit-link-"), "build"); symlinkSync(buildDir, link, "junction"); const missing = path.join(tmpdir(), "no-such-briefing.md"); const result = spawnSync( diff --git a/plugins/claude-config/.claude-plugin/plugin.json b/plugins/claude-config/.claude-plugin/plugin.json index 5f5c69562f..075a225842 100644 --- a/plugins/claude-config/.claude-plugin/plugin.json +++ b/plugins/claude-config/.claude-plugin/plugin.json @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/claude-code-plugin-manifest.json", "name": "claude-config", - "version": "0.55.1", + "version": "0.55.2", "description": "Nine configuration-health skills (plus setup) for a repo's Claude Code configuration: audit (settings.json / .mcp.json / hooks / plugins / permissions drift), audit-automation-gaps (evidence-gated verdicts on automation gaps), audit-permission-grants (allow-rule / allowed-tools grants for auto-mode durability and portability), audit-permission-state (the permission rules actually in effect: every settings scope merged with per-rule provenance, what auto mode drops on entry, config written where nothing reads it, and which managed intents are enforced versus loosenable), draft-auto-mode-rules (interview and draft a paste-ready autoMode classifier block; prints only, never writes), audit-instructions (locally-owned instruction surfaces vs current model capability, proposing removals/rewrites of instructions the model no longer needs, and detecting cross-surface instruction conflicts), audit-prompting-postures (the additive lane: posture guidance the prompting guide says a component's purpose needs but the component does not carry), audit-pass (one coordinated, ordered, resumable pass over a named target: three-scope inventory, run-time-derived exclusion set, stable finding identity, suppression memory, resume, one human gate, delegating every check to the plugin that owns it), and unhobble (the empirical bare-baseline experiment: reversibly strip a repo's standing instructions, log real stumbles against the current model, re-add only what evidence earns).", "author": { "name": "Melodic Software", diff --git a/plugins/claude-config/CHANGELOG.md b/plugins/claude-config/CHANGELOG.md index 89e2d37306..40f4578d3a 100644 --- a/plugins/claude-config/CHANGELOG.md +++ b/plugins/claude-config/CHANGELOG.md @@ -5,6 +5,12 @@ All notable changes to the `claude-config` plugin are documented here. Format fo Versions 0.51.8 to 0.51.9 and 0.51.11 to 0.51.14 were reserved by parallel branches and never released. +## [0.55.2] - 2026-09-30 + +### Changed + +- `automode-entry-diff.sh --oracle` removes its scratch directory on exit; the suites remove their temporary directories. + ## [0.55.1] - 2026-09-30 ### Changed diff --git a/plugins/claude-config/skills/audit-permission-state/scripts/automode-entry-diff.sh b/plugins/claude-config/skills/audit-permission-state/scripts/automode-entry-diff.sh index 02925bb583..b0ffe7cf1d 100755 --- a/plugins/claude-config/skills/audit-permission-state/scripts/automode-entry-diff.sh +++ b/plugins/claude-config/skills/audit-permission-state/scripts/automode-entry-diff.sh @@ -324,6 +324,7 @@ EOF echo "oracle UNAVAILABLE: could not create a scratch directory — the prediction above stands, uncorroborated." exit 0 } + trap 'rm -rf "$scratch"' EXIT capture="$scratch/capture.log" # `--permission-mode auto` is passed so the probe does not depend on the # consumer's own defaultMode. What was actually MEASURED is narrower than that diff --git a/plugins/code-metrics/.claude-plugin/plugin.json b/plugins/code-metrics/.claude-plugin/plugin.json index 9cafd6ca1d..1601f571f8 100644 --- a/plugins/code-metrics/.claude-plugin/plugin.json +++ b/plugins/code-metrics/.claude-plugin/plugin.json @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/claude-code-plugin-manifest.json", "name": "code-metrics", - "version": "0.4.4", + "version": "0.4.5", "description": "Read-only code measures for a change, with cited references and no verdict: lines per file (audit-size), cyclomatic, cognitive, and Halstead complexity (audit-complexity), duplication with sanctioned-replication exclusions (audit-duplication), coverage per function with CRAP from existing lcov, Cobertura, coverage.py, or Go artifacts (audit-coverage), type debt for TypeScript and Python (audit-type-debt), the literacy router for what each number can and cannot say (principles), and a setup skill for the consumer's .claude/code-metrics.yaml. Runs external collectors only when they already resolve, never installs, never runs tests, never emits a finding.", "author": { "name": "Melodic Software", diff --git a/plugins/code-metrics/CHANGELOG.md b/plugins/code-metrics/CHANGELOG.md index b8e542b868..b4793d1a8c 100644 --- a/plugins/code-metrics/CHANGELOG.md +++ b/plugins/code-metrics/CHANGELOG.md @@ -3,6 +3,12 @@ All notable changes to the `code-metrics` plugin are documented here. Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/); this plugin uses semantic versioning. +## [0.4.5] - 2026-09-30 + +### Changed + +- README: the pytest command passes `-o tmp_path_retention_policy=none` so test directories are not kept under `$TMPDIR`. + ## [0.4.4] - 2026-09-30 ### Fixed diff --git a/plugins/code-metrics/README.md b/plugins/code-metrics/README.md index c5e4e8e009..f66cdd3479 100644 --- a/plugins/code-metrics/README.md +++ b/plugins/code-metrics/README.md @@ -135,11 +135,12 @@ is named explicitly or listed under `coverage.artifacts` in the configuration. ## Testing the plugin The Python suites are the `test_*.py` files beside the scripts they cover, and `python3 -m pytest -q` -from this directory runs all of them. To measure them, run the same command under coverage.py from -this directory: +from this directory runs all of them. Add `-o tmp_path_retention_policy=none` so pytest deletes each +test's `tmp_path` directories instead of keeping the last three runs under `$TMPDIR/pytest-of-`. +To measure them, run the same command under coverage.py from this directory: ```shell -python3 -m coverage run -m pytest -q && python3 -m coverage json +python3 -m coverage run -m pytest -q -o tmp_path_retention_policy=none && python3 -m coverage json ``` The `.coveragerc` here sets `source = .`, so every module under the plugin is reported whether or diff --git a/plugins/docs-hygiene/.claude-plugin/plugin.json b/plugins/docs-hygiene/.claude-plugin/plugin.json index 29c84976f1..edfd50a616 100644 --- a/plugins/docs-hygiene/.claude-plugin/plugin.json +++ b/plugins/docs-hygiene/.claude-plugin/plugin.json @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/claude-code-plugin-manifest.json", "name": "docs-hygiene", - "version": "0.23.20", + "version": "0.23.21", "description": "Documentation-hygiene toolkit: compress (flavor-trim markdown with a semantic-diff safety net), audit-noise (classify markdown noise), extract-ssot (deduplicate repeated content into a single source of truth), audit-encapsulation (detect citations into skill-private surfaces), rename-references (sweep stale references after renames), audit-derivability (classify whether a whole document earns its existence: could a fresh agent re-derive it from the code?), audit-progressive-disclosure (grade instruction files against a load-tier model for split opportunities and hub/spoke disclosure defects), write-for-agents (authoring-time doctrine that fires while agent-consumed markdown is being written), write-for-humans (the same moment for the other reader, covering end-user READMEs, RFCs, release notes and guides, and resolving the consuming project's own style guide first), and a file-name set that plans, applies, and enforces a casing rule across a doc tree: setup (the one configuration surface), audit-file-names (read-only inventory plus the reference sweep), realign-file-names (the executor, one human acceptance per file), and generate-file-name-gate (emits the standalone check that keeps the tree from drifting back).", "author": { "name": "Melodic Software", diff --git a/plugins/docs-hygiene/CHANGELOG.md b/plugins/docs-hygiene/CHANGELOG.md index 08e8cb0f42..7598ad05bc 100644 --- a/plugins/docs-hygiene/CHANGELOG.md +++ b/plugins/docs-hygiene/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog: docs-hygiene plugin +## [0.23.21] - 2026-09-30 + +### Changed + +- Test-only: the suites remove their temporary directories on exit. No behavior change. + ## [0.23.20] - 2026-09-29 ### Changed diff --git a/plugins/docs-hygiene/skills/compress/scripts/audit-scan.test.sh b/plugins/docs-hygiene/skills/compress/scripts/audit-scan.test.sh index d82b7fdc96..c84712d70f 100755 --- a/plugins/docs-hygiene/skills/compress/scripts/audit-scan.test.sh +++ b/plugins/docs-hygiene/skills/compress/scripts/audit-scan.test.sh @@ -48,7 +48,9 @@ assert_classify "lean fixture classifies SKIP" '| SKIP |' "$FIX/audit-fixture-di # is already absolute, so invoking the scanner with that path would match the # old `*/.claude/rules/` glob and never exercise the repo-relative arm. Run # from the temp dir with the relative argument the matcher is supposed to see. -RULES_REL="$(mktemp -d)" +SCRATCH="$(mktemp -d)" +trap 'rm -rf "$SCRATCH"' EXIT +RULES_REL="$SCRATCH/rules" mkdir -p "$RULES_REL/.claude/rules" printf '# rule\n\njust really basically actually simply perhaps somewhat very quite might note that keep in mind\n' >"$RULES_REL/.claude/rules/example.md" # Enough flavor tokens that a missed signal-1 would fall through to COMPRESS @@ -61,13 +63,13 @@ case "$out4" in *'author-time-disciplined path (signal 1)'*) ok "repo-relative .claude/rules classifies as signal 1" ;; *) fail "repo-relative .claude/rules should be signal 1 (got: $out4)" ;; esac -rm -rf "$RULES_REL" # Five path refs on one line, ~500 words: line-count density is 1*1000/500 = 2 # (COMPRESS), occurrence density is 5*1000/500 = 10 > 8 (UNCERTAIN). Flavor # tokens keep the file out of the flavor-density SKIP. The scanner itself must # classify — a regex-only check never exercises path_dens. -OCC="$(mktemp -d)" +OCC="$SCRATCH/occ" +mkdir -p "$OCC" write_padded_md "$OCC/occ.md" \ 'see docs/a.md and docs/b.md and docs/c.md and docs/d.md and docs/e.md. just really basically ' 480 out_occ="$(bash "$SCAN" "$OCC/occ.md" 2>/dev/null)" || true @@ -75,18 +77,17 @@ case "$out_occ" in *'| UNCERTAIN |'*'cross-ref density'*) ok "one-line path refs classify UNCERTAIN by occurrence density" ;; *) fail "one-line path refs should classify UNCERTAIN (got: $out_occ)" ;; esac -rm -rf "$OCC" # `@docs/a.md` is one occurrence. The old `(@|path.ext)` regex emitted `@` and # `docs/a.md`, doubling density over the 8/kw threshold at ~200 words. -AT="$(mktemp -d)" +AT="$SCRATCH/at" +mkdir -p "$AT" write_padded_md "$AT/at.md" 'see @docs/a.md on one line. just really ' 200 out_at="$(bash "$SCAN" "$AT/at.md" 2>/dev/null)" || true case "$out_at" in *'| COMPRESS |'*) ok "@-prefixed path ref counts as one occurrence (COMPRESS)" ;; *) fail "@-prefixed path ref should classify COMPRESS, not double-count (got: $out_at)" ;; esac -rm -rf "$AT" if [[ $FAIL -ne 0 ]]; then echo "$FAIL check(s) failed." >&2 diff --git a/plugins/firecrawl/.claude-plugin/plugin.json b/plugins/firecrawl/.claude-plugin/plugin.json index 3bdeb8648e..d98e975b47 100644 --- a/plugins/firecrawl/.claude-plugin/plugin.json +++ b/plugins/firecrawl/.claude-plugin/plugin.json @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/claude-code-plugin-manifest.json", "name": "firecrawl", - "version": "0.5.20", + "version": "0.5.21", "description": "Web scraping, search, crawling, and file parsing through the firecrawl-cli binary with a write-to-disk-then-Read pattern that keeps large results out of context: a user-facing wrapper skill, a lazy-install setup skill, and a separate gated maintainer update skill tracking the upstream CLI and skill source.", "author": { "name": "Melodic Software", diff --git a/plugins/firecrawl/CHANGELOG.md b/plugins/firecrawl/CHANGELOG.md index 809635a204..1ab7fac7e2 100644 --- a/plugins/firecrawl/CHANGELOG.md +++ b/plugins/firecrawl/CHANGELOG.md @@ -3,6 +3,12 @@ All notable changes to the `firecrawl` plugin are documented here. Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/); this plugin uses semantic versioning. +## [0.5.21] - 2026-09-30 + +### Changed + +- Test-only: the suites remove their temporary directories on exit. No behavior change. + ## [0.5.20] - 2026-09-29 ### Added diff --git a/plugins/firecrawl/skills/update/scripts/update.test.sh b/plugins/firecrawl/skills/update/scripts/update.test.sh index 9a2ef4ee51..c25a5e88ed 100755 --- a/plugins/firecrawl/skills/update/scripts/update.test.sh +++ b/plugins/firecrawl/skills/update/scripts/update.test.sh @@ -62,10 +62,11 @@ assert_contains "unknown flag mentions expected modes" "$unknown_out" "expected" # --- 3. Source-guard: helpers callable when sourced ------------------------------ # Sourcing installs the script's own EXIT trap (cleanup of its TMPDIR_RUN); the -# test tmpdir is removed explicitly at the end instead of via trap. +# trap set after it replaces that one and removes both directories. # shellcheck source=update.sh source "$SCRIPT" 2>/dev/null SOURCED_TMPDIR="$TMPDIR_RUN" +trap 'rm -rf "$TEST_TMPDIR" "$SOURCED_TMPDIR"' EXIT if declare -F recorded_field >/dev/null; then pass "source-guard: helpers exposed after source" else @@ -118,8 +119,6 @@ assert_eq "sha256 helper matches direct call" "$sha_direct" "$sha_via_helper" # --- Final report --------------------------------------------------------------------- -rm -rf "$TEST_TMPDIR" "$SOURCED_TMPDIR" - if [[ "$FAILED" -eq 0 ]]; then printf '\nAll %d checks passed.\n' "$CASE_NUM" exit 0 diff --git a/plugins/instruction-placement/.claude-plugin/plugin.json b/plugins/instruction-placement/.claude-plugin/plugin.json index 21d1113c6c..7a633dacc5 100644 --- a/plugins/instruction-placement/.claude-plugin/plugin.json +++ b/plugins/instruction-placement/.claude-plugin/plugin.json @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/claude-code-plugin-manifest.json", "name": "instruction-placement", - "version": "0.16.5", + "version": "0.16.6", "description": "Routes agent-instruction content to the surface that loads it at the right moment. The audit skill sweeps a repository's instruction layer and its ordinary markdown for content whose scope is narrower than the surface carrying it, meaning conventions keyed to one file type or one subtree sitting in an always-loaded CLAUDE.md or AGENTS.md, and for normative conventions stranded in documentation Claude never loads at all, then classifies each against a routing rubric and proposes a destination whose `paths:` glob is machine-validated before it is ever offered. Safety-class content (irreversible actions, secrets, data integrity, external publication, compliance, agent authority) is hard-denied from demotion and reported as held back rather than proposed, because demotion trades guaranteed presence for conditional presence: a deferred surface is absent until a read matches it, absent after a compaction until that trigger recurs, and never inherited by a subagent, which re-acquires it only by reading a covered path itself. Every accepted move regenerates an always-loaded index of deferred surfaces, which is what keeps a demoted rule discoverable from any context that has not happened to touch a path it covers. The audit is read-only and emits a diffable findings artifact; realignment is a separate skill gated per item with no blanket-approve path; a deterministic check skill gates that every rule glob still resolves and the index is current; a migrate skill moves a repository to AGENTS.md as the content home and keeps a CLAUDE.md shim while one is needed; and a setup skill verifies the one thing no other gate can see: that nothing in the repository stops Claude Code reading the index target, since a CLAUDE.md in the working directory or above it is read instead of the AGENTS.md beside it.", "author": { "name": "Melodic Software", diff --git a/plugins/instruction-placement/CHANGELOG.md b/plugins/instruction-placement/CHANGELOG.md index b7366b140a..285df99b4d 100644 --- a/plugins/instruction-placement/CHANGELOG.md +++ b/plugins/instruction-placement/CHANGELOG.md @@ -3,6 +3,12 @@ All notable changes to the `instruction-placement` plugin are documented here. Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/); this plugin uses semantic versioning. +## [0.16.6] - 2026-09-30 + +### Changed + +- `cutover-check.sh` removes its action-map temp file when the release-map parse fails; the suites remove their temporary directories. + ## [0.16.5] - 2026-09-30 ### Changed diff --git a/plugins/instruction-placement/scripts/precompute.test.sh b/plugins/instruction-placement/scripts/precompute.test.sh index 0aae43d40b..842b391028 100755 --- a/plugins/instruction-placement/scripts/precompute.test.sh +++ b/plugins/instruction-placement/scripts/precompute.test.sh @@ -17,6 +17,8 @@ set -uo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" SCRIPT="$SCRIPT_DIR/precompute.sh" +SCRATCH="$(mktemp -d)" +trap 'rm -rf "$SCRATCH"' EXIT FAILED=0 CASE_NUM=0 @@ -41,7 +43,7 @@ assert_contains() { # as one set: the root tree, a nested package tree, and a symlinked shared set. build_fixture() { local dir - dir="$(mktemp -d)" + dir="$(mktemp -d "$SCRATCH/fx.XXXXXX")" mkdir -p "$dir/.claude/rules" "$dir/packages/api/.claude/rules" \ "$dir/shared-rules" "$dir/libs/.claude" : >"$dir/.claude/rules/root.md" @@ -138,7 +140,7 @@ assert_contains "an untracked nested file is not counted inside a repository" \ # This output lands in a skill header, so every mode must exit 0 and print a # usable value even where the probes have nothing to read. # -------------------------------------------------------------------------- -empty="$(mktemp -d)" +empty="$(mktemp -d "$SCRATCH/fx.XXXXXX")" for mode in audit check realign; do out="$(run_in "$empty" "$mode")" rc=$? @@ -164,8 +166,6 @@ run_in "$repo" realign >/dev/null assert_eq "the probes leave the working tree untouched" \ "$before" "$(git -C "$repo" status --porcelain)" -rm -rf "$repo" "$empty" - printf '\n%d case(s), %d failure(s)\n' "$CASE_NUM" "$FAILED" [[ $FAILED -eq 0 ]] || exit 1 exit 0 diff --git a/plugins/instruction-placement/scripts/verify-load.test.sh b/plugins/instruction-placement/scripts/verify-load.test.sh index e63fe94db5..63fd69eafd 100755 --- a/plugins/instruction-placement/scripts/verify-load.test.sh +++ b/plugins/instruction-placement/scripts/verify-load.test.sh @@ -16,6 +16,8 @@ set -uo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" SCRIPT="$SCRIPT_DIR/verify-load.sh" +SCRATCH="$(mktemp -d)" +trap 'rm -rf "$SCRATCH"' EXIT FAILED=0 CASE_NUM=0 @@ -46,7 +48,7 @@ run() { bash "$SCRIPT" "$@" 2>&1; } # A repository where a path-scoped rule genuinely covers the trigger file. build_fixture() { local dir - dir="$(mktemp -d)" + dir="$(mktemp -d "$SCRATCH/fx.XXXXXX")" mkdir -p "$dir/.claude/rules" "$dir/src" printf 'public class Invoice { }\n' >"$dir/src/Invoice.cs" printf 'export const x = 1;\n' >"$dir/src/client.ts" @@ -177,7 +179,6 @@ if [[ -n "$CLI" ]]; then --expect rules/csharp.md --timeout 300)" \ "$(printf 'EXPECTED\trules/csharp.md\tMET')" fi - rm -rf "$near" else skip "a near-miss expectation is MISSING, not MET" "no Claude Code CLI found" fi @@ -188,8 +189,6 @@ fi dirty="$(git -C "$repo" status --porcelain | wc -l | tr -d ' ')" assert_eq "the probe leaves the repository under test untouched" "0" "$dirty" -rm -rf "$repo" - printf '\n%d case(s), %d failure(s), %d skipped\n' "$CASE_NUM" "$FAILED" "$SKIPPED" [[ $FAILED -eq 0 ]] || exit 1 exit 0 diff --git a/plugins/instruction-placement/skills/migrate/scripts/cutover-check.sh b/plugins/instruction-placement/skills/migrate/scripts/cutover-check.sh index 6cd9dedac6..b658f70023 100755 --- a/plugins/instruction-placement/skills/migrate/scripts/cutover-check.sh +++ b/plugins/instruction-placement/skills/migrate/scripts/cutover-check.sh @@ -230,6 +230,7 @@ section '## `claude-code-action` release to installed CLI version' | print $2 "\t" $3 "\t" $4 }' >"$ACTION_MAP" [[ -s "$ACTION_MAP" ]] || { echo "cutover-check: cannot parse the claude-code-action release map from $SOURCES_MD" >&2 + rm -f "$ACTION_MAP" exit 2 } diff --git a/plugins/knowledge/.claude-plugin/plugin.json b/plugins/knowledge/.claude-plugin/plugin.json index 8485db8b8f..87b8f908a8 100644 --- a/plugins/knowledge/.claude-plugin/plugin.json +++ b/plugins/knowledge/.claude-plugin/plugin.json @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/claude-code-plugin-manifest.json", "name": "knowledge", - "version": "0.14.12", + "version": "0.14.13", "description": "Ingest external knowledge into durable, synthesized artifacts. Ships a book-distillation pipeline (PDF/EPUB into concept-organized, author-attributed skill reference files), a video-digest pipeline (watch a single public video from YouTube or X, formerly Twitter: transcript, link harvest, and repo-applicability synthesis), a course-digest pipeline (extract and synthesize online video courses from Dometrain and Teachable into repo-applicable recommendations), a docpage-digest pipeline (single online documentation page into a verified knowledge slice with dual verification including one cross-vendor verifier, and an interview handoff), and a map-corpus pipeline (multi-resource corpus into a classified link map, deterministic node manifests, gate-verified relevance inventory, and an approved queue of docpage-digest runs), plus a re-runnable setup action; a configurable library directory governs where synthesized artifacts land in the consuming repo.", "author": { "name": "Melodic Software", diff --git a/plugins/knowledge/CHANGELOG.md b/plugins/knowledge/CHANGELOG.md index fc9f822655..9c0dd6a769 100644 --- a/plugins/knowledge/CHANGELOG.md +++ b/plugins/knowledge/CHANGELOG.md @@ -4,6 +4,12 @@ All notable changes to the `knowledge` plugin are recorded here. The `version` i `.claude-plugin/plugin.json` is the delivery vehicle. A consumer receives a change only after that version increases. +## [0.14.13] - 2026-09-30 + +### Changed + +- Test-only: the suites remove their temporary directories on exit. No behavior change. + ## [0.14.12] - 2026-09-30 ### Fixed diff --git a/plugins/knowledge/skills/video-digest/extraction/evals/check-watch-outcomes.test.js b/plugins/knowledge/skills/video-digest/extraction/evals/check-watch-outcomes.test.js index fe9f609298..d0079e2da0 100644 --- a/plugins/knowledge/skills/video-digest/extraction/evals/check-watch-outcomes.test.js +++ b/plugins/knowledge/skills/video-digest/extraction/evals/check-watch-outcomes.test.js @@ -2,7 +2,7 @@ import fs from "node:fs"; import os from "node:os"; import path from "node:path"; -import { describe, expect, it } from "vitest"; +import { describe, expect, it, onTestFinished } from "vitest"; import { parseBoundaryLine } from "../lib/watch-slice-sessions.js"; import { @@ -68,6 +68,7 @@ describe("parsePromotedTimestampsSec", () => { describe("checkWatchOutcomes warn-only count floors", () => { it("assigns warn severity to synthesis count checks", () => { const tmp = fs.mkdtempSync(path.join(os.tmpdir(), "watch-outcomes-")); + onTestFinished(() => fs.rmSync(tmp, { recursive: true, force: true })); fs.mkdirSync(path.join(tmp, "run-state"), { recursive: true }); fs.writeFileSync( path.join(tmp, "run-state", "watch.json"), diff --git a/plugins/knowledge/skills/video-digest/extraction/watch/detect-recoverable-bootstrap.test.js b/plugins/knowledge/skills/video-digest/extraction/watch/detect-recoverable-bootstrap.test.js index daacbb789f..0671e6c9f7 100644 --- a/plugins/knowledge/skills/video-digest/extraction/watch/detect-recoverable-bootstrap.test.js +++ b/plugins/knowledge/skills/video-digest/extraction/watch/detect-recoverable-bootstrap.test.js @@ -2,7 +2,7 @@ import fs from "node:fs"; import os from "node:os"; import path from "node:path"; -import { describe, expect, it } from "vitest"; +import { afterAll, describe, expect, it } from "vitest"; import { detectRecoverableBootstrap, @@ -10,6 +10,17 @@ import { } from "./detect-recoverable-bootstrap.js"; import { resolveWorkArtifacts } from "./recover-watch-bootstrap.js"; +const tmpDirs = []; +afterAll(() => { + for (const dir of tmpDirs) fs.rmSync(dir, { recursive: true, force: true }); +}); + +function mkTmp(prefix) { + const dir = fs.mkdtempSync(path.join(os.tmpdir(), prefix)); + tmpDirs.push(dir); + return dir; +} + /** * A fresh temp workDir holding the mp4/vtt/info.json trio recovery requires. * @@ -17,7 +28,7 @@ import { resolveWorkArtifacts } from "./recover-watch-bootstrap.js"; * @returns {string} */ function makeWorkDir(vttName) { - const workDir = fs.mkdtempSync(path.join(os.tmpdir(), "video-extraction-")); + const workDir = mkTmp("video-extraction-"); fs.writeFileSync(path.join(workDir, "video.mp4"), "x"); fs.writeFileSync(path.join(workDir, vttName), "WEBVTT\n"); fs.writeFileSync(path.join(workDir, "meta.info.json"), "{}"); @@ -26,8 +37,8 @@ function makeWorkDir(vttName) { /** Fresh temp frames + contact-sheets dirs, one surviving artifact each. */ function makeFrameAndSheetDirs() { - const framesDir = fs.mkdtempSync(path.join(os.tmpdir(), "video-frames-")); - const sheetsDir = fs.mkdtempSync(path.join(os.tmpdir(), "video-sheets-")); + const framesDir = mkTmp("video-frames-"); + const sheetsDir = mkTmp("video-sheets-"); fs.writeFileSync(path.join(framesDir, "anchor_00010000_0001.png"), "x"); fs.writeFileSync(path.join(sheetsDir, "sheet_001.jpg"), "x"); return { framesDir, sheetsDir }; @@ -40,7 +51,7 @@ function makeFrameAndSheetDirs() { * @returns {string} */ function makeSliceDir(watchState) { - const tmp = fs.mkdtempSync(path.join(os.tmpdir(), "detect-recover-")); + const tmp = mkTmp("detect-recover-"); fs.mkdirSync(path.join(tmp, "run-state")); fs.writeFileSync(path.join(tmp, "run-state", "watch.json"), JSON.stringify(watchState)); return tmp; diff --git a/plugins/knowledge/skills/video-digest/extraction/watch/mixed-source-storage-invariant.test.js b/plugins/knowledge/skills/video-digest/extraction/watch/mixed-source-storage-invariant.test.js index bee3a9fb32..6198e31b9a 100644 --- a/plugins/knowledge/skills/video-digest/extraction/watch/mixed-source-storage-invariant.test.js +++ b/plugins/knowledge/skills/video-digest/extraction/watch/mixed-source-storage-invariant.test.js @@ -41,6 +41,7 @@ const X_URL = `https://x.com/someuser/status/${TWID}`; let workRoot; /** @type {string} */ let fixtureDir; +let previousTmpdir; /** * @param {string} id @@ -59,6 +60,8 @@ describe("storage invariant: mixed-source batches share one queue root", () => { beforeEach(async () => { workRoot = await fs.mkdtemp(path.join(os.tmpdir(), "mixed-source-root-")); fixtureDir = await fs.mkdtemp(path.join(os.tmpdir(), "mixed-source-fix-")); + previousTmpdir = process.env.TMPDIR; + process.env.TMPDIR = fixtureDir; // run-watch keeps its temp dirs, so they land in a dir removed below process.env.VIDEO_DIGEST_WORK_ROOT = workRoot; captured.stderr.length = 0; captured.stdout.length = 0; @@ -66,6 +69,8 @@ describe("storage invariant: mixed-source batches share one queue root", () => { }); afterEach(async () => { + if (previousTmpdir === undefined) delete process.env.TMPDIR; + else process.env.TMPDIR = previousTmpdir; delete process.env.VIDEO_DIGEST_WORK_ROOT; await fs.rm(workRoot, { recursive: true, force: true }); await fs.rm(fixtureDir, { recursive: true, force: true }); diff --git a/plugins/knowledge/skills/video-digest/extraction/watch/run-watch-degradation.test.js b/plugins/knowledge/skills/video-digest/extraction/watch/run-watch-degradation.test.js index af3fa9c6db..1d21c1146b 100644 --- a/plugins/knowledge/skills/video-digest/extraction/watch/run-watch-degradation.test.js +++ b/plugins/knowledge/skills/video-digest/extraction/watch/run-watch-degradation.test.js @@ -82,11 +82,14 @@ const MEDIA_ID = String(BigInt(TWID) - (60_000n << 22n)); let workRoot; /** @type {string} */ let fixtureDir; +let previousTmpdir; describe("run-watch capability-absent ASR degradation (CLI-level)", () => { beforeEach(async () => { workRoot = await fs.mkdtemp(path.join(os.tmpdir(), "watch-degradation-root-")); fixtureDir = await fs.mkdtemp(path.join(os.tmpdir(), "watch-degradation-fix-")); + previousTmpdir = process.env.TMPDIR; + process.env.TMPDIR = fixtureDir; // run-watch keeps its temp dirs, so they land in a dir removed below process.env.YOUTUBE_WORK_ROOT = workRoot; captured.stderr.length = 0; captured.stdout.length = 0; @@ -94,6 +97,8 @@ describe("run-watch capability-absent ASR degradation (CLI-level)", () => { }); afterEach(async () => { + if (previousTmpdir === undefined) delete process.env.TMPDIR; + else process.env.TMPDIR = previousTmpdir; delete process.env.YOUTUBE_WORK_ROOT; await fs.rm(workRoot, { recursive: true, force: true }); await fs.rm(fixtureDir, { recursive: true, force: true }); diff --git a/plugins/knowledge/skills/video-digest/extraction/watch/run-watch-envelope.test.js b/plugins/knowledge/skills/video-digest/extraction/watch/run-watch-envelope.test.js index 65dd8ad228..48750293cc 100644 --- a/plugins/knowledge/skills/video-digest/extraction/watch/run-watch-envelope.test.js +++ b/plugins/knowledge/skills/video-digest/extraction/watch/run-watch-envelope.test.js @@ -75,6 +75,7 @@ hello world let workRoot; /** @type {string} */ let fixtureDir; +let previousTmpdir; /** @param {string} name */ async function writeVtt(name) { @@ -107,6 +108,8 @@ describe("runWatchCli envelope consumption", () => { beforeEach(async () => { workRoot = await fs.mkdtemp(path.join(os.tmpdir(), "watch-envelope-root-")); fixtureDir = await fs.mkdtemp(path.join(os.tmpdir(), "watch-envelope-fix-")); + previousTmpdir = process.env.TMPDIR; + process.env.TMPDIR = fixtureDir; // run-watch keeps its temp dirs, so they land in a dir removed below process.env.VIDEO_DIGEST_WORK_ROOT = workRoot; captured.stderr.length = 0; captured.stdout.length = 0; @@ -117,6 +120,8 @@ describe("runWatchCli envelope consumption", () => { }); afterEach(async () => { + if (previousTmpdir === undefined) delete process.env.TMPDIR; + else process.env.TMPDIR = previousTmpdir; delete process.env.VIDEO_DIGEST_WORK_ROOT; await fs.rm(workRoot, { recursive: true, force: true }); await fs.rm(fixtureDir, { recursive: true, force: true }); diff --git a/plugins/knowledge/skills/video-digest/extraction/watching/extract-anchor-frames.test.js b/plugins/knowledge/skills/video-digest/extraction/watching/extract-anchor-frames.test.js index 3cff479ab4..6c8300a96f 100644 --- a/plugins/knowledge/skills/video-digest/extraction/watching/extract-anchor-frames.test.js +++ b/plugins/knowledge/skills/video-digest/extraction/watching/extract-anchor-frames.test.js @@ -2,7 +2,7 @@ import fs from "node:fs"; import os from "node:os"; import path from "node:path"; -import { describe, expect, it, vi } from "vitest"; +import { describe, expect, it, onTestFinished, vi } from "vitest"; import { anchorFrameFileName, @@ -29,6 +29,7 @@ describe("extractAnchorFrames heartbeat", () => { const timestamps = [1, 2, 3, 4, 5]; const outputDir = fs.mkdtempSync(path.join(os.tmpdir(), "anchor-frames-")); + onTestFinished(() => fs.rmSync(outputDir, { recursive: true, force: true })); await extractAnchorFrames("video.mp4", outputDir, timestamps, { spawn, heartbeatInterval: 2, diff --git a/plugins/knowledge/vendor/video-digestion/shared/main-module.test.js b/plugins/knowledge/vendor/video-digestion/shared/main-module.test.js index 41821f3933..ee676f7808 100644 --- a/plugins/knowledge/vendor/video-digestion/shared/main-module.test.js +++ b/plugins/knowledge/vendor/video-digestion/shared/main-module.test.js @@ -1,18 +1,29 @@ import assert from "node:assert/strict"; import { spawnSync } from "node:child_process"; -import { mkdtempSync, symlinkSync, writeFileSync } from "node:fs"; +import { mkdtempSync, rmSync, symlinkSync, writeFileSync } from "node:fs"; import { tmpdir } from "node:os"; import path from "node:path"; -import { describe, it } from "node:test"; +import { after, describe, it } from "node:test"; import { fileURLToPath, pathToFileURL } from "node:url"; const moduleUrl = pathToFileURL( path.join(path.dirname(fileURLToPath(import.meta.url)), "main-module.js"), ).href; +const scratchDirs = []; +after(() => { + for (const dir of scratchDirs) rmSync(dir, { recursive: true, force: true }); +}); + +function scratch(prefix) { + const dir = mkdtempSync(path.join(tmpdir(), prefix)); + scratchDirs.push(dir); + return dir; +} + /** A directory holding probe.mjs (prints isMainModule for itself) and runner.mjs (imports the probe). */ function fixture() { - const dir = mkdtempSync(path.join(tmpdir(), "main-module-")); + const dir = scratch("main-module-"); writeFileSync( path.join(dir, "probe.mjs"), `import { isMainModule } from ${JSON.stringify(moduleUrl)};\nconsole.log(isMainModule(import.meta.url));\n`, @@ -37,7 +48,7 @@ describe("isMainModule", () => { }); it("is true when the entrypoint path goes through a symlink to its directory", () => { - const link = path.join(mkdtempSync(path.join(tmpdir(), "main-module-link-")), "linked"); + const link = path.join(scratch("main-module-link-"), "linked"); symlinkSync(fixture(), link, "junction"); assert.equal(run([path.join(link, "probe.mjs")]), "true"); }); diff --git a/plugins/playbooks/.claude-plugin/plugin.json b/plugins/playbooks/.claude-plugin/plugin.json index 9a6c11c05f..a0f6be181e 100644 --- a/plugins/playbooks/.claude-plugin/plugin.json +++ b/plugins/playbooks/.claude-plugin/plugin.json @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/claude-code-plugin-manifest.json", "name": "playbooks", - "version": "0.15.2", + "version": "0.15.3", "description": "Doctrine and knowledge playbooks as on-demand skills, repo-sweep for running a catalog of hygiene skills through a repository one commit per step, plus a maintainer-facing update skill. boris carries Boris Cherny's Claude Code workflow tips (howborisusesclaudecode.com), skill-authoring carries Anthropic's internal skill-authoring playbook, and fable-5 carries Claude Fable 5's operating doctrine (self-authored, no upstream). The boris and skill-authoring packs vendor a verbatim upstream baseline; /playbooks:update drift-checks and syncs those baselines centrally (maintainers).", "author": { "name": "Melodic Software", diff --git a/plugins/playbooks/CHANGELOG.md b/plugins/playbooks/CHANGELOG.md index 0e8a4a6c48..d1cc4357c0 100644 --- a/plugins/playbooks/CHANGELOG.md +++ b/plugins/playbooks/CHANGELOG.md @@ -4,6 +4,12 @@ All notable changes to the `playbooks` plugin are recorded here. The `version` i `.claude-plugin/plugin.json` is the delivery vehicle. A consumer receives a change only after that version increases. +## [0.15.3] - 2026-09-30 + +### Changed + +- Test-only: the suites remove their temporary directories on exit. No behavior change. + ## [0.15.2] - 2026-09-29 ### Changed diff --git a/plugins/playbooks/skills/boris/scripts/update.test.sh b/plugins/playbooks/skills/boris/scripts/update.test.sh index f41fe58e6c..d26d7a5021 100755 --- a/plugins/playbooks/skills/boris/scripts/update.test.sh +++ b/plugins/playbooks/skills/boris/scripts/update.test.sh @@ -72,10 +72,11 @@ assert_contains "unknown flag mentions expected modes" "$unknown_out" "expected" # --- 3. Source-guard: helpers callable when sourced ------------------------------ # Sourcing installs the script's own EXIT trap (cleanup of its TMPDIR_RUN); the -# test tmpdir is removed explicitly at the end instead of via trap. +# trap set after it replaces that one and removes both directories. # shellcheck source=update.sh source "$SCRIPT" 2>/dev/null SOURCED_TMPDIR="$TMPDIR_RUN" +trap 'rm -rf "$TEST_TMPDIR" "$SOURCED_TMPDIR"' EXIT if declare -F local_metadata_field >/dev/null; then pass "source-guard: helpers exposed after source" else @@ -184,8 +185,6 @@ fi # --- Final report --------------------------------------------------------------------- -rm -rf "$TEST_TMPDIR" "$SOURCED_TMPDIR" - if [[ "$FAILED" -eq 0 ]]; then printf '\nAll %d checks passed.\n' "$CASE_NUM" exit 0 diff --git a/plugins/playbooks/skills/skill-authoring/scripts/update.test.sh b/plugins/playbooks/skills/skill-authoring/scripts/update.test.sh index f39c680bc5..68c3033ddf 100755 --- a/plugins/playbooks/skills/skill-authoring/scripts/update.test.sh +++ b/plugins/playbooks/skills/skill-authoring/scripts/update.test.sh @@ -72,10 +72,11 @@ assert_contains "unknown flag mentions expected modes" "$unknown_out" "expected" # --- 3. Source-guard: helpers callable when sourced ------------------------------ # Sourcing installs the script's own EXIT trap (cleanup of its TMPDIR_RUN); the -# test tmpdir is removed explicitly at the end instead of via trap. +# trap set after it replaces that one and removes both directories. # shellcheck source=update.sh source "$SCRIPT" 2>/dev/null SOURCED_TMPDIR="$TMPDIR_RUN" +trap 'rm -rf "$TEST_TMPDIR" "$SOURCED_TMPDIR"' EXIT if declare -F local_metadata_field >/dev/null; then pass "source-guard: helpers exposed after source" else @@ -159,8 +160,6 @@ assert_eq "file_sha returns empty for missing path" "" "$missing_sha" # --- Final report -------------------------------------------------------------------- -rm -rf "$TEST_TMPDIR" "$SOURCED_TMPDIR" - if [[ "$FAILED" -eq 0 ]]; then printf '\nAll %d checks passed.\n' "$CASE_NUM" exit 0 diff --git a/plugins/playwright/.claude-plugin/plugin.json b/plugins/playwright/.claude-plugin/plugin.json index 821aa71d89..1b85a70f11 100644 --- a/plugins/playwright/.claude-plugin/plugin.json +++ b/plugins/playwright/.claude-plugin/plugin.json @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/claude-code-plugin-manifest.json", "name": "playwright", - "version": "0.8.0", + "version": "0.8.1", "description": "Live E2E browser automation via Microsoft's @playwright/cli: named sessions, accessibility-ref snapshots, click/fill by ref, screenshots, console and network capture, mocking, tracing, video, and auth state, with artifacts written to disk so only paths enter context, plus a vendored upstream baseline and maintainer drift-check update flow.", "author": { "name": "Melodic Software", diff --git a/plugins/playwright/CHANGELOG.md b/plugins/playwright/CHANGELOG.md index fa05f23c12..9fe5fc6c45 100644 --- a/plugins/playwright/CHANGELOG.md +++ b/plugins/playwright/CHANGELOG.md @@ -3,6 +3,12 @@ All notable changes to the `playwright` plugin are documented here. Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/); this plugin uses semantic versioning. +## [0.8.1] - 2026-09-30 + +### Changed + +- Test-only: the suites remove their temporary directories on exit. No behavior change. + ## [0.8.0] - 2026-09-29 ### Added diff --git a/plugins/playwright/skills/playwright/scripts/update.test.sh b/plugins/playwright/skills/playwright/scripts/update.test.sh index 2751acc768..7917a60d89 100755 --- a/plugins/playwright/skills/playwright/scripts/update.test.sh +++ b/plugins/playwright/skills/playwright/scripts/update.test.sh @@ -67,10 +67,11 @@ assert_contains "unknown flag mentions expected modes" "$unknown_out" "expected" # --- 3. Source-guard: helpers callable when sourced ------------------------------ # Sourcing installs the script's own EXIT trap (cleanup of its TMPDIR_RUN); the -# test tmpdir is removed explicitly at the end instead of via trap. +# trap set after it replaces that one and removes both directories. # shellcheck source=update.sh source "$SCRIPT" 2>/dev/null SOURCED_TMPDIR="$TMPDIR_RUN" +trap 'rm -rf "$TEST_TMPDIR" "$SOURCED_TMPDIR"' EXIT if declare -F read_metadata_field >/dev/null; then pass "source-guard: helpers exposed after source" else @@ -178,8 +179,6 @@ fi # --- Final report --------------------------------------------------------------------- -rm -rf "$TEST_TMPDIR" "$SOURCED_TMPDIR" - if [[ "$FAILED" -eq 0 ]]; then printf '\nAll %d checks passed.\n' "$CASE_NUM" exit 0 diff --git a/plugins/repo-fleet-hygiene/.claude-plugin/plugin.json b/plugins/repo-fleet-hygiene/.claude-plugin/plugin.json index 34ffb8d626..da2f6fc206 100644 --- a/plugins/repo-fleet-hygiene/.claude-plugin/plugin.json +++ b/plugins/repo-fleet-hygiene/.claude-plugin/plugin.json @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/claude-code-plugin-manifest.json", "name": "repo-fleet-hygiene", - "version": "0.27.0", + "version": "0.27.1", "description": "Cross-repository Git/GitHub fleet discovery, evidence rollup, a gated apply verb, and a sync verb that fast-forwards canonical checkouts onto the remote default branch. Audit stays read-only. apply and sync mutate only with --apply plus interactive confirmation or --yes.", "author": { "name": "Melodic Software", diff --git a/plugins/repo-fleet-hygiene/CHANGELOG.md b/plugins/repo-fleet-hygiene/CHANGELOG.md index 286637c923..c9b10d678e 100644 --- a/plugins/repo-fleet-hygiene/CHANGELOG.md +++ b/plugins/repo-fleet-hygiene/CHANGELOG.md @@ -3,6 +3,12 @@ All notable changes to `repo-fleet-hygiene` are documented here. Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/); this plugin uses semantic versioning. +## [0.27.1] - 2026-09-30 + +### Changed + +- Test-only: the suites remove their temporary directories on exit. No behavior change. + ## [0.27.0] - 2026-09-29 ### Added diff --git a/plugins/repo-fleet-hygiene/skills/audit/scripts/audit-fleet.test.sh b/plugins/repo-fleet-hygiene/skills/audit/scripts/audit-fleet.test.sh index 057c0d4c05..0bf3b6cb9b 100755 --- a/plugins/repo-fleet-hygiene/skills/audit/scripts/audit-fleet.test.sh +++ b/plugins/repo-fleet-hygiene/skills/audit/scripts/audit-fleet.test.sh @@ -5,6 +5,7 @@ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" SCRIPT="$SCRIPT_DIR/audit-fleet.sh" TMP="$(mktemp -d)" trap 'rm -rf "$TMP"' EXIT +mkdir "$TMP/plan-tmp" && export TMPDIR="$TMP/plan-tmp" # default plan files (mktemp) land inside the cleaned dir MOCK_BIN="$TMP/bin" mkdir -p "$MOCK_BIN" "$TMP/config" "$TMP/discovered-a" "$TMP/canonical-a" "$TMP/repo-b" "$TMP/old-repo" \ diff --git a/plugins/repo-hygiene/.claude-plugin/plugin.json b/plugins/repo-hygiene/.claude-plugin/plugin.json index 15ddccc757..3d3d7ab048 100644 --- a/plugins/repo-hygiene/.claude-plugin/plugin.json +++ b/plugins/repo-hygiene/.claude-plugin/plugin.json @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/claude-code-plugin-manifest.json", "name": "repo-hygiene", - "version": "0.18.1", + "version": "0.18.2", "description": "Repo hygiene action-router: /repo-hygiene:clean sweeps reclaimable caches, build artifacts, and stale git metadata, and can realign the working tree to a fresh-pull state, dry-run-first, with destructive tiers gated behind explicit confirmation and a session-scoped destructive-command guard. Ecosystem targets are detected at runtime; secrets, runtime dependencies, and skill data are preserved by default.", "author": { "name": "Melodic Software", diff --git a/plugins/repo-hygiene/CHANGELOG.md b/plugins/repo-hygiene/CHANGELOG.md index 25b5eea7c1..e7fcd3e032 100644 --- a/plugins/repo-hygiene/CHANGELOG.md +++ b/plugins/repo-hygiene/CHANGELOG.md @@ -3,6 +3,12 @@ All notable changes to the `repo-hygiene` plugin are documented here. Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/); this plugin uses semantic versioning. +## [0.18.2] - 2026-09-30 + +### Changed + +- Test-only: the suites remove their temporary directories on exit. No behavior change. + ## [0.18.1] - 2026-09-30 ### Changed diff --git a/plugins/repo-hygiene/skills/clean/scripts/clean-build.test.sh b/plugins/repo-hygiene/skills/clean/scripts/clean-build.test.sh index 0fc49313c0..1bb278321b 100755 --- a/plugins/repo-hygiene/skills/clean/scripts/clean-build.test.sh +++ b/plugins/repo-hygiene/skills/clean/scripts/clean-build.test.sh @@ -9,6 +9,7 @@ source "$SCRIPT_DIR/lib/test-helpers.sh" BUILD="$SCRIPT_DIR/clean-build.sh" TEST_TMPDIR="$(mktemp -d)" trap 'rm -rf "$TEST_TMPDIR"' EXIT +export TMPDIR="$TEST_TMPDIR" # default dry-run manifests (mktemp) land inside the cleaned dir FAILED=0 git init "$TEST_TMPDIR/repo" >/dev/null 2>&1 diff --git a/plugins/repo-hygiene/skills/clean/scripts/clean-caches.test.sh b/plugins/repo-hygiene/skills/clean/scripts/clean-caches.test.sh index e09da02a65..834a265bcc 100755 --- a/plugins/repo-hygiene/skills/clean/scripts/clean-caches.test.sh +++ b/plugins/repo-hygiene/skills/clean/scripts/clean-caches.test.sh @@ -9,6 +9,7 @@ source "$SCRIPT_DIR/lib/test-helpers.sh" CLEAN="$SCRIPT_DIR/clean-caches.sh" TEST_TMPDIR="$(mktemp -d)" trap 'rm -rf "$TEST_TMPDIR"' EXIT +export TMPDIR="$TEST_TMPDIR" # default dry-run manifests (mktemp) land inside the cleaned dir FAILED=0 git init "$TEST_TMPDIR/repo" >/dev/null 2>&1 diff --git a/plugins/session-flow/.claude-plugin/plugin.json b/plugins/session-flow/.claude-plugin/plugin.json index 58549d69c1..de97843bb7 100644 --- a/plugins/session-flow/.claude-plugin/plugin.json +++ b/plugins/session-flow/.claude-plugin/plugin.json @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/claude-code-plugin-manifest.json", "name": "session-flow", - "version": "0.41.0", + "version": "0.41.1", "description": "Session-lifecycle toolkit of fifteen skills: workflow (navigate a staged dev workflow and suggest the next stage), handoff (write a save-point and resume prompt for /clear-and-resume), continue-in-background (delegate the task to a fresh background agent that continues it now, using the same save-point engine as handoff, delivered by launching a detached claude --bg session seeded with the resume prompt; launches only on explicit user request), keep-going (recover and continue after any interruption OR when live off-thread work looks stalled: inventory off-thread work, inspect its real output, act only on evidence, then continue; after a usage limit lifts it continues rather than summarizing-and-stalling), find-handoff (recover a lost handoff after /clear, when the resume prompt was written but never copied, via a read-only detection ladder: known-location glob of the handoffs dir, then a bounded, recency-ranked transcript scan for the handoff directive and dashed-rail markers, then a confirm-before-resume gate; surfaces only the resume prompt + metadata, never raw transcript content), clean-stop (get to a durable, linked stopping point before the machine may go away: sweep every repo/worktree for uncommitted, unpushed, or PR-less work, push it durable, put breadcrumbs in PR/issue bodies, then give a free-and-clear verdict), retro (structured end-of-session retrospective with transcript metrics and learning codification), running-retro (in-flight retrospective checkpoints that spawn a subagent to analyze the transcript so far and append classified findings to a cumulative running ledger, which captures and routes only, the live counterpart to retro; also owns a detached-observer substrate that can watch a session out-of-band and run the checkpoint autonomously after the session ends), orient (read-only session orientation: synthesize where the session stands, what it is doing, and why, from durable + off-thread state the built-in /recap never sees: ledgers, handoffs, workflow checklists, running-retro ledgers, open PRs and work-items, and git), orchestrate (arm a session or worker with proactive-orchestration imperatives), reanchor (verify a session's working assumptions are still true against live reality, checking referenced PRs/issues/branches, base-branch drift, renamed/version-drifted surfaces, stale memory-tier files, and the goal a handoff records, compared across the chain so a re-derived goal reports as drift, before building on them), reconcile (retire finished off-thread work and reconcile this session's task ledger with reality, the prune-and-reconcile counterpart to keep-going's resume: inventory the work this session spawned, inspect its real state, retire the finished and close proven-done tasks, auto-settling the finished and gating any kill of still-running work; sibling sessions in the project are reported read-only), setup (check-centric verification of the observer's runtime prerequisites and configuration), show-options (lay out which skills fit this moment as a ranked, nothing-hidden menu: a shortlist per bucket plus the complete remainder by name, resolved from the full installed catalog rather than the truncated in-context listing, so the human decides and no option is withheld for looking already-done), and tidy-work (opt-in inventory of the gitignored .work memory tiers: report age, size, kind, and in-flight status, normalize misplaced handoffs and retros, and remove stale known items behind one confirmation; unknown and in-flight items are always kept).", "author": { "name": "Melodic Software", diff --git a/plugins/session-flow/CHANGELOG.md b/plugins/session-flow/CHANGELOG.md index fc1e8a5447..b2b313f736 100644 --- a/plugins/session-flow/CHANGELOG.md +++ b/plugins/session-flow/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog: session-flow plugin +## [0.41.1] - 2026-09-30 + +### Changed + +- Test-only: the suites remove their temporary directories on exit. No behavior change. + ## [0.41.0] - 2026-09-29 ### Added diff --git a/plugins/session-flow/scripts/harness/hop_chain.test.sh b/plugins/session-flow/scripts/harness/hop_chain.test.sh index d9937777bd..bc19cf670a 100755 --- a/plugins/session-flow/scripts/harness/hop_chain.test.sh +++ b/plugins/session-flow/scripts/harness/hop_chain.test.sh @@ -61,6 +61,7 @@ fi # --budget writes a 20-hop chain; give it its own scratch dir in mixed form so # the native interpreter reads the same path Git Bash printed (windows-path-emit). work="$(mktemp -d)" +trap 'rm -rf "$work"' EXIT if command -v cygpath >/dev/null 2>&1; then work="$(cygpath -m "$work")" fi @@ -70,7 +71,6 @@ if ! "$PY" -X utf8 hop_chain.py --budget --work-dir "$work"; then echo "FAIL: --budget projection" status=1 fi -rm -rf "$work" if [[ "$status" -eq 0 ]]; then echo "PASS: hop_chain.py contract tests" diff --git a/plugins/testing/.claude-plugin/plugin.json b/plugins/testing/.claude-plugin/plugin.json index b824d158f7..ff4693e2b1 100644 --- a/plugins/testing/.claude-plugin/plugin.json +++ b/plugins/testing/.claude-plugin/plugin.json @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/claude-code-plugin-manifest.json", "name": "testing", - "version": "0.12.0", + "version": "0.12.1", "description": "Test-stage discipline across all ecosystems: coverage-gap analysis and test planning (`/testing:plan`), TDD test authoring and placement (`/testing:write`), live E2E plus non-UI smoke verification (`/testing:run-e2e`), failing-test root-cause diagnosis with the reproduce → isolate → fix → retest loop (`/testing:diagnose`), a deterministic can't-fail test audit with a fail-closed gate mode and opt-in findings persistence (`/testing:audit`), its configuration (`/testing:setup`), and opt-in hooks that scan each test file Claude writes and question edits that weaken tests.", "author": { "name": "Melodic Software", diff --git a/plugins/testing/CHANGELOG.md b/plugins/testing/CHANGELOG.md index b3d6f27cfd..b32e9bf8b3 100644 --- a/plugins/testing/CHANGELOG.md +++ b/plugins/testing/CHANGELOG.md @@ -3,6 +3,12 @@ All notable changes to the `testing` plugin are documented here. Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/); this plugin uses semantic versioning. +## [0.12.1] - 2026-09-30 + +### Changed + +- Test-only: the suites remove their temporary directories on exit. No behavior change. + ## [0.12.0] - 2026-09-30 ### Added diff --git a/plugins/testing/scripts/gen-hook-filters.test.sh b/plugins/testing/scripts/gen-hook-filters.test.sh index e72d2da7a4..1795afcde0 100755 --- a/plugins/testing/scripts/gen-hook-filters.test.sh +++ b/plugins/testing/scripts/gen-hook-filters.test.sh @@ -48,13 +48,12 @@ for event in PostToolUse PreToolUse; do done backup="$(mktemp)" +trap 'cp "$backup" "$HOOKS"; rm -f "$backup"' EXIT cp "$HOOKS" "$backup" jq '.hooks.PostToolUse[0].hooks |= .[1:]' "$backup" >"$HOOKS" check "--check exits 1 on drift" '! bash "$GEN" --check 2>/dev/null' bash "$GEN" check "a regenerate restores sync" 'cmp -s "$HOOKS" "$backup"' -cp "$backup" "$HOOKS" -rm -f "$backup" echo echo "$PASS passed, $FAIL failed" diff --git a/plugins/work-items/.claude-plugin/plugin.json b/plugins/work-items/.claude-plugin/plugin.json index a9b8d61b5c..269828040d 100644 --- a/plugins/work-items/.claude-plugin/plugin.json +++ b/plugins/work-items/.claude-plugin/plugin.json @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/claude-code-plugin-manifest.json", "name": "work-items", - "version": "0.43.1", + "version": "0.43.2", "description": "Manages development work items through a provider-neutral tracker seam that ships with the plugin (bundled dispatcher plus github, local-markdown, jira, gitea, and linear adapters; seam plugin-dir canonical, adapters consumer-local-first): dashboard, taxonomy-labeled creation, a race-safe assignee-plus-lease claim protocol, recurring-schedule checks, TODO scanning, stale-lease auditing, plan decomposition into vertical-slice items, a macro-journey router over spec containers (rollup, per-container execution shape, next-step routing), raw-intake triage (issues and unsolicited PRs through raw, verified, briefed, autonomous-eligible states), plus the two work-items loop lanes of the loop-lane convention: a self-paced autonomous work-loop drain (work-class admission gate, adaptive item cap, PR-only) and an attended attend-queue escalation lane. The re-runnable setup skill binds the provider (.work-item-tracker.json), seeds the recurring-schedule seam (.github/recurring-schedule.json), and remaps canonical role labels.", "author": { "name": "Melodic Software", diff --git a/plugins/work-items/CHANGELOG.md b/plugins/work-items/CHANGELOG.md index 5fed085f28..03d636acc3 100644 --- a/plugins/work-items/CHANGELOG.md +++ b/plugins/work-items/CHANGELOG.md @@ -3,6 +3,12 @@ All notable changes to the `work-items` plugin are documented here. Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/); this plugin uses semantic versioning. +## [0.43.2] - 2026-09-30 + +### Changed + +- Test-only: the suites remove their temporary directories on exit. No behavior change. + ## [0.43.1] - 2026-09-30 ### Fixed 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 97553534b2..7748703a82 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 @@ -4,6 +4,9 @@ # its public helpers (no --help contract; it is sourced, never invoked). set -uo pipefail +TMP_ROOT="$(mktemp -d)" +trap 'rm -rf "$TMP_ROOT"' EXIT + SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" source "$SCRIPT_DIR/../../tests/lib.sh" # shellcheck source=common.sh @@ -25,11 +28,11 @@ assert_eq "rate limit → unavailable (8)" "8" "$(wit_map_gh_error 'API rate lim # Bot-wrapper resolution (CONTRACT.md "Identity routing (GitHub adapter)"): # consumer-local-first, plugin-bundled fallback, regardless of adapter location. -CONSUMER_ROOT="$(mktemp -d)" +CONSUMER_ROOT="$(mktemp -d "$TMP_ROOT/d.XXXXXX")" mkdir -p "$CONSUMER_ROOT/tools/github-auth" CONSUMER_WRAPPER="$CONSUMER_ROOT/tools/github-auth/gh-bot.sh" : >"$CONSUMER_WRAPPER" -EMPTY_ROOT="$(mktemp -d)" +EMPTY_ROOT="$(mktemp -d "$TMP_ROOT/d.XXXXXX")" BUNDLED="$WIT_GH_ADAPTER_DIR/../../../github-auth/gh-bot.sh" RESOLVED="$(CLAUDE_PROJECT_DIR="$CONSUMER_ROOT" wit_gh_resolve_bot_wrapper)" @@ -54,7 +57,7 @@ rm -rf "$CONSUMER_ROOT" "$EMPTY_ROOT" # The stub 403s every GraphQL-backed call (`issue view`, `repo view`) the way a # sandboxed session does and serves `gh api`; gh 2.94 reads through `issue view`. if command -v jq >/dev/null 2>&1; then - EMIT_STUB="$(mktemp -d)" + EMIT_STUB="$(mktemp -d "$TMP_ROOT/d.XXXXXX")" cat >"$EMIT_STUB/gh" <<'EOF' #!/usr/bin/env bash if [[ "$1" == "--version" ]]; then @@ -104,7 +107,7 @@ EOF "api repos/o/r/issues/1" assert_not_contains "wit_emit_item on gh 2.45 skips issue view" "$(<"$EMIT_STUB/calls.log")" "issue view" - PR_STUB="$(mktemp -d)" + PR_STUB="$(mktemp -d "$TMP_ROOT/d.XXXXXX")" cat >"$PR_STUB/gh" <<'EOF' #!/usr/bin/env bash [[ "$1" == "--version" ]] && { echo "gh version 2.45.0 (test)"; exit 0; } diff --git a/plugins/work-items/tools/work-item-tracker/adapters/github/create-item.test.sh b/plugins/work-items/tools/work-item-tracker/adapters/github/create-item.test.sh index 064ae817e7..48f383a91a 100755 --- a/plugins/work-items/tools/work-item-tracker/adapters/github/create-item.test.sh +++ b/plugins/work-items/tools/work-item-tracker/adapters/github/create-item.test.sh @@ -1,6 +1,9 @@ #!/usr/bin/env bash # shellcheck disable=SC2154 # FAILED/CASE_NUM initialized by the sourced helper set -uo pipefail + +TMP_ROOT="$(mktemp -d)" +trap 'rm -rf "$TMP_ROOT"' EXIT S="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/create-item.sh" source "$(dirname "$S")/../../lib/verb-test-helpers.sh" @@ -14,8 +17,8 @@ assert_usage_error "$S" --title x --type # --type needs a value # the repo resolve must all take the REST path. Direct adapter invocation # (dispatcher gate is covered in work-item-tracker.test.sh). if command -v jq >/dev/null 2>&1; then - STUB="$(mktemp -d)" - PROJECT="$(mktemp -d)" + STUB="$(mktemp -d "$TMP_ROOT/d.XXXXXX")" + PROJECT="$(mktemp -d "$TMP_ROOT/d.XXXXXX")" cat >"$STUB/gh" <<'EOF' #!/usr/bin/env bash if [[ "$1" == "--version" ]]; then @@ -85,7 +88,7 @@ EOF assert_eq "create-item on gh 2.45 repo resolve id" "github:o/r#42" "$(jq -r '.id' <<<"$OUT")" assert_not_contains "create-item on gh 2.45 never calls repo view" "$(<"$STUB/calls.log")" "repo view" - TYPED_ERR="$(mktemp)" + TYPED_ERR="$(mktemp "$TMP_ROOT/f.XXXXXX")" TYPED_OUT="$(GH_STUB_VERSION=2.45.0 run_create --title t --type Task --repo o/r 2>"$TYPED_ERR")" rc=$? assert_eq "create-item --type on gh 2.45 degrades → exit 0" "0" "$rc" diff --git a/plugins/work-items/tools/work-item-tracker/adapters/github/lease-coordination.test.sh b/plugins/work-items/tools/work-item-tracker/adapters/github/lease-coordination.test.sh index a058802fdd..6e73d4050c 100755 --- a/plugins/work-items/tools/work-item-tracker/adapters/github/lease-coordination.test.sh +++ b/plugins/work-items/tools/work-item-tracker/adapters/github/lease-coordination.test.sh @@ -16,6 +16,9 @@ # shellcheck disable=SC2154 # FAILED/CASE_NUM initialized by the sourced lib set -uo pipefail +TMP_ROOT="$(mktemp -d)" +trap 'rm -rf "$TMP_ROOT"' EXIT + SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" source "$SCRIPT_DIR/../../tests/lib.sh" @@ -144,7 +147,7 @@ marker() { lease_array() { jq -cn --argjson id "$1" --arg body "$2" '[{id:$id, node_id:"MDEx", body:$body, created_at:"2020-01-01T00:00:00Z"}]'; } new_scenario() { - GH_STUB_DIR="$(mktemp -d)" + GH_STUB_DIR="$(mktemp -d "$TMP_ROOT/d.XXXXXX")" export GH_STUB_DIR : >"$GH_STUB_DIR/calls.log" } 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 165dd84aac..9d218e4ff0 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 @@ -5,6 +5,9 @@ # `--json subIssues` projection. End-to-end behavior against the live provider # stays with the on-demand e2e-probe. set -uo pipefail + +TMP_ROOT="$(mktemp -d)" +trap 'rm -rf "$TMP_ROOT"' EXIT S="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/list-sub-items.sh" source "$(dirname "$S")/../../lib/verb-test-helpers.sh" @@ -49,7 +52,7 @@ assert_usage_error "$S" "local-markdown:o/r#1" # foreign provider # `.repository.nameWithOwner`, which is absent there, so it matched no node and # every container came back childless — this case fails on that predicate. if command -v jq >/dev/null 2>&1; then - STUB="$(mktemp -d)" + STUB="$(mktemp -d "$TMP_ROOT/d.XXXXXX")" write_gh_stub "$STUB" # #12 is a genuine cross-repo sub-issue whose number also exists in o/r, so # dropping it is what the same-repo filter is for. @@ -63,7 +66,7 @@ if command -v jq >/dev/null 2>&1; then {"number":13,"title":"unrelated","state":"OPEN","assignees":[],"labels":[],"issueType":null,"blockedBy":{"nodes":[]},"url":"https://github.com/o/r/issues/13"} ]' - ERRFILE="$(mktemp)" + ERRFILE="$(mktemp "$TMP_ROOT/f.XXXXXX")" OUT="$(PATH="$STUB:$PATH" GH_STUB_VIEW="$VIEW" GH_STUB_LIST="$LIST" bash "$S" "github:o/r#99" 2>"$ERRFILE")" rc=$? assert_eq "list-sub-items over stubbed subIssues → exit 0" "0" "$rc" @@ -88,7 +91,7 @@ fi # unattributable node as same-repo would pull in an unrelated same-numbered item. # What changes is that the drop now says so on stderr, leaving stdout parseable. if command -v jq >/dev/null 2>&1; then - STUB="$(mktemp -d)" + STUB="$(mktemp -d "$TMP_ROOT/d.XXXXXX")" write_gh_stub "$STUB" # #21 is well-formed. #22 carries no url at all and #23 a url that is not an # issue path: the two ways a narrowed projection could go unattributable. @@ -103,7 +106,7 @@ if command -v jq >/dev/null 2>&1; then {"number":23,"title":"three","state":"OPEN","assignees":[],"labels":[],"issueType":null,"blockedBy":{"nodes":[]},"url":"https://github.com/o/r/issues/23"} ]' - ERRFILE="$(mktemp)" + ERRFILE="$(mktemp "$TMP_ROOT/f.XXXXXX")" OUT="$(PATH="$STUB:$PATH" GH_STUB_VIEW="$VIEW" GH_STUB_LIST="$LIST" bash "$S" "github:o/r#99" 2>"$ERRFILE")" rc=$? ERR="$(<"$ERRFILE")" @@ -126,7 +129,7 @@ fi # child foreign and return an empty list with no signal, which is the same # silent blindness (#3825) was, just reached by a different route. if command -v jq >/dev/null 2>&1; then - STUB="$(mktemp -d)" + STUB="$(mktemp -d "$TMP_ROOT/d.XXXXXX")" write_gh_stub "$STUB" # Canonical casing from the API differs from the casing used in the id. VIEW='{"subIssues":{"nodes":[ @@ -138,7 +141,7 @@ if command -v jq >/dev/null 2>&1; then {"number":32,"title":"same number, this repo","state":"OPEN","assignees":[],"labels":[],"issueType":null,"blockedBy":{"nodes":[]},"url":"https://github.com/acme/widgets/issues/32"} ]' - ERRFILE="$(mktemp)" + ERRFILE="$(mktemp "$TMP_ROOT/f.XXXXXX")" OUT="$(PATH="$STUB:$PATH" GH_STUB_VIEW="$VIEW" GH_STUB_LIST="$LIST" bash "$S" "github:acme/widgets#99" 2>"$ERRFILE")" rc=$? assert_eq "case-differing repo → exit 0" "0" "$rc" diff --git a/plugins/work-items/tools/work-item-tracker/adapters/local-markdown/claim-integrity.test.sh b/plugins/work-items/tools/work-item-tracker/adapters/local-markdown/claim-integrity.test.sh index 26480be7ca..154854a999 100755 --- a/plugins/work-items/tools/work-item-tracker/adapters/local-markdown/claim-integrity.test.sh +++ b/plugins/work-items/tools/work-item-tracker/adapters/local-markdown/claim-integrity.test.sh @@ -8,14 +8,17 @@ # shellcheck disable=SC2154 # FAILED/CASE_NUM initialized by the sourced lib set -uo pipefail +TMP_ROOT="$(mktemp -d)" +trap 'rm -rf "$TMP_ROOT"' EXIT + SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" TRACKER="$SCRIPT_DIR/../../work-item-tracker.sh" source "$SCRIPT_DIR/../../tests/lib.sh" # --- An expired lease reappears in the frontier (end-to-end, core CLI) --- -STORAGE="$(mktemp -d)" -BINDING="$(mktemp)" +STORAGE="$(mktemp -d "$TMP_ROOT/d.XXXXXX")" +BINDING="$(mktemp "$TMP_ROOT/f.XXXXXX")" jq -cn --arg dir "$STORAGE" \ '{schema_version: "1.0", provider: "local-markdown", config: {lease_ttl_hours: 24, storage_dir: $dir}}' \ >"$BINDING" @@ -68,7 +71,7 @@ LEASE_JSON='{"schema_version":"1.0","holder":"tester","acquired_at":"2020-01-01T MARKER_LINE="${WIT_LEASE_MARKER}${LEASE_JSON} -->" # Happy path (real wit_fm_set): both writes land and the helper reports success. -OK_FILE="$(mktemp)" +OK_FILE="$(mktemp "$TMP_ROOT/f.XXXXXX")" write_item "$OK_FILE" wit_claim_write "$OK_FILE" "$MARKER_LINE" '["tester"]' assert_eq "successful claim write returns 0" "0" "$?" @@ -79,7 +82,7 @@ rm -f "$OK_FILE" # Failure path: simulate a failed assignee write. The helper must fail AND roll the # just-appended marker back, leaving the store as if the claim never happened. -FAIL_FILE="$(mktemp)" +FAIL_FILE="$(mktemp "$TMP_ROOT/f.XXXXXX")" write_item "$FAIL_FILE" wit_fm_set() { return 1; } wit_claim_write "$FAIL_FILE" "$MARKER_LINE" '["tester"]' @@ -92,7 +95,7 @@ rm -f "$FAIL_FILE" # Failure path where the rollback itself cannot run (mktemp fails — the same class # of store condition). The claim still fails, and the helper WARNS that the marker # may be orphaned instead of silently claiming a clean rollback. -FAIL2_FILE="$(mktemp)" +FAIL2_FILE="$(mktemp "$TMP_ROOT/f.XXXXXX")" write_item "$FAIL2_FILE" # Invoked indirectly by the sourced wit_claim_write, which shellcheck cannot see # across the source boundary (it resolves mktemp there as the external command). diff --git a/plugins/work-items/tools/work-item-tracker/adapters/local-markdown/claim.test.sh b/plugins/work-items/tools/work-item-tracker/adapters/local-markdown/claim.test.sh index 5fd047b717..4a233869f1 100755 --- a/plugins/work-items/tools/work-item-tracker/adapters/local-markdown/claim.test.sh +++ b/plugins/work-items/tools/work-item-tracker/adapters/local-markdown/claim.test.sh @@ -1,6 +1,9 @@ #!/usr/bin/env bash # shellcheck disable=SC2154 # FAILED/CASE_NUM initialized by the sourced helper set -uo pipefail + +TMP_ROOT="$(mktemp -d)" +trap 'rm -rf "$TMP_ROOT"' EXIT S="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/claim.sh" source "$(dirname "$S")/../../lib/verb-test-helpers.sh" @@ -12,7 +15,7 @@ assert_usage_error "$S" "github:o/r#1" # together: the optional fields the record reports must be the ones the marker # carries, and an absent --session-id must report as an explicit null. ADAPTER="$(dirname "$S")" -STORE="$(mktemp -d)" +STORE="$(mktemp -d "$TMP_ROOT/d.XXXXXX")" ITEM="$(WIT_STORAGE_DIR="$STORE" bash "$ADAPTER/create-item.sh" --title "claim record")" ITEM_FILE="$(jq -r '.url' <<<"$ITEM" | sed 's#^file://##')" RECORD="$(WIT_STORAGE_DIR="$STORE" bash "$S" "$(jq -r '.id' <<<"$ITEM")" \ diff --git a/plugins/work-items/tools/work-item-tracker/adapters/local-markdown/create-item.test.sh b/plugins/work-items/tools/work-item-tracker/adapters/local-markdown/create-item.test.sh index 7ac1aeb76f..32e4e8618d 100755 --- a/plugins/work-items/tools/work-item-tracker/adapters/local-markdown/create-item.test.sh +++ b/plugins/work-items/tools/work-item-tracker/adapters/local-markdown/create-item.test.sh @@ -1,6 +1,9 @@ #!/usr/bin/env bash # shellcheck disable=SC2154 # FAILED/CASE_NUM initialized by the sourced helper set -uo pipefail + +TMP_ROOT="$(mktemp -d)" +trap 'rm -rf "$TMP_ROOT"' EXIT S="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/create-item.sh" source "$(dirname "$S")/../../lib/verb-test-helpers.sh" @@ -14,7 +17,7 @@ assert_usage_error "$S" --title x --type # --type needs a value # create-item is the verb that writes a whole item, so assert what reaches the # store, not just the exit code. `type` is additive: supplied it round-trips, # omitted it projects as JSON null rather than an empty string. -STORE="$(mktemp -d)" +STORE="$(mktemp -d "$TMP_ROOT/d.XXXXXX")" TYPED="$(WIT_STORAGE_DIR="$STORE" bash "$S" --title "typed item" --type bug)" assert_eq "--type reaches the emitted record" "bug" "$(jq -r '.type' <<<"$TYPED")" assert_contains "--type reaches the stored file" \ diff --git a/plugins/work-items/tools/work-item-tracker/adapters/local-markdown/list-items.test.sh b/plugins/work-items/tools/work-item-tracker/adapters/local-markdown/list-items.test.sh index e0de97e7a9..4052398620 100755 --- a/plugins/work-items/tools/work-item-tracker/adapters/local-markdown/list-items.test.sh +++ b/plugins/work-items/tools/work-item-tracker/adapters/local-markdown/list-items.test.sh @@ -1,6 +1,9 @@ #!/usr/bin/env bash # shellcheck disable=SC2154 # FAILED/CASE_NUM initialized by the sourced helper set -uo pipefail + +TMP_ROOT="$(mktemp -d)" +trap 'rm -rf "$TMP_ROOT"' EXIT S="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/list-items.sh" source "$(dirname "$S")/../../lib/verb-test-helpers.sh" @@ -14,7 +17,7 @@ assert_usage_error "$S" --repo # than empty it or trip the unknown-argument path. The conformance binding never # threads --repo (CB_REPO is empty for this adapter), so this is the only place # the flag's parse is exercised. -STORE="$(mktemp -d)" +STORE="$(mktemp -d "$TMP_ROOT/d.XXXXXX")" WIT_STORAGE_DIR="$STORE" bash "$(dirname "$S")/create-item.sh" --title "repo-parity" >/dev/null LISTED="$(WIT_STORAGE_DIR="$STORE" bash "$S" --repo other/repo)" assert_eq "--repo lists the bound store (exit 0)" "0" "$?" diff --git a/plugins/work-items/tools/work-item-tracker/adapters/local-markdown/renew-lease.test.sh b/plugins/work-items/tools/work-item-tracker/adapters/local-markdown/renew-lease.test.sh index a2d85ee6d4..d2364cc708 100755 --- a/plugins/work-items/tools/work-item-tracker/adapters/local-markdown/renew-lease.test.sh +++ b/plugins/work-items/tools/work-item-tracker/adapters/local-markdown/renew-lease.test.sh @@ -1,6 +1,9 @@ #!/usr/bin/env bash # shellcheck disable=SC2154 # FAILED/CASE_NUM initialized by the sourced helper set -uo pipefail + +TMP_ROOT="$(mktemp -d)" +trap 'rm -rf "$TMP_ROOT"' EXIT S="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/renew-lease.sh" source "$(dirname "$S")/../../lib/verb-test-helpers.sh" @@ -16,8 +19,8 @@ TRACKER="$(dirname "$S")/../../work-item-tracker.sh" # shellcheck source=common.sh source "$(dirname "$S")/common.sh" -STORAGE="$(mktemp -d)" -BINDING="$(mktemp)" +STORAGE="$(mktemp -d "$TMP_ROOT/d.XXXXXX")" +BINDING="$(mktemp "$TMP_ROOT/f.XXXXXX")" jq -cn --arg dir "$STORAGE" \ '{schema_version: "1.0", provider: "local-markdown", config: {lease_ttl_hours: 24, storage_dir: $dir}}' \ >"$BINDING" @@ -70,7 +73,7 @@ assert_eq "the renewal replaced the lease marker rather than appending one" "1" # A store write that cannot run must fail the verb rather than report a renewal # nothing can read back. A PATH shim denies the temp file the rewrite needs (the # dispatcher on this path calls no mktemp of its own, so only the write is hit). -SHIM="$(mktemp -d)" +SHIM="$(mktemp -d "$TMP_ROOT/d.XXXXXX")" printf '#!/usr/bin/env bash\nexit 1\n' >"$SHIM/mktemp" chmod +x "$SHIM/mktemp" LEASE_BEFORE="$(wit_active_lease_json "$LIVE_FILE")" 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 85a3844f83..70436cf335 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 @@ -4,6 +4,9 @@ # once under a PATH shim that makes gh/curl fail: every exercised path is pre-network. set -uo pipefail +TMP_ROOT="$(mktemp -d)" +trap 'rm -rf "$TMP_ROOT"' EXIT + SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" RUNNER="$SCRIPT_DIR/../run-conformance.sh" source "$SCRIPT_DIR/../../tests/lib.sh" @@ -22,7 +25,7 @@ assert_eq "conformance --binding jira exit 0" "0" "$?" # Zero-network: a PATH shim makes gh + curl exit 1 and the suite must still pass # (no unshare -n on Git Bash). -SHIM="$(mktemp -d)" +SHIM="$(mktemp -d "$TMP_ROOT/d.XXXXXX")" write_blocking_network_shim "$SHIM" PATH="$SHIM:$PATH" bash "$RUNNER" --binding jira >/dev/null 2>&1 assert_eq "conformance --binding jira exit 0 under gh/curl-blocking shim" "0" "$?" diff --git a/plugins/work-items/tools/work-item-tracker/conformance/bindings/linear.test.sh b/plugins/work-items/tools/work-item-tracker/conformance/bindings/linear.test.sh index 8d9676e901..0e45598f50 100755 --- a/plugins/work-items/tools/work-item-tracker/conformance/bindings/linear.test.sh +++ b/plugins/work-items/tools/work-item-tracker/conformance/bindings/linear.test.sh @@ -9,6 +9,9 @@ # deferred in the adapter's README. set -uo pipefail +TMP_ROOT="$(mktemp -d)" +trap 'rm -rf "$TMP_ROOT"' EXIT + SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" source "$SCRIPT_DIR/../../tests/lib.sh" CB_REPO="" @@ -46,7 +49,7 @@ fi # # `curl` is PATH-stubbed to one empty page so cb_setup's clean-at-start pass runs # offline rather than being bypassed. -CB_STUB_DIR="$(mktemp -d)" +CB_STUB_DIR="$(mktemp -d "$TMP_ROOT/d.XXXXXX")" cat >"$CB_STUB_DIR/curl" <<'STUB' #!/usr/bin/env bash # Drain the stdin config (the credential arrives that way) so the writer never sees EPIPE. @@ -127,7 +130,7 @@ STUB chmod +x "$CB_STUB_DIR/curl" ( - export PATH="$CB_STUB_DIR:$PATH" + export PATH="$CB_STUB_DIR:$PATH" TMPDIR="$TMP_ROOT" export WIT_LINEAR_API_KEY="throwaway-not-a-real-key" export WIT_CONFORMANCE_LINEAR_HOST="api.linear.app" export WIT_CONFORMANCE_LINEAR_SCOPE="throwaway/SBX" 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 24e9176484..a731ba54d1 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 @@ -4,6 +4,9 @@ # once normally and once under a PATH shim that makes gh/curl fail. set -uo pipefail +TMP_ROOT="$(mktemp -d)" +trap 'rm -rf "$TMP_ROOT"' EXIT + SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" RUNNER="$SCRIPT_DIR/../run-conformance.sh" source "$SCRIPT_DIR/../../tests/lib.sh" @@ -24,7 +27,7 @@ assert_eq "conformance --binding local-markdown exit 0" "0" "$?" # Zero-network: a PATH shim makes gh + curl exit 1; the suite must still pass, # proving the adapter never reaches for a network tool (no unshare -n on Git Bash). -SHIM="$(mktemp -d)" +SHIM="$(mktemp -d "$TMP_ROOT/d.XXXXXX")" write_blocking_network_shim "$SHIM" PATH="$SHIM:$PATH" bash "$RUNNER" --binding local-markdown >/dev/null 2>&1 assert_eq "conformance --binding local-markdown exit 0 under gh/curl-blocking shim" "0" "$?" diff --git a/scripts/affected-tests.test.sh b/scripts/affected-tests.test.sh index 6b34351f29..c79468d8fb 100755 --- a/scripts/affected-tests.test.sh +++ b/scripts/affected-tests.test.sh @@ -10,6 +10,9 @@ # tracks reality, which is the whole failure mode this tool exists to avoid. set -uo pipefail +TMP_ROOT="$(mktemp -d)" +trap 'rm -rf "$TMP_ROOT"' EXIT + SELF_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" REPO_ROOT="$(cd "$SELF_DIR/.." && pwd)" SCRIPT="$SELF_DIR/affected-tests.sh" @@ -521,7 +524,7 @@ else fi # --- --run executes the selected suites, sequentially ---------------------- -marker="$(mktemp "${TMPDIR:-/tmp}/affected-tests-marker.XXXXXX")" +marker="$(mktemp "$TMP_ROOT/affected-tests-marker.XXXXXX")" : >"$marker" (cd "$repo" && MARKER_FILE="$marker" bash scripts/affected-tests.sh --run lib/widget.sh >/dev/null 2>&1) RC=$? diff --git a/scripts/check-changed-skills.test.sh b/scripts/check-changed-skills.test.sh index f6ecd608eb..32d9eb2645 100755 --- a/scripts/check-changed-skills.test.sh +++ b/scripts/check-changed-skills.test.sh @@ -7,6 +7,9 @@ # passthrough, and pass/fail aggregation — not check-skill.sh itself. set -uo pipefail +TMP_ROOT="$(mktemp -d)" +trap 'rm -rf "$TMP_ROOT"' EXIT + SELF_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" SCRIPT="$SELF_DIR/check-changed-skills.sh" @@ -24,7 +27,7 @@ r="" # A stub checker shared by every scenario: records each invocation (skill name + # forwarded env) to $CHECK_LOG, and FAILs iff the skill is named "bad". -STUB="$(mktemp)" +STUB="$(mktemp "$TMP_ROOT/f.XXXXXX")" cat >"$STUB" <<'EOF' #!/usr/bin/env bash printf 'args=%s root=%s base=%s descbaseline=[%s]\n' "$*" "$CHECK_SKILL_SKILLS_ROOT" "$CHECK_SKILL_BASE_REF" "${CHECK_SKILL_DESC_FIELD_BASELINE-}" >>"$CHECK_LOG" diff --git a/scripts/check-contract-slice-prune.test.sh b/scripts/check-contract-slice-prune.test.sh index a2862833b0..585aae3f5c 100755 --- a/scripts/check-contract-slice-prune.test.sh +++ b/scripts/check-contract-slice-prune.test.sh @@ -6,6 +6,9 @@ # convention requires. set -uo pipefail +TMP_ROOT="$(mktemp -d)" +trap 'rm -rf "$TMP_ROOT"' EXIT + SELF_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" SCRIPT="$SELF_DIR/check-contract-slice-prune.sh" PARSE_LIB="$SELF_DIR/../lib/parse-concern-value.sh" @@ -412,7 +415,7 @@ rm -rf "$repo" # proving that would mean spawning a real detached maintenance child, i.e. # re-creating the very race this suite exists to keep out. mk_repo repo -trace="$(mktemp)" +trace="$(mktemp "$TMP_ROOT/f.XXXXXX")" printf 'edit\n' >>"$repo/README.md" ( cd "$repo" || exit 1 diff --git a/scripts/check-detector-eval-coverage.test.sh b/scripts/check-detector-eval-coverage.test.sh index 8b2e2c583b..7155247c81 100755 --- a/scripts/check-detector-eval-coverage.test.sh +++ b/scripts/check-detector-eval-coverage.test.sh @@ -38,6 +38,9 @@ # shellcheck disable=SC2016 # fixture bodies are literal detector source in single quotes; expansion would destroy the shape under test set -uo pipefail +TMP_ROOT="$(mktemp -d)" +trap 'rm -rf "$TMP_ROOT"' EXIT + SELF_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" SCRIPT="$SELF_DIR/check-detector-eval-coverage.sh" @@ -102,8 +105,8 @@ run_gate() { local pairs="$1" shift local outf errf - outf="$(mktemp)" - errf="$(mktemp)" + outf="$(mktemp "$TMP_ROOT/f.XXXXXX")" + errf="$(mktemp "$TMP_ROOT/f.XXXXXX")" (cd "$root" && DETECTOR_EVAL_COVERAGE_PAIRS="$pairs" bash scripts/check-detector-eval-coverage.sh "$@") >"$outf" 2>"$errf" RC=$? OUT="$(cat "$outf")" @@ -244,7 +247,7 @@ rm -rf "$root" mk_tree mk_detector det.sh 'emit warning P1 SRC "message"' mk_evals evals.json "$(evals_json 'names P1')" -mirror="$(mktemp -d)" +mirror="$(mktemp -d "$TMP_ROOT/d.XXXXXX")" while IFS= read -r d; do [[ -d "$d" ]] || continue for exe in "$d"/*; do @@ -253,8 +256,8 @@ while IFS= read -r d; do [[ -e "$mirror/$(basename "$exe")" ]] || ln -s "$exe" "$mirror/$(basename "$exe")" done done < <(printf '%s\n' "${PATH//:/$'\n'}") -outf="$(mktemp)" -errf="$(mktemp)" +outf="$(mktemp "$TMP_ROOT/f.XXXXXX")" +errf="$(mktemp "$TMP_ROOT/f.XXXXXX")" (cd "$root" && PATH="$mirror" DETECTOR_EVAL_COVERAGE_PAIRS="$(pair det.sh evals.json)" bash scripts/check-detector-eval-coverage.sh --check) >"$outf" 2>"$errf" RC=$? OUT="$(cat "$outf")" @@ -273,7 +276,7 @@ rm -rf "$root" mk_tree mk_detector det.sh 'emit warning P1 SRC "message"' mk_evals evals.json "$(evals_json 'names P1')" -mirror="$(mktemp -d)" +mirror="$(mktemp -d "$TMP_ROOT/d.XXXXXX")" while IFS= read -r d; do [[ -d "$d" ]] || continue for exe in "$d"/*; do @@ -282,8 +285,8 @@ while IFS= read -r d; do [[ -e "$mirror/$(basename "$exe")" ]] || ln -s "$exe" "$mirror/$(basename "$exe")" done done < <(printf '%s\n' "${PATH//:/$'\n'}") -outf="$(mktemp)" -errf="$(mktemp)" +outf="$(mktemp "$TMP_ROOT/f.XXXXXX")" +errf="$(mktemp "$TMP_ROOT/f.XXXXXX")" (cd "$root" && PATH="$mirror" DETECTOR_EVAL_COVERAGE_PAIRS="$(pair det.sh evals.json)" bash scripts/check-detector-eval-coverage.sh --check) >"$outf" 2>"$errf" RC=$? OUT="$(cat "$outf")" @@ -298,11 +301,11 @@ fi # An shfmt below the v3.13.0 floor drops call sites silently, so it must stop # the gate the same way a missing one does. -stub="$(mktemp -d)" +stub="$(mktemp -d "$TMP_ROOT/d.XXXXXX")" printf '#!/usr/bin/env bash\necho v3.12.0\n' >"$stub/shfmt" chmod +x "$stub/shfmt" -outf="$(mktemp)" -errf="$(mktemp)" +outf="$(mktemp "$TMP_ROOT/f.XXXXXX")" +errf="$(mktemp "$TMP_ROOT/f.XXXXXX")" (cd "$root" && PATH="$stub:$PATH" DETECTOR_EVAL_COVERAGE_PAIRS="$(pair det.sh evals.json)" bash scripts/check-detector-eval-coverage.sh --check) >"$outf" 2>"$errf" RC=$? OUT="$(cat "$outf")" diff --git a/scripts/check-discriminating-test-skips.test.sh b/scripts/check-discriminating-test-skips.test.sh index a57a08ea25..fac50873f0 100755 --- a/scripts/check-discriminating-test-skips.test.sh +++ b/scripts/check-discriminating-test-skips.test.sh @@ -7,6 +7,9 @@ # shellcheck disable=SC2016 # fixture bodies are literal test code in single quotes set -uo pipefail +TMP_ROOT="$(mktemp -d)" +trap 'rm -rf "$TMP_ROOT"' EXIT + SELF_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" REPO_ROOT="$(cd "$SELF_DIR/.." && pwd)" SCRIPT="$SELF_DIR/check-discriminating-test-skips.sh" @@ -98,7 +101,7 @@ fi rm -rf "$f" # --- runtime: fail_discriminating_skip fails the suite with marker ----------- -tmp_test="$(mktemp --suffix=.test.sh)" +tmp_test="$(mktemp --suffix=.test.sh "$TMP_ROOT/f.XXXXXX")" write_runtime_suite "$tmp_test" \ 'fail_discriminating_skip "synthetic: git diff --cached --name-status has 0 C record(s), expected 1"' if out="$(bash "$tmp_test" 2>&1)"; then @@ -143,7 +146,7 @@ fi rm -rf "$f" # --- runtime: optional skip_case still passes -------------------------------- -tmp_test="$(mktemp --suffix=.test.sh)" +tmp_test="$(mktemp --suffix=.test.sh "$TMP_ROOT/f.XXXXXX")" write_runtime_suite "$tmp_test" 'skip_case "symlinks unsupported on this platform"' if out="$(bash "$tmp_test" 2>&1)"; then if grep -q '^SKIP:' <<<"$out" && ! grep -q '^DISCRIMINATING SKIP:' <<<"$out"; then diff --git a/scripts/check-docs-only.test.sh b/scripts/check-docs-only.test.sh index 31fbf06d2f..d39d52447b 100755 --- a/scripts/check-docs-only.test.sh +++ b/scripts/check-docs-only.test.sh @@ -7,6 +7,9 @@ # widens the allowlist to cover a doc a code lane actually reads. set -uo pipefail +TMP_ROOT="$(mktemp -d)" +trap 'rm -rf "$TMP_ROOT"' EXIT + SELF_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" SCRIPT="$SELF_DIR/check-docs-only.sh" @@ -129,7 +132,7 @@ mk_repo repo base="$(git -C "$repo" rev-parse HEAD)" printf 'changed\n' >"$repo/docs/topics/example/PLAN.md" git_test_config "$repo" add -A >/dev/null && git_test_config "$repo" commit -qm change >/dev/null -gho="$(mktemp)" +gho="$(mktemp "$TMP_ROOT/f.XXXXXX")" (cd "$repo" && GITHUB_OUTPUT="$gho" bash scripts/check-docs-only.sh "$base" >/dev/null 2>&1) if grep -qx 'docs_only=true' "$gho"; then ok "writes docs_only to GITHUB_OUTPUT" diff --git a/scripts/check-exec-form-windows-probe.test.sh b/scripts/check-exec-form-windows-probe.test.sh index 4566e834a0..9d15e4216c 100755 --- a/scripts/check-exec-form-windows-probe.test.sh +++ b/scripts/check-exec-form-windows-probe.test.sh @@ -4,6 +4,9 @@ # shellcheck disable=SC2016 set -uo pipefail +TMP_ROOT="$(mktemp -d)" +trap 'rm -rf "$TMP_ROOT"' EXIT + SELF_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" SCRIPT="$SELF_DIR/check-exec-form-windows-probe.sh" GATE="$SELF_DIR/check-hook-exec-form.sh" @@ -315,7 +318,7 @@ EOF # spawn half; the launcher half needs a real node, so it gets the real one. new_fixture f plugin_file "$f" alpha hooks/hooks.json "$NODE_ROW" - winnode="$(mktemp -d)" + winnode="$(mktemp -d "$TMP_ROOT/d.XXXXXX")" cat >"$winnode/node" <<'EOF' #!/usr/bin/env bash case "$*" in *launcher-probe.cjs*) exec "$REAL_NODE" "$@" ;; esac @@ -342,7 +345,7 @@ fi # --- a required spawn with node hidden fails closed ------------------------- new_fixture f plugin_file "$f" alpha hooks/hooks.json "$NODE_ROW" -hidden="$(mktemp -d)" +hidden="$(mktemp -d "$TMP_ROOT/d.XXXXXX")" # Keep jq and python (and uv, the PyYAML fallback), drop node. mkdir -p "$hidden/bin" for tool in bash jq python3 python uv; do @@ -361,7 +364,7 @@ rm -rf "$f" "$hidden" # --- live probe: healthy fake claude, dropped args, and missing claude ------ new_fixture f plugin_file "$f" alpha hooks/hooks.json "$NODE_ROW" -bin="$(mktemp -d)" +bin="$(mktemp -d "$TMP_ROOT/d.XXXXXX")" cat >"$bin/claude" <<'EOF' #!/usr/bin/env bash settings="" @@ -391,7 +394,7 @@ rm -rf "$f" "$bin" new_fixture f plugin_file "$f" alpha hooks/hooks.json "$NODE_ROW" -bin="$(mktemp -d)" +bin="$(mktemp -d "$TMP_ROOT/d.XXXXXX")" cat >"$bin/claude" <<'EOF' #!/usr/bin/env bash echo 'SyntaxError: Unexpected token :' >&2 @@ -410,7 +413,7 @@ rm -rf "$f" "$bin" new_fixture f plugin_file "$f" alpha hooks/hooks.json "$NODE_ROW" -hidden="$(mktemp -d)/bin" +hidden="$(mktemp -d "$TMP_ROOT/d.XXXXXX")/bin" mkdir -p "$hidden" for tool in bash jq python3 python uv node; do src="$(command -v "$tool" 2>/dev/null || true)" diff --git a/scripts/check-shell-portability.test.sh b/scripts/check-shell-portability.test.sh index d2f63971b6..5acd5633f4 100755 --- a/scripts/check-shell-portability.test.sh +++ b/scripts/check-shell-portability.test.sh @@ -19,6 +19,9 @@ # shellcheck disable=SC2016 # fixture bodies are literal shell content in single quotes; expansion is never wanted set -uo pipefail +TMP_ROOT="$(mktemp -d)" +trap 'rm -rf "$TMP_ROOT"' EXIT + SELF_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" REPO_ROOT="$(cd "$SELF_DIR/.." && pwd)" SCRIPT="$SELF_DIR/check-shell-portability.sh" @@ -53,14 +56,14 @@ scan_paths() { # pattern, so a synthetic case is not coupled to any other class. one_token_list() { local f - f="$(mktemp)" + f="$(mktemp "$TMP_ROOT/f.XXXXXX")" printf '%s\n' "$1" >"$f" printf '%s' "$f" } tmpsh() { local f - f="$(mktemp --suffix=.sh)" + f="$(mktemp --suffix=.sh "$TMP_ROOT/f.XXXXXX")" printf '%s\n' "$1" >"$f" printf '%s' "$f" } @@ -1254,7 +1257,7 @@ amp_clean 'a comment-only line naming the construct' '# never write ${var//pat/& # --- a QUOTE-JOINED record spans physical lines, and the hit is attributed to # the physical line it actually sits on — the same per-line attribution the ERE # classes get, reached through the shared report path. -f="$(mktemp --suffix=.sh)" +f="$(mktemp --suffix=.sh "$TMP_ROOT/f.XXXXXX")" printf 'msg="opened here\nv=${v//X/&}"\n' >"$f" if out="$(scan_paths "$amptok" "$f" 2>&1)"; then fail "an & hit on the second physical line should fire, got success: $out" @@ -1476,7 +1479,7 @@ rm -rf "$fx" "$tok" # Fail-closed behavior # ============================================================================= -BAD_TOKENS="$(mktemp)" +BAD_TOKENS="$(mktemp "$TMP_ROOT/f.XXXXXX")" printf '%s\n' '(unterminated' >"$BAD_TOKENS" # unmatched '(' -- invalid ERE, awk faults f="$(tmpsh 'grep -Eq foo bar')" SHELL_PORTABILITY_TOKENS="$BAD_TOKENS" bash "$SCRIPT" --paths "$f" >/dev/null 2>&1 @@ -1493,7 +1496,7 @@ rm -f "$f" "$BAD_TOKENS" # from the awk program into the shell when the list parsing was extracted, so it # is asserted here alongside the twin's — a regression in the shared path must # turn BOTH suites red, not just the one that was historically missing it. -EMPTY_TOKENS="$(mktemp)" +EMPTY_TOKENS="$(mktemp "$TMP_ROOT/f.XXXXXX")" printf '# only comments\n#\n\n' >"$EMPTY_TOKENS" f="$(tmpsh 'grep -Eq foo bar')" SHELL_PORTABILITY_TOKENS="$EMPTY_TOKENS" bash "$SCRIPT" --paths "$f" >/dev/null 2>&1 @@ -2893,7 +2896,7 @@ rm -f "$f" # --- a QUOTED WORD spanning physical lines is one command, so its option must # still be reached; the newline is data inside the quotes, not a separator. -f="$(mktemp --suffix=.sh)" +f="$(mktemp --suffix=.sh "$TMP_ROOT/f.XXXXXX")" printf 'stat %s\nbar%s -c %%s\n' "'foo" "'" >"$f" if out="$(scan_paths "$REAL_TOKENS" "$f" 2>&1)"; then fail "a quoted newline should not hide the option, got success: $out" @@ -2907,7 +2910,7 @@ rm -f "$f" # --- a joined record is attributed per PHYSICAL line: the hit reports at its # own line, and an annotation excuses only the line carrying it. Without that, # one annotation anywhere inside a joined block would exempt all of it. -f="$(mktemp --suffix=.sh)" +f="$(mktemp --suffix=.sh "$TMP_ROOT/f.XXXXXX")" printf 'x=%sopen\nstat -c %%s "$f"\ndate -d @0 +%%s # portability-ok: fixture\nclose%s\n' "'" "'" >"$f" out="$(scan_paths "$REAL_TOKENS" "$f" 2>&1)" if grep -q "PORTABILITY: ${f}:2:" <<<"$out" && @@ -2920,7 +2923,7 @@ rm -f "$f" # --- a HEREDOC body is data: a stray backquote or apostrophe in it must not # open a frame that swallows the lines after the heredoc ends. -f="$(mktemp --suffix=.sh)" +f="$(mktemp --suffix=.sh "$TMP_ROOT/f.XXXXXX")" printf 'cat >/dev/null <<%sEOF%s\n"CustomRule%sPath" = %s./x%s\nEOF\ngrep -q %sneedle%s "$f"\nprintf "%%s" "x -Path y"\n' \ "'" "'" '`' "'" "'" "'" "'" >"$f" if scan_paths "$REAL_TOKENS" "$f" >/dev/null 2>&1; then @@ -2932,7 +2935,7 @@ rm -f "$f" # --- a heredoc body is still SCANNED: this corpus writes real scripts through # heredocs, and the gate deliberately matches inside literal text. -f="$(mktemp --suffix=.sh)" +f="$(mktemp --suffix=.sh "$TMP_ROOT/f.XXXXXX")" printf 'cat >/tmp/gen.sh <<%sEOF%s\nstat -c %%s "$f"\nEOF\n' "'" "'" >"$f" if out="$(scan_paths "$REAL_TOKENS" "$f" 2>&1)"; then fail "a heredoc body must still be scanned, got success: $out" @@ -2943,7 +2946,7 @@ rm -f "$f" # --- a `#` opening a physical line inside a joined record is a real comment, so # a commented-out fallback there cannot excuse a hit above it. -f="$(mktemp --suffix=.sh)" +f="$(mktemp --suffix=.sh "$TMP_ROOT/f.XXXXXX")" printf 'x=$(stat -c %%s "$f"\n# || stat -f %%z "$f"\n)\n' >"$f" if out="$(scan_paths "$REAL_TOKENS" "$f" 2>&1)"; then fail "a commented-out fallback in a joined record should not guard: $out" @@ -3013,7 +3016,7 @@ rm -f "$f" # --- and if a token list somehow loads no active pattern at all, the run fails # CLOSED rather than reporting a corpus it never checked as clean. -TOK="$(mktemp)" +TOK="$(mktemp "$TMP_ROOT/f.XXXXXX")" printf '# only a comment, no active pattern\n' >"$TOK" f="$(tmpsh 'grep -P x')" out="$(scan_paths "$TOK" "$f" 2>&1)" @@ -3051,7 +3054,7 @@ fi # `PORTABILITY: :` prefix belongs to the gate, which is what makes this a # different assertion from every one above rather than the same one twice. --- tok="$(one_token_list 'grep[[:space:]]+-P')" -f="$(mktemp --suffix=.sh)" +f="$(mktemp --suffix=.sh "$TMP_ROOT/f.XXXXXX")" printf 'echo first\ngrep -P x\n' >"$f" out="$(awk -f "$SCAN_AWK" "$tok" "$f" 2>&1)" rc=$? @@ -3147,7 +3150,7 @@ rm -f "$f" # --- a structural NEWLINE ends a command inside a substitution, so the gap # between a matched call and a `||` must stop at one. Reachable only since # records join on an unterminated quote. -f="$(mktemp --suffix=.sh)" +f="$(mktemp --suffix=.sh "$TMP_ROOT/f.XXXXXX")" printf 'x=$(stat -c %%s "$f"\ntrue) || stat -f %%z "$f"\n' >"$f" if out="$(scan_paths "$REAL_TOKENS" "$f" 2>&1)"; then fail "a later command on a newline owns the status, so this is no ladder: $out" @@ -3169,7 +3172,7 @@ rm -f "$f" # --- a `portability-scope:` line inside a HEREDOC BODY is generated data, not # this file declaring anything about itself, and must not exempt the file. A # grep pre-pass honored it wherever the characters appeared. -f="$(mktemp --suffix=.sh)" +f="$(mktemp --suffix=.sh "$TMP_ROOT/f.XXXXXX")" printf 'cat >/tmp/gen.sh <<%sEOF%s\n# portability-scope: generated fixture\nEOF\nstat -c %%s "$f"\n' \ "'" "'" >"$f" if out="$(scan_paths "$REAL_TOKENS" "$f" 2>&1)"; then @@ -3179,7 +3182,7 @@ else fi rm -f "$f" # a real declaration still exempts the whole file, wherever in it it sits -f="$(mktemp --suffix=.sh)" +f="$(mktemp --suffix=.sh "$TMP_ROOT/f.XXXXXX")" printf 'stat -c %%s "$f"\n# portability-scope: this file is a fixture corpus\n' >"$f" if scan_paths "$REAL_TOKENS" "$f" >/dev/null 2>&1; then ok "a genuine portability-scope declaration still exempts the whole file" @@ -3226,7 +3229,7 @@ for opener in "x='foo" 'y="foo' 'z=$(echo foo'; do 'y="foo') closer='bar"' ;; *) closer=')' ;; esac - f="$(mktemp --suffix=.sh)" + f="$(mktemp --suffix=.sh "$TMP_ROOT/f.XXXXXX")" printf '%s\n' "$opener" '# portability-scope: bogus' "$closer" 'date -d tomorrow' >"$f" if out="$(scan_paths "$REAL_TOKENS" "$f" 2>&1)"; then fail "a scope marker inside an open construct ($opener) must exempt nothing: $out" @@ -3238,7 +3241,7 @@ done # --- and a genuine declaration still grants whole-file scope from either side # of the hit, indented or not. -f="$(mktemp --suffix=.sh)" +f="$(mktemp --suffix=.sh "$TMP_ROOT/f.XXXXXX")" printf '%s\n' 'date -d tomorrow' ' # portability-scope: this file is a fixture corpus' >"$f" if scan_paths "$REAL_TOKENS" "$f" >/dev/null 2>&1; then ok "an indented declaration after the hit still exempts the whole file" @@ -3268,7 +3271,7 @@ rm -f "$f" # next physical line can no longer promote it. POSIX removes `\` # during tokenization (2.2.1), so this spelling IS `$((` and the ladder is # guarded; committing the shorter reading reported it as an unguarded call. -f="$(mktemp --suffix=.sh)" +f="$(mktemp --suffix=.sh "$TMP_ROOT/f.XXXXXX")" printf 'stat -c %%s $(\\\n(1 | 2)) || stat -f %%z f\n' >"$f" if scan_paths "$REAL_TOKENS" "$f" >/dev/null 2>&1; then ok "a \$(( split by a line continuation is still one arithmetic expansion" @@ -3278,7 +3281,7 @@ fi rm -f "$f" # ...and the same command written on ONE line is the control: the shell sees # the same tokens either way, so the gate must too. -f="$(mktemp --suffix=.sh)" +f="$(mktemp --suffix=.sh "$TMP_ROOT/f.XXXXXX")" printf 'stat -c %%s $((1 | 2)) || stat -f %%z f\n' >"$f" if scan_paths "$REAL_TOKENS" "$f" >/dev/null 2>&1; then ok "the same ladder without the continuation is clean too" @@ -3293,7 +3296,7 @@ rm -f "$f" # than re-decide a `#` that now sits behind the commit point. Forgetting it # hands the `||` back its control-operator meaning and a commented-out fallback # starts excusing an unguarded call again. -f="$(mktemp --suffix=.sh)" +f="$(mktemp --suffix=.sh "$TMP_ROOT/f.XXXXXX")" printf 'v=$(true # note\nstat -c %%s "$f" || stat -f %%z "$f")\n' >"$f" if out="$(scan_paths "$REAL_TOKENS" "$f" 2>&1)"; then fail "a ladder inside a comment's shadow should fire, got success: $out" @@ -3306,7 +3309,7 @@ rm -f "$f" # ...with the comment removed the identical second line IS live code, so the # same ladder is a real one. This is the control that pins the difference on # the comment rather than on the join. -f="$(mktemp --suffix=.sh)" +f="$(mktemp --suffix=.sh "$TMP_ROOT/f.XXXXXX")" printf 'v=$(true\nstat -c %%s "$f" || stat -f %%z "$f")\n' >"$f" if scan_paths "$REAL_TOKENS" "$f" >/dev/null 2>&1; then ok "the same joined ladder without the comment is a real ladder" @@ -3323,7 +3326,7 @@ rm -f "$f" # closes inside the one containing it), so a depth-keyed hold is exercised as # well as the count. tok="$(one_token_list '!subst-replacement-ampersand')" -f="$(mktemp --suffix=.sh)" +f="$(mktemp --suffix=.sh "$TMP_ROOT/f.XXXXXX")" printf 'a="${x//p/&} one\n${y//q/${z//r/&}} two"\n' >"$f" if out="$(scan_paths "$tok" "$f" 2>&1)"; then fail "an & in a joined record should fire, got success: $out" @@ -3339,7 +3342,7 @@ rm -f "$f" "$tok" # physical line has to be remembered: forget it and the next line's first `))` # closes the expansion early, after which the `||` behind it reads as a control # operator and a fallback that the shell never reaches starts guarding the call. -f="$(mktemp --suffix=.sh)" +f="$(mktemp --suffix=.sh "$TMP_ROOT/f.XXXXXX")" printf 'stat -c %%s "$f" $(( (1 +\n2)) || stat -f %%z "$f"\n' >"$f" if out="$(scan_paths "$REAL_TOKENS" "$f" 2>&1)"; then fail "a || inside an unterminated arithmetic expansion should not guard: $out" @@ -3350,7 +3353,7 @@ else fi rm -f "$f" # ...same text on one line, same verdict: the join is not what decides it. -f="$(mktemp --suffix=.sh)" +f="$(mktemp --suffix=.sh "$TMP_ROOT/f.XXXXXX")" printf 'stat -c %%s "$f" $(( (1 + 2)) || stat -f %%z "$f"\n' >"$f" if scan_paths "$REAL_TOKENS" "$f" >/dev/null 2>&1; then fail "the unjoined control must report the same unguarded call" @@ -3361,7 +3364,7 @@ rm -f "$f" # ...and the depth is held per FRAME, so the same shape one frame deeper (the # expansion nested inside a `$( )` rather than sitting at the top level) has to # survive the boundary on its own stack slot. -f="$(mktemp --suffix=.sh)" +f="$(mktemp --suffix=.sh "$TMP_ROOT/f.XXXXXX")" printf 'stat -c %%s "$f" $(printf %%s $(( (1 +\n2)) ) || stat -f %%z "$f"\n' >"$f" if out="$(scan_paths "$REAL_TOKENS" "$f" 2>&1)"; then fail "a nested arithmetic frame should keep its depth across the join: $out" @@ -3371,7 +3374,7 @@ else fail "expected the line-1 call reported, got: $out" fi rm -f "$f" -f="$(mktemp --suffix=.sh)" +f="$(mktemp --suffix=.sh "$TMP_ROOT/f.XXXXXX")" printf 'stat -c %%s "$f" $(printf %%s $(( (1 + 2)) ) || stat -f %%z "$f"\n' >"$f" if scan_paths "$REAL_TOKENS" "$f" >/dev/null 2>&1; then fail "the unjoined nested control must report the same unguarded call" @@ -3384,7 +3387,7 @@ rm -f "$f" # The mechanism cases run on two fake awks with distinct banners, so they hold on # any host. The fixture pair after them needs real gawk and mawk, the pair that # disagree on the escape. -probe_dir="$(mktemp -d)" +probe_dir="$(mktemp -d "$TMP_ROOT/d.XXXXXX")" mkdir -p "$probe_dir/bin" printf '#!/usr/bin/env bash\n[[ "${1:-}" == --version ]] && { echo "fake-a 1"; exit 0; }\nexit 0\n' >"$probe_dir/bin/fake-a" printf '#!/usr/bin/env bash\n[[ "${1:-}" == --version ]] && { echo "fake-b 1"; exit 0; }\nexit 3\n' >"$probe_dir/bin/fake-b" diff --git a/scripts/gen-hook-event-registry.test.sh b/scripts/gen-hook-event-registry.test.sh index 2873271eaa..1581077c63 100755 --- a/scripts/gen-hook-event-registry.test.sh +++ b/scripts/gen-hook-event-registry.test.sh @@ -5,6 +5,9 @@ # Hooks reference lifecycle table, so nothing here touches the network. set -uo pipefail +TMP_ROOT="$(mktemp -d)" +trap 'rm -rf "$TMP_ROOT"' EXIT + SELF_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" REPO="$(cd "$SELF_DIR/.." && pwd)" SCRIPT="$SELF_DIR/gen-hook-event-registry.sh" @@ -147,7 +150,7 @@ if ((disagree == 0)); then ok "registry categories agree with slog_category_to"; # --- the under-25-rows refusal --------------------------------------------------- new_fixture f -short="$(mktemp)" +short="$(mktemp "$TMP_ROOT/f.XXXXXX")" FIXTURES+=("$short") head -12 "$TABLE" >"$short" out=$(bash "$SCRIPT" --from "$short" --root "$f" 2>&1) @@ -160,7 +163,7 @@ fi # --- an unknown event is excluded with a warning, never registered ------------------ new_fixture f -odd="$(mktemp)" +odd="$(mktemp "$TMP_ROOT/f.XXXXXX")" FIXTURES+=("$odd") cp "$TABLE" "$odd" # shellcheck disable=SC2016 # the backticks are markdown table text, not a substitution diff --git a/scripts/lib/changed-files.test.sh b/scripts/lib/changed-files.test.sh index abc9106aa8..cec2a0eb52 100755 --- a/scripts/lib/changed-files.test.sh +++ b/scripts/lib/changed-files.test.sh @@ -11,6 +11,9 @@ # one failing test if it is ever dropped again. set -uo pipefail +TMP_ROOT="$(mktemp -d)" +trap 'rm -rf "$TMP_ROOT"' EXIT + SELF_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" SCRIPTS_DIR="$(cd "$SELF_DIR/.." && pwd)" # shellcheck source=changed-files.sh @@ -24,7 +27,7 @@ SCRIPTS_DIR="$(cd "$SELF_DIR/.." && pwd)" # mk_repo -> prints the path of a fresh repo with one committed base tree. mk_repo() { local dir - dir="$(mktemp -d)" + dir="$(mktemp -d "$TMP_ROOT/d.XXXXXX")" git_init_test_repo "$dir" >/dev/null || return 1 mkdir -p "$dir/plugins/p1/skills/alpha" "$dir/docs" printf 'seed\n' >"$dir/plugins/p1/skills/alpha/SKILL.md" @@ -97,7 +100,7 @@ else fail "resolve_base returned an unresolvable ref" fi # A repo with no main/master and no origin/ has nothing to fall back to. -bare="$(mktemp -d)" +bare="$(mktemp -d "$TMP_ROOT/d.XXXXXX")" git_init_test_repo "$bare" >/dev/null printf 'seed\n' >"$bare/f.txt" git_test_config "$bare" add -A >/dev/null diff --git a/scripts/lib/gate-entry.test.sh b/scripts/lib/gate-entry.test.sh index eb330648ad..d184727d78 100755 --- a/scripts/lib/gate-entry.test.sh +++ b/scripts/lib/gate-entry.test.sh @@ -6,6 +6,9 @@ # shellcheck disable=SC2016 # child programs stay single-quoted so this shell does not expand $1 before bash -c set -uo pipefail +TMP_ROOT="$(mktemp -d)" +trap 'rm -rf "$TMP_ROOT"' EXIT + SELF_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" REPO_ROOT="$(cd "$SELF_DIR/../.." && pwd)" # shellcheck source=changed-files.sh @@ -102,7 +105,7 @@ else fi # --all and --paths must not consult a ref, even when git cannot resolve one. -log="$(mktemp)" +log="$(mktemp "$TMP_ROOT/f.XXXXXX")" rc=0 child 'source "$1"; git() { printf x >>"$2"; return 1; }; gate_entry::classify --all' "$log" || rc=$? if [[ "$rc" -eq 0 && ! -s "$log" ]]; then @@ -229,7 +232,7 @@ fi # --- empty discovery is not a failed discovery ---------------------------- -repo="$(mktemp -d)" +repo="$(mktemp -d "$TMP_ROOT/d.XXXXXX")" git_init_test_repo "$repo" >/dev/null printf 'seed\n' >"$repo/f.txt" git_test_config "$repo" add -A >/dev/null @@ -333,7 +336,7 @@ else fail "stale base-ref allowlist entries: $stale" fi -scratch="$(mktemp -d)" +scratch="$(mktemp -d "$TMP_ROOT/d.XXXXXX")" printf '%s\n' 'git rev-parse -q --verify "$b^{commit}" >/dev/null || exit 2' >"$scratch/scratch.sh" if [[ -n "$(base_ref_predicate_hits "$scratch")" ]]; then ok "a scratch script hand-rolling the predicate is caught" diff --git a/scripts/lib/read-list.test.sh b/scripts/lib/read-list.test.sh index 176a3f21f2..4cde9dd280 100755 --- a/scripts/lib/read-list.test.sh +++ b/scripts/lib/read-list.test.sh @@ -9,6 +9,9 @@ # collapsed the two (#3161). set -uo pipefail +TMP_ROOT="$(mktemp -d)" +trap 'rm -rf "$TMP_ROOT"' EXIT + SELF_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" # shellcheck source=read-list.sh . "$SELF_DIR/read-list.sh" @@ -19,7 +22,7 @@ SELF_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" # mk -> path of a temp list file mk() { local f - f="$(mktemp)" + f="$(mktemp "$TMP_ROOT/f.XXXXXX")" printf '%s' "$1" >"$f" printf '%s' "$f" } diff --git a/scripts/test-tmp-cleanup-baseline.txt b/scripts/test-tmp-cleanup-baseline.txt index 10a60e4499..32fb0b7ecc 100644 --- a/scripts/test-tmp-cleanup-baseline.txt +++ b/scripts/test-tmp-cleanup-baseline.txt @@ -2,36 +2,3 @@ # repo-relative path per line; a `#` starts a comment. Stale-guarded: an entry # whose file is gone or no longer offends fails the gate, so the list only # shrinks. Never add a suite here: give it the trap instead. -plugins/docs-hygiene/skills/compress/scripts/audit-scan.test.sh -plugins/firecrawl/skills/update/scripts/update.test.sh -plugins/instruction-placement/scripts/precompute.test.sh -plugins/instruction-placement/scripts/verify-load.test.sh -plugins/playbooks/skills/boris/scripts/update.test.sh -plugins/playbooks/skills/skill-authoring/scripts/update.test.sh -plugins/playwright/skills/playwright/scripts/update.test.sh -plugins/session-flow/scripts/harness/hop_chain.test.sh -plugins/testing/scripts/gen-hook-filters.test.sh -plugins/work-items/tools/work-item-tracker/adapters/github/common.test.sh -plugins/work-items/tools/work-item-tracker/adapters/github/create-item.test.sh -plugins/work-items/tools/work-item-tracker/adapters/github/lease-coordination.test.sh -plugins/work-items/tools/work-item-tracker/adapters/github/list-sub-items.test.sh -plugins/work-items/tools/work-item-tracker/adapters/local-markdown/claim-integrity.test.sh -plugins/work-items/tools/work-item-tracker/adapters/local-markdown/claim.test.sh -plugins/work-items/tools/work-item-tracker/adapters/local-markdown/create-item.test.sh -plugins/work-items/tools/work-item-tracker/adapters/local-markdown/list-items.test.sh -plugins/work-items/tools/work-item-tracker/adapters/local-markdown/renew-lease.test.sh -plugins/work-items/tools/work-item-tracker/conformance/bindings/jira.test.sh -plugins/work-items/tools/work-item-tracker/conformance/bindings/linear.test.sh -plugins/work-items/tools/work-item-tracker/conformance/bindings/local-markdown.test.sh -scripts/affected-tests.test.sh -scripts/check-changed-skills.test.sh -scripts/check-contract-slice-prune.test.sh -scripts/check-detector-eval-coverage.test.sh -scripts/check-discriminating-test-skips.test.sh -scripts/check-docs-only.test.sh -scripts/check-exec-form-windows-probe.test.sh -scripts/check-shell-portability.test.sh -scripts/gen-hook-event-registry.test.sh -scripts/lib/changed-files.test.sh -scripts/lib/gate-entry.test.sh -scripts/lib/read-list.test.sh