Skip to content

fix(diff): emit the unresolved_target slug, not the sentence - #566

Merged
KageBinary merged 1 commit into
mainfrom
fix/diff-unresolved-target-slug
Sep 1, 2026
Merged

fix(diff): emit the unresolved_target slug, not the sentence#566
KageBinary merged 1 commit into
mainfrom
fix/diff-unresolved-target-slug

Conversation

@KageBinary

Copy link
Copy Markdown
Collaborator

docs/llm-format.md:29 says a target that does not exist is always unresolved_target, whichever command was asked. #538 unified everything except diff.ts, and nothing has touched it since.

Found while reviewing #547: twelve commands resolve a target, #547 converts ten, #559 takes locate, and diff is the twelfth that nobody has.

What was wrong

ix diff --format json on a target that does not exist emitted:

{"error": "No entity found matching \"decide.ts\"."}

The human sentence sits in the error field — where every other command puts the machine slug — and there is no message field at all. A consumer routing on error === "unresolved_target" gets an English sentence; one reading message gets nothing.

There are two such sites, not one. The symbol path (resolveEntityFull, diff.ts:514) and the file-like path (resolveFileOrEntity, diff.ts:520) each carry their own copy, so ix diff 3 5 Foo and ix diff 3 5 Foo.ts were separately broken. Both are fixed and separately tested.

The --format llm branch already emitted the right slug, but with the older No entity resolved for "…" wording. That is now the shared text, so the string is gone from diff.ts. The eight remaining copies belong to the commands #547 converts and are deliberately left alone to avoid conflicting with that PR.

unresolvedTargetMessage and unresolvedTargetRecord are split out of reportUnresolvedTarget so a command can emit the shared record without the exit code — which is the whole point of this PR being separable.

The exit code is deliberately not in this PR

Making ix diff exit non-zero is a breaking change for the plugins (CONTRIBUTING.md → CLI Standards → Exit codes). ix-cursor-plugin's ix_diff tool routes on the exit status at mcp/tools/map.ts:206; today it reads raw.summary/raw.total off the error object and reports a successful "0 changes" for a target that does not exist. Only the exit code fixes that, and it belongs in #547's queue behind the five plugin PRs.

A test pins the current exit behaviour so nobody adds it without doing that work.

Tests

Four, each mutation-checked against the code it covers:

mutation result
revert both json sites to the prose-in-error shape 2 failed / 2 passed
revert the llm wording only 1 failed / 3 passed
add process.exitCode = 1 1 failed / 3 passed

Verification

typecheck, eslint and knip clean; ix-cli 1509 passed / 2 skipped.

One thing worth flagging separately: src/cli/__tests__/watch-dedup.test.ts ("starts both the tsx source CLI and a normal built CLI child") fails when run in isolation on unmodified main — 4 of 4 attempts at 793c56b with this change stashed. It passes in the full suite, so it is order- or build-dependent rather than broken. Pre-existing and unrelated to this PR, but it should probably get an issue.

`docs/llm-format.md:29` says a target that does not exist is always
`unresolved_target`, whichever command was asked. #538 unified everything
except `diff.ts`, which nothing has touched since.

Its `--format json` branch emitted:

    {"error": "No entity found matching \"decide.ts\"."}

with the human sentence in the `error` field -- where every other command puts
the machine slug -- and no `message` field at all. A consumer routing on
`error === "unresolved_target"` gets an English sentence instead, and one
routing on `message` gets nothing.

There are two such sites, not one: the symbol path (`resolveEntityFull`) and
the file-like path (`resolveFileOrEntity`) each carry their own copy. Both were
wrong. The `--format llm` branch already emitted the right slug but with the
older `No entity resolved for "..."` wording, which is now the shared text --
that string is gone from `diff.ts`, and the eight remaining copies belong to
the commands #547 converts, deliberately left for that PR to avoid a conflict.

`unresolvedTargetMessage` and `unresolvedTargetRecord` split out of
`reportUnresolvedTarget` so a command can emit the shared record *without* the
exit code, which is the whole point here.

## The exit code is deliberately not part of this

Making `ix diff` exit non-zero is a breaking change for the plugins
(CONTRIBUTING -> CLI Standards -> Exit codes). `ix-cursor-plugin`'s `ix_diff`
tool routes on the exit status at `mcp/tools/map.ts:206`, and today it reads
`raw.summary`/`raw.total` off the error object and reports a successful
"0 changes" for a target that does not exist. Only the exit code fixes that,
and it belongs in #547's queue behind the five plugin PRs. A test pins the
current exit behaviour so nobody adds it without doing that work.

Four tests, each mutation-checked against the code it covers: reverting the
json shape fails two, reverting the llm wording fails the third, and adding the
exit code fails the fourth.

ix-cli: 1509 passed / 2 skipped. typecheck, eslint, knip clean.

Note `src/cli/__tests__/watch-dedup.test.ts` ("starts both the tsx source CLI
and a normal built CLI child") fails when run in isolation on unmodified main
-- 4 of 4 attempts at 793c56b with this change stashed. Pre-existing and
unrelated; it passes in the full suite.
@KageBinary
KageBinary merged commit a2cab21 into main Sep 1, 2026
20 checks passed
@KageBinary
KageBinary deleted the fix/diff-unresolved-target-slug branch September 1, 2026 19:39
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.

1 participant