Skip to content

grove v0.63.25: audit fixes (stale files, compiler inputs, cgo, retries, verdict history) - #42

Merged
tabladrum merged 6 commits into
mainfrom
release/v0.63.25
Oct 21, 2026
Merged

tabladrum merged 6 commits into
mainfrom
release/v0.63.25

Conversation

@tabladrum

Copy link
Copy Markdown
Contributor

Fixes five findings from the 2026-10-20 stack audit (prism docs/research/stack-audit-2026-10-20/REPORT.md), plus astkit v0.16.10.

Gates: GOWORK=off go test ./..., go vet ./... green. grove-eval (gin, cobra, socket.io, flask) identical to main and within baseline.json.

🤖 Generated with Claude Code

https://claude.ai/code/session_011Ai1qs5rPZ1cXyu74s4cdB

tabladrum and others added 6 commits October 20, 2026 21:22
A file whose stat no longer matched its stored record but whose content
could not be hashed (over the 10 MiB cap, not a regular file, unreadable)
stayed in currentFiles, so the prune pass kept the symbols of its last
readable version. Those facts were served as current with nothing marking
them stale (stack audit 2026-10-20, finding 1: a removed Target stayed
indexed after a.go grew past the cap).

Drop the facts rather than keep them labelled stale: Grove already
documents this contract for the sibling case (CHANGELOG "Parse failures
invalidate stale symbols: a changed file that fails to parse now has its
previously stored symbols pruned instead of serving the last successfully
parsed version"), and there is no per-file staleness label anywhere in the
store or query results to carry a "last good" claim. Mid-edit syntax errors
are unaffected: those files still parse partially and index what parsed.
The error remains in IndexResult.Errors, and is reported again on every run
until the file indexes, because the file is no longer stored.

Unreadable directories keep their existing shield (walkFailed): there the
files are not seen at all, which says nothing about their content.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
goListPackage did not decode CgoFiles, so the project importer and
packageFiles saw only GoFiles. A local package whose files all import "C"
(GoFiles=[], CgoFiles=[binding.go], astkit's thirdparty/tsobjc) loaded as
an empty package named "": importers lost its declarations, interface
dispatch through it failed, and its own files were never type-checked or
given import edges (stack audit 2026-10-20, finding 4).

cgo files are now parsed with the ordinary Go files in the project
importer, in packageFiles (import edges, per-package analysis), and the
per-package check sets FakeImportC — the handling the dependency source
importer in the same file already uses. The files are checked as written,
not the cgo-generated versions, so every position stays in the user's
source (test pins New->helper at 13:2 in binding.go). Type errors located
in cgo files are expected under FakeImportC (C.x is untyped) and no longer
mark the package as partially checked; an error elsewhere still does.

Verified against astkit's thirdparty/tsobjc: now name "tsobjc" with
GetLanguage declared, not partial.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…part

carryForwardVerdict re-read the previous merged verdict and, for every
analyzer whose edges were carried forward, appended all of its previous
lines and then the current ones. A failed attempt ("no edges produced —
previous native edges carried forward") is itself a carry line that is not
"skipped:", so it was carried again on the next run: one two-line
diagnostic grew to 4, 6, 8, 10 lines, and Grove's own index already held
repeated CSharp pass/no-edge messages (stack audit 2026-10-20, finding 10).

The lines of each analyzer's last pass that produced edges now live in
their own meta key (native-last-good). The persisted verdict per analyzer
is:
- ran and produced edges: this run's lines (they become the last good);
- carried forward: the last good lines once, then this run's lines, which
  say why nothing replaced them — "no changed files" (unchanged) versus
  "<tool> not found"/"no edges produced"/"timed out" (unavailable/failed).
  Older attempts are superseded, not accumulated;
- disabled, or no files of its languages: no last-good claim, since its
  native edges are dropped.

Line formats are unchanged, so Prism's readiness and completion checks read
the same prefixes. An index written before the key existed derives the last
good lines once from its stored verdict (deduplicated, without skip/carry
lines or pass timings).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Build configuration is plaintext (tsconfig.json, pom.xml, Cargo.toml) or
not indexed at all (go.mod, go.sum), and native scope was the set of
changed source languages. Editing only tsconfig.json after a completed
TypeScript pass therefore gave "js-ts: skipped: no changed files in its
languages", and a go.mod-only edit was a no-op index: the graph kept
import/type resolution computed under the old configuration (stack audit
2026-10-20, finding 2).

The walk now records compiler inputs per analyzer
(native.CompilerInputAnalyzers): go.mod/go.sum/go.work(.sum);
tsconfig*.json, jsconfig*.json, package.json; the Maven/Gradle files the
javac classpath cache already keys on; Cargo.toml/Cargo.lock;
compile_commands.json; *.csproj/Directory.Build.props; composer.json;
pyproject.toml/setup.cfg/requirements.txt (availability inputs). They are
fingerprinted even when gitignored (a generated compile_commands.json
usually is). Content hashes are stored in meta (native-inputs) and reused
while size+mtime match, so a touch is not a change. On an added, removed or
changed input:
- the analyzer is removed from native-complete, so it gets a full
  run-to-completion pass (on this run and until it completes);
- the no-op shortcut is skipped for that run;
- resident worker state is dropped for that analyzer only (TypeScript
  worker programs, javac worker, rust-analyzer server);
- result.Native says "<analyzer>: compiler inputs changed (<files>);
  running a full pass".
The first run that records fingerprints reports no change (a cold index
runs everything anyway). Inputs under an unreadable subtree keep their
stored state.

Tested end to end for TypeScript (tsconfig.json) and Go (go.mod edit
reruns go; touch-only stays a no-op; settles afterwards). The other
languages share the same path; their file lists are unit-tested only, as
no Java/Rust/C#/PHP end-to-end fixture runs the decision here.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…unded

The no-op return ("no file changes since last index") came before the
logic that gives an analyzer without a completed pass a full run. After a
failed first pass (compiler unavailable, timeout, no edges), installing
the missing tool and running a plain index changed nothing until --force,
while Prism's readiness advice said a plain `prism index` would fix it
(stack audit 2026-10-20, finding 5).

native.Result now reports Incomplete: analyzers due a full pass that had
files and did not complete it, split into "unavailable" (never started)
and failed. The indexer keeps a retry record per analyzer (meta
native-retry: failures, next-due time, reason):
- unavailable: rechecked after 15s, doubling to at most 10 min. A
  no-change index runs only the cheap availability probe when due, and the
  full pass only once the probe reports available;
- ran and failed: re-run after 1 min, doubling to at most 6 h, since a
  failed compiler pass can cost up to the 30 min completion cap;
- completed: record cleared;
- compiler inputs changed (finding 2): due immediately, and durable, so an
  interrupted run is retried by the next index with or without changes.
The same schedule now also applies to runs where other languages changed:
an incomplete analyzer whose own languages are unchanged is re-run when
due, not on every edit. Its languages changing still runs it in full.

The no-change message is accurate: "skipped: no file changes since last
index; js-ts has no completed pass (<reason>); next retry in 12s, or use
--force to retry now", or "... still unavailable (<reason>); rechecked
now, next check in 30s". A retry is reported as "<analyzer>: retrying its
incomplete full pass (last attempt: <reason>)". The bare "(use --force to
re-run analyzers)" form remains for a settled index.

Analyzers with no recorded attempt (no files of their languages, or an
index from before this record) are not retried on no-change runs; the
next run with changes attempts them as before.

Tests: TypeScript unavailable -> pending -> recheck with a fake clock;
Go unavailable (go off PATH) -> PATH restored -> a plain no-change index
after the wait completes the pass and records it -> settles to a no-op.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Picks up the call-column fix for multibyte text before a call,
Engine.Validate rejecting syntax errors, and .mts/.cts detection. The eval
module's indirect requirement moves with it, so grove-eval builds against
the same astkit.

grove-eval unchanged (gin, cobra, socket.io, flask: identical P/R/F1).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@tabladrum
tabladrum merged commit 0287098 into main Oct 21, 2026
12 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant