arborkit 1.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.
Files changed (94) hide show
  1. package/CHANGELOG.md +21 -0
  2. package/LICENSE +21 -0
  3. package/README.md +134 -0
  4. package/dist/addressing.d.ts +22 -0
  5. package/dist/addressing.js +56 -0
  6. package/dist/addressing.js.map +1 -0
  7. package/dist/ag-ui.d.ts +45 -0
  8. package/dist/ag-ui.js +50 -0
  9. package/dist/ag-ui.js.map +1 -0
  10. package/dist/arbor.d.ts +73 -0
  11. package/dist/arbor.js +1542 -0
  12. package/dist/arbor.js.map +1 -0
  13. package/dist/artifact-tree.d.ts +66 -0
  14. package/dist/artifact-tree.js +285 -0
  15. package/dist/artifact-tree.js.map +1 -0
  16. package/dist/clock.d.ts +15 -0
  17. package/dist/clock.js +23 -0
  18. package/dist/clock.js.map +1 -0
  19. package/dist/decompose.d.ts +18 -0
  20. package/dist/decompose.js +22 -0
  21. package/dist/decompose.js.map +1 -0
  22. package/dist/delta-storage.d.ts +55 -0
  23. package/dist/delta-storage.js +106 -0
  24. package/dist/delta-storage.js.map +1 -0
  25. package/dist/delta.d.ts +55 -0
  26. package/dist/delta.js +740 -0
  27. package/dist/delta.js.map +1 -0
  28. package/dist/embedding-port.d.ts +17 -0
  29. package/dist/embedding-port.js +21 -0
  30. package/dist/embedding-port.js.map +1 -0
  31. package/dist/embedding-text.d.ts +14 -0
  32. package/dist/embedding-text.js +21 -0
  33. package/dist/embedding-text.js.map +1 -0
  34. package/dist/errors.d.ts +37 -0
  35. package/dist/errors.js +59 -0
  36. package/dist/errors.js.map +1 -0
  37. package/dist/event-log.d.ts +75 -0
  38. package/dist/event-log.js +82 -0
  39. package/dist/event-log.js.map +1 -0
  40. package/dist/file-storage.d.ts +22 -0
  41. package/dist/file-storage.js +42 -0
  42. package/dist/file-storage.js.map +1 -0
  43. package/dist/ids.d.ts +17 -0
  44. package/dist/ids.js +22 -0
  45. package/dist/ids.js.map +1 -0
  46. package/dist/index.d.ts +29 -0
  47. package/dist/index.js +1826 -0
  48. package/dist/index.js.map +1 -0
  49. package/dist/json-edit.d.ts +18 -0
  50. package/dist/json-edit.js +85 -0
  51. package/dist/json-edit.js.map +1 -0
  52. package/dist/jsonpointer.d.ts +14 -0
  53. package/dist/jsonpointer.js +33 -0
  54. package/dist/jsonpointer.js.map +1 -0
  55. package/dist/mutator.d.ts +59 -0
  56. package/dist/mutator.js +244 -0
  57. package/dist/mutator.js.map +1 -0
  58. package/dist/navigator.d.ts +85 -0
  59. package/dist/navigator.js +192 -0
  60. package/dist/navigator.js.map +1 -0
  61. package/dist/path-glob.d.ts +13 -0
  62. package/dist/path-glob.js +40 -0
  63. package/dist/path-glob.js.map +1 -0
  64. package/dist/registry-validator.d.ts +15 -0
  65. package/dist/registry-validator.js +11 -0
  66. package/dist/registry-validator.js.map +1 -0
  67. package/dist/replay.d.ts +38 -0
  68. package/dist/replay.js +183 -0
  69. package/dist/replay.js.map +1 -0
  70. package/dist/semantic-index.d.ts +88 -0
  71. package/dist/semantic-index.js +226 -0
  72. package/dist/semantic-index.js.map +1 -0
  73. package/dist/storage.d.ts +39 -0
  74. package/dist/storage.js +378 -0
  75. package/dist/storage.js.map +1 -0
  76. package/dist/toolset.d.ts +78 -0
  77. package/dist/toolset.js +306 -0
  78. package/dist/toolset.js.map +1 -0
  79. package/dist/type-aware-decision.d.ts +8 -0
  80. package/dist/type-aware-decision.js +17 -0
  81. package/dist/type-aware-decision.js.map +1 -0
  82. package/dist/type-registry.d.ts +20 -0
  83. package/dist/type-registry.js +17 -0
  84. package/dist/type-registry.js.map +1 -0
  85. package/dist/types.d.ts +28 -0
  86. package/dist/types.js +1 -0
  87. package/dist/types.js.map +1 -0
  88. package/dist/vector-index-port.d.ts +34 -0
  89. package/dist/vector-index-port.js +49 -0
  90. package/dist/vector-index-port.js.map +1 -0
  91. package/dist/zod-adapter.d.ts +13 -0
  92. package/dist/zod-adapter.js +34 -0
  93. package/dist/zod-adapter.js.map +1 -0
  94. package/package.json +47 -0
@@ -0,0 +1,15 @@
1
+ import { Validator } from './mutator.js';
2
+ import { TypeRegistry } from './type-registry.js';
3
+ import './types.js';
4
+ import './clock.js';
5
+ import './artifact-tree.js';
6
+ import './ids.js';
7
+ import './decompose.js';
8
+ import './addressing.js';
9
+ import './event-log.js';
10
+ import './errors.js';
11
+
12
+ /** Build a Mutator `Validator` that runs the registered `validate()` for the node's type. */
13
+ declare function makeRegistryValidator(registry: TypeRegistry): Validator;
14
+
15
+ export { makeRegistryValidator };
@@ -0,0 +1,11 @@
1
+ // src/registry-validator.ts
2
+ function makeRegistryValidator(registry) {
3
+ return ({ proposed, type }) => {
4
+ if (type === void 0) return;
5
+ registry.get(type)?.validate?.(proposed);
6
+ };
7
+ }
8
+ export {
9
+ makeRegistryValidator
10
+ };
11
+ //# sourceMappingURL=registry-validator.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/registry-validator.ts"],"sourcesContent":["import type { Validator } from \"./mutator\";\r\nimport type { TypeRegistry } from \"./type-registry\";\r\n\r\n/** Build a Mutator `Validator` that runs the registered `validate()` for the node's type. */\r\nexport function makeRegistryValidator(registry: TypeRegistry): Validator {\r\n return ({ proposed, type }) => {\r\n if (type === undefined) return;\r\n registry.get(type)?.validate?.(proposed);\r\n };\r\n}\r\n"],"mappings":";AAIO,SAAS,sBAAsB,UAAmC;AACvE,SAAO,CAAC,EAAE,UAAU,KAAK,MAAM;AAC7B,QAAI,SAAS,OAAW;AACxB,aAAS,IAAI,IAAI,GAAG,WAAW,QAAQ;AAAA,EACzC;AACF;","names":[]}
@@ -0,0 +1,38 @@
1
+ import { Json } from './types.js';
2
+ import { ArtifactTree } from './artifact-tree.js';
3
+ import { Addressing } from './addressing.js';
4
+ import { EventLog, MutationEvent } from './event-log.js';
5
+ import { Mutator } from './mutator.js';
6
+ import { Ref } from './errors.js';
7
+ import './ids.js';
8
+ import './clock.js';
9
+ import './decompose.js';
10
+
11
+ /** Value-level time-travel over the reversible event-log. */
12
+ declare class Replay {
13
+ private readonly tree;
14
+ private readonly log;
15
+ constructor(tree: ArtifactTree, log: EventLog);
16
+ /** The whole artifact's JSON value as of `version` (0 = initial, log.length = current).
17
+ * Throws if `version` is below the compaction floor (that history was dropped). */
18
+ reconstructValueAt(version: number): Json;
19
+ /** The value at JSON Pointer `path` as of `version`, or undefined if absent then. */
20
+ getAt(path: string, version: number): Json | undefined;
21
+ /** The mutations applied between version `vA` (inclusive) and `vB` (exclusive).
22
+ * Events compacted away are not included. */
23
+ diff(vA: number, vB: number): MutationEvent[];
24
+ /** The node's {type, tags} as of `version`, by scanning later events on its path.
25
+ * Move-aware: if the version-`version` occupant was moved away, follow it to its
26
+ * new path and keep scanning — its type/tags travel with it. If the scan exhausts
27
+ * on a FOLLOWED path, the occupant is the live node there — read it directly
28
+ * ("keep current" would read whatever now sits at the original path).
29
+ * type: string | null (untyped/absent) | undefined (unknown — keep current).
30
+ * tags: string[] ([] = untagged) | undefined (unknown/pre-M14 — keep current).
31
+ * Limitation (same as pre-M14): array-index paths can mis-resolve across sibling
32
+ * index shifts; exact for object paths. */
33
+ private stateAt;
34
+ /** Restore the node at `ref` to its value, type, AND tags as of `toVersion`, as a new live mutation. */
35
+ revert(mutator: Mutator, addressing: Addressing, ref: Ref, toVersion: number): void;
36
+ }
37
+
38
+ export { Replay };
package/dist/replay.js ADDED
@@ -0,0 +1,183 @@
1
+ // src/errors.ts
2
+ var ArborError = class extends Error {
3
+ constructor(code, message) {
4
+ super(message);
5
+ this.code = code;
6
+ this.name = new.target.name;
7
+ }
8
+ code;
9
+ };
10
+ var InvalidOpError = class extends ArborError {
11
+ constructor(message) {
12
+ super("INVALID_OP", message);
13
+ }
14
+ };
15
+
16
+ // src/jsonpointer.ts
17
+ function decodeSegment(s) {
18
+ return s.replace(/~1/g, "/").replace(/~0/g, "~");
19
+ }
20
+ function parsePointer(pointer) {
21
+ if (pointer === "") return [];
22
+ if (!pointer.startsWith("/")) {
23
+ throw new Error(`Invalid JSON Pointer (must be "" or start with "/"): ${pointer}`);
24
+ }
25
+ return pointer.slice(1).split("/").map(decodeSegment);
26
+ }
27
+
28
+ // src/json-edit.ts
29
+ function getAtPath(value, pointer) {
30
+ const segs = parsePointer(pointer);
31
+ let cur = value;
32
+ for (const seg of segs) {
33
+ if (Array.isArray(cur)) {
34
+ const i = Number(seg);
35
+ cur = Number.isInteger(i) && i >= 0 && i < cur.length ? cur[i] : void 0;
36
+ } else if (cur !== null && typeof cur === "object") {
37
+ cur = seg in cur ? cur[seg] : void 0;
38
+ } else {
39
+ return void 0;
40
+ }
41
+ if (cur === void 0) return void 0;
42
+ }
43
+ return cur;
44
+ }
45
+ function navParent(root, pointer) {
46
+ const segs = parsePointer(pointer);
47
+ if (segs.length === 0) return void 0;
48
+ let cur = root;
49
+ for (let i = 0; i < segs.length - 1; i++) {
50
+ const seg = segs[i];
51
+ if (Array.isArray(cur)) cur = cur[Number(seg)];
52
+ else if (cur !== null && typeof cur === "object") cur = cur[seg];
53
+ else return void 0;
54
+ if (cur === void 0 || cur === null) return void 0;
55
+ }
56
+ return { parent: cur, key: segs[segs.length - 1] };
57
+ }
58
+ function setAtPathMut(value, pointer, newVal) {
59
+ if (pointer === "") return newVal;
60
+ const pk = navParent(value, pointer);
61
+ if (!pk || pk.parent === null || typeof pk.parent !== "object") return value;
62
+ if (Array.isArray(pk.parent)) pk.parent[Number(pk.key)] = newVal;
63
+ else pk.parent[pk.key] = newVal;
64
+ return value;
65
+ }
66
+ function removeAtPathMut(value, pointer) {
67
+ if (pointer === "") return null;
68
+ const pk = navParent(value, pointer);
69
+ if (!pk || pk.parent === null || typeof pk.parent !== "object") return value;
70
+ if (Array.isArray(pk.parent)) pk.parent.splice(Number(pk.key), 1);
71
+ else delete pk.parent[pk.key];
72
+ return value;
73
+ }
74
+ function insertAtPathMut(value, pointer, val) {
75
+ if (pointer === "") return val;
76
+ const pk = navParent(value, pointer);
77
+ if (!pk || pk.parent === null || typeof pk.parent !== "object") return value;
78
+ if (Array.isArray(pk.parent)) pk.parent.splice(Number(pk.key), 0, val);
79
+ else pk.parent[pk.key] = val;
80
+ return value;
81
+ }
82
+
83
+ // src/replay.ts
84
+ function reverseApplyValue(value, e) {
85
+ switch (e.kind) {
86
+ case "set":
87
+ return e.path === void 0 ? value : setAtPathMut(value, e.path, structuredClone(e.before ?? null));
88
+ case "insert":
89
+ return e.path === void 0 ? value : removeAtPathMut(value, e.path);
90
+ case "remove":
91
+ return e.path === void 0 ? value : insertAtPathMut(value, e.path, structuredClone(e.before ?? null));
92
+ case "move": {
93
+ if (e.toPath === void 0 || e.fromPath === void 0) return value;
94
+ const moved = getAtPath(value, e.toPath) ?? null;
95
+ const withoutMoved = removeAtPathMut(value, e.toPath);
96
+ return insertAtPathMut(withoutMoved, e.fromPath, moved);
97
+ }
98
+ }
99
+ }
100
+ var Replay = class {
101
+ constructor(tree, log) {
102
+ this.tree = tree;
103
+ this.log = log;
104
+ }
105
+ tree;
106
+ log;
107
+ /** The whole artifact's JSON value as of `version` (0 = initial, log.length = current).
108
+ * Throws if `version` is below the compaction floor (that history was dropped). */
109
+ reconstructValueAt(version) {
110
+ const total = this.log.length();
111
+ const floor = this.log.baseSeqValue();
112
+ if (version < floor) {
113
+ throw new InvalidOpError(`cannot reconstruct version ${version}: history before ${floor} was compacted`);
114
+ }
115
+ const target = Math.min(version, total);
116
+ let value = structuredClone(this.tree.toJson());
117
+ for (let seq = total - 1; seq >= target; seq--) {
118
+ value = reverseApplyValue(value, this.log.at(seq));
119
+ }
120
+ return value;
121
+ }
122
+ /** The value at JSON Pointer `path` as of `version`, or undefined if absent then. */
123
+ getAt(path, version) {
124
+ return getAtPath(this.reconstructValueAt(version), path);
125
+ }
126
+ /** The mutations applied between version `vA` (inclusive) and `vB` (exclusive).
127
+ * Events compacted away are not included. */
128
+ diff(vA, vB) {
129
+ return this.log.since(vA).filter((e) => e.seq < vB);
130
+ }
131
+ /** The node's {type, tags} as of `version`, by scanning later events on its path.
132
+ * Move-aware: if the version-`version` occupant was moved away, follow it to its
133
+ * new path and keep scanning — its type/tags travel with it. If the scan exhausts
134
+ * on a FOLLOWED path, the occupant is the live node there — read it directly
135
+ * ("keep current" would read whatever now sits at the original path).
136
+ * type: string | null (untyped/absent) | undefined (unknown — keep current).
137
+ * tags: string[] ([] = untagged) | undefined (unknown/pre-M14 — keep current).
138
+ * Limitation (same as pre-M14): array-index paths can mis-resolve across sibling
139
+ * index shifts; exact for object paths. */
140
+ stateAt(path, version, addressing) {
141
+ const total = this.log.length();
142
+ let p = path;
143
+ for (let seq = Math.max(version, this.log.baseSeqValue()); seq < total; seq++) {
144
+ const e = this.log.at(seq);
145
+ if (e.kind === "move") {
146
+ if (e.fromPath === p) {
147
+ p = e.toPath ?? p;
148
+ continue;
149
+ }
150
+ if (e.toPath === p) return { type: null, tags: [] };
151
+ continue;
152
+ }
153
+ if (e.path !== p) continue;
154
+ if (e.kind === "set" || e.kind === "remove") {
155
+ return {
156
+ type: e.nodeTypeBefore === void 0 ? void 0 : e.nodeTypeBefore,
157
+ tags: e.tagsBefore
158
+ // absent (pre-M14) → undefined = keep current
159
+ };
160
+ }
161
+ if (e.kind === "insert") return { type: null, tags: [] };
162
+ }
163
+ if (p !== path) {
164
+ const live = addressing.byPath(p);
165
+ return { type: live?.type ?? null, tags: live?.tags ?? [] };
166
+ }
167
+ return { type: void 0, tags: void 0 };
168
+ }
169
+ /** Restore the node at `ref` to its value, type, AND tags as of `toVersion`, as a new live mutation. */
170
+ revert(mutator, addressing, ref, toVersion) {
171
+ const path = "id" in ref ? addressing.pathOf(ref.id) : ref.path;
172
+ const past = this.getAt(path, toVersion);
173
+ const { type, tags } = this.stateAt(path, toVersion, addressing);
174
+ const opts = {};
175
+ if (type !== void 0) opts.type = type;
176
+ if (tags !== void 0) opts.tags = tags;
177
+ mutator.set({ path }, past ?? null, opts);
178
+ }
179
+ };
180
+ export {
181
+ Replay
182
+ };
183
+ //# sourceMappingURL=replay.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/errors.ts","../src/jsonpointer.ts","../src/json-edit.ts","../src/replay.ts"],"sourcesContent":["import type { NodeId } from \"./types\";\r\n\r\n/** A reference to a node: by stable id or by JSON Pointer path. */\r\nexport type Ref = { id: NodeId } | { path: string };\r\n\r\nexport class ArborError extends Error {\r\n constructor(\r\n public readonly code: string,\r\n message: string,\r\n ) {\r\n super(message);\r\n this.name = new.target.name;\r\n }\r\n}\r\n\r\nexport class NodeNotFoundError extends ArborError {\r\n constructor(public readonly ref: Ref) {\r\n super(\"NODE_NOT_FOUND\", `Node not found: ${JSON.stringify(ref)}`);\r\n }\r\n}\r\n\r\nexport class ScopeViolationError extends ArborError {\r\n constructor(\r\n public readonly targetPath: string,\r\n public readonly scope: string,\r\n ) {\r\n super(\"SCOPE_VIOLATION\", `Access outside scope: ${targetPath} (scope: ${scope})`);\r\n }\r\n}\r\n\r\nexport class StaleVersionError extends ArborError {\r\n constructor(\r\n public readonly id: NodeId,\r\n public readonly expected: number,\r\n public readonly actual: number,\r\n ) {\r\n super(\"STALE_VERSION\", `Stale version for ${id}: expected ${expected}, actual ${actual}`);\r\n }\r\n}\r\n\r\nexport class InvalidOpError extends ArborError {\r\n constructor(message: string) {\r\n super(\"INVALID_OP\", message);\r\n }\r\n}\r\n\r\nexport class ValidationError extends ArborError {\r\n constructor(\r\n public readonly type: string | undefined,\r\n public readonly details: string,\r\n ) {\r\n super(\"VALIDATION_ERROR\", `Validation failed${type ? ` for type ${type}` : \"\"}: ${details}`);\r\n }\r\n}\r\n","/** Escape a single reference token per RFC 6901: ~ -> ~0, / -> ~1. */\r\nexport function encodeSegment(s: string): string {\r\n return s.replace(/~/g, \"~0\").replace(/\\//g, \"~1\");\r\n}\r\n\r\n/** Unescape a single reference token per RFC 6901: ~1 -> /, then ~0 -> ~. */\r\nexport function decodeSegment(s: string): string {\r\n return s.replace(/~1/g, \"/\").replace(/~0/g, \"~\");\r\n}\r\n\r\n/** Build a JSON Pointer string. Empty segment list => \"\" (root). */\r\nexport function buildPointer(segments: ReadonlyArray<string | number>): string {\r\n if (segments.length === 0) return \"\";\r\n return \"/\" + segments.map((s) => encodeSegment(String(s))).join(\"/\");\r\n}\r\n\r\n/** Append one child key to a parent pointer, escaping the key exactly as `buildPointer` does. */\r\nexport function appendPointer(parent: string, key: string | number): string {\r\n return parent + \"/\" + encodeSegment(String(key));\r\n}\r\n\r\n/** True when `path` is at or under `scope` (JSON Pointer prefix). Undefined scope = everywhere. */\r\nexport function isWithin(path: string, scope: string | undefined): boolean {\r\n return scope === undefined || path === scope || path.startsWith(scope + \"/\");\r\n}\r\n\r\n/** Parse a JSON Pointer into decoded segments. \"\" => [] (root). */\r\nexport function parsePointer(pointer: string): string[] {\r\n if (pointer === \"\") return [];\r\n if (!pointer.startsWith(\"/\")) {\r\n throw new Error(`Invalid JSON Pointer (must be \"\" or start with \"/\"): ${pointer}`);\r\n }\r\n return pointer.slice(1).split(\"/\").map(decodeSegment);\r\n}\r\n","import type { Json } from \"./types\";\r\nimport { parsePointer } from \"./jsonpointer\";\r\n\r\n/** Read the value at a JSON Pointer, or undefined if any segment is missing. */\r\nexport function getAtPath(value: Json, pointer: string): Json | undefined {\r\n const segs = parsePointer(pointer);\r\n let cur: Json | undefined = value;\r\n for (const seg of segs) {\r\n if (Array.isArray(cur)) {\r\n const i = Number(seg);\r\n cur = Number.isInteger(i) && i >= 0 && i < cur.length ? cur[i] : undefined;\r\n } else if (cur !== null && typeof cur === \"object\") {\r\n cur = seg in cur ? (cur as Record<string, Json>)[seg] : undefined;\r\n } else {\r\n return undefined;\r\n }\r\n if (cur === undefined) return undefined;\r\n }\r\n return cur;\r\n}\r\n\r\n/** Navigate (within `root`) to the container holding the pointer's last segment. */\r\nfunction navParent(root: Json, pointer: string): { parent: Json; key: string } | undefined {\r\n const segs = parsePointer(pointer);\r\n if (segs.length === 0) return undefined;\r\n let cur: Json | undefined = root;\r\n for (let i = 0; i < segs.length - 1; i++) {\r\n const seg = segs[i];\r\n if (Array.isArray(cur)) cur = cur[Number(seg)];\r\n else if (cur !== null && typeof cur === \"object\") cur = (cur as Record<string, Json>)[seg];\r\n else return undefined;\r\n if (cur === undefined || cur === null) return undefined;\r\n }\r\n return { parent: cur, key: segs[segs.length - 1] };\r\n}\r\n\r\n/** In-place variant of setAtPath: mutates `value` (root pointer returns `newVal`). */\r\nexport function setAtPathMut(value: Json, pointer: string, newVal: Json): Json {\r\n if (pointer === \"\") return newVal;\r\n const pk = navParent(value, pointer);\r\n if (!pk || pk.parent === null || typeof pk.parent !== \"object\") return value;\r\n if (Array.isArray(pk.parent)) pk.parent[Number(pk.key)] = newVal;\r\n else (pk.parent as Record<string, Json>)[pk.key] = newVal;\r\n return value;\r\n}\r\n\r\n/** In-place variant of removeAtPath: mutates `value` (root pointer returns null). */\r\nexport function removeAtPathMut(value: Json, pointer: string): Json {\r\n if (pointer === \"\") return null;\r\n const pk = navParent(value, pointer);\r\n if (!pk || pk.parent === null || typeof pk.parent !== \"object\") return value;\r\n if (Array.isArray(pk.parent)) pk.parent.splice(Number(pk.key), 1);\r\n else delete (pk.parent as Record<string, Json>)[pk.key];\r\n return value;\r\n}\r\n\r\n/** In-place variant of insertAtPath: mutates `value` (root pointer returns `val`). */\r\nexport function insertAtPathMut(value: Json, pointer: string, val: Json): Json {\r\n if (pointer === \"\") return val;\r\n const pk = navParent(value, pointer);\r\n if (!pk || pk.parent === null || typeof pk.parent !== \"object\") return value;\r\n if (Array.isArray(pk.parent)) pk.parent.splice(Number(pk.key), 0, val);\r\n else (pk.parent as Record<string, Json>)[pk.key] = val;\r\n return value;\r\n}\r\n\r\n/** Return a copy of `value` with the value at `pointer` replaced (root → returns `newVal`). */\r\nexport function setAtPath(value: Json, pointer: string, newVal: Json): Json {\r\n return setAtPathMut(structuredClone(value), pointer, newVal);\r\n}\r\n\r\n/** Return a copy of `value` with the element at `pointer` removed (object delete / array splice). */\r\nexport function removeAtPath(value: Json, pointer: string): Json {\r\n return removeAtPathMut(structuredClone(value), pointer);\r\n}\r\n\r\n/** Return a copy of `value` with `val` inserted at `pointer` (object set / array splice-in). */\r\nexport function insertAtPath(value: Json, pointer: string, val: Json): Json {\r\n return insertAtPathMut(structuredClone(value), pointer, val);\r\n}\r\n","import type { Json } from \"./types\";\r\nimport type { ArtifactTree } from \"./artifact-tree\";\r\nimport type { Addressing } from \"./addressing\";\r\nimport type { EventLog, MutationEvent } from \"./event-log\";\r\nimport type { Mutator } from \"./mutator\";\r\nimport { type Ref, InvalidOpError } from \"./errors\";\r\nimport { getAtPath, setAtPathMut, removeAtPathMut, insertAtPathMut } from \"./json-edit\";\r\n\r\n/** Undo a single event on a JSON value IN PLACE (the caller owns `value` — it is the\r\n * single upfront clone made by `reconstructValueAt`). The spliced-in `before` values\r\n * are still cloned (M14): they protect the LOG from mutation through the output. */\r\nfunction reverseApplyValue(value: Json, e: MutationEvent): Json {\r\n switch (e.kind) {\r\n case \"set\":\r\n return e.path === undefined ? value : setAtPathMut(value, e.path, structuredClone(e.before ?? null));\r\n case \"insert\":\r\n return e.path === undefined ? value : removeAtPathMut(value, e.path);\r\n case \"remove\":\r\n return e.path === undefined ? value : insertAtPathMut(value, e.path, structuredClone(e.before ?? null));\r\n case \"move\": {\r\n if (e.toPath === undefined || e.fromPath === undefined) return value;\r\n const moved = getAtPath(value, e.toPath) ?? null;\r\n const withoutMoved = removeAtPathMut(value, e.toPath);\r\n return insertAtPathMut(withoutMoved, e.fromPath, moved);\r\n }\r\n }\r\n}\r\n\r\n/** Value-level time-travel over the reversible event-log. */\r\nexport class Replay {\r\n constructor(\r\n private readonly tree: ArtifactTree,\r\n private readonly log: EventLog,\r\n ) {}\r\n\r\n /** The whole artifact's JSON value as of `version` (0 = initial, log.length = current).\r\n * Throws if `version` is below the compaction floor (that history was dropped). */\r\n reconstructValueAt(version: number): Json {\r\n const total = this.log.length();\r\n const floor = this.log.baseSeqValue();\r\n if (version < floor) {\r\n throw new InvalidOpError(`cannot reconstruct version ${version}: history before ${floor} was compacted`);\r\n }\r\n const target = Math.min(version, total);\r\n let value: Json = structuredClone(this.tree.toJson());\r\n for (let seq = total - 1; seq >= target; seq--) {\r\n value = reverseApplyValue(value, this.log.at(seq)!);\r\n }\r\n return value;\r\n }\r\n\r\n /** The value at JSON Pointer `path` as of `version`, or undefined if absent then. */\r\n getAt(path: string, version: number): Json | undefined {\r\n return getAtPath(this.reconstructValueAt(version), path);\r\n }\r\n\r\n /** The mutations applied between version `vA` (inclusive) and `vB` (exclusive).\r\n * Events compacted away are not included. */\r\n diff(vA: number, vB: number): MutationEvent[] {\r\n return this.log.since(vA).filter((e) => e.seq < vB);\r\n }\r\n\r\n /** The node's {type, tags} as of `version`, by scanning later events on its path.\r\n * Move-aware: if the version-`version` occupant was moved away, follow it to its\r\n * new path and keep scanning — its type/tags travel with it. If the scan exhausts\r\n * on a FOLLOWED path, the occupant is the live node there — read it directly\r\n * (\"keep current\" would read whatever now sits at the original path).\r\n * type: string | null (untyped/absent) | undefined (unknown — keep current).\r\n * tags: string[] ([] = untagged) | undefined (unknown/pre-M14 — keep current).\r\n * Limitation (same as pre-M14): array-index paths can mis-resolve across sibling\r\n * index shifts; exact for object paths. */\r\n private stateAt(\r\n path: string,\r\n version: number,\r\n addressing: Addressing,\r\n ): { type: string | null | undefined; tags: string[] | undefined } {\r\n const total = this.log.length();\r\n let p = path;\r\n for (let seq = Math.max(version, this.log.baseSeqValue()); seq < total; seq++) {\r\n const e = this.log.at(seq)!;\r\n if (e.kind === \"move\") {\r\n if (e.fromPath === p) {\r\n p = e.toPath ?? p; // occupant moved away — follow it\r\n continue;\r\n }\r\n if (e.toPath === p) return { type: null, tags: [] }; // something ELSE moved in → vacant at `version`\r\n continue;\r\n }\r\n if (e.path !== p) continue;\r\n if (e.kind === \"set\" || e.kind === \"remove\") {\r\n return {\r\n type: e.nodeTypeBefore === undefined ? undefined : e.nodeTypeBefore,\r\n tags: e.tagsBefore, // absent (pre-M14) → undefined = keep current\r\n };\r\n }\r\n if (e.kind === \"insert\") return { type: null, tags: [] }; // node did not exist at `version`\r\n }\r\n if (p !== path) {\r\n // Followed through moves and nothing later touched the occupant: it is the\r\n // live node at `p` — its type/tags are the version-`version` answer.\r\n const live = addressing.byPath(p);\r\n return { type: live?.type ?? null, tags: live?.tags ?? [] };\r\n }\r\n return { type: undefined, tags: undefined }; // untouched since `version` — keep current\r\n }\r\n\r\n /** Restore the node at `ref` to its value, type, AND tags as of `toVersion`, as a new live mutation. */\r\n revert(mutator: Mutator, addressing: Addressing, ref: Ref, toVersion: number): void {\r\n const path = \"id\" in ref ? addressing.pathOf(ref.id) : ref.path;\r\n const past = this.getAt(path, toVersion);\r\n const { type, tags } = this.stateAt(path, toVersion, addressing);\r\n const opts: { type?: string | null; tags?: string[] } = {};\r\n if (type !== undefined) opts.type = type;\r\n if (tags !== undefined) opts.tags = tags;\r\n mutator.set({ path }, past ?? null, opts);\r\n }\r\n}\r\n"],"mappings":"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","names":[]}
@@ -0,0 +1,88 @@
1
+ import { ArbNode, NodeId } from './types.js';
2
+ import { ArtifactTree } from './artifact-tree.js';
3
+ import { Addressing } from './addressing.js';
4
+ import { TypeRegistry } from './type-registry.js';
5
+ import { EmbeddingPort } from './embedding-port.js';
6
+ import { VectorIndexPort } from './vector-index-port.js';
7
+ import './ids.js';
8
+ import './clock.js';
9
+ import './decompose.js';
10
+
11
+ interface SearchOpts {
12
+ k?: number;
13
+ under?: string;
14
+ type?: string;
15
+ tag?: string;
16
+ freshness?: "best-effort" | "wait";
17
+ }
18
+ interface SearchHit {
19
+ id: NodeId;
20
+ path: string;
21
+ type?: string;
22
+ score: number;
23
+ snippet: string;
24
+ }
25
+ interface SearchResult {
26
+ results: SearchHit[];
27
+ staleCount: number;
28
+ }
29
+ /**
30
+ * Owns the per-node semantic index: a stale queue fed by mutation hooks, an async
31
+ * batched reindexer, and (Task 6) search. Embedding is never in the mutation path —
32
+ * mutations only mark stale; reindex does the async embedding work.
33
+ */
34
+ declare class SemanticIndex {
35
+ private readonly tree;
36
+ private readonly addressing;
37
+ private readonly embedding;
38
+ private readonly vectors;
39
+ private readonly registry?;
40
+ private readonly stale;
41
+ private readonly pendingRemoval;
42
+ constructor(tree: ArtifactTree, addressing: Addressing, embedding: EmbeddingPort, vectors: VectorIndexPort, registry?: TypeRegistry | undefined);
43
+ /** The nearest ancestor whose type declares embedText — the semantic unit that
44
+ * owns this shard — or undefined. (O(depth) walk per call — fine at v1 sizes.) */
45
+ private embedTextAncestor;
46
+ /**
47
+ * A node is a suppressed decomposition shard iff some ANCESTOR has a typed
48
+ * embedText AND the node does not itself declare a typed embedText.
49
+ */
50
+ private isSuppressedShard;
51
+ /** Mutation hook: a node's content changed (set/insert). Marks it stale if its embedding-text changed. */
52
+ onChange(node: ArbNode): void;
53
+ /** Mutation hook: a node was removed. Drops it from the index and the stale queue. */
54
+ onRemove(nodeId: NodeId): void;
55
+ /**
56
+ * Snapshot of both queues for transaction rollback.
57
+ *
58
+ * The sync hooks (`onChange`, `onRemove`) only move ids between the `stale`
59
+ * queue and the `pendingRemoval` queue; all actual vector mutations happen in
60
+ * the async `reindex()`. This snapshot covers both queues so that a rollback
61
+ * fully restores the observable state of the index without touching vectors.
62
+ */
63
+ txSnapshot(): unknown;
64
+ /** Restore both queues captured by `txSnapshot`. */
65
+ txRestore(snapshot: unknown): void;
66
+ /** Convenience: the hooks to wire into `MutatorDeps`. */
67
+ hooks(): {
68
+ onChange: (node: ArbNode) => void;
69
+ onRemove: (nodeId: NodeId) => void;
70
+ onTxSnapshot: () => unknown;
71
+ onTxRestore: (snapshot: unknown) => void;
72
+ };
73
+ staleCount(): number;
74
+ /** Embed every stale node (one batch), upsert vectors, mark fresh, clear the processed ids.
75
+ * Interleave-safe: mutations landing during the embed await are respected — a node
76
+ * removed mid-flight is dropped (never resurrected), and a node whose text changed
77
+ * stays queued for the next pass instead of being marked fresh for the old text. */
78
+ reindex(): Promise<void>;
79
+ private snippetOf;
80
+ /**
81
+ * Semantic search: embed the query, rank indexed nodes by cosine, post-filter by
82
+ * under/type/tag, return top-k. `freshness: "wait"` flushes the reindexer first;
83
+ * default `best-effort` searches what's indexed and reports `staleCount`.
84
+ */
85
+ search(queryText: string, opts?: SearchOpts): Promise<SearchResult>;
86
+ }
87
+
88
+ export { type SearchHit, type SearchOpts, type SearchResult, SemanticIndex };
@@ -0,0 +1,226 @@
1
+ // src/embedding-text.ts
2
+ function toEmbeddingText(node, value, typeDef) {
3
+ if (typeDef?.embedText) return typeDef.embedText(value);
4
+ if (node.kind !== "leaf") return null;
5
+ if (typeof value === "string") return value;
6
+ if (value !== null && typeof value === "object") return JSON.stringify(value);
7
+ return null;
8
+ }
9
+ function textHash(text) {
10
+ let h = 2166136261 >>> 0;
11
+ for (let i = 0; i < text.length; i++) {
12
+ h ^= text.charCodeAt(i);
13
+ h = Math.imul(h, 16777619) >>> 0;
14
+ }
15
+ return h.toString(16);
16
+ }
17
+
18
+ // src/jsonpointer.ts
19
+ function isWithin(path, scope) {
20
+ return scope === void 0 || path === scope || path.startsWith(scope + "/");
21
+ }
22
+
23
+ // src/semantic-index.ts
24
+ var SemanticIndex = class {
25
+ constructor(tree, addressing, embedding, vectors, registry) {
26
+ this.tree = tree;
27
+ this.addressing = addressing;
28
+ this.embedding = embedding;
29
+ this.vectors = vectors;
30
+ this.registry = registry;
31
+ for (const node of tree.allNodes()) {
32
+ if (node.meta.embedding.state === "stale") this.stale.add(node.id);
33
+ }
34
+ }
35
+ tree;
36
+ addressing;
37
+ embedding;
38
+ vectors;
39
+ registry;
40
+ stale = /* @__PURE__ */ new Set();
41
+ pendingRemoval = /* @__PURE__ */ new Set();
42
+ /** The nearest ancestor whose type declares embedText — the semantic unit that
43
+ * owns this shard — or undefined. (O(depth) walk per call — fine at v1 sizes.) */
44
+ embedTextAncestor(node) {
45
+ let pid = node.parentId;
46
+ while (pid !== null) {
47
+ const anc = this.tree.get(pid);
48
+ const ancDef = anc?.type ? this.registry?.get(anc.type) : void 0;
49
+ if (ancDef?.embedText) return anc;
50
+ pid = anc?.parentId ?? null;
51
+ }
52
+ return void 0;
53
+ }
54
+ /**
55
+ * A node is a suppressed decomposition shard iff some ANCESTOR has a typed
56
+ * embedText AND the node does not itself declare a typed embedText.
57
+ */
58
+ isSuppressedShard(node) {
59
+ const ownTypeDef = node.type ? this.registry?.get(node.type) : void 0;
60
+ if (ownTypeDef?.embedText) return false;
61
+ return this.embedTextAncestor(node) !== void 0;
62
+ }
63
+ /** Mutation hook: a node's content changed (set/insert). Marks it stale if its embedding-text changed. */
64
+ onChange(node) {
65
+ if (this.isSuppressedShard(node)) {
66
+ node.meta.embedding = { state: "none" };
67
+ this.pendingRemoval.add(node.id);
68
+ this.stale.delete(node.id);
69
+ const anc = this.embedTextAncestor(node);
70
+ if (anc) this.onChange(anc);
71
+ return;
72
+ }
73
+ const value = this.tree.toJson(node.id);
74
+ const typeDef = node.type ? this.registry?.get(node.type) : void 0;
75
+ const text = toEmbeddingText(node, value, typeDef);
76
+ if (text === null) {
77
+ node.meta.embedding = { state: "none" };
78
+ this.pendingRemoval.add(node.id);
79
+ this.stale.delete(node.id);
80
+ return;
81
+ }
82
+ const hash = textHash(text);
83
+ if (node.meta.embedding.state === "fresh" && node.meta.embedding.textHash === hash) {
84
+ return;
85
+ }
86
+ node.meta.embedding = { state: "stale", textHash: hash };
87
+ this.pendingRemoval.delete(node.id);
88
+ this.stale.add(node.id);
89
+ }
90
+ /** Mutation hook: a node was removed. Drops it from the index and the stale queue. */
91
+ onRemove(nodeId) {
92
+ this.pendingRemoval.add(nodeId);
93
+ this.stale.delete(nodeId);
94
+ }
95
+ /**
96
+ * Snapshot of both queues for transaction rollback.
97
+ *
98
+ * The sync hooks (`onChange`, `onRemove`) only move ids between the `stale`
99
+ * queue and the `pendingRemoval` queue; all actual vector mutations happen in
100
+ * the async `reindex()`. This snapshot covers both queues so that a rollback
101
+ * fully restores the observable state of the index without touching vectors.
102
+ */
103
+ txSnapshot() {
104
+ return {
105
+ stale: new Set(this.stale),
106
+ pendingRemoval: new Set(this.pendingRemoval)
107
+ };
108
+ }
109
+ /** Restore both queues captured by `txSnapshot`. */
110
+ txRestore(snapshot) {
111
+ const snap = snapshot;
112
+ this.stale.clear();
113
+ for (const id of snap.stale) this.stale.add(id);
114
+ this.pendingRemoval.clear();
115
+ for (const id of snap.pendingRemoval) this.pendingRemoval.add(id);
116
+ }
117
+ /** Convenience: the hooks to wire into `MutatorDeps`. */
118
+ hooks() {
119
+ return {
120
+ onChange: (node) => this.onChange(node),
121
+ onRemove: (nodeId) => this.onRemove(nodeId),
122
+ onTxSnapshot: () => this.txSnapshot(),
123
+ onTxRestore: (snapshot) => this.txRestore(snapshot)
124
+ };
125
+ }
126
+ staleCount() {
127
+ return this.stale.size;
128
+ }
129
+ /** Embed every stale node (one batch), upsert vectors, mark fresh, clear the processed ids.
130
+ * Interleave-safe: mutations landing during the embed await are respected — a node
131
+ * removed mid-flight is dropped (never resurrected), and a node whose text changed
132
+ * stays queued for the next pass instead of being marked fresh for the old text. */
133
+ async reindex() {
134
+ for (const id of this.pendingRemoval) await this.vectors.remove(id);
135
+ this.pendingRemoval.clear();
136
+ const ids = [...this.stale];
137
+ if (ids.length === 0) return;
138
+ const completed = /* @__PURE__ */ new Set();
139
+ const items = [];
140
+ for (const id of ids) {
141
+ const node = this.tree.get(id);
142
+ if (!node) {
143
+ completed.add(id);
144
+ continue;
145
+ }
146
+ if (this.isSuppressedShard(node)) {
147
+ node.meta.embedding = { state: "none" };
148
+ await this.vectors.remove(id);
149
+ completed.add(id);
150
+ continue;
151
+ }
152
+ const value = this.tree.toJson(id);
153
+ const typeDef = node.type ? this.registry?.get(node.type) : void 0;
154
+ const text = toEmbeddingText(node, value, typeDef);
155
+ if (text === null) {
156
+ node.meta.embedding = { state: "none" };
157
+ await this.vectors.remove(id);
158
+ completed.add(id);
159
+ continue;
160
+ }
161
+ items.push({ id, text, hash: textHash(text) });
162
+ }
163
+ if (items.length > 0) {
164
+ const embedded = await this.embedding.embed(items.map((it) => it.text));
165
+ const upserts = [];
166
+ for (const [i, it] of items.entries()) {
167
+ const node = this.tree.get(it.id);
168
+ if (!node) {
169
+ await this.vectors.remove(it.id);
170
+ completed.add(it.id);
171
+ continue;
172
+ }
173
+ const cur = node.meta.embedding;
174
+ if (cur.state !== "stale" || cur.textHash !== void 0 && cur.textHash !== it.hash) {
175
+ if (node.meta.embedding.state === "none") completed.add(it.id);
176
+ continue;
177
+ }
178
+ upserts.push({ nodeId: it.id, vector: embedded[i] });
179
+ node.meta.embedding = { state: "fresh", textHash: it.hash };
180
+ completed.add(it.id);
181
+ }
182
+ if (upserts.length > 0) await this.vectors.upsert(upserts);
183
+ }
184
+ for (const id of ids) {
185
+ if (completed.has(id)) this.stale.delete(id);
186
+ }
187
+ }
188
+ snippetOf(value) {
189
+ const s = JSON.stringify(value);
190
+ return s.length <= 80 ? s : s.slice(0, 80) + "\u2026";
191
+ }
192
+ /**
193
+ * Semantic search: embed the query, rank indexed nodes by cosine, post-filter by
194
+ * under/type/tag, return top-k. `freshness: "wait"` flushes the reindexer first;
195
+ * default `best-effort` searches what's indexed and reports `staleCount`.
196
+ */
197
+ async search(queryText, opts = {}) {
198
+ if (opts.freshness === "wait") await this.reindex();
199
+ const k = opts.k ?? 8;
200
+ const [queryVec] = await this.embedding.embed([queryText]);
201
+ const ranked = await this.vectors.search(queryVec, await this.vectors.size());
202
+ const results = [];
203
+ for (const hit of ranked) {
204
+ if (results.length >= k) break;
205
+ if (this.pendingRemoval.has(hit.nodeId)) continue;
206
+ const node = this.tree.get(hit.nodeId);
207
+ if (!node) continue;
208
+ const path = this.addressing.pathOf(node.id);
209
+ if (opts.under !== void 0 && !isWithin(path, opts.under)) continue;
210
+ if (opts.type !== void 0 && node.type !== opts.type) continue;
211
+ if (opts.tag !== void 0 && !(node.tags?.includes(opts.tag) ?? false)) continue;
212
+ results.push({
213
+ id: node.id,
214
+ path,
215
+ type: node.type,
216
+ score: hit.score,
217
+ snippet: this.snippetOf(this.tree.toJson(node.id))
218
+ });
219
+ }
220
+ return { results, staleCount: this.staleCount() };
221
+ }
222
+ };
223
+ export {
224
+ SemanticIndex
225
+ };
226
+ //# sourceMappingURL=semantic-index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/embedding-text.ts","../src/jsonpointer.ts","../src/semantic-index.ts"],"sourcesContent":["import type { ArbNode, Json } from \"./types\";\r\nimport type { TypeDef } from \"./type-registry\";\r\n\r\n/**\r\n * The text used to embed a node, or null if the node is not embedded.\r\n * A registered type's `embedText` wins; otherwise: string leaf → its value;\r\n * opaque object/array leaf → its JSON; numeric/boolean/null leaves and all\r\n * structural containers → null.\r\n */\r\nexport function toEmbeddingText(node: ArbNode, value: Json, typeDef?: TypeDef): string | null {\r\n if (typeDef?.embedText) return typeDef.embedText(value);\r\n if (node.kind !== \"leaf\") return null;\r\n if (typeof value === \"string\") return value;\r\n if (value !== null && typeof value === \"object\") return JSON.stringify(value);\r\n return null;\r\n}\r\n\r\n/** Deterministic 32-bit FNV-1a hash, hex-encoded — used to dedupe re-embedding. */\r\nexport function textHash(text: string): string {\r\n let h = 2166136261 >>> 0;\r\n for (let i = 0; i < text.length; i++) {\r\n h ^= text.charCodeAt(i);\r\n h = Math.imul(h, 16777619) >>> 0;\r\n }\r\n return h.toString(16);\r\n}\r\n","/** Escape a single reference token per RFC 6901: ~ -> ~0, / -> ~1. */\r\nexport function encodeSegment(s: string): string {\r\n return s.replace(/~/g, \"~0\").replace(/\\//g, \"~1\");\r\n}\r\n\r\n/** Unescape a single reference token per RFC 6901: ~1 -> /, then ~0 -> ~. */\r\nexport function decodeSegment(s: string): string {\r\n return s.replace(/~1/g, \"/\").replace(/~0/g, \"~\");\r\n}\r\n\r\n/** Build a JSON Pointer string. Empty segment list => \"\" (root). */\r\nexport function buildPointer(segments: ReadonlyArray<string | number>): string {\r\n if (segments.length === 0) return \"\";\r\n return \"/\" + segments.map((s) => encodeSegment(String(s))).join(\"/\");\r\n}\r\n\r\n/** Append one child key to a parent pointer, escaping the key exactly as `buildPointer` does. */\r\nexport function appendPointer(parent: string, key: string | number): string {\r\n return parent + \"/\" + encodeSegment(String(key));\r\n}\r\n\r\n/** True when `path` is at or under `scope` (JSON Pointer prefix). Undefined scope = everywhere. */\r\nexport function isWithin(path: string, scope: string | undefined): boolean {\r\n return scope === undefined || path === scope || path.startsWith(scope + \"/\");\r\n}\r\n\r\n/** Parse a JSON Pointer into decoded segments. \"\" => [] (root). */\r\nexport function parsePointer(pointer: string): string[] {\r\n if (pointer === \"\") return [];\r\n if (!pointer.startsWith(\"/\")) {\r\n throw new Error(`Invalid JSON Pointer (must be \"\" or start with \"/\"): ${pointer}`);\r\n }\r\n return pointer.slice(1).split(\"/\").map(decodeSegment);\r\n}\r\n","import type { ArbNode, NodeId, Json } from \"./types\";\r\nimport type { ArtifactTree } from \"./artifact-tree\";\r\nimport type { Addressing } from \"./addressing\";\r\nimport type { TypeRegistry } from \"./type-registry\";\r\nimport type { EmbeddingPort } from \"./embedding-port\";\r\nimport type { VectorIndexPort } from \"./vector-index-port\";\r\nimport { toEmbeddingText, textHash } from \"./embedding-text\";\r\nimport { isWithin } from \"./jsonpointer\";\r\n\r\nexport interface SearchOpts {\r\n k?: number;\r\n under?: string;\r\n type?: string;\r\n tag?: string;\r\n freshness?: \"best-effort\" | \"wait\";\r\n}\r\n\r\nexport interface SearchHit {\r\n id: NodeId;\r\n path: string;\r\n type?: string;\r\n score: number;\r\n snippet: string;\r\n}\r\n\r\nexport interface SearchResult {\r\n results: SearchHit[];\r\n staleCount: number;\r\n}\r\n\r\n/**\r\n * Owns the per-node semantic index: a stale queue fed by mutation hooks, an async\r\n * batched reindexer, and (Task 6) search. Embedding is never in the mutation path —\r\n * mutations only mark stale; reindex does the async embedding work.\r\n */\r\nexport class SemanticIndex {\r\n private readonly stale = new Set<NodeId>();\r\n private readonly pendingRemoval = new Set<NodeId>();\r\n\r\n constructor(\r\n private readonly tree: ArtifactTree,\r\n private readonly addressing: Addressing,\r\n private readonly embedding: EmbeddingPort,\r\n private readonly vectors: VectorIndexPort,\r\n private readonly registry?: TypeRegistry,\r\n ) {\r\n // Recover the stale queue after a restore: `stale` itself is not persisted,\r\n // but each node's `meta.embedding.state` is. A fresh tree has only \"none\"\r\n // states, so this is a no-op there.\r\n for (const node of tree.allNodes()) {\r\n if (node.meta.embedding.state === \"stale\") this.stale.add(node.id);\r\n }\r\n }\r\n\r\n /** The nearest ancestor whose type declares embedText — the semantic unit that\r\n * owns this shard — or undefined. (O(depth) walk per call — fine at v1 sizes.) */\r\n private embedTextAncestor(node: ArbNode): ArbNode | undefined {\r\n let pid = node.parentId;\r\n while (pid !== null) {\r\n const anc = this.tree.get(pid);\r\n const ancDef = anc?.type ? this.registry?.get(anc.type) : undefined;\r\n if (ancDef?.embedText) return anc;\r\n pid = anc?.parentId ?? null;\r\n }\r\n return undefined;\r\n }\r\n\r\n /**\r\n * A node is a suppressed decomposition shard iff some ANCESTOR has a typed\r\n * embedText AND the node does not itself declare a typed embedText.\r\n */\r\n private isSuppressedShard(node: ArbNode): boolean {\r\n const ownTypeDef = node.type ? this.registry?.get(node.type) : undefined;\r\n if (ownTypeDef?.embedText) return false; // node is its own semantic unit\r\n return this.embedTextAncestor(node) !== undefined;\r\n }\r\n\r\n /** Mutation hook: a node's content changed (set/insert). Marks it stale if its embedding-text changed. */\r\n onChange(node: ArbNode): void {\r\n if (this.isSuppressedShard(node)) {\r\n node.meta.embedding = { state: \"none\" };\r\n this.pendingRemoval.add(node.id);\r\n this.stale.delete(node.id);\r\n // The owning typed unit's embed text covers this shard — re-hash IT.\r\n // (Terminates: the ancestor has its own embedText, so it takes the\r\n // normal branch below.)\r\n const anc = this.embedTextAncestor(node);\r\n if (anc) this.onChange(anc);\r\n return;\r\n }\r\n const value = this.tree.toJson(node.id);\r\n const typeDef = node.type ? this.registry?.get(node.type) : undefined;\r\n const text = toEmbeddingText(node, value, typeDef);\r\n if (text === null) {\r\n node.meta.embedding = { state: \"none\" };\r\n this.pendingRemoval.add(node.id);\r\n this.stale.delete(node.id);\r\n return;\r\n }\r\n const hash = textHash(text);\r\n if (node.meta.embedding.state === \"fresh\" && node.meta.embedding.textHash === hash) {\r\n return;\r\n }\r\n node.meta.embedding = { state: \"stale\", textHash: hash };\r\n this.pendingRemoval.delete(node.id);\r\n this.stale.add(node.id);\r\n }\r\n\r\n /** Mutation hook: a node was removed. Drops it from the index and the stale queue. */\r\n onRemove(nodeId: NodeId): void {\r\n this.pendingRemoval.add(nodeId);\r\n this.stale.delete(nodeId);\r\n }\r\n\r\n /**\r\n * Snapshot of both queues for transaction rollback.\r\n *\r\n * The sync hooks (`onChange`, `onRemove`) only move ids between the `stale`\r\n * queue and the `pendingRemoval` queue; all actual vector mutations happen in\r\n * the async `reindex()`. This snapshot covers both queues so that a rollback\r\n * fully restores the observable state of the index without touching vectors.\r\n */\r\n txSnapshot(): unknown {\r\n return {\r\n stale: new Set(this.stale),\r\n pendingRemoval: new Set(this.pendingRemoval),\r\n };\r\n }\r\n\r\n /** Restore both queues captured by `txSnapshot`. */\r\n txRestore(snapshot: unknown): void {\r\n const snap = snapshot as { stale: Set<NodeId>; pendingRemoval: Set<NodeId> };\r\n this.stale.clear();\r\n for (const id of snap.stale) this.stale.add(id);\r\n this.pendingRemoval.clear();\r\n for (const id of snap.pendingRemoval) this.pendingRemoval.add(id);\r\n }\r\n\r\n /** Convenience: the hooks to wire into `MutatorDeps`. */\r\n hooks(): {\r\n onChange: (node: ArbNode) => void;\r\n onRemove: (nodeId: NodeId) => void;\r\n onTxSnapshot: () => unknown;\r\n onTxRestore: (snapshot: unknown) => void;\r\n } {\r\n return {\r\n onChange: (node) => this.onChange(node),\r\n onRemove: (nodeId) => this.onRemove(nodeId),\r\n onTxSnapshot: () => this.txSnapshot(),\r\n onTxRestore: (snapshot) => this.txRestore(snapshot),\r\n };\r\n }\r\n\r\n staleCount(): number {\r\n return this.stale.size;\r\n }\r\n\r\n /** Embed every stale node (one batch), upsert vectors, mark fresh, clear the processed ids.\r\n * Interleave-safe: mutations landing during the embed await are respected — a node\r\n * removed mid-flight is dropped (never resurrected), and a node whose text changed\r\n * stays queued for the next pass instead of being marked fresh for the old text. */\r\n async reindex(): Promise<void> {\r\n // First: drain deferred removals (HOLE 2 fix — removals queued by sync hooks).\r\n for (const id of this.pendingRemoval) await this.vectors.remove(id);\r\n this.pendingRemoval.clear();\r\n\r\n const ids = [...this.stale];\r\n if (ids.length === 0) return;\r\n const completed = new Set<NodeId>();\r\n const items: { id: NodeId; text: string; hash: string }[] = [];\r\n for (const id of ids) {\r\n const node = this.tree.get(id);\r\n if (!node) {\r\n completed.add(id);\r\n continue;\r\n }\r\n // HOLE 1 fix: guard-aware reindex — suppress shards that belong to a typed\r\n // ancestor's embedding unit.\r\n if (this.isSuppressedShard(node)) {\r\n node.meta.embedding = { state: \"none\" };\r\n await this.vectors.remove(id);\r\n completed.add(id);\r\n continue;\r\n }\r\n const value = this.tree.toJson(id);\r\n const typeDef = node.type ? this.registry?.get(node.type) : undefined;\r\n const text = toEmbeddingText(node, value, typeDef);\r\n if (text === null) {\r\n node.meta.embedding = { state: \"none\" };\r\n await this.vectors.remove(id);\r\n completed.add(id);\r\n continue;\r\n }\r\n items.push({ id, text, hash: textHash(text) });\r\n }\r\n if (items.length > 0) {\r\n const embedded = await this.embedding.embed(items.map((it) => it.text));\r\n // Mutations may have landed during the await — re-validate every item\r\n // against the live tree before trusting the embedded batch.\r\n const upserts: { nodeId: NodeId; vector: number[] }[] = [];\r\n for (const [i, it] of items.entries()) {\r\n const node = this.tree.get(it.id);\r\n if (!node) {\r\n await this.vectors.remove(it.id); // removed mid-flight — do not resurrect\r\n completed.add(it.id);\r\n continue;\r\n }\r\n // Positive trust check: mark fresh ONLY if the node still awaits exactly the\r\n // text we embedded. \"none\" (text became null / became a suppressed shard\r\n // mid-flight) means its removal is already queued — complete without\r\n // upserting; any other divergence (new hash) stays queued for the next pass.\r\n // A stale node with NO textHash (a delta-restored node — restoreFromDelta\r\n // marks {state:\"stale\"} without a hash) cannot have been touched during the\r\n // await: any mid-flight change writes a concrete hash (\"stale\"), \"none\", or\r\n // removes the node. So an undefined hash means our pre-await snapshot is\r\n // current — trust the batch.\r\n const cur = node.meta.embedding;\r\n if (cur.state !== \"stale\" || (cur.textHash !== undefined && cur.textHash !== it.hash)) {\r\n if (node.meta.embedding.state === \"none\") completed.add(it.id);\r\n continue;\r\n }\r\n upserts.push({ nodeId: it.id, vector: embedded[i] });\r\n node.meta.embedding = { state: \"fresh\", textHash: it.hash };\r\n completed.add(it.id);\r\n }\r\n if (upserts.length > 0) await this.vectors.upsert(upserts);\r\n }\r\n for (const id of ids) {\r\n if (completed.has(id)) this.stale.delete(id);\r\n }\r\n }\r\n\r\n private snippetOf(value: Json): string {\r\n const s = JSON.stringify(value);\r\n return s.length <= 80 ? s : s.slice(0, 80) + \"…\";\r\n }\r\n\r\n /**\r\n * Semantic search: embed the query, rank indexed nodes by cosine, post-filter by\r\n * under/type/tag, return top-k. `freshness: \"wait\"` flushes the reindexer first;\r\n * default `best-effort` searches what's indexed and reports `staleCount`.\r\n */\r\n async search(queryText: string, opts: SearchOpts = {}): Promise<SearchResult> {\r\n if (opts.freshness === \"wait\") await this.reindex();\r\n const k = opts.k ?? 8;\r\n const [queryVec] = await this.embedding.embed([queryText]);\r\n const ranked = await this.vectors.search(queryVec, await this.vectors.size());\r\n const results: SearchHit[] = [];\r\n for (const hit of ranked) {\r\n if (results.length >= k) break;\r\n // Skip logically-removed entries that haven't been physically flushed yet.\r\n if (this.pendingRemoval.has(hit.nodeId)) continue;\r\n const node = this.tree.get(hit.nodeId);\r\n if (!node) continue;\r\n const path = this.addressing.pathOf(node.id);\r\n if (opts.under !== undefined && !isWithin(path, opts.under)) continue;\r\n if (opts.type !== undefined && node.type !== opts.type) continue;\r\n if (opts.tag !== undefined && !(node.tags?.includes(opts.tag) ?? false)) continue;\r\n results.push({\r\n id: node.id,\r\n path,\r\n type: node.type,\r\n score: hit.score,\r\n snippet: this.snippetOf(this.tree.toJson(node.id)),\r\n });\r\n }\r\n return { results, staleCount: this.staleCount() };\r\n }\r\n}\r\n"],"mappings":"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","names":[]}