@mikode13/harness 9.1.0 → 9.3.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/README.md +14 -6
- package/dist/engines/domain/model/commandLoops.d.ts +59 -0
- package/dist/engines/domain/model/commandLoops.js +143 -0
- package/dist/engines/domain/model/commandLoops.js.map +1 -0
- package/dist/engines/domain/model/llmAgent.d.ts +6 -0
- package/dist/engines/domain/model/llmAgent.js +154 -42
- package/dist/engines/domain/model/llmAgent.js.map +1 -1
- package/dist/factory/infrastructure/agentLLMFactory.d.ts +2 -2
- package/dist/factory/infrastructure/agentLLMFactory.js +2 -2
- package/dist/factory/infrastructure/agentLLMFactory.js.map +1 -1
- package/dist/recovery/domain/recoveryStore.d.ts +8 -0
- package/dist/recovery/domain/recoveryStore.js.map +1 -1
- package/dist/recovery/infrastructure/fileRecoveryStore.js +38 -2
- package/dist/recovery/infrastructure/fileRecoveryStore.js.map +1 -1
- package/dist/tools/domain/editResponse.d.ts +7 -2
- package/dist/tools/domain/editResponse.js +33 -11
- package/dist/tools/domain/editResponse.js.map +1 -1
- package/dist/tools/domain/preparedCall.d.ts +26 -4
- package/dist/tools/domain/preparedCall.js +1 -1
- package/dist/tools/domain/preparedCall.js.map +1 -1
- package/dist/tools/domain/v4aPatch.d.ts +6 -6
- package/dist/tools/domain/v4aPatch.js +59 -21
- package/dist/tools/domain/v4aPatch.js.map +1 -1
- package/dist/tools/infrastructure/commandOutput.d.ts +14 -0
- package/dist/tools/infrastructure/commandOutput.js +99 -0
- package/dist/tools/infrastructure/commandOutput.js.map +1 -0
- package/dist/tools/infrastructure/commandProcess.d.ts +3 -1
- package/dist/tools/infrastructure/commandProcess.js +3 -1
- package/dist/tools/infrastructure/commandProcess.js.map +1 -1
- package/dist/tools/infrastructure/commandTool.js +25 -11
- package/dist/tools/infrastructure/commandTool.js.map +1 -1
- package/dist/tools/infrastructure/trackedEdits.d.ts +3 -3
- package/dist/tools/infrastructure/trackedEdits.js +17 -11
- package/dist/tools/infrastructure/trackedEdits.js.map +1 -1
- package/package.json +1 -1
|
@@ -15,13 +15,10 @@ function lineCount(text) {
|
|
|
15
15
|
return lines.at(-1) === '' ? lines.length - 1 : lines.length;
|
|
16
16
|
}
|
|
17
17
|
/**
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
* the file again. What does not fit is counted, not shown.
|
|
18
|
+
* The lines around each change from `before` to `after`, as `after` reads them, numbered, with
|
|
19
|
+
* two lines of context. What does not fit is counted, not shown.
|
|
21
20
|
*/
|
|
22
|
-
|
|
23
|
-
if (after === '')
|
|
24
|
-
return `Changed ${path}. The file is now empty.`;
|
|
21
|
+
function changedLines(path, before, after) {
|
|
25
22
|
const shown = [];
|
|
26
23
|
let hidden = 0;
|
|
27
24
|
let line = 0;
|
|
@@ -43,11 +40,36 @@ export function describeModification(path, before, after) {
|
|
|
43
40
|
hidden++;
|
|
44
41
|
line++;
|
|
45
42
|
}
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
43
|
+
if (hidden > 0) {
|
|
44
|
+
shown.push(`[${String(hidden)} more lines of the change are not shown. Read the file to see them.]`);
|
|
45
|
+
}
|
|
46
|
+
return shown;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* What a modification returns: its path, then the lines around each change as the file now
|
|
50
|
+
* reads them. The model can check its edit without reading the file again.
|
|
51
|
+
*/
|
|
52
|
+
export function describeModification(path, before, after) {
|
|
53
|
+
if (after === '')
|
|
54
|
+
return `Changed ${path}. The file is now empty.`;
|
|
55
|
+
return [
|
|
56
|
+
`Changed ${path}. It now reads, around the change:`,
|
|
57
|
+
...changedLines(path, before, after),
|
|
58
|
+
].join('\n');
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* What an edit adds to its response when the formatter rewrote what the model sent: that the file
|
|
62
|
+
* must be read again, and those lines as it now reads them. Nothing when the formatter left the
|
|
63
|
+
* file as written.
|
|
64
|
+
*/
|
|
65
|
+
export function describeFormatting(path, written, formatted) {
|
|
66
|
+
if (written === formatted || formatted === '')
|
|
67
|
+
return '';
|
|
68
|
+
return [
|
|
69
|
+
'',
|
|
70
|
+
'The formatter rewrote some of what you sent, so read the file again before you edit it. Those lines now read:',
|
|
71
|
+
...changedLines(path, written, formatted),
|
|
72
|
+
].join('\n');
|
|
51
73
|
}
|
|
52
74
|
// What one edit's diff may hold for the person watching; the history keeps the whole change.
|
|
53
75
|
const maxDiffLines = 400;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"editResponse.js","sourceRoot":"","sources":["../../../src/tools/domain/editResponse.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,8BAA8B,CAAC;AAC3D,OAAO,EAAE,OAAO,EAAE,MAAM,iBAAiB,CAAC;AAE1C,2DAA2D;AAC3D,MAAM,YAAY,GAAG,CAAC,CAAC;AACvB,sEAAsE;AACtE,MAAM,aAAa,GAAG,EAAE,CAAC;AACzB,mGAAmG;AACnG,SAAS,SAAS,CAAC,IAAY,EAAE,IAAY;IAC5C,OAAO,OAAO,CAAC,IAAI,EAAE,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;AACtE,CAAC;AAED,SAAS,SAAS,CAAC,IAAY;IAC9B,IAAI,IAAI,KAAK,EAAE;QAAE,OAAO,CAAC,CAAC;IAC1B,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IAC/B,OAAO,KAAK,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,MAAM,CAAC;AAC9D,CAAC;AAED
|
|
1
|
+
{"version":3,"file":"editResponse.js","sourceRoot":"","sources":["../../../src/tools/domain/editResponse.ts"],"names":[],"mappings":"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","sourcesContent":["import { unifiedDiff } from '#src/diff/domain/unifiedDiff';\nimport { cutLine } from './lineLength.ts';\n\n// Enough to place a change without reading the file again.\nconst contextLines = 2;\n// What one response may show: a large rewrite is named, not repeated.\nconst maxShownLines = 60;\n/** A line of `path` as the model reads it: without the `\\r` of a CRLF ending, and not too long. */\nfunction shownLine(path: string, text: string): string {\n\treturn cutLine(path, text.endsWith('\\r') ? text.slice(0, -1) : text);\n}\n\nfunction lineCount(text: string): number {\n\tif (text === '') return 0;\n\tconst lines = text.split('\\n');\n\treturn lines.at(-1) === '' ? lines.length - 1 : lines.length;\n}\n\n/**\n * The lines around each change from `before` to `after`, as `after` reads them, numbered, with\n * two lines of context. What does not fit is counted, not shown.\n */\nfunction changedLines(path: string, before: string, after: string): string[] {\n\tconst shown: string[] = [];\n\tlet hidden = 0;\n\tlet line = 0;\n\tfor (const text of unifiedDiff(before, after, { context: contextLines }).split('\\n')) {\n\t\tconst hunk = /^@@ -\\d+(?:,\\d+)? \\+(\\d+)(?:,\\d+)? @@/.exec(text);\n\t\tif (hunk) {\n\t\t\tif (shown.length > 0 && shown.length < maxShownLines) shown.push('…');\n\t\t\tline = Number(hunk[1]);\n\t\t\tcontinue;\n\t\t}\n\t\t// Removed lines are gone from the file, and git's marker is not a line of it.\n\t\tif (!text.startsWith(' ') && !text.startsWith('+')) continue;\n\t\tif (shown.length < maxShownLines) {\n\t\t\tshown.push(`${String(line)}: ${shownLine(path, text.slice(1))}`);\n\t\t} else hidden++;\n\t\tline++;\n\t}\n\tif (hidden > 0) {\n\t\tshown.push(\n\t\t\t`[${String(hidden)} more lines of the change are not shown. Read the file to see them.]`,\n\t\t);\n\t}\n\treturn shown;\n}\n\n/**\n * What a modification returns: its path, then the lines around each change as the file now\n * reads them. The model can check its edit without reading the file again.\n */\nexport function describeModification(path: string, before: string, after: string): string {\n\tif (after === '') return `Changed ${path}. The file is now empty.`;\n\treturn [\n\t\t`Changed ${path}. It now reads, around the change:`,\n\t\t...changedLines(path, before, after),\n\t].join('\\n');\n}\n\n/**\n * What an edit adds to its response when the formatter rewrote what the model sent: that the file\n * must be read again, and those lines as it now reads them. Nothing when the formatter left the\n * file as written.\n */\nexport function describeFormatting(path: string, written: string, formatted: string): string {\n\tif (written === formatted || formatted === '') return '';\n\treturn [\n\t\t'',\n\t\t'The formatter rewrote some of what you sent, so read the file again before you edit it. Those lines now read:',\n\t\t...changedLines(path, written, formatted),\n\t].join('\\n');\n}\n\n// What one edit's diff may hold for the person watching; the history keeps the whole change.\nconst maxDiffLines = 400;\n\n/**\n * An edit as a git-style unified diff, for the person watching it happen. A missing side is a\n * file that did not exist, or no longer does. Too long a diff is cut, and says so.\n */\nexport function editDiff(\n\tpath: string,\n\tbefore: string | undefined,\n\tafter: string | undefined,\n): string {\n\tconst lines = [\n\t\t`--- ${before === undefined ? '/dev/null' : `a/${path}`}`,\n\t\t`+++ ${after === undefined ? '/dev/null' : `b/${path}`}`,\n\t\t...unifiedDiff(before ?? '', after ?? '', { context: contextLines }).split('\\n'),\n\t];\n\tif (lines.length <= maxDiffLines) return lines.join('\\n');\n\treturn [\n\t\t...lines.slice(0, maxDiffLines),\n\t\t`... ${String(lines.length - maxDiffLines)} more lines of the diff not shown`,\n\t].join('\\n');\n}\n\n/** What a creation returns: a confirmation and the size, not the content the model just sent. */\nexport function describeCreation(path: string, content: Buffer): string {\n\tconst lines = lineCount(content.toString('utf8'));\n\treturn `Created ${path} (${String(lines)} ${lines === 1 ? 'line' : 'lines'}, ${String(content.length)} bytes).`;\n}\n\nexport function describeDeletion(path: string): string {\n\treturn `Deleted ${path}.`;\n}\n"]}
|
|
@@ -2,6 +2,27 @@ import type { CommandLine, ToolRisk } from '#src/agent/domain/approval';
|
|
|
2
2
|
import type { ToolDefinition } from '#src/llm/domain/tool';
|
|
3
3
|
import type { ToolCallContext } from './readRegistry.ts';
|
|
4
4
|
import type { Tool, ToolSummarizer } from './tool.ts';
|
|
5
|
+
/** How a configured command ended, for telling a command run again for nothing. */
|
|
6
|
+
export interface CommandEnding {
|
|
7
|
+
/** It exited with code 0. */
|
|
8
|
+
passed: boolean;
|
|
9
|
+
/** How it ended, such as "exited with code 1" or "ran past its time limit". */
|
|
10
|
+
how: string;
|
|
11
|
+
/** What it printed, or the end of it, without colors, as the user is shown it. */
|
|
12
|
+
output: string;
|
|
13
|
+
/**
|
|
14
|
+
* A hash of everything it printed, line by line, without colors or the times a known test
|
|
15
|
+
* runner adds: what tells two endings apart.
|
|
16
|
+
*/
|
|
17
|
+
fingerprint: string;
|
|
18
|
+
/**
|
|
19
|
+
* What the files the run changed held when it ended, as `RunJournal.contentState` gives it.
|
|
20
|
+
* Missing when it changed something the history could not record.
|
|
21
|
+
*/
|
|
22
|
+
state?: string;
|
|
23
|
+
/** The file of every change the run had applied by then, in order, as the model names it. */
|
|
24
|
+
changed: readonly string[];
|
|
25
|
+
}
|
|
5
26
|
/**
|
|
6
27
|
* A tool call checked and fixed before anyone approves it: its risk is judged on what will
|
|
7
28
|
* really happen, and `run` does exactly that, nothing worked out again.
|
|
@@ -13,11 +34,12 @@ export interface PreparedCall {
|
|
|
13
34
|
/** For a call that runs a configured command, what it will run, fixed when prepared. */
|
|
14
35
|
readonly command?: CommandLine;
|
|
15
36
|
/**
|
|
16
|
-
* For a call that runs a configured command, how it ended once it has
|
|
17
|
-
*
|
|
18
|
-
* `undefined` before it runs, and when it never did.
|
|
37
|
+
* For a call that runs a configured command, how it ended once it has. `undefined` before it
|
|
38
|
+
* runs, and when it never did.
|
|
19
39
|
*/
|
|
20
|
-
readonly ending?: () =>
|
|
40
|
+
readonly ending?: () => CommandEnding | undefined;
|
|
41
|
+
/** A consumer's call: whatever it changes, the history does not record. */
|
|
42
|
+
readonly unrecorded?: true;
|
|
21
43
|
run(signal: AbortSignal): Promise<string>;
|
|
22
44
|
}
|
|
23
45
|
/**
|
|
@@ -21,6 +21,6 @@ export async function prepareCall(tool, input, signal, call) {
|
|
|
21
21
|
if (isPreparing(tool))
|
|
22
22
|
return tool.prepare(input, signal, call);
|
|
23
23
|
const risk = tool.risk(input);
|
|
24
|
-
return { risk, run: runSignal => tool.execute(input, runSignal) };
|
|
24
|
+
return { risk, unrecorded: true, run: runSignal => tool.execute(input, runSignal) };
|
|
25
25
|
}
|
|
26
26
|
//# sourceMappingURL=preparedCall.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"preparedCall.js","sourceRoot":"","sources":["../../../src/tools/domain/preparedCall.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"preparedCall.js","sourceRoot":"","sources":["../../../src/tools/domain/preparedCall.ts"],"names":[],"mappings":"AA+CA;;;;GAIG;AACH,MAAM,OAAO,kBAAmB,SAAQ,KAAK;IACnC,QAAQ,CAAqB;IAEtC,YAAY,OAAe,EAAE,EAAE,QAAQ,KAA4B,EAAE;QACpE,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,QAAQ,GAAG,QAAQ,CAAC;IAC1B,CAAC;CACD;AAgBD,SAAS,WAAW,CAAC,IAAe;IACnC,OAAO,SAAS,IAAI,IAAI,CAAC;AAC1B,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,WAAW,CAChC,IAAe,EACf,KAAc,EACd,MAAmB,EACnB,IAAqB;IAErB,IAAI,WAAW,CAAC,IAAI,CAAC;QAAE,OAAO,IAAI,CAAC,OAAO,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,CAAC,CAAC;IAEhE,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IAC9B,OAAO,EAAE,IAAI,EAAE,UAAU,EAAE,IAAI,EAAE,GAAG,EAAE,SAAS,CAAC,EAAE,CAAC,IAAI,CAAC,OAAO,CAAC,KAAK,EAAE,SAAS,CAAC,EAAE,CAAC;AACrF,CAAC","sourcesContent":["import type { CommandLine, ToolRisk } from '#src/agent/domain/approval';\nimport type { ToolDefinition } from '#src/llm/domain/tool';\nimport type { ToolCallContext } from './readRegistry.ts';\nimport type { Tool, ToolSummarizer } from './tool.ts';\n\n/** How a configured command ended, for telling a command run again for nothing. */\nexport interface CommandEnding {\n\t/** It exited with code 0. */\n\tpassed: boolean;\n\t/** How it ended, such as \"exited with code 1\" or \"ran past its time limit\". */\n\thow: string;\n\t/** What it printed, or the end of it, without colors, as the user is shown it. */\n\toutput: string;\n\t/**\n\t * A hash of everything it printed, line by line, without colors or the times a known test\n\t * runner adds: what tells two endings apart.\n\t */\n\tfingerprint: string;\n\t/**\n\t * What the files the run changed held when it ended, as `RunJournal.contentState` gives it.\n\t * Missing when it changed something the history could not record.\n\t */\n\tstate?: string;\n\t/** The file of every change the run had applied by then, in order, as the model names it. */\n\tchanged: readonly string[];\n}\n\n/**\n * A tool call checked and fixed before anyone approves it: its risk is judged on what will\n * really happen, and `run` does exactly that, nothing worked out again.\n */\nexport interface PreparedCall {\n\treadonly risk: ToolRisk;\n\t/** For a change to a file, what it will change as a unified diff, fixed when prepared. */\n\treadonly diff?: string;\n\t/** For a call that runs a configured command, what it will run, fixed when prepared. */\n\treadonly command?: CommandLine;\n\t/**\n\t * For a call that runs a configured command, how it ended once it has. `undefined` before it\n\t * runs, and when it never did.\n\t */\n\treadonly ending?: () => CommandEnding | undefined;\n\t/** A consumer's call: whatever it changes, the history does not record. */\n\treadonly unrecorded?: true;\n\trun(signal: AbortSignal): Promise<string>;\n}\n\n/**\n * A tool ran and produced a result the model should see as an error. Internal tools use this\n * instead of a plain error when the result also carries narrative metadata, such as a command's\n * non-zero exit code.\n */\nexport class ToolExecutionError extends Error {\n\treadonly exitCode: number | undefined;\n\n\tconstructor(message: string, { exitCode }: { exitCode?: number } = {}) {\n\t\tsuper(message);\n\t\tthis.exitCode = exitCode;\n\t}\n}\n\n/**\n * A tool built by the harness, such as a write tool, which prepares each call against the\n * state of the run before it is approved. Internal: consumers build a `Tool`.\n */\nexport interface PreparingTool extends ToolDefinition {\n\t/** Describes the model's input without exposing preparation details to the provider. */\n\tsummarize?: ToolSummarizer;\n\t/** Rejects with a message for the model when the call cannot run. */\n\tprepare(input: unknown, signal: AbortSignal, call: ToolCallContext): Promise<PreparedCall>;\n}\n\n/** What an agent can run: a consumer's `Tool`, or one the harness built. */\nexport type AgentTool = Tool | PreparingTool;\n\nfunction isPreparing(tool: AgentTool): tool is PreparingTool {\n\treturn 'prepare' in tool;\n}\n\n/**\n * Brings any tool into the one lifecycle of prepare, approve, run. A consumer's `Tool` has its\n * risk judged now, and later runs on the input as the model sent it.\n */\nexport async function prepareCall(\n\ttool: AgentTool,\n\tinput: unknown,\n\tsignal: AbortSignal,\n\tcall: ToolCallContext,\n): Promise<PreparedCall> {\n\tif (isPreparing(tool)) return tool.prepare(input, signal, call);\n\n\tconst risk = tool.risk(input);\n\treturn { risk, unrecorded: true, run: runSignal => tool.execute(input, runSignal) };\n}\n"]}
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Applies the hunks of a V4A `update_file` diff to `content`, in order, each
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* written back as it was, ending included, and an added line ends like the line before it.
|
|
2
|
+
* Applies the hunks of a V4A `update_file` diff to `content`, in order, each at its first match
|
|
3
|
+
* after the one before it and after its anchor, as Codex does: the order of the hunks says which
|
|
4
|
+
* of two alike places is meant. Each line the hunk does not change is written back as it was,
|
|
5
|
+
* ending included, and an added line ends like the line before it.
|
|
7
6
|
*
|
|
8
|
-
* @throws {EditRefusedError} naming the hunk, what it expected and
|
|
7
|
+
* @throws {EditRefusedError} naming the hunk, what it expected and the lines of the file most
|
|
8
|
+
* like it.
|
|
9
9
|
*/
|
|
10
10
|
export declare function applyUpdate(content: string, diff: string, path: string): string;
|
|
11
11
|
/**
|
|
@@ -5,6 +5,7 @@ const maxDiffBytes = 512 * 1024;
|
|
|
5
5
|
const maxHunks = 500;
|
|
6
6
|
// What an error quotes of a hunk or of the file, to fix the call without flooding the context.
|
|
7
7
|
const quotedLines = 8;
|
|
8
|
+
const nearLines = 16;
|
|
8
9
|
/** The lines a hunk expects in the file: the ones it keeps and the ones it removes. */
|
|
9
10
|
function expected(hunk) {
|
|
10
11
|
return hunk.steps.filter(step => step.kind !== 'add').map(step => step.text);
|
|
@@ -119,19 +120,58 @@ function quote(lines) {
|
|
|
119
120
|
shown.push(` … ${String(lines.length - quotedLines)} more`);
|
|
120
121
|
return shown.join('\n');
|
|
121
122
|
}
|
|
122
|
-
/**
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
123
|
+
/**
|
|
124
|
+
* The stretch of the file most like a hunk that did not match, so the model can copy its real
|
|
125
|
+
* lines: as long as the hunk, holding the most of its lines in any order, so a line changed or
|
|
126
|
+
* left out does not hide it. On a tie, the first at or after where the hunk was looked for.
|
|
127
|
+
*/
|
|
128
|
+
function near(lines, hunk, from) {
|
|
129
|
+
const old = expected(hunk);
|
|
130
|
+
const wanted = new Set(old.map(line => line.trim()).filter(line => line !== ''));
|
|
131
|
+
// The stretch slides one line at a time, counting the hunk's lines in it as they enter and
|
|
132
|
+
// leave, so a large hunk that failed costs no more than reading the file once.
|
|
133
|
+
const inStretch = new Map();
|
|
134
|
+
let score = 0;
|
|
135
|
+
const slide = (line, by) => {
|
|
136
|
+
const text = line?.trim();
|
|
137
|
+
if (text === undefined || !wanted.has(text))
|
|
138
|
+
return;
|
|
139
|
+
const count = (inStretch.get(text) ?? 0) + by;
|
|
140
|
+
inStretch.set(text, count);
|
|
141
|
+
if (count === (by === 1 ? 1 : 0))
|
|
142
|
+
score += by;
|
|
143
|
+
};
|
|
144
|
+
for (let at = 0; at < old.length; at++)
|
|
145
|
+
slide(lines[at], 1);
|
|
146
|
+
let best = { at: -1, score: 0 };
|
|
147
|
+
for (let at = 0; at < lines.length; at++) {
|
|
148
|
+
if (at > 0) {
|
|
149
|
+
slide(lines[at - 1], -1);
|
|
150
|
+
slide(lines[at + old.length - 1], 1);
|
|
151
|
+
}
|
|
152
|
+
const later = best.at < from && at >= from;
|
|
153
|
+
if (score > best.score || (score > 0 && score === best.score && later))
|
|
154
|
+
best = { at, score };
|
|
155
|
+
}
|
|
156
|
+
if (best.at === -1)
|
|
129
157
|
return '';
|
|
130
|
-
|
|
158
|
+
// From the first of the hunk's lines in the stretch to the last, with a line around them.
|
|
159
|
+
const held = (line) => line !== undefined && wanted.has(line.trim());
|
|
160
|
+
let first = best.at;
|
|
161
|
+
while (!held(lines[first]))
|
|
162
|
+
first++;
|
|
163
|
+
let last = Math.min(best.at + old.length, lines.length) - 1;
|
|
164
|
+
while (!held(lines[last]))
|
|
165
|
+
last--;
|
|
166
|
+
const start = Math.max(0, first - 1);
|
|
167
|
+
const end = Math.min(lines.length, last + 2, start + nearLines);
|
|
131
168
|
const shown = lines
|
|
132
|
-
.slice(
|
|
133
|
-
.map((line, offset) => ` ${String(
|
|
134
|
-
|
|
169
|
+
.slice(start, end)
|
|
170
|
+
.map((line, offset) => ` ${String(start + offset + 1)}: ${line}`);
|
|
171
|
+
const order = first < from
|
|
172
|
+
? '\nThose lines come before where the hunk was looked for: each hunk is found after the one before it, and after its "@@" lines.'
|
|
173
|
+
: '';
|
|
174
|
+
return `\nThe file has, near line ${String(first + 1)}:\n${shown.join('\n')}${order}`;
|
|
135
175
|
}
|
|
136
176
|
/**
|
|
137
177
|
* Where a hunk's anchor lines are, one after another, so its change is found after them. The
|
|
@@ -152,13 +192,13 @@ function seekAnchor(lines, hunk, from, path, n) {
|
|
|
152
192
|
return cursor;
|
|
153
193
|
}
|
|
154
194
|
/**
|
|
155
|
-
* Applies the hunks of a V4A `update_file` diff to `content`, in order, each
|
|
156
|
-
*
|
|
157
|
-
*
|
|
158
|
-
*
|
|
159
|
-
* written back as it was, ending included, and an added line ends like the line before it.
|
|
195
|
+
* Applies the hunks of a V4A `update_file` diff to `content`, in order, each at its first match
|
|
196
|
+
* after the one before it and after its anchor, as Codex does: the order of the hunks says which
|
|
197
|
+
* of two alike places is meant. Each line the hunk does not change is written back as it was,
|
|
198
|
+
* ending included, and an added line ends like the line before it.
|
|
160
199
|
*
|
|
161
|
-
* @throws {EditRefusedError} naming the hunk, what it expected and
|
|
200
|
+
* @throws {EditRefusedError} naming the hunk, what it expected and the lines of the file most
|
|
201
|
+
* like it.
|
|
162
202
|
*/
|
|
163
203
|
export function applyUpdate(content, diff, path) {
|
|
164
204
|
checkSize(diff, path);
|
|
@@ -181,11 +221,9 @@ export function applyUpdate(content, diff, path) {
|
|
|
181
221
|
cursor = at + added.length;
|
|
182
222
|
continue;
|
|
183
223
|
}
|
|
184
|
-
const at = seek(texts, old, { from, atEnd: hunk.atEnd, once:
|
|
224
|
+
const at = seek(texts, old, { from, atEnd: hunk.atEnd, once: false });
|
|
185
225
|
if (typeof at !== 'number') {
|
|
186
|
-
throw new EditRefusedError(
|
|
187
|
-
? `Hunk ${String(n)} of the patch for "${path}" does not match the file. It expected:\n${quote(old)}${near(texts, hunk)}`
|
|
188
|
-
: `Hunk ${String(n)} of the patch for "${path}" matches ${String(at.places)} places; add an "@@" line naming the function or more context`);
|
|
226
|
+
throw new EditRefusedError(`Hunk ${String(n)} of the patch for "${path}" does not match the file. It expected:\n${quote(old)}${near(texts, hunk, from)}`);
|
|
189
227
|
}
|
|
190
228
|
// A kept line stays as the file has it, even where the patch copied it with a space lost. An
|
|
191
229
|
// added line that takes a removed one's place takes its ending too.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"v4aPatch.js","sourceRoot":"","sources":["../../../src/tools/domain/v4aPatch.ts"],"names":[],"mappings":"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","sourcesContent":["import { EditRefusedError } from './fileEdits.ts';\nimport { addedLine, joinLines, type Line, splitLines, textsOf } from './textLines.ts';\n\n// A patch is a few changes, not a file: one far larger is more likely a mistake.\nconst maxDiffBytes = 512 * 1024;\nconst maxHunks = 500;\n// What an error quotes of a hunk or of the file, to fix the call without flooding the context.\nconst quotedLines = 8;\n\n/** One line of a hunk: kept as the file has it, removed, or added. */\ninterface Step {\n\tkind: 'keep' | 'remove' | 'add';\n\ttext: string;\n}\n\n/** One change of a V4A diff: the lines it expects, and the lines it puts in their place. */\ninterface Hunk {\n\t/** The text after `@@`, such as a function's first line, found before the change. */\n\tanchor: string | undefined;\n\tsteps: Step[];\n\t/** `*** End of File`: the change is at the end of the file. */\n\tatEnd: boolean;\n}\n\n/** The lines a hunk expects in the file: the ones it keeps and the ones it removes. */\nfunction expected(hunk: Hunk): string[] {\n\treturn hunk.steps.filter(step => step.kind !== 'add').map(step => step.text);\n}\n\nfunction changes(hunk: Hunk): boolean {\n\treturn hunk.steps.some(step => step.kind !== 'keep');\n}\n\nfunction checkSize(diff: string, path: string): void {\n\tif (Buffer.byteLength(diff) > maxDiffBytes) {\n\t\tthrow new EditRefusedError(\n\t\t\t`The patch for \"${path}\" is larger than ${String(maxDiffBytes / 1024)} KB; send smaller patches`,\n\t\t);\n\t}\n}\n\n/** The diff's lines, without the newline that ends the last one. */\nfunction diffLines(diff: string): string[] {\n\tconst lines = diff.split('\\n').map(line => (line.endsWith('\\r') ? line.slice(0, -1) : line));\n\tif (lines.at(-1) === '') lines.pop();\n\treturn lines;\n}\n\nfunction parseHunks(diff: string, path: string): Hunk[] {\n\tconst hunks: Hunk[] = [];\n\tlet current: Hunk | undefined;\n\tfor (const [index, line] of diffLines(diff).entries()) {\n\t\tif (line.startsWith('@@')) {\n\t\t\tconst anchor = line.slice(2).trim();\n\t\t\t// Two `@@` lines in a row name an anchor and then one inside it.\n\t\t\tif (current?.steps.length === 0 && !current.atEnd) {\n\t\t\t\tcurrent.anchor = [current.anchor, anchor].filter(Boolean).join('\\n') || undefined;\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tcurrent = { anchor: anchor || undefined, steps: [], atEnd: false };\n\t\t\thunks.push(current);\n\t\t\tcontinue;\n\t\t}\n\t\tif (line === '*** End of File') {\n\t\t\tif (current) current.atEnd = true;\n\t\t\tcontinue;\n\t\t}\n\t\tif (current?.atEnd) {\n\t\t\tthrow new EditRefusedError(\n\t\t\t\t`Line ${String(index + 1)} of the patch for \"${path}\" follows \"*** End of File\", which ends its hunk; start another hunk with \"@@\"`,\n\t\t\t);\n\t\t}\n\t\t// A diff may open with its first change, without `@@`.\n\t\tif (!current) {\n\t\t\tcurrent = { anchor: undefined, steps: [], atEnd: false };\n\t\t\thunks.push(current);\n\t\t}\n\t\tconst marker = line[0];\n\t\tconst text = line.slice(1);\n\t\t// An empty line is an empty context line whose space was trimmed.\n\t\tif (line === '' || marker === ' ') {\n\t\t\tcurrent.steps.push({ kind: 'keep', text });\n\t\t} else if (marker === '-') {\n\t\t\tcurrent.steps.push({ kind: 'remove', text });\n\t\t} else if (marker === '+') {\n\t\t\tcurrent.steps.push({ kind: 'add', text });\n\t\t} else {\n\t\t\tthrow new EditRefusedError(\n\t\t\t\t`Line ${String(index + 1)} of the patch for \"${path}\" starts with \"${marker ?? ''}\"; each line of a hunk starts with \" \", \"-\" or \"+\"`,\n\t\t\t);\n\t\t}\n\t}\n\tconst changing = hunks.filter(changes);\n\tif (changing.length === 0) {\n\t\tthrow new EditRefusedError(`The patch for \"${path}\" changes nothing`);\n\t}\n\tif (changing.length > maxHunks) {\n\t\tthrow new EditRefusedError(\n\t\t\t`The patch for \"${path}\" has more than ${String(maxHunks)} hunks; send smaller patches`,\n\t\t);\n\t}\n\treturn changing;\n}\n\n// Each pass forgives a little more: the model may copy a line with its trailing spaces lost.\nconst passes: ((line: string) => string)[] = [\n\tline => line,\n\tline => line.trimEnd(),\n\tline => line.trim(),\n];\n\n/**\n * Where `pattern` starts in `lines`, at or after `from`, by the first pass that finds it. `null`\n * when no pass does. With `once`, the first pass that matches must find one place, or the number\n * of places is returned; without it, the first place is taken.\n */\nfunction seek(\n\tlines: string[],\n\tpattern: string[],\n\t{ from, atEnd, once }: { from: number; atEnd: boolean; once: boolean },\n): number | null | { places: number } {\n\tconst last = lines.length - pattern.length;\n\tfor (const normalize of passes) {\n\t\tconst wanted = pattern.map(normalize);\n\t\tconst matchesAt = (start: number) =>\n\t\t\twanted.every((line, offset) => normalize(lines[start + offset] ?? '') === line);\n\t\tif (atEnd) {\n\t\t\tif (last >= from && matchesAt(last)) return last;\n\t\t\tcontinue;\n\t\t}\n\t\tconst found: number[] = [];\n\t\tfor (let start = from; start <= last; start++) {\n\t\t\tif (!matchesAt(start)) continue;\n\t\t\tif (!once) return start;\n\t\t\tfound.push(start);\n\t\t}\n\t\tif (found.length === 1) return found[0] ?? null;\n\t\tif (found.length > 1) return { places: found.length };\n\t}\n\treturn null;\n}\n\nfunction quote(lines: string[]): string {\n\tconst shown = lines.slice(0, quotedLines).map(line => ` ${line}`);\n\tif (lines.length > quotedLines) shown.push(` … ${String(lines.length - quotedLines)} more`);\n\treturn shown.join('\\n');\n}\n\n/** The real lines near where a hunk's first line appears, so the model can copy them. */\nfunction near(lines: string[], hunk: Hunk): string {\n\tconst first = expected(hunk)\n\t\t.find(line => line.trim() !== '')\n\t\t?.trim();\n\tconst at = first === undefined ? -1 : lines.findIndex(line => line.trim() === first);\n\tif (at === -1) return '';\n\tconst from = Math.max(0, at - 1);\n\tconst shown = lines\n\t\t.slice(from, from + quotedLines)\n\t\t.map((line, offset) => ` ${String(from + offset + 1)}: ${line}`);\n\treturn `\\nThe file has, near line ${String(at + 1)}:\\n${shown.join('\\n')}`;\n}\n\n/**\n * Where a hunk's anchor lines are, one after another, so its change is found after them. The\n * first must appear once; each one after it is the first that follows the one before, as V4A\n * reads `@@ class B:` then `@@ def __init__(self):`.\n */\nfunction seekAnchor(lines: string[], hunk: Hunk, from: number, path: string, n: number): number {\n\tlet cursor = from;\n\tfor (const [index, anchor] of (hunk.anchor?.split('\\n') ?? []).entries()) {\n\t\tconst at = seek(lines, [anchor], { from: cursor, atEnd: false, once: index === 0 });\n\t\tif (typeof at !== 'number') {\n\t\t\tthrow new EditRefusedError(\n\t\t\t\tat === null\n\t\t\t\t\t? `Hunk ${String(n)} of the patch for \"${path}\" names \"@@ ${anchor}\", which is not in the file after the hunks before it`\n\t\t\t\t\t: `Hunk ${String(n)} of the patch for \"${path}\" names \"@@ ${anchor}\", which appears ${String(at.places)} times; name a line that appears once, or put an \"@@\" line before it naming what holds it`,\n\t\t\t);\n\t\t}\n\t\tcursor = at + 1;\n\t}\n\treturn cursor;\n}\n\n/**\n * Applies the hunks of a V4A `update_file` diff to `content`, in order, each after the one before\n * it. A hunk with no anchor must match exactly once in the part of the file it searches, or\n * nothing is changed: a hunk is never applied where the model did not mean it. After an anchor,\n * it is the first match: the anchor already says where. Each line the hunk does not change is\n * written back as it was, ending included, and an added line ends like the line before it.\n *\n * @throws {EditRefusedError} naming the hunk, what it expected and what the file has near it.\n */\nexport function applyUpdate(content: string, diff: string, path: string): string {\n\tcheckSize(diff, path);\n\tconst text = splitLines(content);\n\tconst lines: Line[] = [...text.lines];\n\tlet cursor = 0;\n\tfor (const [index, hunk] of parseHunks(diff, path).entries()) {\n\t\tconst n = index + 1;\n\t\tconst texts = textsOf(lines);\n\t\tconst from = seekAnchor(texts, hunk, cursor, path, n);\n\t\tconst old = expected(hunk);\n\t\tif (old.length === 0) {\n\t\t\t// Only added lines: after the anchor, or at the end.\n\t\t\tconst at = hunk.anchor === undefined || hunk.atEnd ? lines.length : from;\n\t\t\tconst added: Line[] = [];\n\t\t\tfor (const step of hunk.steps) {\n\t\t\t\tadded.push(addedLine(step.text, added.at(-1) ?? lines[at - 1], text.eol));\n\t\t\t}\n\t\t\tlines.splice(at, 0, ...added);\n\t\t\tcursor = at + added.length;\n\t\t\tcontinue;\n\t\t}\n\t\tconst at = seek(texts, old, { from, atEnd: hunk.atEnd, once: hunk.anchor === undefined });\n\t\tif (typeof at !== 'number') {\n\t\t\tthrow new EditRefusedError(\n\t\t\t\tat === null\n\t\t\t\t\t? `Hunk ${String(n)} of the patch for \"${path}\" does not match the file. It expected:\\n${quote(old)}${near(texts, hunk)}`\n\t\t\t\t\t: `Hunk ${String(n)} of the patch for \"${path}\" matches ${String(at.places)} places; add an \"@@\" line naming the function or more context`,\n\t\t\t);\n\t\t}\n\t\t// A kept line stays as the file has it, even where the patch copied it with a space lost. An\n\t\t// added line that takes a removed one's place takes its ending too.\n\t\tconst replacement: Line[] = [];\n\t\tconst replaced: Line[] = [];\n\t\tlet offset = 0;\n\t\tfor (const step of hunk.steps) {\n\t\t\tif (step.kind === 'remove') {\n\t\t\t\tconst removed = lines[at + offset++];\n\t\t\t\tif (removed) replaced.push(removed);\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tif (step.kind === 'keep') {\n\t\t\t\treplaced.length = 0;\n\t\t\t\tconst kept = lines[at + offset++];\n\t\t\t\treplacement.push(kept ?? addedLine(step.text, replacement.at(-1), text.eol));\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tconst before = replaced.shift() ?? replacement.at(-1) ?? lines[at - 1];\n\t\t\treplacement.push(addedLine(step.text, before, text.eol));\n\t\t}\n\t\tlines.splice(at, old.length, ...replacement);\n\t\tcursor = at + replacement.length;\n\t}\n\treturn joinLines({ ...text, lines, finalNewline: text.finalNewline || text.lines.length === 0 });\n}\n\n/**\n * The content of a file a V4A `create_file` diff makes: every line starts with `+`. It ends with\n * a newline, as a text file does.\n *\n * @throws {EditRefusedError} when a line is not an added one.\n */\nexport function createdContent(diff: string, path: string): string {\n\tcheckSize(diff, path);\n\tconst lines = diffLines(diff).filter(line => line !== '*** End of File');\n\tfor (const [index, line] of lines.entries()) {\n\t\tif (!line.startsWith('+')) {\n\t\t\tthrow new EditRefusedError(\n\t\t\t\t`Line ${String(index + 1)} of the patch creating \"${path}\" does not start with \"+\"; every line of a new file does`,\n\t\t\t);\n\t\t}\n\t}\n\treturn joinLines({\n\t\tlines: lines.map(line => ({ text: line.slice(1), end: '\\n' })),\n\t\teol: '\\n',\n\t\tfinalNewline: true,\n\t});\n}\n"]}
|
|
1
|
+
{"version":3,"file":"v4aPatch.js","sourceRoot":"","sources":["../../../src/tools/domain/v4aPatch.ts"],"names":[],"mappings":"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","sourcesContent":["import { EditRefusedError } from './fileEdits.ts';\nimport { addedLine, joinLines, type Line, splitLines, textsOf } from './textLines.ts';\n\n// A patch is a few changes, not a file: one far larger is more likely a mistake.\nconst maxDiffBytes = 512 * 1024;\nconst maxHunks = 500;\n// What an error quotes of a hunk or of the file, to fix the call without flooding the context.\nconst quotedLines = 8;\nconst nearLines = 16;\n\n/** One line of a hunk: kept as the file has it, removed, or added. */\ninterface Step {\n\tkind: 'keep' | 'remove' | 'add';\n\ttext: string;\n}\n\n/** One change of a V4A diff: the lines it expects, and the lines it puts in their place. */\ninterface Hunk {\n\t/** The text after `@@`, such as a function's first line, found before the change. */\n\tanchor: string | undefined;\n\tsteps: Step[];\n\t/** `*** End of File`: the change is at the end of the file. */\n\tatEnd: boolean;\n}\n\n/** The lines a hunk expects in the file: the ones it keeps and the ones it removes. */\nfunction expected(hunk: Hunk): string[] {\n\treturn hunk.steps.filter(step => step.kind !== 'add').map(step => step.text);\n}\n\nfunction changes(hunk: Hunk): boolean {\n\treturn hunk.steps.some(step => step.kind !== 'keep');\n}\n\nfunction checkSize(diff: string, path: string): void {\n\tif (Buffer.byteLength(diff) > maxDiffBytes) {\n\t\tthrow new EditRefusedError(\n\t\t\t`The patch for \"${path}\" is larger than ${String(maxDiffBytes / 1024)} KB; send smaller patches`,\n\t\t);\n\t}\n}\n\n/** The diff's lines, without the newline that ends the last one. */\nfunction diffLines(diff: string): string[] {\n\tconst lines = diff.split('\\n').map(line => (line.endsWith('\\r') ? line.slice(0, -1) : line));\n\tif (lines.at(-1) === '') lines.pop();\n\treturn lines;\n}\n\nfunction parseHunks(diff: string, path: string): Hunk[] {\n\tconst hunks: Hunk[] = [];\n\tlet current: Hunk | undefined;\n\tfor (const [index, line] of diffLines(diff).entries()) {\n\t\tif (line.startsWith('@@')) {\n\t\t\tconst anchor = line.slice(2).trim();\n\t\t\t// Two `@@` lines in a row name an anchor and then one inside it.\n\t\t\tif (current?.steps.length === 0 && !current.atEnd) {\n\t\t\t\tcurrent.anchor = [current.anchor, anchor].filter(Boolean).join('\\n') || undefined;\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tcurrent = { anchor: anchor || undefined, steps: [], atEnd: false };\n\t\t\thunks.push(current);\n\t\t\tcontinue;\n\t\t}\n\t\tif (line === '*** End of File') {\n\t\t\tif (current) current.atEnd = true;\n\t\t\tcontinue;\n\t\t}\n\t\tif (current?.atEnd) {\n\t\t\tthrow new EditRefusedError(\n\t\t\t\t`Line ${String(index + 1)} of the patch for \"${path}\" follows \"*** End of File\", which ends its hunk; start another hunk with \"@@\"`,\n\t\t\t);\n\t\t}\n\t\t// A diff may open with its first change, without `@@`.\n\t\tif (!current) {\n\t\t\tcurrent = { anchor: undefined, steps: [], atEnd: false };\n\t\t\thunks.push(current);\n\t\t}\n\t\tconst marker = line[0];\n\t\tconst text = line.slice(1);\n\t\t// An empty line is an empty context line whose space was trimmed.\n\t\tif (line === '' || marker === ' ') {\n\t\t\tcurrent.steps.push({ kind: 'keep', text });\n\t\t} else if (marker === '-') {\n\t\t\tcurrent.steps.push({ kind: 'remove', text });\n\t\t} else if (marker === '+') {\n\t\t\tcurrent.steps.push({ kind: 'add', text });\n\t\t} else {\n\t\t\tthrow new EditRefusedError(\n\t\t\t\t`Line ${String(index + 1)} of the patch for \"${path}\" starts with \"${marker ?? ''}\"; each line of a hunk starts with \" \", \"-\" or \"+\"`,\n\t\t\t);\n\t\t}\n\t}\n\tconst changing = hunks.filter(changes);\n\tif (changing.length === 0) {\n\t\tthrow new EditRefusedError(`The patch for \"${path}\" changes nothing`);\n\t}\n\tif (changing.length > maxHunks) {\n\t\tthrow new EditRefusedError(\n\t\t\t`The patch for \"${path}\" has more than ${String(maxHunks)} hunks; send smaller patches`,\n\t\t);\n\t}\n\treturn changing;\n}\n\n// Each pass forgives a little more: the model may copy a line with its trailing spaces lost.\nconst passes: ((line: string) => string)[] = [\n\tline => line,\n\tline => line.trimEnd(),\n\tline => line.trim(),\n];\n\n/**\n * Where `pattern` starts in `lines`, at or after `from`, by the first pass that finds it. `null`\n * when no pass does. With `once`, the first pass that matches must find one place, or the number\n * of places is returned; without it, the first place is taken.\n */\nfunction seek(\n\tlines: string[],\n\tpattern: string[],\n\t{ from, atEnd, once }: { from: number; atEnd: boolean; once: boolean },\n): number | null | { places: number } {\n\tconst last = lines.length - pattern.length;\n\tfor (const normalize of passes) {\n\t\tconst wanted = pattern.map(normalize);\n\t\tconst matchesAt = (start: number) =>\n\t\t\twanted.every((line, offset) => normalize(lines[start + offset] ?? '') === line);\n\t\tif (atEnd) {\n\t\t\tif (last >= from && matchesAt(last)) return last;\n\t\t\tcontinue;\n\t\t}\n\t\tconst found: number[] = [];\n\t\tfor (let start = from; start <= last; start++) {\n\t\t\tif (!matchesAt(start)) continue;\n\t\t\tif (!once) return start;\n\t\t\tfound.push(start);\n\t\t}\n\t\tif (found.length === 1) return found[0] ?? null;\n\t\tif (found.length > 1) return { places: found.length };\n\t}\n\treturn null;\n}\n\nfunction quote(lines: string[]): string {\n\tconst shown = lines.slice(0, quotedLines).map(line => ` ${line}`);\n\tif (lines.length > quotedLines) shown.push(` … ${String(lines.length - quotedLines)} more`);\n\treturn shown.join('\\n');\n}\n\n/**\n * The stretch of the file most like a hunk that did not match, so the model can copy its real\n * lines: as long as the hunk, holding the most of its lines in any order, so a line changed or\n * left out does not hide it. On a tie, the first at or after where the hunk was looked for.\n */\nfunction near(lines: string[], hunk: Hunk, from: number): string {\n\tconst old = expected(hunk);\n\tconst wanted = new Set(old.map(line => line.trim()).filter(line => line !== ''));\n\t// The stretch slides one line at a time, counting the hunk's lines in it as they enter and\n\t// leave, so a large hunk that failed costs no more than reading the file once.\n\tconst inStretch = new Map<string, number>();\n\tlet score = 0;\n\tconst slide = (line: string | undefined, by: 1 | -1) => {\n\t\tconst text = line?.trim();\n\t\tif (text === undefined || !wanted.has(text)) return;\n\t\tconst count = (inStretch.get(text) ?? 0) + by;\n\t\tinStretch.set(text, count);\n\t\tif (count === (by === 1 ? 1 : 0)) score += by;\n\t};\n\tfor (let at = 0; at < old.length; at++) slide(lines[at], 1);\n\tlet best = { at: -1, score: 0 };\n\tfor (let at = 0; at < lines.length; at++) {\n\t\tif (at > 0) {\n\t\t\tslide(lines[at - 1], -1);\n\t\t\tslide(lines[at + old.length - 1], 1);\n\t\t}\n\t\tconst later = best.at < from && at >= from;\n\t\tif (score > best.score || (score > 0 && score === best.score && later)) best = { at, score };\n\t}\n\tif (best.at === -1) return '';\n\t// From the first of the hunk's lines in the stretch to the last, with a line around them.\n\tconst held = (line: string | undefined) => line !== undefined && wanted.has(line.trim());\n\tlet first = best.at;\n\twhile (!held(lines[first])) first++;\n\tlet last = Math.min(best.at + old.length, lines.length) - 1;\n\twhile (!held(lines[last])) last--;\n\tconst start = Math.max(0, first - 1);\n\tconst end = Math.min(lines.length, last + 2, start + nearLines);\n\tconst shown = lines\n\t\t.slice(start, end)\n\t\t.map((line, offset) => ` ${String(start + offset + 1)}: ${line}`);\n\tconst order =\n\t\tfirst < from\n\t\t\t? '\\nThose lines come before where the hunk was looked for: each hunk is found after the one before it, and after its \"@@\" lines.'\n\t\t\t: '';\n\treturn `\\nThe file has, near line ${String(first + 1)}:\\n${shown.join('\\n')}${order}`;\n}\n\n/**\n * Where a hunk's anchor lines are, one after another, so its change is found after them. The\n * first must appear once; each one after it is the first that follows the one before, as V4A\n * reads `@@ class B:` then `@@ def __init__(self):`.\n */\nfunction seekAnchor(lines: string[], hunk: Hunk, from: number, path: string, n: number): number {\n\tlet cursor = from;\n\tfor (const [index, anchor] of (hunk.anchor?.split('\\n') ?? []).entries()) {\n\t\tconst at = seek(lines, [anchor], { from: cursor, atEnd: false, once: index === 0 });\n\t\tif (typeof at !== 'number') {\n\t\t\tthrow new EditRefusedError(\n\t\t\t\tat === null\n\t\t\t\t\t? `Hunk ${String(n)} of the patch for \"${path}\" names \"@@ ${anchor}\", which is not in the file after the hunks before it`\n\t\t\t\t\t: `Hunk ${String(n)} of the patch for \"${path}\" names \"@@ ${anchor}\", which appears ${String(at.places)} times; name a line that appears once, or put an \"@@\" line before it naming what holds it`,\n\t\t\t);\n\t\t}\n\t\tcursor = at + 1;\n\t}\n\treturn cursor;\n}\n\n/**\n * Applies the hunks of a V4A `update_file` diff to `content`, in order, each at its first match\n * after the one before it and after its anchor, as Codex does: the order of the hunks says which\n * of two alike places is meant. Each line the hunk does not change is written back as it was,\n * ending included, and an added line ends like the line before it.\n *\n * @throws {EditRefusedError} naming the hunk, what it expected and the lines of the file most\n * like it.\n */\nexport function applyUpdate(content: string, diff: string, path: string): string {\n\tcheckSize(diff, path);\n\tconst text = splitLines(content);\n\tconst lines: Line[] = [...text.lines];\n\tlet cursor = 0;\n\tfor (const [index, hunk] of parseHunks(diff, path).entries()) {\n\t\tconst n = index + 1;\n\t\tconst texts = textsOf(lines);\n\t\tconst from = seekAnchor(texts, hunk, cursor, path, n);\n\t\tconst old = expected(hunk);\n\t\tif (old.length === 0) {\n\t\t\t// Only added lines: after the anchor, or at the end.\n\t\t\tconst at = hunk.anchor === undefined || hunk.atEnd ? lines.length : from;\n\t\t\tconst added: Line[] = [];\n\t\t\tfor (const step of hunk.steps) {\n\t\t\t\tadded.push(addedLine(step.text, added.at(-1) ?? lines[at - 1], text.eol));\n\t\t\t}\n\t\t\tlines.splice(at, 0, ...added);\n\t\t\tcursor = at + added.length;\n\t\t\tcontinue;\n\t\t}\n\t\tconst at = seek(texts, old, { from, atEnd: hunk.atEnd, once: false });\n\t\tif (typeof at !== 'number') {\n\t\t\tthrow new EditRefusedError(\n\t\t\t\t`Hunk ${String(n)} of the patch for \"${path}\" does not match the file. It expected:\\n${quote(old)}${near(texts, hunk, from)}`,\n\t\t\t);\n\t\t}\n\t\t// A kept line stays as the file has it, even where the patch copied it with a space lost. An\n\t\t// added line that takes a removed one's place takes its ending too.\n\t\tconst replacement: Line[] = [];\n\t\tconst replaced: Line[] = [];\n\t\tlet offset = 0;\n\t\tfor (const step of hunk.steps) {\n\t\t\tif (step.kind === 'remove') {\n\t\t\t\tconst removed = lines[at + offset++];\n\t\t\t\tif (removed) replaced.push(removed);\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tif (step.kind === 'keep') {\n\t\t\t\treplaced.length = 0;\n\t\t\t\tconst kept = lines[at + offset++];\n\t\t\t\treplacement.push(kept ?? addedLine(step.text, replacement.at(-1), text.eol));\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tconst before = replaced.shift() ?? replacement.at(-1) ?? lines[at - 1];\n\t\t\treplacement.push(addedLine(step.text, before, text.eol));\n\t\t}\n\t\tlines.splice(at, old.length, ...replacement);\n\t\tcursor = at + replacement.length;\n\t}\n\treturn joinLines({ ...text, lines, finalNewline: text.finalNewline || text.lines.length === 0 });\n}\n\n/**\n * The content of a file a V4A `create_file` diff makes: every line starts with `+`. It ends with\n * a newline, as a text file does.\n *\n * @throws {EditRefusedError} when a line is not an added one.\n */\nexport function createdContent(diff: string, path: string): string {\n\tcheckSize(diff, path);\n\tconst lines = diffLines(diff).filter(line => line !== '*** End of File');\n\tfor (const [index, line] of lines.entries()) {\n\t\tif (!line.startsWith('+')) {\n\t\t\tthrow new EditRefusedError(\n\t\t\t\t`Line ${String(index + 1)} of the patch creating \"${path}\" does not start with \"+\"; every line of a new file does`,\n\t\t\t);\n\t\t}\n\t}\n\treturn joinLines({\n\t\tlines: lines.map(line => ({ text: line.slice(1), end: '\\n' })),\n\t\teol: '\\n',\n\t\tfinalNewline: true,\n\t});\n}\n"]}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/** What a command printed without its colors, as the user is shown it. */
|
|
2
|
+
export declare function withoutColors(output: string): string;
|
|
3
|
+
/** One line as two runs that ended alike print it: no colors, and no time a known runner adds. */
|
|
4
|
+
export declare function normalizedLine(line: string): string;
|
|
5
|
+
/**
|
|
6
|
+
* Everything a command printed, as one hash: every line, normalized, of its standard output and
|
|
7
|
+
* its error apart, since their order between them can differ from one run to the next. Not only
|
|
8
|
+
* the end its result keeps, so two failures that end alike but differ before are told apart.
|
|
9
|
+
*/
|
|
10
|
+
export declare class OutputFingerprint {
|
|
11
|
+
private readonly streams;
|
|
12
|
+
push(stream: 'stdout' | 'stderr', chunk: Buffer): void;
|
|
13
|
+
digest(): string;
|
|
14
|
+
}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import { StringDecoder } from 'node:string_decoder';
|
|
3
|
+
// eslint-disable-next-line no-control-regex -- the escape that starts every terminal color
|
|
4
|
+
const colors = /\u001b\[[0-9;?]*[ -/]*[@-~]/g;
|
|
5
|
+
const duration = String.raw `\d+(?:\.\d+)?\s?(?:ms|s|m|h)`;
|
|
6
|
+
const clock = String.raw `\d{1,2}:\d{2}:\d{2}(?:\.\d+)?(?:\s?[AP]M)?`;
|
|
7
|
+
const anyTime = new RegExp(String.raw `\b(?:${clock}|${duration})\b`, 'g');
|
|
8
|
+
/**
|
|
9
|
+
* The lines a known runner prints only to say when or for how long it ran, each with what of it
|
|
10
|
+
* is a time. Anything else keeps its times: in `expected 1s, received 2s` they are what failed.
|
|
11
|
+
*/
|
|
12
|
+
const runnerTimes = [
|
|
13
|
+
// Vitest's and Jest's summaries, and the last line of yarn, cargo or a build.
|
|
14
|
+
{ line: /^\s*(?:Start at|Duration|Time:|Done in|Finished|finished in)\s/, time: anyTime },
|
|
15
|
+
// cargo test's result, and pytest's summary between rules of `=`.
|
|
16
|
+
{ line: /^\s*test result:|^=+ .* =+$/, time: anyTime },
|
|
17
|
+
// Mocha's counts, such as "5 passing (12ms)".
|
|
18
|
+
{ line: /^\s*\d+ (?:passing|failing|pending) \(/, time: anyTime },
|
|
19
|
+
// A test or file reported with a mark or a status, such as Vitest's "✓ a.test.ts (3 tests)
|
|
20
|
+
// 12ms", Jest's "PASS a.test.ts (5.1 s)" or Go's "--- FAIL: TestA (0.00s)": the time at its end.
|
|
21
|
+
{
|
|
22
|
+
line: /^\s*(?:[✓✔√×✗✕✘↓]|PASS\b|FAIL\b|ok\b|not ok\b|--- (?:PASS|FAIL|SKIP):)/,
|
|
23
|
+
time: new RegExp(String.raw `\(?\b${duration}\)?\s*$`),
|
|
24
|
+
},
|
|
25
|
+
// Node's test runner, whose durations have no unit.
|
|
26
|
+
{ line: /^\s*(?:[#ℹ] duration_ms|duration_ms:)\s/, time: /\d+(?:\.\d+)?\s*$/ },
|
|
27
|
+
// `tsc --watch`, which starts each line with the time.
|
|
28
|
+
{ line: new RegExp(String.raw `^\[?${clock}\]?\s`), time: new RegExp(`^\\[?${clock}\\]?`) },
|
|
29
|
+
// `prettier --write`, such as "src/a.ts 12ms (unchanged)".
|
|
30
|
+
{
|
|
31
|
+
line: new RegExp(String.raw `^\S*[/.]\S*\s${duration}(?:\s\(unchanged\))?$`),
|
|
32
|
+
time: new RegExp(String.raw `\b${duration}\b`),
|
|
33
|
+
},
|
|
34
|
+
];
|
|
35
|
+
/** What a command printed without its colors, as the user is shown it. */
|
|
36
|
+
export function withoutColors(output) {
|
|
37
|
+
return output.replace(colors, '');
|
|
38
|
+
}
|
|
39
|
+
/** One line as two runs that ended alike print it: no colors, and no time a known runner adds. */
|
|
40
|
+
export function normalizedLine(line) {
|
|
41
|
+
const plain = withoutColors(line);
|
|
42
|
+
const rule = runnerTimes.find(({ line: pattern }) => pattern.test(plain));
|
|
43
|
+
return rule ? plain.replace(rule.time, '<time>') : plain;
|
|
44
|
+
}
|
|
45
|
+
// A line longer than this is hashed as printed, colors and all, whether it came whole or in
|
|
46
|
+
// chunks: no runner prints its time in one, and what is kept of it stays bounded.
|
|
47
|
+
const longestLine = 65_536;
|
|
48
|
+
/** One stream's lines, hashed as they arrive. */
|
|
49
|
+
class StreamFingerprint {
|
|
50
|
+
decoder = new StringDecoder('utf8');
|
|
51
|
+
hash = createHash('sha256');
|
|
52
|
+
partial = '';
|
|
53
|
+
// Part of the line being read was already hashed, as printed, since it ran past `longestLine`.
|
|
54
|
+
long = false;
|
|
55
|
+
push(chunk) {
|
|
56
|
+
this.add(this.decoder.write(chunk));
|
|
57
|
+
}
|
|
58
|
+
digest() {
|
|
59
|
+
this.add(this.decoder.end());
|
|
60
|
+
if (this.partial !== '' || this.long)
|
|
61
|
+
this.line(this.partial);
|
|
62
|
+
return this.hash.digest('hex');
|
|
63
|
+
}
|
|
64
|
+
add(text) {
|
|
65
|
+
const lines = (this.partial + text).split('\n');
|
|
66
|
+
this.partial = lines.pop() ?? '';
|
|
67
|
+
for (const line of lines)
|
|
68
|
+
this.line(line);
|
|
69
|
+
if (this.partial.length > longestLine) {
|
|
70
|
+
this.hash.update(this.partial);
|
|
71
|
+
this.partial = '';
|
|
72
|
+
this.long = true;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
// A long line is hashed as it came, `\r` included, so its pieces add up to the whole line.
|
|
76
|
+
line(line) {
|
|
77
|
+
this.hash.update(this.long || line.length > longestLine
|
|
78
|
+
? `${line}\n`
|
|
79
|
+
: `${normalizedLine(line.replace(/\r$/, ''))}\n`);
|
|
80
|
+
this.long = false;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Everything a command printed, as one hash: every line, normalized, of its standard output and
|
|
85
|
+
* its error apart, since their order between them can differ from one run to the next. Not only
|
|
86
|
+
* the end its result keeps, so two failures that end alike but differ before are told apart.
|
|
87
|
+
*/
|
|
88
|
+
export class OutputFingerprint {
|
|
89
|
+
streams = { stdout: new StreamFingerprint(), stderr: new StreamFingerprint() };
|
|
90
|
+
push(stream, chunk) {
|
|
91
|
+
this.streams[stream].push(chunk);
|
|
92
|
+
}
|
|
93
|
+
digest() {
|
|
94
|
+
return createHash('sha256')
|
|
95
|
+
.update(`${this.streams.stdout.digest()}\n${this.streams.stderr.digest()}`)
|
|
96
|
+
.digest('hex');
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
//# sourceMappingURL=commandOutput.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"commandOutput.js","sourceRoot":"","sources":["../../../src/tools/infrastructure/commandOutput.ts"],"names":[],"mappings":"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","sourcesContent":["import { createHash, type Hash } from 'node:crypto';\nimport { StringDecoder } from 'node:string_decoder';\n\n// eslint-disable-next-line no-control-regex -- the escape that starts every terminal color\nconst colors = /\\u001b\\[[0-9;?]*[ -/]*[@-~]/g;\n\nconst duration = String.raw`\\d+(?:\\.\\d+)?\\s?(?:ms|s|m|h)`;\nconst clock = String.raw`\\d{1,2}:\\d{2}:\\d{2}(?:\\.\\d+)?(?:\\s?[AP]M)?`;\nconst anyTime = new RegExp(String.raw`\\b(?:${clock}|${duration})\\b`, 'g');\n\n/**\n * The lines a known runner prints only to say when or for how long it ran, each with what of it\n * is a time. Anything else keeps its times: in `expected 1s, received 2s` they are what failed.\n */\nconst runnerTimes: { line: RegExp; time: RegExp }[] = [\n\t// Vitest's and Jest's summaries, and the last line of yarn, cargo or a build.\n\t{ line: /^\\s*(?:Start at|Duration|Time:|Done in|Finished|finished in)\\s/, time: anyTime },\n\t// cargo test's result, and pytest's summary between rules of `=`.\n\t{ line: /^\\s*test result:|^=+ .* =+$/, time: anyTime },\n\t// Mocha's counts, such as \"5 passing (12ms)\".\n\t{ line: /^\\s*\\d+ (?:passing|failing|pending) \\(/, time: anyTime },\n\t// A test or file reported with a mark or a status, such as Vitest's \"✓ a.test.ts (3 tests)\n\t// 12ms\", Jest's \"PASS a.test.ts (5.1 s)\" or Go's \"--- FAIL: TestA (0.00s)\": the time at its end.\n\t{\n\t\tline: /^\\s*(?:[✓✔√×✗✕✘↓]|PASS\\b|FAIL\\b|ok\\b|not ok\\b|--- (?:PASS|FAIL|SKIP):)/,\n\t\ttime: new RegExp(String.raw`\\(?\\b${duration}\\)?\\s*$`),\n\t},\n\t// Node's test runner, whose durations have no unit.\n\t{ line: /^\\s*(?:[#ℹ] duration_ms|duration_ms:)\\s/, time: /\\d+(?:\\.\\d+)?\\s*$/ },\n\t// `tsc --watch`, which starts each line with the time.\n\t{ line: new RegExp(String.raw`^\\[?${clock}\\]?\\s`), time: new RegExp(`^\\\\[?${clock}\\\\]?`) },\n\t// `prettier --write`, such as \"src/a.ts 12ms (unchanged)\".\n\t{\n\t\tline: new RegExp(String.raw`^\\S*[/.]\\S*\\s${duration}(?:\\s\\(unchanged\\))?$`),\n\t\ttime: new RegExp(String.raw`\\b${duration}\\b`),\n\t},\n];\n\n/** What a command printed without its colors, as the user is shown it. */\nexport function withoutColors(output: string): string {\n\treturn output.replace(colors, '');\n}\n\n/** One line as two runs that ended alike print it: no colors, and no time a known runner adds. */\nexport function normalizedLine(line: string): string {\n\tconst plain = withoutColors(line);\n\tconst rule = runnerTimes.find(({ line: pattern }) => pattern.test(plain));\n\treturn rule ? plain.replace(rule.time, '<time>') : plain;\n}\n\n// A line longer than this is hashed as printed, colors and all, whether it came whole or in\n// chunks: no runner prints its time in one, and what is kept of it stays bounded.\nconst longestLine = 65_536;\n\n/** One stream's lines, hashed as they arrive. */\nclass StreamFingerprint {\n\tprivate readonly decoder = new StringDecoder('utf8');\n\tprivate readonly hash: Hash = createHash('sha256');\n\tprivate partial = '';\n\t// Part of the line being read was already hashed, as printed, since it ran past `longestLine`.\n\tprivate long = false;\n\n\tpush(chunk: Buffer): void {\n\t\tthis.add(this.decoder.write(chunk));\n\t}\n\n\tdigest(): string {\n\t\tthis.add(this.decoder.end());\n\t\tif (this.partial !== '' || this.long) this.line(this.partial);\n\t\treturn this.hash.digest('hex');\n\t}\n\n\tprivate add(text: string): void {\n\t\tconst lines = (this.partial + text).split('\\n');\n\t\tthis.partial = lines.pop() ?? '';\n\t\tfor (const line of lines) this.line(line);\n\t\tif (this.partial.length > longestLine) {\n\t\t\tthis.hash.update(this.partial);\n\t\t\tthis.partial = '';\n\t\t\tthis.long = true;\n\t\t}\n\t}\n\n\t// A long line is hashed as it came, `\\r` included, so its pieces add up to the whole line.\n\tprivate line(line: string): void {\n\t\tthis.hash.update(\n\t\t\tthis.long || line.length > longestLine\n\t\t\t\t? `${line}\\n`\n\t\t\t\t: `${normalizedLine(line.replace(/\\r$/, ''))}\\n`,\n\t\t);\n\t\tthis.long = false;\n\t}\n}\n\n/**\n * Everything a command printed, as one hash: every line, normalized, of its standard output and\n * its error apart, since their order between them can differ from one run to the next. Not only\n * the end its result keeps, so two failures that end alike but differ before are told apart.\n */\nexport class OutputFingerprint {\n\tprivate readonly streams = { stdout: new StreamFingerprint(), stderr: new StreamFingerprint() };\n\n\tpush(stream: 'stdout' | 'stderr', chunk: Buffer): void {\n\t\tthis.streams[stream].push(chunk);\n\t}\n\n\tdigest(): string {\n\t\treturn createHash('sha256')\n\t\t\t.update(`${this.streams.stdout.digest()}\\n${this.streams.stderr.digest()}`)\n\t\t\t.digest('hex');\n\t}\n}\n"]}
|
|
@@ -30,7 +30,7 @@ export interface CommandProcessResult {
|
|
|
30
30
|
*
|
|
31
31
|
* @throws {Error} when the program cannot be started, such as when it is not found; nothing ran.
|
|
32
32
|
*/
|
|
33
|
-
export declare function runCommandProcess(executable: string, args: string[], { cwd, env, timeoutMs, outputBytes, signal, gracePeriodMs, killWaitMs, recheckMs, }: {
|
|
33
|
+
export declare function runCommandProcess(executable: string, args: string[], { cwd, env, timeoutMs, outputBytes, signal, gracePeriodMs, killWaitMs, recheckMs, onOutput, }: {
|
|
34
34
|
cwd: string;
|
|
35
35
|
env: NodeJS.ProcessEnv;
|
|
36
36
|
timeoutMs: number;
|
|
@@ -42,6 +42,8 @@ export declare function runCommandProcess(executable: string, args: string[], {
|
|
|
42
42
|
killWaitMs?: number;
|
|
43
43
|
/** How often an unconfirmed group is asked about again. Defaults to 1 s. */
|
|
44
44
|
recheckMs?: number;
|
|
45
|
+
/** Given every chunk it prints as it arrives, all of it, not only the end `output` keeps. */
|
|
46
|
+
onOutput?: (stream: 'stdout' | 'stderr', chunk: Buffer) => void;
|
|
45
47
|
}): Promise<CommandProcessResult>;
|
|
46
48
|
/** The groups of the commands still running, for tests. */
|
|
47
49
|
export declare function liveGroupIds(): number[];
|
|
@@ -18,7 +18,7 @@ const drainMs = 1_000;
|
|
|
18
18
|
*
|
|
19
19
|
* @throws {Error} when the program cannot be started, such as when it is not found; nothing ran.
|
|
20
20
|
*/
|
|
21
|
-
export function runCommandProcess(executable, args, { cwd, env, timeoutMs, outputBytes, signal, gracePeriodMs = defaultGracePeriodMs, killWaitMs = defaultKillWaitMs, recheckMs = defaultRecheckMs, }) {
|
|
21
|
+
export function runCommandProcess(executable, args, { cwd, env, timeoutMs, outputBytes, signal, gracePeriodMs = defaultGracePeriodMs, killWaitMs = defaultKillWaitMs, recheckMs = defaultRecheckMs, onOutput, }) {
|
|
22
22
|
// A rejection, not a throw: a caller awaiting the result must see it as one.
|
|
23
23
|
if (signal.aborted)
|
|
24
24
|
return Promise.reject(signal.reason);
|
|
@@ -42,9 +42,11 @@ export function runCommandProcess(executable, args, { cwd, env, timeoutMs, outpu
|
|
|
42
42
|
}))));
|
|
43
43
|
child.stdout.on('data', (chunk) => {
|
|
44
44
|
tail.push(chunk);
|
|
45
|
+
onOutput?.('stdout', chunk);
|
|
45
46
|
});
|
|
46
47
|
child.stderr.on('data', (chunk) => {
|
|
47
48
|
tail.push(chunk);
|
|
49
|
+
onOutput?.('stderr', chunk);
|
|
48
50
|
});
|
|
49
51
|
const stop = (why) => {
|
|
50
52
|
// The first reason stands: a timeout during a cancellation's grace period is still a
|