pi-hashline-edit-pro 3.0.4 → 4.0.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/README.md +111 -129
- package/index.ts +22 -11
- package/package.json +1 -1
- package/prompts/grep.md +1 -1
- package/prompts/insert-guidelines.md +1 -2
- package/prompts/insert-snippet.md +1 -1
- package/prompts/insert.md +1 -1
- package/prompts/read.md +1 -1
- package/prompts/replace-guidelines.md +0 -1
- package/prompts/replace-snippet.md +1 -1
- package/prompts/replace.md +1 -1
- package/prompts/undo-last-change.md +1 -1
- package/src/anchor-registry.ts +605 -0
- package/src/commit.ts +21 -7
- package/src/constants.ts +4 -1
- package/src/edit-common.ts +45 -8
- package/src/file-reader.ts +4 -1
- package/src/grep.ts +26 -8
- package/src/hash-store/cache.ts +0 -4
- package/src/hash-store.ts +50 -137
- package/src/hashline/anchor-table.json +1 -1
- package/src/hashline/apply.ts +4 -1
- package/src/hashline/hash.ts +12 -175
- package/src/hashline/parse.ts +1 -1
- package/src/hashline/resolve.ts +11 -43
- package/src/insert.ts +16 -31
- package/src/paths.ts +4 -0
- package/src/payload-contract.ts +3 -13
- package/src/read.ts +4 -5
- package/src/replace-render.ts +11 -2
- package/src/replace-undo.ts +15 -5
- package/src/replace.ts +26 -42
- package/src/served.ts +14 -121
- package/src/write-hook.ts +2 -4
- package/src/missing-path.ts +0 -54
package/src/replace.ts
CHANGED
|
@@ -3,13 +3,14 @@ import type {
|
|
|
3
3
|
ToolDefinition,
|
|
4
4
|
} from "@earendil-works/pi-coding-agent";
|
|
5
5
|
import { constants } from "fs";
|
|
6
|
+
import { relative } from "path";
|
|
6
7
|
import {
|
|
7
8
|
genDiff,
|
|
8
9
|
type LineEnding,
|
|
9
10
|
} from "./replace-diff";
|
|
10
11
|
import { readNormFile, type NormFile } from "./file-reader";
|
|
11
12
|
import { editToolSchema, type ReqParams, assertReq, normReq } from "./payload-contract";
|
|
12
|
-
import { decodeStringArray
|
|
13
|
+
import { decodeStringArray } from "./utils";
|
|
13
14
|
import { loadP, loadGuide } from "./prompts";
|
|
14
15
|
import { type FileIdentity } from "./fs-write";
|
|
15
16
|
import { applyEdit,
|
|
@@ -28,10 +29,11 @@ import {
|
|
|
28
29
|
type RRState,
|
|
29
30
|
} from "./replace-render";
|
|
30
31
|
import { loadHashStore, type HashStore } from "./hash-store";
|
|
31
|
-
import {
|
|
32
|
-
import {
|
|
32
|
+
import { adoptAnchors, servedForPath } from "./anchor-registry";
|
|
33
|
+
import { resolveTarget } from "./fs-write";
|
|
34
|
+
import { toCwd } from "./paths";
|
|
33
35
|
import { noopPayloadKey, markBoundaryNoop, consumeBoundaryBypass, clearBoundaryBypass } from "./boundary-bypass";
|
|
34
|
-
import { queuedEdit, editToolBase, editRenderCallWrapper, editRenderResultWrapper } from "./edit-common";
|
|
36
|
+
import { queuedEdit, editToolBase, editRenderCallWrapper, editRenderResultWrapper, resolveEditTarget } from "./edit-common";
|
|
35
37
|
|
|
36
38
|
export { editToolSchema, type ReqParams, assertReq };
|
|
37
39
|
|
|
@@ -79,7 +81,7 @@ export interface ExecPipelineOptions {
|
|
|
79
81
|
preloadedNorm?: NormFile;
|
|
80
82
|
}
|
|
81
83
|
|
|
82
|
-
function hashSpan(hashes: string[], from: string, to: string): [number, number] | undefined {
|
|
84
|
+
export function hashSpan(hashes: string[], from: string, to: string): [number, number] | undefined {
|
|
83
85
|
const a = hashes.indexOf(from);
|
|
84
86
|
const b = hashes.indexOf(to);
|
|
85
87
|
if (a < 0 || b < 0) return undefined;
|
|
@@ -87,20 +89,11 @@ function hashSpan(hashes: string[], from: string, to: string): [number, number]
|
|
|
87
89
|
}
|
|
88
90
|
async function noteAnchorError(absolutePath: string, error: unknown, scopeHashes: string[], noPersist?: boolean): Promise<void> {
|
|
89
91
|
if (noPersist === true) return;
|
|
90
|
-
if (error instanceof RangeStaleError)
|
|
91
|
-
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
function collectRemovedHashes(
|
|
95
|
-
edit: HEdit,
|
|
96
|
-
originalHashes: string[],
|
|
97
|
-
): Set<string> {
|
|
98
|
-
const span = hashSpan(originalHashes, edit.hash_bounds[0].hash, edit.hash_bounds[1].hash);
|
|
99
|
-
const removedHashes = new Set<string>();
|
|
100
|
-
if (span) {
|
|
101
|
-
for (let i = span[0]; i <= span[1]; i++) removedHashes.add(originalHashes[i]!);
|
|
92
|
+
if (error instanceof RangeStaleError) {
|
|
93
|
+
adoptAnchors(absolutePath, error.rangeServedMap);
|
|
94
|
+
} else if (error instanceof AnchorMismatchError) {
|
|
95
|
+
adoptAnchors(absolutePath, error.feedbackMap);
|
|
102
96
|
}
|
|
103
|
-
return removedHashes;
|
|
104
97
|
}
|
|
105
98
|
|
|
106
99
|
function countLineChanges(
|
|
@@ -119,13 +112,12 @@ function countLineChanges(
|
|
|
119
112
|
}
|
|
120
113
|
|
|
121
114
|
export async function execPipeline(
|
|
115
|
+
targetPath: string,
|
|
122
116
|
params: ReqParams,
|
|
123
117
|
cwd: string,
|
|
124
118
|
options?: ExecPipelineOptions,
|
|
125
119
|
): Promise<PipelineResult> {
|
|
126
120
|
|
|
127
|
-
const path = params.path;
|
|
128
|
-
|
|
129
121
|
const editWarnings: string[] = [];
|
|
130
122
|
let replacementLines = params.replacement_lines;
|
|
131
123
|
const expandedReplacement = decodeStringArray(replacementLines);
|
|
@@ -143,11 +135,13 @@ export async function execPipeline(
|
|
|
143
135
|
);
|
|
144
136
|
|
|
145
137
|
const hashStore = options?.store ?? await loadHashStore();
|
|
138
|
+
const preResolvedPath = await resolveTarget(toCwd(targetPath, cwd));
|
|
139
|
+
const served = servedForPath(preResolvedPath);
|
|
146
140
|
const { normalized: originalNormalized, bom, originalEnding, fileHashes: originalHashes, hadUtf8DecodeErrors, absolutePath, identity } = await readNormFile(
|
|
147
|
-
|
|
141
|
+
targetPath, cwd, { signal: options?.signal, accessMode: options?.accessMode, maxLines: MAX_HASH_LINES, store: hashStore, noPersist: options?.noPersist, allocation: options?.noPersist ? "shadow" : "real", preloadedNorm: options?.preloadedNorm },
|
|
148
142
|
);
|
|
143
|
+
const displayPath = relative(cwd, absolutePath).replace(/\\/g, "/") || targetPath;
|
|
149
144
|
|
|
150
|
-
const served = await getServed(hashStore, absolutePath);
|
|
151
145
|
let anchorResult: ReturnType<typeof applyEdit>;
|
|
152
146
|
try {
|
|
153
147
|
anchorResult = applyEdit(
|
|
@@ -155,7 +149,7 @@ export async function execPipeline(
|
|
|
155
149
|
edit,
|
|
156
150
|
options?.signal,
|
|
157
151
|
originalHashes,
|
|
158
|
-
|
|
152
|
+
displayPath,
|
|
159
153
|
served,
|
|
160
154
|
options?.skipBoundaryDedup,
|
|
161
155
|
);
|
|
@@ -167,17 +161,12 @@ export async function execPipeline(
|
|
|
167
161
|
const result = anchorResult.content;
|
|
168
162
|
const isNoop = result === originalNormalized;
|
|
169
163
|
|
|
170
|
-
const noPersist = options?.noPersist;
|
|
171
|
-
const removedHashes = isNoop
|
|
172
|
-
? undefined
|
|
173
|
-
: collectRemovedHashes(edit, originalHashes);
|
|
174
164
|
const resultHashes = isNoop
|
|
175
165
|
? originalHashes
|
|
176
166
|
: await lineHashes(result, absolutePath, {
|
|
177
167
|
content: originalNormalized,
|
|
178
168
|
hashes: originalHashes,
|
|
179
|
-
|
|
180
|
-
}, hashStore, noPersist !== true);
|
|
169
|
+
}, hashStore, false, true);
|
|
181
170
|
const warnings = [...editWarnings, ...(anchorResult.warnings ?? [])];
|
|
182
171
|
const { totalAddedLines, totalRemovedLines } = countLineChanges(
|
|
183
172
|
edit, originalHashes, isNoop, anchorResult.autoFixes?.length ?? 0,
|
|
@@ -187,7 +176,7 @@ export async function execPipeline(
|
|
|
187
176
|
const aboveFixes = sortedFixes.filter((fix) => fix.kind === "leading" || fix.kind === "last-new-before");
|
|
188
177
|
const belowFixes = sortedFixes.filter((fix) => fix.kind === "trailing" || fix.kind === "first-new-after");
|
|
189
178
|
return {
|
|
190
|
-
path,
|
|
179
|
+
path: displayPath,
|
|
191
180
|
originalNormalized,
|
|
192
181
|
result,
|
|
193
182
|
bom,
|
|
@@ -230,12 +219,10 @@ export async function compPreview(
|
|
|
230
219
|
): Promise<RPreview> {
|
|
231
220
|
try {
|
|
232
221
|
const normalized = normReq(request);
|
|
233
|
-
if (isRec(normalized)) {
|
|
234
|
-
const resolution = await resolveReplacePath(normalized);
|
|
235
|
-
if (resolution) normalized.path = resolution.path;
|
|
236
|
-
}
|
|
237
222
|
assertReq(normalized);
|
|
223
|
+
const targetPath = resolveEditTarget(normalized.remove_from, normalized.remove_to);
|
|
238
224
|
const pipe = await execPipeline(
|
|
225
|
+
targetPath,
|
|
239
226
|
normalized,
|
|
240
227
|
cwd,
|
|
241
228
|
{ accessMode: constants.R_OK, noPersist: true, signal },
|
|
@@ -270,17 +257,14 @@ export function buildToolDef(): ToolDef {
|
|
|
270
257
|
renderResult: editRenderResultWrapper,
|
|
271
258
|
async execute(_toolCallId, params, signal, _onUpdate, ctx) {
|
|
272
259
|
const canonical = normReq(params);
|
|
273
|
-
const resolution = isRec(canonical) ? await resolveReplacePath(canonical) : undefined;
|
|
274
|
-
if (resolution && isRec(canonical)) {
|
|
275
|
-
canonical.path = resolution.path;
|
|
276
|
-
}
|
|
277
260
|
assertReq(canonical);
|
|
278
261
|
const normalizedParams = canonical;
|
|
279
|
-
const
|
|
280
|
-
return queuedEdit(
|
|
262
|
+
const targetPath = resolveEditTarget(normalizedParams.remove_from, normalizedParams.remove_to);
|
|
263
|
+
return queuedEdit(targetPath, ctx.cwd, signal, async (absolutePath, mutationTargetPath) => {
|
|
281
264
|
const noopPayload = noopPayloadKey(mutationTargetPath, normalizedParams.remove_from, normalizedParams.remove_to, normalizedParams.replacement_lines);
|
|
282
265
|
const boundaryBypass = consumeBoundaryBypass(mutationTargetPath, noopPayload);
|
|
283
266
|
const pipe = await execPipeline(
|
|
267
|
+
targetPath,
|
|
284
268
|
normalizedParams,
|
|
285
269
|
ctx.cwd,
|
|
286
270
|
{ accessMode: constants.R_OK | constants.W_OK, signal, skipBoundaryDedup: boundaryBypass },
|
|
@@ -289,11 +273,11 @@ export function buildToolDef(): ToolDef {
|
|
|
289
273
|
? ["[W_BOUNDARY_BYPASS] Boundary dedup was off for this call and is back on."]
|
|
290
274
|
: [];
|
|
291
275
|
return commitEdit(pipe, {
|
|
292
|
-
path,
|
|
276
|
+
path: pipe.path,
|
|
293
277
|
absolutePath,
|
|
294
278
|
mutationTargetPath,
|
|
279
|
+
editAnchors: [normalizedParams.remove_from, normalizedParams.remove_to],
|
|
295
280
|
signal,
|
|
296
|
-
prefixWarnings: resolution ? [resolution.warning] : [],
|
|
297
281
|
appliedWarnings,
|
|
298
282
|
onApplied: () => clearBoundaryBypass(mutationTargetPath),
|
|
299
283
|
onNoopDedup: () => markBoundaryNoop(mutationTargetPath, noopPayload),
|
package/src/served.ts
CHANGED
|
@@ -1,7 +1,5 @@
|
|
|
1
|
-
import { loadHashStore, parseStoredServed, STORE_NOT_OPEN_MESSAGE, withStore, type HashStore } from "./hash-store";
|
|
2
|
-
import { touchSession } from "./hash-store/cache";
|
|
3
|
-
import { withBusyRetry } from "./hash-store/retry";
|
|
4
1
|
import { HASH_CLASS } from "./hashline/alphabet";
|
|
2
|
+
import { hashSource } from "./hashline";
|
|
5
3
|
import { contentChecksum } from "./hashline/hasher";
|
|
6
4
|
|
|
7
5
|
const SERVED_DIFF_ROW_RE = new RegExp(`^[+ ](${HASH_CLASS})│`);
|
|
@@ -15,125 +13,20 @@ export function servedHashesFromDiff(diff: string): string[] {
|
|
|
15
13
|
return hashes;
|
|
16
14
|
}
|
|
17
15
|
|
|
18
|
-
export function
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
const hash = match[1]!;
|
|
24
|
-
const content = line.slice(match[0].length);
|
|
25
|
-
map.set(hash, contentChecksum(content));
|
|
26
|
-
}
|
|
27
|
-
return map;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
export function buildServedMap(fileHashes: string[], fileLines: string[], wantedHashes: string[]): Map<string, string> {
|
|
16
|
+
export function buildServedMap(
|
|
17
|
+
fileHashes: string[],
|
|
18
|
+
fileLines: string[],
|
|
19
|
+
wantedHashes: string[],
|
|
20
|
+
): Array<[string, string]> {
|
|
31
21
|
const index = new Map<string, number>();
|
|
32
|
-
for (let i = 0; i < fileHashes.length; i++)
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
const idx = index.get(h);
|
|
36
|
-
if (idx !== undefined) map.set(h, contentChecksum(fileLines[idx]!));
|
|
37
|
-
}
|
|
38
|
-
return map;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
export function getServed(store: HashStore, path: string): Map<string, string> | undefined {
|
|
42
|
-
const row = withBusyRetry(() => store.stmts.servedGet(path));
|
|
43
|
-
const parsed = parseStoredServed(row, () => store.stmts.servedDelete(path));
|
|
44
|
-
if (!parsed) return undefined;
|
|
45
|
-
return parsed;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
function computeUpdate(
|
|
49
|
-
store: HashStore,
|
|
50
|
-
path: string,
|
|
51
|
-
entries: Map<string, string>,
|
|
52
|
-
scope?: ReadonlySet<string>,
|
|
53
|
-
): Map<string, string> | undefined {
|
|
54
|
-
const existing = getServed(store, path);
|
|
55
|
-
if (!existing && entries.size === 0 && !scope) return undefined;
|
|
56
|
-
const map = existing ? new Map(existing) : new Map<string, string>();
|
|
57
|
-
let changed = false;
|
|
58
|
-
if (scope) {
|
|
59
|
-
for (const hash of [...map.keys()]) {
|
|
60
|
-
if (!scope.has(hash)) {
|
|
61
|
-
map.delete(hash);
|
|
62
|
-
changed = true;
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
for (const [hash, content] of entries) {
|
|
67
|
-
const prev = map.get(hash);
|
|
68
|
-
if (prev !== content) {
|
|
69
|
-
map.set(hash, content);
|
|
70
|
-
changed = true;
|
|
71
|
-
}
|
|
22
|
+
for (let i = 0; i < fileHashes.length; i++) {
|
|
23
|
+
const existing = index.get(fileHashes[i]!);
|
|
24
|
+
if (existing === undefined) index.set(fileHashes[i]!, i);
|
|
72
25
|
}
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
export function recordServed(
|
|
80
|
-
store: HashStore,
|
|
81
|
-
path: string,
|
|
82
|
-
entries: Map<string, string>,
|
|
83
|
-
scope?: ReadonlySet<string>,
|
|
84
|
-
): void {
|
|
85
|
-
touchSession(path);
|
|
86
|
-
try {
|
|
87
|
-
withStore(() => {
|
|
88
|
-
const map = computeUpdate(store, path, entries, scope);
|
|
89
|
-
if (!map) return;
|
|
90
|
-
const obj = Object.fromEntries(map);
|
|
91
|
-
store.stmts.servedUpsert(path, JSON.stringify(obj), Date.now());
|
|
92
|
-
});
|
|
93
|
-
return;
|
|
94
|
-
} catch (error) {
|
|
95
|
-
if (!(error instanceof Error && error.message === STORE_NOT_OPEN_MESSAGE)) throw error;
|
|
26
|
+
const entries: Array<[string, string]> = [];
|
|
27
|
+
for (const hash of wantedHashes) {
|
|
28
|
+
const idx = index.get(hash);
|
|
29
|
+
if (idx !== undefined) entries.push([hash, contentChecksum(hashSource(fileLines[idx]!))]);
|
|
96
30
|
}
|
|
97
|
-
|
|
98
|
-
if (!map) return;
|
|
99
|
-
const obj = Object.fromEntries(map);
|
|
100
|
-
store.stmts.servedUpsert(path, JSON.stringify(obj), Date.now());
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
export function recordServedDiff(
|
|
104
|
-
store: HashStore,
|
|
105
|
-
path: string,
|
|
106
|
-
diff: string,
|
|
107
|
-
scope?: ReadonlySet<string>,
|
|
108
|
-
): void {
|
|
109
|
-
recordServed(store, path, servedMapFromDiff(diff), scope);
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
export function clearServed(store: HashStore, path: string): void {
|
|
113
|
-
store.stmts.servedDelete(path);
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
export async function recordServedSafe(
|
|
117
|
-
path: string,
|
|
118
|
-
entries: Map<string, string>,
|
|
119
|
-
context: string,
|
|
120
|
-
scope?: ReadonlySet<string>,
|
|
121
|
-
): Promise<void> {
|
|
122
|
-
if (entries.size === 0 && !scope) return;
|
|
123
|
-
try {
|
|
124
|
-
const store = await loadHashStore();
|
|
125
|
-
recordServed(store, path, entries, scope);
|
|
126
|
-
} catch (error) {
|
|
127
|
-
console.error(`Failed to record served state (${context}):`, error);
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
export async function recordServedDiffSafe(
|
|
132
|
-
path: string,
|
|
133
|
-
diff: string,
|
|
134
|
-
context: string,
|
|
135
|
-
scope?: ReadonlySet<string>,
|
|
136
|
-
): Promise<void> {
|
|
137
|
-
if (!diff) return;
|
|
138
|
-
await recordServedSafe(path, servedMapFromDiff(diff), context, scope);
|
|
31
|
+
return entries;
|
|
139
32
|
}
|
package/src/write-hook.ts
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
2
2
|
import { HASH_CLASS } from "./hashline/alphabet";
|
|
3
3
|
import { HASH_SEP } from "./hashline/hash";
|
|
4
|
-
import {
|
|
5
|
-
import { getServed } from "./served";
|
|
4
|
+
import { ownersForPath } from "./anchor-registry";
|
|
6
5
|
import { resolveInCwd } from "./fs-write";
|
|
7
6
|
import { abortIf, splitLines, isRec, normalizeFilePath } from "./utils";
|
|
8
7
|
|
|
@@ -28,8 +27,7 @@ export async function servedHashEchoDenial(rawPath: string, content: string, cwd
|
|
|
28
27
|
abortIf(signal);
|
|
29
28
|
const { resolved } = await resolveInCwd(rawPath, cwd);
|
|
30
29
|
abortIf(signal);
|
|
31
|
-
const
|
|
32
|
-
const served = getServed(store, resolved);
|
|
30
|
+
const served = ownersForPath(resolved);
|
|
33
31
|
if (!served || served.size === 0) return undefined;
|
|
34
32
|
const echo = findServedHashEcho(content, served);
|
|
35
33
|
if (!echo) return undefined;
|
package/src/missing-path.ts
DELETED
|
@@ -1,54 +0,0 @@
|
|
|
1
|
-
import { loadHashStore, findSnapshotPaths, findServedPaths, rankRecentPaths } from "./hash-store";
|
|
2
|
-
import { parseHashRef } from "./hashline/parse";
|
|
3
|
-
import { stripAnchorRow } from "./hashline/resolve";
|
|
4
|
-
|
|
5
|
-
export async function resolvePathFromHashes(hashes: string[]): Promise<{ path: string; warning: string } | undefined> {
|
|
6
|
-
let store;
|
|
7
|
-
try {
|
|
8
|
-
store = await loadHashStore();
|
|
9
|
-
} catch {
|
|
10
|
-
return undefined;
|
|
11
|
-
}
|
|
12
|
-
const matches = [...new Set([...findSnapshotPaths(store, hashes), ...findServedPaths(store, hashes)])];
|
|
13
|
-
if (matches.length === 0) return undefined;
|
|
14
|
-
if (matches.length === 1) {
|
|
15
|
-
const single = matches[0]!;
|
|
16
|
-
return { path: single, warning: `[W_BAD_SHAPE] Missing "path" resolved to ${single}.` };
|
|
17
|
-
}
|
|
18
|
-
const ranked = rankRecentPaths(store, [...matches]);
|
|
19
|
-
const picked = ranked[0]!;
|
|
20
|
-
const shown = ranked.slice(0, 3);
|
|
21
|
-
const hidden = ranked.length - shown.length;
|
|
22
|
-
const listed = hidden > 0 ? `${shown.join(", ")}, ... (+${hidden} more)` : shown.join(", ");
|
|
23
|
-
return { path: picked, warning: `[W_BAD_SHAPE] Missing "path" resolved to ${picked} (picked most recent of ${ranked.length}: ${listed}).` };
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
export async function resolveReplacePath(request: Record<string, unknown>): Promise<{ path: string; warning: string } | undefined> {
|
|
27
|
-
if (typeof request.path === "string") return undefined;
|
|
28
|
-
const from = request.remove_from;
|
|
29
|
-
const to = request.remove_to;
|
|
30
|
-
if (typeof from !== "string" || typeof to !== "string") return undefined;
|
|
31
|
-
const hashes: string[] = [];
|
|
32
|
-
for (const ref of [from, to]) {
|
|
33
|
-
try {
|
|
34
|
-
hashes.push(parseHashRef(ref).hash);
|
|
35
|
-
} catch {
|
|
36
|
-
return undefined;
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
return resolvePathFromHashes(hashes);
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
export async function resolveInsertPath(request: Record<string, unknown>): Promise<{ path: string; warning: string } | undefined> {
|
|
43
|
-
if (typeof request.path === "string") return undefined;
|
|
44
|
-
const anchor = request.anchor;
|
|
45
|
-
if (typeof anchor !== "string") return undefined;
|
|
46
|
-
let hash: string;
|
|
47
|
-
try {
|
|
48
|
-
const stripped = stripAnchorRow(anchor.trim(), "anchor entry");
|
|
49
|
-
hash = parseHashRef(stripped).hash;
|
|
50
|
-
} catch {
|
|
51
|
-
return undefined;
|
|
52
|
-
}
|
|
53
|
-
return resolvePathFromHashes([hash]);
|
|
54
|
-
}
|