@oh-my-pi/hashline 15.5.11 → 15.5.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.
package/src/input.ts CHANGED
@@ -9,7 +9,7 @@
9
9
  */
10
10
  import * as path from "node:path";
11
11
  import { applyEdits } from "./apply";
12
- import { HL_FILE_HASH_SEP, HL_FILE_PREFIX } from "./format";
12
+ import { HL_FILE_HASH_LENGTH, HL_FILE_HASH_SEP, HL_FILE_PREFIX } from "./format";
13
13
  import { parsePatch, parsePatchStreaming } from "./parser";
14
14
  import { Tokenizer } from "./tokenizer";
15
15
  import type { ApplyResult, Edit, SplitOptions } from "./types";
@@ -56,7 +56,7 @@ function tryParseRecoveryHeader(line: string, cwd?: string): RawSection | null {
56
56
  if (!line.startsWith(HL_FILE_PREFIX)) return null;
57
57
  const body = stripApplyPatchPathNoise(line.slice(HL_FILE_PREFIX.length).trim());
58
58
  if (body.length === 0) return null;
59
- const match = /^(\S+?)(?:#([0-9A-Fa-f]{3}))?\s*$/.exec(body);
59
+ const match = new RegExp(`^(\\S+?)(?:#([0-9A-Fa-f]{${HL_FILE_HASH_LENGTH}}))?\\s*$`).exec(body);
60
60
  if (match === null) return null;
61
61
  const path = normalizeHashlinePath(match[1], cwd);
62
62
  if (path.length === 0) return null;
@@ -95,7 +95,7 @@ function parseHashlineHeaderLine(line: string, cwd?: string): RawSection | null
95
95
  const recovered = tryParseRecoveryHeader(trimmed, cwd);
96
96
  if (recovered !== null) return recovered;
97
97
  throw new Error(
98
- `Input header must be ${HL_FILE_PREFIX}PATH or ${HL_FILE_PREFIX}PATH${HL_FILE_HASH_SEP}TAG with a 3-hex snapshot tag; got ${JSON.stringify(trimmed)}.`,
98
+ `Input header must be ${HL_FILE_PREFIX}PATH or ${HL_FILE_PREFIX}PATH${HL_FILE_HASH_SEP}TAG with a ${HL_FILE_HASH_LENGTH}-hex content-hash tag; got ${JSON.stringify(trimmed)}.`,
99
99
  );
100
100
  }
101
101
 
@@ -159,7 +159,7 @@ function splitRawSections(input: string, options: SplitOptions = {}): RawSection
159
159
  if (/^@@\s+[-+]?\d+,\d+\s+[-+]?\d+,\d+\s+@@/.test(firstTrimmed)) {
160
160
  throw new Error(
161
161
  "unified-diff hunk header (`@@ -N,M +N,M @@`) is not valid in hashline. " +
162
- "File sections start with `¶path#HASH`; hunks are bare `A B` lines.",
162
+ "File sections start with `¶path#HASH`; use `replace`, `delete`, or `insert` ops.",
163
163
  );
164
164
  }
165
165
  const preview = JSON.stringify(firstLine.slice(0, 120));
@@ -244,14 +244,14 @@ export class PatchSection {
244
244
  }
245
245
 
246
246
  /**
247
- * True when at least one edit anchors to concrete file content. Pure BOF/EOF
248
- * literal inserts do not count: those are safe to apply to files that don't
249
- * yet exist.
247
+ * True when at least one edit anchors to concrete file content. Pure
248
+ * `insert head:` / `insert tail:` literal inserts do not count: those are
249
+ * safe to apply to files that don't yet exist.
250
250
  */
251
251
  get hasAnchorScopedEdit(): boolean {
252
252
  return this.edits.some(edit => {
253
- if (edit.kind === "delete" || edit.kind === "repeat") return true;
254
- return edit.cursor.kind === "before_anchor";
253
+ if (edit.kind === "delete") return true;
254
+ return edit.cursor.kind === "before_anchor" || edit.cursor.kind === "after_anchor";
255
255
  });
256
256
  }
257
257
 
@@ -263,10 +263,7 @@ export class PatchSection {
263
263
  lines.add(edit.anchor.line);
264
264
  continue;
265
265
  }
266
- if (edit.kind === "repeat") {
267
- for (let line = edit.range.start.line; line <= edit.range.end.line; line++) lines.add(line);
268
- }
269
- if (edit.cursor.kind === "before_anchor") {
266
+ if (edit.cursor.kind === "before_anchor" || edit.cursor.kind === "after_anchor") {
270
267
  lines.add(edit.cursor.anchor.line);
271
268
  }
272
269
  }
package/src/messages.ts CHANGED
@@ -21,49 +21,30 @@ export const END_PATCH_MARKER = "*** End Patch";
21
21
  */
22
22
  export const ABORT_MARKER = "*** Abort";
23
23
 
24
- /**
25
- * Warning text appended when two consecutive hunks target the exact same
26
- * concrete range. The second hunk wins; the first is discarded.
27
- */
24
+ /** Warning text appended when two consecutive hunks target the exact same concrete range. */
28
25
  export const REPLACE_PAIR_COALESCED_WARNING =
29
- "Detected two identical-range hashline hunks; kept only the second hunk. Issue ONE hunk per range — payload is the final desired content, never both old and new.";
26
+ "Detected two identical-range hashline hunks; kept only the second hunk. Issue ONE `replace N..M:` hunk per range — payload is the final desired content, never both old and new.";
30
27
 
31
- /**
32
- * Warning text appended when a bare hunk header (`A B` with no body)
33
- * is followed by an overlapping concrete hunk. The earlier bare hunk is
34
- * dropped on the assumption that the model expressed an old/new pair across
35
- * two hunks; only the second hunk's payload is applied.
36
- */
28
+ /** Warning text appended when an empty bodyless hunk is followed by an overlapping concrete hunk. */
37
29
  export const REPLACE_PAIR_COALESCED_OVERLAP_WARNING =
38
- "Detected an overlapping bare hashline hunk immediately followed by a concrete hunk; dropped the earlier bare hunk. Issue ONE hunk per range — payload is the final desired content, never both old and new.";
30
+ "Detected an overlapping bare hashline hunk immediately followed by a concrete hunk; dropped the earlier bare hunk. Issue ONE `replace N..M:` hunk per range — payload is the final desired content, never both old and new.";
39
31
 
40
- /**
41
- * Warning text appended when bare body rows (no `+` / `&` prefix) follow a
42
- * hunk header and the parser auto-converts them to `+literal` rows because
43
- * no `+`/`&` row was present in the hunk. Helps the model learn the
44
- * canonical body-row syntax while keeping the patch applying.
45
- */
32
+ /** Warning text appended when bare body rows are auto-converted to literal rows. */
46
33
  export const BARE_BODY_AUTO_PIPED_WARNING =
47
- "Auto-prefixed bare body row(s) with `+`. Always start payload rows with `+TEXT` (literal) or `&A..B` (repeat) — pasting raw code as payload is not a portable shape.";
34
+ "Auto-prefixed bare body row(s) with `+`. Body rows must be `+TEXT` literal lines; pasting raw code as payload is not a portable shape.";
48
35
 
49
- /**
50
- * Warning text emitted when a body row begins with `+&A..B` — the model
51
- * mistakenly prefixed a repeat row with the `+` literal sigil. We reroute
52
- * the row as a `&A..B` repeat so the patch still applies, then surface this
53
- * warning so the model sees the mistake on the next turn.
54
- */
55
- export const PLUS_PREFIXED_REPEAT_WARNING =
56
- "A body row started with `+&A..B`. `+` (literal text) and `&A..B` (repeat) are sibling row kinds — a row uses exactly one of them. Treated as `&A..B`; remove the leading `+` next time.";
36
+ /** Error text emitted when a hunk body contains a unified-diff-style `-` row. */
37
+ export const MINUS_ROW_REJECTED =
38
+ "`-` rows are not valid; hashline ranges already name the lines being changed. To insert a literal line starting with `-`, write `+-…`.";
57
39
 
58
- /**
59
- * Warning text emitted when a hunk body contains unified-diff-style rows
60
- * (`-old`, ` context`) and the parser silently converts them: `-` rows are
61
- * dropped (the hunk header's range already deletes those lines), and the
62
- * leading metadata-space on context rows is stripped once unified-diff
63
- * mode is detected. Bare body rows are auto-prefixed with `+` regardless.
64
- */
65
- export const UNIFIED_DIFF_BODY_AUTO_CONVERT_WARNING =
66
- "Hunk body contained unified-diff-style rows (`-old`, ` context`). The `-` rows were dropped (the hunk header's range already deletes those lines); context rows were treated as `+TEXT` literals. Use `+TEXT` (literal) or `&A..B` (repeat) directly next time.";
40
+ /** Error text emitted when a replace hunk has no body. */
41
+ export const EMPTY_REPLACE = "`replace N..M:` needs at least one `+TEXT` body row. To delete lines, use `delete N..M`.";
42
+
43
+ /** Error text emitted when a delete hunk receives a body row. */
44
+ export const DELETE_TAKES_NO_BODY = "`delete N..M` does not take body rows. Remove the body, or use `replace N..M:`.";
45
+
46
+ /** Error text emitted when an insert hunk has no body. */
47
+ export const EMPTY_INSERT = "`insert` needs at least one `+TEXT` body row.";
67
48
 
68
49
  /** Warning text emitted by `Recovery` when an external write fits a cached snapshot. */
69
50
  export const RECOVERY_EXTERNAL_WARNING =
package/src/mismatch.ts CHANGED
@@ -6,17 +6,16 @@
6
6
  * plus a couple of lines of surrounding context. The {@link MismatchError}
7
7
  * formats this into a message at construction time.
8
8
  */
9
- import { formatNumberedLine, HL_FILE_HASH_SEP, HL_FILE_PREFIX } from "./format";
9
+ import { formatNumberedLine, HL_FILE_HASH_EXAMPLES, HL_FILE_HASH_SEP, HL_FILE_PREFIX } from "./format";
10
10
  import { MISMATCH_CONTEXT } from "./messages";
11
11
 
12
12
  const LINE_REF_RE = /^\s*[>+\-*]*\s*(\d+)(?::.*)?\s*$/;
13
-
14
13
  /** Format the required-shape diagnostic shown when a line reference is malformed. */
15
14
  export function formatFullAnchorRequirement(raw?: string): string {
16
15
  const received = raw === undefined ? "" : ` Received ${JSON.stringify(raw)}.`;
17
16
  return (
18
- `a bare line number from read/search output plus the section header snapshot tag ` +
19
- `(for example ${HL_FILE_PREFIX}src/foo.ts${HL_FILE_HASH_SEP}0A3 and line "160")${received}`
17
+ `a bare line number from read/search output plus the section header content-hash tag ` +
18
+ `(for example ${HL_FILE_PREFIX}src/foo.ts${HL_FILE_HASH_SEP}${HL_FILE_HASH_EXAMPLES[0]} and line "160")${received}`
20
19
  );
21
20
  }
22
21