Skip to content

fix(trace,impact): render module-level callers instead of silently dropping (closes #223)#248

Open
Wolfvin wants to merge 1 commit into
mainfrom
fix/issue-223-module-level-callers-trace-impact
Open

fix(trace,impact): render module-level callers instead of silently dropping (closes #223)#248
Wolfvin wants to merge 1 commit into
mainfrom
fix/issue-223-module-level-callers-trace-impact

Conversation

@Wolfvin

@Wolfvin Wolfvin commented Jul 12, 2026

Copy link
Copy Markdown
Owner

Closes #223

Patch Peta (delta struktural)

Files:

  • tests/test_issue223_module_level_callers.py — 16 tests covering helper detection, query_callers, trace_via_graph, analyze_impact, and the no-pollution constraint (DoD refactor: backend-focused cleanup — remove hardcoded paths, garbage files, frontend deps #3).
    M scripts/graph_model.py — add _MODULE_LEVEL_SENTINEL constant, is_module_level_source_id() helper, and _module_level_caller_entry() builder. _bfs now emits a synthesized entry for synthetic <file>:0:<module> source_ids instead of silently dropping them, in BOTH the normal-resolution path AND the cyclic-visited path. Module-level entries are terminal (no further BFS enqueue) since module scope is the top of a file's call hierarchy.
    M scripts/trace_engine.py — _bfs_trace_graph preserves module_level marker on chain entries (normal + cyclic) and sets fn="<module>" for clarity. Module-level entries are NOT enqueued for further BFS.
    M scripts/impact_engine.py — analyze_impact emits a "module-level caller — calls X" entry in affected["direct"] AND a "N hops from X (module-level caller)" entry in affected["indirect"] when from_id is a synthetic module-level id. New helper _file_from_module_level_id() extracts the file path for the human-readable relation string.
    Endpoints: none
    Schema: no SQL schema change. New field module_level: true (boolean) appears on result dicts from graph_model.query_callers / query_callees and on chain entries from trace_engine.trace_via_graph — consumers can opt-in to render it distinctly. No existing field is removed or renamed.
    Konvensi: synthetic module-level source_ids follow the format <file>:0:<module> (emitted by js_backend_parser.py:248, ts_backend_parser.py:125, fallback_js_backend.py:196). Future parsers that emit module-top-level calls MUST use the same format so is_module_level_source_id() recognizes them. Do NOT create a matching entry in graph_nodes — the synthetic id stays edge-only (constraint from issue fix(trace,impact): module-level callers invisible due to synthetic source_id, inconsistent with rc #223, keeps list/search output free of fake <module> function entries).

Klaim + Bukti

import: python3 -c "import sys; sys.path.insert(0,'scripts'); import graph_model, trace_engine, impact_engine" → exit 0 (no errors)
unit tests: python3 -m pytest tests/test_graph_model.py tests/test_issue223_module_level_callers.py tests/test_edge_resolver.py tests/test_search_engine.py tests/test_command_count.py tests/test_command_registry.py tests/test_doctor.py tests/test_orient.py → 128 passed in 6.62s
parser: python3 -m pytest tests/test_ts_backend_parser.py tests/test_js_backend_parser.py → 18 passed, 29 skipped (skips = tree-sitter grammars not installed in this env, pre-existing)
dod #1 (trace up shows module-level callers): end-to-end smoke test on synthetic fixture (1 module-level caller + 1 regular caller) →
trace_via_graph requirePermission --direction up up_chain (3 entries): depth=0 fn='requirePermission' file='auth.ts' node_id='auth.ts:10:requirePermission' depth=1 fn='<module>' file='auth.ts' node_id='auth.ts:0:<module>' [module-level] depth=1 fn='handler' file='auth.ts' node_id='auth.ts:5:handler'
Pre-fix: only depth=0 + depth=1 handler entries (module-level silently dropped).
dod #2 (impact dependent count consistent with rc): same fixture → analyze_impact requirePermission returns 2 entries in affected["direct"] (1 module-level + 1 regular handler). Pre-fix: only 1 entry (handler).
direct dependents (2): name='<module>' file='auth.ts' relation='module-level caller — calls requirePermission' [module-level] name='handler' file='auth.ts' relation='calls requirePermission'
dod #3 (list/search NOT polluted): find_nodes_by_name("<module>", db_path)[] (no fake node). SELECT * FROM graph_nodes WHERE node_id LIKE '%:0:<module>' OR name = '<module>' → 0 rows. Synthetic source_id stays edge-only as required by the issue constraint.
dod #4 (0 new test regressions): 3 failures in tests/test_compact_format.py (TestSearchPagination ×2, TestGraphSchemaCommand::test_returns_correct_counts) — verified pre-existing on clean main (root cause: tree-sitter-typescript grammar not installed in worker env → TSBackendParser falls back → CALLS edge count differs). NOT caused by this PR.

Klaim yang TIDAK bisa dibuktikan di sini:

  • Before/after trace output untuk requirePermission di KDS backend real (rc:49 case from issue body): tidak dijalankan karena KDS workspace tidak tersedia di sesi worker ini. Reproduksi di fixture sintetik (2 callers: 1 module-level + 1 regular) sudah memvalidasi structural fix. Verifikasi pada codebase nyata KDS diserahkan ke BOS saat review.

Catatan Pendekatan

none — implementasi mengikuti instruksi issue (cari cara lain tanpa buat fake node entry; fallback query yang follow dangling source_id dan render sebagai "module-level caller").

Breaking / Found-not-fixed

  • Cyclic handling for module-level callers: _bfs now emits a cyclic entry for module-level source_ids that are revisited. In practice this branch is hard to trigger (module-level callers are terminal — they're added to visited on first encounter and never enqueued for further BFS, so they can't naturally reappear as a neighbor). The code path exists for defensive correctness; if a future change makes module-level callers non-terminal, the cyclic path becomes relevant.
  • Pre-existing failures (out of scope): tests/test_compact_format.py::TestSearchPagination (2) and ::TestGraphSchemaCommand::test_returns_correct_counts (1) — verified pre-existing on clean main, root cause is missing tree-sitter grammars in worker env, NOT this PR.
  • test_large_file_parsing.py segfault (Known Gap [ARCH] Replace flat registry with true graph data model (nodes + edges) #7 in CONTEXT.md): not run — would abort the test session. Pre-existing.

…opping (closes #223)

PR #219 (issue #210) fixed ref_count undercounting by adding a synthetic
source_id = '<file>:0:<module>' for module-top-level calls. The synthetic
id has NO matching entry in graph_nodes (intentional — keeps list/search
output free of fake <module> function entries).

Pre-#223 this caused trace --direction up and impact to silently drop
ALL module-level callers: the BFS JOIN graph_nodes.node_id = ? returned
no row, and the caller was skipped. ref_count (computed from target side)
was correct, but trace/impact (computed from source side) were wrong —
inconsistent and dangerous for anyone using trace to decide 'safe to
delete?'. Concrete repro from issue body: search requirePermission
shows rc:49, but trace --direction up showed only 1 entry (itself).

Fix:
- graph_model.py: add _MODULE_LEVEL_SENTINEL constant + helper
  is_module_level_source_id() + _module_level_caller_entry() builder.
  _bfs now emits a synthesized entry for synthetic <file>:0:<module>
  source_ids instead of dropping them, in BOTH the normal path and
  the cyclic-visited path. Module-level entries are terminal (no
  further BFS) since module scope is the top of a file's call hierarchy.
- trace_engine.py: _bfs_trace_graph preserves module_level marker on
  chain entries (both normal + cyclic) so formatters can render
  'module-level caller in <file>' distinctly. fn is set to '<module>'
  for clarity.
- impact_engine.py: analyze_impact emits a 'module-level caller' entry
  in affected['direct'] AND affected['indirect'] when from_id is a
  synthetic module-level id. Helper _file_from_module_level_id()
  extracts the file path for the human-readable relation string.

Constraint from issue #223: do NOT create a fake <module> node in
graph_nodes. The synthetic source_id stays edge-only — list/search
output remains free of <module> entries. Verified by tests.

Tests: tests/test_issue223_module_level_callers.py — 16 tests covering
helper detection, query_callers, trace_via_graph, analyze_impact, and
the no-pollution constraint.
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

@sonarqubecloud

Copy link
Copy Markdown

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.

fix(trace,impact): module-level callers invisible due to synthetic source_id, inconsistent with rc

1 participant