Skip to content

feat(extract): call-graph edge extractor (caller→callee) + Tier-1 calls gate - #26

Merged
v0ropaev merged 1 commit into
masterfrom
feat/call-graph-extractor
Jul 7, 2026
Merged

v0ropaev merged 1 commit into
masterfrom
feat/call-graph-extractor

Conversation

@v0ropaev

@v0ropaev v0ropaev commented Jul 7, 2026

Copy link
Copy Markdown
Owner

What

The sixth deterministic extractor — deterministic caller→callee edges, the foundation under the future business-process extractor (DESIGN §14 item 2, next in the design's own sequence).

  • One call_edge per resolved pair (call:{caller_fq}->{callee_fq}), call-site lines aggregated in the payload (the import-edge precedent).
  • Three deterministic tiers, precision-first (only first-party-resolved edges are emitted — never a wrong guess): same-module; imported-name (cross-file — from x import f [as g], import x[.y] [as z]; x.y.f() via per-module import tables incl. relative imports); self.method() to a method of the same class.
  • Grounding: caller def span (role caller; module span for module-level calls) + callee def span (role callee, cross-file — the thesis property, one artifact spans both files).
  • Documented gaps (asserted in the gate, DESIGN §12 honest-recall rule): obj.method(...), getattr/dynamic, super(), inherited self-calls, star imports, re-export indirection, decorator/default-arg expressions, class-body calls.

Design subtleties (all gate-tested)

  • Body-only scanning: a def's own decorators/parameter defaults are excluded by construction (we walk only function_definition.body).
  • No-descend traversal: nested defs attribute calls to the innermost function (cg.edge.outer.inner, never outer); chained a().b() collects the inner a() only.
  • Direct recursion = a single-span artifact (one grounding row — the (artifact_id, span_id) PK), role caller.
  • Mutual recursion = two distinct artifacts over an identical evidence span set — the exact collision class fixed by identity v2 (fix(ids): fold logical_key into artifact_id (identity rule v2) #24), now regression-tested at the extractor level.
  • Relative-import base handled for non-__init__ modules (from .sib import f in pkg.mod → pkg.sib).

Gate #13 (tier1_calls_test, hand-labeled oracle)

10 tests / 11 oracle edges across 5 fixture modules: set equality · mutual-recursion identity regression · cross-file provenance · module-level caller + merged lines (two h() sites → one artifact) · class-instantiation edges (callee_kind="class") · six known gaps asserted NOT extracted · nested-def attribution · all grounded + role/kind checks · direct-recursion single-span. Headline HARD gates: twelve → thirteen.

Checks

  • ruff + mypy --strict clean (75 files); 94 passed, 1 skipped (84 + 10 new) — the gate passed first run.
  • Registered in default_extractors() (feeds both kb index and kb watch); summarize/embed_text gained call_edge branches; framework_versions empty (import-graph rationale). kb index stays static/offline.

Next cycles toward the crown jewel: PathEngine slicing → sink/entrypoint registries → LLM labeler + span-binding validator.

…lls gate

Add the sixth deterministic extractor — the deterministic foundation
under the future business-process extractor (DESIGN §14 item 2).

One `call_edge` artifact per RESOLVED caller->callee pair
(call:{caller_fq}->{callee_fq}; call-site lines aggregated in the
payload, the import-edge precedent). Three deterministic resolution
tiers, precision-first (only first-party-resolved edges are emitted,
never a wrong guess): same-module calls; imported-name calls —
CROSS-FILE, `from x import f [as g]` and `import x[.y] [as z];
x.y.f()` module-attribute forms via per-module import tables (incl.
relative imports, with the package-vs-module base handled for
non-__init__ modules); and `self.method()` to a method of the same
class. Documented gaps, surfaced by the gate: obj.method(...), getattr/
dynamic, super(), inherited self-calls, star imports, re-export
indirection, decorator/default-arg expressions, class-body calls.

Grounding: the caller def span (role `caller`; the module span for
module-level calls) + the callee def span (role `callee`, cross-file).
Direct recursion is a single-span artifact (one edge row — the
(artifact_id, span_id) PK); mutual recursion yields two distinct
artifacts (identity rule v2 — the collision class fixed in #24, now
regression-tested at the extractor level). Body-only scanning excludes
a def's own decorators/parameter defaults by construction; nested defs
attribute calls to the innermost function (no-descend traversal).
framework_versions deliberately empty (the import-graph rationale).

New Tier-1 HARD gate `tier1_calls_test` (hand-labeled oracle, 11 edges,
10 tests): set equality; mutual-recursion identity regression;
cross-file provenance; module-caller + merged lines; instantiation
edges (callee_kind=class); six known gaps asserted. Headline HARD
gates: twelve -> thirteen. Registered in default_extractors();
summarize/embed_text gained call_edge branches. 94 eval tests pass.
@v0ropaev
v0ropaev merged commit d15da2a into master Jul 7, 2026
4 checks passed
@v0ropaev
v0ropaev deleted the feat/call-graph-extractor branch July 7, 2026 10:20
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