feat: compile-check replays a changed-files check whose inputs did not change

Merged
HA
1d ago
+326-29

Summary

  • Running compile-check again on the same edit no longer checks the same changed files a second time. When an assembly's inputs, its baseline and the worker build are all the ones the previous changed-files check saw, the run reports that check's result without starting the worker.
  • The output says so: FastChecks[].Reused is true for a check that was replayed.

User Impact

  • Before: every run re-checked each fast-checked assembly through the worker, even when nothing had changed since the last run. That happens often when an agent fixes another assembly or simply runs the command again to confirm. Each repeat cost about as much as the first check.
  • After: a repeat of the same edit is answered from the record of the last check. The --full reminder and UncheckedCodes still show, because the replayed assembly stays among the fast-checked ones.
  • On a large project (561 assemblies, one method body edited in a large assembly), the second run over the same edit took 0.48 s against 1.93 s for the run that checked it (medians of three edits), a quarter of the time. Details under Verification.

Changes

  • When a check is replayed. Three keys must match the record: the input key, the baseline's input key and the worker build key. The record is looked up inside the check, after every condition the check itself needs has passed: the baseline is usable, the non-source inputs and generator output are unchanged, 1 to 30 files changed, and the worker build matches the baseline. A check is therefore replayed only where it would have run, for the inputs it ran on. --full and --all never reach it.
  • What is recorded. A fresh check that the run uses is recorded through the record store added in the previous PR. Recorded: success, the merged diagnostics, the changed files, the unchecked codes, and whether generators were not rerun.
  • Edits during a check. Before recording, the input key is computed again. The worker reads the sources while it checks, so an edit made during the check would otherwise be recorded under the key of the bytes before it. Putting those bytes back would then replay what the edited file reported. When the key moved, the result still answers this run, but nothing is recorded, and the next run checks again. An edit that is made and then undone within one check still cannot be told apart; seeing it would need the worker to report hashes of what it read.
  • A failed write fails the run, as a compile result that cannot be recorded already does. This record exists for the next run, like the compile result.
  • Output. UnitResult.FastCheckReused is new and maps to FastChecks[].Reused. The replay deliberately does not set UnitResult.Reused. That flag moves an assembly to ReusedAssemblies, which would drop it from FastChecks and hide the --full reminder and UncheckedCodes.
  • Changed test expectation. TestFastPathCleanResultKeepsBaselineFiles used to require that a clean check leave every file the check keeps for the assembly untouched. A check now adds its own record. The test therefore requires every earlier file to keep its hash and exactly one file to be added. It still does not name the record, so the package stays unaware of the store's file names.
  • The compile-check doc, the skill and ADR 0015 describe the record, the replay and Reused.

Verification

  • New tests:

    • a second run over the same edit replays: no worker call, every reported field the same, with an error so a wrong Succeeded shows;
    • another edit checks again and replaces the record;
    • a changed worker build falls back instead of replaying;
    • missing generator output falls back instead of replaying;
    • a --full compile leaves no record of the earlier check;
    • a record that cannot be written fails the run;
    • an edit during the check leaves no record, so reverting it checks again;
    • the dispatcher marks a replayed check and keeps it fast-checked, with the --full reminder.
  • Mutations, each restored afterwards:

    MutationFails
    Look up before the worker check, with the baseline's worker keythe worker-build test
    Look up right after the baseline loadsthe worker-build and generator-output tests
    Ignore a failed writethe failed-write test
    Set Reused instead of FastCheckReusedthe replay test and the another-edit test
    Never set FastCheckReusedthe replay test and the another-edit test
    Never recordthe replay, another-edit, failed-write and clean-check tests
    Record without UncheckedCodesthe replay test
    Record without GeneratorsNotRerunthe replay test
    Replay without the changed filesthe replay test
    Forget keeps the recordthe --full test and the store's test
    Skip recomputing the key before recordingthe edited-during-check test
    Dispatcher leaves Reused unsetthe dispatcher test
  • cli/dispatcher:

    • golangci-lint fmt --diff is empty; go vet ./... passes; golangci-lint run and the complexity config both report 0 issues.
    • go test ./... and go test -race ./internal/compilecheck/... pass.
    • Coverage with the CI exclusions: 94.24% (baseline 94.2).
  • The compile-check package source has no name of the record (git grep -P for the record type, its constants and its file name returns nothing).

  • check-skill-size passes. The skill is 6,818 bytes. scripts/sync-tool-docs.sh --check reports no drift.

  • cli/release-automation: go test ./... passes. The file-length check passes.

  • Measured on a large Unity project (561 assemblies), with one method body edited in a large assembly. This build and the previous integration-branch build ran alternately on the same output directory, over three different edits:

    RunMedian real time
    Previous build, checking (6 runs)1.79 s
    This build, first run on an edit (checks and records)1.93 s
    This build, second run on the same edit (replays)0.48 s
    • Recomputing the key before recording does not show above the noise: against the previous build's run just before it on the same edit, the first run differed by -270, -70 and +40 ms.
    • The JSON output of every replay matched the run that checked the edit, apart from FastChecks[].Reused; so did a replay of an edit with a type error. The first run's output matched the previous build's.
    • The binary was built from the head of the stacked follow-up (#3330), which changes only how a full compile records its result; the changed-files check is the code in this PR.