docs: finish the whole-file-diff sweep across comments and RFCs

Codex's stale-prose pass found seven more spots still describing the
result diff as ALWAYS an applied contextual hunk, or a create/binary
overwrite as rendering "only the call-time card": the DiffCallView JSDoc
and the acp bridge diff-arm comment, the FsWriteOutcome.before and
readTextForDiff JSDoc, and three RFC lines. All now say: the result diff
is the applied change — a contextual hunk when there is a before-image,
else a whole-file diff (create / undiffable binary) — and a successful
mutation always returns the result diff so the model-facing text can't
clobber it. Regenerate the cordis catalog (source line shift).
This commit is contained in:
Tianyi Cui
2026-07-03 21:50:52 +08:00
parent d753660d66
commit e07886599c
7 changed files with 20 additions and 14 deletions

View File

@@ -547,7 +547,7 @@ async execute(exec: ToolExecution): Promise<ToolExecutionResult>
Types: [ToolDefinition](../core-data-structures/tools.md) · [ToolExecution](../core-data-structures/tools.md) · [ToolExecutionResult](../core-data-structures/tools.md)
Source: [`packages/core/tools/src/index.ts:365`](../../packages/core/tools/src/index.ts)
Source: [`packages/core/tools/src/index.ts:366`](../../packages/core/tools/src/index.ts)
## Inherited tier (cordis core + loader/hmr/timer)

View File

@@ -35,7 +35,7 @@ Per the [capability-seam split](2026-06-13-capability-seams.md), the storage bac
### 3. The bridge renders a `diff` result card
`ToolResultView` gains a `DiffResultView { card:'diff'; title?; diffs: FileDiff[] }`; the bridge's result-side `switch (view.card)` gets a `diff` arm emitting the `{type:'diff'}` `ToolCallContent` blocks (mirroring the call-side arm). An ACP `tool_call_update.content` REPLACES the call's content in an editor, so the result-time contextual hunk **supersedes** the call-time snippet — the two-update sequence (call snippet, then result hunk) matches `claude-agent-acp` exactly.
`ToolResultView` gains a `DiffResultView { card:'diff'; title?; diffs: FileDiff[] }`; the bridge's result-side `switch (view.card)` gets a `diff` arm emitting the `{type:'diff'}` `ToolCallContent` blocks (mirroring the call-side arm). An ACP `tool_call_update.content` REPLACES the call's content in an editor, so the result diff **supersedes** the call-time snippet (and keeps the model-facing result text from clobbering it) — the two-update sequence (call snippet, then result diff) matches `claude-agent-acp` exactly.
### The diff algorithm — a third-party runtime dependency over vendoring
@@ -44,7 +44,7 @@ Computing hunks-with-context is a solved problem with sharp edge cases (grouping
## Non-goals
- **Live incremental diff streaming.** The hunk is computed once, after the mutation completes; there is no per-keystroke diff.
- **Diffing a binary/non-UTF-8 overwrite.** `before` is `null` for such a file (it has no text diff basis); the write still succeeds and renders the call-time card only.
- **Diffing a binary/non-UTF-8 overwrite.** `before` is `null` for such a file (it has no text diff basis); the write still succeeds and the result renders a whole-file diff (`oldText: null`) rather than a contextual hunk.
- **Rename/move diffs.** Only content diffs of a single resolved path.
## Related

View File

@@ -65,6 +65,6 @@ interface TerminalResultView { card: 'terminal'; title?: string; output?: string
## Related
- Supersedes the deferral in [Collapse tool-owned UI presentation](../../rejected/simplification/2026-06-20-generic-tool-rendering.md) (rejected — "wait for two real tools and two real consumers, then a tagged render-intent union"). That bar is now met; this is that union.
- Extended by [Result-time applied-hunk diffs](2026-07-02-result-time-applied-hunk-diffs.md), which adds a persisted `meta` channel so write/edit emit a result-time contextual-hunk `DiffResultView` (context lines + one hunk per `replace_all` site) on top of this union's call-time diff card.
- Extended by [Result-time applied-hunk diffs](2026-07-02-result-time-applied-hunk-diffs.md), which adds a persisted `meta` channel so write/edit emit a result-time `DiffResultView` — the applied change (a contextual hunk with context lines / one per `replace_all` site, or a whole-file diff for a create) — on top of this union's call-time diff card.
- Folds `ToolTerminal` into the `terminal` views described by [ACP terminal and tool-call rendering](../feature/2026-06-18-acp-terminal-and-tool-rendering.md) (the `_meta` terminal-card convention and capability gate are unchanged; only the harness-side presentation type changes).
- The ACP SDK's `Diff` / `ToolCallContent` types back the new `diff` card.

View File

@@ -159,8 +159,9 @@ export interface TerminalCallView {
* A call that creates or modifies files, rendered as an inline diff card by a
* capable UI. Set by a tool whose call writes/edits a file (e.g. `write`,
* `edit`). The diffs are derived from the call ARGUMENTS (a create's `oldText` is
* `null`); the result-time applied-hunk diff (with context) is a separate
* {@link DiffResultView} the tool emits after `execute`.
* `null`); the tool emits a separate {@link DiffResultView} after `execute` — the
* applied change (an edit/overwrite hunk with context, or a whole-file diff for a
* create).
*/
export interface DiffCallView {
card: 'diff'

View File

@@ -386,8 +386,9 @@ export async function readForEdit(
* Best-effort read of a file's current text for a before/after diff basis, used
* by an overwrite. Returns the LF-normalized decoded content, or `null` when the
* file is binary or not valid UTF-8 — a write must succeed regardless of the
* prior bytes, so an undiffable prior file simply yields no contextual diff
* (the caller treats `null` the same as an absent file: call-time card only).
* prior bytes, so an undiffable prior file simply yields no contextual-hunk basis
* (the caller treats `null` the same as an absent file: the result renders a
* whole-file diff rather than an applied hunk).
*/
export async function readTextForDiff(absolutePath: string, signal?: AbortSignal): Promise<string | null> {
const buffer = await readFileAbortable(absolutePath, 'read', signal)

View File

@@ -103,8 +103,10 @@ export interface FsWriteOutcome {
version: FsVersion
/**
* The file's content BEFORE the write, or `null` when the file did not exist
* (a create). LF-normalized storage text (the diff basis), never a diff — a
* consumer computes the result-time contextual diff from `before`/`after`.
* (a create) or was undiffable (binary/non-UTF-8). LF-normalized storage text
* (the diff basis), never a diff — a consumer computes the result-time
* contextual diff from `before`/`after` when `before` is present, else falls
* back to a whole-file diff.
*/
before: string | null
/** The file's content AFTER the write, LF-normalized to share `before`'s diff basis. */

View File

@@ -1170,10 +1170,12 @@ function toolResultUpdate(callId: CallId, view: ToolResultView, isError: boolean
...view.title !== undefined ? { title: view.title } : {},
}
case 'diff': {
// A result-time applied-hunk diff: emit one `{ type: 'diff' }` content block
// per hunk (mirroring the call-side diff arm). `tool_call_update.content`
// REPLACES the call's content in an editor, so these hunks supersede the
// call-time whole-file snippet the pending card installed.
// A result-time diff: emit one `{ type: 'diff' }` content block per entry
// (an applied hunk for an edit/overwrite, or a whole-file diff for a
// create), mirroring the call-side diff arm. `tool_call_update.content`
// REPLACES the call's content in an editor, so this result diff supersedes
// the diff the pending card installed (and keeps the model-facing result
// text from clobbering it).
const content: AcpToolCallContent[] = view.diffs.map(d => ({ type: 'diff', path: d.path, oldText: d.oldText, newText: d.newText }))
return {
sessionUpdate: 'tool_call_update',