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

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions