@oh-my-pi/pi-coding-agent 13.18.0 → 14.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +316 -1
- package/package.json +86 -24
- package/scripts/format-prompts.ts +2 -2
- package/src/autoresearch/apply-contract-to-state.ts +24 -0
- package/src/autoresearch/contract.ts +0 -44
- package/src/autoresearch/dashboard.ts +1 -2
- package/src/autoresearch/git.ts +116 -30
- package/src/autoresearch/helpers.ts +49 -0
- package/src/autoresearch/index.ts +28 -187
- package/src/autoresearch/prompt.md +26 -9
- package/src/autoresearch/state.ts +0 -6
- package/src/autoresearch/tools/init-experiment.ts +202 -117
- package/src/autoresearch/tools/log-experiment.ts +123 -178
- package/src/autoresearch/tools/run-experiment.ts +48 -10
- package/src/autoresearch/types.ts +2 -2
- package/src/capability/index.ts +4 -2
- package/src/cli/file-processor.ts +3 -3
- package/src/cli/grep-cli.ts +8 -8
- package/src/cli/grievances-cli.ts +78 -0
- package/src/cli/read-cli.ts +67 -0
- package/src/cli/setup-cli.ts +4 -4
- package/src/cli/update-cli.ts +3 -3
- package/src/cli.ts +2 -0
- package/src/commands/grep.ts +6 -1
- package/src/commands/grievances.ts +20 -0
- package/src/commands/read.ts +33 -0
- package/src/commit/agentic/agent.ts +5 -8
- package/src/commit/agentic/index.ts +22 -26
- package/src/commit/agentic/tools/analyze-file.ts +3 -3
- package/src/commit/agentic/tools/git-file-diff.ts +3 -6
- package/src/commit/agentic/tools/git-hunk.ts +3 -3
- package/src/commit/agentic/tools/git-overview.ts +6 -9
- package/src/commit/agentic/tools/index.ts +6 -8
- package/src/commit/agentic/tools/propose-commit.ts +4 -7
- package/src/commit/agentic/tools/recent-commits.ts +3 -3
- package/src/commit/agentic/tools/split-commit.ts +4 -4
- package/src/commit/agentic/validation.ts +1 -1
- package/src/commit/analysis/conventional.ts +4 -4
- package/src/commit/analysis/summary.ts +3 -3
- package/src/commit/changelog/generate.ts +4 -4
- package/src/commit/changelog/index.ts +5 -9
- package/src/commit/map-reduce/map-phase.ts +4 -4
- package/src/commit/map-reduce/reduce-phase.ts +4 -4
- package/src/commit/pipeline.ts +13 -16
- package/src/config/keybindings.ts +7 -6
- package/src/config/prompt-templates.ts +44 -226
- package/src/config/resolve-config-value.ts +4 -2
- package/src/config/settings-schema.ts +98 -2
- package/src/config/settings.ts +25 -26
- package/src/dap/client.ts +674 -0
- package/src/dap/config.ts +150 -0
- package/src/dap/defaults.json +211 -0
- package/src/dap/index.ts +4 -0
- package/src/dap/session.ts +1255 -0
- package/src/dap/types.ts +600 -0
- package/src/debug/log-viewer.ts +3 -2
- package/src/discovery/builtin.ts +1 -2
- package/src/discovery/codex.ts +2 -2
- package/src/discovery/github.ts +2 -1
- package/src/discovery/helpers.ts +2 -2
- package/src/discovery/opencode.ts +2 -2
- package/src/edit/diff.ts +818 -0
- package/src/edit/index.ts +309 -0
- package/src/edit/line-hash.ts +67 -0
- package/src/edit/modes/chunk.ts +454 -0
- package/src/{patch → edit/modes}/hashline.ts +741 -361
- package/src/{patch/applicator.ts → edit/modes/patch.ts} +420 -117
- package/src/{patch/fuzzy.ts → edit/modes/replace.ts} +519 -197
- package/src/{patch → edit}/normalize.ts +97 -76
- package/src/{patch/shared.ts → edit/renderer.ts} +181 -108
- package/src/exec/bash-executor.ts +4 -2
- package/src/exec/idle-timeout-watchdog.ts +126 -0
- package/src/exec/non-interactive-env.ts +5 -0
- package/src/extensibility/custom-commands/bundled/ci-green/index.ts +6 -18
- package/src/extensibility/custom-commands/bundled/review/index.ts +45 -43
- package/src/extensibility/custom-commands/loader.ts +1 -2
- package/src/extensibility/custom-tools/loader.ts +34 -11
- package/src/extensibility/custom-tools/types.ts +1 -1
- package/src/extensibility/extensions/loader.ts +9 -4
- package/src/extensibility/extensions/runner.ts +24 -1
- package/src/extensibility/extensions/types.ts +4 -2
- package/src/extensibility/hooks/loader.ts +5 -6
- package/src/extensibility/hooks/types.ts +2 -2
- package/src/extensibility/plugins/doctor.ts +2 -1
- package/src/extensibility/plugins/marketplace/fetcher.ts +2 -57
- package/src/extensibility/plugins/marketplace/source-resolver.ts +4 -4
- package/src/extensibility/slash-commands.ts +3 -7
- package/src/index.ts +3 -1
- package/src/internal-urls/docs-index.generated.ts +11 -11
- package/src/ipy/executor.ts +58 -17
- package/src/ipy/gateway-coordinator.ts +6 -4
- package/src/ipy/kernel.ts +45 -22
- package/src/ipy/runtime.ts +2 -2
- package/src/lsp/client.ts +7 -4
- package/src/lsp/clients/lsp-linter-client.ts +4 -4
- package/src/lsp/config.ts +2 -2
- package/src/lsp/defaults.json +688 -154
- package/src/lsp/index.ts +234 -45
- package/src/lsp/lspmux.ts +2 -2
- package/src/lsp/startup-events.ts +13 -0
- package/src/lsp/types.ts +12 -1
- package/src/lsp/utils.ts +8 -1
- package/src/main.ts +125 -47
- package/src/memories/index.ts +4 -5
- package/src/modes/acp/acp-agent.ts +563 -163
- package/src/modes/acp/acp-event-mapper.ts +9 -1
- package/src/modes/acp/acp-mode.ts +4 -2
- package/src/modes/components/agent-dashboard.ts +3 -4
- package/src/modes/components/diff.ts +6 -7
- package/src/modes/components/footer.ts +9 -29
- package/src/modes/components/hook-editor.ts +3 -3
- package/src/modes/components/hook-selector.ts +6 -1
- package/src/modes/components/read-tool-group.ts +6 -12
- package/src/modes/components/session-observer-overlay.ts +472 -0
- package/src/modes/components/settings-defs.ts +24 -0
- package/src/modes/components/status-line.ts +15 -61
- package/src/modes/components/tool-execution.ts +1 -1
- package/src/modes/components/welcome.ts +1 -1
- package/src/modes/controllers/btw-controller.ts +2 -2
- package/src/modes/controllers/command-controller.ts +4 -2
- package/src/modes/controllers/event-controller.ts +59 -2
- package/src/modes/controllers/extension-ui-controller.ts +1 -0
- package/src/modes/controllers/input-controller.ts +15 -8
- package/src/modes/controllers/selector-controller.ts +26 -0
- package/src/modes/index.ts +20 -2
- package/src/modes/interactive-mode.ts +278 -69
- package/src/modes/rpc/host-tools.ts +186 -0
- package/src/modes/rpc/rpc-client.ts +178 -13
- package/src/modes/rpc/rpc-mode.ts +73 -3
- package/src/modes/rpc/rpc-types.ts +53 -1
- package/src/modes/session-observer-registry.ts +146 -0
- package/src/modes/shared.ts +0 -42
- package/src/modes/theme/theme.ts +80 -8
- package/src/modes/types.ts +4 -2
- package/src/modes/utils/keybinding-matchers.ts +9 -0
- package/src/prompts/system/custom-system-prompt.md +5 -0
- package/src/prompts/system/system-prompt.md +8 -1
- package/src/prompts/tools/chunk-edit.md +219 -0
- package/src/prompts/tools/debug.md +43 -0
- package/src/prompts/tools/grep.md +3 -0
- package/src/prompts/tools/lsp.md +5 -5
- package/src/prompts/tools/read-chunk.md +17 -0
- package/src/prompts/tools/read.md +19 -5
- package/src/sdk.ts +216 -165
- package/src/secrets/index.ts +1 -1
- package/src/secrets/obfuscator.ts +25 -17
- package/src/session/agent-session.ts +381 -286
- package/src/session/agent-storage.ts +12 -12
- package/src/session/compaction/branch-summarization.ts +3 -3
- package/src/session/compaction/compaction.ts +5 -6
- package/src/session/compaction/utils.ts +3 -3
- package/src/session/history-storage.ts +62 -19
- package/src/session/messages.ts +3 -3
- package/src/session/session-dump-format.ts +203 -0
- package/src/session/session-manager.ts +15 -5
- package/src/session/session-storage.ts +4 -2
- package/src/session/streaming-output.ts +1 -1
- package/src/session/tool-choice-queue.ts +213 -0
- package/src/slash-commands/builtin-registry.ts +56 -8
- package/src/ssh/connection-manager.ts +2 -2
- package/src/ssh/sshfs-mount.ts +5 -5
- package/src/stt/downloader.ts +4 -4
- package/src/stt/recorder.ts +4 -4
- package/src/stt/transcriber.ts +2 -2
- package/src/system-prompt.ts +25 -13
- package/src/task/agents.ts +5 -6
- package/src/task/commands.ts +2 -5
- package/src/task/executor.ts +32 -4
- package/src/task/index.ts +91 -82
- package/src/task/template.ts +2 -2
- package/src/task/types.ts +25 -0
- package/src/task/worktree.ts +131 -149
- package/src/tools/ask.ts +2 -3
- package/src/tools/ast-edit.ts +7 -7
- package/src/tools/ast-grep.ts +7 -7
- package/src/tools/auto-generated-guard.ts +36 -41
- package/src/tools/await-tool.ts +2 -2
- package/src/tools/bash.ts +5 -23
- package/src/tools/browser.ts +4 -5
- package/src/tools/calculator.ts +2 -3
- package/src/tools/cancel-job.ts +2 -2
- package/src/tools/checkpoint.ts +3 -3
- package/src/tools/debug.ts +1007 -0
- package/src/tools/exit-plan-mode.ts +3 -3
- package/src/tools/fetch.ts +67 -3
- package/src/tools/find.ts +4 -5
- package/src/tools/fs-cache-invalidation.ts +5 -0
- package/src/tools/gemini-image.ts +13 -5
- package/src/tools/gh.ts +130 -308
- package/src/tools/grep.ts +57 -9
- package/src/tools/index.ts +44 -22
- package/src/tools/inspect-image.ts +4 -4
- package/src/tools/output-meta.ts +1 -1
- package/src/tools/python.ts +19 -6
- package/src/tools/read.ts +211 -146
- package/src/tools/render-mermaid.ts +2 -3
- package/src/tools/render-utils.ts +20 -6
- package/src/tools/renderers.ts +3 -1
- package/src/tools/report-tool-issue.ts +80 -0
- package/src/tools/resolve.ts +70 -39
- package/src/tools/search-tool-bm25.ts +2 -2
- package/src/tools/ssh.ts +2 -2
- package/src/tools/todo-write.ts +2 -2
- package/src/tools/tool-timeouts.ts +1 -0
- package/src/tools/write.ts +5 -6
- package/src/tui/tree-list.ts +3 -1
- package/src/utils/clipboard.ts +80 -0
- package/src/utils/commit-message-generator.ts +2 -3
- package/src/utils/edit-mode.ts +49 -0
- package/src/utils/external-editor.ts +11 -5
- package/src/utils/file-display-mode.ts +6 -5
- package/src/utils/file-mentions.ts +8 -7
- package/src/utils/git.ts +1400 -0
- package/src/utils/image-loading.ts +98 -0
- package/src/utils/title-generator.ts +2 -3
- package/src/utils/tools-manager.ts +6 -6
- package/src/web/scrapers/choosealicense.ts +1 -1
- package/src/web/search/index.ts +3 -3
- package/src/web/search/render.ts +6 -4
- package/src/autoresearch/command-initialize.md +0 -34
- package/src/commit/git/errors.ts +0 -9
- package/src/commit/git/index.ts +0 -210
- package/src/commit/git/operations.ts +0 -54
- package/src/patch/diff.ts +0 -433
- package/src/patch/index.ts +0 -888
- package/src/patch/parser.ts +0 -532
- package/src/patch/types.ts +0 -292
- package/src/prompts/agents/oracle.md +0 -77
- package/src/tools/gh-cli.ts +0 -125
- package/src/tools/pending-action.ts +0 -49
- package/src/utils/child-process.ts +0 -88
- package/src/utils/frontmatter.ts +0 -117
- package/src/utils/image-input.ts +0 -274
- package/src/utils/mime.ts +0 -53
- package/src/utils/prompt-format.ts +0 -170
|
@@ -7,8 +7,33 @@
|
|
|
7
7
|
|
|
8
8
|
import * as fs from "node:fs";
|
|
9
9
|
import * as path from "node:path";
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
10
|
+
import type { AgentToolResult } from "@oh-my-pi/pi-agent-core";
|
|
11
|
+
import { StringEnum } from "@oh-my-pi/pi-ai";
|
|
12
|
+
import { isEnoent } from "@oh-my-pi/pi-utils";
|
|
13
|
+
import { type Static, Type } from "@sinclair/typebox";
|
|
14
|
+
import {
|
|
15
|
+
type FileDiagnosticsResult,
|
|
16
|
+
flushLspWritethroughBatch,
|
|
17
|
+
type WritethroughCallback,
|
|
18
|
+
type WritethroughDeferredHandle,
|
|
19
|
+
} from "../../lsp";
|
|
20
|
+
import type { ToolSession } from "../../tools";
|
|
21
|
+
import { assertEditableFile } from "../../tools/auto-generated-guard";
|
|
22
|
+
import {
|
|
23
|
+
invalidateFsScanAfterDelete,
|
|
24
|
+
invalidateFsScanAfterRename,
|
|
25
|
+
invalidateFsScanAfterWrite,
|
|
26
|
+
} from "../../tools/fs-cache-invalidation";
|
|
27
|
+
import { outputMeta } from "../../tools/output-meta";
|
|
28
|
+
import { resolveToCwd } from "../../tools/path-utils";
|
|
29
|
+
import { enforcePlanModeWrite, resolvePlanPath } from "../../tools/plan-mode-guard";
|
|
30
|
+
import {
|
|
31
|
+
ApplyPatchError,
|
|
32
|
+
type DiffHunk,
|
|
33
|
+
generateUnifiedDiffString,
|
|
34
|
+
normalizeCreateContent,
|
|
35
|
+
parseDiffHunks,
|
|
36
|
+
} from "../diff";
|
|
12
37
|
import {
|
|
13
38
|
adjustIndentation,
|
|
14
39
|
convertLeadingTabsToSpaces,
|
|
@@ -18,18 +43,56 @@ import {
|
|
|
18
43
|
normalizeToLF,
|
|
19
44
|
restoreLineEndings,
|
|
20
45
|
stripBom,
|
|
21
|
-
} from "
|
|
22
|
-
import {
|
|
23
|
-
import
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
} from "./
|
|
32
|
-
|
|
46
|
+
} from "../normalize";
|
|
47
|
+
import type { EditToolDetails, LspBatchRequest } from "../renderer";
|
|
48
|
+
import {
|
|
49
|
+
type ContextLineResult,
|
|
50
|
+
DEFAULT_FUZZY_THRESHOLD,
|
|
51
|
+
findClosestSequenceMatch,
|
|
52
|
+
findContextLine,
|
|
53
|
+
findMatch,
|
|
54
|
+
type SequenceSearchResult,
|
|
55
|
+
seekSequence,
|
|
56
|
+
} from "./replace";
|
|
57
|
+
|
|
58
|
+
export type Operation = "create" | "delete" | "update";
|
|
59
|
+
|
|
60
|
+
export interface PatchInput {
|
|
61
|
+
path: string;
|
|
62
|
+
op: Operation;
|
|
63
|
+
rename?: string;
|
|
64
|
+
diff?: string;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export interface FileSystem {
|
|
68
|
+
exists(path: string): Promise<boolean>;
|
|
69
|
+
read(path: string): Promise<string>;
|
|
70
|
+
readBinary?: (path: string) => Promise<Uint8Array>;
|
|
71
|
+
write(path: string, content: string): Promise<void>;
|
|
72
|
+
delete(path: string): Promise<void>;
|
|
73
|
+
mkdir(path: string): Promise<void>;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
interface FileChange {
|
|
77
|
+
type: Operation;
|
|
78
|
+
path: string;
|
|
79
|
+
newPath?: string;
|
|
80
|
+
oldContent?: string;
|
|
81
|
+
newContent?: string;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export interface ApplyPatchResult {
|
|
85
|
+
change: FileChange;
|
|
86
|
+
warnings?: string[];
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export interface ApplyPatchOptions {
|
|
90
|
+
cwd: string;
|
|
91
|
+
dryRun?: boolean;
|
|
92
|
+
fuzzyThreshold?: number;
|
|
93
|
+
allowFuzzy?: boolean;
|
|
94
|
+
fs?: FileSystem;
|
|
95
|
+
}
|
|
33
96
|
|
|
34
97
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
35
98
|
// Default File System
|
|
@@ -38,14 +101,13 @@ import { ApplyPatchError, normalizePatchInput } from "./types";
|
|
|
38
101
|
/** Default filesystem implementation using Bun APIs */
|
|
39
102
|
export const defaultFileSystem: FileSystem = {
|
|
40
103
|
async exists(path: string): Promise<boolean> {
|
|
41
|
-
return
|
|
104
|
+
return Bun.file(path).exists();
|
|
42
105
|
},
|
|
43
106
|
async read(path: string): Promise<string> {
|
|
44
107
|
return Bun.file(path).text();
|
|
45
108
|
},
|
|
46
109
|
async readBinary(path: string): Promise<Uint8Array> {
|
|
47
|
-
|
|
48
|
-
return new Uint8Array(buffer);
|
|
110
|
+
return fs.promises.readFile(path);
|
|
49
111
|
},
|
|
50
112
|
async write(path: string, content: string): Promise<void> {
|
|
51
113
|
await Bun.write(path, content);
|
|
@@ -76,6 +138,71 @@ interface HunkVariant {
|
|
|
76
138
|
kind: HunkVariantKind;
|
|
77
139
|
}
|
|
78
140
|
|
|
141
|
+
function isBlankLine(line: string): boolean {
|
|
142
|
+
return line.trim().length === 0;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function areEqualLines(left: string[], right: string[]): boolean {
|
|
146
|
+
if (left.length !== right.length) return false;
|
|
147
|
+
for (let i = 0; i < left.length; i++) {
|
|
148
|
+
if (left[i] !== right[i]) return false;
|
|
149
|
+
}
|
|
150
|
+
return true;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function areEqualTrimmedLines(left: string[], right: string[]): boolean {
|
|
154
|
+
if (left.length !== right.length) return false;
|
|
155
|
+
for (let i = 0; i < left.length; i++) {
|
|
156
|
+
if (left[i].trim() !== right[i].trim()) return false;
|
|
157
|
+
}
|
|
158
|
+
return true;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function getIndentChar(lines: string[]): string {
|
|
162
|
+
for (const line of lines) {
|
|
163
|
+
const ws = getLeadingWhitespace(line);
|
|
164
|
+
if (ws.length > 0) return ws[0];
|
|
165
|
+
}
|
|
166
|
+
return " ";
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function collectIndentDeltas(oldLines: string[], actualLines: string[]): number[] {
|
|
170
|
+
const deltas: number[] = [];
|
|
171
|
+
const lineCount = Math.min(oldLines.length, actualLines.length);
|
|
172
|
+
for (let i = 0; i < lineCount; i++) {
|
|
173
|
+
const oldLine = oldLines[i];
|
|
174
|
+
const actualLine = actualLines[i];
|
|
175
|
+
if (isBlankLine(oldLine) || isBlankLine(actualLine)) continue;
|
|
176
|
+
deltas.push(countLeadingWhitespace(actualLine) - countLeadingWhitespace(oldLine));
|
|
177
|
+
}
|
|
178
|
+
return deltas;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
function applyIndentDelta(lines: string[], delta: number, indentChar: string): string[] {
|
|
182
|
+
return lines.map(line => {
|
|
183
|
+
if (isBlankLine(line)) return line;
|
|
184
|
+
if (delta > 0) return indentChar.repeat(delta) + line;
|
|
185
|
+
const toRemove = Math.min(-delta, countLeadingWhitespace(line));
|
|
186
|
+
return line.slice(toRemove);
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
function canConvertTabsToSpaces(oldLines: string[], actualLines: string[], spacesPerTab: number): boolean {
|
|
191
|
+
const lineCount = Math.min(oldLines.length, actualLines.length);
|
|
192
|
+
for (let i = 0; i < lineCount; i++) {
|
|
193
|
+
const oldLine = oldLines[i];
|
|
194
|
+
const actualLine = actualLines[i];
|
|
195
|
+
if (isBlankLine(oldLine) || isBlankLine(actualLine)) continue;
|
|
196
|
+
const oldIndent = getLeadingWhitespace(oldLine);
|
|
197
|
+
const actualIndent = getLeadingWhitespace(actualLine);
|
|
198
|
+
if (oldIndent.length === 0) continue;
|
|
199
|
+
if (actualIndent.length !== oldIndent.length * spacesPerTab) {
|
|
200
|
+
return false;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
return true;
|
|
204
|
+
}
|
|
205
|
+
|
|
79
206
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
80
207
|
// Replacement Computation
|
|
81
208
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
@@ -87,42 +214,17 @@ function adjustLinesIndentation(patternLines: string[], actualLines: string[], n
|
|
|
87
214
|
}
|
|
88
215
|
|
|
89
216
|
// If pattern already matches actual exactly (including indentation), preserve agent's intended changes
|
|
90
|
-
if (patternLines
|
|
91
|
-
|
|
92
|
-
for (let i = 0; i < patternLines.length; i++) {
|
|
93
|
-
if (patternLines[i] !== actualLines[i]) {
|
|
94
|
-
exactMatch = false;
|
|
95
|
-
break;
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
if (exactMatch) {
|
|
99
|
-
return newLines;
|
|
100
|
-
}
|
|
217
|
+
if (areEqualLines(patternLines, actualLines)) {
|
|
218
|
+
return newLines;
|
|
101
219
|
}
|
|
102
220
|
|
|
103
221
|
// If the patch is purely an indentation change (same trimmed content), apply exactly as specified
|
|
104
|
-
if (patternLines
|
|
105
|
-
|
|
106
|
-
for (let i = 0; i < patternLines.length; i++) {
|
|
107
|
-
if (patternLines[i].trim() !== newLines[i].trim()) {
|
|
108
|
-
indentationOnly = false;
|
|
109
|
-
break;
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
if (indentationOnly) {
|
|
113
|
-
return newLines;
|
|
114
|
-
}
|
|
222
|
+
if (areEqualTrimmedLines(patternLines, newLines)) {
|
|
223
|
+
return newLines;
|
|
115
224
|
}
|
|
116
225
|
|
|
117
226
|
// Detect indent character from actual content
|
|
118
|
-
|
|
119
|
-
for (const line of actualLines) {
|
|
120
|
-
const ws = getLeadingWhitespace(line);
|
|
121
|
-
if (ws.length > 0) {
|
|
122
|
-
indentChar = ws[0];
|
|
123
|
-
break;
|
|
124
|
-
}
|
|
125
|
-
}
|
|
227
|
+
const indentChar = getIndentChar(actualLines);
|
|
126
228
|
|
|
127
229
|
let patternTabOnly = true;
|
|
128
230
|
let actualSpaceOnly = true;
|
|
@@ -171,9 +273,8 @@ function adjustLinesIndentation(patternLines: string[], actualLines: string[], n
|
|
|
171
273
|
}
|
|
172
274
|
}
|
|
173
275
|
|
|
174
|
-
if (consistent && ratio) {
|
|
175
|
-
|
|
176
|
-
return converted;
|
|
276
|
+
if (consistent && ratio && canConvertTabsToSpaces(patternLines, actualLines, ratio)) {
|
|
277
|
+
return convertLeadingTabsToSpaces(newLines.join("\n"), ratio).split("\n");
|
|
177
278
|
}
|
|
178
279
|
}
|
|
179
280
|
|
|
@@ -282,20 +383,8 @@ function adjustLinesIndentation(patternLines: string[], actualLines: string[], n
|
|
|
282
383
|
patternMin = 0;
|
|
283
384
|
}
|
|
284
385
|
|
|
285
|
-
|
|
286
|
-
const deltas
|
|
287
|
-
for (let i = 0; i < Math.min(patternLines.length, actualLines.length); i++) {
|
|
288
|
-
const patternLine = patternLines[i];
|
|
289
|
-
const actualLine = actualLines[i];
|
|
290
|
-
if (patternLine.trim().length === 0 || actualLine.trim().length === 0) continue;
|
|
291
|
-
const pIndent = countLeadingWhitespace(patternLine);
|
|
292
|
-
const aIndent = countLeadingWhitespace(actualLine);
|
|
293
|
-
deltas.push(aIndent - pIndent);
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
if (deltas.length > 0 && deltas.every(value => value === deltas[0])) {
|
|
297
|
-
delta = deltas[0];
|
|
298
|
-
}
|
|
386
|
+
const deltas = collectIndentDeltas(patternLines, actualLines);
|
|
387
|
+
const delta = deltas.length > 0 && deltas.every(value => value === deltas[0]) ? deltas[0] : undefined;
|
|
299
388
|
|
|
300
389
|
// Track which actual lines we've used to handle duplicate content correctly
|
|
301
390
|
const usedActualLines = new Map<string, number>(); // trimmed content -> count used
|
|
@@ -328,11 +417,7 @@ function adjustLinesIndentation(patternLines: string[], actualLines: string[], n
|
|
|
328
417
|
if (delta && delta !== 0) {
|
|
329
418
|
const newIndent = countLeadingWhitespace(newLine);
|
|
330
419
|
if (newIndent === patternMin) {
|
|
331
|
-
|
|
332
|
-
return indentChar.repeat(delta) + newLine;
|
|
333
|
-
}
|
|
334
|
-
const toRemove = Math.min(-delta, newIndent);
|
|
335
|
-
return newLine.slice(toRemove);
|
|
420
|
+
return applyIndentDelta([newLine], delta, indentChar)[0];
|
|
336
421
|
}
|
|
337
422
|
}
|
|
338
423
|
return newLine;
|
|
@@ -737,7 +822,7 @@ function findSequenceWithHint(
|
|
|
737
822
|
hintIndex: number | undefined,
|
|
738
823
|
eof: boolean,
|
|
739
824
|
allowFuzzy: boolean,
|
|
740
|
-
):
|
|
825
|
+
): SequenceSearchResult {
|
|
741
826
|
// Prefer content-based search starting from currentIndex
|
|
742
827
|
const primaryResult = seekSequence(lines, pattern, currentIndex, eof, { allowFuzzy });
|
|
743
828
|
if (
|
|
@@ -910,6 +995,17 @@ function applyTrailingNewlinePolicy(content: string, hadFinalNewline: boolean):
|
|
|
910
995
|
return content.replace(/\n+$/u, "");
|
|
911
996
|
}
|
|
912
997
|
|
|
998
|
+
async function readExistingPatchFile(fileSystem: FileSystem, absolutePath: string, path: string): Promise<string> {
|
|
999
|
+
try {
|
|
1000
|
+
return await fileSystem.read(absolutePath);
|
|
1001
|
+
} catch (error) {
|
|
1002
|
+
if (isEnoent(error)) {
|
|
1003
|
+
throw new ApplyPatchError(`File not found: ${path}`);
|
|
1004
|
+
}
|
|
1005
|
+
throw error;
|
|
1006
|
+
}
|
|
1007
|
+
}
|
|
1008
|
+
|
|
913
1009
|
/**
|
|
914
1010
|
* Compute replacements needed to transform originalLines using the diff hunks.
|
|
915
1011
|
*/
|
|
@@ -937,6 +1033,7 @@ function computeReplacements(
|
|
|
937
1033
|
}
|
|
938
1034
|
const lineHint = hunk.oldStartLine;
|
|
939
1035
|
const allowAggressiveFallbacks = hunk.changeContext !== undefined || lineHint !== undefined || hunk.isEndOfFile;
|
|
1036
|
+
const fallbackVariants = filterFallbackVariants(buildFallbackVariants(hunk), allowAggressiveFallbacks);
|
|
940
1037
|
if (lineHint !== undefined && hunk.changeContext === undefined && !hunk.hasContextLines) {
|
|
941
1038
|
lineIndex = Math.max(0, Math.min(lineHint - 1, originalLines.length - 1));
|
|
942
1039
|
}
|
|
@@ -1059,7 +1156,7 @@ function computeReplacements(
|
|
|
1059
1156
|
}
|
|
1060
1157
|
|
|
1061
1158
|
if (searchResult.index === undefined || (searchResult.matchCount ?? 0) > 1) {
|
|
1062
|
-
for (const variant of
|
|
1159
|
+
for (const variant of fallbackVariants) {
|
|
1063
1160
|
if (variant.oldLines.length === 0) continue;
|
|
1064
1161
|
const variantResult = findSequenceWithHint(
|
|
1065
1162
|
originalLines,
|
|
@@ -1079,7 +1176,7 @@ function computeReplacements(
|
|
|
1079
1176
|
}
|
|
1080
1177
|
|
|
1081
1178
|
if (searchResult.index === undefined && contextIndex !== undefined) {
|
|
1082
|
-
for (const variant of
|
|
1179
|
+
for (const variant of fallbackVariants) {
|
|
1083
1180
|
if (variant.oldLines.length !== 1 || variant.newLines.length !== 1) continue;
|
|
1084
1181
|
const removedLine = variant.oldLines[0];
|
|
1085
1182
|
const hasSharedDuplicate = hunk.newLines.some(line => line.trim() === removedLine.trim());
|
|
@@ -1178,23 +1275,8 @@ function computeReplacements(
|
|
|
1178
1275
|
if (hunk.changeContext === undefined && !hunk.hasContextLines && !hunk.isEndOfFile && lineHint === undefined) {
|
|
1179
1276
|
const secondMatch = seekSequence(originalLines, pattern, found + 1, false, { allowFuzzy });
|
|
1180
1277
|
if (secondMatch.index !== undefined) {
|
|
1181
|
-
|
|
1182
|
-
const
|
|
1183
|
-
const contextLines = 2;
|
|
1184
|
-
const maxLineLength = 80;
|
|
1185
|
-
const start = Math.max(0, startIdx - contextLines);
|
|
1186
|
-
const end = Math.min(originalLines.length, startIdx + contextLines + 1);
|
|
1187
|
-
const lines = originalLines.slice(start, end);
|
|
1188
|
-
return lines
|
|
1189
|
-
.map((line, i) => {
|
|
1190
|
-
const num = start + i + 1;
|
|
1191
|
-
const truncated = line.length > maxLineLength ? `${line.slice(0, maxLineLength - 1)}…` : line;
|
|
1192
|
-
return ` ${num} | ${truncated}`;
|
|
1193
|
-
})
|
|
1194
|
-
.join("\n");
|
|
1195
|
-
};
|
|
1196
|
-
const preview1 = formatPreview(found);
|
|
1197
|
-
const preview2 = formatPreview(secondMatch.index);
|
|
1278
|
+
const preview1 = formatSequenceMatchPreview(originalLines, found);
|
|
1279
|
+
const preview2 = formatSequenceMatchPreview(originalLines, secondMatch.index);
|
|
1198
1280
|
throw new ApplyPatchError(
|
|
1199
1281
|
`Found 2 occurrences in ${path}:\n\n${preview1}\n\n${preview2}\n\n` +
|
|
1200
1282
|
`Add more context lines to disambiguate.`,
|
|
@@ -1317,15 +1399,7 @@ function applyHunksToContent(
|
|
|
1317
1399
|
}
|
|
1318
1400
|
|
|
1319
1401
|
const content = newLines.join("\n");
|
|
1320
|
-
|
|
1321
|
-
// Preserve original trailing newline behavior
|
|
1322
|
-
if (hadFinalNewline && !content.endsWith("\n")) {
|
|
1323
|
-
return { content: `${content}\n`, warnings };
|
|
1324
|
-
}
|
|
1325
|
-
if (!hadFinalNewline && content.endsWith("\n")) {
|
|
1326
|
-
return { content: content.slice(0, -1), warnings };
|
|
1327
|
-
}
|
|
1328
|
-
return { content, warnings };
|
|
1402
|
+
return { content: applyTrailingNewlinePolicy(content, hadFinalNewline), warnings };
|
|
1329
1403
|
}
|
|
1330
1404
|
|
|
1331
1405
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
@@ -1336,18 +1410,14 @@ function applyHunksToContent(
|
|
|
1336
1410
|
* Apply a patch operation to the filesystem.
|
|
1337
1411
|
*/
|
|
1338
1412
|
export async function applyPatch(input: PatchInput, options: ApplyPatchOptions): Promise<ApplyPatchResult> {
|
|
1339
|
-
|
|
1340
|
-
return applyNormalizedPatch(normalized, options);
|
|
1413
|
+
return applyNormalizedPatch(input, options);
|
|
1341
1414
|
}
|
|
1342
1415
|
|
|
1343
1416
|
/**
|
|
1344
1417
|
* Apply a normalized patch operation to the filesystem.
|
|
1345
1418
|
* @internal
|
|
1346
1419
|
*/
|
|
1347
|
-
async function applyNormalizedPatch(
|
|
1348
|
-
input: NormalizedPatchInput,
|
|
1349
|
-
options: ApplyPatchOptions,
|
|
1350
|
-
): Promise<ApplyPatchResult> {
|
|
1420
|
+
async function applyNormalizedPatch(input: PatchInput, options: ApplyPatchOptions): Promise<ApplyPatchResult> {
|
|
1351
1421
|
const {
|
|
1352
1422
|
cwd,
|
|
1353
1423
|
dryRun = false,
|
|
@@ -1358,6 +1428,7 @@ async function applyNormalizedPatch(
|
|
|
1358
1428
|
|
|
1359
1429
|
const resolvePath = (p: string): string => resolveToCwd(p, cwd);
|
|
1360
1430
|
const absolutePath = resolvePath(input.path);
|
|
1431
|
+
const op = input.op ?? "update";
|
|
1361
1432
|
|
|
1362
1433
|
if (input.rename) {
|
|
1363
1434
|
const destPath = resolvePath(input.rename);
|
|
@@ -1367,7 +1438,7 @@ async function applyNormalizedPatch(
|
|
|
1367
1438
|
}
|
|
1368
1439
|
|
|
1369
1440
|
// Handle CREATE operation
|
|
1370
|
-
if (
|
|
1441
|
+
if (op === "create") {
|
|
1371
1442
|
if (!input.diff) {
|
|
1372
1443
|
throw new ApplyPatchError("Create operation requires diff (file content)");
|
|
1373
1444
|
}
|
|
@@ -1393,12 +1464,8 @@ async function applyNormalizedPatch(
|
|
|
1393
1464
|
}
|
|
1394
1465
|
|
|
1395
1466
|
// Handle DELETE operation
|
|
1396
|
-
if (
|
|
1397
|
-
|
|
1398
|
-
throw new ApplyPatchError(`File not found: ${input.path}`);
|
|
1399
|
-
}
|
|
1400
|
-
|
|
1401
|
-
const oldContent = await fs.read(absolutePath);
|
|
1467
|
+
if (op === "delete") {
|
|
1468
|
+
const oldContent = await readExistingPatchFile(fs, absolutePath, input.path);
|
|
1402
1469
|
if (!dryRun) {
|
|
1403
1470
|
await fs.delete(absolutePath);
|
|
1404
1471
|
}
|
|
@@ -1417,11 +1484,7 @@ async function applyNormalizedPatch(
|
|
|
1417
1484
|
throw new ApplyPatchError("Update operation requires diff (hunks)");
|
|
1418
1485
|
}
|
|
1419
1486
|
|
|
1420
|
-
|
|
1421
|
-
throw new ApplyPatchError(`File not found: ${input.path}`);
|
|
1422
|
-
}
|
|
1423
|
-
|
|
1424
|
-
const originalContent = await fs.read(absolutePath);
|
|
1487
|
+
const originalContent = await readExistingPatchFile(fs, absolutePath, input.path);
|
|
1425
1488
|
const { bom: bomFromText, text: strippedContent } = stripBom(originalContent);
|
|
1426
1489
|
let bom = bomFromText;
|
|
1427
1490
|
if (!bom && fs.readBinary) {
|
|
@@ -1432,7 +1495,7 @@ async function applyNormalizedPatch(
|
|
|
1432
1495
|
}
|
|
1433
1496
|
const lineEnding = detectLineEnding(strippedContent);
|
|
1434
1497
|
const normalizedContent = normalizeToLF(strippedContent);
|
|
1435
|
-
const hunks =
|
|
1498
|
+
const hunks = parseDiffHunks(input.diff);
|
|
1436
1499
|
|
|
1437
1500
|
if (hunks.length === 0) {
|
|
1438
1501
|
throw new ApplyPatchError("Diff contains no hunks");
|
|
@@ -1480,3 +1543,243 @@ async function applyNormalizedPatch(
|
|
|
1480
1543
|
export async function previewPatch(input: PatchInput, options: ApplyPatchOptions): Promise<ApplyPatchResult> {
|
|
1481
1544
|
return applyPatch(input, { ...options, dryRun: true });
|
|
1482
1545
|
}
|
|
1546
|
+
|
|
1547
|
+
export async function computePatchDiff(
|
|
1548
|
+
input: PatchInput,
|
|
1549
|
+
cwd: string,
|
|
1550
|
+
options?: { fuzzyThreshold?: number; allowFuzzy?: boolean },
|
|
1551
|
+
): Promise<
|
|
1552
|
+
| {
|
|
1553
|
+
diff: string;
|
|
1554
|
+
firstChangedLine: number | undefined;
|
|
1555
|
+
}
|
|
1556
|
+
| {
|
|
1557
|
+
error: string;
|
|
1558
|
+
}
|
|
1559
|
+
> {
|
|
1560
|
+
try {
|
|
1561
|
+
const result = await previewPatch(input, {
|
|
1562
|
+
cwd,
|
|
1563
|
+
fuzzyThreshold: options?.fuzzyThreshold,
|
|
1564
|
+
allowFuzzy: options?.allowFuzzy,
|
|
1565
|
+
});
|
|
1566
|
+
const oldContent = result.change.oldContent ?? "";
|
|
1567
|
+
const newContent = result.change.newContent ?? "";
|
|
1568
|
+
const normalizedOld = normalizeToLF(stripBom(oldContent).text);
|
|
1569
|
+
const normalizedNew = normalizeToLF(stripBom(newContent).text);
|
|
1570
|
+
if (!normalizedOld && !normalizedNew) {
|
|
1571
|
+
return { diff: "", firstChangedLine: undefined };
|
|
1572
|
+
}
|
|
1573
|
+
return generateUnifiedDiffString(normalizedOld, normalizedNew);
|
|
1574
|
+
} catch (err) {
|
|
1575
|
+
return { error: err instanceof Error ? err.message : String(err) };
|
|
1576
|
+
}
|
|
1577
|
+
}
|
|
1578
|
+
|
|
1579
|
+
export const patchEditSchema = Type.Object({
|
|
1580
|
+
path: Type.String({ description: "File path" }),
|
|
1581
|
+
op: Type.Optional(
|
|
1582
|
+
StringEnum(["create", "delete", "update"], {
|
|
1583
|
+
description: "Operation (default: update)",
|
|
1584
|
+
}),
|
|
1585
|
+
),
|
|
1586
|
+
rename: Type.Optional(Type.String({ description: "New path for move" })),
|
|
1587
|
+
diff: Type.Optional(Type.String({ description: "Diff hunks (update) or full content (create)" })),
|
|
1588
|
+
});
|
|
1589
|
+
|
|
1590
|
+
export type PatchParams = Static<typeof patchEditSchema>;
|
|
1591
|
+
|
|
1592
|
+
interface ExecutePatchModeOptions {
|
|
1593
|
+
session: ToolSession;
|
|
1594
|
+
params: PatchParams;
|
|
1595
|
+
signal?: AbortSignal;
|
|
1596
|
+
batchRequest?: LspBatchRequest;
|
|
1597
|
+
allowFuzzy: boolean;
|
|
1598
|
+
fuzzyThreshold: number;
|
|
1599
|
+
writethrough: WritethroughCallback;
|
|
1600
|
+
beginDeferredDiagnosticsForPath: (path: string) => WritethroughDeferredHandle;
|
|
1601
|
+
}
|
|
1602
|
+
|
|
1603
|
+
export function isPatchParams(params: unknown): params is PatchParams {
|
|
1604
|
+
if (typeof params !== "object" || params === null || !("path" in params)) {
|
|
1605
|
+
return false;
|
|
1606
|
+
}
|
|
1607
|
+
return !("old_text" in params) && !("new_text" in params) && !("edits" in params);
|
|
1608
|
+
}
|
|
1609
|
+
|
|
1610
|
+
class LspFileSystem implements FileSystem {
|
|
1611
|
+
#lastDiagnostics: FileDiagnosticsResult | undefined;
|
|
1612
|
+
#fileCache: Record<string, Bun.BunFile> = {};
|
|
1613
|
+
|
|
1614
|
+
constructor(
|
|
1615
|
+
private readonly writethrough: WritethroughCallback,
|
|
1616
|
+
private readonly signal?: AbortSignal,
|
|
1617
|
+
private readonly batchRequest?: LspBatchRequest,
|
|
1618
|
+
private readonly deferredForPath?: (path: string) => WritethroughDeferredHandle,
|
|
1619
|
+
) {}
|
|
1620
|
+
|
|
1621
|
+
#getFile(path: string): Bun.BunFile {
|
|
1622
|
+
if (this.#fileCache[path]) {
|
|
1623
|
+
return this.#fileCache[path];
|
|
1624
|
+
}
|
|
1625
|
+
const file = Bun.file(path);
|
|
1626
|
+
this.#fileCache[path] = file;
|
|
1627
|
+
return file;
|
|
1628
|
+
}
|
|
1629
|
+
|
|
1630
|
+
async exists(path: string): Promise<boolean> {
|
|
1631
|
+
return this.#getFile(path).exists();
|
|
1632
|
+
}
|
|
1633
|
+
|
|
1634
|
+
async read(path: string): Promise<string> {
|
|
1635
|
+
return this.#getFile(path).text();
|
|
1636
|
+
}
|
|
1637
|
+
|
|
1638
|
+
async readBinary(path: string): Promise<Uint8Array> {
|
|
1639
|
+
const bytes = await fs.promises.readFile(path);
|
|
1640
|
+
return bytes;
|
|
1641
|
+
}
|
|
1642
|
+
|
|
1643
|
+
async write(path: string, content: string): Promise<void> {
|
|
1644
|
+
const file = this.#getFile(path);
|
|
1645
|
+
const deferredForPath = this.deferredForPath;
|
|
1646
|
+
const result = await this.writethrough(
|
|
1647
|
+
path,
|
|
1648
|
+
content,
|
|
1649
|
+
this.signal,
|
|
1650
|
+
file,
|
|
1651
|
+
this.batchRequest,
|
|
1652
|
+
deferredForPath ? (dst: string) => deferredForPath(dst) : undefined,
|
|
1653
|
+
);
|
|
1654
|
+
if (result) {
|
|
1655
|
+
this.#lastDiagnostics = result;
|
|
1656
|
+
}
|
|
1657
|
+
}
|
|
1658
|
+
|
|
1659
|
+
async delete(path: string): Promise<void> {
|
|
1660
|
+
await this.#getFile(path).unlink();
|
|
1661
|
+
}
|
|
1662
|
+
|
|
1663
|
+
async mkdir(path: string): Promise<void> {
|
|
1664
|
+
await fs.promises.mkdir(path, { recursive: true });
|
|
1665
|
+
}
|
|
1666
|
+
|
|
1667
|
+
getDiagnostics(): FileDiagnosticsResult | undefined {
|
|
1668
|
+
return this.#lastDiagnostics;
|
|
1669
|
+
}
|
|
1670
|
+
}
|
|
1671
|
+
|
|
1672
|
+
function mergeDiagnosticsWithWarnings(
|
|
1673
|
+
diagnostics: FileDiagnosticsResult | undefined,
|
|
1674
|
+
warnings: string[],
|
|
1675
|
+
): FileDiagnosticsResult | undefined {
|
|
1676
|
+
if (warnings.length === 0) return diagnostics;
|
|
1677
|
+
const warningMessages = warnings.map(warning => `patch: ${warning}`);
|
|
1678
|
+
if (!diagnostics) {
|
|
1679
|
+
return {
|
|
1680
|
+
server: "patch",
|
|
1681
|
+
messages: warningMessages,
|
|
1682
|
+
summary: `Patch warnings: ${warnings.length}`,
|
|
1683
|
+
errored: false,
|
|
1684
|
+
};
|
|
1685
|
+
}
|
|
1686
|
+
return {
|
|
1687
|
+
...diagnostics,
|
|
1688
|
+
messages: [...warningMessages, ...diagnostics.messages],
|
|
1689
|
+
summary: `${diagnostics.summary}; Patch warnings: ${warnings.length}`,
|
|
1690
|
+
};
|
|
1691
|
+
}
|
|
1692
|
+
|
|
1693
|
+
export async function executePatchMode(
|
|
1694
|
+
options: ExecutePatchModeOptions,
|
|
1695
|
+
): Promise<AgentToolResult<EditToolDetails, typeof patchEditSchema>> {
|
|
1696
|
+
const {
|
|
1697
|
+
session,
|
|
1698
|
+
params,
|
|
1699
|
+
signal,
|
|
1700
|
+
batchRequest,
|
|
1701
|
+
allowFuzzy,
|
|
1702
|
+
fuzzyThreshold,
|
|
1703
|
+
writethrough,
|
|
1704
|
+
beginDeferredDiagnosticsForPath,
|
|
1705
|
+
} = options;
|
|
1706
|
+
const { path, op: rawOp, rename, diff } = params;
|
|
1707
|
+
|
|
1708
|
+
const op: Operation = rawOp === "create" || rawOp === "delete" ? rawOp : "update";
|
|
1709
|
+
|
|
1710
|
+
enforcePlanModeWrite(session, path, { op, move: rename });
|
|
1711
|
+
const resolvedPath = resolvePlanPath(session, path);
|
|
1712
|
+
const resolvedRename = rename ? resolvePlanPath(session, rename) : undefined;
|
|
1713
|
+
|
|
1714
|
+
if (path.endsWith(".ipynb")) {
|
|
1715
|
+
throw new Error("Cannot edit Jupyter notebooks with the Edit tool. Use the NotebookEdit tool instead.");
|
|
1716
|
+
}
|
|
1717
|
+
if (rename?.endsWith(".ipynb")) {
|
|
1718
|
+
throw new Error("Cannot edit Jupyter notebooks with the Edit tool. Use the NotebookEdit tool instead.");
|
|
1719
|
+
}
|
|
1720
|
+
|
|
1721
|
+
await assertEditableFile(resolvedPath, path);
|
|
1722
|
+
|
|
1723
|
+
const input: PatchInput = { path: resolvedPath, op, rename: resolvedRename, diff };
|
|
1724
|
+
const patchFileSystem = new LspFileSystem(writethrough, signal, batchRequest, beginDeferredDiagnosticsForPath);
|
|
1725
|
+
const result = await applyPatch(input, {
|
|
1726
|
+
cwd: session.cwd,
|
|
1727
|
+
fs: patchFileSystem,
|
|
1728
|
+
fuzzyThreshold,
|
|
1729
|
+
allowFuzzy,
|
|
1730
|
+
});
|
|
1731
|
+
|
|
1732
|
+
if (resolvedRename) {
|
|
1733
|
+
invalidateFsScanAfterRename(resolvedPath, resolvedRename);
|
|
1734
|
+
} else if (result.change.type === "delete") {
|
|
1735
|
+
invalidateFsScanAfterDelete(resolvedPath);
|
|
1736
|
+
} else {
|
|
1737
|
+
invalidateFsScanAfterWrite(resolvedPath);
|
|
1738
|
+
}
|
|
1739
|
+
const effectiveRename = result.change.newPath ? rename : undefined;
|
|
1740
|
+
|
|
1741
|
+
let diffResult: { diff: string; firstChangedLine: number | undefined } = {
|
|
1742
|
+
diff: "",
|
|
1743
|
+
firstChangedLine: undefined,
|
|
1744
|
+
};
|
|
1745
|
+
if (result.change.type === "update" && result.change.oldContent && result.change.newContent) {
|
|
1746
|
+
const normalizedOld = normalizeToLF(stripBom(result.change.oldContent).text);
|
|
1747
|
+
const normalizedNew = normalizeToLF(stripBom(result.change.newContent).text);
|
|
1748
|
+
diffResult = generateUnifiedDiffString(normalizedOld, normalizedNew);
|
|
1749
|
+
}
|
|
1750
|
+
|
|
1751
|
+
let resultText: string;
|
|
1752
|
+
switch (result.change.type) {
|
|
1753
|
+
case "create":
|
|
1754
|
+
resultText = `Created ${path}`;
|
|
1755
|
+
break;
|
|
1756
|
+
case "delete":
|
|
1757
|
+
resultText = `Deleted ${path}`;
|
|
1758
|
+
break;
|
|
1759
|
+
case "update":
|
|
1760
|
+
resultText = effectiveRename ? `Updated and moved ${path} to ${effectiveRename}` : `Updated ${path}`;
|
|
1761
|
+
break;
|
|
1762
|
+
}
|
|
1763
|
+
|
|
1764
|
+
let diagnostics = patchFileSystem.getDiagnostics();
|
|
1765
|
+
if (op === "delete" && batchRequest?.flush) {
|
|
1766
|
+
const flushedDiagnostics = await flushLspWritethroughBatch(batchRequest.id, session.cwd, signal);
|
|
1767
|
+
diagnostics ??= flushedDiagnostics;
|
|
1768
|
+
}
|
|
1769
|
+
const mergedDiagnostics = mergeDiagnosticsWithWarnings(diagnostics, result.warnings ?? []);
|
|
1770
|
+
const meta = outputMeta()
|
|
1771
|
+
.diagnostics(mergedDiagnostics?.summary ?? "", mergedDiagnostics?.messages ?? [])
|
|
1772
|
+
.get();
|
|
1773
|
+
|
|
1774
|
+
return {
|
|
1775
|
+
content: [{ type: "text", text: resultText }],
|
|
1776
|
+
details: {
|
|
1777
|
+
diff: diffResult.diff,
|
|
1778
|
+
firstChangedLine: diffResult.firstChangedLine,
|
|
1779
|
+
diagnostics: mergedDiagnostics,
|
|
1780
|
+
op,
|
|
1781
|
+
move: effectiveRename,
|
|
1782
|
+
meta,
|
|
1783
|
+
},
|
|
1784
|
+
};
|
|
1785
|
+
}
|