Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ Working seed — the engine runs, the docket renders, and git + Jujutsu ingestio
- [x] Git adapter with 3-way adjudication
- [x] Jujutsu adapter with 3-way adjudication (`--source jj`, revsets accepted)

**Known limitation:** functions that share a bare name within one file (e.g., a `render` method on two classes, or same-named Go methods on two types) are tracked by first occurrence only; the docket flags them as ambiguous rather than tracking each definition separately.
Same-named definitions in one file (e.g. a `render` method on two classes) are each tracked separately: diffing matches identical bodies first, then pairs the rest, so only the definition that actually changed is reported.

## License

Expand Down
41 changes: 41 additions & 0 deletions TODO.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,44 @@ Text conversion happens only in the structural engine at parse time
store unconverted bytes. Pinned by
`test_cli_distinct_binaries_are_not_collapsed`.

## ~~Same-named functions tracked by first occurrence only~~ RESOLVED 2026-08-22

`FunctionMap` was `HashMap<name, Def>`, so a second same-key definition in
one file was dropped with an ambiguity note and the first occurrence's
changes shadowed the rest. The map now holds every definition
(`HashMap<String, Vec<FnDef>>`) and diffing aligns each name group by
content: exact body match first, positional pairing of leftovers, remainder
becomes added/removed. Qualified keys (`(Type).name`) stay rejected — they
are unstable under impl-block refactors
(`test_engine_impl_move_is_not_a_conflict`). Pinned by
`test_engine_duplicate_function_names_tracked_separately`,
`test_engine_go_same_name_methods_tracked_separately`, and
`test_engine_rust_impl_method_collision_tracked_separately`.

## Rename/rename divergence is swallowed

Base has `f`; ours renames `f -> g`, theirs renames `f -> k`. Both sides
deleted `f`, so it reads as convergent; `g` and `k` read as plain additions.
Merged result silently holds both names. Pre-existing (the old code hit its
convergent-clean branch the same way), but def-level tracking makes a fix
reachable: detect that the removed base defs survive under different names
per side, then emit High. Trigger: first real rename/rename dispute or the
hosted intent-scoring work, whichever comes first.

## Positional pairing can swap row attribution on count asymmetry

When a new same-named def lands *above* a modified one
(base 1x `f`, head 2x `f`), leftover pairing reports "changed" at the new
def's row and "added" at the modified one's row. Counts and severity are
right; only line numbers are swapped. Right fix: similarity-based leftover
pairing (edit distance or tree-sitter diff hash) instead of document order.
Trigger: when a docket consumer starts using dispute rows for navigation.

## Fallback conflict message says "modified" for pure deletion divergence

When both branches delete different copies of a same-named def, the High
dispute reuses the "both branches modified function `X` differently"
wording (severity is correct, only the verb is off). Right fix: a dedicated
"deleted differently" message variant. Trigger: first docket consumer that
pattern-matches dispute details.

13 changes: 8 additions & 5 deletions src/engine/language.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,14 @@
//! on the declarator itself. That relationship is captured by
//! [`LangConfig::wrapped_functions`].
//!
//! Function map keys are always bare names. Receiver- or impl-qualified keys
//! (`(*T).name`, `(A).name`) were tried and rejected: they make keys unstable
//! under refactoring, so moving a function between impl blocks fabricates
//! High-severity 3-way conflicts (and false Blocked verdicts). Same-named
//! functions are reported as ambiguous instead — see `Engine::diff_snapshots`.
//! Function map keys are always bare names, but every same-key definition
//! is kept and diffing aligns each group by content (exact source match
//! first, then positional pairing of leftovers — see `align_defs`).
//! Receiver- or impl-qualified keys (`(*T).name`, `(A).name`) were tried and
//! rejected: they make keys unstable under refactoring, so moving a function
//! between impl blocks fabricates High-severity 3-way conflicts (and false
//! Blocked verdicts). Content-first alignment keeps identity stable under
//! those moves while still tracking each definition separately.

use tree_sitter::{Language, Node};

Expand Down
Loading
Loading