feat(extract): call-graph edge extractor (caller→callee) + Tier-1 calls gate - #26
Merged
Merged
Conversation
…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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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).
call_edgeper resolved pair (call:{caller_fq}->{callee_fq}), call-site lines aggregated in the payload (the import-edge precedent).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.caller; module span for module-level calls) + callee def span (rolecallee, cross-file — the thesis property, one artifact spans both files).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)
function_definition.body).cg.edge.outer.inner, neverouter); chaineda().b()collects the innera()only.(artifact_id, span_id)PK), rolecaller.__init__modules (from .sib import finpkg.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 --strictclean (75 files); 94 passed, 1 skipped (84 + 10 new) — the gate passed first run.default_extractors()(feeds bothkb indexandkb watch);summarize/embed_textgainedcall_edgebranches;framework_versionsempty (import-graph rationale).kb indexstays static/offline.Next cycles toward the crown jewel:
PathEngineslicing → sink/entrypoint registries → LLM labeler + span-binding validator.