Skip to content

feat(code-intel): no completeness claim without data (PLN-2027 PR 1) - #15

Merged
wongk merged 3 commits into
mainfrom
campaign-prd-739-20260915-c1
Sep 15, 2026
Merged

wongk merged 3 commits into
mainfrom
campaign-prd-739-20260915-c1

Conversation

@wongk

@wongk wongk commented Sep 15, 2026

Copy link
Copy Markdown

Campaign chunk C1 of 7 (PRD-739 → PLN-2027), built one chunk at a time against main.

C1 — No completeness claim without data; no silent empty results mid-rebuild (FR1, FR3, FR6)

Lands PLN-2027 PR 1.

  • Store-backed code-intel entry points (analyze_changes, code_query, check_coverage, open_file_graph) now call require_ready: mid-rebuild they raise IndexRebuilding; on an absent or empty index they raise CodeIntelUnavailable. Gating lives in the infra functions, so lc code behaves like MCP.
  • code_changes, code_query intel selects, file-graph kinds and relations report data_status and stamp objective: "partial" when call-graph or import data is missing, instead of exhaustive.
  • New fork-only contract test tests/gateway/test_completeness_contract.py covers the missing-data case for every enumerative op.

Acceptance ledger

Criteria Met Met differently Not met
24 21 3 0

PRD-739 user-story ACs mapped to PR 1 (separate audit): 5 met by this branch, 3 already met on main, 1 owned by a later unit (PR 2: relations pre-limit total and truncated surviving packing), 0 not met.

Deviations from the plan

  • Plan: Call the gate at the top of each store-backed entry point: analyze_changes; code_query (skipped for describe); check_coverage; open_file_graph.
    Did: analyze_changes calls require_ready after collect_changes (the git diff) and before the first store read (src/lemoncrow/infra/code_intel/change_impact.py:502). code_query calls it after argument validation (src/lemoncrow/infra/code_intel/query.py:564). check_coverage and open_file_graph gate first.
    Why: tests/infra/code_intel/test_change_impact.py::test_non_git_directory_is_an_error_not_an_empty_report pins GitUnavailable for a non-git directory, and a malformed query should surface as QueryError whatever state the index is in. Both gates still run before any store read.
  • Plan: Call-graph data is unavailable when store.snapshot() shows intel_available false, or when call_edges == 0 and references == 0 while symbols > 0.
    Did: Added CodeIntelStore.call_graph_gap() (src/lemoncrow/infra/code_intel/store.py:570). It applies the same rule with EXISTS probes and is shared by change_impact and code_query.
    Why: snapshot() runs full COUNT(*) queries across both databases on every call. EXISTS answers the same question with index seeks on the code_query hot path.
  • Plan: query.py: when an intel-backed select finds no intel connection, return data_status unavailable and make to_dict emit partial.
    Did: code_query intel selects also report data_status unavailable and objective partial when intel.sqlite exists but holds no call edge and no reference while symbols exist. This is the change_impact rule (src/lemoncrow/infra/code_intel/query.py:591).
    Why: On first connect the engine creates intel.sqlite with empty tables (engine _init_secondary_schemas), so an absent file is rarely the live missing-data state. Without the extension, code_query callers over an unbuilt call graph still returned zero rows marked exhaustive (FR3). Fail-safe side effect: a repository with genuinely no calls reads partial.
  • Plan: For every enumerative op (relations callers/callees/usages, ...), build its missing-data case.
    Did: The callers and callees cases use the engine's own traverse_call_graph with a neighbour lookup that returns None. usages is pinned at _maybe_attach_code_rendered with a payload that carries data_status unavailable (tests/gateway/test_completeness_contract.py:237). The engine is unchanged.
    Why: engine.find_references has no unavailable state: with no reference rows it falls back to a zoekt/text search, so there is no engine missing-data case for usages to build. Adding one would mean editing the upstream-owned engine.py.
  • Plan: Absent index for store-backed tools: raise CodeIntelUnavailable rather than return empty (decision 2, including files == 0).
    Did: Replaced two tests that pinned the opposite behaviour: tests/infra/code_intel/test_query.py test_empty_index_degrades_to_empty became test_absent_index_raises_unavailable, and tests/infra/code_intel/test_file_graph.py test_an_empty_index_analyses_cleanly became test_an_empty_index_raises_rather_than_analysing_nothing. Gave two fixtures real data with their assertions unchanged: test_coverage.py test_git_ignored_files_are_excluded_not_missing now indexes one file, and test_completeness.py test_change_impact_stamps_an_objective gains a call edge.
    Why: Those fixtures were an empty index or a call graph with no edges, which now raise or read partial by design. Counterfactuals M16 and M12 show the modified tests still guard their original behaviour.
    Affects: PLN-2027 PR 4
  • Plan: Verification per PR: make lint no new findings (baseline 9); make typecheck clean from a fresh cache; the full suite in a scratch worktree diffed against a main baseline run (section 9).
    Did: Ran ruff over the make-lint paths: 10 findings, none in files this change touched, so the plan's baseline of 9 is stale. Ran mypy --strict on the 8 changed src files. Ran pytest -m 'not slow' once in this worktree and re-ran each of its 17 failures on origin/main in a scratch worktree, which was then removed.
    Why: The campaign brief scopes verification: it excludes make typecheck over all of src and full main-vs-branch suite diffs, and caps any single run at 10 minutes. The capped run finished in 9:00.

Review

/code-review:deep --base origin/main (run cr-12127, 5 reviewers + 4 verifiers, all collected): NEEDS_ATTENTION — verified 4, justified 0, rejected 0, pending 0, coverage gaps 0. No BLOCKING and no correctness or security findings; 1 HIGH (design), 3 MEDIUM (code quality).

Fixed

  • design_critic_f0 [HIGH] src/lemoncrow/gateway/adapters/mcp_server.py:9006 data_status gate compares against the raw literal "unavailable" instead of the DATA_UNAVAILABLE constant the same diff adds; now imports DATA_UNAVAILABLE from lemoncrow.infra.code_intel.completeness and compares against it (6d17c2af)
  • design_critic_f1 [MEDIUM] src/lemoncrow/infra/code_intel/file_graph.py:151 import-gap check computed inline in FileGraph.init instead of beside CodeIntelStore.call_graph_gap(); moved to CodeIntelStore.import_gap() with the same rule (files indexed, no import rows) and the same reason text, and FileGraph.init now calls it (18773adf)

Declined

  • bhb_f0 [MEDIUM] tests/gateway/test_completeness_contract.py:91 _git() and _repo_with_an_edit() duplicate _git()/_init_repo() in tests/infra/code_intel/test_change_impact.py

    Declined. The duplication is real, but it follows the suite's existing convention and is not drift this PR introduced: the tests tree already carries about fifteen module-private _git helpers (tests/core, tests/gateway, tests/infra, git_history), each with its own signature and return type. Consolidating just these two means creating a new shared test-support module or editing the upstream-owned tests/helpers, plus editing a pre-existing file (tests/infra/code_intel/test_change_impact.py) outside this change. That adds upstream merge surface for no behaviour change. Nothing can drift silently: both helpers pass check=True, so any divergence fails loudly at the git call. _repo_with_an_edit is also not a copy of _init_repo: it commits and then rewrites the working tree to produce the diff code_changes needs, in eight lines. A new abstraction for two call sites is not worth it pre-PMF.

  • bhb_f1 [MEDIUM] tests/gateway/test_completeness_contract.py:175 _tear() duplicates the tear_index fixture added to tests/infra/code_intel/conftest.py

    Declined. The claim is accurate, but the fixture it asks us to reuse is out of reach: tear_index lives in tests/infra/code_intel/conftest.py, which pytest does not expose to tests/gateway. The only places both directories can share are the root tests/conftest.py and tests/helpers, and both are upstream-owned. Deduplicating means either adding fork-specific code to upstream files or creating a new shared module for a five-line DELETE FROM files. It is also not a new pattern: tests/gateway/test_code_engine_cache_invalidation.py already had its own module-local _tear on origin/main, so a gateway test keeping one matches the existing layout. Importing that private helper across test modules would couple two unrelated test files just to save six lines. If the files table is ever renamed, all three copies fail loudly rather than drifting.

Re-review after fixes (--since-last-review, incremental diff 04d22e7..18773ad, 46 LOC, fast path with one reviewer): APPROVED — verified 0, justified 0, rejected 0, pending 0, coverage gaps 0. Confirms the HIGH at mcp_server.py:9006 is fixed.

Verification

  • uv run pytest -q tests/infra/code_intelpass: 395 passed (386 on base + 9 new). The first run failed 1, test_coverage.py::test_git_ignored_files_are_excluded_not_missing: its fixture was an empty index, which now raises by decision 2. Fixed by indexing one real file with the assertions unchanged.
  • uv run pytest -q tests/gateway/test_completeness_contract.py tests/gateway/test_code_engine_cache_invalidation.py tests/gateway/test_mcp_tool_handlers.py tests/pass: 226 passed, 2 deselected
  • git grep -l --untracked -E 'require_ready|reset_readiness_probes|call_graph_gap|objective_for_data|DATA_AVAILABLE|DATA_UNAVAILABLE|analyze_changes|ChangeImpactRpass: 19 files. conftest.py is not a test module. Excluded per the brief: tests/benchmarks/test_edit_ab_real.py and tests/benchmarks/test_shell_ab_real.py (benchmarks), and tests/gateway/test_mcp_jsonrpc_e2e.py (slow, known base failure).
  • uv run pytest -q tests/gateway/test_cap_tools_list_gate.py tests/gateway/test_cli.py tests/gateway/test_completeness_contract.py tests/gateway/test_context_mcp_pass: 497 passed, 5 deselected
  • uv run ruff check src/lemoncrow/infra/code_intel/completeness.py src/lemoncrow/infra/code_intel/store.py src/lemoncrow/infra/code_intel/freshness.py src/lemoncrpass: All checks passed!
  • uv run mypy --strict src/lemoncrow/infra/code_intel/completeness.py src/lemoncrow/infra/code_intel/store.py src/lemoncrow/infra/code_intel/freshness.py src/lemopass: Success: no issues found in 8 source files (also clean without --strict)
  • uv run python /private/tmp/claude-501/-Users-kris-wong-Dev-lemoncrow/9d1d9d21-4fff-4e14-89f2-09de7d76a208/scratchpad/counterfactuals.pypass: Counterfactual proof: 13 single-line mutations, with failure messages parsed per test from junit XML. 73/73 guarding test instances failed under their mutation with the guarded message. Every mutated file was restored byte-identical, and the git diff hash was unchanged.
  • uv run pytest -q tests/gateway/test_completeness_contract.py tests/infra/code_intel/test_completeness.pypass: 73 passed after the pre-commit hook black-reformatted both files, run against the committed bytes of 04d22e7
  • uv run pytest -q tests/test_mypyc_compile_safety.pypass: 3 passed
  • uv run ruff check src benchmarks tests scripts integrationspass: make lint equivalent: 10 findings, 0 in files this change touched (all 10 are in untouched files); the plan's stated baseline of 9 is stale
  • git diff --checkpass: clean
  • uv run pytest -q -m "not slow"pass: Run in this worktree under the 10-minute cap: 17 failed, 6795 passed, 16 skipped, 155 deselected in 540.79s. All 17 failures reproduce on origin/main (next row), so none is attributable to this change. They are 2 cross_vendor_routing, test_code_context_index_workers, 2 docs migration, claude_paralle
  • origin/main (00e1b390) scratch worktree .claude/worktrees/prd-739-20260915-cb (same path length as the branch worktree, removed afterwards): uv sync --frozen; upass: 17 failed on origin/main, the identical set; lemoncrow imported from the scratch worktree
  • Resolve: uv run pytest -q tests/gateway/test_completeness_contract.py tests/infra/code_intel/test_file_graph.py tests/infra/code_intel/test_completeness.py tests/infra/cpass
  • Resolve: uv run ruff check src/lemoncrow/gateway/adapters/mcp_server.py src/lemoncrow/infra/code_intel/file_graph.py src/lemoncrow/infra/code_intel/store.pypass
  • Resolve: uv run mypy --strict src/lemoncrow/gateway/adapters/mcp_server.py src/lemoncrow/infra/code_intel/file_graph.py src/lemoncrow/infra/code_intel/store.pypass
  • Resolve: uv run pytest -q tests/test_mypyc_compile_safety.pypass
  • Resolve: uv run pytest -q tests/infra/code_intel/test_intel_store.pypass
  • Resolve: git diff --checkpass

Still owed by later chunks

  • C2 (PR 2): relations pre-limit total (related_total) and truncated that survives budget packing (FR2).
  • C3 (PR 3): read-only broker; fixes the pre-existing test_tools_list_hides_internal_workflow_tools failure on main.
  • C4 (PR 4): coverage exclusion rules, anchors and semantics docs (FR7–FR10). Its coverage tests need ready, non-empty index fixtures, because check_coverage now raises on an absent or empty index.
  • C5–C7 (PR 5–7): fork update channel, repo-correct PR risk and module warm-up, P2 spikes plus removal of the fork-only lc code export/import.
  • After C1–C3 merge: the plan §10 GA check (one /code-review:deep with LemonCrow reinstalled).

Artifacts: PRD-739 · PLN-2027

wongk and others added 3 commits September 15, 2026 09:42
PLN-2027 unit PR 1 (PRD-739 FR1, FR3, FR6).

- freshness.require_ready: throttled readiness gate for the store-backed
  tools. Mid-rebuild raises IndexRebuilding; an absent or empty index
  raises CodeIntelUnavailable. Called by analyze_changes, code_query,
  check_coverage and open_file_graph.
- completeness.objective_for_data; CodeIntelStore.call_graph_gap.
- code_changes, code_query intel selects and file-graph kinds report
  data_status/reason and a partial objective when their data is missing.
- relations: data_status "unavailable" stamps partial; "empty" stays
  exhaustive.
- tests/gateway/test_completeness_contract.py pins the contract per op.

Co-Authored-By: Claude <noreply@anthropic.com>

Co-Authored-By: lemoncrow <302591943+lemoncrow-agent[bot]@users.noreply.github.com>
…a literal (PLN-2027 PR 1)

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…027 PR 1)

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
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