You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
There were several comments here that got marked dead but had some legitimate points and questions so adding them here for discussion:
laika23 asked for exactly two kinds of conflict: replace versus extend on one symbol, and the quieter one, a contract change on one side and new callers written against the old contract on the other. The first is the case the detector asserts on today. The second it only catches if the caller-side agent declares the contract it depends on, and I don't expect agents to do that reliably. Intents can declare dependencies, but no rule evaluates them yet, and the docs say so. The fix I've settled on is caller reach from a tree-sitter code graph: A declares replace on PaymentService, B declares extend on CheckoutController, the graph says CheckoutController calls PaymentService::charge, and B gets that surfaced, at a severity that can't block, because a graph edge is evidence and not a declaration. I've measured it on my own repo and haven't built it. One thing that came out of measuring: only edges the parser actually extracted are usable. The ones it had to guess were mostly name collisions on common helper names. On reads, agreed, and it falls out of the model: there is no read operation, so a reader has nothing to declare and two readers can't collide. On same-line conflicts, Foremerge never looks at lines, so it neither duplicates Git there nor replaces it.
The post-run checks in the same comment are the part I'd steal. Of the four, Foremerge does one: it runs the test command itself against the exact tree and records the exit code, and the agent's "tests pass" is stored as a claim that satisfies nothing. The second one, diff paths against an allowed list, is the one I'm embarrassed about. The ChangeSet already carries the files and symbols pulled out of the diff, with a regex, which is the wrong tool, and the intent carries the declared scopes, and nothing compares the two. That comparison is the next thing on the roadmap. Until then the declared scopes are readable per intent from the CLI or the JSON API, so a hand-written allow-list can be replaced by a jq over that today, for file scopes at least. Zero changed files with exit 0, and a summary that reads as complete over half-finished work, it does nothing about.
zane_shu wanted the smallest conflicting scope rather than a diff after both agents finish. That's what a finding is: the rule, both intents, the one scope they collide on, and the operation each declared on it, produced at publish, when there is no diff to show. The caveat is that it's only as small as the declaration. Declare domain:payments and domain:payments is the finding you get.
th0t3p asked what happens when an agent forgets to declare, or declares symbol:PaymentService while also touching api:POST /v1/charges. Forgetting entirely is bounded: nothing gets accepted without an intent behind it. Under-declaring is the real failure and it's the same gap as above, so yes, the same silent miss Git gives you, with one difference. The declaration and the diff are both on record, so the miss can be reconstructed afterwards, which is how I found the blind spots in my replay. Not caught, but auditable. On deriving scopes automatically: before the edit isn't possible, for the reason ttoinou gave upthread, so the shape is declare before, derive after, compare. The tree-sitter graph is how I intend to do the deriving properly.
mjyoke1111 asked how the detection was validated. Honestly: three things, and none of them is precision or recall on real work. There are five public fixtures with reviewer-labelled expected findings, including a negative control, run in CI on three platforms. That's a regression oracle, not a measurement. There's a ten-pair paraphrase probe, ten real conflicts and nine compatible pairs run twice, once with operations declared and once with them inferred from prose. Inference caught one of the ten and raised a false HIGH on nine of nine compatible pairs. That number is why operations are declared now, so it measured the thing I removed. And there's the 76-intent replay from my own repo, which found one real Git conflict, flagged, and a containment blind spot, and is too small for a rate. So tuning has been driven by incidents, not a corpus. The rule I've written for myself before any comparative number gets published is at least three independent-work scenarios per conflict scenario, for the reason you give: pairs in real repos are overwhelmingly independent, and behaviour on independent work is what decides whether the tool stays installed. The other honest gap: even the replay can't tell me a flagged pair was a false alarm, because a clean merge isn't proof of compatibility either way.
rowkav09 asked whether symbol identity gets normalized or stays opaque strings. It's normalized, more than I'd expected to need. Case and separators are folded, and the namespace prefix is dropped down to container::member, so App\Services\Report::render and Report::render are one scope. But only an exact match with the namespace kept can reach HIGH. A match that only exists after dropping the namespace is surfaced at lower severity, because App\Billing\Report::render against App\Admin\Report::render is the failure mode. What the replay actually hit is different: containment. Report against Report::render, or a path prefix against a file under it, don't match at any tier. The plan is a shallow fixed hierarchy for that, class contains members, file contains symbols, directory contains files, and tree-sitter rather than an LSP for resolution, as a local opt-in analyzer that adds evidence to the response while strings stay the wire format. One number that settled the identity question for me: on my own repo, class::method names were unique for about 99.6% of methods and bare method names for about 72%, so the folded key is the right identity and bare names would be the wrong one. "Polite suggestion box" is fair for everything below HIGH, and it's the design: HIGH blocks and is only asserted on two declarations meeting exactly. Everything that needed a heuristic to produce is surfaced and can't block, because the ambiguity the heuristic resolved is the ambiguity I don't trust it to resolve.
reacted with thumbs up emoji reacted with thumbs down emoji reacted with laugh emoji reacted with hooray emoji reacted with confused emoji reacted with heart emoji reacted with rocket emoji reacted with eyes emoji
Uh oh!
There was an error while loading. Please reload this page.
There were several comments here that got marked dead but had some legitimate points and questions so adding them here for discussion:
laika23 asked for exactly two kinds of conflict: replace versus extend on one symbol, and the quieter one, a contract change on one side and new callers written against the old contract on the other. The first is the case the detector asserts on today. The second it only catches if the caller-side agent declares the contract it depends on, and I don't expect agents to do that reliably. Intents can declare dependencies, but no rule evaluates them yet, and the docs say so. The fix I've settled on is caller reach from a tree-sitter code graph: A declares replace on PaymentService, B declares extend on CheckoutController, the graph says CheckoutController calls PaymentService::charge, and B gets that surfaced, at a severity that can't block, because a graph edge is evidence and not a declaration. I've measured it on my own repo and haven't built it. One thing that came out of measuring: only edges the parser actually extracted are usable. The ones it had to guess were mostly name collisions on common helper names. On reads, agreed, and it falls out of the model: there is no read operation, so a reader has nothing to declare and two readers can't collide. On same-line conflicts, Foremerge never looks at lines, so it neither duplicates Git there nor replaces it.
The post-run checks in the same comment are the part I'd steal. Of the four, Foremerge does one: it runs the test command itself against the exact tree and records the exit code, and the agent's "tests pass" is stored as a claim that satisfies nothing. The second one, diff paths against an allowed list, is the one I'm embarrassed about. The ChangeSet already carries the files and symbols pulled out of the diff, with a regex, which is the wrong tool, and the intent carries the declared scopes, and nothing compares the two. That comparison is the next thing on the roadmap. Until then the declared scopes are readable per intent from the CLI or the JSON API, so a hand-written allow-list can be replaced by a jq over that today, for file scopes at least. Zero changed files with exit 0, and a summary that reads as complete over half-finished work, it does nothing about.
zane_shu wanted the smallest conflicting scope rather than a diff after both agents finish. That's what a finding is: the rule, both intents, the one scope they collide on, and the operation each declared on it, produced at publish, when there is no diff to show. The caveat is that it's only as small as the declaration. Declare domain:payments and domain:payments is the finding you get.
th0t3p asked what happens when an agent forgets to declare, or declares symbol:PaymentService while also touching api:POST /v1/charges. Forgetting entirely is bounded: nothing gets accepted without an intent behind it. Under-declaring is the real failure and it's the same gap as above, so yes, the same silent miss Git gives you, with one difference. The declaration and the diff are both on record, so the miss can be reconstructed afterwards, which is how I found the blind spots in my replay. Not caught, but auditable. On deriving scopes automatically: before the edit isn't possible, for the reason ttoinou gave upthread, so the shape is declare before, derive after, compare. The tree-sitter graph is how I intend to do the deriving properly.
mjyoke1111 asked how the detection was validated. Honestly: three things, and none of them is precision or recall on real work. There are five public fixtures with reviewer-labelled expected findings, including a negative control, run in CI on three platforms. That's a regression oracle, not a measurement. There's a ten-pair paraphrase probe, ten real conflicts and nine compatible pairs run twice, once with operations declared and once with them inferred from prose. Inference caught one of the ten and raised a false HIGH on nine of nine compatible pairs. That number is why operations are declared now, so it measured the thing I removed. And there's the 76-intent replay from my own repo, which found one real Git conflict, flagged, and a containment blind spot, and is too small for a rate. So tuning has been driven by incidents, not a corpus. The rule I've written for myself before any comparative number gets published is at least three independent-work scenarios per conflict scenario, for the reason you give: pairs in real repos are overwhelmingly independent, and behaviour on independent work is what decides whether the tool stays installed. The other honest gap: even the replay can't tell me a flagged pair was a false alarm, because a clean merge isn't proof of compatibility either way.
rowkav09 asked whether symbol identity gets normalized or stays opaque strings. It's normalized, more than I'd expected to need. Case and separators are folded, and the namespace prefix is dropped down to container::member, so App\Services\Report::render and Report::render are one scope. But only an exact match with the namespace kept can reach HIGH. A match that only exists after dropping the namespace is surfaced at lower severity, because App\Billing\Report::render against App\Admin\Report::render is the failure mode. What the replay actually hit is different: containment. Report against Report::render, or a path prefix against a file under it, don't match at any tier. The plan is a shallow fixed hierarchy for that, class contains members, file contains symbols, directory contains files, and tree-sitter rather than an LSP for resolution, as a local opt-in analyzer that adds evidence to the response while strings stay the wire format. One number that settled the identity question for me: on my own repo, class::method names were unique for about 99.6% of methods and bare method names for about 72%, so the folded key is the right identity and bare names would be the wrong one. "Polite suggestion box" is fair for everything below HIGH, and it's the design: HIGH blocks and is only asserted on two declarations meeting exactly. Everything that needed a heuristic to produce is surfaced and can't block, because the ambiguity the heuristic resolved is the ambiguity I don't trust it to resolve.
All reactions