@oh-my-pi/pi-coding-agent 15.1.3 → 15.1.5

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.
@@ -18,8 +18,8 @@ import type { OutputMeta } from "./output-meta";
18
18
  import { resolveToolSearchScope } from "./path-utils";
19
19
  import {
20
20
  appendParseErrorsBulletList,
21
+ capParseErrors,
21
22
  createCachedComponent,
22
- dedupeParseErrors,
23
23
  formatCodeFrameLine,
24
24
  formatCount,
25
25
  formatEmptyMessage,
@@ -146,6 +146,8 @@ export interface AstEditToolDetails {
146
146
  applied: boolean;
147
147
  limitReached: boolean;
148
148
  parseErrors?: string[];
149
+ /** Total parse error count before {@link PARSE_ERRORS_LIMIT} capping. Omitted when no errors. */
150
+ parseErrorsTotal?: number;
149
151
  scopePath?: string;
150
152
  files?: string[];
151
153
  fileReplacements?: Array<{ path: string; count: number }>;
@@ -210,7 +212,7 @@ export class AstEditTool implements AgentTool<typeof astEditSchema, AstEditToolD
210
212
  signal,
211
213
  });
212
214
 
213
- const dedupedParseErrors = dedupeParseErrors(result.parseErrors);
215
+ const { errors: cappedParseErrors, total: parseErrorsTotal } = capParseErrors(result.parseErrors);
214
216
  const formatPath = (filePath: string): string =>
215
217
  formatResultPath(filePath, isDirectory, resolvedSearchPath, this.session.cwd);
216
218
 
@@ -237,15 +239,15 @@ export class AstEditTool implements AgentTool<typeof astEditSchema, AstEditToolD
237
239
  filesSearched: result.filesSearched,
238
240
  applied: result.applied,
239
241
  limitReached: result.limitReached,
240
- ...(dedupedParseErrors.length > 0 ? { parseErrors: dedupedParseErrors } : {}),
242
+ ...(cappedParseErrors.length > 0 ? { parseErrors: cappedParseErrors, parseErrorsTotal } : {}),
241
243
  scopePath,
242
244
  files: fileList,
243
245
  fileReplacements: [],
244
246
  };
245
247
 
246
248
  if (result.totalReplacements === 0) {
247
- const parseMessage = dedupedParseErrors.length
248
- ? `\n${formatParseErrors(dedupedParseErrors).join("\n")}`
249
+ const parseMessage = cappedParseErrors.length
250
+ ? `\n${formatParseErrors(cappedParseErrors, parseErrorsTotal).join("\n")}`
249
251
  : "";
250
252
  return toolResult(baseDetails).text(`No replacements made${parseMessage}`).done();
251
253
  }
@@ -308,8 +310,8 @@ export class AstEditTool implements AgentTool<typeof astEditSchema, AstEditToolD
308
310
  if (result.limitReached) {
309
311
  outputLines.push("", "Limit reached; narrow paths.");
310
312
  }
311
- if (dedupedParseErrors.length) {
312
- outputLines.push("", ...formatParseErrors(dedupedParseErrors));
313
+ if (cappedParseErrors.length) {
314
+ outputLines.push("", ...formatParseErrors(cappedParseErrors, parseErrorsTotal));
313
315
  }
314
316
 
315
317
  // Register pending action so `resolve` can apply or discard these previewed changes
@@ -326,7 +328,9 @@ export class AstEditTool implements AgentTool<typeof astEditSchema, AstEditToolD
326
328
  maxFiles,
327
329
  failOnParseError: false,
328
330
  });
329
- const dedupedApplyParseErrors = dedupeParseErrors(applyResult.parseErrors);
331
+ const { errors: cappedApplyParseErrors, total: applyParseErrorsTotal } = capParseErrors(
332
+ applyResult.parseErrors,
333
+ );
330
334
  const { record: recordAppliedFile, list: appliedFileList } = createFileRecorder();
331
335
  const appliedFileReplacementCounts = new Map<string, number>();
332
336
  for (const fileChange of applyResult.fileChanges) {
@@ -350,7 +354,9 @@ export class AstEditTool implements AgentTool<typeof astEditSchema, AstEditToolD
350
354
  filesSearched: applyResult.filesSearched,
351
355
  applied: applyResult.applied,
352
356
  limitReached: applyResult.limitReached,
353
- ...(dedupedApplyParseErrors.length > 0 ? { parseErrors: dedupedApplyParseErrors } : {}),
357
+ ...(cappedApplyParseErrors.length > 0
358
+ ? { parseErrors: cappedApplyParseErrors, parseErrorsTotal: applyParseErrorsTotal }
359
+ : {}),
354
360
  scopePath,
355
361
  files: appliedFileList,
356
362
  fileReplacements: appliedFileReplacements,
@@ -441,7 +447,7 @@ export const astEditToolRenderer = {
441
447
  if (filesSearched > 0) meta.push(`searched ${filesSearched}`);
442
448
  const header = renderStatusLine({ icon: "warning", title: "AST Edit", description, meta }, uiTheme);
443
449
  const lines = [header, formatEmptyMessage("No replacements made", uiTheme)];
444
- appendParseErrorsBulletList(lines, details?.parseErrors, uiTheme);
450
+ appendParseErrorsBulletList(lines, details?.parseErrors, uiTheme, details?.parseErrorsTotal);
445
451
  return new Text(lines.join("\n"), 0, 0);
446
452
  }
447
453
 
@@ -470,7 +476,9 @@ export const astEditToolRenderer = {
470
476
  extraLines.push(uiTheme.fg("warning", "limit reached; narrow path"));
471
477
  }
472
478
  if (details?.parseErrors?.length) {
473
- extraLines.push(uiTheme.fg("warning", formatParseErrorsCountLabel(details.parseErrors)));
479
+ extraLines.push(
480
+ uiTheme.fg("warning", formatParseErrorsCountLabel(details.parseErrors, details.parseErrorsTotal)),
481
+ );
474
482
  }
475
483
  return createCachedComponent(
476
484
  () => options.expanded,
@@ -18,8 +18,8 @@ import type { OutputMeta } from "./output-meta";
18
18
  import { resolveToolSearchScope } from "./path-utils";
19
19
  import {
20
20
  appendParseErrorsBulletList,
21
+ capParseErrors,
21
22
  createCachedComponent,
22
- dedupeParseErrors,
23
23
  formatCodeFrameLine,
24
24
  formatCount,
25
25
  formatEmptyMessage,
@@ -104,6 +104,8 @@ export interface AstGrepToolDetails {
104
104
  filesSearched: number;
105
105
  limitReached: boolean;
106
106
  parseErrors?: string[];
107
+ /** Total parse error count before {@link PARSE_ERRORS_LIMIT} capping. Omitted when no errors. */
108
+ parseErrorsTotal?: number;
107
109
  scopePath?: string;
108
110
  files?: string[];
109
111
  fileMatches?: Array<{ path: string; count: number }>;
@@ -172,7 +174,7 @@ export class AstGrepTool implements AgentTool<typeof astGrepSchema, AstGrepToolD
172
174
  const parseError = error.match(/^.+: (.+: parse error \(syntax tree contains error nodes\))$/);
173
175
  return parseError?.[1] ?? error;
174
176
  });
175
- const dedupedParseErrors = dedupeParseErrors(normalizedParseErrors);
177
+ const { errors: cappedParseErrors, total: parseErrorsTotal } = capParseErrors(normalizedParseErrors);
176
178
  const formatPath = (filePath: string): string =>
177
179
  formatResultPath(filePath, isDirectory, resolvedSearchPath, this.session.cwd);
178
180
 
@@ -193,18 +195,18 @@ export class AstGrepTool implements AgentTool<typeof astGrepSchema, AstGrepToolD
193
195
  fileCount: result.filesWithMatches,
194
196
  filesSearched: result.filesSearched,
195
197
  limitReached: result.limitReached,
196
- ...(dedupedParseErrors.length > 0 ? { parseErrors: dedupedParseErrors } : {}),
198
+ ...(cappedParseErrors.length > 0 ? { parseErrors: cappedParseErrors, parseErrorsTotal } : {}),
197
199
  scopePath,
198
200
  files: fileList,
199
201
  fileMatches: [],
200
202
  };
201
203
 
202
204
  if (result.matches.length === 0) {
203
- const noMatchMessage = dedupedParseErrors.length
205
+ const noMatchMessage = cappedParseErrors.length
204
206
  ? "No matches found. Parse issues mean the query may be mis-scoped; narrow `paths` before concluding absence."
205
207
  : "No matches found";
206
- const parseMessage = dedupedParseErrors.length
207
- ? `\n${formatParseErrors(dedupedParseErrors).join("\n")}`
208
+ const parseMessage = cappedParseErrors.length
209
+ ? `\n${formatParseErrors(cappedParseErrors, parseErrorsTotal).join("\n")}`
208
210
  : "";
209
211
  return toolResult(baseDetails).text(`${noMatchMessage}${parseMessage}`).done();
210
212
  }
@@ -269,8 +271,8 @@ export class AstGrepTool implements AgentTool<typeof astGrepSchema, AstGrepToolD
269
271
  if (result.limitReached) {
270
272
  outputLines.push("", "Result limit reached; narrow paths or increase limit.");
271
273
  }
272
- if (dedupedParseErrors.length) {
273
- outputLines.push("", ...formatParseErrors(dedupedParseErrors));
274
+ if (cappedParseErrors.length) {
275
+ outputLines.push("", ...formatParseErrors(cappedParseErrors, parseErrorsTotal));
274
276
  }
275
277
 
276
278
  return toolResult(details).text(outputLines.join("\n")).done();
@@ -329,7 +331,7 @@ export const astGrepToolRenderer = {
329
331
  const lines = [header, formatEmptyMessage("No matches found", uiTheme)];
330
332
  if (details?.parseErrors?.length) {
331
333
  lines.push(uiTheme.fg("warning", "Query may be mis-scoped; narrow `paths` before concluding absence"));
332
- appendParseErrorsBulletList(lines, details.parseErrors, uiTheme);
334
+ appendParseErrorsBulletList(lines, details.parseErrors, uiTheme, details.parseErrorsTotal);
333
335
  }
334
336
  return new Text(lines.join("\n"), 0, 0);
335
337
  }
@@ -356,7 +358,9 @@ export const astGrepToolRenderer = {
356
358
  extraLines.push(uiTheme.fg("warning", "limit reached; narrow paths or increase limit"));
357
359
  }
358
360
  if (details?.parseErrors?.length) {
359
- extraLines.push(uiTheme.fg("warning", formatParseErrorsCountLabel(details.parseErrors)));
361
+ extraLines.push(
362
+ uiTheme.fg("warning", formatParseErrorsCountLabel(details.parseErrors, details.parseErrorsTotal)),
363
+ );
360
364
  }
361
365
 
362
366
  return createCachedComponent(
@@ -633,17 +633,29 @@ export function dedupeParseErrors(errors: string[] | undefined): string[] {
633
633
  return deduped;
634
634
  }
635
635
 
636
- export function formatParseErrors(errors: string[]): string[] {
636
+ export function formatParseErrors(errors: string[], total?: number): string[] {
637
637
  const deduped = dedupeParseErrors(errors);
638
638
  if (deduped.length === 0) return [];
639
+ const fullCount = total ?? deduped.length;
639
640
  const capped = deduped.slice(0, PARSE_ERRORS_LIMIT);
640
- const header =
641
- deduped.length > PARSE_ERRORS_LIMIT
642
- ? `Parse issues (${PARSE_ERRORS_LIMIT} / ${deduped.length}):`
643
- : "Parse issues:";
641
+ const header = fullCount > capped.length ? `Parse issues (${capped.length} / ${fullCount}):` : "Parse issues:";
644
642
  return [header, ...capped.map(err => `- ${err}`)];
645
643
  }
646
644
 
645
+ /**
646
+ * Cap an upstream parse-error list to {@link PARSE_ERRORS_LIMIT} unique entries,
647
+ * preserving the original deduplicated total. Use this at the source so tool
648
+ * details never carry thousands of per-file parse errors into traces or
649
+ * renderers.
650
+ */
651
+ export function capParseErrors(
652
+ errors: string[] | undefined,
653
+ limit: number = PARSE_ERRORS_LIMIT,
654
+ ): { errors: string[]; total: number } {
655
+ const deduped = dedupeParseErrors(errors);
656
+ return { errors: deduped.slice(0, limit), total: deduped.length };
657
+ }
658
+
647
659
  // =============================================================================
648
660
  // Renderer helpers shared by search / find / ast tools
649
661
  // =============================================================================
@@ -712,14 +724,16 @@ export function appendParseErrorsBulletList(
712
724
  lines: string[],
713
725
  parseErrors: readonly string[] | undefined,
714
726
  theme: Theme,
727
+ total?: number,
715
728
  ): void {
716
729
  if (!parseErrors || parseErrors.length === 0) return;
730
+ const fullCount = total ?? parseErrors.length;
717
731
  const capped = parseErrors.slice(0, PARSE_ERRORS_LIMIT);
718
732
  for (const err of capped) {
719
733
  lines.push(theme.fg("warning", ` - ${err}`));
720
734
  }
721
- if (parseErrors.length > PARSE_ERRORS_LIMIT) {
722
- lines.push(theme.fg("dim", ` … ${parseErrors.length - PARSE_ERRORS_LIMIT} more`));
735
+ if (fullCount > capped.length) {
736
+ lines.push(theme.fg("dim", ` … ${fullCount - capped.length} more`));
723
737
  }
724
738
  }
725
739
 
@@ -727,11 +741,11 @@ export function appendParseErrorsBulletList(
727
741
  * Human-readable summary string for the parse-issues count, capped by
728
742
  * {@link PARSE_ERRORS_LIMIT}.
729
743
  */
730
- export function formatParseErrorsCountLabel(parseErrors: readonly string[]): string {
731
- const total = parseErrors.length;
732
- return total > PARSE_ERRORS_LIMIT
733
- ? `${PARSE_ERRORS_LIMIT} / ${total} parse issues`
734
- : `${total} parse issue${total !== 1 ? "s" : ""}`;
744
+ export function formatParseErrorsCountLabel(parseErrors: readonly string[], total?: number): string {
745
+ const fullCount = total ?? parseErrors.length;
746
+ return fullCount > PARSE_ERRORS_LIMIT
747
+ ? `${PARSE_ERRORS_LIMIT} / ${fullCount} parse issues`
748
+ : `${fullCount} parse issue${fullCount !== 1 ? "s" : ""}`;
735
749
  }
736
750
 
737
751
  // =============================================================================