@oh-my-pi/hashline 15.5.9 → 15.5.11

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.
@@ -10,6 +10,14 @@ export interface MismatchDetails {
10
10
  actualFileHash: string;
11
11
  fileLines: string[];
12
12
  anchorLines?: readonly number[];
13
+ /**
14
+ * `true` when the section's expected hash resolved to a recorded snapshot
15
+ * (file content drifted since that snapshot), `false` when no snapshot
16
+ * was ever recorded for the hash (likely fabricated or carried over from
17
+ * a prior session). Drives a more actionable rejection message; defaults
18
+ * to `true` for backward compatibility with direct callers.
19
+ */
20
+ hashRecognized?: boolean;
13
21
  }
14
22
  /**
15
23
  * Raised when a hashline section's snapshot tag doesn't match the live file's
@@ -23,6 +31,7 @@ export declare class MismatchError extends Error {
23
31
  readonly actualFileHash: string;
24
32
  readonly fileLines: string[];
25
33
  readonly anchorLines: readonly number[];
34
+ readonly hashRecognized: boolean;
26
35
  constructor(details: MismatchDetails);
27
36
  get displayMessage(): string;
28
37
  static rejectionHeader(details: MismatchDetails): string[];
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "type": "module",
3
3
  "name": "@oh-my-pi/hashline",
4
- "version": "15.5.9",
4
+ "version": "15.5.11",
5
5
  "description": "Hashline: a compact, line-anchored patch language and applier. Pluggable FS/IO so it works over disk, in-memory, or any custom backend.",
6
6
  "homepage": "https://omp.sh",
7
7
  "author": "Can Boluk",
package/src/mismatch.ts CHANGED
@@ -37,6 +37,14 @@ export interface MismatchDetails {
37
37
  actualFileHash: string;
38
38
  fileLines: string[];
39
39
  anchorLines?: readonly number[];
40
+ /**
41
+ * `true` when the section's expected hash resolved to a recorded snapshot
42
+ * (file content drifted since that snapshot), `false` when no snapshot
43
+ * was ever recorded for the hash (likely fabricated or carried over from
44
+ * a prior session). Drives a more actionable rejection message; defaults
45
+ * to `true` for backward compatibility with direct callers.
46
+ */
47
+ hashRecognized?: boolean;
40
48
  }
41
49
 
42
50
  function getMismatchDisplayLines(anchorLines: readonly number[], fileLines: string[]): number[] {
@@ -62,6 +70,7 @@ export class MismatchError extends Error {
62
70
  readonly actualFileHash: string;
63
71
  readonly fileLines: string[];
64
72
  readonly anchorLines: readonly number[];
73
+ readonly hashRecognized: boolean;
65
74
 
66
75
  constructor(details: MismatchDetails) {
67
76
  super(MismatchError.formatMessage(details));
@@ -71,6 +80,7 @@ export class MismatchError extends Error {
71
80
  this.actualFileHash = details.actualFileHash;
72
81
  this.fileLines = details.fileLines;
73
82
  this.anchorLines = details.anchorLines ?? [];
83
+ this.hashRecognized = details.hashRecognized ?? true;
74
84
  }
75
85
 
76
86
  get displayMessage(): string {
@@ -80,14 +90,22 @@ export class MismatchError extends Error {
80
90
  actualFileHash: this.actualFileHash,
81
91
  fileLines: this.fileLines,
82
92
  anchorLines: this.anchorLines,
93
+ hashRecognized: this.hashRecognized,
83
94
  });
84
95
  }
85
96
 
86
97
  static rejectionHeader(details: MismatchDetails): string[] {
87
98
  const pathText = details.path ? ` for ${details.path}` : "";
99
+ const hashRecognized = details.hashRecognized ?? true;
100
+ if (!hashRecognized) {
101
+ return [
102
+ `Edit rejected${pathText}: hash ${HL_FILE_HASH_SEP}${details.expectedFileHash} is not from this session.`,
103
+ `The current file hashes to ${HL_FILE_HASH_SEP}${details.actualFileHash}. Re-read the file with \`read\` to copy a current ${HL_FILE_PREFIX}path${HL_FILE_HASH_SEP}tag header — never invent the tag and never reuse one from a prior session.`,
104
+ ];
105
+ }
88
106
  return [
89
107
  `Edit rejected${pathText}: file changed between read and edit.`,
90
- `Section is bound to ${HL_FILE_HASH_SEP}${details.expectedFileHash}, but the current file hashes to ${HL_FILE_HASH_SEP}${details.actualFileHash}. If your previous edit in this session modified this file, copy the ${HL_FILE_PREFIX}path${HL_FILE_HASH_SEP}newhash from that edit's response. Otherwise re-read the file before retrying.`,
108
+ `Section is bound to ${HL_FILE_HASH_SEP}${details.expectedFileHash}, but the current file hashes to ${HL_FILE_HASH_SEP}${details.actualFileHash}. If a prior edit in this session modified this file, copy the ${HL_FILE_PREFIX}path${HL_FILE_HASH_SEP}newhash header from that edit's response; otherwise re-read the file with \`read\` to refresh the tag before retrying.`,
91
109
  ];
92
110
  }
93
111
 
package/src/patcher.ts CHANGED
@@ -359,6 +359,7 @@ export class Patcher {
359
359
  actualFileHash: currentHash,
360
360
  fileLines: normalized.split("\n"),
361
361
  anchorLines: section.collectAnchorLines(),
362
+ hashRecognized: snapshot !== null,
362
363
  });
363
364
  }
364
365
  }