mono-pilot 0.1.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 qianwan
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,87 @@
1
+ # MonoPilot
2
+
3
+ > Cursor-compatible coding agent profile powered by pi-mono.
4
+
5
+ **⚠️ Disclaimer:** This project is an independent compatibility implementation and is **not affiliated with Cursor** or Anysphere Inc.
6
+
7
+ ## MVP Scope (Current)
8
+
9
+ This repository is intentionally in a **minimal MVP** stage:
10
+
11
+ - ✅ Core toolset is wired through `mono-pilot` extensions (`read`, `rg`, `glob`, `shell`, `delete`, `apply_patch`).
12
+ - ✅ Built-in `edit` / `write` are removed from default exposed tools.
13
+ - ✅ File mutation is funneled through `apply_patch` (which internally delegates to robust pi-mono primitives).
14
+ - ✅ Additional tools can be migrated incrementally without changing the launcher architecture.
15
+
16
+ ## What ships now
17
+
18
+ - `src/cli.ts` – `mono-pilot` launcher that wraps `pi`
19
+ - `src/extensions/mono-pilot.ts` – extension entrypoint (wires the current toolset)
20
+ - `tools/*.ts` – tool implementations (`read`, `rg`, `glob`, `shell`, `delete`, `apply_patch`)
21
+ - `tools/*.md` – tool prompt specs injected at runtime
22
+
23
+ ## Local development setup
24
+
25
+ ```bash
26
+ git clone https://github.com/qianwan/mono-pilot.git
27
+ cd mono-pilot
28
+ npm install
29
+ npm run build
30
+ ```
31
+
32
+ ## Use in any repository
33
+
34
+ ```bash
35
+ # Run directly without global install
36
+ cd /path/to/your/project
37
+ npx mono-pilot
38
+
39
+ # Or install globally
40
+ npm install -g mono-pilot
41
+ mono-pilot
42
+ ```
43
+
44
+ ## Usage
45
+
46
+ ```bash
47
+ # Interactive
48
+ mono-pilot
49
+
50
+ # One-shot prompt
51
+ mono-pilot -p "Refactor this module"
52
+
53
+ # Continue previous session
54
+ mono-pilot --continue
55
+ ```
56
+
57
+ By default, `mono-pilot` launches pi with:
58
+
59
+ - `--no-extensions`
60
+ - `--extension <mono-pilot extension>`
61
+ - `--tools read,bash,grep,find,ls`
62
+
63
+ So the exposed write path is `apply_patch`, not built-in `edit` / `write`.
64
+ If you pass `--tools` manually and include `edit`/`write`, MonoPilot strips them automatically.
65
+
66
+ ## Publishing
67
+
68
+ ```bash
69
+ # 1) Validate and build
70
+ npm run check
71
+ npm run build
72
+
73
+ # 2) Verify package contents
74
+ npm pack --dry-run
75
+
76
+ # 3) Publish
77
+ npm publish
78
+ ```
79
+
80
+ ## Roadmap
81
+
82
+ - Gradually migrate other Cursor-style tools from `tools/`
83
+ - Keep compatibility behavior focused and testable, one tool at a time
84
+
85
+ ---
86
+
87
+ *Not affiliated with Cursor or Anysphere Inc. Cursor is a trademark of Anysphere Inc.*
@@ -0,0 +1,85 @@
1
+ #!/usr/bin/env node
2
+ import { spawn } from "node:child_process";
3
+ import { dirname, resolve } from "node:path";
4
+ import process from "node:process";
5
+ import { fileURLToPath } from "node:url";
6
+ const DEFAULT_TOOLS = "ls";
7
+ const TOOL_BLACKLIST = new Set(["edit", "write", "grep", "read", "glob", "bash"]);
8
+ function hasFlag(args, names) {
9
+ for (let i = 0; i < args.length; i++) {
10
+ const arg = args[i];
11
+ if (names.includes(arg))
12
+ return true;
13
+ for (const name of names) {
14
+ if (arg.startsWith(`${name}=`))
15
+ return true;
16
+ }
17
+ }
18
+ return false;
19
+ }
20
+ function sanitizeToolList(rawValue) {
21
+ const tools = rawValue
22
+ .split(",")
23
+ .map((tool) => tool.trim())
24
+ .filter(Boolean)
25
+ .filter((tool) => !TOOL_BLACKLIST.has(tool));
26
+ if (tools.length === 0) {
27
+ return DEFAULT_TOOLS;
28
+ }
29
+ return Array.from(new Set(tools)).join(",");
30
+ }
31
+ function sanitizeToolsArgs(args) {
32
+ const sanitized = [];
33
+ for (let i = 0; i < args.length; i++) {
34
+ const arg = args[i];
35
+ if (arg === "--tools") {
36
+ const raw = args[i + 1] ?? "";
37
+ sanitized.push("--tools", sanitizeToolList(raw));
38
+ i++;
39
+ continue;
40
+ }
41
+ if (arg.startsWith("--tools=")) {
42
+ const raw = arg.slice("--tools=".length);
43
+ sanitized.push(`--tools=${sanitizeToolList(raw)}`);
44
+ continue;
45
+ }
46
+ sanitized.push(arg);
47
+ }
48
+ return sanitized;
49
+ }
50
+ function buildPiArgs(userArgs) {
51
+ const here = dirname(fileURLToPath(import.meta.url));
52
+ const extensionPath = resolve(here, "extensions", "mono-pilot.js");
53
+ const sanitizedUserArgs = sanitizeToolsArgs(userArgs);
54
+ const args = ["--no-extensions", "--extension", extensionPath];
55
+ if (!hasFlag(sanitizedUserArgs, ["--tools", "--no-tools"])) {
56
+ args.push("--tools", DEFAULT_TOOLS);
57
+ }
58
+ return [...args, ...sanitizedUserArgs];
59
+ }
60
+ function resolvePiCliPath() {
61
+ const codingAgentEntryUrl = import.meta.resolve("@mariozechner/pi-coding-agent");
62
+ const codingAgentEntryPath = fileURLToPath(codingAgentEntryUrl);
63
+ return resolve(dirname(codingAgentEntryPath), "cli.js");
64
+ }
65
+ function main() {
66
+ const userArgs = process.argv.slice(2);
67
+ const piArgs = buildPiArgs(userArgs);
68
+ const piCliPath = resolvePiCliPath();
69
+ const child = spawn(process.execPath, [piCliPath, ...piArgs], {
70
+ stdio: "inherit",
71
+ env: process.env,
72
+ });
73
+ child.on("exit", (code, signal) => {
74
+ if (signal) {
75
+ process.kill(process.pid, signal);
76
+ return;
77
+ }
78
+ process.exit(code ?? 1);
79
+ });
80
+ child.on("error", (error) => {
81
+ console.error(`[mono-pilot] Failed to launch pi: ${error.message}`);
82
+ process.exit(1);
83
+ });
84
+ }
85
+ main();
@@ -0,0 +1,19 @@
1
+ import shellExtension from "../../tools/shell.js";
2
+ import globExtension from "../../tools/glob.js";
3
+ import rgExtension from "../../tools/rg.js";
4
+ import readFileExtension from "../../tools/read-file.js";
5
+ import deleteExtension from "../../tools/delete.js";
6
+ import applyPatchExtension from "../../tools/apply-patch.js";
7
+ const toolExtensions = [
8
+ shellExtension,
9
+ globExtension,
10
+ rgExtension,
11
+ readFileExtension,
12
+ deleteExtension,
13
+ applyPatchExtension,
14
+ ];
15
+ export default function monoPilotExtension(pi) {
16
+ for (const register of toolExtensions) {
17
+ register(pi);
18
+ }
19
+ }
@@ -0,0 +1,31 @@
1
+ # Tools
2
+
3
+ This directory stores the tool layer for `mono-pilot`.
4
+
5
+ ## Structure
6
+
7
+ Each tool is split into two files:
8
+
9
+ - `*.ts`: tool implementation (registration, validation, execution)
10
+ - `*.md`: tool spec text injected into the runtime prompt
11
+
12
+ ## Current tools
13
+
14
+ - `apply-patch.ts` / `apply-patch.md` (`apply_patch`)
15
+ - Apply single-file patches in `*** Begin Patch` format.
16
+ - `read-file.ts` / `read-file.md` (`read`)
17
+ - Read file content with truncation and pagination behavior.
18
+ - `rg.ts` / `rg.md` (`rg`)
19
+ - Search file content with ripgrep.
20
+ - `glob.ts` / `glob.md` (`Glob`)
21
+ - Find paths by glob pattern.
22
+ - `shell.ts` / `shell.md` (`shell`)
23
+ - Execute shell commands in the workspace.
24
+ - `delete.ts` / `delete.md` (`Delete`)
25
+ - Delete files or directories.
26
+
27
+ ## Maintenance rules
28
+
29
+ - Keep each `*.ts` and `*.md` pair aligned.
30
+ - When adding/removing tools, update this file in the same commit.
31
+ - Keep wording concrete and implementation-focused.
@@ -0,0 +1,337 @@
1
+ /**
2
+ * Apply Patch Tool Example
3
+ *
4
+ * Adds an `apply_patch` custom tool that applies a single-file patch document
5
+ * in the familiar "*** Begin Patch" format.
6
+ *
7
+ * This is intentionally low-intrusion:
8
+ * - it does NOT modify built-in tools
9
+ * - it delegates actual file mutations to built-in `write` and `edit` tools
10
+ */
11
+ import { mkdir, readFile, rename } from "node:fs/promises";
12
+ import { dirname, isAbsolute } from "node:path";
13
+ import { fileURLToPath } from "node:url";
14
+ import { createEditTool, createWriteTool } from "@mariozechner/pi-coding-agent";
15
+ import { Text } from "@mariozechner/pi-tui";
16
+ import { Type } from "@sinclair/typebox";
17
+ const BEGIN_PATCH = "*** Begin Patch";
18
+ const END_PATCH = "*** End Patch";
19
+ const ADD_FILE = "*** Add File: ";
20
+ const UPDATE_FILE = "*** Update File: ";
21
+ const MOVE_TO = "*** Move to: ";
22
+ const END_OF_FILE = "*** End of File";
23
+ const APPLY_PATCH_PROMPT_MARKER = "## ApplyPatch";
24
+ const APPLY_PATCH_DOC_PATH = fileURLToPath(new URL("./apply-patch.md", import.meta.url));
25
+ const MAX_RENDER_CALL_LINES = 24;
26
+ const MAX_RENDER_CALL_LINE_CHARS = 180;
27
+ const applyPatchSchema = Type.Object({
28
+ patch: Type.String({
29
+ description: "Single-file patch document in ApplyPatch format. Must start with *** Begin Patch and end with *** End Patch.",
30
+ }),
31
+ });
32
+ function normalizeLineEndings(text) {
33
+ return text.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
34
+ }
35
+ function buildPatchPreview(patch) {
36
+ const normalized = normalizeLineEndings(patch).trimEnd();
37
+ if (!normalized)
38
+ return "(empty patch)";
39
+ const lines = normalized.split("\n");
40
+ const previewLines = lines.slice(0, MAX_RENDER_CALL_LINES).map((line) => {
41
+ if (line.length <= MAX_RENDER_CALL_LINE_CHARS)
42
+ return line;
43
+ return `${line.slice(0, MAX_RENDER_CALL_LINE_CHARS - 1)}…`;
44
+ });
45
+ if (lines.length > MAX_RENDER_CALL_LINES) {
46
+ previewLines.push(`... (${lines.length - MAX_RENDER_CALL_LINES} more line(s))`);
47
+ }
48
+ return previewLines.join("\n");
49
+ }
50
+ function normalizeAddFileLines(lines) {
51
+ const nonEmpty = lines.filter((line) => line.length > 0);
52
+ if (nonEmpty.length === 0)
53
+ return lines;
54
+ // Some models emit "+ " as a visual separator in Add File lines.
55
+ // If all non-empty lines share this one-space prefix, strip it once.
56
+ const allNonEmptyStartWithSpace = nonEmpty.every((line) => line.startsWith(" "));
57
+ if (!allNonEmptyStartWithSpace)
58
+ return lines;
59
+ return lines.map((line) => (line.startsWith(" ") ? line.slice(1) : line));
60
+ }
61
+ function normalizePatchPath(rawPath) {
62
+ const trimmed = rawPath.trim();
63
+ const withoutAt = trimmed.startsWith("@") ? trimmed.slice(1) : trimmed;
64
+ if (!withoutAt) {
65
+ throw new Error("Patch file path cannot be empty");
66
+ }
67
+ if (!isAbsolute(withoutAt)) {
68
+ throw new Error(`Patch path must be absolute: ${rawPath}`);
69
+ }
70
+ return withoutAt;
71
+ }
72
+ function isFileHeader(line) {
73
+ return line.startsWith(ADD_FILE) || line.startsWith(UPDATE_FILE);
74
+ }
75
+ function parseAddFile(lines, startIndex) {
76
+ const header = lines[startIndex];
77
+ const rawPath = header.slice(ADD_FILE.length);
78
+ const path = normalizePatchPath(rawPath);
79
+ const addLines = [];
80
+ let i = startIndex + 1;
81
+ while (i < lines.length && lines[i] !== END_PATCH) {
82
+ const line = lines[i];
83
+ if (!line.startsWith("+")) {
84
+ throw new Error(`Add File only allows '+' lines, got: ${line}`);
85
+ }
86
+ addLines.push(line.slice(1));
87
+ i++;
88
+ }
89
+ if (addLines.length === 0) {
90
+ throw new Error("Add File operation requires at least one '+' content line");
91
+ }
92
+ const normalizedLines = normalizeAddFileLines(addLines);
93
+ return {
94
+ operation: { kind: "add", path, lines: normalizedLines },
95
+ nextIndex: i,
96
+ };
97
+ }
98
+ function parseUpdateFile(lines, startIndex) {
99
+ const header = lines[startIndex];
100
+ const rawPath = header.slice(UPDATE_FILE.length);
101
+ const path = normalizePatchPath(rawPath);
102
+ let moveTo;
103
+ let i = startIndex + 1;
104
+ if (i < lines.length && lines[i].startsWith(MOVE_TO)) {
105
+ moveTo = normalizePatchPath(lines[i].slice(MOVE_TO.length));
106
+ i++;
107
+ }
108
+ const hunks = [];
109
+ let currentHeaders;
110
+ let currentLines = [];
111
+ const flushCurrentHunk = () => {
112
+ if (!currentHeaders)
113
+ return;
114
+ hunks.push({
115
+ headers: currentHeaders,
116
+ lines: currentLines,
117
+ });
118
+ currentHeaders = undefined;
119
+ currentLines = [];
120
+ };
121
+ while (i < lines.length && lines[i] !== END_PATCH) {
122
+ const line = lines[i];
123
+ if (line === END_OF_FILE) {
124
+ i++;
125
+ break;
126
+ }
127
+ if (line.startsWith("@@")) {
128
+ if (!currentHeaders) {
129
+ currentHeaders = [line];
130
+ }
131
+ else if (currentLines.length === 0) {
132
+ // Support multiple consecutive @@ headers to narrow context.
133
+ currentHeaders.push(line);
134
+ }
135
+ else {
136
+ flushCurrentHunk();
137
+ currentHeaders = [line];
138
+ }
139
+ i++;
140
+ continue;
141
+ }
142
+ const marker = line[0];
143
+ if (marker === " " || marker === "+" || marker === "-") {
144
+ if (!currentHeaders) {
145
+ throw new Error(`Update File change line encountered before hunk header @@: ${line}`);
146
+ }
147
+ currentLines.push(line);
148
+ i++;
149
+ continue;
150
+ }
151
+ if (isFileHeader(line)) {
152
+ throw new Error("Patch must contain exactly one file operation");
153
+ }
154
+ throw new Error(`Invalid update patch line: ${line}`);
155
+ }
156
+ flushCurrentHunk();
157
+ return {
158
+ operation: { kind: "update", path, moveTo, hunks },
159
+ nextIndex: i,
160
+ };
161
+ }
162
+ function parsePatchDocument(patchText) {
163
+ const normalized = normalizeLineEndings(patchText);
164
+ const lines = normalized.split("\n");
165
+ if (lines.length === 0 || lines[0] !== BEGIN_PATCH) {
166
+ throw new Error(`Patch must start with "${BEGIN_PATCH}"`);
167
+ }
168
+ let index = 1;
169
+ if (index >= lines.length) {
170
+ throw new Error("Patch is incomplete");
171
+ }
172
+ const opHeader = lines[index] ?? "";
173
+ if (!isFileHeader(opHeader)) {
174
+ throw new Error(`Expected "${ADD_FILE}" or "${UPDATE_FILE}", got: ${opHeader}`);
175
+ }
176
+ const parsed = opHeader.startsWith(ADD_FILE) ? parseAddFile(lines, index) : parseUpdateFile(lines, index);
177
+ index = parsed.nextIndex;
178
+ if (index >= lines.length || lines[index] !== END_PATCH) {
179
+ throw new Error(`Patch must end with "${END_PATCH}"`);
180
+ }
181
+ for (let i = index + 1; i < lines.length; i++) {
182
+ if (lines[i] !== "") {
183
+ throw new Error(`Unexpected trailing content after "${END_PATCH}"`);
184
+ }
185
+ }
186
+ return { operation: parsed.operation };
187
+ }
188
+ function buildReplacementTexts(hunk) {
189
+ const oldLines = [];
190
+ const newLines = [];
191
+ for (const line of hunk.lines) {
192
+ const marker = line[0];
193
+ const text = line.slice(1);
194
+ if (marker === " " || marker === "-")
195
+ oldLines.push(text);
196
+ if (marker === " " || marker === "+")
197
+ newLines.push(text);
198
+ }
199
+ return {
200
+ oldText: oldLines.join("\n"),
201
+ newText: newLines.join("\n"),
202
+ };
203
+ }
204
+ function hunkHasChanges(hunk) {
205
+ return hunk.lines.some((line) => {
206
+ const marker = line[0];
207
+ return marker === "+" || marker === "-";
208
+ });
209
+ }
210
+ function parseFirstChangedLine(details) {
211
+ if (typeof details !== "object" || details === null)
212
+ return undefined;
213
+ const record = details;
214
+ const value = record.firstChangedLine;
215
+ return typeof value === "number" && Number.isInteger(value) ? value : undefined;
216
+ }
217
+ export default function (pi) {
218
+ let applyPatchSpecCache;
219
+ const getApplyPatchSpec = async () => {
220
+ if (applyPatchSpecCache !== undefined) {
221
+ return applyPatchSpecCache ?? undefined;
222
+ }
223
+ try {
224
+ const doc = await readFile(APPLY_PATCH_DOC_PATH, "utf-8");
225
+ applyPatchSpecCache = doc.trim();
226
+ return applyPatchSpecCache;
227
+ }
228
+ catch {
229
+ // Best effort. The tool still works without prompt injection.
230
+ applyPatchSpecCache = null;
231
+ return undefined;
232
+ }
233
+ };
234
+ pi.on("before_agent_start", async (event) => {
235
+ if (event.systemPrompt.includes(APPLY_PATCH_PROMPT_MARKER)) {
236
+ return;
237
+ }
238
+ const spec = await getApplyPatchSpec();
239
+ if (!spec)
240
+ return;
241
+ return {
242
+ systemPrompt: `${event.systemPrompt}\n\n${APPLY_PATCH_PROMPT_MARKER}\n\n${spec}`,
243
+ };
244
+ });
245
+ pi.registerTool({
246
+ name: "apply_patch",
247
+ label: "Apply Patch",
248
+ description: "Apply a single-file patch in *** Begin Patch format. Supports *** Add File and *** Update File operations.",
249
+ parameters: applyPatchSchema,
250
+ renderCall(args, theme) {
251
+ const patch = typeof args.patch === "string" ? args.patch : "";
252
+ const preview = buildPatchPreview(patch);
253
+ let text = theme.fg("toolTitle", theme.bold("apply_patch"));
254
+ text += ` ${theme.fg("muted", "(patch input)")}`;
255
+ text += `\n${theme.fg("toolOutput", preview)}`;
256
+ return new Text(text, 0, 0);
257
+ },
258
+ async execute(toolCallId, params, signal, _onUpdate, ctx) {
259
+ const parsed = parsePatchDocument(params.patch);
260
+ const writeTool = createWriteTool(ctx.cwd);
261
+ const editTool = createEditTool(ctx.cwd);
262
+ if (parsed.operation.kind === "add") {
263
+ const content = parsed.operation.lines.join("\n");
264
+ await writeTool.execute(`${toolCallId}:add`, {
265
+ path: parsed.operation.path,
266
+ content,
267
+ }, signal);
268
+ const details = {
269
+ operation: "add",
270
+ path: parsed.operation.path,
271
+ bytesWritten: Buffer.byteLength(content, "utf-8"),
272
+ };
273
+ return {
274
+ content: [{ type: "text", text: `Applied patch: added ${parsed.operation.path}` }],
275
+ details,
276
+ };
277
+ }
278
+ const firstChangedLines = [];
279
+ let appliedHunks = 0;
280
+ let noopHunks = 0;
281
+ for (let i = 0; i < parsed.operation.hunks.length; i++) {
282
+ const hunk = parsed.operation.hunks[i];
283
+ if (!hunkHasChanges(hunk)) {
284
+ noopHunks++;
285
+ continue;
286
+ }
287
+ const replacement = buildReplacementTexts(hunk);
288
+ if (replacement.oldText === replacement.newText) {
289
+ noopHunks++;
290
+ continue;
291
+ }
292
+ const result = await editTool.execute(`${toolCallId}:hunk:${i + 1}`, {
293
+ path: parsed.operation.path,
294
+ oldText: replacement.oldText,
295
+ newText: replacement.newText,
296
+ }, signal);
297
+ appliedHunks++;
298
+ const firstChangedLine = parseFirstChangedLine(result.details);
299
+ if (firstChangedLine !== undefined) {
300
+ firstChangedLines.push(firstChangedLine);
301
+ }
302
+ }
303
+ let movedTo;
304
+ if (parsed.operation.moveTo && parsed.operation.moveTo !== parsed.operation.path) {
305
+ await mkdir(dirname(parsed.operation.moveTo), { recursive: true });
306
+ await rename(parsed.operation.path, parsed.operation.moveTo);
307
+ movedTo = parsed.operation.moveTo;
308
+ }
309
+ const details = {
310
+ operation: "update",
311
+ path: parsed.operation.path,
312
+ moveTo: movedTo,
313
+ hunkCount: parsed.operation.hunks.length,
314
+ appliedHunks,
315
+ noopHunks,
316
+ firstChangedLines,
317
+ };
318
+ const suffix = [];
319
+ if (noopHunks > 0) {
320
+ suffix.push(`skipped ${noopHunks} no-op hunk(s)`);
321
+ }
322
+ if (movedTo) {
323
+ suffix.push(`moved to ${movedTo}`);
324
+ }
325
+ const suffixText = suffix.length > 0 ? ` (${suffix.join(", ")})` : "";
326
+ return {
327
+ content: [
328
+ {
329
+ type: "text",
330
+ text: `Applied patch: updated ${parsed.operation.path} with ${appliedHunks} hunk(s)${suffixText}.`,
331
+ },
332
+ ],
333
+ details,
334
+ };
335
+ },
336
+ });
337
+ }
@@ -0,0 +1,93 @@
1
+ // Use this tool to edit files.
2
+ // Your patch language is a stripped-down, file-oriented diff format designed to be easy to parse and safe to apply. You can think of it as a high-level envelope:
3
+ //
4
+ // *** Begin Patch
5
+ // [ one file section ]
6
+ // *** End Patch
7
+ //
8
+ // Within that envelope, you get one file operation.
9
+ // You MUST include a header to specify the action you are taking.
10
+ // Each operation starts with one of two headers:
11
+ //
12
+ // *** Add File: <path> - create a new file. Every following line is a + line (the initial contents).
13
+ // *** Update File: <path> - patch an existing file in place (optionally with a rename).
14
+ //
15
+ // Then one or more "hunks", each introduced by @@ (optionally followed by a hunk header).
16
+ // Within a hunk each line starts with:
17
+ //
18
+ // For instructions on [context_before] and [context_after]:
19
+ // - By default, show 3 lines of code immediately above and 3 lines immediately below each change. If a change is within 3 lines of a previous change, do NOT duplicate the first change's [context_after] lines in the second change's [context_before] lines.
20
+ // - If 3 lines of context is insufficient to uniquely identify the snippet of code within the file, use the @@ operator to indicate the class or function to which the snippet belongs. For instance, we might have:
21
+ // @@ class BaseClass
22
+ // [3 lines of pre-context]
23
+ // - [old_code]
24
+ // + [new_code]
25
+ // [3 lines of post-context]
26
+ //
27
+ // - If a code block is repeated so many times in a class or function such that even a single `@@` statement and 3 lines of context cannot uniquely identify the snippet of code, you can use multiple `@@` statements to jump to the right context. For instance:
28
+ //
29
+ // @@ class BaseClass
30
+ // @@ def method():
31
+ // [3 lines of pre-context]
32
+ // - [old_code]
33
+ // + [new_code]
34
+ // [3 lines of post-context]
35
+ //
36
+ // The full grammar definition is below:
37
+ // Patch := Begin { FileOp } End
38
+ // Begin := "*** Begin Patch" NEWLINE
39
+ // End := "*** End Patch" NEWLINE
40
+ // FileOp := AddFile | UpdateFile
41
+ // AddFile := "*** Add File: " path NEWLINE { "+" line NEWLINE }
42
+ // UpdateFile := "*** Update File: " path NEWLINE { Hunk }
43
+ // Hunk := "@@" [ header ] NEWLINE { HunkLine } [ "*** End of File" NEWLINE ]
44
+ // HunkLine := (" " | "-" | "+") text NEWLINE
45
+ //
46
+ // Example for Update File:
47
+ // *** Begin Patch
48
+ // *** Update File: pygorithm/searching/binary_search.py
49
+ // @@ class BaseClass
50
+ // @@ def search():
51
+ // - pass
52
+ // + raise NotImplementedError()
53
+ //
54
+ // @@ class Subclass
55
+ // @@ def search():
56
+ // - pass
57
+ // + raise NotImplementedError()
58
+ // *** End Patch
59
+ //
60
+ // Example for Add File:
61
+ // *** Begin Patch
62
+ // *** Add File: [path/to/file]
63
+ // + [new_code]
64
+ // *** End Patch
65
+ //
66
+ // It is important to remember:
67
+ // - You must only include one file per call
68
+ // - You must include a header with your intended action (Add/Update)
69
+ // - You must prefix new lines with ` +` even when creating a new file
70
+ //
71
+ // All file paths must be absolute paths. Make sure to read the file before applying a patch to get the latest file content, unless you are creating a new file.
72
+ //
73
+ // IMPORTANT: This tool only accepts string inputs that obey the lark grammar start: begin_patch hunk end_patch
74
+ // begin_patch: "*** Begin Patch" LF
75
+ // end_patch: "*** End Patch" LF?
76
+ //
77
+ // hunk: add_hunk | update_hunk
78
+ // add_hunk: "*** Add File: " filename LF add_line+
79
+ // update_hunk: "*** Update File: " filename LF change?
80
+ //
81
+ // filename: /(.+)/
82
+ // add_line: "+" /(.*)/ LF -> line
83
+ //
84
+ // change: (change_context | change_line)+ eof_line?
85
+ //
86
+ // change_context: ("@@" | "@@ " /(.+)/) LF
87
+ // change_line: ("+" | "-" | " ") /(.*)/ LF
88
+ // eof_line: "*** End of File" LF
89
+ //
90
+ // %import common.LF
91
+ // . You must reason carefully about the input and make sure it obeys the grammar.
92
+ // IMPORTANT: Do NOT call this tool in parallel with other tools.
93
+ type ApplyPatch = (FREEFORM) => any;