greprag 5.79.0 → 5.82.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.
Files changed (64) hide show
  1. package/dist/capture-manifest.js +2 -1
  2. package/dist/codex-fast-hook.js +6 -0
  3. package/dist/codex-steering.js +1 -1
  4. package/dist/commands/announce.js +97 -0
  5. package/dist/commands/app-model.js +0 -1
  6. package/dist/commands/arm-reminder.js +9 -7
  7. package/dist/commands/collision-check.js +7 -6
  8. package/dist/commands/corpus/client.js +13 -3
  9. package/dist/commands/delivery-reminder.js +35 -14
  10. package/dist/commands/deploy-gate.js +55 -0
  11. package/dist/commands/deploy-lock.js +100 -0
  12. package/dist/commands/deploy-record.js +145 -0
  13. package/dist/commands/deploy-verify.js +111 -0
  14. package/dist/commands/inbox-primer-reminder.js +5 -5
  15. package/dist/commands/inbox-watch.js +2 -4
  16. package/dist/commands/init.js +96 -1
  17. package/dist/commands/load.js +40 -0
  18. package/dist/commands/loadout-reminder.js +1 -1
  19. package/dist/commands/merge-guard.js +419 -0
  20. package/dist/commands/merge-lock.js +176 -0
  21. package/dist/commands/parity-reminder.js +53 -0
  22. package/dist/commands/persona-reminder.js +11 -0
  23. package/dist/commands/persona.js +50 -0
  24. package/dist/commands/procedure.js +77 -6
  25. package/dist/commands/reminder-registry.js +107 -3
  26. package/dist/commands/repodoc.js +433 -0
  27. package/dist/commands/search.js +149 -0
  28. package/dist/commands/skillgain.js +33 -25
  29. package/dist/delivery-lifecycle.js +16 -1
  30. package/dist/deploy-gate.js +355 -0
  31. package/dist/deploy-locks.js +339 -0
  32. package/dist/deploy-verify.js +209 -0
  33. package/dist/env-redaction.js +157 -0
  34. package/dist/harness-limits.js +17 -0
  35. package/dist/hook-runtime.js +11 -1
  36. package/dist/hook.js +229 -88
  37. package/dist/index.js +601 -567
  38. package/dist/inline-atom-episode.js +15 -7
  39. package/dist/inline-atom.js +8 -2
  40. package/dist/native-skill-adoption.js +11 -0
  41. package/dist/native-skill-mirror.js +8 -1
  42. package/dist/node-identity.bundle.js +1166 -0
  43. package/dist/opencode-plugin.bundle.js +307 -119
  44. package/dist/procedure-enabled.js +55 -0
  45. package/dist/procedure-runtime.js +6 -0
  46. package/dist/procedure-scope.js +190 -0
  47. package/dist/procedure-watch.js +29 -16
  48. package/dist/procedure.js +111 -5
  49. package/dist/project-anchor.js +1 -14
  50. package/dist/reminder-injector.js +11 -10
  51. package/dist/repodoc-client.js +296 -0
  52. package/dist/session-id.js +7 -8
  53. package/dist/skill-landing.js +57 -2
  54. package/dist/skill-mirror-client.js +14 -0
  55. package/dist/skill-mirror-files.js +18 -0
  56. package/package.json +2 -2
  57. package/scripts/bundle-node-identity.mjs +47 -0
  58. package/skill/templates/chip-spawn.md +7 -1
  59. package/skill/templates/delivery.md +105 -0
  60. package/skill/templates/prompt-audit.md +196 -0
  61. package/skill/templates/skill-change.md +25 -2
  62. package/dist/assistant-doctrine.js +0 -85
  63. package/dist/commands/assistant-reminder.js +0 -19
  64. package/dist/commands/assistant.js +0 -95
@@ -0,0 +1,1166 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
3
+ var __getOwnPropNames = Object.getOwnPropertyNames;
4
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
5
+ var __export = (target, all) => {
6
+ for (var name in all)
7
+ __defProp(target, name, { get: all[name], enumerable: true });
8
+ };
9
+ var __copyProps = (to, from, except, desc) => {
10
+ if (from && typeof from === "object" || typeof from === "function") {
11
+ for (let key of __getOwnPropNames(from))
12
+ if (!__hasOwnProp.call(to, key) && key !== except)
13
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
14
+ }
15
+ return to;
16
+ };
17
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
18
+
19
+ // <stdin>
20
+ var stdin_exports = {};
21
+ __export(stdin_exports, {
22
+ CONTAINER_TYPES: () => CONTAINER_TYPES,
23
+ GRAPH_VERSION: () => GRAPH_VERSION,
24
+ GRAVEYARD_CAP: () => GRAVEYARD_CAP,
25
+ ZERO_STRUCTURE: () => ZERO_STRUCTURE,
26
+ bootstrapPreviousNodes: () => bootstrapPreviousNodes,
27
+ fingerprint: () => fingerprint2,
28
+ fingerprintAll: () => fingerprintAll2,
29
+ hashBody: () => hashBody,
30
+ isExactMatch: () => isExactMatch2,
31
+ isSameContent: () => isSameContent,
32
+ makeMinter: () => makeMinter,
33
+ markdownToBlocks: () => markdownToBlocks,
34
+ matchNodes: () => matchNodes,
35
+ parseNodeGraph: () => parseNodeGraph,
36
+ rebuildPreviousFromResult: () => rebuildPreviousFromResult,
37
+ reconcile: () => reconcile,
38
+ sentenceArraysEqual: () => sentenceArraysEqual,
39
+ sentenceSignalOverlapScore: () => sentenceSignalOverlapScore,
40
+ sentenceTuplesEqual: () => sentenceTuplesEqual,
41
+ serializeNodeGraph: () => serializeNodeGraph,
42
+ shareAnySentenceTuple: () => shareAnySentenceTuple,
43
+ sidecarPathFor: () => sidecarPathFor,
44
+ simpleHash: () => simpleHash2,
45
+ splitSentences: () => splitSentences2,
46
+ tokenizeWords: () => tokenizeWords
47
+ });
48
+ module.exports = __toCommonJS(stdin_exports);
49
+
50
+ // ../core/src/node-graph/markdown-walker.ts
51
+ var MIN_CHUNK_WORDS = 30;
52
+ var MAX_CHUNK_WORDS = 300;
53
+ function markdownToBlocks(markdown, options = {}) {
54
+ if (!markdown || !markdown.trim())
55
+ return [];
56
+ const minWords = options.minWords ?? MIN_CHUNK_WORDS;
57
+ const maxWords = options.maxWords ?? MAX_CHUNK_WORDS;
58
+ const rawBlocks = parseRawBlocks(markdown);
59
+ const sized = applySizeRules(rawBlocks, minWords, maxWords);
60
+ return emitBlocks(sized);
61
+ }
62
+ function parseRawBlocks(markdown) {
63
+ const blocks = [];
64
+ const lines = markdown.split("\n");
65
+ let i = 0;
66
+ if (lines[0]?.trim() === "---") {
67
+ i = 1;
68
+ while (i < lines.length && lines[i].trim() !== "---")
69
+ i++;
70
+ i++;
71
+ }
72
+ while (i < lines.length) {
73
+ const line = lines[i];
74
+ const trimmed = line.trim();
75
+ if (trimmed === "") {
76
+ i++;
77
+ continue;
78
+ }
79
+ const fenceMatch = trimmed.match(/^(`{3,}|~{3,})(.*)$/);
80
+ if (fenceMatch) {
81
+ const fence = fenceMatch[1];
82
+ const language = fenceMatch[2].trim() || void 0;
83
+ const codeLines = [];
84
+ i++;
85
+ while (i < lines.length) {
86
+ if (lines[i].trim().startsWith(fence[0].repeat(fence.length))) {
87
+ i++;
88
+ break;
89
+ }
90
+ codeLines.push(lines[i]);
91
+ i++;
92
+ }
93
+ blocks.push({ type: "code", text: codeLines.join("\n"), language });
94
+ continue;
95
+ }
96
+ if (/^(-{3,}|\*{3,}|_{3,})$/.test(trimmed)) {
97
+ blocks.push({ type: "horizontalRule", text: "" });
98
+ i++;
99
+ continue;
100
+ }
101
+ const headingMatch = trimmed.match(/^(#{1,6})\s+(.+)$/);
102
+ if (headingMatch) {
103
+ blocks.push({
104
+ type: "heading",
105
+ text: headingMatch[2].trim(),
106
+ level: headingMatch[1].length
107
+ });
108
+ i++;
109
+ continue;
110
+ }
111
+ if (trimmed.includes("|") && i + 1 < lines.length && /^\|?[\s\-:|]+\|/.test(lines[i + 1]?.trim() || "")) {
112
+ const tableLines = [line];
113
+ i++;
114
+ while (i < lines.length && lines[i].trim().includes("|")) {
115
+ tableLines.push(lines[i]);
116
+ i++;
117
+ }
118
+ blocks.push({ type: "table", text: tableLines.join("\n") });
119
+ continue;
120
+ }
121
+ if (trimmed.startsWith(">")) {
122
+ const quoteLines = [];
123
+ while (i < lines.length) {
124
+ const t = lines[i].trim();
125
+ if (t.startsWith(">")) {
126
+ quoteLines.push(t.replace(/^>\s?/, ""));
127
+ i++;
128
+ } else if (t === "") {
129
+ break;
130
+ } else {
131
+ break;
132
+ }
133
+ }
134
+ blocks.push({ type: "blockquote", text: quoteLines.join("\n") });
135
+ continue;
136
+ }
137
+ if (/^[-*+]\s/.test(trimmed) || /^\d+\.\s/.test(trimmed)) {
138
+ const items = [];
139
+ let current = [];
140
+ const flushItem = () => {
141
+ if (current.length > 0) {
142
+ items.push(current.join("\n").trim());
143
+ current = [];
144
+ }
145
+ };
146
+ while (i < lines.length) {
147
+ const t = lines[i].trim();
148
+ if (/^[-*+]\s/.test(t) || /^\d+\.\s/.test(t)) {
149
+ flushItem();
150
+ current.push(t.replace(/^([-*+]|\d+\.)\s+/, ""));
151
+ i++;
152
+ } else if (t === "") {
153
+ const next = lines[i + 1]?.trim() || "";
154
+ if (/^[-*+]\s/.test(next) || /^\d+\.\s/.test(next) || next.startsWith(" ") || next.startsWith(" ")) {
155
+ i++;
156
+ continue;
157
+ }
158
+ break;
159
+ } else if (lines[i].startsWith(" ") || lines[i].startsWith(" ")) {
160
+ current.push(t);
161
+ i++;
162
+ } else {
163
+ break;
164
+ }
165
+ }
166
+ flushItem();
167
+ if (items.length > 0) {
168
+ blocks.push({ type: "list", text: "", listItems: items });
169
+ }
170
+ continue;
171
+ }
172
+ const paraLines = [line];
173
+ i++;
174
+ while (i < lines.length) {
175
+ const t = lines[i].trim();
176
+ if (t === "" || /^#{1,6}\s/.test(t) || /^(`{3,}|~{3,})/.test(t) || t.startsWith(">") || /^[-*+]\s/.test(t) || /^\d+\.\s/.test(t) || t.startsWith("|") && t.endsWith("|")) {
177
+ break;
178
+ }
179
+ paraLines.push(lines[i]);
180
+ i++;
181
+ }
182
+ blocks.push({ type: "paragraph", text: paraLines.join(" ").trim() });
183
+ }
184
+ return blocks;
185
+ }
186
+ var SENTENCE_BOUNDARY = /(?<=[.!?])\s+(?=[A-Z])/;
187
+ function countWords(text) {
188
+ return text.trim().split(/\s+/).filter((w) => w.length > 0).length;
189
+ }
190
+ function splitTable(block, maxWords) {
191
+ if (countWords(block.text) <= maxWords)
192
+ return [block];
193
+ const lines = block.text.split("\n").filter((l) => l.trim() !== "");
194
+ if (lines.length < 3)
195
+ return [block];
196
+ const header = lines[0];
197
+ const sep = lines[1];
198
+ const headerWords = countWords(header) + countWords(sep);
199
+ const out = [];
200
+ let buf = [];
201
+ let bufWords = 0;
202
+ const flush = () => {
203
+ if (buf.length > 0) {
204
+ out.push({ type: "table", text: [header, sep, ...buf].join("\n") });
205
+ buf = [];
206
+ bufWords = 0;
207
+ }
208
+ };
209
+ for (const row of lines.slice(2)) {
210
+ const rw = countWords(row);
211
+ if (buf.length > 0 && headerWords + bufWords + rw > maxWords)
212
+ flush();
213
+ buf.push(row);
214
+ bufWords += rw;
215
+ }
216
+ flush();
217
+ return out.length > 0 ? out : [block];
218
+ }
219
+ function applySizeRules(raw, minWords, maxWords) {
220
+ const out = [];
221
+ for (const block of raw) {
222
+ if (block.type === "table") {
223
+ out.push(...splitTable(block, maxWords));
224
+ continue;
225
+ }
226
+ if (block.type !== "paragraph") {
227
+ out.push(block);
228
+ continue;
229
+ }
230
+ const words = countWords(block.text);
231
+ if (words <= maxWords) {
232
+ out.push(block);
233
+ continue;
234
+ }
235
+ const sentences = block.text.split(SENTENCE_BOUNDARY);
236
+ let buf = [];
237
+ let bufWords = 0;
238
+ for (const s of sentences) {
239
+ const sw = countWords(s);
240
+ if (bufWords + sw > maxWords && buf.length > 0) {
241
+ out.push({ type: "paragraph", text: buf.join(" ").trim() });
242
+ buf = [s];
243
+ bufWords = sw;
244
+ } else {
245
+ buf.push(s);
246
+ bufWords += sw;
247
+ }
248
+ }
249
+ if (buf.length > 0) {
250
+ out.push({ type: "paragraph", text: buf.join(" ").trim() });
251
+ }
252
+ }
253
+ return out;
254
+ }
255
+ function emitBlocks(raw) {
256
+ const out = [];
257
+ const headingStack = [];
258
+ const levelStack = [];
259
+ let topLevelOrdinal = 0;
260
+ const currentHeadingPath = () => [...headingStack];
261
+ for (const r of raw) {
262
+ if (r.type === "heading") {
263
+ const lvl = r.level || 1;
264
+ while (levelStack.length > 0 && levelStack[levelStack.length - 1] >= lvl) {
265
+ levelStack.pop();
266
+ headingStack.pop();
267
+ }
268
+ const pathForHeading = [...headingStack];
269
+ headingStack.push(r.text);
270
+ levelStack.push(lvl);
271
+ out.push({
272
+ position: out.length,
273
+ type: "heading",
274
+ text: r.text,
275
+ parentPosition: null,
276
+ ordinalInParent: topLevelOrdinal++,
277
+ level: lvl,
278
+ headingPath: pathForHeading
279
+ });
280
+ continue;
281
+ }
282
+ if (r.type === "list" && r.listItems) {
283
+ const listPos = out.length;
284
+ out.push({
285
+ position: listPos,
286
+ type: "list",
287
+ text: "",
288
+ parentPosition: null,
289
+ ordinalInParent: topLevelOrdinal++,
290
+ headingPath: currentHeadingPath()
291
+ });
292
+ let itemOrdinal = 0;
293
+ for (const itemText of r.listItems) {
294
+ out.push({
295
+ position: out.length,
296
+ type: "listItem",
297
+ text: itemText,
298
+ parentPosition: listPos,
299
+ ordinalInParent: itemOrdinal++,
300
+ headingPath: currentHeadingPath()
301
+ });
302
+ }
303
+ continue;
304
+ }
305
+ out.push({
306
+ position: out.length,
307
+ type: r.type,
308
+ text: r.text,
309
+ parentPosition: null,
310
+ ordinalInParent: topLevelOrdinal++,
311
+ language: r.language,
312
+ headingPath: currentHeadingPath()
313
+ });
314
+ }
315
+ return out;
316
+ }
317
+
318
+ // ../core/src/node-identity/types.ts
319
+ var ZERO_STRUCTURE = { bold: 0, italic: 0, links: 0, code: 0 };
320
+ var CONTAINER_TYPES = ["list"];
321
+
322
+ // ../core/src/node-identity/fingerprint.ts
323
+ var WORD_FALLBACK_WINDOW = 5;
324
+ var PREFIX_LEN = 3;
325
+ function fingerprint2(block, allBlocks) {
326
+ const text = block.text || "";
327
+ const sentences = splitSentences2(text);
328
+ const words = tokenizeWords(text);
329
+ const fp = {
330
+ type: block.type,
331
+ position: block.position,
332
+ parentPosition: block.parentPosition,
333
+ ordinalInParent: block.ordinalInParent,
334
+ charCount: text.length,
335
+ sentenceCount: sentences.length,
336
+ wordCount: words.length,
337
+ sentences: sentences.map(sentenceTuple),
338
+ structureSig: block.inlineMarks || ZERO_STRUCTURE,
339
+ prevType: allBlocks[block.position - 1]?.type ?? null,
340
+ nextType: allBlocks[block.position + 1]?.type ?? null,
341
+ parentType: block.parentPosition != null ? allBlocks[block.parentPosition]?.type ?? null : null,
342
+ firstWords: words.slice(0, WORD_FALLBACK_WINDOW),
343
+ lastWords: words.slice(-WORD_FALLBACK_WINDOW)
344
+ };
345
+ if (block.type === "heading")
346
+ fp.level = block.level;
347
+ if (block.type === "code") {
348
+ fp.language = block.language || "";
349
+ fp.contentHash = simpleHash2(text);
350
+ }
351
+ if (CONTAINER_TYPES.includes(block.type)) {
352
+ const children = allBlocks.filter((b) => b.parentPosition === block.position);
353
+ fp.childCount = children.length;
354
+ fp.childTypes = children.map((c) => c.type);
355
+ }
356
+ return fp;
357
+ }
358
+ function fingerprintAll2(blocks) {
359
+ return blocks.map((b) => fingerprint2(b, blocks));
360
+ }
361
+ function isExactMatch2(a, b) {
362
+ if (a.type !== b.type)
363
+ return false;
364
+ if (a.level !== b.level)
365
+ return false;
366
+ if (a.language !== b.language)
367
+ return false;
368
+ if (a.charCount !== b.charCount)
369
+ return false;
370
+ if (a.sentenceCount !== b.sentenceCount)
371
+ return false;
372
+ if (a.wordCount !== b.wordCount)
373
+ return false;
374
+ if (!sentenceArraysEqual(a.sentences, b.sentences))
375
+ return false;
376
+ if (!structureEqual(a.structureSig, b.structureSig))
377
+ return false;
378
+ if (a.childCount != null || b.childCount != null) {
379
+ if (a.childCount !== b.childCount)
380
+ return false;
381
+ if (!arraysEqual(a.childTypes, b.childTypes))
382
+ return false;
383
+ }
384
+ return true;
385
+ }
386
+ function isSameContent(a, b) {
387
+ const aIsContainer = a.childCount != null;
388
+ const bIsContainer = b.childCount != null;
389
+ if (aIsContainer !== bIsContainer)
390
+ return false;
391
+ if (aIsContainer) {
392
+ return a.childCount === b.childCount && arraysEqual(a.childTypes, b.childTypes);
393
+ }
394
+ if (a.sentences != null && b.sentences != null) {
395
+ if (!sentenceArraysEqual(a.sentences, b.sentences))
396
+ return false;
397
+ return structureEqual(a.structureSig, b.structureSig);
398
+ }
399
+ return false;
400
+ }
401
+ function sentenceTuplesEqual(a, b) {
402
+ return a.c === b.c && a.f === b.f && a.l === b.l && a.t === b.t && arraysEqual(a.wls, b.wls) && arraysEqual(a.w, b.w);
403
+ }
404
+ function sentenceArraysEqual(a, b) {
405
+ if (!Array.isArray(a) || !Array.isArray(b))
406
+ return false;
407
+ if (a.length !== b.length)
408
+ return false;
409
+ for (let i = 0; i < a.length; i++) {
410
+ if (!sentenceTuplesEqual(a[i], b[i]))
411
+ return false;
412
+ }
413
+ return true;
414
+ }
415
+ function shareAnySentenceTuple(a, b) {
416
+ if (!Array.isArray(a) || !Array.isArray(b))
417
+ return false;
418
+ for (const sa of a) {
419
+ for (const sb of b) {
420
+ if (sentenceTuplesEqual(sa, sb))
421
+ return true;
422
+ }
423
+ }
424
+ return false;
425
+ }
426
+ function sentenceSignalOverlapScore(a, b) {
427
+ if (!a.sentences || !b.sentences)
428
+ return 0;
429
+ let score = 0;
430
+ for (const sa of a.sentences) {
431
+ for (const sb of b.sentences) {
432
+ if (sa.f === sb.f)
433
+ score++;
434
+ if (sa.l === sb.l)
435
+ score++;
436
+ if (sa.t === sb.t)
437
+ score++;
438
+ if (arraysEqual(sa.wls, sb.wls))
439
+ score += 2;
440
+ if (Array.isArray(sa.w) && Array.isArray(sb.w)) {
441
+ const aSet = new Set(sa.w);
442
+ let shared = 0;
443
+ for (const w of sb.w)
444
+ if (aSet.has(w))
445
+ shared++;
446
+ score += shared * 3;
447
+ if (arraysEqual(sa.w, sb.w))
448
+ score += 10;
449
+ }
450
+ }
451
+ }
452
+ return score;
453
+ }
454
+ function splitSentences2(text) {
455
+ if (!text)
456
+ return [];
457
+ const sentences = [];
458
+ const re = /([^.!?]+?)([.!?]+)(\s+|$)/g;
459
+ let lastIdx = 0;
460
+ let m;
461
+ while ((m = re.exec(text)) !== null) {
462
+ const term = m[2][0];
463
+ sentences.push({
464
+ text: m[1].trim(),
465
+ terminator: term === "." ? "D" : term === "!" ? "E" : term === "?" ? "Q" : "-"
466
+ });
467
+ lastIdx = re.lastIndex;
468
+ }
469
+ if (lastIdx < text.length) {
470
+ const remaining = text.slice(lastIdx).trim();
471
+ if (remaining)
472
+ sentences.push({ text: remaining, terminator: "-" });
473
+ }
474
+ return sentences;
475
+ }
476
+ function tokenizeWords(text) {
477
+ if (!text)
478
+ return [];
479
+ return text.split(/\s+/).map((w) => w.replace(/^[^\w]+|[^\w]+$/g, "")).filter((w) => w.length > 0);
480
+ }
481
+ function simpleHash2(s) {
482
+ let h = 0;
483
+ for (let i = 0; i < s.length; i++) {
484
+ h = (h << 5) - h + s.charCodeAt(i);
485
+ h |= 0;
486
+ }
487
+ return h.toString(16);
488
+ }
489
+ function sentenceTuple(sentence) {
490
+ const t = sentence.text;
491
+ const words = tokenizeWords(t);
492
+ return {
493
+ c: t.length,
494
+ f: t.slice(0, PREFIX_LEN),
495
+ l: t.slice(-PREFIX_LEN),
496
+ t: sentence.terminator,
497
+ wls: words.map((w) => w.length),
498
+ w: words
499
+ };
500
+ }
501
+ function arraysEqual(a, b) {
502
+ if (!Array.isArray(a) || !Array.isArray(b))
503
+ return false;
504
+ if (a.length !== b.length)
505
+ return false;
506
+ for (let i = 0; i < a.length; i++)
507
+ if (a[i] !== b[i])
508
+ return false;
509
+ return true;
510
+ }
511
+ function structureEqual(a, b) {
512
+ if (!a || !b)
513
+ return false;
514
+ return a.bold === b.bold && a.italic === b.italic && a.links === b.links && a.code === b.code;
515
+ }
516
+
517
+ // ../core/src/node-identity/matcher.ts
518
+ function matchNodes(previousNodes, newBlocks, options = {}) {
519
+ const graveyard = options.graveyard || [];
520
+ const mintId = options.mintId || defaultMintId;
521
+ const newFingerprints = fingerprintAll2(newBlocks);
522
+ const pinned = [];
523
+ const claimedPrevIds = /* @__PURE__ */ new Set();
524
+ const claimedGraveIds = /* @__PURE__ */ new Set();
525
+ const unmatched = newBlocks.map((block, i) => ({
526
+ position: newFingerprints[i].position,
527
+ fingerprint: newFingerprints[i],
528
+ block
529
+ }));
530
+ pinExactMatches(unmatched, previousNodes, claimedPrevIds, pinned);
531
+ applySplitRule(unmatched, previousNodes, claimedPrevIds, pinned, mintId);
532
+ applyMergeRule(unmatched, previousNodes, claimedPrevIds, pinned);
533
+ applyTypeChangeRule(unmatched, previousNodes, claimedPrevIds, pinned);
534
+ applyEditRule(unmatched, previousNodes, claimedPrevIds, pinned);
535
+ applySlotContinuityRule(unmatched, previousNodes, claimedPrevIds, pinned);
536
+ applyGraveyardRestoreRule(unmatched, graveyard, claimedGraveIds, pinned);
537
+ applyInsertRule(unmatched, pinned, mintId);
538
+ const orphaned = previousNodes.filter((prev) => !claimedPrevIds.has(prev.id)).map((prev) => ({ id: prev.id, fingerprint: prev.fingerprint }));
539
+ const remainingGraveyard = graveyard.filter((g) => !claimedGraveIds.has(g.id));
540
+ const preserved = pinned.filter((p) => p.mutation !== "inserted").length;
541
+ return {
542
+ pinned,
543
+ unmatched,
544
+ orphaned,
545
+ graveyardRestored: pinned.filter((p) => p.mutation === "graveyard-restore"),
546
+ nextGraveyard: [...orphaned, ...remainingGraveyard],
547
+ summary: {
548
+ totalBlocks: newBlocks.length,
549
+ pinnedCount: pinned.length,
550
+ unmatchedCount: unmatched.length,
551
+ orphanedCount: orphaned.length,
552
+ preserved,
553
+ fresh: pinned.length - preserved
554
+ }
555
+ };
556
+ }
557
+ function rebuildPreviousFromResult(result) {
558
+ return result.pinned.slice().sort((a, b) => a.position - b.position).map((p) => ({ id: p.id, fingerprint: p.fingerprint }));
559
+ }
560
+ function bootstrapPreviousNodes(blocks, mintId = defaultMintId) {
561
+ const fps = fingerprintAll2(blocks);
562
+ return blocks.map((_, i) => ({ id: mintId(), fingerprint: fps[i] }));
563
+ }
564
+ function pinExactMatches(unmatched, previousNodes, claimedPrevIds, pinned) {
565
+ let changedA = true;
566
+ while (changedA) {
567
+ changedA = false;
568
+ const prevToCands = /* @__PURE__ */ new Map();
569
+ const candToPrevs = /* @__PURE__ */ new Map();
570
+ for (const prev of previousNodes) {
571
+ if (claimedPrevIds.has(prev.id))
572
+ continue;
573
+ const cands = unmatched.filter((u) => isExactMatch2(prev.fingerprint, u.fingerprint));
574
+ prevToCands.set(prev.id, cands);
575
+ for (const c of cands) {
576
+ if (!candToPrevs.has(c.position))
577
+ candToPrevs.set(c.position, []);
578
+ candToPrevs.get(c.position).push(prev);
579
+ }
580
+ }
581
+ for (const [prevId, cands] of prevToCands) {
582
+ if (claimedPrevIds.has(prevId))
583
+ continue;
584
+ if (cands.length !== 1)
585
+ continue;
586
+ const cand = cands[0];
587
+ const prevs = candToPrevs.get(cand.position);
588
+ if (!prevs || prevs.length !== 1)
589
+ continue;
590
+ if (!unmatched.includes(cand))
591
+ continue;
592
+ const prev = previousNodes.find((p) => p.id === prevId);
593
+ const origPos = prev.fingerprint.position;
594
+ claimedPrevIds.add(prevId);
595
+ pinned.push({
596
+ id: prevId,
597
+ position: cand.position,
598
+ fingerprint: cand.fingerprint,
599
+ block: cand.block,
600
+ mutation: origPos !== cand.position ? "moved" : "unchanged"
601
+ });
602
+ unmatched.splice(unmatched.indexOf(cand), 1);
603
+ changedA = true;
604
+ }
605
+ }
606
+ let changedB = true;
607
+ while (changedB) {
608
+ changedB = false;
609
+ for (const prev of previousNodes) {
610
+ if (claimedPrevIds.has(prev.id))
611
+ continue;
612
+ const candidates = unmatched.filter((u) => isExactMatch2(prev.fingerprint, u.fingerprint));
613
+ if (candidates.length === 0)
614
+ continue;
615
+ const prevIdx = previousNodes.findIndex((p) => p.id === prev.id);
616
+ const lo = slotLowBound(previousNodes, claimedPrevIds, pinned, prevIdx);
617
+ const hi = slotHighBound(previousNodes, claimedPrevIds, pinned, prevIdx);
618
+ let inRange;
619
+ if (lo + 1 < hi) {
620
+ inRange = candidates.filter((c) => c.position > lo && c.position < hi);
621
+ } else if (lo > hi) {
622
+ inRange = candidates;
623
+ } else {
624
+ inRange = [];
625
+ }
626
+ if (inRange.length === 0)
627
+ continue;
628
+ const origPos = prev.fingerprint.position;
629
+ const best = nearestBy(inRange, (c) => c.position, origPos);
630
+ claimedPrevIds.add(prev.id);
631
+ pinned.push({
632
+ id: prev.id,
633
+ position: best.position,
634
+ fingerprint: best.fingerprint,
635
+ block: best.block,
636
+ mutation: origPos !== best.position ? "moved" : "unchanged"
637
+ });
638
+ unmatched.splice(unmatched.indexOf(best), 1);
639
+ changedB = true;
640
+ }
641
+ }
642
+ }
643
+ function applySplitRule(unmatched, previousNodes, claimedPrevIds, pinned, mintId) {
644
+ let progress = true;
645
+ while (progress) {
646
+ progress = false;
647
+ for (const orphan of previousNodes) {
648
+ if (claimedPrevIds.has(orphan.id))
649
+ continue;
650
+ const orphanSents = orphan.fingerprint.sentences;
651
+ if (!orphanSents || orphanSents.length < 2)
652
+ continue;
653
+ let claimed = false;
654
+ for (let startIdx = 0; startIdx < unmatched.length && !claimed; startIdx++) {
655
+ const start = unmatched[startIdx];
656
+ if (start.fingerprint.type !== orphan.fingerprint.type)
657
+ continue;
658
+ const group = [startIdx];
659
+ let concatLen = start.fingerprint.sentences.length;
660
+ for (let next = startIdx + 1; next < unmatched.length; next++) {
661
+ const prev = unmatched[next - 1];
662
+ const cur = unmatched[next];
663
+ if (cur.position !== prev.position + 1)
664
+ break;
665
+ if (cur.fingerprint.type !== orphan.fingerprint.type)
666
+ break;
667
+ group.push(next);
668
+ concatLen += cur.fingerprint.sentences.length;
669
+ if (concatLen > orphanSents.length)
670
+ break;
671
+ if (concatLen < orphanSents.length)
672
+ continue;
673
+ const concat = [];
674
+ for (const gi of group)
675
+ concat.push(...unmatched[gi].fingerprint.sentences);
676
+ if (!sentenceArraysEqual(concat, orphanSents))
677
+ break;
678
+ claimedPrevIds.add(orphan.id);
679
+ for (let i = 0; i < group.length; i++) {
680
+ const c = unmatched[group[i]];
681
+ pinned.push({
682
+ id: i === 0 ? orphan.id : mintId(),
683
+ position: c.position,
684
+ fingerprint: c.fingerprint,
685
+ block: c.block,
686
+ mutation: i === 0 ? "split-first" : `split-${i + 1}`
687
+ });
688
+ }
689
+ for (let i = group.length - 1; i >= 0; i--)
690
+ unmatched.splice(group[i], 1);
691
+ claimed = true;
692
+ progress = true;
693
+ break;
694
+ }
695
+ }
696
+ }
697
+ }
698
+ }
699
+ function applyMergeRule(unmatched, previousNodes, claimedPrevIds, pinned) {
700
+ let progress = true;
701
+ while (progress) {
702
+ progress = false;
703
+ for (let ui = 0; ui < unmatched.length; ui++) {
704
+ const candidate = unmatched[ui];
705
+ const candidateSents = candidate.fingerprint.sentences;
706
+ if (!candidateSents || candidateSents.length < 2)
707
+ continue;
708
+ let merged = false;
709
+ for (let s = 0; s < previousNodes.length && !merged; s++) {
710
+ const start = previousNodes[s];
711
+ if (claimedPrevIds.has(start.id))
712
+ continue;
713
+ if (start.fingerprint.type !== candidate.fingerprint.type)
714
+ continue;
715
+ const group = [s];
716
+ let concatLen = start.fingerprint.sentences.length;
717
+ for (let next = s + 1; next < previousNodes.length; next++) {
718
+ const prev = previousNodes[next - 1];
719
+ const cur = previousNodes[next];
720
+ if (claimedPrevIds.has(cur.id))
721
+ break;
722
+ if (cur.fingerprint.type !== candidate.fingerprint.type)
723
+ break;
724
+ if (cur.fingerprint.position !== prev.fingerprint.position + 1)
725
+ break;
726
+ group.push(next);
727
+ concatLen += cur.fingerprint.sentences.length;
728
+ if (concatLen > candidateSents.length)
729
+ break;
730
+ if (concatLen < candidateSents.length)
731
+ continue;
732
+ const concat = [];
733
+ for (const gi of group)
734
+ concat.push(...previousNodes[gi].fingerprint.sentences);
735
+ if (!sentenceArraysEqual(concat, candidateSents))
736
+ break;
737
+ for (const gi of group)
738
+ claimedPrevIds.add(previousNodes[gi].id);
739
+ pinned.push({
740
+ id: previousNodes[group[0]].id,
741
+ position: candidate.position,
742
+ fingerprint: candidate.fingerprint,
743
+ block: candidate.block,
744
+ mutation: `merge-${group.length}-way`
745
+ });
746
+ unmatched.splice(ui, 1);
747
+ merged = true;
748
+ progress = true;
749
+ break;
750
+ }
751
+ }
752
+ if (merged)
753
+ break;
754
+ }
755
+ }
756
+ }
757
+ function applyTypeChangeRule(unmatched, previousNodes, claimedPrevIds, pinned) {
758
+ let progress = true;
759
+ while (progress) {
760
+ progress = false;
761
+ for (let ui = 0; ui < unmatched.length; ui++) {
762
+ const candidate = unmatched[ui];
763
+ const candidateOrphans = previousNodes.filter((p) => {
764
+ if (claimedPrevIds.has(p.id))
765
+ return false;
766
+ if (p.fingerprint.type === candidate.fingerprint.type)
767
+ return false;
768
+ if (!isSameContent(p.fingerprint, candidate.fingerprint))
769
+ return false;
770
+ const idx = previousNodes.findIndex((x) => x.id === p.id);
771
+ const lo = slotLowBound(previousNodes, claimedPrevIds, pinned, idx);
772
+ const hi = slotHighBound(previousNodes, claimedPrevIds, pinned, idx);
773
+ return candidate.position > lo && candidate.position < hi;
774
+ });
775
+ if (candidateOrphans.length === 0)
776
+ continue;
777
+ const best = nearestBy(candidateOrphans, (o) => o.fingerprint.position, candidate.position);
778
+ claimedPrevIds.add(best.id);
779
+ pinned.push({
780
+ id: best.id,
781
+ position: candidate.position,
782
+ fingerprint: candidate.fingerprint,
783
+ block: candidate.block,
784
+ mutation: `type-change-${best.fingerprint.type}-to-${candidate.fingerprint.type}`
785
+ });
786
+ unmatched.splice(ui, 1);
787
+ progress = true;
788
+ ui--;
789
+ }
790
+ }
791
+ }
792
+ function applyEditRule(unmatched, previousNodes, claimedPrevIds, pinned) {
793
+ const byPos = [...unmatched].sort((a, b) => a.position - b.position);
794
+ for (const candidate of byPos) {
795
+ if (!unmatched.includes(candidate))
796
+ continue;
797
+ const candidateOrphans = previousNodes.filter((p) => {
798
+ if (claimedPrevIds.has(p.id))
799
+ return false;
800
+ if (p.fingerprint.type !== candidate.fingerprint.type)
801
+ return false;
802
+ if (!shareAnySentenceTuple(p.fingerprint.sentences, candidate.fingerprint.sentences))
803
+ return false;
804
+ const idx = previousNodes.findIndex((x) => x.id === p.id);
805
+ const lo = slotLowBound(previousNodes, claimedPrevIds, pinned, idx);
806
+ const hi = slotHighBound(previousNodes, claimedPrevIds, pinned, idx);
807
+ return candidate.position > lo && candidate.position < hi;
808
+ });
809
+ if (candidateOrphans.length !== 1)
810
+ continue;
811
+ const orphan = candidateOrphans[0];
812
+ claimedPrevIds.add(orphan.id);
813
+ pinned.push({
814
+ id: orphan.id,
815
+ position: candidate.position,
816
+ fingerprint: candidate.fingerprint,
817
+ block: candidate.block,
818
+ mutation: "edited"
819
+ });
820
+ unmatched.splice(unmatched.indexOf(candidate), 1);
821
+ }
822
+ }
823
+ function applySlotContinuityRule(unmatched, previousNodes, claimedPrevIds, pinned) {
824
+ let progress = true;
825
+ while (progress) {
826
+ progress = false;
827
+ for (let ui = 0; ui < unmatched.length; ui++) {
828
+ const candidate = unmatched[ui];
829
+ const matchingOrphans = previousNodes.filter((orphan) => {
830
+ if (claimedPrevIds.has(orphan.id))
831
+ return false;
832
+ if (orphan.fingerprint.type !== candidate.fingerprint.type)
833
+ return false;
834
+ const idx = previousNodes.findIndex((x) => x.id === orphan.id);
835
+ const prevAnchor = findPinnedNeighbor(previousNodes, claimedPrevIds, idx, -1);
836
+ const nextAnchor = findPinnedNeighbor(previousNodes, claimedPrevIds, idx, 1);
837
+ const prevNewPos = prevAnchor ? findPinnedPosition(pinned, prevAnchor.id) : -1;
838
+ const nextNewPos = nextAnchor ? findPinnedPosition(pinned, nextAnchor.id) : Infinity;
839
+ return candidate.position > prevNewPos && candidate.position < nextNewPos;
840
+ });
841
+ if (matchingOrphans.length === 0)
842
+ continue;
843
+ const scored = matchingOrphans.map((orphan) => ({
844
+ orphan,
845
+ score: sentenceSignalOverlapScore(orphan.fingerprint, candidate.fingerprint)
846
+ })).sort((a, b) => b.score - a.score);
847
+ const topScore = scored[0].score;
848
+ const tied = scored.filter((s) => s.score === topScore);
849
+ if (tied.length > 1 && topScore === 0)
850
+ continue;
851
+ const best = nearestBy(
852
+ tied.map((t) => t.orphan),
853
+ (o) => o.fingerprint.position,
854
+ candidate.position
855
+ );
856
+ claimedPrevIds.add(best.id);
857
+ pinned.push({
858
+ id: best.id,
859
+ position: candidate.position,
860
+ fingerprint: candidate.fingerprint,
861
+ block: candidate.block,
862
+ mutation: "slot-preserved"
863
+ });
864
+ unmatched.splice(ui, 1);
865
+ ui--;
866
+ progress = true;
867
+ }
868
+ }
869
+ }
870
+ function applyGraveyardRestoreRule(unmatched, graveyard, claimedGraveIds, pinned) {
871
+ for (let ui = unmatched.length - 1; ui >= 0; ui--) {
872
+ const candidate = unmatched[ui];
873
+ const ghosts = graveyard.filter(
874
+ (g) => !claimedGraveIds.has(g.id) && isExactMatch2(g.fingerprint, candidate.fingerprint)
875
+ );
876
+ if (ghosts.length === 0)
877
+ continue;
878
+ const best = nearestBy(ghosts, (g) => g.fingerprint.position, candidate.position);
879
+ claimedGraveIds.add(best.id);
880
+ pinned.push({
881
+ id: best.id,
882
+ position: candidate.position,
883
+ fingerprint: candidate.fingerprint,
884
+ block: candidate.block,
885
+ mutation: "graveyard-restore"
886
+ });
887
+ unmatched.splice(ui, 1);
888
+ }
889
+ }
890
+ function applyInsertRule(unmatched, pinned, mintId) {
891
+ for (let i = unmatched.length - 1; i >= 0; i--) {
892
+ const candidate = unmatched[i];
893
+ pinned.push({
894
+ id: mintId(),
895
+ position: candidate.position,
896
+ fingerprint: candidate.fingerprint,
897
+ block: candidate.block,
898
+ mutation: "inserted"
899
+ });
900
+ unmatched.splice(i, 1);
901
+ }
902
+ }
903
+ function findPinnedNeighbor(previousNodes, claimedPrevIds, startIdx, direction) {
904
+ for (let i = startIdx + direction; i >= 0 && i < previousNodes.length; i += direction) {
905
+ if (claimedPrevIds.has(previousNodes[i].id))
906
+ return previousNodes[i];
907
+ }
908
+ return null;
909
+ }
910
+ function findPinnedPosition(pinned, id) {
911
+ const entry = pinned.find((p) => p.id === id);
912
+ return entry ? entry.position : -1;
913
+ }
914
+ function slotLowBound(previousNodes, claimedPrevIds, pinned, orphanIdx) {
915
+ const prev = findPinnedNeighbor(previousNodes, claimedPrevIds, orphanIdx, -1);
916
+ return prev ? findPinnedPosition(pinned, prev.id) : -1;
917
+ }
918
+ function slotHighBound(previousNodes, claimedPrevIds, pinned, orphanIdx) {
919
+ const next = findPinnedNeighbor(previousNodes, claimedPrevIds, orphanIdx, 1);
920
+ return next ? findPinnedPosition(pinned, next.id) : Infinity;
921
+ }
922
+ function nearestBy(items, project, target) {
923
+ let best = items[0];
924
+ let bestDist = Math.abs(project(best) - target);
925
+ for (const item of items) {
926
+ const d = Math.abs(project(item) - target);
927
+ if (d < bestDist) {
928
+ best = item;
929
+ bestDist = d;
930
+ }
931
+ }
932
+ return best;
933
+ }
934
+ function defaultMintId() {
935
+ return Math.floor(Math.random() * 4294967295).toString(16).padStart(8, "0");
936
+ }
937
+
938
+ // ../core/src/node-identity/graph.ts
939
+ var GRAPH_VERSION = 1;
940
+ var GRAVEYARD_CAP = 50;
941
+ function hashBody(body) {
942
+ return `${body.length.toString(16)}-${simpleHash2(body)}`;
943
+ }
944
+ function reconcile(graph, blocks, options) {
945
+ const previous = graph?.nodes ?? [];
946
+ const graveyard = graph?.graveyard ?? [];
947
+ const mintId = options.mintId ?? makeMinter(collectIds(previous, graveyard));
948
+ const match = previous.length === 0 && graveyard.length === 0 ? bootstrapMatch(blocks, mintId) : matchNodes(previous, blocks, { graveyard, mintId });
949
+ const ordered = [...match.pinned].sort((a, b) => a.position - b.position);
950
+ const next = {
951
+ version: GRAPH_VERSION,
952
+ path: options.path,
953
+ bodyHash: hashBody(options.body),
954
+ nodes: ordered.map((p) => ({ id: p.id, fingerprint: p.fingerprint })),
955
+ graveyard: capGraveyard(match.nextGraveyard)
956
+ };
957
+ const changed = graph === null || serializeNodeGraph(graph) !== serializeNodeGraph(next);
958
+ return {
959
+ blocks: ordered.map((p) => ({ ...p.block, id: p.id })),
960
+ graph: next,
961
+ changed,
962
+ match
963
+ };
964
+ }
965
+ function bootstrapMatch(blocks, mintId) {
966
+ const seeded = bootstrapPreviousNodes(blocks, mintId);
967
+ return {
968
+ pinned: blocks.map((block, i) => ({
969
+ id: seeded[i].id,
970
+ position: block.position,
971
+ fingerprint: seeded[i].fingerprint,
972
+ block,
973
+ mutation: "inserted"
974
+ })),
975
+ unmatched: [],
976
+ orphaned: [],
977
+ graveyardRestored: [],
978
+ nextGraveyard: [],
979
+ summary: {
980
+ totalBlocks: blocks.length,
981
+ pinnedCount: blocks.length,
982
+ unmatchedCount: 0,
983
+ orphanedCount: 0,
984
+ preserved: 0,
985
+ fresh: blocks.length
986
+ }
987
+ };
988
+ }
989
+ function capGraveyard(entries) {
990
+ const seen = /* @__PURE__ */ new Set();
991
+ const out = [];
992
+ for (const e of entries) {
993
+ if (seen.has(e.id))
994
+ continue;
995
+ seen.add(e.id);
996
+ out.push(e);
997
+ if (out.length >= GRAVEYARD_CAP)
998
+ break;
999
+ }
1000
+ return out;
1001
+ }
1002
+ function collectIds(...groups) {
1003
+ const used = /* @__PURE__ */ new Set();
1004
+ for (const group of groups)
1005
+ for (const e of group)
1006
+ used.add(e.id);
1007
+ return used;
1008
+ }
1009
+ function makeMinter(used) {
1010
+ const taken = new Set(used);
1011
+ return () => {
1012
+ for (let attempt = 0; attempt < 1e3; attempt++) {
1013
+ const id = Math.floor(Math.random() * 4294967296).toString(16).padStart(8, "0");
1014
+ if (!taken.has(id)) {
1015
+ taken.add(id);
1016
+ return id;
1017
+ }
1018
+ }
1019
+ throw new Error("node-identity: could not mint an unused id in 1000 attempts");
1020
+ };
1021
+ }
1022
+ function serializeNodeGraph(graph) {
1023
+ const lines = [];
1024
+ lines.push("{");
1025
+ lines.push(` "version": ${graph.version},`);
1026
+ lines.push(` "path": ${JSON.stringify(graph.path)},`);
1027
+ lines.push(` "bodyHash": ${JSON.stringify(graph.bodyHash)},`);
1028
+ lines.push(` "nodes": [`);
1029
+ lines.push(...entryLines(graph.nodes));
1030
+ lines.push(` ],`);
1031
+ lines.push(` "graveyard": [`);
1032
+ lines.push(...entryLines(graph.graveyard));
1033
+ lines.push(` ]`);
1034
+ lines.push("}");
1035
+ return lines.join("\n") + "\n";
1036
+ }
1037
+ function entryLines(entries) {
1038
+ return entries.map((e, i) => {
1039
+ const comma = i === entries.length - 1 ? "" : ",";
1040
+ return ` ${JSON.stringify({ id: e.id, fp: packFingerprint(e.fingerprint) })}${comma}`;
1041
+ });
1042
+ }
1043
+ function parseNodeGraph(text) {
1044
+ if (!text)
1045
+ return null;
1046
+ let raw;
1047
+ try {
1048
+ raw = JSON.parse(text);
1049
+ } catch {
1050
+ return null;
1051
+ }
1052
+ if (!raw || typeof raw !== "object")
1053
+ return null;
1054
+ if (raw.version !== GRAPH_VERSION)
1055
+ return null;
1056
+ if (!Array.isArray(raw.nodes) || !Array.isArray(raw.graveyard))
1057
+ return null;
1058
+ const nodes = raw.nodes.map(unpackEntry).filter(isEntry);
1059
+ const graveyard = raw.graveyard.map(unpackEntry).filter(isEntry);
1060
+ if (nodes.length !== raw.nodes.length)
1061
+ return null;
1062
+ return {
1063
+ version: GRAPH_VERSION,
1064
+ path: typeof raw.path === "string" ? raw.path : "",
1065
+ bodyHash: typeof raw.bodyHash === "string" ? raw.bodyHash : "",
1066
+ nodes,
1067
+ graveyard
1068
+ };
1069
+ }
1070
+ function isEntry(e) {
1071
+ return e !== null;
1072
+ }
1073
+ function packFingerprint(fp) {
1074
+ const out = {
1075
+ type: fp.type,
1076
+ position: fp.position,
1077
+ charCount: fp.charCount,
1078
+ sentenceCount: fp.sentenceCount,
1079
+ wordCount: fp.wordCount,
1080
+ sentences: fp.sentences.map((s) => ({ c: s.c, f: s.f, l: s.l, t: s.t, w: s.w })),
1081
+ structureSig: fp.structureSig
1082
+ };
1083
+ if (fp.level !== void 0)
1084
+ out.level = fp.level;
1085
+ if (fp.language !== void 0)
1086
+ out.language = fp.language;
1087
+ if (fp.childCount !== void 0)
1088
+ out.childCount = fp.childCount;
1089
+ if (fp.childTypes !== void 0)
1090
+ out.childTypes = fp.childTypes;
1091
+ return out;
1092
+ }
1093
+ function unpackEntry(raw) {
1094
+ if (!raw || typeof raw.id !== "string" || !raw.fp)
1095
+ return null;
1096
+ const fp = raw.fp;
1097
+ if (typeof fp.type !== "string" || !Array.isArray(fp.sentences))
1098
+ return null;
1099
+ const sentences = fp.sentences.map((s) => ({
1100
+ c: s.c ?? 0,
1101
+ f: s.f ?? "",
1102
+ l: s.l ?? "",
1103
+ t: s.t ?? "-",
1104
+ wls: Array.isArray(s.wls) ? s.wls : Array.isArray(s.w) ? s.w.map((w) => w.length) : [],
1105
+ w: Array.isArray(s.w) ? s.w : []
1106
+ }));
1107
+ return {
1108
+ id: raw.id,
1109
+ fingerprint: {
1110
+ type: fp.type,
1111
+ position: fp.position ?? 0,
1112
+ parentPosition: null,
1113
+ ordinalInParent: 0,
1114
+ charCount: fp.charCount ?? 0,
1115
+ sentenceCount: fp.sentenceCount ?? sentences.length,
1116
+ wordCount: fp.wordCount ?? 0,
1117
+ sentences,
1118
+ structureSig: fp.structureSig ?? { bold: 0, italic: 0, links: 0, code: 0 },
1119
+ prevType: null,
1120
+ nextType: null,
1121
+ parentType: null,
1122
+ firstWords: [],
1123
+ lastWords: [],
1124
+ ...fp.level !== void 0 ? { level: fp.level } : {},
1125
+ ...fp.language !== void 0 ? { language: fp.language } : {},
1126
+ ...fp.childCount !== void 0 ? { childCount: fp.childCount } : {},
1127
+ ...fp.childTypes !== void 0 ? { childTypes: fp.childTypes } : {}
1128
+ }
1129
+ };
1130
+ }
1131
+ function sidecarPathFor(relativePath) {
1132
+ const normalized = relativePath.replace(/\\/g, "/").replace(/^\.\//, "");
1133
+ if (!normalized || normalized.startsWith("/") || /^[a-zA-Z]:/.test(normalized))
1134
+ return null;
1135
+ if (normalized.split("/").some((seg) => seg === ".." || seg === ""))
1136
+ return null;
1137
+ return `.greprag/node-identity/${normalized}.json`;
1138
+ }
1139
+ // Annotate the CommonJS export names for ESM import in node:
1140
+ 0 && (module.exports = {
1141
+ CONTAINER_TYPES,
1142
+ GRAPH_VERSION,
1143
+ GRAVEYARD_CAP,
1144
+ ZERO_STRUCTURE,
1145
+ bootstrapPreviousNodes,
1146
+ fingerprint,
1147
+ fingerprintAll,
1148
+ hashBody,
1149
+ isExactMatch,
1150
+ isSameContent,
1151
+ makeMinter,
1152
+ markdownToBlocks,
1153
+ matchNodes,
1154
+ parseNodeGraph,
1155
+ rebuildPreviousFromResult,
1156
+ reconcile,
1157
+ sentenceArraysEqual,
1158
+ sentenceSignalOverlapScore,
1159
+ sentenceTuplesEqual,
1160
+ serializeNodeGraph,
1161
+ shareAnySentenceTuple,
1162
+ sidecarPathFor,
1163
+ simpleHash,
1164
+ splitSentences,
1165
+ tokenizeWords
1166
+ });