pi-hashline-edit-pro 0.3.6 → 0.3.7

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/index.ts CHANGED
@@ -4,6 +4,8 @@ import { join, isAbsolute } from "path";
4
4
  import { computeLineHashes, formatHashlineRegion } from "./src/hashline";
5
5
  import { registerEditTool } from "./src/edit";
6
6
  import { registerReadTool } from "./src/read";
7
+ import { normalizeToLF } from "./src/edit-diff";
8
+ import { getVisibleLines } from "./src/utils";
7
9
 
8
10
  export default function (pi: ExtensionAPI): void {
9
11
  registerReadTool(pi);
@@ -22,9 +24,8 @@ export default function (pi: ExtensionAPI): void {
22
24
  const content = await readFile(absolutePath, "utf-8");
23
25
 
24
26
  // Normalize and compute hashline output
25
- const normalized = content.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
26
- const lines = normalized.split("\n");
27
- const visibleLines = normalized.endsWith("\n") ? lines.slice(0, -1) : lines;
27
+ const normalized = normalizeToLF(content);
28
+ const visibleLines = getVisibleLines(normalized);
28
29
 
29
30
  if (visibleLines.length === 0) return;
30
31
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-hashline-edit-pro",
3
- "version": "0.3.6",
3
+ "version": "0.3.7",
4
4
  "description": "Strict hashline read/edit tool override for pi-coding-agent with hash-anchored edits (4-char, 24-bit)",
5
5
  "repository": {
6
6
  "type": "git",
@@ -6,6 +6,7 @@ import {
6
6
  formatHashlineRegion,
7
7
  } from "./hashline";
8
8
  import { formatHashlineReadPreview } from "./read";
9
+ import { getVisibleLines } from "./utils";
9
10
 
10
11
  type ToolResult = {
11
12
  content: Array<{ type: "text"; text: string }>;
@@ -96,11 +97,6 @@ export interface SuccessResponseInput {
96
97
 
97
98
  // ─── Helpers ────────────────────────────────────────────────────────────
98
99
 
99
- function getVisibleLines(text: string): string[] {
100
- if (text.length === 0) return [];
101
- const lines = text.split("\n");
102
- return text.endsWith("\n") ? lines.slice(0, -1) : lines;
103
- }
104
100
 
105
101
  function countDiffLines(diff: string, marker: "+" | "-"): number {
106
102
  if (!diff) return 0;
@@ -6,10 +6,12 @@ import {
6
6
  assertNoBareHashPrefixLines,
7
7
  maybeWarnSuspiciousUnicodeEscapePlaceholder,
8
8
  formatMismatchError,
9
+ describeEdit,
9
10
  type ResolvedHashlineEdit,
10
11
  type NoopEdit,
11
12
  type HashlineEdit,
12
13
  } from "./resolve";
14
+ import { countVisibleLines } from "../utils";
13
15
 
14
16
 
15
17
  type LineIndex = {
@@ -58,16 +60,6 @@ function assertDoesNotEmptyFile(originalContent: string, result: string): void {
58
60
  }
59
61
  }
60
62
 
61
- function describeEdit(edit: ResolvedHashlineEdit): string {
62
- switch (edit.op) {
63
- case "replace":
64
- return `replace ${edit.start.hash}-${edit.end.hash}`;
65
- case "append":
66
- return edit.pos ? `append after ${edit.pos.hash}` : "append at EOF";
67
- case "prepend":
68
- return edit.pos ? `prepend before ${edit.pos.hash}` : "prepend at BOF";
69
- }
70
- }
71
63
 
72
64
  function throwEditConflict(
73
65
  left: { index: number; label: string },
@@ -521,13 +513,6 @@ export function computeChangedLineRange(
521
513
  ): { firstChangedLine: number; lastChangedLine: number } | null {
522
514
  if (original === result) return null;
523
515
 
524
- function countVisibleLines(text: string): number {
525
- if (text.length === 0) {
526
- return 0;
527
- }
528
- const lines = text.split("\n");
529
- return text.endsWith("\n") ? lines.length - 1 : lines.length;
530
- }
531
516
 
532
517
  if (original.length === 0) {
533
518
  return {
@@ -26,6 +26,7 @@ export {
26
26
  type ResolvedHashlineEdit,
27
27
  type HashlineToolEdit,
28
28
  type NoopEdit,
29
+ describeEdit,
29
30
  resolveEditAnchors,
30
31
  validateAnchorEdits,
31
32
  assertNoBareHashPrefixLines,
@@ -303,6 +303,21 @@ export function assertNoBareHashPrefixLines(
303
303
  );
304
304
  }
305
305
 
306
+
307
+ /**
308
+ * Human-readable label for a resolved edit (used in warnings and conflict errors).
309
+ */
310
+ export function describeEdit(edit: ResolvedHashlineEdit): string {
311
+ switch (edit.op) {
312
+ case "replace":
313
+ return `replace ${edit.start.hash}-${edit.end.hash}`;
314
+ case "append":
315
+ return edit.pos ? `append after ${edit.pos.hash}` : "append at EOF";
316
+ case "prepend":
317
+ return edit.pos ? `prepend before ${edit.pos.hash}` : "prepend at BOF";
318
+ }
319
+ }
320
+
306
321
  export function validateAnchorEdits(
307
322
  edits: HashlineEdit[],
308
323
  fileLines: string[],
@@ -327,16 +342,6 @@ export function validateAnchorEdits(
327
342
  return result;
328
343
  };
329
344
 
330
- function describeEdit(edit: ResolvedHashlineEdit): string {
331
- switch (edit.op) {
332
- case "replace":
333
- return `replace ${edit.start.hash}-${edit.end.hash}`;
334
- case "append":
335
- return edit.pos ? `append after ${edit.pos.hash}` : "append at EOF";
336
- case "prepend":
337
- return edit.pos ? `prepend before ${edit.pos.hash}` : "prepend at BOF";
338
- }
339
- }
340
345
 
341
346
  for (const edit of edits) {
342
347
  throwIfAborted(signal);
package/src/read.ts CHANGED
@@ -17,6 +17,7 @@ import { computeLineHashes, formatHashlineRegion } from "./hashline";
17
17
  import { resolveToCwd } from "./path-utils";
18
18
  import { throwIfAborted } from "./runtime";
19
19
  import { getFileSnapshot } from "./snapshot";
20
+ import { getVisibleLines } from "./utils";
20
21
 
21
22
  const READ_DESC = readFileSync(
22
23
  new URL("../prompts/read.md", import.meta.url),
@@ -55,21 +56,13 @@ function normalizePositiveInteger(
55
56
  return value;
56
57
  }
57
58
 
58
- function getPreviewLines(text: string): string[] {
59
- if (text.length === 0) {
60
- return [];
61
- }
62
-
63
- const lines = text.split("\n");
64
- return text.endsWith("\n") ? lines.slice(0, -1) : lines;
65
- }
66
59
 
67
60
  export function formatHashlineReadPreview(
68
61
  text: string,
69
62
  options: { offset?: number; limit?: number },
70
63
  precomputedHashes?: string[],
71
64
  ): { text: string; truncation?: TruncationResult; nextOffset?: number } {
72
- const allLines = getPreviewLines(text);
65
+ const allLines = getVisibleLines(text);
73
66
  const totalLines = allLines.length;
74
67
  const startLine = normalizePositiveInteger(options.offset, "offset") ?? 1;
75
68
  if (totalLines === 0) {
package/src/utils.ts CHANGED
@@ -9,3 +9,19 @@ export function isRecord(value: unknown): value is Record<string, unknown> {
9
9
  export function hasOwn(record: Record<string, unknown>, key: string): boolean {
10
10
  return Object.hasOwn(record, key);
11
11
  }
12
+
13
+ /**
14
+ * Return the visible lines of a text (excluding the terminal-newline sentinel).
15
+ */
16
+ export function getVisibleLines(text: string): string[] {
17
+ if (text.length === 0) return [];
18
+ const lines = text.split("\n");
19
+ return text.endsWith("\n") ? lines.slice(0, -1) : lines;
20
+ }
21
+
22
+ /**
23
+ * Count the visible lines of a text (excluding the terminal-newline sentinel).
24
+ */
25
+ export function countVisibleLines(text: string): number {
26
+ return getVisibleLines(text).length;
27
+ }