@oh-my-pi/hashline 17.2.15 → 17.3.0
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 +6 -0
- package/dist/types/apply.d.ts +13 -15
- package/dist/types/messages.d.ts +20 -33
- package/dist/types/syntax.d.ts +8 -7
- package/package.json +3 -3
- package/src/apply.ts +536 -1003
- package/src/messages.ts +39 -58
- package/src/prompt.md +4 -2
- package/src/syntax.ts +33 -7
package/src/messages.ts
CHANGED
|
@@ -281,11 +281,10 @@ export function pasteAfterBlockUnresolvedLoweredWarning(line: number): string {
|
|
|
281
281
|
return unresolvedLoweredWarning(`PUT >${line}*`, line, `PUT >${line}`);
|
|
282
282
|
}
|
|
283
283
|
/**
|
|
284
|
-
* A one-sided boundary echo
|
|
285
|
-
*
|
|
286
|
-
*
|
|
287
|
-
*
|
|
288
|
-
* edit is rejected instead of repaired.
|
|
284
|
+
* A one-sided exact boundary echo cannot cover the selected range after the
|
|
285
|
+
* duplicated body rows are removed. Applying or dropping it would lose
|
|
286
|
+
* distinct range content, so the edit is rejected unless a parse-restoring
|
|
287
|
+
* boundary combination proves another reading.
|
|
289
288
|
*/
|
|
290
289
|
export function ambiguousBoundaryEchoMessage(
|
|
291
290
|
startLine: number,
|
|
@@ -299,82 +298,60 @@ export function ambiguousBoundaryEchoMessage(
|
|
|
299
298
|
: `ends by restating the ${count} line(s) just below the range`;
|
|
300
299
|
return (
|
|
301
300
|
`\`PUT ${startLine}${HL_RANGE_SEP}${endLine}:\` rejected: the body ${where}, ` +
|
|
302
|
-
`but is too short to be the full final content of the
|
|
303
|
-
`
|
|
304
|
-
`Re-issue with the range covering exactly the lines that change and the body as their complete ` +
|
|
305
|
-
`final content: drop the restated keeper from the body, or widen the range to consume it.`
|
|
301
|
+
`but is too short to be the full final content of the selected range. ` +
|
|
302
|
+
`Re-issue with the range covering exactly the lines that change and the body as their complete final content.`
|
|
306
303
|
);
|
|
307
304
|
}
|
|
308
305
|
|
|
309
306
|
/**
|
|
310
|
-
* A
|
|
311
|
-
*
|
|
312
|
-
* terminate: the payload has no unmatched opener for them and its indentation
|
|
313
|
-
* is not deeper than the closer. Sparing the closer would have to guess
|
|
314
|
-
* whether the payload belongs before it (inside the block) or after it (a
|
|
315
|
-
* sibling), so the edit is rejected instead of repaired.
|
|
307
|
+
* A syntax-essential selected edge can be retained on either side of the
|
|
308
|
+
* payload, but indentation does not establish which placement was intended.
|
|
316
309
|
*/
|
|
317
|
-
export function
|
|
318
|
-
startLine: number,
|
|
319
|
-
endLine: number,
|
|
320
|
-
closerLine: number,
|
|
321
|
-
count: number,
|
|
322
|
-
): string {
|
|
323
|
-
const closers = count === 1 ? `line ${closerLine}` : `lines ${closerLine}-${closerLine + count - 1}`;
|
|
310
|
+
export function ambiguousBoundaryPlacementMessage(startLine: number, endLine: number): string {
|
|
324
311
|
return (
|
|
325
|
-
`\`PUT ${startLine}${HL_RANGE_SEP}${endLine}:\` rejected:
|
|
326
|
-
|
|
327
|
-
`
|
|
328
|
-
`before or after the closer is ambiguous. Restate the closer in the body at the intended position, ` +
|
|
329
|
-
`or use \`PUT <${closerLine}:\` / \`PUT >${closerLine}:\` instead.`
|
|
312
|
+
`\`PUT ${startLine}${HL_RANGE_SEP}${endLine}:\` rejected: a selected boundary row is required for the file to parse, ` +
|
|
313
|
+
`but the body indentation does not establish whether it belongs before or after that row. ` +
|
|
314
|
+
`Re-read the region and re-issue with a range that excludes every unchanged boundary row.`
|
|
330
315
|
);
|
|
331
316
|
}
|
|
317
|
+
|
|
332
318
|
/**
|
|
333
|
-
*
|
|
334
|
-
*
|
|
335
|
-
* the construct above" mistake — but the payload's indentation claims a depth
|
|
336
|
-
* inside the block those closers terminate, so whether the new content
|
|
337
|
-
* belongs before or after the spared closer is ambiguous. Rejected instead of
|
|
338
|
-
* repaired; the at-or-above-depth reading is auto-repaired by sparing the
|
|
339
|
-
* closer ahead of the payload.
|
|
319
|
+
* Exact-text boundary rows were removed because the remaining payload covers
|
|
320
|
+
* the selected range and the same rows already survive immediately outside it.
|
|
340
321
|
*/
|
|
341
|
-
export function
|
|
342
|
-
const
|
|
322
|
+
export function textualBoundaryEchoWarning(startLine: number, leading: number, trailing: number): string {
|
|
323
|
+
const parts: string[] = [];
|
|
324
|
+
if (leading > 0) parts.push(`${leading} leading`);
|
|
325
|
+
if (trailing > 0) parts.push(`${trailing} trailing`);
|
|
343
326
|
return (
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
`closer terminates — whether the new content belongs before or after the closer is ambiguous. ` +
|
|
347
|
-
`Start the range on the first line that actually changes, or restate the closer in the body at the intended position.`
|
|
327
|
+
`Auto-repaired a replacement boundary echo at line ${startLine}: dropped ${parts.join(" and ")} body line(s) ` +
|
|
328
|
+
`already present outside the range. Issue the body as final content for the selected range only.`
|
|
348
329
|
);
|
|
349
330
|
}
|
|
350
331
|
|
|
351
332
|
/**
|
|
352
|
-
* A replacement range
|
|
353
|
-
*
|
|
354
|
-
*
|
|
355
|
-
*
|
|
356
|
-
* and opener/closer text shape cannot prove a syntactic block (the braces may
|
|
357
|
-
* be literal prose), so the edit applies as authored and the author decides.
|
|
333
|
+
* A replacement range's boundary disposition was corrected by the
|
|
334
|
+
* syntax-probe-judged search: syntax-essential source boundary rows were
|
|
335
|
+
* retained, or exact body echoes of surviving outside rows were removed. The
|
|
336
|
+
* authored result did not parse and the selected result does.
|
|
358
337
|
*/
|
|
359
|
-
export function
|
|
338
|
+
export function boundaryVariantRepairWarning(startLine: number, kept: number, dropped: number): string {
|
|
339
|
+
const keptPart = kept === 0 ? "" : `retained ${kept} syntax-essential source boundary row(s) selected by the range`;
|
|
340
|
+
const droppedPart = dropped === 0 ? "" : `dropped ${dropped} body row(s) duplicated just outside the range`;
|
|
341
|
+
const action = [keptPart, droppedPart].filter(Boolean).join(" and ");
|
|
360
342
|
return (
|
|
361
|
-
|
|
362
|
-
`
|
|
363
|
-
`
|
|
364
|
-
`re-issue with a block op on the construct's opening line (\`PUT N*:\`) so the closing line resolves ` +
|
|
365
|
-
`automatically; if the delimiters are literal text, ignore this warning.`
|
|
343
|
+
`Auto-repaired replacement boundaries at line ${startLine}: ${action}. ` +
|
|
344
|
+
`The result was verified by the syntax probe — re-issue with the range covering exactly the changed ` +
|
|
345
|
+
`lines and the body as their complete final content.`
|
|
366
346
|
);
|
|
367
347
|
}
|
|
368
348
|
|
|
369
349
|
/**
|
|
370
350
|
* The applied result no longer parses while the pre-edit content did: the
|
|
371
351
|
* patch introduced a syntax error. Advisory, never a rejection — the applier
|
|
372
|
-
* honors the authored edit — but the breakage is machine-confirmed
|
|
373
|
-
* tree-sitter
|
|
374
|
-
*
|
|
375
|
-
* classic trigger is a balance-neutral misplacement: a statement landed on
|
|
376
|
-
* the wrong line number with no delimiter anomaly for the repair heuristics
|
|
377
|
-
* to notice.
|
|
352
|
+
* honors the authored edit — but the breakage is machine-confirmed by
|
|
353
|
+
* tree-sitter and surfaced in the same response instead of waiting for a
|
|
354
|
+
* compiler pass.
|
|
378
355
|
*/
|
|
379
356
|
export function editBrokeParseWarning(firstChangedLine: number | undefined): string {
|
|
380
357
|
const at = firstChangedLine === undefined ? "" : ` near line ${firstChangedLine}`;
|
|
@@ -392,6 +369,10 @@ export function editBrokeParseWarning(firstChangedLine: number | undefined): str
|
|
|
392
369
|
export const UNRESOLVED_BLOCK_INTERNAL =
|
|
393
370
|
"internal error: unresolved block edit reached the applier (resolveBlockEdits was not run).";
|
|
394
371
|
|
|
372
|
+
/** Internal invariant: clipboard edits must be concrete before application. */
|
|
373
|
+
export const UNRESOLVED_CLIPBOARD_INTERNAL =
|
|
374
|
+
"internal error: unresolved clipboard edit reached the applier (resolveClipboardEdits was not run).";
|
|
375
|
+
|
|
395
376
|
/** `REM` received a body row or coexists with line edits. */
|
|
396
377
|
export const REM_TAKES_NO_BODY =
|
|
397
378
|
"`REM` deletes the whole file and takes no body rows or line ops. Issue it alone under the header.";
|
package/src/prompt.md
CHANGED
|
@@ -7,9 +7,11 @@ Section: `[PATH#TAG]`; `TAG`: 4-hex snapshot from latest `read`/`search`, REQUIR
|
|
|
7
7
|
<ops>
|
|
8
8
|
`PUT N.=M:`: replace original inclusive lines N–M with body.
|
|
9
9
|
`PUT N*:`: replace syntactic block beginning N; closing line resolved.
|
|
10
|
-
`PUT <N:`
|
|
10
|
+
`PUT <N:` insert body rows before line N (`PUT <1:` = file head).
|
|
11
|
+
`PUT >N:` insert body rows after line N (`PUT >$:` = file tail).
|
|
11
12
|
`PUT >N*:`: insert after block N's end, at sibling depth. Append inside block: `PUT >M:`.
|
|
12
|
-
`PUT <N
|
|
13
|
+
`PUT <N @name` / `PUT >N @name` paste register `@name` at the gap before/after line N; omit `@name` for the anonymous register.
|
|
14
|
+
`PUT N.=M @name` / `PUT N* @name` paste `@name` over the range / resolved block; `@name` required here.
|
|
13
15
|
`CUT N.=M` / `CUT N*`: delete and capture inclusive lines N–M / block N; anonymous or given `@name`.
|
|
14
16
|
`REM`: delete section file. `MV DEST`: move/rename (quote paths with spaces); prior edits apply to source, final content to `DEST`.
|
|
15
17
|
Single line: `PUT N.=N:` / `CUT N.=N`. Ranges name original inclusive touched lines; body length irrelevant.
|
package/src/syntax.ts
CHANGED
|
@@ -1,13 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Syntax probe for candidate edit results, via the native tree-sitter parser.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
* heuristics as the only available evidence.
|
|
4
|
+
* Replacement-boundary repair uses parsing as its semantic filter: exact
|
|
5
|
+
* outside-row equality may justify dropping a duplicated payload edge, while
|
|
6
|
+
* retaining a selected source boundary additionally requires source-range
|
|
7
|
+
* structure, indentation, or a narrow pure-closer shape. An unrecognized
|
|
8
|
+
* language yields no structural proof, so only evidence-complete textual
|
|
9
|
+
* normalization remains available.
|
|
11
10
|
*/
|
|
12
11
|
|
|
13
12
|
import { enclosingBlockBoundaries } from "@oh-my-pi/pi-natives";
|
|
@@ -16,6 +15,33 @@ import { enclosingBlockBoundaries } from "@oh-my-pi/pi-natives";
|
|
|
16
15
|
const parseCache = new Map<string, boolean>();
|
|
17
16
|
const PARSE_CACHE_MAX = 256;
|
|
18
17
|
|
|
18
|
+
const boundaryCache = new Map<string, readonly number[]>();
|
|
19
|
+
|
|
20
|
+
/** Syntactic node boundaries outside a visible source range. */
|
|
21
|
+
export function enclosingBoundaries(
|
|
22
|
+
lines: readonly string[],
|
|
23
|
+
path: string,
|
|
24
|
+
startLine: number,
|
|
25
|
+
endLine: number,
|
|
26
|
+
): readonly number[] {
|
|
27
|
+
const text = lines.join("\n");
|
|
28
|
+
const key = `${Bun.hash(text).toString(36)}:${text.length}:${path}:${startLine}:${endLine}`;
|
|
29
|
+
const cached = boundaryCache.get(key);
|
|
30
|
+
if (cached !== undefined) return cached;
|
|
31
|
+
let boundaries: readonly number[];
|
|
32
|
+
try {
|
|
33
|
+
boundaries = enclosingBlockBoundaries({ code: text, path, ranges: [{ startLine, endLine }] }) ?? [];
|
|
34
|
+
} catch {
|
|
35
|
+
boundaries = [];
|
|
36
|
+
}
|
|
37
|
+
if (boundaryCache.size >= PARSE_CACHE_MAX) {
|
|
38
|
+
const oldest = boundaryCache.keys().next().value;
|
|
39
|
+
if (oldest !== undefined) boundaryCache.delete(oldest);
|
|
40
|
+
}
|
|
41
|
+
boundaryCache.set(key, boundaries);
|
|
42
|
+
return boundaries;
|
|
43
|
+
}
|
|
44
|
+
|
|
19
45
|
/**
|
|
20
46
|
* `true` when `text` parses without a syntax error under the language inferred
|
|
21
47
|
* from `path`. `false` covers "does not parse" and "cannot tell" alike — no
|