Skip to content

fix(diff): key node diff by identity, not line-bearing id (closes #300) - #302

Merged
Wolfvin merged 1 commit into
mainfrom
fix/issue-300-node-diff-lineshift
Jul 17, 2026
Merged

fix(diff): key node diff by identity, not line-bearing id (closes #300)#302
Wolfvin merged 1 commit into
mainfrom
fix/issue-300-node-diff-lineshift

Conversation

@Wolfvin

@Wolfvin Wolfvin commented Jul 17, 2026

Copy link
Copy Markdown
Owner

Closes #300. Stacked on #298 — base is feat/issue-297-edge-diff, reuses the identity helper landed there. Merge #298 first; this retargets to main automatically.

Masalah

_diff_backend() mengunci node dengan n["id"], dan node id memuat nomor baris. Menambahkan satu komentar di atas file — tanpa menyentuh satu fungsi pun — melaporkan setiap fungsi di file itu sebagai ditambah DAN dihapus, plus new_dead / resolved_dead palsu.

Registry nyata (smart-tax-assistance/app, 4.188 node), geser baris seluruh codebase, 0 perubahan sungguhan:

main hari ini PR ini
added_nodes 1.137 0
removed_nodes 1.137 0
new_dead 260 0
resolved_dead 260 0

260 alarm "fungsi ini baru saja mati" yang palsu. Itu paruh yang berbahaya: agent yang percaya new_dead bisa menghapus kode hidup. Kelas yang sama dengan #293 (Go/Java false-dead) dan #294 (JSX handler false-dead). Pemicunya cuma "ada yang menambah docstring atau merapikan import" — jadi kemungkinan besar ini sudah terjadi diam-diam di banyak PR.

Yang berubah

Identitas node = (file, impl_for, fn) lewat _node_key/_index_nodes, difaktorkan dari _endpoint_key milik #297satu skema identitas untuk kedua paruh diff, bukan dua.

Fungsi yang berbagi (file, impl_for, fn) — closure visit/_walk yang didefinisikan berulang di satu scope, yang registry sendiri sudah tandai duplicate_define — dapat occurrence index urut baris. Jadi menghapus satu dari dua tetap terdeteksi, sementara geser baris menjaga kuncinya stabil. (Mengunci tanpa index akan menggabungkan mereka dan menyembunyikan penghapusan nyata.)

Edge tetap butuh id mentah, jadi node diindeks dua kali: by id untuk endpoint, by identity untuk diff.

Bonus: sinyal yang selama ini hilang, kembali

Node yang bergeser baris tidak pernah dibandingkan dengan dirinya yang lama — jadi perubahan ref_count/status-nya lenyap begitu saja. Membuang derau sekaligus memulihkan ini: changed_nodes sekarang selamat melintasi geser baris. Ada testnya.

Verifikasi (dijalankan)

  • 14 test baru (tests/test_diff_engine_nodes.py): geser baris → nol; add/remove nyata → terdeteksi; newly-dead nyata → terdeteksi; sudah-dead + geser → bukan new_dead; hapus 1 dari 2 duplicate_define → terdeteksi; pindah file → terhitung; bentuk output tetap.
  • 16 test edge feat(diff): compare call-graph edges in snapshot diff — 25,876 edges saved to disk but never compared #297 tetap hijau — edge resolution lewat id-map tak regres.
  • Skala nyata: 3.878 dari 4.188 id bergeser → 0/0/0/0.
  • Full suite: 19 gagal di branch, 19 di main, daftar identik — nol regresi.

Bentuk output tidak berubah. Command count tetap 12. Bug fix → skip-design-doc.

🤖 Generated with Claude Code

@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.

_diff_backend() keyed nodes by n["id"], which embeds a line number, so
adding a comment to a file reported every function in it as added AND
removed and fired false new_dead / resolved_dead. On the real 4,188-node
polyglot workspace a pure line shift with zero real changes produced
1,137 false adds, 1,137 false removes and 260 false dead-code alarms.

False new_dead is the dangerous half: an agent reading "this function
just became dead" can delete live code. Same class as #293 and #294.

Nodes now key on (file, impl_for, fn) via the _node_key/_index_nodes pair
factored out of #297's _endpoint_key, so both halves of the diff share one
identity scheme. Functions that share (file, impl_for, fn) — closures like
`visit` redefined in a scope, already flagged duplicate_define — get an
occurrence index in line order, so deleting one of two is still detected
while a shift keeps the keys stable.

Edge resolution still needs raw ids, so nodes are indexed twice: by id for
endpoints, by identity for the diff.

Recovers lost signal too: a shifted node was never compared against its
old self, so its ref_count/status change vanished. changed_nodes now
survives a line shift.

Output shape unchanged. Full suite: 19 failures on this branch, 19 on
main, identical list.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@sonarqubecloud

Copy link
Copy Markdown

@Wolfvin
Wolfvin changed the base branch from feat/issue-297-edge-diff to main July 17, 2026 06:20
@Wolfvin
Wolfvin merged commit 894a9ee into main Jul 17, 2026
1 check passed
@Wolfvin
Wolfvin deleted the fix/issue-300-node-diff-lineshift branch July 17, 2026 06:21
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(diff): node diff keyed by line-bearing id — adding a comment reports functions as added/removed and emits FALSE new_dead

1 participant