@oh-my-pi/pi-coding-agent 14.4.0 → 14.4.1

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.
@@ -8,7 +8,7 @@
8
8
  * if the file has changed since the caller last read it, hash mismatches are caught
9
9
  * before any mutation occurs.
10
10
  *
11
- * Displayed format: `LINE+ID:TEXT`
11
+ * Displayed format: `LINE+ID|TEXT`
12
12
  * Reference format: `"LINE+ID"` (e.g. `"1ab"`)
13
13
  *
14
14
  * In tool JSON, each edit's `content` is `string[]` (one string per logical line) or
@@ -28,7 +28,7 @@ import { resolveToCwd } from "../../tools/path-utils";
28
28
  import { enforcePlanModeWrite, resolvePlanPath } from "../../tools/plan-mode-guard";
29
29
  import { formatCodeFrameLine } from "../../tools/render-utils";
30
30
  import { generateDiffString } from "../diff";
31
- import { computeLineHash, formatLineHash, HASHLINE_BIGRAM_RE_SRC, HASHLINE_CONTENT_SEPARATOR } from "../line-hash";
31
+ import { computeLineHash, formatHashLine, HASHLINE_BIGRAM_RE_SRC, HASHLINE_CONTENT_SEPARATOR } from "../line-hash";
32
32
  import { detectLineEnding, normalizeToLF, restoreLineEndings, stripBom } from "../normalize";
33
33
  import type { EditToolDetails, LspBatchRequest } from "../renderer";
34
34
 
@@ -38,7 +38,7 @@ export interface HashMismatch {
38
38
  actual: string;
39
39
  }
40
40
 
41
- export type Anchor = { line: number; hash: string };
41
+ export type Anchor = { line: number; hash: string; contentHint?: string };
42
42
  export type HashlineEdit =
43
43
  | { op: "replace_line"; pos: Anchor; lines: string[] }
44
44
  | { op: "replace_range"; pos: Anchor; end: Anchor; lines: string[] }
@@ -47,10 +47,11 @@ export type HashlineEdit =
47
47
  | { op: "append_file"; lines: string[] }
48
48
  | { op: "prepend_file"; lines: string[] };
49
49
 
50
- // Tight prefix matchers for the new format `LINE+ID:content`. Hard
51
- // cutover do not accept legacy `LINENUM#BIGRAM:content` or tab separators.
52
- // The terminator must be a literal colon; line-number digits are mandatory.
53
- const HASHLINE_CONTENT_SEPARATOR_RE = HASHLINE_CONTENT_SEPARATOR.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
50
+ // Tight prefix matchers for the new format `LINE+ID|content`. The pipe is the
51
+ // canonical separator; legacy reads using `:` are tolerated for back-compat.
52
+ // Line-number digits are mandatory.
53
+ // Accept both `|` (canonical) and `:` (legacy) so re-reads of older outputs still parse.
54
+ const HASHLINE_CONTENT_SEPARATOR_RE = "[:|]";
54
55
  const HASHLINE_PREFIX_RE = new RegExp(
55
56
  `^\\s*(?:>>>|>>)?\\s*(?:\\+\\s*)?\\d+${HASHLINE_BIGRAM_RE_SRC}${HASHLINE_CONTENT_SEPARATOR_RE}`,
56
57
  );
@@ -312,8 +313,6 @@ interface ResolvedHashlineStreamOptions {
312
313
  maxChunkBytes: number;
313
314
  }
314
315
 
315
- type HashlineLineFormatter = (lineNumber: number, line: string) => string;
316
-
317
316
  interface HashlineChunkEmitter {
318
317
  pushLine: (line: string) => string[];
319
318
  flush: () => string | undefined;
@@ -329,7 +328,7 @@ function resolveHashlineStreamOptions(options: HashlineStreamOptions): ResolvedH
329
328
 
330
329
  function createHashlineChunkEmitter(
331
330
  options: ResolvedHashlineStreamOptions,
332
- formatLine: HashlineLineFormatter,
331
+ formatLine = formatHashLine,
333
332
  ): HashlineChunkEmitter {
334
333
  let lineNumber = options.startLine;
335
334
  let outLines: string[] = [];
@@ -373,10 +372,6 @@ function createHashlineChunkEmitter(
373
372
  return { pushLine, flush };
374
373
  }
375
374
 
376
- function formatHashlineStreamLine(lineNumber: number, line: string): string {
377
- return `${formatLineHash(lineNumber, line)}${HASHLINE_CONTENT_SEPARATOR}${line}`;
378
- }
379
-
380
375
  function isReadableStream(value: unknown): value is ReadableStream<Uint8Array> {
381
376
  return (
382
377
  typeof value === "object" &&
@@ -416,7 +411,7 @@ export async function* streamHashLinesFromUtf8(
416
411
  let pending = "";
417
412
  let sawAnyText = false;
418
413
  let endedWithNewline = false;
419
- const emitter = createHashlineChunkEmitter(resolvedOptions, formatHashlineStreamLine);
414
+ const emitter = createHashlineChunkEmitter(resolvedOptions);
420
415
 
421
416
  const consumeText = (text: string): string[] => {
422
417
  if (text.length === 0) return [];
@@ -469,7 +464,7 @@ export async function* streamHashLinesFromLines(
469
464
  options: HashlineStreamOptions = {},
470
465
  ): AsyncGenerator<string> {
471
466
  const resolvedOptions = resolveHashlineStreamOptions(options);
472
- const emitter = createHashlineChunkEmitter(resolvedOptions, formatHashlineStreamLine);
467
+ const emitter = createHashlineChunkEmitter(resolvedOptions);
473
468
  let sawAnyLine = false;
474
469
 
475
470
  const asyncIterator = (lines as AsyncIterable<string>)[Symbol.asyncIterator];
@@ -530,7 +525,7 @@ const MISMATCH_CONTEXT = 2;
530
525
  /**
531
526
  * Error thrown when one or more hashline references have stale hashes.
532
527
  *
533
- * Displays grep-style output with `:` separator on mismatched lines and `-` on
528
+ * Displays grep-style output with `>` separator on mismatched lines and `:` on
534
529
  * surrounding context, showing the correct `LINE+ID` so the caller can fix all refs at once.
535
530
  */
536
531
  export class HashlineMismatchError extends Error {
@@ -552,7 +547,7 @@ export class HashlineMismatchError extends Error {
552
547
  /**
553
548
  * User-visible variant of {@link formatMessage} — omits the bigram fingerprint
554
549
  * and uses a `│` gutter so TUI rendering is clean. The model still receives
555
- * the full `LINE+ID:content` form via {@link Error.message}.
550
+ * the full `LINE+ID|content` form via {@link Error.message}.
556
551
  */
557
552
  get displayMessage(): string {
558
553
  return HashlineMismatchError.formatDisplayMessage(this.mismatches, this.fileLines);
@@ -609,7 +604,7 @@ export class HashlineMismatchError extends Error {
609
604
 
610
605
  lines.push(
611
606
  `Edit rejected: ${mismatches.length} line${mismatches.length > 1 ? "s have" : " has"} changed since the last read. The edit was NOT applied.`,
612
- "Use the updated anchors shown below (`:` marks changed lines, `-` marks context) and retry the edit.",
607
+ "Use the updated anchors shown below (`>` marks changed lines, `:` marks context) and retry the edit.",
613
608
  );
614
609
  lines.push("");
615
610
 
@@ -626,9 +621,9 @@ export class HashlineMismatchError extends Error {
626
621
  const prefix = `${lineNum}${hash}`;
627
622
 
628
623
  if (mismatchSet.has(lineNum)) {
629
- lines.push(`${prefix}:${text}`);
624
+ lines.push(`${prefix}>${text}`);
630
625
  } else {
631
- lines.push(`${prefix}-${text}`);
626
+ lines.push(`${prefix}:${text}`);
632
627
  }
633
628
  }
634
629
  return lines.join("\n");
@@ -762,7 +757,7 @@ function collectBoundaryDuplicationWarning(edit: HashlineEdit, originalFileLines
762
757
  const trimmedNext = nextSurvivingLine.trim();
763
758
  const trimmedLast = lastInsertedLine.trim();
764
759
  if (trimmedLast.length > 0 && trimmedLast === trimmedNext) {
765
- const tag = formatLineHash(endLine + 1, nextSurvivingLine);
760
+ const tag = formatHashLine(endLine + 1, nextSurvivingLine);
766
761
  warnings.push(
767
762
  `Possible boundary duplication: your last replacement line \`${trimmedLast}\` is identical to the next surviving line ${tag}. ` +
768
763
  `If you meant to replace the entire block, set \`end\` to ${tag} instead.`,