Skip to content

fix(diff): node diff keyed by line-bearing id — adding a comment reports functions as added/removed and emits FALSE new_dead #300

Description

@Wolfvin

Mengapa (falsifiable — repro dijalankan 2026-07-17)

Ditemukan saat verifikasi #297 (edge-diff), bukan scope PR #298 — jadi tidak di-fix di sana.

_diff_backend() (scripts/diff_engine.py) mengunci node dengan n["id"]:

old_nodes = {n["id"]: n for n in old.get("nodes", [])}
new_nodes = {n["id"]: n for n in new.get("nodes", [])}

Node id memuat nomor baris (checkout.py:6). Akibatnya menambahkan satu komentar di atas file — tanpa menyentuh satu pun fungsi — membuat SETIAP fungsi di file itu dilaporkan ditambah DAN dihapus, plus new_dead / resolved_dead PALSU.

Repro terisolasi (graph identik, semua baris turun 5 karena komentar ditambahkan):

old = {'nodes':[{'id':'checkout.py:6','fn':'handle_checkout','file':'checkout.py','line':6,'ref_count':0,'status':'dead'},
                {'id':'checkout.py:12','fn':'send_receipt','file':'checkout.py','line':12,'ref_count':1,'status':'active'}],
       'edges':[{'from':'checkout.py:6','to':'checkout.py:12'}]}
new = {'nodes':[{'id':'checkout.py:11','fn':'handle_checkout',...,'line':11,...},
                {'id':'checkout.py:17','fn':'send_receipt',...,'line':17,...}],
       'edges':[{'from':'checkout.py:11','to':'checkout.py:17'}]}
_diff_backend(old, new)

Hasil aktual — perubahan nyata: NOL:

Field Dilaporkan Benar
added_nodes ['handle_checkout', 'send_receipt'] []
removed_nodes ['handle_checkout', 'send_receipt'] []
new_dead ['handle_checkout'] []fungsi ini sudah dead sejak sebelumnya, tidak "baru mati"
resolved_dead ['handle_checkout'] []

Fungsi yang sama muncul di added_nodes dan removed_nodes sekaligus — tanda pasti kunci identitasnya salah.

Terverifikasi juga lewat CLI penuh pada workspace nyata: satu komentar header + satu rewire → impact --check diff melaporkan added: 3, removed: 3, new_dead: 2 padahal yang berubah cuma satu edge.

Konteks

Tujuan

Node diff kebal geser-baris: hanya fungsi yang benar-benar muncul/hilang yang dilaporkan, dan new_dead hanya untuk fungsi yang benar-benar baru mati.

Constraint

  • Pakai ulang _endpoint_key() dari feat(diff): compare call-graph edges in snapshot diff — 25,876 edges saved to disk but never compared #297 — sudah ada di file yang sama, sudah teruji, kuncinya sudah diukur (2.429 node: (file,fn) → 38 tabrakan; (file,impl_for,fn) → 9, dan ke-9 nya sudah ditandai duplicate_define). Jangan bikin skema identitas ketiga.
  • Bentuk output tidak boleh berubahadded_nodes/removed_nodes/changed_nodes tetap {"name","file",...}. commands/diff.py, dashboard, formatter, MCP mengonsumsinya. Yang berubah cuma anggota daftarnya.
  • Fungsi yang benar-benar pindah file harus tetap terhitung removed+added (file bagian dari kunci) — itu benar, bukan bug.
  • Zero dead code. Command count tetap 12.

Definition of Done

  • Geser baris murni (komentar ditambah) → added_nodes, removed_nodes, new_dead, resolved_dead semuanya kosong
  • Fungsi yang benar-benar ditambah/dihapus tetap terdeteksi
  • Fungsi yang benar-benar berubah dead → muncul di new_dead; yang sudah dead sejak awal → TIDAK muncul
  • changed_nodes (ref_count/status) tetap bekerja lintas geser baris — sekarang node yang bergeser tak pernah dibandingkan sama sekali, jadi perubahan rc-nya luput
  • Test regresi: gagal kalau kunci dikembalikan ke n["id"]
  • Bentuk output tidak berubah; _diff_frontend tak regres; suite tak regres

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions