Skip to content

feature/release 2026 29 06#134

Merged
ar7casper merged 67 commits into
masterfrom
feature/release-2026-29-06
Jul 7, 2026
Merged

feature/release 2026 29 06#134
ar7casper merged 67 commits into
masterfrom
feature/release-2026-29-06

Conversation

@ar7casper

Copy link
Copy Markdown
Contributor
  • fix(parsers/python): match imports by module components in both directions
  • feat(context): gate local-only suppression on trust_boundaries, not just app type
  • fix(cli): add zig to the --language choices for parse and scan
  • fix(go-parser): key generic-type methods on base type, not "unknown"
  • build(parsers/go): stop tracking the compiled go_parser binary
  • fix(reachability): anchor user-input entry-point patterns with word boundary
  • fix(parsers): CM-A is_test_file anchored to path components, not substring (BUG-NEW 2,22)
  • fix(parsers/zig): u13 call_graph_builder — local-type/builtin/const-alias (BUG-NEW 3,17,41)
  • fix(parsers/zig): u14 function_extractor — fn-name/test-classification/container-methods (BUG-NEW 16,26,37)
  • fix(parsers/c): u1 call_graph_builder — builtin-leak same-file pre-check (BUG-NEW 13)
  • fix(parsers/c): u2 function_extractor — ctor/dtor/struct/union/lambda/operator/template/ns (BUG-NEW 14,15,32,33,35,39,40)
  • fix(parsers/c): u3 unit_generator — carry is_inline into the unit (BUG-NEW 29)
  • fix(parsers/php): u5 call_graph_builder — variable-fn + parent::/self::/static:: + <?php-tag root cause (BUG-NEW 20,52)
  • fix(parsers/c): land deferred [51] member-dispatch + [30] virtual/inherited dispatch
  • fix(parsers/ruby): u10 call_graph_builder — parenless/crossfile-class/builtin/method-object/substring-import (BUG-NEW 1,8,11,31,49)
  • fix(parsers/php): u6 function_extractor — nested-fn/attribute-start_line/enum/braceless-ns (BUG-NEW 7,25,36,54)
  • fix(parsers/php): u7 unit_generator — namespace key-drift + is_static (BUG-NEW 43,28)
  • fix(parsers/ruby): u11 function_extractor — nested/singleton/define_method/alias/private/nested-class (BUG-NEW 6,18,24,42,44,50)
  • fix(parsers/ruby): guard alias resolution to single unconditional binding (BUG-NEW 31)
  • fix(reachability): u12 entry_point_detector — seed silent binary main (BUG-NEW 4)
  • fix(parsers/go): u4 BUG-5 — normalize call_graph.json func records to snake_case (BUG-NEW 5)
  • fix(parsers/go): [5] re-verify — complete the snake-case normalization schema
  • fix(parsers/go): u4 callgraph — func-value alias edge + top-level call routing (BUG-NEW 19)
  • feat(reachability): opt-in library-mode seeds the public API surface
  • fix(parsers/python): u9 function_extractor — 6 finder bugs (BUG-NEW 9,21,23,34,45,48)
  • fix(parsers/python): u8 call_graph_builder — builtin-leak/alias/import-overresolve/inherited-self (BUG-NEW 10,12,27,46)
  • fix(parsers/python): [34] re-verify — token-aware property classification (cached_property)
  • fix(parsers/python): guard alias resolution to single unconditional binding (BUG-NEW 12)
  • fix(parsers/python): resolve self/cls-aliased method calls in the call graph
  • fix(parsers/go): classify entry points by signature, not return type or bare next(
  • fix(parsers/js): extract module-level block-scoped functions
  • fix(parsers/c): resolve static-inline functions in included headers
  • fix(parsers/php): keep both same-name defs from conditional/guarded branches
  • feat(reachability): warn on silent library blackout across all parsers
  • fix(parsers/c): stop same-(file,name) functions collapsing to one func_id
  • fix(parsers/python): extract block-scoped def/class units
  • fix(parsers/ruby): keep both same-name defs from conditional branches
  • feat(reachability): extend opt-in library-mode to all parsers + CLI
  • fix(zig,php parsers): attribute generic-container & anonymous-class methods correctly
  • docs(zig,php parsers): surface grammar-alignment prerequisite in regression tests
  • fix(parsers/php): resolve $this->/self:: calls into used traits
  • fix(parsers/c): guard macro-alias resolution against cyclic #defines
  • fix(parser_adapter): restore --fresh dataset deletion silently dropped by the feat(reachability): opt-in library-mode seeds the public API surface #117 merge

gadievron and others added 30 commits June 12, 2026 10:41
…tions

`_resolve_import` Strategy 2 bound an imported function to a file whose dotted
module path string-`endswith` the imported module (either direction). That crosses
component boundaries: `from utils import helper` matched `extra_utils.py`
(`'extra_utils'.endswith('utils')`) — a false call-graph edge to the wrong module
that no Python layout can produce.

Fix: match by dotted COMPONENTS, in EITHER direction — the imported module is a
component-suffix of the file path (a repo-root prefix on the file, `src/pkg/auth.py`
for `from pkg.auth import ...`), OR the file path is a component-suffix of the
imported module (the repo-IS-the-package self-import, `auth.py` == `myapp/auth.py`
recorded shallow via relative_to(repo_path), for `from myapp.auth import ...`).
Both are real layouts and are KEPT; only the substring-crossing matches are dropped.

An adversarial review caught that an earlier forward-only version dropped the
reverse direction (repo-is-package self-imports) — a recall regression. Because the
reachability filter keeps only reachable units, a dropped real edge silently removes
attack surface; so an edge that COULD be real (e.g. `from pkg.auth import login` to a
top-level `auth.py`, structurally identical to a self-import) is kept, not forbidden.
Net: strictly fewer false edges than master, never fewer real ones.

Tests (tests/parsers/python/test_call_graph_import_resolution.py): 2 precision drops
(substring-crossing `extra_utils`/`utils`, `extra_auth`/`auth`) + 6 recall guards
(exact, repo-root-prefix, top-level, repo-is-package self-import, nested reverse-prefix,
relative). Full python suite: 12 passed.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ust app type

A data-processing library (parser, deserializer, codec) takes untrusted INPUT
DATA — that data crossing into the code is the attack surface even with no
network listener. The generator sets requires_remote_trigger=False for every
library, so the Stage 1/2 prompts emitted 'only flag REMOTE attacker, not local
users' and discarded exactly those bugs (tree-sitter: all parser memory-safety
findings suppressed until requires_remote_trigger was hand-set).

Add ApplicationContext.suppress_local_only(): suppress ONLY when no trust
boundary is 'untrusted'. Reuses the already-captured trust_boundaries — no new
field. Gate all 7 sites (6 live in prompts/, 1 display in context/). A genuine
no-attack-surface library (all-trusted) is unchanged; an untrusted-input library
is now analysed. Generator instruction reworded to set the flag from untrusted
input, not blanket-false per type.

Tests: 8 new (suppress_local_only unit + Stage 1 + Stage 2 present/absent).
The zig parser is dispatched by parse_repository (core/parser_adapter.py:126)
and has a working test_pipeline, but 'zig' was missing from the parse/scan
--language choices, so 'openant parse --language zig' errored with 'invalid
choice'. Pre-existing gap (present on master); surfaced while verifying the
library-mode work — zig library-mode is unreachable via the CLI without this.

Verified: 'openant parse /tmp/zig-kf --language zig --level reachable
--library-mode' now runs (9 -> 21 reachable functions under library-mode).
typeToString had no case for *ast.IndexExpr / *ast.IndexListExpr, so methods on
generic types (func (s *Stack[T]) Push()) keyed under class "unknown". Distinct
generic types' same-named methods then collided onto one unit id and the store
silently overwrote one (data loss). Generic call sites (Gen[K,V](), obj.M[T]())
were likewise dropped from the call graph.

- extractor.go: add IndexExpr/IndexListExpr cases to typeToString (bare base type)
- callgraph.go: unwrap generic instantiation in analyzeCallExpr; reuse the
  Ident/SelectorExpr classification (fixes all generic call shapes + receiver)
- extractor.go: add duplicateIDWarning() - stderr signal on funcID collision so a
  future class-key collapse fails loudly instead of silently overwriting

Tests: 5 new (generic receiver keys, no-collision data-loss regression, generic
calls, dup-guard). go test ./... green; independent + judge verified, no regression.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The binary is listed in .gitignore but was tracked since the initial commit, so it
drifted stale on every source change (master's committed binary still emits
"unknown.Push"). Untrack it so it stays ignored; CI builds it on every run
(.github/workflows/test.yaml) and the dev pipeline rebuilds it when absent
(parsers/go/test_pipeline.py). Compile on demand — no committed artifact.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…oundary

The FastAPI input pattern (Query|Body|Form|File|Header|Cookie)\s*\( and the
ArgumentParser pattern lacked a leading \b, so any identifier ending in one of
those words (setCookie(, PQsendQuery(, getHeader(, MyArgumentParser() matched
as a user-input source and was seeded as a false remote-web entry point across
C/Go/PHP/Python repos. Anchor both with \b; qualified forms like fastapi.Query(
still match (the '.' provides the boundary).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…undaries

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…and scan

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…s in both directions

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…rns with word boundary

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…tring (BUG-NEW 2,22)

Local-only finder-fixes-54. 3 parametrized tests across 5 langs.
is_test_file used unanchored 'pattern in path_lower' (zig worst: bare test/spec tokens), so
real source whose name CONTAINS a token (latest/contest/attestation/inspector) was silently
dropped from extraction (skip_tests=True is the pipeline default). Anchored to exact path
DIRECTORY components + basename conventions across c/php/python/ruby/zig repository_scanner.
JS/Go already anchored -> untouched (judge-confirmed). Decoys not excluded; real test files
still excluded (recall preserved). Judge: AGREE / SHIP-AS-IS.
Local-only; not pushed.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

(cherry picked from commit d8c5f3fb0387c969a7e7c899a8d59e598c46a477)
…lias (BUG-NEW 3,17,41)

Local-only finder-fixes-54 (base master 368b559). TDD; 5 tests in the new
tests/parsers/zig/test_call_graph_builder_u*.py. Judge (vs prepared
recommendation, independent re-derivation from raw): AGREE / SHIP-AS-IS.
CM-B builtin pre-check scoped SAME-FILE (judge-confirmed: no global cross-file
fallback; genuine-builtin non-link pinned by a negative test). Combined parser
suite: 70 passed, 10 skipped.

Local-only; not pushed.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

(cherry picked from commit 4294ffe24e0814bc2668f1f6c46c3874de56b01e)
…n/container-methods (BUG-NEW 16,26,37)

Local-only finder-fixes-54. 7 new tests.
[37] fixed grammar node-type keys (variable_declaration + struct/enum/union/opaque_declaration,
members=function_declaration) so container methods extract as C.m; [16] first identifier as
fn name; [26] anchored test classification. Cross-unit: the [37] fix changes the call-graph
edge target to the now-correct qualified id m.zig:C.m -> updated 2 test_call_graph_builder_u13
assertions (judge-verified legitimate, not a weakening). Judge: AGREE / SHIP-AS-IS.
(Follow-up: dead _extract_struct_methods left for a separate cleanup.)
Local-only; not pushed.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

(cherry picked from commit 1aae27a79420beb4ab1ad2f2c064124c221ec26d)
…eck (BUG-NEW 13)

Local-only finder-fixes-54 (base master 368b559). TDD; 6 tests in the new
tests/parsers/c/test_call_graph_builder_u*.py. Judge (vs prepared
recommendation, independent re-derivation from raw): AGREE / SHIP-AS-IS.
CM-B builtin pre-check scoped SAME-FILE (judge-confirmed: no global cross-file
fallback; genuine-builtin non-link pinned by a negative test). Combined parser
suite: 70 passed, 10 skipped.

Local-only; not pushed.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

(cherry picked from commit ef1a3f91f163547a7652132f65613bb1be56c973)
…/operator/template/ns (BUG-NEW 14,15,32,33,35,39,40)

Local-only finder-fixes-54 (base 368b559). 8 tests.
struct_specifier/union_specifier branches mirror class_specifier (closes [32] AND the
[38] static-call dup — VERIFIED: Helper::compute resolves); ctor/dtor on unqualified
leaf; lambda_expression; operator_name; template args in func_id (g<int>!=g);
namespace-qualifier vs class-qualifier (namespaced free fn class_name=None).
Judge+probe: AGREE / SHIP-AS-IS. Full parser suite 131 passed, 10 skipped.
Local-only; not pushed.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

(cherry picked from commit e360860ceac539a3d1df0f5068faf560e3e9c6c2)
…G-NEW 29)

Local-only finder-fixes-54 (base master 368b559). The C extractor produces
func_data['is_inline'] but create_unit dropped it from unit metadata (and the
parallel generate_analyzer_output dropped 'isInline'). Carry it in both, matching
the is_static/is_exported convention.

Adds RF-1 field-contract guard tests/parsers/c/test_c_schema_completeness.py:
an explicit producer-key -> dotted-unit-location MAP (+ snake->camel analyzer
map) that round-trips each contracted field, so future drops fail automatically;
plus a self-check pinning the drift-prone key in the map. 5 tests; c+php
parser suites pass.

Judge (vs prepared recommendation): AGREE / SHIP-AS-IS; RF-1 realized as the
sound field-contract MAP (not a key-superset). Independent+judge concurred.

Local-only; not pushed.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

(cherry picked from commit 5eece7512f6872d878039e4866a32d13d4311a86)
…::/static:: + <?php-tag root cause (BUG-NEW 20,52)

Local-only finder-fixes-54. 6 tests.
[20] resolve $f() via a single string-literal binding (multi/non-literal declines).
[52] parent::/self::/static:: parse as relative_scope (was unhandled -> dropped); parent::
resolves via a class->parent index + cross-file _resolve_class_call (NOT _resolve_self_call,
which is same-file/same-class — independent+judge confirmed via a shadow-method probe).
DEEPER ROOT CAUSE fixed: extractor stores bodies without a <?php tag, so tree-sitter parsed
them as inline HTML -> ZERO call nodes (masked ALL php edges); prepend <?php before re-parse
(idempotent, offset-sound, no spurious 'php' edge — judge-verified necessary + in-scope).
Judge+independent: AGREE / SHIP-AS-IS.
Local-only; not pushed.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

(cherry picked from commit fb4983428c0ebd0d38ab7eafcd4b84df37dc7c1d)
…erited dispatch

Adds the local-decl type-inference machinery that was MISSING for these two
deferred bugs (judge-confirmed last time they couldn't be a resolver tweak).
10 tests; full c suite 50 passed (no regression of the 9 committed c bugs).

[51] member-dispatch (sound, same-file): preserve the receiver
(_extract_call_name_and_receiver); new _extract_local_var_types (varname->type
from declaration nodes incl. pointer/init/reference declarators); methods_by_class
index; receiver-type-aware _resolve_member_call resolves w.compute()/w->compute()
to Type::compute when the receiver's local-declared type defines it same-file —
else FALLS BACK to base-name resolution (no false edge when type unknown).

[30] virtual/inherited dispatch (static-declared-type FLOOR + inheritance walk):
function_extractor now extracts base classes (_extract_base_classes walks
base_class_clause -> class_bases index); _resolve_member_call does a cycle-guarded
BFS UP the base chain, resolving to the first ancestor that defines the method.
Base* b; b->compute() links ONLY Base::compute (the static type's method) — derived
overrides are the DOCUMENTED non-goal (over-approximation = false edges). Override
stops the walk; unknown/no-definer -> None.

Precision negatives all pass (unknown receiver, free fn unchanged, two-class
disambiguation, override-stops-walk, no-ancestor, inheritance cycle). Independent +
judge verified SOUND from raw; no false edge introduced vs prior behavior. One
pre-existing same-file same-name-cross-namespace mislink is unchanged by this fix
(documented optional follow-up). go/python tree changes are separate (already committed).
Local-only; not pushed.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

(cherry picked from commit e55be766db832831a716c3c97509f9af9d103d88)
…/builtin/method-object/substring-import (BUG-NEW 1,8,11,31,49)

Local-only finder-fixes-54 (base master 368b559). TDD; 10 tests in the new
tests/parsers/ruby/test_call_graph_builder_u*.py. Judge (vs prepared
recommendation, independent re-derivation from raw): AGREE / SHIP-AS-IS.
CM-B builtin pre-check scoped SAME-FILE (judge-confirmed: no global cross-file
fallback; genuine-builtin non-link pinned by a negative test). Combined parser
suite: 70 passed, 10 skipped.

Local-only; not pushed.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

(cherry picked from commit 74153f98a94f9e8b95c39083d3d0eaf71ea7956e)
…ine/enum/braceless-ns (BUG-NEW 7,25,36,54)

Local-only finder-fixes-54. 4 tests.
enum_declaration branch (closes [36] AND the [53] dup — enum methods get class_name);
recurse nested functions; start_line past attribute_list [25]; [54] braceless namespace
metadata carried to following siblings (sibling-not-child of namespace_definition).
Judge+probe: AGREE / SHIP-AS-IS.
Local-only; not pushed.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

(cherry picked from commit 7efc40e3339f47c6203a11dbbe6b2776c15a6c5e)
… (BUG-NEW 43,28)

Local-only finder-fixes-54 (base master 368b559).
- [43] create_unit read func_data.get('namespace') but the extractor writes
  'namespace_name' (canonical, emitted in 5 sites) -> unit namespace was always
  None. Aligned the CONSUMER to read 'namespace_name' (1 read + 2 emit sites;
  lower blast radius than renaming the producer; no other reader of either key).
- [28] is_static produced but never carried into unit metadata -> carried it
  (create_unit + generate_analyzer_output).
Adds RF-1 field-contract guard tests/parsers/php/test_php_schema_completeness.py
(producer-key -> unit-reader MAP, encodes the namespace rename leg) + a
membership self-check. 6 tests; php parser suite passes.

Judge (vs prepared recommendation): AGREE / SHIP-AS-IS; consumer is the correct
side for [43]. Independent+judge concurred. SURFACED (separate, out-of-scope,
not fixed): php extractor never emits 'visibility' (_get_visibility uncalled) ->
private methods report public — logged for a separate fix.

Local-only; not pushed.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

(cherry picked from commit f193401fa6c74764b274150f5a54ccb01dedc927)
…ethod/alias/private/nested-class (BUG-NEW 6,18,24,42,44,50)

Local-only finder-fixes-54. 8 tests.
recurse method bodies; is_singleton before initialize; define_method(:sym) registers sym
[24] and alias node registers name [42] (DISTINCT AST nodes, judge-confirmed); private/
public/protected keyword-state visibility model [44]; compose Outer::Inner [50].
Judge: AGREE / SHIP-AS-IS.
Local-only; not pushed.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

(cherry picked from commit 825c92d71c686234828215102da60eb584e0de7b)
…ding (BUG-NEW 31)

Ruby hunk of the alias-guard (python hunk ships in the python PR). Local-only; not pushed.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… (BUG-NEW 4)

Local-only finder-fixes-54. 5 tests; 33 passed (detector+reachability), no regress.
ENTRY_POINT_TYPES omitted 'main', and _get_entry_point_reasons gated the unit-type
signal solely on that set, so a silent main (no decorator/input pattern) accumulated
zero reasons -> never seeded -> its callees fell out of the reachable set. Added 'main'
to ENTRY_POINT_TYPES + a defensive name=='main' fallback. Language-agnostic; for Go this
also requires the BUG-5 camelCase normalization (separate commit) so unitType is read.
Judge: AGREE / SHIP-AS-IS.
Local-only; not pushed.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

(cherry picked from commit 95a708fb2d2bdaff619d24c3883aafb3072b601b)
… snake_case (BUG-NEW 5)

Local-only finder-fixes-54. 3 tests; 143 passed, 31 skipped, no regress.
The Go binary emits camelCase FunctionInfo keys (unitType/startLine/filePath/...) while
every Python consumer reads snake_case -> Go func records had unit_type=None, silently
disabling ALL unit_type-based logic for Go (entry-point seeding, reachability, by-type
stats). Root: the two normalization blocks in test_pipeline.py normalized the WRONG way
(emitting camelCase, stale "detector expects camelCase" comment) while EntryPointDetector
reads snake. Added a shared idempotent normalize_go_function_records() (camel->snake,
already-snake passes through) at both call_graph.json build + reachability-filter boundaries.
END-TO-END verified (real Go binary): a silent func main() now seeds reachability via
unit_type:main and its helper is reachable (the [4]+[5] payoff).
Deliberately NOT touched: the analyzer_output.json camelCase surface (consumed camelCase by
repository_index.py/tools.py — a separate intentional contract). Consumer-check corrected the
deep-investigation's "likely non-bug" verdict: [5] is a REAL bug on the call_graph surface.
Local-only; not pushed.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

(cherry picked from commit c33c35ab16e099b72a36464f26b98359cee79bc2)
…n schema

Re-verification (independent + judge) of the shipped [5] go-camelCase fix. Confirmed
correct on the call_graph.json surface (real bug; analyzer_output.json camelCase is a
separate intentional contract, untouched). Gap closed: normalize_go_function_records
omitted parameters/returns/is_async (present in Go FunctionInfo); added them with the
isAsync->is_async fallback (defense-in-depth — no live consumer reads them yet, but the
record now matches the snake-case shape). +2 unit tests (camel->snake full schema +
idempotency, no Go toolchain needed). go parser suite: 9 passed.
Local-only; not pushed.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

(cherry picked from commit 908384050b46c9c5afb2d59cc36cba406045c415)
…l routing (BUG-NEW 19)

Local-only finder-fixes-54. go test ./... green (3 new go tests); binary rebuilt (go build -o go_parser .).
[19] f := helper; f() dropped the edge — collectFuncValueAliases now tracks a SINGLE
unconditional name:=<ident> binding and rewrites f() to its target before the builtins
filter (reassigned/conditional/non-ident RHS -> dropped, no false edge). DEEPER bug found
+ fixed (the reported baseline 'direct helper() resolves' was FALSE): resolveCalls routed
every call into the method branch via "" == callerInfo.ClassName, so top-level calls
produced 0 edges, masking the alias bug — guarded the routing to require a non-empty
class match. Scope widened by that one routing line (flagged for the judge).
Method-value alias (m := obj.M) = the distinct BUG-044, correctly dropped (out of scope).
Local-only; not pushed.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

(cherry picked from commit 52440afa1887427c79efe64676477c010f0420bc)
A pure library exposes no main/route/CLI entry point, so the structural detector
finds nothing and apply_reachability_filter drops EVERY unit — the library, and
any vulnerable sink it contains, is never analysed (verified: a library whose
public function calls a private eval() sink scans to 0 units). The public API IS
the entry surface for a library.

Adds an opt-in `library_mode` to apply_reachability_filter: when set, seed every
public/exported function (`is_exported` when the parser provides it, else
name-not-underscore) and let the existing forward BFS pull in their callees. The
seed merge is union-only, so it can never demote a structurally-detected app
entry point — turning the flag on for an app can only ADD reachable units.
Default is False, so every existing caller is byte-identical.

Threaded through parse_repository -> _parse_python (the Python parse path that
applies this filter; other languages compute reachability in their own pipelines
and are a follow-on). The CLI --library-mode flag is a thin follow-on passthrough.

Tests (tests/test_library_mode_reachability.py): blackout-when-off,
public-API-seeded-when-on (private callee reached via the edge), unreferenced-
private-stays-out (precision), app-baseline-unchanged, app-mode-on-is-additive-
only (adversarial: union can't subtract), and a parse_repository wiring guard.
6 passed; e2e confirmed False -> [] / True -> [public_api, _sink].

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…,21,23,34,45,48)

Pilot unit of the local-only finder-fixes-54 batch (base GitHub master 368b559).
TDD; 8 RED->GREEN tests in tests/parsers/python/test_function_extractor_u9.py
(full python parser suite: 12 passed). Verified per-bug pytest summary inline.

- [9]  recurse into function bodies (_process_function_tree) — nested def extracted
- [21] emit module-level name-bound lambdas (handler = lambda ...)
- [23] start_line = first decorator line (was the def line; off-by-N for stacked)
- [34] classify @x.setter/@x.deleter as 'property'; getter keeps canonical
       func_id C.x, setter -> qualified_name C.x.setter (role suffix), preserving
       func_id == path:qualified_name (the call_graph_builder reconstruction
       invariant) — order-independent + adds property_role metadata
- [45] recurse into nested classes (_process_class_tree) — Outer.Inner.deep
- [48] classify test by path COMPONENTS / basename conventions, not the bare
       substring 'test' (latest.py no longer mis-flagged)

Reconciliation: a BLIND /bug-fix derivation initially used an order-based #N
func_id suffix for [34]; an independent re-derivation + judge (vs the prepared
fix-design recommendation) found it breaks the path:qualified_name invariant and
is order-dependent. Replaced with the role-in-qualified_name scheme (judge: SHIP
the other 5 as-is). _store_function residual collisions now disambiguate by
source line (#L<line>), not emission order.

Local-only; not pushed.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

(cherry picked from commit 24510d1a071b131e8b48aa089d4dbe28c61aa50f)
This was referenced Jul 10, 2026
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.

2 participants