diff --git a/.changeset/fix-windows-staging-path.md b/.changeset/fix-windows-staging-path.md new file mode 100644 index 0000000..e634730 --- /dev/null +++ b/.changeset/fix-windows-staging-path.md @@ -0,0 +1,5 @@ +--- +'@systemfsoftware/claude-code-comment-checker': patch +--- + +Fix Windows artifact upload path and execute full release pipeline. diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 95ff14c..802c3d5 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -97,7 +97,7 @@ jobs: uses: actions/cache@v4 with: path: .turbo/cache - key: turbo-${{ runner.os }}-${{ hashFiles('rust-toolchain.toml', 'Cargo.toml', 'Cargo.lock', '.cargo/config.toml', 'crates/comment-checker/**', 'turbo.json') }} + key: turbo-${{ runner.os }}-${{ hashFiles('rust-toolchain.toml', 'Cargo.toml', 'Cargo.lock', '.cargo/config.toml', 'crates/comment-checker/**', 'package.json', 'turbo.json') }} restore-keys: | turbo-${{ runner.os }}- @@ -120,4 +120,4 @@ jobs: pnpm-store-${{ runner.os }}- - name: Mutation gate via turbo (core classifier, 100%) - run: pnpm install --frozen-lockfile && pnpm mutants + run: pnpm install --frozen-lockfile && pnpm gate:mutants diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 707a5e0..be5c632 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -106,24 +106,24 @@ jobs: run: ./scripts/tools/run-binary-smoke.ts --target ${{ matrix.target }} --bin-dir target/${{ matrix.target }}/release - name: Stage platform package - run: ./scripts/tools/stage-platform-package.ts --target ${{ matrix.target }} --suffix ${{ matrix.suffix }} --stage ${{ runner.temp }}/platform-${{ matrix.suffix }} --bin-dir target/${{ matrix.target }}/release + run: ./scripts/tools/stage-platform-package.ts --target ${{ matrix.target }} --suffix ${{ matrix.suffix }} --stage dist/staging/platform-${{ matrix.suffix }} --bin-dir target/${{ matrix.target }}/release - name: Bundle tarball - run: ./scripts/tools/bundle-release-tarball.ts --target ${{ matrix.target }} --bin-dir target/${{ matrix.target }}/release --out-dir ${{ runner.temp }}/release-tarball-${{ matrix.suffix }} + run: ./scripts/tools/bundle-release-tarball.ts --target ${{ matrix.target }} --bin-dir target/${{ matrix.target }}/release --out-dir dist/release-tarball-${{ matrix.suffix }} - name: Upload artifacts uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 with: name: release-${{ matrix.suffix }} path: | - ${{ runner.temp }}/release-tarball-${{ matrix.suffix }}/comment-checker-${{ matrix.target }}.tar.gz - ${{ runner.temp }}/platform-${{ matrix.suffix }}/binarySha256 + dist/release-tarball-${{ matrix.suffix }}/comment-checker-${{ matrix.target }}.tar.gz + dist/staging/platform-${{ matrix.suffix }}/binarySha256 - name: Upload staged platform package uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 with: name: platform-stage-${{ matrix.suffix }} - path: ${{ runner.temp }}/platform-${{ matrix.suffix }} + path: dist/staging/platform-${{ matrix.suffix }} if-no-files-found: error publish: diff --git a/docs/solutions/integration-issues/cached-gate-task-scope-exceeds-determinants.md b/docs/solutions/integration-issues/cached-gate-task-scope-exceeds-determinants.md new file mode 100644 index 0000000..70faba6 --- /dev/null +++ b/docs/solutions/integration-issues/cached-gate-task-scope-exceeds-determinants.md @@ -0,0 +1,130 @@ +--- +title: A cached gate whose task scope exceeds its determinant scope has a hit rate of zero on the runs that matter +date: 2026-08-21 +updated: 2026-08-23 +supersedes_own_prior_revision: true +category: integration-issues +module: cargo-mutants mutation gate (cached mutation task + CI mutation job transport cache) +problem_type: integration_issue +component: tooling +severity: high +symptoms: + - "The mutation gate re-executed its full multi-minute mutant loop on every release run while reporting a restored transport cache in the same job" + - "Engine log reads `cache miss, executing ` immediately after the transport step reports `Cache restored successfully` — two caches, one hit, one miss, same job" + - "Transport post-job reports `Cache hit occurred on the primary key ..., not saving cache`, so the freshly computed verdict is discarded and the next identical run misses again — the miss never self-heals" + - "Earlier inverse symptom in the same subsystem: a toolchain-definition-only edit left the engine hash unmoved and replayed a stale verdict" +root_cause: config_error +resolution_type: config_change +related_components: + - development_workflow + - testing_framework +tags: [turbo, cargo-mutants, mutation-testing, cache-key, task-scope, actions-cache, determinant-set, hash-coverage, spurious-invalidation] +--- + +# Task scope must equal determinant scope, or the cache is decoration + +## Problem + +The mutation gate — a cached `cargo mutants` run over the core classifier — was declared as a **package-scoped** task owned by the npm launcher package, while its command escaped that package to run at the repository root against the Rust crate. The build engine unconditionally hashes the owning package's manifest into a package-scoped task hash. That manifest carries the launcher's published version, which the release automation rewrites on every release. The version field is not a determinant of a mutation verdict, yet it sat in the verdict's cache key, so every release produced a fresh hash and a guaranteed full re-execution. + +The transport layer then made the miss permanent. The CI transport cache key hashed only the true determinants, so it *hit* while the engine hash *missed*. Exact-key restore suppresses the post-job save, so the fresh verdict computed under the new hash was thrown away every time. Two keys for one artifact, moving independently: the cache could never converge. + +## Symptoms + +- Engine reports `cache miss, executing ` in the same job where the transport step reports a successful restore. +- Transport post-job reports `Cache hit occurred on the primary key ..., not saving cache` — the deposit is refused precisely when it is needed. +- The re-execution correlates with release commits, not with source changes: a version bump and changelog append are sufficient to force it. +- No error and no warning at any layer. Each layer is behaving as documented; only the wall-clock cost reveals the defect. +- Inverse historical symptom in the same subsystem: an edit confined to the dev-shell toolchain definition left the hash unmoved and replayed a stale verdict. + +## What Didn't Work + +- **Reading the declared input list as proof of the hash surface.** The declared inputs named only Rust sources, manifests, lockfiles and the toolchain pin. The launcher manifest appeared in the hash anyway, because package-scoped tasks hash their owner's manifest automatically. A declared-input audit cannot see automatic inputs; only a hash-movement probe can. +- **Widening the transport key to chase the engine.** This is the prior revision's prescription and it is sound but unstable: it makes the key a maintenance dependency of an implicit, engine-version-specific set. It drifted the moment the toolchain-pin change rewrote the determinant list, and the drift was silent. +- **Treating the transport cache as a second opinion.** It is a bag labelled by its key, not a verdict store. A bag hit says nothing about whether the bag contains the entry the engine wants. +- **Two falsified hypotheses — do not re-litigate:** + - *False: "editing the gate command body replays a stale verdict."* The engine self-hashes the task's script body; editing it moves the hash and forces a miss. Command bodies need no explicit input. + - *False: "environment or task-option changes replay stale verdicts."* Declared environment values and task options are hashed; a divergent build-jobs value moved the hash and missed. + +## Mechanism + +Let $D$ be the determinant set of a verdict — every byte whose change can legitimately change the outcome — and $I$ the engine's actual hash input set, automatic inputs included. Two independent failure directions exist, and the prior revision of this document covered only the first: + +$$ +D \setminus I \neq \emptyset \;\Rightarrow\; \textbf{stale replay (unsound)} +$$ +$$ +I \setminus D \neq \emptyset \;\Rightarrow\; \textbf{spurious invalidation (sound, zero value)} +$$ + +Soundness requires $D \subseteq I$. **Usefulness requires $I \subseteq D$.** A cache is worth its complexity only at $I = D$. + +Spurious invalidation is not uniformly harmless. Let $p$ be the probability that a member of $I \setminus D$ is mutated between two runs. Hit rate is bounded by $1 - p$. When a member of $I \setminus D$ is written by the *release automation itself*, $p = 1$ on release runs, so: + +$$ +\text{hit rate on release runs} = 0 +$$ + +The cache then costs storage, key maintenance and reader confusion while returning nothing on exactly the runs whose latency is most visible. + +The source of $I \setminus D$ here was **scope mismatch**. A package-scoped task inherits its owner package's manifest as an automatic hash input. The gate's determinants lived entirely outside that package — the command's own first act was to leave it. Therefore: + +$$ +\text{scope}(\text{task}) \neq \text{scope}(D) \;\Rightarrow\; I \setminus D \supseteq \{\text{owner manifest}\} +$$ + +The transport layer adds the second condition. Let $K$ be the transport bag key surface: + +$$ +K \subsetneq I \;\Rightarrow\; \exists\, \text{engine-hash change with no key change} \;\Rightarrow\; \text{exact-key restore} \;\Rightarrow\; \text{save suppressed} +$$ + +A subset key does not corrupt verdicts — the engine hash still gates replay — but it **discards every deposit** made under a hash the key cannot distinguish, converting the cache into a permanent re-execute. Note the interaction that made this defect self-sustaining: the scope mismatch inflated $I$ with a field absent from $K$, so $K \subsetneq I$ held for exactly the field that changed every release. + +## Solution + +Collapse both conditions by making scope, determinants and key coincide, rather than maintaining a superset relation between three independently-edited surfaces. + +1. **Re-scope the task to a root task.** A gate whose command reads outside its own package is a root-workspace task, not a package task. Declaring it at the root removes the launcher manifest from $I$ entirely, because the root task's owner is the workspace itself. The gate command moves to the root workspace manifest's script table; the launcher manifest loses the script it never legitimately owned. +2. **Give the cached form a distinct invocation name.** A root task executes the identically-named script from the root manifest, so a script that shells back into the engine for the same task name recurses. Keep the raw command under the task's own name and expose the cached entrypoint under a distinct name. +3. **Declare $I$ as exactly $D$**: mutation-target sources and tests, the crate manifest, the workspace dependency manifest and lockfile, the build config, and the toolchain pin. Nothing else. Every prior member of $I \setminus D$ — the launcher manifest, and the nix flake and its lock, which the toolchain-pin change had already made non-determinants — stays out. +4. **Set the transport key to the same enumeration**, plus the descriptors the engine self-hashes for a root task (the workspace task configuration and the root workspace manifest). With $K = I = D$ the three surfaces move together by construction and cannot drift apart under later edits. + +## Architectural Invariants + +- **Scope–determinant identity.** A cached task must be declared at the scope of its determinants. A command whose first action leaves its own package is misfiled; re-scope it rather than compensating downstream. Corollary: any automatic hash input that is not a determinant is a latent hit-rate bug, and one written by automation is a guaranteed one. +- **Two-sided key correctness.** $D \subseteq I$ buys soundness; $I \subseteq D$ buys value. Audit both directions. A cache audit that checks only for missing inputs will certify a cache with a hit rate of zero. +- **Release-mutated fields are never cache inputs.** Version strings, changelog bodies and other release artifacts are outputs of the shipping process. Any of them inside a gate's hash sets $p = 1$ on release runs. +- **Transport keys co-move with engine hashes.** A transport cache wrapped around a content-addressed engine must key on a surface that changes whenever the engine hash changes. Subset keys silently refuse deposits, because exact-key restore suppresses the save. Prefer equality by construction over a maintained superset: a superset is a standing obligation on every future edit to either surface. +- **Probe, don't trust.** Hash membership is an empirical property of the engine version in use. Establish it by mutating one file and observing hash movement, never from documentation or intuition. Automatic inputs are invisible to a reading pass of the declared inputs. +- **A stale verdict is observationally identical to a fresh one; a spurious miss is observationally identical to a real one.** Neither is discoverable from the pass/fail signal. Never disable caching to fix either — fix the key. + +## Proof protocol + +Probe with the engine's dry-run hash report, mutating one surface at a time, and assert on both the reported input key set and the hash: + +| Edit under test | Expected input set | Expected hash | Expected cache event | +| --- | --- | --- | --- | +| release version bump + changelog append | launcher manifest absent | unchanged | hit (full replay) | +| mutation-target source | present | changes | miss + re-execution | +| toolchain pin | present | changes | miss + re-execution | +| gate command body | self-hashed | changes | miss | +| declared env value | env-hashed | changes | miss | +| no change / reverted | — | unchanged | hit | + +Session verification (2026-08-23): pre-fix input set contained the launcher manifest alongside twenty Rust determinants, confirming $I \setminus D \neq \emptyset$ by direct report rather than inference. Post-fix, the input set contained only determinants plus the root descriptors the engine self-hashes. Cold run: miss, $117$ mutants, $113$ caught, $4$ unviable, $0$ survived, ~2m4s. Immediate re-run: hit, 9 ms. Simulated release bump — version rewritten and changelog appended, exactly the release automation's edit — hit, 8 ms. Pre-fix that same edit forced a full ~4 minute re-execution whose result was then discarded by the transport layer. + +The falsifying observation to demand before believing any fix here: a release-shaped edit followed by a cache **hit**. A passing gate proves nothing about the cache. + +## Prevention + +- Before declaring a cached task, enumerate its determinants, then declare it at the narrowest scope containing all of them and nothing else. Gate: the dry-run input report must equal that enumeration — check for extra members, not only missing ones. +- Never let release automation write a file inside a gate's hash surface. Gate: the release-bump probe row above must show an unchanged hash. +- Set the transport key equal to the engine's full surface rather than a superset, so the two cannot drift. Gate: whenever either surface changes, re-run the release-bump and source-edit probe rows; a key hit paired with an engine miss is the signature of drift. +- A command that changes directory out of its own package is the grep-able smell for scope mismatch. Treat it as a re-scope trigger, not a portability detail. +- Record falsified hypotheses (command-body staleness, env staleness — both false; transport-key widening — sound but unstable) so the coverage boundary is not re-derived from intuition. Gate: this document. + +## Related + +- docs/solutions/design-patterns/evidence-gated-context-aware-classification.md — the classifier gate whose verdict this cache transports; its enforcement section is the upstream consumer. +- docs/solutions/architecture-patterns/rust-cli-npm-distribution.md — shares the CI surface and the gate-the-derived-surface discipline; the release automation that rewrites the launcher version is described there. diff --git a/docs/solutions/integration-issues/turbo-mutants-flake-nix-cache-key.md b/docs/solutions/integration-issues/turbo-mutants-flake-nix-cache-key.md deleted file mode 100644 index 3d3f747..0000000 --- a/docs/solutions/integration-issues/turbo-mutants-flake-nix-cache-key.md +++ /dev/null @@ -1,114 +0,0 @@ ---- -title: Turbo-cached mutation gate replays stale verdicts when the toolchain definition misses the cache key -date: 2026-08-21 -category: integration-issues -module: cargo-mutants mutation gate (turbo mutants task + CI mutation job cache) -problem_type: integration_issue -component: tooling -severity: high -symptoms: - - "A flake.nix-only edit replayed the previous cargo-mutants verdict verbatim: the turbo task hash did not move and the run reported a local cache hit" - - "Changing the dev-shell toolchain (the cargo-mutants version lives in the flake's devShells.default package list) did not invalidate the cached verdict, so a stale green could replay instead of a fresh run" - - "Inverse CI-side symptom: config-only edits moved turbo's own hash but not the actions/cache bag key, so the gate re-executed on every CI run and the fresh verdict was never persisted (exact-key restore suppresses the post-job save — documented actions/cache behavior, per the CI workflow's own cache-step comment)" -root_cause: config_error -resolution_type: config_change -related_components: - - development_workflow - - testing_framework -tags: [turbo, cargo-mutants, mutation-testing, cache-key, flake, actions-cache, stale-cache, hash-coverage] ---- - -# Mutation gate cache hashed the toolchain pin but not the toolchain definition - -## Problem - -The turbo `mutants` task — the cached cargo-mutants gate over the core classifier — hashed the nix lockfile as an input but not the flake that defines the dev-shell toolchain, including the cargo-mutants version. An edit confined to the toolchain definition therefore left the task hash untouched and turbo replayed the previous verdict without running the mutant loop. The same class of gap existed one level up: the CI mutation job's actions/cache key did not cover the files turbo hashes beyond the task's declared inputs, so config-only edits re-executed the gate every run while the restored exact key suppressed the save — the fresh verdict never persisted. - -## Symptoms - -- Editing the toolchain definition did not invalidate the mutation gate; turbo replayed the previous verdict verbatim (task hash unchanged, local cache hit reported). -- Config-only edits (task options, the npm launcher's gate script) moved turbo's internal hash but not the CI bag key; the gate re-executed every CI run and nothing was saved back. -- No error, no warning: a stale replay is indistinguishable in output from a correct one. Only the absence of the expected multi-minute runtime exposes it. - -## What Didn't Work - -- **Trusting the task's input list as proof of coverage.** The list looked complete — lockfile, workspace config, source globs. An input covers only the file it names; the toolchain definition was simply absent. A reading pass cannot prove hash coverage; only a hash-movement probe can. -- **Treating the CI cache key as an independent ledger.** The transport cache (actions/cache) is a bag labeled by its key, not a second opinion. When the bag key is a strict subset of the engine's hash surface, unchanged inputs restore an exact key, and exact-key restore suppresses the post-run save — so any edit outside the bag key re-executes forever without persisting. -- **Two adversarial hypotheses, falsified by probe — do not re-argue them:** - - *False: "editing the gate script body replays stale verdicts."* Turbo self-hashes the task's package script body. Editing the launcher's `scripts.mutants` entry moved the hash and forced a miss. Script bodies are covered automatically; they need no explicit input. - - *False: "env or option changes replay stale verdicts."* Turbo hashes declared env values and its own task options. A probe setting a divergent `CARGO_BUILD_JOBS` value moved the hash and missed. Divergent environment busts the cache; it never replays. - -## Mechanism - -A cached verdict is sound if and only if every determinant of the verdict is in the key: - -``` -stale replay possible <=> Determinants(verdict) \ Inputs(hash) != {} -``` - -Determinants here: mutation-target sources and tests, workspace manifests, dependency lockfiles, toolchain definition + toolchain pin, build config, and the gate command itself. The bug was a single-element set difference: the toolchain definition file. Turbo's automatic hashing covers the script body and declared env values — never arbitrary repository files outside the package — so any file whose bytes change the verdict must appear in the task's explicit input list. - -The transport layer adds a second condition. The CI bag key must be a superset of the engine's full hash surface: - -``` -bag key ⊇ turbo hash surface (else: exact-key restore suppresses save) -``` - -A violation does not replay stale verdicts — turbo's own hash still gates replay — but it discards every fresh verdict produced under a hash the bag key cannot distinguish, turning the cache into a permanent re-execute. - -## Solution - -Fix on branch `turbo-rust` (PR opening on that branch), pending merge as of -this writing. - -1. Add the toolchain definition to the task's explicit inputs, beside the toolchain pin that was already there: - -``` -"inputs": [ - "$TURBO_ROOT$/package.json", - "$TURBO_ROOT$/Cargo.toml", - "$TURBO_ROOT$/Cargo.lock", - "$TURBO_ROOT$/flake.lock", - "$TURBO_ROOT$/flake.nix", - "$TURBO_ROOT$/.cargo/config.toml", - "$TURBO_ROOT$/crates/comment-checker/Cargo.toml", - "$TURBO_ROOT$/crates/comment-checker/src/**", - "$TURBO_ROOT$/crates/comment-checker/tests/**" -] -``` - -2. Widen the CI mutation job's actions/cache `hashFiles` key to a superset: the task input set above, plus the two descriptors turbo self-hashes (the turbo config and the npm launcher package manifest carrying the gate script). A narrower key silently discards fresh verdicts. -3. Restore build-state caching for the miss path (registry, git checkouts, target dir), keyed on the dependency lockfile + toolchain pin, sharing a common restore-key prefix with the gate job's build cache. -4. Set the build-jobs env var explicitly on the mutation run line, matching the gate job — declared env values are hash inputs, so an unset-or-varies value makes local and CI keys diverge for no reason. - -## Architectural Invariants - -- **Hash-coverage completeness:** every file that can change the verdict must be in the task's input list. Automatic coverage extends only to the script body and declared env values. Corollary: when moving a gate behind a cache, enumerate its determinants first, then make the input list equal that enumeration. -- **Transport-key superset:** a transport cache wrapped around a content-addressed engine must key on a superset of the engine's own hash surface, because exact-key restore suppresses the save. Subset keys do not corrupt verdicts; they discard deposits. -- **Probe, don't trust:** membership in the hash is an empirical property of the engine version in use. Establish it by editing the file alone and observing hash movement, not by documentation or intuition. -- **A stale verdict is observationally identical to a fresh one.** Never disable caching to fix staleness; fix the key. The only honest signal of a replay is missing execution time. - -## Proof protocol - -Probe with the engine's dry-run hash report (`turbo run mutants --dry=json` or equivalent), editing one file at a time: - -| Edit under test | Expected hash | Expected cache event | -| --- | --- | --- | -| toolchain definition (post-fix) | changes | miss + re-execution | -| gate script body | changes | miss (self-hashed) | -| declared env value | changes | miss (env-hashed) | -| no change / reverted file | unchanged | hit (full replay) | - -Session verification (2026-08-21, four runs): input-set change -> miss, 117 mutants, 113 caught, 4 unviable, 0 survived, ~2m36s; unchanged -> hit in 10 ms; toolchain-definition edit -> miss, full re-execution; revert -> hit in 8 ms. The pre-fix behavior — toolchain edit leaving the hash unchanged and replaying — was the defect this loop reproduced and closed. - -## Prevention - -- When a file must invalidate a cached gate, add it to the task's explicit inputs. Gate: the probe above — edit the file alone, the dry-run hash must change; revert, it must not. -- Keep the transport cache key a superset of the engine hash surface. Gate: whenever an input is added to the task, diff the transport key's file list against the task inputs plus self-hashed descriptors; every engine-side input must appear. -- Do not put a value in declared task env that must not be a hash input; every local variance busts the team cache. Gate: the env-value probe row above. -- Record falsified hypotheses (script-body staleness, env staleness — both false) so the coverage boundary is not re-litigated from intuition. Gate: this document. - -## Related - -- docs/solutions/design-patterns/evidence-gated-context-aware-classification.md — the classifier gate whose verdict this cache transports; its enforcement section is the upstream consumer. -- docs/solutions/architecture-patterns/rust-cli-npm-distribution.md — shares the CI surface and the gate-the-derived-surface discipline. diff --git a/npm/packages/comment-checker/package.json b/npm/packages/comment-checker/package.json index cac4b41..6a4a9e7 100644 --- a/npm/packages/comment-checker/package.json +++ b/npm/packages/comment-checker/package.json @@ -20,8 +20,7 @@ "scripts": { "build": "tsdown", "typecheck": "tsc -b", - "lint": "f=${OXLINT_FORMAT:-${AGENT:+agent}}; oxlint . --format=${f:-default}", - "mutants": "cd ../.. && cargo mutants --file crates/comment-checker/src/classify.rs --timeout 90" + "lint": "f=${OXLINT_FORMAT:-${AGENT:+agent}}; oxlint . --format=${f:-default}" }, "devDependencies": { "@effect/platform-node": "4.0.0-rc.108", diff --git a/package.json b/package.json index 6977ace..11f6851 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,8 @@ "build": "turbo build", "typecheck": "turbo typecheck", "lint": "turbo lint", - "mutants": "turbo mutants" + "mutants": "cargo mutants --file crates/comment-checker/src/classify.rs --timeout 90", + "gate:mutants": "turbo run //#mutants" }, "devDependencies": { "@effect/tsgo": "latest", diff --git a/turbo.json b/turbo.json index a495e4f..46dd515 100644 --- a/turbo.json +++ b/turbo.json @@ -47,16 +47,16 @@ ], "cache": true }, - "mutants": { + "//#mutants": { "outputLogs": "new-only", "inputs": [ - "$TURBO_ROOT$/rust-toolchain.toml", - "$TURBO_ROOT$/Cargo.toml", - "$TURBO_ROOT$/Cargo.lock", - "$TURBO_ROOT$/.cargo/config.toml", - "$TURBO_ROOT$/crates/comment-checker/Cargo.toml", - "$TURBO_ROOT$/crates/comment-checker/src/**", - "$TURBO_ROOT$/crates/comment-checker/tests/**" + "rust-toolchain.toml", + "Cargo.toml", + "Cargo.lock", + ".cargo/config.toml", + "crates/comment-checker/Cargo.toml", + "crates/comment-checker/src/**", + "crates/comment-checker/tests/**" ], "outputs": [], "cache": true