Repository navigation
Python: record module-level calls (app = FastAPI(), @app.post(...)) in a calls-only __global_context__ unit - #3704
Merged
Conversation
…unit
Python sliced only defs, so the code a module runs at import -- app =
FastAPI(), @app.post("/items"), setup_logging(), the __main__ guard -- was
recorded nowhere, and a web app's route registration with it. The Mode C
slicer now emits Mode D's synthetic __global_context__ bucket for Python and
embedded_python (top-level module_level_unit flag): every sliced unit's span
and each `class Name` header's name are blanked, the rest is scanned for
calls (qualifiers and receiver types included, so app.post is typed
FastAPI.post).
It is calls-only: magnitude/impact 0 and flagged calls_only, which the file
mass, function-analysis report, function-impact forensics, file-archetype
function count, named-structure retention and folder function count skip, so
no file's size or classification moves because of it.
Synthetic units have no function_data row (#2691), so their calls now ride
on a synthetic_unit_data child table (FK cascade) that the state rehydrator
restores; a delta scan resolves them like a fresh scan. This also covers
Mode D's existing buckets, which were lost on delta scans.
Golden masters (both, 42 differences each): fastapi applications.py direct
downstream 44 -> 191, routing.py 13 -> 30, param_functions.py 92 -> 103
(module-level app/router construction and calls now link to the defining
file), and what those move: Composition Fit z-scores, within-repo file
clusters, network macros, three forensic top lists.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GCpfMCUMpco98QGbsuv2sr
Contributor
… audit) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GCpfMCUMpco98QGbsuv2sr
app = FastAPI(), @app.post(...)) in a calls-only __global_context__ unit
This was referenced Sep 26, 2026
Merged
squid-protocol
added a commit
that referenced
this pull request
Sep 26, 2026
…corator applied to it (#3708) * python: record module-level calls in a calls-only __global_context__ unit Python sliced only defs, so the code a module runs at import -- app = FastAPI(), @app.post("/items"), setup_logging(), the __main__ guard -- was recorded nowhere, and a web app's route registration with it. The Mode C slicer now emits Mode D's synthetic __global_context__ bucket for Python and embedded_python (top-level module_level_unit flag): every sliced unit's span and each `class Name` header's name are blanked, the rest is scanned for calls (qualifiers and receiver types included, so app.post is typed FastAPI.post). It is calls-only: magnitude/impact 0 and flagged calls_only, which the file mass, function-analysis report, function-impact forensics, file-archetype function count, named-structure retention and folder function count skip, so no file's size or classification moves because of it. Synthetic units have no function_data row (#2691), so their calls now ride on a synthetic_unit_data child table (FK cascade) that the state rehydrator restores; a delta scan resolves them like a fresh scan. This also covers Mode D's existing buckets, which were lost on delta scans. Golden masters (both, 42 differences each): fastapi applications.py direct downstream 44 -> 191, routing.py 13 -> 30, param_functions.py 92 -> 103 (module-level app/router construction and calls now link to the defining file), and what those move: Composition Fit z-scores, within-repo file clusters, network macros, three forensic top lists. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GCpfMCUMpco98QGbsuv2sr * rehydrator: read the synthetic unit's name through an alias (dead-key audit) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GCpfMCUMpco98QGbsuv2sr * call graph: decorator edges (python) -- a decorated function -> each decorator A decorator's wrapper runs around every call to the function it decorates, and neither the call graph nor the module unit recorded that link. The detector now captures, per def, the contiguous decorator block directly above it (argument lines included, shielded text, bounded look-back) as decorated_by / decorated_by_qualifiers (`@app.post(...)` -> post via app); python and embedded_python opt in with a top-level decorator_edges flag. The resolver links them through the usual ladder as fcall_data kind 'decorator' -- counted in decorators_by_step, never in the call-resolution rates -- and they reach function fan-in/PageRank like COBOL transfers do. A function now also falls back to its module's receiver types (`app = FastAPI()` at top level) for a receiver it does not bind itself; a name the function binds with no known class is recorded as "" and hides the module type. Both columns persist in function_data and rehydrate. call_graph_resolution.py python (pyan3): calls-only precision 99.4% (1806 judged), recall 51.3 -> 51.4, resolution recall 96.9 -> 97.0; new, not gated: decorator edges 100% precision (575 judged, 0 wrong), recall with decorators 67.9%. Golden masters: no drift. Stacked on #3704 (module-level unit). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GCpfMCUMpco98QGbsuv2sr --------- Co-authored-by: Claude <noreply@anthropic.com>
squid-protocol
added a commit
that referenced
this pull request
Sep 26, 2026
…ed definitions win over the file's first (#3712) * python: record module-level calls in a calls-only __global_context__ unit Python sliced only defs, so the code a module runs at import -- app = FastAPI(), @app.post("/items"), setup_logging(), the __main__ guard -- was recorded nowhere, and a web app's route registration with it. The Mode C slicer now emits Mode D's synthetic __global_context__ bucket for Python and embedded_python (top-level module_level_unit flag): every sliced unit's span and each `class Name` header's name are blanked, the rest is scanned for calls (qualifiers and receiver types included, so app.post is typed FastAPI.post). It is calls-only: magnitude/impact 0 and flagged calls_only, which the file mass, function-analysis report, function-impact forensics, file-archetype function count, named-structure retention and folder function count skip, so no file's size or classification moves because of it. Synthetic units have no function_data row (#2691), so their calls now ride on a synthetic_unit_data child table (FK cascade) that the state rehydrator restores; a delta scan resolves them like a fresh scan. This also covers Mode D's existing buckets, which were lost on delta scans. Golden masters (both, 42 differences each): fastapi applications.py direct downstream 44 -> 191, routing.py 13 -> 30, param_functions.py 92 -> 103 (module-level app/router construction and calls now link to the defining file), and what those move: Composition Fit z-scores, within-repo file clusters, network macros, three forensic top lists. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GCpfMCUMpco98QGbsuv2sr * rehydrator: read the synthetic unit's name through an alias (dead-key audit) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GCpfMCUMpco98QGbsuv2sr * call graph: decorator edges (python) -- a decorated function -> each decorator A decorator's wrapper runs around every call to the function it decorates, and neither the call graph nor the module unit recorded that link. The detector now captures, per def, the contiguous decorator block directly above it (argument lines included, shielded text, bounded look-back) as decorated_by / decorated_by_qualifiers (`@app.post(...)` -> post via app); python and embedded_python opt in with a top-level decorator_edges flag. The resolver links them through the usual ladder as fcall_data kind 'decorator' -- counted in decorators_by_step, never in the call-resolution rates -- and they reach function fan-in/PageRank like COBOL transfers do. A function now also falls back to its module's receiver types (`app = FastAPI()` at top level) for a receiver it does not bind itself; a name the function binds with no known class is recorded as "" and hides the module type. Both columns persist in function_data and rehydrate. call_graph_resolution.py python (pyan3): calls-only precision 99.4% (1806 judged), recall 51.3 -> 51.4, resolution recall 96.9 -> 97.0; new, not gated: decorator edges 100% precision (575 judged, 0 wrong), recall with decorators 67.9%. Golden masters: no drift. Stacked on #3704 (module-level unit). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GCpfMCUMpco98QGbsuv2sr * call graph: reference edges (python), and nested definitions win over the file's first A function name used as a value -- Depends(get_db), key=sort_key, callback=self.on_done, return wrapper -- is a link the framework or caller invokes later, and was recorded nowhere. The detector (python and embedded_python, top-level reference_edges flag) records candidates in argument, keyword, assignment, return/yield/lambda and collection positions as references_to / references_qualifiers. Variables are excluded: the function's parameters and locals (tuple and starred targets included), every nested def's parameters, the bindings of every enclosing function (closure variables), the module's own variables (`app = FastAPI()`), and in a signature anything but a default or a call argument (so `Sequence[Depends]` is not one). Names the function also invokes are already edges. The resolver keeps a reference only when it reaches a function confidently through a scoped step (class, qualified, typed, file, import) -- `unique` is not enough for a bare name -- as fcall_data kind 'reference', counted in references_by_step and never in the call rates. A bare call or reference to a name the caller defines nested inside itself now resolves to that nested definition, not the file's first of that name (every decorator's own `wrapper`); the rehydrator restores loc for the caller's span. The module unit carries no references yet (synthetic_unit_data does not persist them). call_graph_resolution.py python (pyan3): call precision 99.4 -> 99.8 (1806 judged), recall 51.4 -> 51.6, resolution recall 97.0 -> 97.5; new, not gated: reference precision 99.7% (323 judged), recall across calls, decorators and references 77.3%. Golden masters: no drift. Stacked on #3708 (decorator edges). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GCpfMCUMpco98QGbsuv2sr * reference edges: type the signature parser's buffer (mypy); C5 test scoped to calls `return helper` now draws a reference edge o2 -> helper, which is the point of this PR; the C5 test's claim is that the nested header is not a call, so it checks call-kind rows and asserts the reference separately. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GCpfMCUMpco98QGbsuv2sr --------- Co-authored-by: Claude <noreply@anthropic.com>
squid-protocol
added a commit
that referenced
this pull request
Sep 26, 2026
… (with measurements) (#3713) * python: record module-level calls in a calls-only __global_context__ unit Python sliced only defs, so the code a module runs at import -- app = FastAPI(), @app.post("/items"), setup_logging(), the __main__ guard -- was recorded nowhere, and a web app's route registration with it. The Mode C slicer now emits Mode D's synthetic __global_context__ bucket for Python and embedded_python (top-level module_level_unit flag): every sliced unit's span and each `class Name` header's name are blanked, the rest is scanned for calls (qualifiers and receiver types included, so app.post is typed FastAPI.post). It is calls-only: magnitude/impact 0 and flagged calls_only, which the file mass, function-analysis report, function-impact forensics, file-archetype function count, named-structure retention and folder function count skip, so no file's size or classification moves because of it. Synthetic units have no function_data row (#2691), so their calls now ride on a synthetic_unit_data child table (FK cascade) that the state rehydrator restores; a delta scan resolves them like a fresh scan. This also covers Mode D's existing buckets, which were lost on delta scans. Golden masters (both, 42 differences each): fastapi applications.py direct downstream 44 -> 191, routing.py 13 -> 30, param_functions.py 92 -> 103 (module-level app/router construction and calls now link to the defining file), and what those move: Composition Fit z-scores, within-repo file clusters, network macros, three forensic top lists. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GCpfMCUMpco98QGbsuv2sr * rehydrator: read the synthetic unit's name through an alias (dead-key audit) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GCpfMCUMpco98QGbsuv2sr * call graph: decorator edges (python) -- a decorated function -> each decorator A decorator's wrapper runs around every call to the function it decorates, and neither the call graph nor the module unit recorded that link. The detector now captures, per def, the contiguous decorator block directly above it (argument lines included, shielded text, bounded look-back) as decorated_by / decorated_by_qualifiers (`@app.post(...)` -> post via app); python and embedded_python opt in with a top-level decorator_edges flag. The resolver links them through the usual ladder as fcall_data kind 'decorator' -- counted in decorators_by_step, never in the call-resolution rates -- and they reach function fan-in/PageRank like COBOL transfers do. A function now also falls back to its module's receiver types (`app = FastAPI()` at top level) for a receiver it does not bind itself; a name the function binds with no known class is recorded as "" and hides the module type. Both columns persist in function_data and rehydrate. call_graph_resolution.py python (pyan3): calls-only precision 99.4% (1806 judged), recall 51.3 -> 51.4, resolution recall 96.9 -> 97.0; new, not gated: decorator edges 100% precision (575 judged, 0 wrong), recall with decorators 67.9%. Golden masters: no drift. Stacked on #3704 (module-level unit). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GCpfMCUMpco98QGbsuv2sr * call graph: reference edges (python), and nested definitions win over the file's first A function name used as a value -- Depends(get_db), key=sort_key, callback=self.on_done, return wrapper -- is a link the framework or caller invokes later, and was recorded nowhere. The detector (python and embedded_python, top-level reference_edges flag) records candidates in argument, keyword, assignment, return/yield/lambda and collection positions as references_to / references_qualifiers. Variables are excluded: the function's parameters and locals (tuple and starred targets included), every nested def's parameters, the bindings of every enclosing function (closure variables), the module's own variables (`app = FastAPI()`), and in a signature anything but a default or a call argument (so `Sequence[Depends]` is not one). Names the function also invokes are already edges. The resolver keeps a reference only when it reaches a function confidently through a scoped step (class, qualified, typed, file, import) -- `unique` is not enough for a bare name -- as fcall_data kind 'reference', counted in references_by_step and never in the call rates. A bare call or reference to a name the caller defines nested inside itself now resolves to that nested definition, not the file's first of that name (every decorator's own `wrapper`); the rehydrator restores loc for the caller's span. The module unit carries no references yet (synthetic_unit_data does not persist them). call_graph_resolution.py python (pyan3): call precision 99.4 -> 99.8 (1806 judged), recall 51.4 -> 51.6, resolution recall 97.0 -> 97.5; new, not gated: reference precision 99.7% (323 judged), recall across calls, decorators and references 77.3%. Golden masters: no drift. Stacked on #3708 (decorator edges). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GCpfMCUMpco98QGbsuv2sr * reference edges: type the signature parser's buffer (mypy); C5 test scoped to calls `return helper` now draws a reference edge o2 -> helper, which is the point of this PR; the C5 test's claim is that the nested header is not a call, so it checks call-kind rows and asserts the reference separately. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GCpfMCUMpco98QGbsuv2sr * docs: edge kinds, and what the call graph adds to a parser, with its measurements Adds two sections to docs/function_call_graph.md: the fcall_data edge kinds (call, transfer, decorator, reference -- what each links, which languages record it, what joins the file graph) and what the resolver knows on top of a parser's callee names, each capability with its pyan3 measurement and the limits those numbers carry (one reference tool, one language, tuned on the scored corpus). Deliberately not a why_gitgalaxy_beats_ast_here.md claim: resolution is something a parser does not attempt, not something it gets wrong. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GCpfMCUMpco98QGbsuv2sr --------- Co-authored-by: Claude <noreply@anthropic.com>
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.
Summary
Part of the Level 2 call-graph work (epic #3265).
Problem. A classification of every pyan3 edge the resolver doesn't link on language-crucible's Python repos found decorators to be the largest real gap: 641 edges, 18% of pyan's total.
defs, so the code a module runs at import time never entered the call graph. That includesapp = FastAPI(),@app.post("/items"),setup_logging()andif __name__ == "__main__": main().Fix.
Slicer (
detector.py,_module_level_unit): for Python and embedded_python (top-levelmodule_level_unitprofile flag, not arulespattern, per orphaned_logic reports 100% dead code for five languages whose invocation model never names the callee — the family #2727 closed without covering #2806), the Mode C slicer emits Mode D's existing synthetic__global_context__bucket.class Nameheader's name blanked, since a class statement isn't a call.@app.post(...)afterapp = FastAPI()resolves through Call resolver:typedstep — resolvex.method()when the function shows x's class (Python) #3693'stypedstep toFastAPI.post.Calls-only. The unit has magnitude/impact 0 and a
calls_onlyflag. That flag is honoured by everything that sizes, counts or lists functions, so no file's size or classification moves because of it:signal_processor);log_function_count;Without this, the first cut moved 3,688 golden-master values. Most of that came from files with no functions losing the fallback magnitude estimate, and from every Python file gaining one "function".
Delta scans. Synthetic units have no
function_datarow (functions_found counts the slicer's synthetic __global_context__ buckets, inflating every per-function descriptor for 5 languages #2691), so the state rehydrator could not restore them, and an incremental scan would have lost these calls. They now also go into a new per-filesynthetic_unit_datachild table (FK cascade, with thecalls_onlyflag), which the rehydrator restores. Mode D's existing buckets are persisted and restored the same way.Type of change
detector.py, python profiles,signal_processor.py,archetype_classifier.py,statistical_auditor.py,record_keeper.py,audit_recorder.py,state_rehydrator.py)Measured
Module-level call pairs now recorded, per language-crucible Python repo (confident = scoped/unique):
typed193,import193,file30file29,unseen3call_graph_resolution.py python(pyan3) is unchanged by design: 99.4% precision, 51.3% recall, 96.9% resolution recall. pyan has no module node, and it attributes a decorator to the decorated function, so these links are invisible to that metric.call_graph_accuracy.py --ciandtree_sitter_accuracy_audit.py --ci --all(30 languages) both pass: the synthetic unit is never counted as a function.Golden masters
Both fixtures re-blessed: 42 differences each. All were diffed with
golden_diffagainst HEAD and reviewed, and all trace to the new cross-file call edges:applications.py44 → 191: test modules buildFastAPI()at module level, andFastAPIlives there;routing.py13 → 30;param_functions.py92 → 103.network_macro(modularity 0.849 → 0.824, avg path length 1.98 → 1.82);Tests
tests/extraction/languages/test_python_module_level_unit.py:tests/tools_recorders/test_fcall_data.py: a synthetic unit persists tosynthetic_unit_data(notfunction_data), rehydrates withcalls_only, and resolves identically after rehydration.Verification
Next in this line: decorator → decorated-function edges (
@provide_bucket_namewraps every call to the method), then reference edges for functions passed as values (Depends(get_db), callbacks).🤖 Generated with Claude Code
https://claude.ai/code/session_01GCpfMCUMpco98QGbsuv2sr