feat: compile-check keeps the records of skipped dependents and compiles one with no saved generator output

Merged
HA
4d ago
+445-66

Summary

  • A dependent that uloop compile-check leaves out now keeps its result, baseline and saved generator output, so its next own edit is checked on the changed files instead of compiled in full.
  • A unit with analyzers that has no saved generator output (one the check never compiled to the end) is compiled on a named change instead of being narrowed by names - whether it was picked up as a dependent or started on its own sources ahead of the reference - so a changed name only its generators write is no longer missed.

User Impact

  • Before: leaving a dependent out deleted its records. Its next edit compiled in full, and because its generator output was gone, name narrowing read only the hand-written sources and could miss a changed name a generator writes.
  • After: the records stay. The first named change after the check has never compiled a dependent with analyzers compiles it once (reported as no saved generator output), and later changes are narrowed using the generator output that compile saved.

Changes

  • skipUnchangedDependent no longer calls removeUnitOutputs. The units of the same run already read Unity's reference assembly for a skipped dependent because it counts as having kept the surface, and later runs trust the leftovers only through the input key and output hashes of the record. CompileUnit still clears the outputs before a compile.
  • nameIndex.lookup also returns whether the unit's saved generator output was read. namedEffect matches the changed names first; if none matches and the unit has analyzers but no saved generator output, the change touches it with the reason no saved generator output.
  • CompileUnit removes the generator output directory when csc did not finish (failed to start, was stopped, or exited without an error diagnostic), so "the directory exists" keeps meaning "a compile finished" - otherwise a failed compile left an empty directory and the next run narrowed the unit by its hand-written sources alone.
  • ChangedReference.Reason, NameIndexReport.WithoutGenerated (compiled units only, not blocked ones), and the VibeLog changed_by.reason / name_index.without_generated report it.
  • docs/compile-check.md drops the no baseline cause that no longer exists (a skip no longer removes the baseline).
  • docs/compile-check.md, ADR 0017 (with an amendment line), and docs/vibe-logs.md are updated.

Verification

  • cli/dispatcher: gofmt -l . empty, go vet ./..., golangci-lint run ./... 0 issues, go test ./... -count=1 pass.
  • Reversed/renamed tests: TestCompileUnitsKeepsTheOutputOfASkippedDependent (was ...SkipsADependentWhoseReferenceKeptItsSurface), TestCompileUnitsKeepsTheRecordOfASkippedDependent (was ...LeavesNoManifestForASkippedDependent).
  • New tests: TestCompileUnitsKeepsTheBaselineOfASkippedDependent, TestCompileUnitsReadsUnitysReferenceForASkippedDependent, TestCompileUnitsNamesTheChangedNameEvenWithoutSavedGeneratorOutput, TestCompileUnitsCompilesADependentWithoutSavedGeneratorOutput, TestCompileUnitsLeavesOutADependentWithoutAnalyzersByItsSourcesAlone, TestCompileUnitsLeavesOutADependentOnceItHasSavedGeneratorOutput, TestCompileUnitsKeepsTheSavedGeneratorOutputOfASkippedDependent (third run), TestCompileUnitsCompilesAgainADependentWhoseCompileNeverRan, TestCompileUnitsDoesNotCountABlockedDependentAsCompiledWithoutGeneratorOutput, TestCompileUnitsCompilesAgainAnEarlyUnitWithoutSavedGeneratorOutput (dependent and own-sources cases), TestCompileUnitLeavesNoGeneratorOutputWhenTheCompilerDoesNotRun, TestCompileUnitKeepsGeneratorOutputOnlyWhenTheCompilerFinished; VibeLog test extended for reason and without_generated.
  • Mutations (each reverted):
    • (a) call removeUnitOutputs in the skip again → ...KeepsTheOutputOfASkippedDependent, ...KeepsTheRecordOfASkippedDependent, ...KeepsTheBaselineOfASkippedDependent fail. ...ReadsUnitysReferenceForASkippedDependent passes under this mutation (it pins that Unity's reference is read, which held before too); it fails when the skip no longer marks the dependent as having kept the surface.
    • (b) drop the len(Analyzers) > 0 condition → ...LeavesOutADependentWithoutAnalyzersByItsSourcesAlone and six existing name-narrowing tests fail.
    • (c) drop the no-saved-output rule → ...CompilesADependentWithoutSavedGeneratorOutput and ...LeavesOutADependentOnceItHasSavedGeneratorOutput fail.
    • (d) keep the generator output directory of a compile that did not finish → the two compiler tests and ...CompilesAgainADependentWhoseCompileNeverRan fail.
    • (e) count blocked units in without_generated → ...DoesNotCountABlockedDependent... fails.
    • (f) apply the rule only to units selected as dependents → ...CompilesAgainAnEarlyUnitWithoutSavedGeneratorOutput fails.
    • (g) remove the saved generator output when skipping → ...KeepsTheSavedGeneratorOutputOfASkippedDependent fails.