mono-pilot 0.2.10 → 0.2.13

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.
Files changed (155) hide show
  1. package/README.md +260 -2
  2. package/dist/src/agents-paths.js +36 -0
  3. package/dist/src/brief/blocks.js +83 -0
  4. package/dist/src/brief/defaults.js +60 -0
  5. package/dist/src/brief/frontmatter.js +53 -0
  6. package/dist/src/brief/paths.js +10 -0
  7. package/dist/src/brief/reflection.js +27 -0
  8. package/dist/src/cli.js +62 -5
  9. package/dist/src/cluster/bus.js +102 -0
  10. package/dist/src/cluster/follower.js +137 -0
  11. package/dist/src/cluster/init.js +182 -0
  12. package/dist/src/cluster/leader.js +97 -0
  13. package/dist/src/cluster/log.js +49 -0
  14. package/dist/src/cluster/protocol.js +34 -0
  15. package/dist/src/cluster/services/bus.js +243 -0
  16. package/dist/src/cluster/services/embedding.js +12 -0
  17. package/dist/src/cluster/socket.js +86 -0
  18. package/dist/src/cluster/test-bus.js +175 -0
  19. package/dist/src/cluster_v2/connection-lifecycle.js +31 -0
  20. package/dist/src/cluster_v2/connection-lifecycle.test.js +24 -0
  21. package/dist/src/cluster_v2/connection.js +159 -0
  22. package/dist/src/cluster_v2/connection.test.js +55 -0
  23. package/dist/src/cluster_v2/events.js +102 -0
  24. package/dist/src/cluster_v2/index.js +2 -0
  25. package/dist/src/cluster_v2/observability.js +99 -0
  26. package/dist/src/cluster_v2/observability.test.js +46 -0
  27. package/dist/src/cluster_v2/rpc.js +389 -0
  28. package/dist/src/cluster_v2/rpc.test.js +110 -0
  29. package/dist/src/cluster_v2/runtime.failover.integration.test.js +156 -0
  30. package/dist/src/cluster_v2/runtime.js +531 -0
  31. package/dist/src/cluster_v2/runtime.lease-compromise.integration.test.js +91 -0
  32. package/dist/src/cluster_v2/runtime.lifecycle.integration.test.js +225 -0
  33. package/dist/src/cluster_v2/services/bus.integration.test.js +140 -0
  34. package/dist/src/cluster_v2/services/bus.js +450 -0
  35. package/dist/src/cluster_v2/services/discord/auth-store.js +82 -0
  36. package/dist/src/cluster_v2/services/discord/collector.js +569 -0
  37. package/dist/src/cluster_v2/services/discord/index.js +1 -0
  38. package/dist/src/cluster_v2/services/discord/oauth.js +87 -0
  39. package/dist/src/cluster_v2/services/discord/rpc-client.js +325 -0
  40. package/dist/src/cluster_v2/services/embedding.js +66 -0
  41. package/dist/src/cluster_v2/services/registry-cache.js +107 -0
  42. package/dist/src/cluster_v2/services/registry-cache.test.js +66 -0
  43. package/dist/src/cluster_v2/services/registry.js +36 -0
  44. package/dist/src/cluster_v2/services/twitter/collector.js +1055 -0
  45. package/dist/src/cluster_v2/services/twitter/index.js +1 -0
  46. package/dist/src/config/digest.js +78 -0
  47. package/dist/src/config/discord.js +143 -0
  48. package/dist/src/config/image-gen.js +48 -0
  49. package/dist/src/config/mono-pilot.js +31 -0
  50. package/dist/src/config/twitter.js +100 -0
  51. package/dist/src/extensions/cluster.js +311 -0
  52. package/dist/src/extensions/commands/build-memory.js +76 -0
  53. package/dist/src/extensions/commands/digest/backfill.js +779 -0
  54. package/dist/src/extensions/commands/digest/index.js +1133 -0
  55. package/dist/src/extensions/commands/image-model.js +214 -0
  56. package/dist/src/extensions/game/bus-injection.js +47 -0
  57. package/dist/src/extensions/game/identity.js +83 -0
  58. package/dist/src/extensions/game/mailbox.js +61 -0
  59. package/dist/src/extensions/game/system-prompt.js +134 -0
  60. package/dist/src/extensions/game/tools.js +28 -0
  61. package/dist/src/extensions/lifecycle.js +337 -0
  62. package/dist/src/extensions/mode-runtime.js +26 -2
  63. package/dist/src/extensions/mono-game.js +66 -0
  64. package/dist/src/extensions/mono-pilot.js +100 -18
  65. package/dist/src/extensions/nvim.js +47 -0
  66. package/dist/src/extensions/session-hints.js +1 -2
  67. package/dist/src/extensions/sftp.js +897 -0
  68. package/dist/src/extensions/status.js +676 -0
  69. package/dist/src/extensions/system-events.js +478 -0
  70. package/dist/src/extensions/system-prompt.js +24 -14
  71. package/dist/src/extensions/user-message.js +70 -1
  72. package/dist/src/lsp/client.js +235 -0
  73. package/dist/src/lsp/index.js +165 -0
  74. package/dist/src/lsp/runtime.js +67 -0
  75. package/dist/src/lsp/server.js +242 -0
  76. package/dist/src/memory/build-memory.js +103 -0
  77. package/dist/src/memory/config/defaults.js +55 -0
  78. package/dist/src/memory/config/loader.js +29 -0
  79. package/dist/src/memory/config/paths.js +9 -0
  80. package/dist/src/memory/config/resolve.js +90 -0
  81. package/dist/src/memory/config/types.js +1 -0
  82. package/dist/src/memory/embeddings/batch-runner.js +39 -0
  83. package/dist/src/memory/embeddings/cache.js +47 -0
  84. package/dist/src/memory/embeddings/chunk-limits.js +26 -0
  85. package/dist/src/memory/embeddings/input-limits.js +48 -0
  86. package/dist/src/memory/embeddings/local.js +108 -0
  87. package/dist/src/memory/embeddings/types.js +1 -0
  88. package/dist/src/memory/index-manager.js +552 -0
  89. package/dist/src/memory/indexing/embeddings.js +67 -0
  90. package/dist/src/memory/indexing/files.js +180 -0
  91. package/dist/src/memory/indexing/index-file.js +105 -0
  92. package/dist/src/memory/log.js +38 -0
  93. package/dist/src/memory/paths.js +15 -0
  94. package/dist/src/memory/runtime/index.js +299 -0
  95. package/dist/src/memory/runtime/thread.js +116 -0
  96. package/dist/src/memory/search/fts.js +57 -0
  97. package/dist/src/memory/search/hybrid.js +50 -0
  98. package/dist/src/memory/search/text.js +30 -0
  99. package/dist/src/memory/search/vector.js +43 -0
  100. package/dist/src/memory/session/content-hash.js +7 -0
  101. package/dist/src/memory/session/entry.js +33 -0
  102. package/dist/src/memory/session/flush-policy.js +34 -0
  103. package/dist/src/memory/session/hook.js +191 -0
  104. package/dist/src/memory/session/paths.js +15 -0
  105. package/dist/src/memory/session/session-reader.js +88 -0
  106. package/dist/src/memory/session/transcript/content-hash.js +7 -0
  107. package/dist/src/memory/session/transcript/entry.js +28 -0
  108. package/dist/src/memory/session/transcript/flush.js +56 -0
  109. package/dist/src/memory/session/transcript/paths.js +28 -0
  110. package/dist/src/memory/session/transcript/reader.js +112 -0
  111. package/dist/src/memory/session/transcript/state.js +31 -0
  112. package/dist/src/memory/store/schema.js +89 -0
  113. package/dist/src/memory/store/sqlite.js +89 -0
  114. package/dist/src/memory/types.js +1 -0
  115. package/dist/src/memory/warm.js +25 -0
  116. package/dist/{tools → src/tools}/README.md +28 -2
  117. package/dist/{tools → src/tools}/apply-patch-description.md +8 -2
  118. package/dist/{tools → src/tools}/apply-patch.js +174 -104
  119. package/dist/{tools → src/tools}/apply-patch.test.js +52 -1
  120. package/dist/{tools/ask-question.js → src/tools/ask-user-question.js} +3 -3
  121. package/dist/src/tools/ast-grep.js +357 -0
  122. package/dist/src/tools/brief-write.js +122 -0
  123. package/dist/src/tools/bus-send.js +100 -0
  124. package/dist/{tools → src/tools}/call-mcp-tool.js +20 -24
  125. package/dist/src/tools/codex-apply-patch-description.md +52 -0
  126. package/dist/src/tools/codex-apply-patch.js +540 -0
  127. package/dist/{tools → src/tools}/delete.js +24 -0
  128. package/dist/src/tools/exit-plan-mode.js +83 -0
  129. package/dist/{tools → src/tools}/fetch-mcp-resource.js +31 -3
  130. package/dist/src/tools/generate-image.js +567 -0
  131. package/dist/{tools → src/tools}/glob.js +55 -1
  132. package/dist/{tools → src/tools}/list-mcp-resources.js +32 -3
  133. package/dist/{tools → src/tools}/list-mcp-tools.js +38 -3
  134. package/dist/src/tools/ls.js +48 -0
  135. package/dist/src/tools/lsp-diagnostics.js +67 -0
  136. package/dist/src/tools/lsp-symbols.js +54 -0
  137. package/dist/src/tools/mailbox.js +85 -0
  138. package/dist/src/tools/memory-get.js +90 -0
  139. package/dist/src/tools/memory-search.js +180 -0
  140. package/dist/{tools → src/tools}/plan-mode-reminder.md +3 -4
  141. package/dist/{tools → src/tools}/read-file.js +8 -19
  142. package/dist/{tools → src/tools}/rg.js +10 -20
  143. package/dist/{tools → src/tools}/shell.js +19 -42
  144. package/dist/{tools → src/tools}/subagent.js +255 -6
  145. package/dist/{tools → src/tools}/switch-mode.js +37 -6
  146. package/dist/{tools → src/tools}/web-fetch.js +105 -7
  147. package/dist/{tools → src/tools}/web-search.js +29 -1
  148. package/package.json +21 -9
  149. package/dist/src/utils/mcp-client.js +0 -282
  150. /package/dist/{tools → src/tools}/ask-mode-reminder.md +0 -0
  151. /package/dist/{tools → src/tools}/rg.test.js +0 -0
  152. /package/dist/{tools → src/tools}/semantic-search-description.md +0 -0
  153. /package/dist/{tools → src/tools}/semantic-search.js +0 -0
  154. /package/dist/{tools → src/tools}/shell-description.md +0 -0
  155. /package/dist/{tools → src/tools}/subagent-description.md +0 -0
@@ -13,7 +13,7 @@ const MOVE_TO = "*** Move to: ";
13
13
  const END_OF_FILE = "*** End of File";
14
14
  const MAX_RENDER_SUMMARY_PATH_CHARS = 96;
15
15
  const MAX_RENDER_FIRST_CHANGED_LINES = 12;
16
- const DESCRIPTION = readFileSync(fileURLToPath(new URL("./shell-description.md", import.meta.url)), "utf-8").trim();
16
+ const DESCRIPTION = readFileSync(fileURLToPath(new URL("./apply-patch-description.md", import.meta.url)), "utf-8").trim();
17
17
  const applyPatchSchema = Type.Object({
18
18
  patch: Type.String({
19
19
  description: "Single-file patch document in ApplyPatch format. Must start with *** Begin Patch and end with *** End Patch.",
@@ -98,6 +98,13 @@ function buildRenderDetailLines(details) {
98
98
  lines.push("patch:");
99
99
  lines.push(details.patchText.length > 0 ? details.patchText : "(empty patch)");
100
100
  }
101
+ if (details.sftp) {
102
+ const targets = details.sftp.targets.length > 0 ? details.sftp.targets.join(", ") : "(none)";
103
+ lines.push(`sftp uploaded: ${details.sftp.uploaded} to ${targets}`);
104
+ if (details.sftp.errors && details.sftp.errors.length > 0) {
105
+ lines.push(`sftp errors: ${details.sftp.errors.join("; ")}`);
106
+ }
107
+ }
101
108
  return lines;
102
109
  }
103
110
  function normalizeAddFileLines(lines) {
@@ -161,15 +168,18 @@ function parseUpdateFile(lines, startIndex) {
161
168
  const hunks = [];
162
169
  let currentHeaders;
163
170
  let currentLines = [];
171
+ let currentLineHint;
164
172
  const flushCurrentHunk = () => {
165
173
  if (!currentHeaders)
166
174
  return;
167
175
  hunks.push({
168
176
  headers: currentHeaders,
169
177
  lines: currentLines,
178
+ lineHint: currentLineHint,
170
179
  });
171
180
  currentHeaders = undefined;
172
181
  currentLines = [];
182
+ currentLineHint = undefined;
173
183
  };
174
184
  while (i < lines.length && lines[i] !== END_PATCH) {
175
185
  const line = lines[i];
@@ -178,16 +188,19 @@ function parseUpdateFile(lines, startIndex) {
178
188
  break;
179
189
  }
180
190
  if (line.startsWith("@@")) {
191
+ const { headerText, lineHint } = parseHunkHeaderLine(line);
181
192
  if (!currentHeaders) {
182
- currentHeaders = [line];
183
- }
184
- else if (currentLines.length === 0) {
185
- // Support multiple consecutive @@ headers to narrow context.
186
- currentHeaders.push(line);
193
+ currentHeaders = [];
187
194
  }
188
- else {
195
+ else if (currentLines.length > 0) {
189
196
  flushCurrentHunk();
190
- currentHeaders = [line];
197
+ currentHeaders = [];
198
+ }
199
+ if (headerText) {
200
+ currentHeaders.push(headerText);
201
+ }
202
+ if (lineHint && !currentLineHint) {
203
+ currentLineHint = lineHint;
191
204
  }
192
205
  i++;
193
206
  continue;
@@ -274,6 +287,52 @@ function parseFirstChangedLine(details) {
274
287
  const value = record.firstChangedLine;
275
288
  return typeof value === "number" && Number.isInteger(value) ? value : undefined;
276
289
  }
290
+ function toZeroBasedLine(line) {
291
+ return Math.max(0, line - 1);
292
+ }
293
+ function parseDiffHeader(header) {
294
+ const match = header.match(/^-(\d+)(?:,(\d+))?\s+\+(\d+)(?:,(\d+))?\s+@@(?:\s+(.*))?$/);
295
+ if (!match)
296
+ return null;
297
+ const oldStart = Number(match[1]);
298
+ const oldCount = match[2] ? Number(match[2]) : undefined;
299
+ const contextText = match[5]?.trim();
300
+ if (!Number.isInteger(oldStart) || oldStart <= 0) {
301
+ return {
302
+ headerText: contextText || undefined,
303
+ };
304
+ }
305
+ const startLine = toZeroBasedLine(oldStart);
306
+ const endLine = oldCount && oldCount > 0 ? startLine + oldCount - 1 : undefined;
307
+ return {
308
+ lineHint: { startLine, endLine },
309
+ headerText: contextText || undefined,
310
+ };
311
+ }
312
+ function parseLineDirective(header) {
313
+ const match = header.match(/^line\s*(?::|=)?\s*(\d+)(?:\s*(?:-|\.\.)\s*(\d+))?$/i);
314
+ if (!match)
315
+ return null;
316
+ const start = Number(match[1]);
317
+ if (!Number.isInteger(start) || start <= 0)
318
+ return null;
319
+ const end = match[2] ? Number(match[2]) : undefined;
320
+ const startLine = toZeroBasedLine(start);
321
+ const endLine = end && end >= start ? toZeroBasedLine(end) : undefined;
322
+ return { lineHint: { startLine, endLine } };
323
+ }
324
+ function parseHunkHeaderLine(line) {
325
+ const header = line.replace(/^@@/, "").trim();
326
+ if (!header)
327
+ return {};
328
+ const diffParsed = parseDiffHeader(header);
329
+ if (diffParsed)
330
+ return diffParsed;
331
+ const lineParsed = parseLineDirective(header);
332
+ if (lineParsed)
333
+ return lineParsed;
334
+ return { headerText: header };
335
+ }
277
336
  function normalizeHeaderText(header) {
278
337
  const trimmed = header.replace(/^@@/, "").trim();
279
338
  return trimmed.length > 0 ? trimmed : undefined;
@@ -360,6 +419,106 @@ export function alignReplacement(fileContent, oldText, newText, options) {
360
419
  }
361
420
  return null;
362
421
  }
422
+ export async function applyPatchToFilesystem(options) {
423
+ const toolCallId = options.toolCallId ?? "apply-patch";
424
+ const parsed = parsePatchDocument(options.patchText);
425
+ const patchLineCount = getNormalizedPatchLines(options.patchText).length;
426
+ const normalizedPatchText = normalizeLineEndings(options.patchText);
427
+ const writeTool = createWriteTool(options.cwd);
428
+ const editTool = createEditTool(options.cwd);
429
+ if (parsed.operation.kind === "add") {
430
+ const content = parsed.operation.lines.join("\n");
431
+ await writeTool.execute(`${toolCallId}:add`, {
432
+ path: parsed.operation.path,
433
+ content,
434
+ }, options.signal);
435
+ const details = {
436
+ operation: "add",
437
+ path: parsed.operation.path,
438
+ bytesWritten: Buffer.byteLength(content, "utf-8"),
439
+ patchLineCount,
440
+ patchText: normalizedPatchText,
441
+ };
442
+ return {
443
+ summary: `Applied patch: added ${parsed.operation.path}`,
444
+ details,
445
+ };
446
+ }
447
+ const firstChangedLines = [];
448
+ let appliedHunks = 0;
449
+ let noopHunks = 0;
450
+ for (let i = 0; i < parsed.operation.hunks.length; i++) {
451
+ const hunk = parsed.operation.hunks[i];
452
+ if (!hunkHasChanges(hunk)) {
453
+ noopHunks++;
454
+ continue;
455
+ }
456
+ let { oldText, newText } = buildReplacementTexts(hunk);
457
+ if (oldText === newText) {
458
+ noopHunks++;
459
+ continue;
460
+ }
461
+ const absolutePath = isAbsolute(parsed.operation.path)
462
+ ? parsed.operation.path
463
+ : resolve(options.cwd, parsed.operation.path);
464
+ if (existsSync(absolutePath)) {
465
+ try {
466
+ const fileContent = readFileSync(absolutePath, "utf-8");
467
+ const headerStart = findHeaderSearchStart(fileContent, hunk.headers);
468
+ const startLine = hunk.lineHint?.startLine ?? headerStart;
469
+ const endLine = hunk.lineHint?.endLine;
470
+ const alignOptions = startLine !== undefined || endLine !== undefined ? { startLine, endLine } : undefined;
471
+ const aligned = alignReplacement(fileContent, oldText, newText, alignOptions);
472
+ if (aligned) {
473
+ oldText = aligned.oldText;
474
+ newText = aligned.newText;
475
+ }
476
+ }
477
+ catch {
478
+ // Ignore read errors, let editTool handle it
479
+ }
480
+ }
481
+ const result = await editTool.execute(`${toolCallId}:hunk:${i + 1}`, {
482
+ path: parsed.operation.path,
483
+ oldText,
484
+ newText,
485
+ }, options.signal);
486
+ appliedHunks++;
487
+ const firstChangedLine = parseFirstChangedLine(result.details);
488
+ if (firstChangedLine !== undefined) {
489
+ firstChangedLines.push(firstChangedLine);
490
+ }
491
+ }
492
+ let movedTo;
493
+ if (parsed.operation.moveTo && parsed.operation.moveTo !== parsed.operation.path) {
494
+ await mkdir(dirname(parsed.operation.moveTo), { recursive: true });
495
+ await rename(parsed.operation.path, parsed.operation.moveTo);
496
+ movedTo = parsed.operation.moveTo;
497
+ }
498
+ const details = {
499
+ operation: "update",
500
+ path: parsed.operation.path,
501
+ moveTo: movedTo,
502
+ hunkCount: parsed.operation.hunks.length,
503
+ appliedHunks,
504
+ noopHunks,
505
+ firstChangedLines,
506
+ patchLineCount,
507
+ patchText: normalizedPatchText,
508
+ };
509
+ const suffix = [];
510
+ if (noopHunks > 0) {
511
+ suffix.push(`skipped ${noopHunks} no-op hunk(s)`);
512
+ }
513
+ if (movedTo) {
514
+ suffix.push(`moved to ${movedTo}`);
515
+ }
516
+ const suffixText = suffix.length > 0 ? ` (${suffix.join(", ")})` : "";
517
+ return {
518
+ summary: `Applied patch: updated ${parsed.operation.path} with ${appliedHunks} hunk(s)${suffixText}.`,
519
+ details,
520
+ };
521
+ }
363
522
  export default function (pi) {
364
523
  // System prompt injection is handled centrally by system-prompt extension.
365
524
  pi.registerTool({
@@ -391,103 +550,14 @@ export default function (pi) {
391
550
  return new Text(text, 0, 0);
392
551
  },
393
552
  async execute(toolCallId, params, signal, _onUpdate, ctx) {
394
- const parsed = parsePatchDocument(params.patch);
395
- const patchLineCount = getNormalizedPatchLines(params.patch).length;
396
- const patchText = normalizeLineEndings(params.patch);
397
- const writeTool = createWriteTool(ctx.cwd);
398
- const editTool = createEditTool(ctx.cwd);
399
- if (parsed.operation.kind === "add") {
400
- const content = parsed.operation.lines.join("\n");
401
- await writeTool.execute(`${toolCallId}:add`, {
402
- path: parsed.operation.path,
403
- content,
404
- }, signal);
405
- const details = {
406
- operation: "add",
407
- path: parsed.operation.path,
408
- bytesWritten: Buffer.byteLength(content, "utf-8"),
409
- patchLineCount,
410
- patchText,
411
- };
412
- return {
413
- content: [{ type: "text", text: `Applied patch: added ${parsed.operation.path}` }],
414
- details,
415
- };
416
- }
417
- const firstChangedLines = [];
418
- let appliedHunks = 0;
419
- let noopHunks = 0;
420
- for (let i = 0; i < parsed.operation.hunks.length; i++) {
421
- const hunk = parsed.operation.hunks[i];
422
- if (!hunkHasChanges(hunk)) {
423
- noopHunks++;
424
- continue;
425
- }
426
- let { oldText, newText } = buildReplacementTexts(hunk);
427
- if (oldText === newText) {
428
- noopHunks++;
429
- continue;
430
- }
431
- const absolutePath = isAbsolute(parsed.operation.path)
432
- ? parsed.operation.path
433
- : resolve(ctx.cwd, parsed.operation.path);
434
- if (existsSync(absolutePath)) {
435
- try {
436
- const fileContent = readFileSync(absolutePath, "utf-8");
437
- const headerStart = findHeaderSearchStart(fileContent, hunk.headers);
438
- const aligned = alignReplacement(fileContent, oldText, newText, headerStart !== undefined ? { startLine: headerStart } : undefined);
439
- if (aligned) {
440
- oldText = aligned.oldText;
441
- newText = aligned.newText;
442
- }
443
- }
444
- catch {
445
- // Ignore read errors, let editTool handle it
446
- }
447
- }
448
- const result = await editTool.execute(`${toolCallId}:hunk:${i + 1}`, {
449
- path: parsed.operation.path,
450
- oldText,
451
- newText,
452
- }, signal);
453
- appliedHunks++;
454
- const firstChangedLine = parseFirstChangedLine(result.details);
455
- if (firstChangedLine !== undefined) {
456
- firstChangedLines.push(firstChangedLine);
457
- }
458
- }
459
- let movedTo;
460
- if (parsed.operation.moveTo && parsed.operation.moveTo !== parsed.operation.path) {
461
- await mkdir(dirname(parsed.operation.moveTo), { recursive: true });
462
- await rename(parsed.operation.path, parsed.operation.moveTo);
463
- movedTo = parsed.operation.moveTo;
464
- }
465
- const details = {
466
- operation: "update",
467
- path: parsed.operation.path,
468
- moveTo: movedTo,
469
- hunkCount: parsed.operation.hunks.length,
470
- appliedHunks,
471
- noopHunks,
472
- firstChangedLines,
473
- patchLineCount,
474
- patchText,
475
- };
476
- const suffix = [];
477
- if (noopHunks > 0) {
478
- suffix.push(`skipped ${noopHunks} no-op hunk(s)`);
479
- }
480
- if (movedTo) {
481
- suffix.push(`moved to ${movedTo}`);
482
- }
483
- const suffixText = suffix.length > 0 ? ` (${suffix.join(", ")})` : "";
553
+ const { summary, details } = await applyPatchToFilesystem({
554
+ patchText: params.patch,
555
+ cwd: ctx.cwd,
556
+ toolCallId,
557
+ signal,
558
+ });
484
559
  return {
485
- content: [
486
- {
487
- type: "text",
488
- text: `Applied patch: updated ${parsed.operation.path} with ${appliedHunks} hunk(s)${suffixText}.`,
489
- },
490
- ],
560
+ content: [{ type: "text", text: summary }],
491
561
  details,
492
562
  };
493
563
  },
@@ -1,5 +1,14 @@
1
1
  import { describe, it, expect } from "vitest";
2
- import { alignReplacement } from "./apply-patch.js";
2
+ import { mkdtemp, readFile, writeFile } from "node:fs/promises";
3
+ import { tmpdir } from "node:os";
4
+ import { resolve } from "node:path";
5
+ import { fileURLToPath } from "node:url";
6
+ import { alignReplacement, applyPatchToFilesystem } from "./apply-patch.js";
7
+ const fixtureRoot = fileURLToPath(new URL("./apply-patch-fixtures", import.meta.url));
8
+ const fixtureOriginal = resolve(fixtureRoot, "original.md");
9
+ const fixturePatches = resolve(fixtureRoot, "patches");
10
+ const fixtureExpected = resolve(fixtureRoot, "expected");
11
+ const PATCH_TARGET = "{{FILE}}";
3
12
  describe("ApplyPatch Robustness - alignReplacement", () => {
4
13
  it("should align 2-space indentation to tabs", () => {
5
14
  const fileContent = "function test() {\n\t\tfoo();\n\t\tbar();\n}";
@@ -87,3 +96,45 @@ describe("ApplyPatch Robustness - alignReplacement", () => {
87
96
  expect(result?.newText).toBe(" bar();");
88
97
  });
89
98
  });
99
+ describe("ApplyPatch fixtures", () => {
100
+ const cases = [
101
+ {
102
+ name: "context-only",
103
+ patch: "00-context-only.patch",
104
+ expected: "00-context-only.md",
105
+ },
106
+ {
107
+ name: "line-hint",
108
+ patch: "01-line-hint.patch",
109
+ expected: "01-line-hint.md",
110
+ },
111
+ {
112
+ name: "line-range",
113
+ patch: "02-line-range.patch",
114
+ expected: "02-line-range.md",
115
+ },
116
+ {
117
+ name: "diff-header",
118
+ patch: "03-diff-header.patch",
119
+ expected: "03-diff-header.md",
120
+ },
121
+ ];
122
+ for (const entry of cases) {
123
+ it(`applies fixture patch: ${entry.name}`, async () => {
124
+ const tempDir = await mkdtemp(`${tmpdir()}/apply-patch-fixture-`);
125
+ const targetPath = resolve(tempDir, "target.md");
126
+ const original = await readFile(fixtureOriginal, "utf-8");
127
+ await writeFile(targetPath, original, "utf-8");
128
+ const patchTemplate = await readFile(resolve(fixturePatches, entry.patch), "utf-8");
129
+ const patchText = patchTemplate.replaceAll(PATCH_TARGET, targetPath);
130
+ await applyPatchToFilesystem({
131
+ patchText,
132
+ cwd: tempDir,
133
+ toolCallId: "fixture",
134
+ });
135
+ const expected = await readFile(resolve(fixtureExpected, entry.expected), "utf-8");
136
+ const actual = await readFile(targetPath, "utf-8");
137
+ expect(actual).toBe(expected);
138
+ });
139
+ }
140
+ });
@@ -212,11 +212,11 @@ function buildSuccessText(answers, questions) {
212
212
  }
213
213
  return lines.join("\n");
214
214
  }
215
- export default function askQuestionExtension(pi) {
215
+ export default function askUserQuestionExtension(pi) {
216
216
  // System prompt injection is handled centrally by system-prompt extension.
217
217
  pi.registerTool({
218
- name: "AskQuestion",
219
- label: "AskQuestion",
218
+ name: "AskUserQuestion",
219
+ label: "AskUserQuestion",
220
220
  description: DESCRIPTION,
221
221
  parameters: askQuestionSchema,
222
222
  async execute(_toolCallId, params, signal, _onUpdate, ctx) {