Skip to content

feat(diff): compare call-graph edges in snapshot diff — 25,876 edges saved to disk but never compared #297

Description

@Wolfvin

Mengapa (falsifiable — diverifikasi 2026-07-17)

Snapshot CodeLens menyimpan edge call-graph ke disk tapi tidak pernah membandingkannya. _diff_backend() (scripts/diff_engine.py:303-353) hanya membaca nodes — kata edges tidak muncul sekali pun di seluruh fungsi itu, padahal _empty_backend() (baris 204-205) mendeklarasikan {"nodes": [], "edges": []} dan save_snapshot() men-deepcopy seluruh dict backend.

Repro (snapshot nyata di repo ini, scripts/.codelens/snapshots/20260714T080003Z.json):

backend keys: ['last_updated', 'workspace', 'nodes', 'edges']
nodes: 2429   edges: 25876
Data di snapshot Dibandingkan oleh _diff_backend()?
2.429 nodes ✅ ya (added/removed/changed + new_dead/resolved_dead)
25.876 edges tidak — diabaikan total

Akibatnya impact --check diff bisa bilang "fungsi X jadi dead", tapi tidak bisa bilang:

  • edge baru muncul dari modul A ke modul B (arsitektur melar diam-diam)
  • edge X→Y hilang (perilaku berubah tanpa fungsi mana pun hilang)

Perubahan struktural yang tidak menghapus/menambah fungsi = tak terlihat sepenuhnya hari ini. Padahal datanya sudah ada di disk.

Mekanisme (dari inspeksi data snapshot, bukan asumsi)

Edge tidak seragam bentuknya — ada 3 shape, dan ini menentukan desain:

Shape Jumlah Arti
{from, to_fn, resolved:false} 21.323 call tak ter-resolve (target = nama saja)
{from, to} 3.669 call ter-resolve (to = node id)
{from, to, via_self} 884 call ter-resolve via self.x()

Fakta terukur yang membentuk constraint:

  • 82% edge tak ter-resolve. Target teratasnya: get, append, group, strip, join, startswith, search, match — method builtin/stdlib. Nol nilai arsitektural.
  • Resolved edge: 4.553 total tapi hanya 3.309 pasangan (from,to) unik — 547 pasangan berulang (maks 26×), karena satu edge dicatat per call-site.
  • via_self bernilai True (884) atau absen (3.669) → kualifier, bukan bagian identitas.

Konsekuensi: diff naif atas 25.876 edge = sampah (21k noise builtin), dan diff sebagai multiset akan melaporkan "berubah" hanya karena jumlah call-site bergeser — padahal bentuk graph sama.

Konteks

Tujuan

_diff_backend() membandingkan bentuk call-graph, bukan cuma keberadaan fungsi. Setelah ini, satu perintah bisa menjawab: "edge apa yang muncul/hilang antara dua checkpoint?"

Constraint

  • Identitas edge = pasangan (from, to) unik atas resolved edge saja. Set, bukan multiset — perubahan jumlah call-site bukan perubahan arsitektur.
  • Unresolved edge (to_fn) TIDAK masuk detail diff — hanya delta jumlah. 21k append/strip/get akan menenggelamkan sinyal.
  • via_self dikeluarkan dari kunci identitas (kualifier).
  • Count selalu eksak; list detail dibatasi (cap) + flag truncated — 3.309 pasangan bisa meledak jadi ribuan entri saat file di-rename.
  • Backward compatible: field lama (added_nodes, removed_nodes, changed_nodes, *_count, new_dead, resolved_dead) tidak boleh berubah bentuk/namacommands/diff.py, dashboard.py, formatter, dan MCP mengonsumsinya.
  • Snapshot lama (tanpa key edges, atau shape campur) harus di-handle tanpa crash → .get("edges", []).
  • Zero dead code. Tidak ada command baru — command count tetap 12.
  • Bukan feature-class file baru (engine sudah ada) → tidak butuh design doc; PR pakai label skip-design-doc bila CI meminta.

Definition of Done

  • _diff_backend() mengembalikan added_edges, removed_edges (resolved-only, distinct (from,to), capped + truncated) dan added_edge_count / removed_edge_count (eksak)
  • Delta unresolved dilaporkan sebagai jumlah saja (unresolved_count from→to), tanpa detail per-edge
  • summary di diff_snapshots() dan diff_current_vs_last() menyertakan edges_added / edges_removed
  • Field lama tidak berubah — test existing impact --check diff tetap hijau
  • Snapshot tanpa edges tidak crash (backward compat)
  • Unit test: edge ditambah → muncul di added_edges; edge dihapus → muncul di removed_edges; call-site bertambah tanpa pasangan baru → tidak dilaporkan; unresolved edge tidak bocor ke detail
  • Diverifikasi di workspace polyglot nyata (Coretax-Auto-Downloader/smart-tax-assistance/app, 425 file): dua checkpoint + satu perubahan nyata → edge delta benar
  • Full test suite hijau

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions