@prestyj/cli 4.12.1 → 4.14.0
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/dist/app-sidecar.js +258 -45
- package/dist/app-sidecar.js.map +1 -1
- package/dist/cli/auth.d.ts.map +1 -1
- package/dist/cli/auth.js +5 -2
- package/dist/cli/auth.js.map +1 -1
- package/dist/cli/shared.d.ts.map +1 -1
- package/dist/cli/shared.js +2 -0
- package/dist/cli/shared.js.map +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +6 -1
- package/dist/cli.js.map +1 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +1 -0
- package/dist/config.js.map +1 -1
- package/dist/core/auth-providers.d.ts.map +1 -1
- package/dist/core/auth-providers.js +7 -0
- package/dist/core/auth-providers.js.map +1 -1
- package/dist/core/encode-cwd.d.ts +25 -0
- package/dist/core/encode-cwd.d.ts.map +1 -0
- package/dist/core/encode-cwd.js +40 -0
- package/dist/core/encode-cwd.js.map +1 -0
- package/dist/core/encode-cwd.test.d.ts +2 -0
- package/dist/core/encode-cwd.test.d.ts.map +1 -0
- package/dist/core/encode-cwd.test.js +61 -0
- package/dist/core/encode-cwd.test.js.map +1 -0
- package/dist/core/mcp/client.d.ts +11 -0
- package/dist/core/mcp/client.d.ts.map +1 -1
- package/dist/core/mcp/client.js +87 -35
- package/dist/core/mcp/client.js.map +1 -1
- package/dist/core/mcp/defaults.d.ts +1 -1
- package/dist/core/mcp/defaults.d.ts.map +1 -1
- package/dist/core/mcp/defaults.js +9 -40
- package/dist/core/mcp/defaults.js.map +1 -1
- package/dist/core/mcp/loopback.d.ts +27 -0
- package/dist/core/mcp/loopback.d.ts.map +1 -0
- package/dist/core/mcp/loopback.js +66 -0
- package/dist/core/mcp/loopback.js.map +1 -0
- package/dist/core/mcp/loopback.test.d.ts +2 -0
- package/dist/core/mcp/loopback.test.d.ts.map +1 -0
- package/dist/core/mcp/loopback.test.js +87 -0
- package/dist/core/mcp/loopback.test.js.map +1 -0
- package/dist/core/mcp/parse-add-command.d.ts.map +1 -1
- package/dist/core/mcp/parse-add-command.js +1 -0
- package/dist/core/mcp/parse-add-command.js.map +1 -1
- package/dist/core/mcp/parse-add-command.test.js +8 -2
- package/dist/core/mcp/parse-add-command.test.js.map +1 -1
- package/dist/core/mcp/resolve-stdio.d.ts +27 -0
- package/dist/core/mcp/resolve-stdio.d.ts.map +1 -0
- package/dist/core/mcp/resolve-stdio.js +149 -0
- package/dist/core/mcp/resolve-stdio.js.map +1 -0
- package/dist/core/mcp/resolve-stdio.test.d.ts +2 -0
- package/dist/core/mcp/resolve-stdio.test.d.ts.map +1 -0
- package/dist/core/mcp/resolve-stdio.test.js +63 -0
- package/dist/core/mcp/resolve-stdio.test.js.map +1 -0
- package/dist/core/mcp/store.d.ts +4 -4
- package/dist/core/mcp/store.d.ts.map +1 -1
- package/dist/core/mcp/store.js +7 -1
- package/dist/core/mcp/store.js.map +1 -1
- package/dist/core/mcp/store.test.js +11 -2
- package/dist/core/mcp/store.test.js.map +1 -1
- package/dist/core/mcp/types.d.ts +5 -1
- package/dist/core/mcp/types.d.ts.map +1 -1
- package/dist/core/project-discovery.d.ts.map +1 -1
- package/dist/core/project-discovery.js +2 -9
- package/dist/core/project-discovery.js.map +1 -1
- package/dist/core/radio.d.ts +7 -2
- package/dist/core/radio.d.ts.map +1 -1
- package/dist/core/radio.js.map +1 -1
- package/dist/core/resolve-start.test.js +1 -0
- package/dist/core/resolve-start.test.js.map +1 -1
- package/dist/core/session-manager.d.ts.map +1 -1
- package/dist/core/session-manager.js +1 -3
- package/dist/core/session-manager.js.map +1 -1
- package/dist/core/settings-manager.d.ts +1 -0
- package/dist/core/settings-manager.d.ts.map +1 -1
- package/dist/core/settings-manager.js +1 -0
- package/dist/core/settings-manager.js.map +1 -1
- package/dist/session.d.ts.map +1 -1
- package/dist/session.js +1 -3
- package/dist/session.js.map +1 -1
- package/dist/system-prompt.d.ts.map +1 -1
- package/dist/system-prompt.js +9 -4
- package/dist/system-prompt.js.map +1 -1
- package/dist/system-prompt.test.js +6 -3
- package/dist/system-prompt.test.js.map +1 -1
- package/dist/tools/bash.d.ts.map +1 -1
- package/dist/tools/bash.js +10 -2
- package/dist/tools/bash.js.map +1 -1
- package/dist/tools/code-skeleton.d.ts +29 -0
- package/dist/tools/code-skeleton.d.ts.map +1 -0
- package/dist/tools/code-skeleton.js +115 -0
- package/dist/tools/code-skeleton.js.map +1 -0
- package/dist/tools/code-skeleton.test.d.ts +2 -0
- package/dist/tools/code-skeleton.test.d.ts.map +1 -0
- package/dist/tools/code-skeleton.test.js +115 -0
- package/dist/tools/code-skeleton.test.js.map +1 -0
- package/dist/tools/compress-integration.test.d.ts +2 -0
- package/dist/tools/compress-integration.test.d.ts.map +1 -0
- package/dist/tools/compress-integration.test.js +59 -0
- package/dist/tools/compress-integration.test.js.map +1 -0
- package/dist/tools/compress.d.ts +57 -0
- package/dist/tools/compress.d.ts.map +1 -0
- package/dist/tools/compress.js +181 -0
- package/dist/tools/compress.js.map +1 -0
- package/dist/tools/compress.test.d.ts +2 -0
- package/dist/tools/compress.test.d.ts.map +1 -0
- package/dist/tools/compress.test.js +60 -0
- package/dist/tools/compress.test.js.map +1 -0
- package/dist/tools/edit-diff.d.ts.map +1 -1
- package/dist/tools/edit-diff.js +25 -8
- package/dist/tools/edit-diff.js.map +1 -1
- package/dist/tools/ls.d.ts.map +1 -1
- package/dist/tools/ls.js +7 -4
- package/dist/tools/ls.js.map +1 -1
- package/dist/tools/task-output.d.ts.map +1 -1
- package/dist/tools/task-output.js +13 -3
- package/dist/tools/task-output.js.map +1 -1
- package/dist/tools/tasks.d.ts +6 -0
- package/dist/tools/tasks.d.ts.map +1 -1
- package/dist/tools/tasks.js +41 -4
- package/dist/tools/tasks.js.map +1 -1
- package/dist/tools/tasks.test.d.ts +2 -0
- package/dist/tools/tasks.test.d.ts.map +1 -0
- package/dist/tools/tasks.test.js +62 -0
- package/dist/tools/tasks.test.js.map +1 -0
- package/dist/ui/App.d.ts.map +1 -1
- package/dist/ui/App.js.map +1 -1
- package/dist/ui/components/ChatScreen.d.ts +5 -5
- package/dist/ui/components/ChatScreen.d.ts.map +1 -1
- package/dist/ui/components/InputArea.d.ts +5 -5
- package/dist/ui/components/InputArea.d.ts.map +1 -1
- package/dist/ui/components/ModelSelector.d.ts.map +1 -1
- package/dist/ui/components/ModelSelector.js +1 -0
- package/dist/ui/components/ModelSelector.js.map +1 -1
- package/dist/ui/components/TaskPickerMenu.d.ts +2 -2
- package/dist/ui/components/TaskPickerMenu.d.ts.map +1 -1
- package/dist/ui/components/TaskPickerMenu.js.map +1 -1
- package/dist/ui/hooks/useAgentLoop.d.ts.map +1 -1
- package/dist/ui/hooks/useAgentLoop.js +13 -4
- package/dist/ui/hooks/useAgentLoop.js.map +1 -1
- package/dist/ui/login.d.ts.map +1 -1
- package/dist/ui/login.js +1 -0
- package/dist/ui/login.js.map +1 -1
- package/package.json +6 -5
- package/dist/core/tasks-store.d.ts +0 -24
- package/dist/core/tasks-store.d.ts.map +0 -1
- package/dist/core/tasks-store.js +0 -81
- package/dist/core/tasks-store.js.map +0 -1
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import ts from "typescript";
|
|
3
|
+
import { extractSkeleton } from "./code-skeleton.js";
|
|
4
|
+
/**
|
|
5
|
+
* Strong fidelity check: re-parse the skeleton and confirm `name` resolves to a
|
|
6
|
+
* real declaration (not a comment or substring). For callables we also assert
|
|
7
|
+
* the signature text carries a parameter list, so "name kept, signature lost"
|
|
8
|
+
* fails here instead of slipping through a regex.
|
|
9
|
+
*/
|
|
10
|
+
function skeletonDeclares(skeleton, name) {
|
|
11
|
+
const sf = ts.createSourceFile("skel.ts", skeleton, ts.ScriptTarget.Latest, true);
|
|
12
|
+
let found = false;
|
|
13
|
+
const walk = (node) => {
|
|
14
|
+
if (found)
|
|
15
|
+
return;
|
|
16
|
+
if ((ts.isFunctionDeclaration(node) ||
|
|
17
|
+
ts.isClassDeclaration(node) ||
|
|
18
|
+
ts.isInterfaceDeclaration(node) ||
|
|
19
|
+
ts.isTypeAliasDeclaration(node) ||
|
|
20
|
+
ts.isEnumDeclaration(node)) &&
|
|
21
|
+
node.name?.getText(sf) === name) {
|
|
22
|
+
found = true;
|
|
23
|
+
}
|
|
24
|
+
else if (ts.isVariableDeclaration(node) && node.name.getText(sf) === name) {
|
|
25
|
+
found = true;
|
|
26
|
+
}
|
|
27
|
+
else if (ts.isExportDeclaration(node)) {
|
|
28
|
+
if (node.exportClause && ts.isNamedExports(node.exportClause)) {
|
|
29
|
+
if (node.exportClause.elements.some((e) => e.name.text === name))
|
|
30
|
+
found = true;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
ts.forEachChild(node, walk);
|
|
34
|
+
};
|
|
35
|
+
walk(sf);
|
|
36
|
+
return found;
|
|
37
|
+
}
|
|
38
|
+
describe("extractSkeleton", () => {
|
|
39
|
+
it("keeps re-export barrels (export {} from / export *) — does NOT render them empty", () => {
|
|
40
|
+
const src = `
|
|
41
|
+
export { foo, bar } from "./other.js";
|
|
42
|
+
export * from "./barrel.js";
|
|
43
|
+
`;
|
|
44
|
+
const r = extractSkeleton(src);
|
|
45
|
+
expect(r.empty).toBe(false);
|
|
46
|
+
expect(r.skeleton).toContain('export { foo, bar } from "./other.js"');
|
|
47
|
+
expect(r.skeleton).toContain('export * from "./barrel.js"');
|
|
48
|
+
expect(skeletonDeclares(r.skeleton, "foo")).toBe(true);
|
|
49
|
+
expect(skeletonDeclares(r.skeleton, "bar")).toBe(true);
|
|
50
|
+
});
|
|
51
|
+
it("preserves the callable signature of an exported arrow const", () => {
|
|
52
|
+
const src = `export const handler = async (req: Request, opts: Opts): Promise<Response> => {
|
|
53
|
+
return new Response("hi");
|
|
54
|
+
};`;
|
|
55
|
+
const r = extractSkeleton(src);
|
|
56
|
+
expect(r.skeleton).toContain("req: Request");
|
|
57
|
+
expect(r.skeleton).toContain("opts: Opts");
|
|
58
|
+
expect(r.skeleton).toContain("Promise<Response>");
|
|
59
|
+
expect(r.skeleton).not.toContain('new Response("hi")'); // body gone
|
|
60
|
+
expect(r.skeleton).toContain("/* … */"); // stub marked
|
|
61
|
+
});
|
|
62
|
+
it("keeps function declarations' params/return and stubs the body", () => {
|
|
63
|
+
const src = `export function add(a: number, b: number): number {
|
|
64
|
+
const sum = a + b;
|
|
65
|
+
return sum;
|
|
66
|
+
}`;
|
|
67
|
+
const r = extractSkeleton(src);
|
|
68
|
+
expect(r.skeleton).toContain("add(a: number, b: number): number");
|
|
69
|
+
expect(r.skeleton).not.toContain("const sum");
|
|
70
|
+
expect(r.skeleton).toContain("/* … */");
|
|
71
|
+
});
|
|
72
|
+
it("keeps public class members but drops private ones", () => {
|
|
73
|
+
const src = `export class Svc extends Base {
|
|
74
|
+
private secret = 42;
|
|
75
|
+
async run(x: string): Promise<void> { await this.secret; }
|
|
76
|
+
}`;
|
|
77
|
+
const r = extractSkeleton(src);
|
|
78
|
+
expect(r.skeleton).toContain("class Svc extends Base");
|
|
79
|
+
expect(r.skeleton).toContain("run(x: string): Promise<void>");
|
|
80
|
+
expect(r.skeleton).not.toContain("secret");
|
|
81
|
+
});
|
|
82
|
+
it("keeps interfaces and type aliases whole (pure signal)", () => {
|
|
83
|
+
const src = `export interface User { id: string; name: string; }
|
|
84
|
+
export type ID = string | number;`;
|
|
85
|
+
const r = extractSkeleton(src);
|
|
86
|
+
expect(r.skeleton).toContain("id: string");
|
|
87
|
+
expect(r.skeleton).toContain("type ID = string | number");
|
|
88
|
+
});
|
|
89
|
+
it("omits internal (non-exported) consts and functions' bodies", () => {
|
|
90
|
+
const src = `const internal = computeThing();
|
|
91
|
+
export function api(): void { internal(); }`;
|
|
92
|
+
const r = extractSkeleton(src);
|
|
93
|
+
expect(r.skeleton).not.toContain("computeThing");
|
|
94
|
+
expect(r.skeleton).toContain("api(): void");
|
|
95
|
+
});
|
|
96
|
+
it("never fabricates: every line traces to source or is a marked stub", () => {
|
|
97
|
+
const src = `import { z } from "zod";
|
|
98
|
+
export function f(a: string): string { return a.trim(); }`;
|
|
99
|
+
const r = extractSkeleton(src);
|
|
100
|
+
const srcText = src;
|
|
101
|
+
for (const line of r.skeleton.split("\n")) {
|
|
102
|
+
const t = line.trim();
|
|
103
|
+
if (!t)
|
|
104
|
+
continue;
|
|
105
|
+
const isStub = t.includes("/* … */");
|
|
106
|
+
const tracesBack = srcText.includes(t.replace(/\s*\{ \/\* … \*\/ \}.*$/, "").trim());
|
|
107
|
+
expect(isStub || tracesBack).toBe(true);
|
|
108
|
+
}
|
|
109
|
+
});
|
|
110
|
+
it("reports empty for a file with no extractable API", () => {
|
|
111
|
+
const r = extractSkeleton(`const x = 1; console.log(x);`);
|
|
112
|
+
expect(r.empty).toBe(true);
|
|
113
|
+
});
|
|
114
|
+
});
|
|
115
|
+
//# sourceMappingURL=code-skeleton.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"code-skeleton.test.js","sourceRoot":"","sources":["../../src/tools/code-skeleton.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"compress-integration.test.d.ts","sourceRoot":"","sources":["../../src/tools/compress-integration.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import { compressToolOutput } from "./compress.js";
|
|
3
|
+
import { truncateTail, MAX_LINES } from "./truncate.js";
|
|
4
|
+
/**
|
|
5
|
+
* Integration-level guarantees for the live tool path (bash / task_output).
|
|
6
|
+
* The contract: compression only fires on output that was ALREADY going to be
|
|
7
|
+
* truncated, and on that branch it must keep more signal than a blind tail
|
|
8
|
+
* slice — never less — while staying reversible (caller keeps the overflow file).
|
|
9
|
+
*/
|
|
10
|
+
describe("compressToolOutput (live tool-output seam)", () => {
|
|
11
|
+
it("keeps a FATAL line that blind tail-truncation would drop", () => {
|
|
12
|
+
const lines = [];
|
|
13
|
+
for (let i = 0; i < 5000; i++) {
|
|
14
|
+
if (i === 1200)
|
|
15
|
+
lines.push("ERROR FATAL: segfault in worker pool");
|
|
16
|
+
else
|
|
17
|
+
lines.push(`INFO step ${i} ok`);
|
|
18
|
+
}
|
|
19
|
+
const raw = lines.join("\n");
|
|
20
|
+
// Today's behaviour: tail slice keeps only the last MAX_LINES — the FATAL at
|
|
21
|
+
// line 1200 is in the discarded head.
|
|
22
|
+
const tail = truncateTail(raw);
|
|
23
|
+
expect(tail.content).not.toContain("FATAL: segfault");
|
|
24
|
+
// Compression keeps it.
|
|
25
|
+
const c = compressToolOutput(raw);
|
|
26
|
+
expect(c.content).toContain("FATAL: segfault in worker pool");
|
|
27
|
+
expect(c.notice).toMatch(/fewer tokens/);
|
|
28
|
+
});
|
|
29
|
+
it("preserves the very end of the output (tail semantics)", () => {
|
|
30
|
+
const lines = Array.from({ length: 6000 }, (_, i) => `line ${i}`);
|
|
31
|
+
const c = compressToolOutput(lines.join("\n"));
|
|
32
|
+
expect(c.content).toContain("line 5999");
|
|
33
|
+
expect(c.content).toContain("line 5998");
|
|
34
|
+
});
|
|
35
|
+
it("collapses repeated spam into a count", () => {
|
|
36
|
+
const lines = ["boot"];
|
|
37
|
+
for (let i = 0; i < 3000; i++)
|
|
38
|
+
lines.push("WARN socket hang up");
|
|
39
|
+
lines.push("ERROR aborted");
|
|
40
|
+
const c = compressToolOutput(lines.join("\n"));
|
|
41
|
+
expect(c.content).toMatch(/×\d+/);
|
|
42
|
+
expect(c.content).toContain("ERROR aborted");
|
|
43
|
+
});
|
|
44
|
+
it("produces fewer tokens than the original on large output", () => {
|
|
45
|
+
const lines = Array.from({ length: 8000 }, (_, i) => `compiled module ${i} ok in ${i}ms`);
|
|
46
|
+
const raw = lines.join("\n");
|
|
47
|
+
const c = compressToolOutput(raw);
|
|
48
|
+
expect(c.content.length).toBeLessThan(raw.length / 2);
|
|
49
|
+
});
|
|
50
|
+
it("only the over-limit branch is affected — small output is never compressed here", () => {
|
|
51
|
+
// The tools call compressToolOutput ONLY when truncateTail reports truncated.
|
|
52
|
+
// A small output stays below MAX_LINES, so the branch never runs. Assert the
|
|
53
|
+
// gate, not the compressor: small output is not truncated.
|
|
54
|
+
const small = Array.from({ length: 10 }, (_, i) => `line ${i}`).join("\n");
|
|
55
|
+
expect(truncateTail(small).truncated).toBe(false);
|
|
56
|
+
expect(MAX_LINES).toBeGreaterThan(10);
|
|
57
|
+
});
|
|
58
|
+
});
|
|
59
|
+
//# sourceMappingURL=compress-integration.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"compress-integration.test.js","sourceRoot":"","sources":["../../src/tools/compress-integration.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Content-aware tool-output compression.
|
|
3
|
+
*
|
|
4
|
+
* Today's tool path shrinks oversized output with blunt head/tail line cuts
|
|
5
|
+
* (`truncate.ts`). That throws away whichever half the model didn't get — a
|
|
6
|
+
* stack trace buried in the middle of a 10k-line log, the one failing assertion
|
|
7
|
+
* between thousands of passing ones, the shape of a giant JSON array.
|
|
8
|
+
*
|
|
9
|
+
* `compressOutput` keeps the *signal* instead of a positional slice:
|
|
10
|
+
* - logs: collapse repeated runs, keep every error/warn line + local context,
|
|
11
|
+
* keep head and tail, drop only the boring middle.
|
|
12
|
+
* - JSON: keep array shape + a sample, replace the long tail with a count.
|
|
13
|
+
* - text: middle-out (head + tail) so the command/context AND the result survive.
|
|
14
|
+
*
|
|
15
|
+
* It is lossy by design but information-preserving: the goal is fewer tokens
|
|
16
|
+
* with the same understanding. Pair with an overflow file for full reversibility.
|
|
17
|
+
*/
|
|
18
|
+
export interface CompressResult {
|
|
19
|
+
content: string;
|
|
20
|
+
/** Which strategy fired — for telemetry / benchmarking. */
|
|
21
|
+
strategy: "json" | "log" | "text" | "none";
|
|
22
|
+
originalTokens: number;
|
|
23
|
+
compressedTokens: number;
|
|
24
|
+
}
|
|
25
|
+
export interface CompressOptions {
|
|
26
|
+
/** Don't compress below this token count — small output isn't worth touching. */
|
|
27
|
+
minTokens?: number;
|
|
28
|
+
/** Lines of context to keep on each side of an important log line. */
|
|
29
|
+
contextLines?: number;
|
|
30
|
+
/** Head/tail lines always retained. */
|
|
31
|
+
headLines?: number;
|
|
32
|
+
tailLines?: number;
|
|
33
|
+
/** Max elements kept per JSON array before summarising the rest. */
|
|
34
|
+
jsonArraySample?: number;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Compress a tool output string, auto-detecting its content type.
|
|
38
|
+
*/
|
|
39
|
+
export declare function compressOutput(raw: string, opts?: CompressOptions): CompressResult;
|
|
40
|
+
export interface ToolOutputCompression {
|
|
41
|
+
content: string;
|
|
42
|
+
/** Human/agent-facing summary of what was kept, for the truncation notice. */
|
|
43
|
+
notice: string;
|
|
44
|
+
strategy: CompressResult["strategy"];
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Compress output that a tool was ALREADY going to truncate.
|
|
48
|
+
*
|
|
49
|
+
* This is the only sanctioned live integration point. It is invoked solely on
|
|
50
|
+
* the over-limit branch of a tool (bash / task_output) — where today's
|
|
51
|
+
* alternative is a blind tail slice that discards the head and any mid-stream
|
|
52
|
+
* error. It keeps generous head/tail (tool output is tail-oriented) plus every
|
|
53
|
+
* error line and collapses repeats. The caller still writes the full original
|
|
54
|
+
* to an overflow file, so this stays fully reversible.
|
|
55
|
+
*/
|
|
56
|
+
export declare function compressToolOutput(raw: string): ToolOutputCompression;
|
|
57
|
+
//# sourceMappingURL=compress.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"compress.d.ts","sourceRoot":"","sources":["../../src/tools/compress.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;GAgBG;AAEH,MAAM,WAAW,cAAc;IAC7B,OAAO,EAAE,MAAM,CAAC;IAChB,2DAA2D;IAC3D,QAAQ,EAAE,MAAM,GAAG,KAAK,GAAG,MAAM,GAAG,MAAM,CAAC;IAC3C,cAAc,EAAE,MAAM,CAAC;IACvB,gBAAgB,EAAE,MAAM,CAAC;CAC1B;AAED,MAAM,WAAW,eAAe;IAC9B,iFAAiF;IACjF,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,sEAAsE;IACtE,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,uCAAuC;IACvC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,oEAAoE;IACpE,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B;AA+BD;;GAEG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,GAAE,eAAoB,GAAG,cAAc,CAkBtF;AA2HD,MAAM,WAAW,qBAAqB;IACpC,OAAO,EAAE,MAAM,CAAC;IAChB,8EAA8E;IAC9E,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,cAAc,CAAC,UAAU,CAAC,CAAC;CACtC;AAED;;;;;;;;;GASG;AACH,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,MAAM,GAAG,qBAAqB,CAQrE"}
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
import { estimateTokens } from "../core/compaction/token-estimator.js";
|
|
2
|
+
const DEFAULTS = {
|
|
3
|
+
minTokens: 400,
|
|
4
|
+
contextLines: 2,
|
|
5
|
+
headLines: 8,
|
|
6
|
+
tailLines: 8,
|
|
7
|
+
jsonArraySample: 5,
|
|
8
|
+
};
|
|
9
|
+
/** Lines that must never be dropped from a log. */
|
|
10
|
+
const IMPORTANT = /\b(error|fatal|panic|exception|traceback|fail(?:ed|ure)?|warn(?:ing)?|assert|fault|denied|refused|timeout|unhandled|rejected|✗|✖|❌)\b|^\s*at\s+|Error:|Caused by:/i;
|
|
11
|
+
function tokens(text) {
|
|
12
|
+
return estimateTokens(text);
|
|
13
|
+
}
|
|
14
|
+
function result(content, strategy, original) {
|
|
15
|
+
return {
|
|
16
|
+
content,
|
|
17
|
+
strategy,
|
|
18
|
+
originalTokens: tokens(original),
|
|
19
|
+
compressedTokens: tokens(content),
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Compress a tool output string, auto-detecting its content type.
|
|
24
|
+
*/
|
|
25
|
+
export function compressOutput(raw, opts = {}) {
|
|
26
|
+
const o = { ...DEFAULTS, ...opts };
|
|
27
|
+
if (tokens(raw) < o.minTokens)
|
|
28
|
+
return result(raw, "none", raw);
|
|
29
|
+
const trimmed = raw.trim();
|
|
30
|
+
if (trimmed.length > 1 && (trimmed[0] === "{" || trimmed[0] === "[")) {
|
|
31
|
+
const json = tryCompressJson(trimmed, o);
|
|
32
|
+
if (json !== null)
|
|
33
|
+
return result(json, "json", raw);
|
|
34
|
+
}
|
|
35
|
+
const lines = raw.split("\n");
|
|
36
|
+
const importantCount = lines.reduce((n, l) => (IMPORTANT.test(l) ? n + 1 : n), 0);
|
|
37
|
+
// Treat as a log when it's many lines OR carries error signal worth isolating.
|
|
38
|
+
if (lines.length > o.headLines + o.tailLines + 10 && (importantCount > 0 || lines.length > 200)) {
|
|
39
|
+
return result(compressLog(lines, o), "log", raw);
|
|
40
|
+
}
|
|
41
|
+
return result(compressText(lines, o), "text", raw);
|
|
42
|
+
}
|
|
43
|
+
/* ----------------------------------------------------------------------- */
|
|
44
|
+
/* Log: keep error lines + context, collapse repeats, drop the boring middle */
|
|
45
|
+
/* ----------------------------------------------------------------------- */
|
|
46
|
+
function compressLog(lines, o) {
|
|
47
|
+
// 1. Collapse consecutive identical / near-identical runs into one entry.
|
|
48
|
+
// `exact` tracks whether every line in the run was byte-identical, so the
|
|
49
|
+
// emitted marker can be honest: "(×N)" only when truly identical, otherwise
|
|
50
|
+
// "(×N similar)" so the model never assumes the shown text (e.g. a single
|
|
51
|
+
// timestamp) was repeated verbatim across the whole run.
|
|
52
|
+
const collapsed = [];
|
|
53
|
+
for (let i = 0; i < lines.length; i++) {
|
|
54
|
+
const prev = collapsed[collapsed.length - 1];
|
|
55
|
+
if (prev && normalize(prev.text) === normalize(lines[i])) {
|
|
56
|
+
prev.count++;
|
|
57
|
+
if (lines[i] !== prev.text)
|
|
58
|
+
prev.exact = false;
|
|
59
|
+
}
|
|
60
|
+
else {
|
|
61
|
+
collapsed.push({ text: lines[i], count: 1, idx: i, exact: true });
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
// 2. Mark which collapsed entries to keep: head, tail, important + context.
|
|
65
|
+
const keep = new Set();
|
|
66
|
+
for (let i = 0; i < Math.min(o.headLines, collapsed.length); i++)
|
|
67
|
+
keep.add(i);
|
|
68
|
+
for (let i = Math.max(0, collapsed.length - o.tailLines); i < collapsed.length; i++)
|
|
69
|
+
keep.add(i);
|
|
70
|
+
for (let i = 0; i < collapsed.length; i++) {
|
|
71
|
+
if (IMPORTANT.test(collapsed[i].text)) {
|
|
72
|
+
for (let j = i - o.contextLines; j <= i + o.contextLines; j++) {
|
|
73
|
+
if (j >= 0 && j < collapsed.length)
|
|
74
|
+
keep.add(j);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
// 3. Emit, replacing dropped gaps with a single omission marker.
|
|
79
|
+
const out = [];
|
|
80
|
+
let droppedLines = 0;
|
|
81
|
+
let gapStart = -1;
|
|
82
|
+
const flushGap = () => {
|
|
83
|
+
if (droppedLines > 0) {
|
|
84
|
+
out.push(`… ${droppedLines} line${droppedLines === 1 ? "" : "s"} omitted …`);
|
|
85
|
+
droppedLines = 0;
|
|
86
|
+
gapStart = -1;
|
|
87
|
+
}
|
|
88
|
+
};
|
|
89
|
+
for (let i = 0; i < collapsed.length; i++) {
|
|
90
|
+
if (keep.has(i)) {
|
|
91
|
+
flushGap();
|
|
92
|
+
const e = collapsed[i];
|
|
93
|
+
if (e.count > 1)
|
|
94
|
+
out.push(`${e.text} (×${e.count}${e.exact ? "" : " similar"})`);
|
|
95
|
+
else
|
|
96
|
+
out.push(e.text);
|
|
97
|
+
}
|
|
98
|
+
else {
|
|
99
|
+
if (gapStart === -1)
|
|
100
|
+
gapStart = i;
|
|
101
|
+
droppedLines += collapsed[i].count;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
flushGap();
|
|
105
|
+
return out.join("\n");
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Strip only *volatile noise* (timestamps, hex addresses) so lines that differ
|
|
109
|
+
* solely by a leading timestamp collapse as repeats. Deliberately does NOT nuke
|
|
110
|
+
* arbitrary integers: when the number IS the content (`line 5998` vs `line 5999`,
|
|
111
|
+
* a port, an id), the lines must stay distinct so the tail isn't flattened into
|
|
112
|
+
* a single "(×N)" and genuinely different output isn't reported as repetition.
|
|
113
|
+
*/
|
|
114
|
+
function normalize(line) {
|
|
115
|
+
return line
|
|
116
|
+
.replace(/\d{4}-\d{2}-\d{2}[T ]\d{2}:\d{2}:\d{2}(?:\.\d+)?Z?/g, "<ts>")
|
|
117
|
+
.replace(/\b\d{2}:\d{2}:\d{2}(?:\.\d+)?\b/g, "<ts>")
|
|
118
|
+
.replace(/0x[0-9a-f]+/gi, "<addr>")
|
|
119
|
+
.trim();
|
|
120
|
+
}
|
|
121
|
+
/* ----------------------------------------------------------------------- */
|
|
122
|
+
/* JSON: keep shape + a sample of long arrays, summarise the rest */
|
|
123
|
+
/* ----------------------------------------------------------------------- */
|
|
124
|
+
function tryCompressJson(text, o) {
|
|
125
|
+
let parsed;
|
|
126
|
+
try {
|
|
127
|
+
parsed = JSON.parse(text);
|
|
128
|
+
}
|
|
129
|
+
catch {
|
|
130
|
+
return null;
|
|
131
|
+
}
|
|
132
|
+
const shrunk = shrinkJson(parsed, o);
|
|
133
|
+
return JSON.stringify(shrunk, null, 2);
|
|
134
|
+
}
|
|
135
|
+
function shrinkJson(value, o) {
|
|
136
|
+
if (Array.isArray(value)) {
|
|
137
|
+
if (value.length <= o.jsonArraySample)
|
|
138
|
+
return value.map((v) => shrinkJson(v, o));
|
|
139
|
+
const sample = value.slice(0, o.jsonArraySample).map((v) => shrinkJson(v, o));
|
|
140
|
+
sample.push(`… ${value.length - o.jsonArraySample} more of ${value.length} items omitted …`);
|
|
141
|
+
return sample;
|
|
142
|
+
}
|
|
143
|
+
if (value && typeof value === "object") {
|
|
144
|
+
const out = {};
|
|
145
|
+
for (const [k, v] of Object.entries(value))
|
|
146
|
+
out[k] = shrinkJson(v, o);
|
|
147
|
+
return out;
|
|
148
|
+
}
|
|
149
|
+
return value;
|
|
150
|
+
}
|
|
151
|
+
/* ----------------------------------------------------------------------- */
|
|
152
|
+
/* Text: middle-out so both the command/context and the result survive */
|
|
153
|
+
/* ----------------------------------------------------------------------- */
|
|
154
|
+
function compressText(lines, o) {
|
|
155
|
+
const budget = o.headLines + o.tailLines;
|
|
156
|
+
if (lines.length <= budget)
|
|
157
|
+
return lines.join("\n");
|
|
158
|
+
const head = lines.slice(0, o.headLines);
|
|
159
|
+
const tail = lines.slice(lines.length - o.tailLines);
|
|
160
|
+
const dropped = lines.length - budget;
|
|
161
|
+
return [...head, `… ${dropped} lines omitted …`, ...tail].join("\n");
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* Compress output that a tool was ALREADY going to truncate.
|
|
165
|
+
*
|
|
166
|
+
* This is the only sanctioned live integration point. It is invoked solely on
|
|
167
|
+
* the over-limit branch of a tool (bash / task_output) — where today's
|
|
168
|
+
* alternative is a blind tail slice that discards the head and any mid-stream
|
|
169
|
+
* error. It keeps generous head/tail (tool output is tail-oriented) plus every
|
|
170
|
+
* error line and collapses repeats. The caller still writes the full original
|
|
171
|
+
* to an overflow file, so this stays fully reversible.
|
|
172
|
+
*/
|
|
173
|
+
export function compressToolOutput(raw) {
|
|
174
|
+
const r = compressOutput(raw, { headLines: 12, tailLines: 30, minTokens: 0 });
|
|
175
|
+
const saved = r.originalTokens - r.compressedTokens;
|
|
176
|
+
const notice = r.strategy === "none"
|
|
177
|
+
? ""
|
|
178
|
+
: `Compressed (${r.strategy}): kept errors + head/tail, collapsed repeats — ~${saved} fewer tokens.`;
|
|
179
|
+
return { content: r.content, notice, strategy: r.strategy };
|
|
180
|
+
}
|
|
181
|
+
//# sourceMappingURL=compress.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"compress.js","sourceRoot":"","sources":["../../src/tools/compress.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"compress.test.d.ts","sourceRoot":"","sources":["../../src/tools/compress.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import { compressOutput } from "./compress.js";
|
|
3
|
+
describe("compressOutput", () => {
|
|
4
|
+
it("leaves small output untouched", () => {
|
|
5
|
+
const small = "line one\nline two\nline three";
|
|
6
|
+
const r = compressOutput(small);
|
|
7
|
+
expect(r.strategy).toBe("none");
|
|
8
|
+
expect(r.content).toBe(small);
|
|
9
|
+
});
|
|
10
|
+
it("keeps a FATAL line buried in the middle of a huge log", () => {
|
|
11
|
+
const lines = [];
|
|
12
|
+
for (let i = 0; i < 5000; i++) {
|
|
13
|
+
if (i === 2600)
|
|
14
|
+
lines.push("ERROR FATAL: heap allocation failed at chunk 8821");
|
|
15
|
+
else
|
|
16
|
+
lines.push(`INFO compiled module ${i} ok`);
|
|
17
|
+
}
|
|
18
|
+
const r = compressOutput(lines.join("\n"));
|
|
19
|
+
expect(r.strategy).toBe("log");
|
|
20
|
+
expect(r.content).toContain("FATAL: heap allocation failed at chunk 8821");
|
|
21
|
+
expect(r.compressedTokens).toBeLessThan(r.originalTokens * 0.2);
|
|
22
|
+
});
|
|
23
|
+
it("collapses repeated runs into a count", () => {
|
|
24
|
+
const lines = ["start"];
|
|
25
|
+
for (let i = 0; i < 200; i++)
|
|
26
|
+
lines.push("WARN retry: Connection refused: postgres:5432");
|
|
27
|
+
lines.push("ERROR giving up after 200 attempts");
|
|
28
|
+
const r = compressOutput(lines.join("\n"));
|
|
29
|
+
expect(r.content).toContain("Connection refused: postgres:5432");
|
|
30
|
+
expect(r.content).toContain("giving up after 200 attempts");
|
|
31
|
+
expect(r.content).toMatch(/×\d+/);
|
|
32
|
+
});
|
|
33
|
+
it("summarises a long JSON array but keeps shape and early matches", () => {
|
|
34
|
+
const matches = [];
|
|
35
|
+
for (let i = 0; i < 412; i++) {
|
|
36
|
+
matches.push({ path: i === 3 ? "src/core/auth.ts" : `src/gen/file-${i}.ts`, line: i });
|
|
37
|
+
}
|
|
38
|
+
const r = compressOutput(JSON.stringify({ total: 412, matches }, null, 2));
|
|
39
|
+
expect(r.strategy).toBe("json");
|
|
40
|
+
expect(r.content).toContain('"total": 412');
|
|
41
|
+
expect(r.content).toContain("src/core/auth.ts");
|
|
42
|
+
expect(r.content).toMatch(/more of 412 items omitted/);
|
|
43
|
+
expect(r.compressedTokens).toBeLessThan(r.originalTokens * 0.5);
|
|
44
|
+
});
|
|
45
|
+
it("preserves the failing assertion among thousands of passes", () => {
|
|
46
|
+
const lines = [];
|
|
47
|
+
for (let i = 0; i < 2000; i++) {
|
|
48
|
+
if (i === 1450) {
|
|
49
|
+
lines.push("✗ user-auth › rejects expired token");
|
|
50
|
+
lines.push(" AssertionError: Expected 401 but received 200");
|
|
51
|
+
}
|
|
52
|
+
else
|
|
53
|
+
lines.push(`✓ case ${i}`);
|
|
54
|
+
}
|
|
55
|
+
const r = compressOutput(lines.join("\n"));
|
|
56
|
+
expect(r.content).toContain("rejects expired token");
|
|
57
|
+
expect(r.content).toContain("Expected 401 but received 200");
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
//# sourceMappingURL=compress.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"compress.test.js","sourceRoot":"","sources":["../../src/tools/compress.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"edit-diff.d.ts","sourceRoot":"","sources":["../../src/tools/edit-diff.ts"],"names":[],"mappings":"AAsEA;;;;;;;;;;;;;GAaG;AACH,wBAAgB,6BAA6B,CAC3C,OAAO,EAAE,MAAM,EACf,GAAG,EAAE,MAAM,EACX,IAAI,EAAE,MAAM,GACX,MAAM,GAAG,IAAI,CAgDf;AAED;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAuDzF;AAcD;;;;;;;GAOG;AACH,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAYjE;AAED;;;;;GAKG;AACH,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAYlE;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAM3D;AAED;;GAEG;AACH,wBAAgB,aAAa,CAC3B,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,MAAM,GACd;IAAE,KAAK,EAAE,OAAO,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,OAAO,CAAA;CAAE,
|
|
1
|
+
{"version":3,"file":"edit-diff.d.ts","sourceRoot":"","sources":["../../src/tools/edit-diff.ts"],"names":[],"mappings":"AAsEA;;;;;;;;;;;;;GAaG;AACH,wBAAgB,6BAA6B,CAC3C,OAAO,EAAE,MAAM,EACf,GAAG,EAAE,MAAM,EACX,IAAI,EAAE,MAAM,GACX,MAAM,GAAG,IAAI,CAgDf;AAED;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAuDzF;AAcD;;;;;;;GAOG;AACH,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAYjE;AAED;;;;;GAKG;AACH,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAYlE;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAM3D;AAED;;GAEG;AACH,wBAAgB,aAAa,CAC3B,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,MAAM,GACd;IAAE,KAAK,EAAE,OAAO,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,OAAO,CAAA;CAAE,CA4C5E;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAqCzE;AASD;;;;;;;;GAQG;AACH,MAAM,WAAW,cAAc;IAC7B,OAAO,EAAE,MAAM,CAAC;IAIhB,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,wBAAgB,kBAAkB,CAChC,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,MAAM,EACf,YAAY,SAAI,EAChB,UAAU,SAAI,GACb,cAAc,GAAG,IAAI,CAuCvB;AAED;;;;;;GAMG;AACH,wBAAgB,mBAAmB,CACjC,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,MAAM,EACZ,GAAG,SAAI,GACN;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,EAAE,CA6BrC;AAED;;GAEG;AACH,wBAAgB,YAAY,CAAC,UAAU,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAa7F"}
|
package/dist/tools/edit-diff.js
CHANGED
|
@@ -286,18 +286,31 @@ export function fuzzyFindText(content, oldText) {
|
|
|
286
286
|
}
|
|
287
287
|
// Fuzzy match line-by-line so stripped trailing whitespace in earlier lines
|
|
288
288
|
// cannot shift offsets and make us replace the wrong byte range.
|
|
289
|
+
//
|
|
290
|
+
// Performance: use a lazy normalization cache — each content line is
|
|
291
|
+
// normalized on first access and cached, so lines before the match are
|
|
292
|
+
// normalized exactly once (not once per window), and lines after the
|
|
293
|
+
// match are never touched. This gives O(n+m) worst-case (no match)
|
|
294
|
+
// while avoiding wasted work when the match is found early.
|
|
289
295
|
const oldLines = oldText.split("\n");
|
|
290
296
|
const contentLines = content.split("\n");
|
|
291
|
-
const
|
|
297
|
+
const normalizedOldJoined = oldLines.map(normalizeForFuzzyMatch).join("\n");
|
|
298
|
+
const normalizedCache = new Array(contentLines.length);
|
|
292
299
|
for (let startLine = 0; startLine + oldLines.length <= contentLines.length; startLine++) {
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
300
|
+
// Build the normalized candidate window using the lazy cache
|
|
301
|
+
let normalizedCandidate = "";
|
|
302
|
+
for (let j = startLine; j < startLine + oldLines.length; j++) {
|
|
303
|
+
normalizedCandidate += normalizedCache[j] ??= normalizeForFuzzyMatch(contentLines[j]);
|
|
304
|
+
if (j < startLine + oldLines.length - 1)
|
|
305
|
+
normalizedCandidate += "\n";
|
|
306
|
+
}
|
|
307
|
+
if (normalizedCandidate !== normalizedOldJoined)
|
|
296
308
|
continue;
|
|
297
309
|
let actualIndex = 0;
|
|
298
310
|
for (let i = 0; i < startLine; i++) {
|
|
299
311
|
actualIndex += contentLines[i].length + 1; // +1 for \n
|
|
300
312
|
}
|
|
313
|
+
const candidateLines = contentLines.slice(startLine, startLine + oldLines.length);
|
|
301
314
|
return {
|
|
302
315
|
found: true,
|
|
303
316
|
index: actualIndex,
|
|
@@ -335,11 +348,15 @@ export function countOccurrences(content, oldText) {
|
|
|
335
348
|
}
|
|
336
349
|
const oldLines = oldText.split("\n");
|
|
337
350
|
const contentLines = content.split("\n");
|
|
351
|
+
// Lazy normalization cache (same optimization as fuzzyFindText).
|
|
352
|
+
const normalizedCache = new Array(contentLines.length);
|
|
338
353
|
for (let startLine = 0; startLine + oldLines.length <= contentLines.length; startLine++) {
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
.
|
|
354
|
+
let normalizedCandidate = "";
|
|
355
|
+
for (let j = startLine; j < startLine + oldLines.length; j++) {
|
|
356
|
+
normalizedCandidate += normalizedCache[j] ??= normalizeForFuzzyMatch(contentLines[j]);
|
|
357
|
+
if (j < startLine + oldLines.length - 1)
|
|
358
|
+
normalizedCandidate += "\n";
|
|
359
|
+
}
|
|
343
360
|
if (normalizedCandidate === normalizedOld)
|
|
344
361
|
count++;
|
|
345
362
|
}
|