@oh-my-pi/hashline 16.1.22 → 16.1.23

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/CHANGELOG.md CHANGED
@@ -2,6 +2,16 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [16.1.23] - 2026-06-26
6
+
7
+ ### Added
8
+
9
+ - Updated prompt documentation to include support for Markdown section operations
10
+
11
+ ### Fixed
12
+
13
+ - Improved file path recovery to correctly handle read-only or incorrectly typed paths
14
+
5
15
  ## [16.1.14] - 2026-06-22
6
16
 
7
17
  ### Fixed
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "type": "module",
3
3
  "name": "@oh-my-pi/hashline",
4
- "version": "16.1.22",
4
+ "version": "16.1.23",
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/patcher.ts CHANGED
@@ -256,13 +256,15 @@ export class Patcher {
256
256
 
257
257
  let target = section;
258
258
  let canonicalPath = this.fs.canonicalPath(target.path);
259
- await this.fs.preflightWrite(target.path);
260
259
  let read = await this.#tryRead(target.path);
261
260
 
262
261
  // Path recovery: the authored path doesn't exist on disk, but its
263
262
  // filename + snapshot tag may name a file the model read this session
264
263
  // (it supplied a bare filename, or the wrong directory). Rebind to that
265
- // file so the edit lands where the tag points, and warn.
264
+ // file so the edit lands where the tag points, and warn. This runs
265
+ // before the write gate so a recoverable bare/mis-typed path is rebound
266
+ // to its real (writable) location instead of being rejected against the
267
+ // literal — possibly read-only — path it was authored as.
266
268
  if (!read.exists) {
267
269
  const recovered = this.#recoverSectionPathFromTag(target, canonicalPath);
268
270
  if (recovered && this.fs.allowTagPathRecovery(target.path, recovered.section.path)) {
@@ -271,10 +273,15 @@ export class Patcher {
271
273
  );
272
274
  target = recovered.section;
273
275
  canonicalPath = recovered.canonicalPath;
274
- await this.fs.preflightWrite(target.path);
275
276
  read = await this.#tryRead(target.path);
276
277
  }
277
278
  }
279
+
280
+ // Gate the final (possibly recovered) target before any write work, so
281
+ // an unrecoverable read-only target (e.g. a plan-mode working-tree path)
282
+ // fails with the write guard rather than a misleading "file not found".
283
+ await this.fs.preflightWrite(target.path);
284
+
278
285
  if (!read.exists) {
279
286
  throw new Error(`File not found: ${target.path}. Use the write tool to create new files.`);
280
287
  }
package/src/prompt.md CHANGED
@@ -34,6 +34,7 @@ Body rows appear only under a `:` header. Every body row is `+TEXT` — add a li
34
34
  - Whole construct → `SWAP.BLK N` (tree-sitter resolves the end); lines inside it → `SWAP N.=M`.
35
35
  - `SWAP.BLK N` resolves EXACTLY the node at N. Leading decorators/attributes/doc-comments are separate nodes: point N at the FIRST decorator to sweep both; standalone line-comments are never swept — use `SWAP N.=M`.
36
36
  - Block ops (`SWAP.BLK`/`DEL.BLK`/`INS.BLK.POST`) anchor the OPENING line of a MULTI-LINE construct — never its closer, last line, or a bare inner statement. Anchoring one statement resolves to ONE line and is REJECTED: use the plain op (`SWAP N.=N` / `DEL N` / `INS.POST N`), or point N at the real opener. Saw the closer? Use plain `INS.POST M:`.
37
+ - Markdown: a heading line IS a block opener — `SWAP.BLK`/`DEL.BLK`/`INS.BLK.POST` on a `##`/`###` heading resolves its WHOLE section (heading through every nested deeper heading, up to the next same-or-higher heading). So `DEL.BLK` drops the section, `SWAP.BLK` rewrites it, `INS.BLK.POST` lands after it (end the inserted body with a blank line to keep the next heading separated).
37
38
  - Non-adjacent changes = separate hunks; untouched lines stay out of every range.
38
39
  - Pure additions use `INS.PRE` / `INS.POST` / `INS.HEAD` / `INS.TAIL`, never a widened `SWAP` — retyped keepers are exactly what gets dropped. (A multi-line `SWAP` whose body restates the line just past the range is auto-dropped as an off-by-one keeper with a warning — issue the payload for the range only; never lean on the repair.)
39
40
  - NEVER format/restyle code with this tool; run the project formatter instead.