Skip to content

fix(uncertainty): withdraw the real-absence claim over untracked source - #978

Closed
nhuphuoc-bic wants to merge 1 commit into
tirth8205:mainfrom
nhuphuoc-bic:fix/untracked-sources-defeat-real-absence
Closed

nhuphuoc-bic wants to merge 1 commit into
tirth8205:mainfrom
nhuphuoc-bic:fix/untracked-sources-defeat-real-absence

Conversation

@nhuphuoc-bic

Copy link
Copy Markdown
Contributor

The problem

_staleness proves the graph current from two signals: build commit vs HEAD, and the target file's mtime vs the build timestamp. Neither can see a source file git has never been told about — the commit still matches, and a file the graph holds no row for has no mtime to compare.

An incremental update discovers changes through git diff, so such a file is absent from the index however current the build is. The consequence is that the strongest sentence this module produces lands on exactly the case it is least true for:

agent writes new_consumer.py, does not `git add` it
→ query_graph(references_to, Foo)  → 0 results
→ confidence: "'Foo' is indexed and the graph is current, so this 0 is a real absence"

The agent stops looking for the consumer it just wrote. Per this module's own docstring — "saying so is what stops an agent grepping anyway" — that is the marker doing harm rather than nothing.

Measured on a 1,489-file TypeScript monorepo: create a .ts importing an indexed symbol, run update → 0 files updated, and the reference count does not move. git add the same file → 1 files updated, 2 nodes, 3 edges, count moves. Remove it → back.

The change

Untracked source becomes a third staleness signal, evaluated from _settled() on both paths that would otherwise report the graph as current — including the early return taken when a graph carries no last_updated metadata, which is where the first draft of this silently did nothing.

Two things keep it quiet where it should be:

  • "Source" is calibrated from the index, not a fixed extension list — the suffixes already present in get_all_files(). A repo whose languages this build does not parse never reports a file the index was never going to hold.
  • --porcelain already honours .gitignore, so build output and vendored trees stay out.

It is empty-result-only like every other marker here, so no response that carries results changes, and the note stays inside MAX_CONFIDENCE_CHARS.

Cost: one git status on a path that already spawns a subprocess for _live_git_head, and only ever on an empty result — never the hot path. Measured at ~10 ms on the monorepo above.

Tests

Six cases in tests/test_uncertainty.py, all of which fail when the signal is removed (verified — 3 fail on the message assertions, the others guard the quiet paths):

  • the real-absence claim is withdrawn, and the file count is named
  • the remedy (git add) is present, within the character budget
  • untracked non-source does not raise it
  • gitignored source does not raise it
  • a non-empty result still carries no confidence key — the token-budget guarantee
  • it outranks the "not indexed" wording on the unresolved-target path

ruff and mypy clean; full suite 2991 passed, 9 skipped, 2 xpassed.

A question for you

The marker is attached only when total_results == 0, per the deliberate rule in query.py that non-empty responses stay byte-identical. But the dangerous shape of this bug is often low, not zero — three new files exist, and references_to returns 11 instead of 14, which no one thinks to doubt.

Extending the note to non-empty results would cover that, at the cost of the rule you wrote for token economy. I have kept this PR inside your existing rule; happy to add it if you think the trade is worth it.

Relation to integration/token-efficiency-hardening

This composes with _find_content_mismatches there rather than overlapping it: that pass walks files already in the graph, so it catches the revert round-trip but not a file the index has never held. Based on main for review convenience — rebase or cherry-pick as suits you, as with #969.

An incremental update discovers changes through git, so a source file that
was never added is absent from the index however current the build is.
Neither staleness signal can see it: the build commit still matches HEAD,
and a file the graph holds no row for has no mtime to compare.

The result is that the strongest sentence this module produces is attached
to exactly the case it is least true for. An agent writes a consumer, does
not add it, asks who references the symbol, and is told the zero is a real
absence -- so it stops looking for the file it just wrote.

Adds untracked source as a third staleness signal, evaluated on both paths
that would otherwise report the graph as current, including the early
return taken when a graph carries no build timestamp. Source is calibrated
from the extensions already in the index, so a repo whose languages this
build does not parse never reports a file the index was never going to
hold; --porcelain honours .gitignore, so build output stays out.

Empty-result-only, like every other marker here, so no response that
carries results changes.
@nhuphuoc-bic

Copy link
Copy Markdown
Contributor Author

Superseded by #987 — same change, reopened from my personal account so the contribution is attributed correctly. The commit here was authored with an address that matches no GitHub account, so it credited nobody. Nothing about the code changed; the description there is shorter and carries a measurement taken after this one was opened.

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