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,306 @@
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 NodeNotFoundError = class extends ArborError {
11
+ constructor(ref) {
12
+ super("NODE_NOT_FOUND", `Node not found: ${JSON.stringify(ref)}`);
13
+ this.ref = ref;
14
+ }
15
+ ref;
16
+ };
17
+ var ScopeViolationError = class extends ArborError {
18
+ constructor(targetPath, scope) {
19
+ super("SCOPE_VIOLATION", `Access outside scope: ${targetPath} (scope: ${scope})`);
20
+ this.targetPath = targetPath;
21
+ this.scope = scope;
22
+ }
23
+ targetPath;
24
+ scope;
25
+ };
26
+ var InvalidOpError = class extends ArborError {
27
+ constructor(message) {
28
+ super("INVALID_OP", message);
29
+ }
30
+ };
31
+
32
+ // src/decompose.ts
33
+ function byteSize(value) {
34
+ return Buffer.byteLength(JSON.stringify(value), "utf8");
35
+ }
36
+
37
+ // src/jsonpointer.ts
38
+ function encodeSegment(s) {
39
+ return s.replace(/~/g, "~0").replace(/\//g, "~1");
40
+ }
41
+ function decodeSegment(s) {
42
+ return s.replace(/~1/g, "/").replace(/~0/g, "~");
43
+ }
44
+ function appendPointer(parent, key) {
45
+ return parent + "/" + encodeSegment(String(key));
46
+ }
47
+ function isWithin(path, scope) {
48
+ return scope === void 0 || path === scope || path.startsWith(scope + "/");
49
+ }
50
+ function parsePointer(pointer) {
51
+ if (pointer === "") return [];
52
+ if (!pointer.startsWith("/")) {
53
+ throw new Error(`Invalid JSON Pointer (must be "" or start with "/"): ${pointer}`);
54
+ }
55
+ return pointer.slice(1).split("/").map(decodeSegment);
56
+ }
57
+
58
+ // src/path-glob.ts
59
+ function matchSegments(pattern, path) {
60
+ if (pattern.length === 0) return path.length === 0;
61
+ const [head, ...rest] = pattern;
62
+ if (head === "**") {
63
+ for (let i = 0; i <= path.length; i++) {
64
+ if (matchSegments(rest, path.slice(i))) return true;
65
+ }
66
+ return false;
67
+ }
68
+ if (path.length === 0) return false;
69
+ if (head === "*" || head === path[0]) {
70
+ return matchSegments(rest, path.slice(1));
71
+ }
72
+ return false;
73
+ }
74
+ function compileGlob(pattern) {
75
+ const patternSegs = parsePointer(pattern);
76
+ return (path) => matchSegments(patternSegs, parsePointer(path));
77
+ }
78
+
79
+ // src/navigator.ts
80
+ var DEFAULT_LIMIT = 100;
81
+ var PREVIEW_MAX = 50;
82
+ function previewOf(node) {
83
+ if (node.kind === "leaf") {
84
+ const s = JSON.stringify(node.content);
85
+ return s.length <= PREVIEW_MAX ? s : s.slice(0, PREVIEW_MAX) + "\u2026";
86
+ }
87
+ return node.kind === "array" ? `[${node.childIds.length} items]` : `{${node.childIds.length} keys}`;
88
+ }
89
+ var Navigator = class {
90
+ constructor(tree, addressing) {
91
+ this.tree = tree;
92
+ this.addressing = addressing;
93
+ }
94
+ tree;
95
+ addressing;
96
+ resolve(ref) {
97
+ const node = "id" in ref ? this.addressing.byId(ref.id) : this.addressing.byPath(ref.path);
98
+ if (!node) throw new NodeNotFoundError(ref);
99
+ return node;
100
+ }
101
+ summarize(node) {
102
+ return {
103
+ id: node.id,
104
+ key: node.key,
105
+ kind: node.kind,
106
+ type: node.type,
107
+ hasChildren: node.childIds.length > 0,
108
+ size: node.kind === "leaf" ? byteSize(node.content) : node.childIds.length,
109
+ preview: previewOf(node)
110
+ };
111
+ }
112
+ describe(ref = { path: "" }, opts = {}) {
113
+ const node = this.resolve(ref);
114
+ const all = this.tree.children(node.id);
115
+ const total = all.length;
116
+ const offset = opts.offset ?? 0;
117
+ const limit = opts.limit ?? DEFAULT_LIMIT;
118
+ const page = all.slice(offset, offset + limit);
119
+ const result = {
120
+ node: {
121
+ id: node.id,
122
+ path: this.addressing.pathOf(node.id),
123
+ key: node.key,
124
+ kind: node.kind,
125
+ type: node.type
126
+ },
127
+ children: page.map((c) => this.summarize(c))
128
+ };
129
+ const nextOffset = offset + page.length;
130
+ if (nextOffset < total) {
131
+ result.truncated = { shown: page.length, total, nextOffset };
132
+ }
133
+ return result;
134
+ }
135
+ get(ref, opts = {}) {
136
+ const node = this.resolve(ref);
137
+ const maxDepth = opts.maxDepth;
138
+ let truncated = false;
139
+ const reconstruct = (id, depth) => {
140
+ const n = this.tree.get(id);
141
+ if (n.kind === "leaf") return n.content;
142
+ if (maxDepth !== void 0 && depth >= maxDepth) {
143
+ truncated = true;
144
+ const label = n.kind === "array" ? `${n.childIds.length} items` : `${n.childIds.length} keys`;
145
+ return `[truncated: ${label}]`;
146
+ }
147
+ if (n.kind === "array") return n.childIds.map((cid) => reconstruct(cid, depth + 1));
148
+ const obj = {};
149
+ for (const cid of n.childIds) {
150
+ const c = this.tree.get(cid);
151
+ obj[String(c.key)] = reconstruct(cid, depth + 1);
152
+ }
153
+ return obj;
154
+ };
155
+ const content = reconstruct(node.id, 0);
156
+ const result = {
157
+ id: node.id,
158
+ path: this.addressing.pathOf(node.id),
159
+ type: node.type,
160
+ content,
161
+ meta: node.meta
162
+ };
163
+ if (truncated) result.truncated = true;
164
+ return result;
165
+ }
166
+ matches(node, sel, path, glob) {
167
+ if (sel.type !== void 0 && node.type !== sel.type) return false;
168
+ if (sel.tag !== void 0 && !(node.tags?.includes(sel.tag) ?? false)) return false;
169
+ if (glob !== void 0 && !glob(path)) return false;
170
+ return true;
171
+ }
172
+ /** Find nodes matching ALL provided selector fields (exact `type`, `tag` membership, glob `pathPattern`). */
173
+ find(selector, opts = {}) {
174
+ const limit = opts.limit ?? DEFAULT_LIMIT;
175
+ const within = opts.within;
176
+ const glob = selector.pathPattern !== void 0 ? compileGlob(selector.pathPattern) : void 0;
177
+ const hits = [];
178
+ let truncated = false;
179
+ const visit = (id, path) => {
180
+ if (hits.length >= limit) {
181
+ truncated = true;
182
+ return;
183
+ }
184
+ const node = this.tree.get(id);
185
+ if (this.matches(node, selector, path, glob)) {
186
+ if (isWithin(path, within)) {
187
+ hits.push({ id: node.id, path, type: node.type });
188
+ }
189
+ }
190
+ for (const cid of node.childIds) {
191
+ if (hits.length >= limit) {
192
+ truncated = true;
193
+ break;
194
+ }
195
+ const child = this.tree.get(cid);
196
+ visit(cid, appendPointer(path, child.key));
197
+ }
198
+ };
199
+ visit(this.tree.rootIdValue(), "");
200
+ return { hits, truncated };
201
+ }
202
+ };
203
+
204
+ // src/toolset.ts
205
+ function eventWithinScope(e, scope) {
206
+ if (e.path !== void 0 && isWithin(e.path, scope)) return true;
207
+ if (e.toPath !== void 0 && isWithin(e.toPath, scope)) return true;
208
+ if (e.fromPath !== void 0 && isWithin(e.fromPath, scope)) return true;
209
+ return false;
210
+ }
211
+ async function run(fn) {
212
+ try {
213
+ return { ok: true, value: await fn() };
214
+ } catch (e) {
215
+ if (e instanceof ArborError) return { ok: false, error: { code: e.code, message: e.message } };
216
+ const message = e instanceof Error ? e.message : String(e);
217
+ return { ok: false, error: { code: "ERROR", message } };
218
+ }
219
+ }
220
+ function makeToolset(deps, binding = {}) {
221
+ const { tree, addressing } = deps;
222
+ const navigator = new Navigator(tree, addressing);
223
+ return {
224
+ describe: (ref, opts) => run(() => {
225
+ const target = ref ?? (binding.readScope !== void 0 ? { path: binding.readScope } : { path: "" });
226
+ const r = navigator.describe(target, opts);
227
+ if (!isWithin(r.node.path, binding.readScope)) {
228
+ throw new ScopeViolationError(r.node.path, binding.readScope);
229
+ }
230
+ return r;
231
+ }),
232
+ get: (ref, opts) => run(() => {
233
+ const r = navigator.get(ref, opts);
234
+ if (!isWithin(r.path, binding.readScope)) {
235
+ throw new ScopeViolationError(r.path, binding.readScope);
236
+ }
237
+ return structuredClone(r);
238
+ }),
239
+ find: (selector, opts) => run(() => {
240
+ if (binding.readScope !== void 0 && opts?.within !== void 0 && !isWithin(opts.within, binding.readScope)) {
241
+ throw new ScopeViolationError(opts.within, binding.readScope);
242
+ }
243
+ return navigator.find(selector, { ...opts, within: opts?.within ?? binding.readScope });
244
+ }),
245
+ search: (query, opts = {}) => run(async () => {
246
+ if (!deps.index) throw new InvalidOpError("no semantic index configured for this toolset");
247
+ if (binding.readScope !== void 0 && opts.under !== void 0 && !isWithin(opts.under, binding.readScope)) {
248
+ throw new ScopeViolationError(opts.under, binding.readScope);
249
+ }
250
+ const under = opts.under ?? binding.readScope;
251
+ return deps.index.search(query, { ...opts, under });
252
+ }),
253
+ patch: (ref, op) => run(() => {
254
+ const common = { owner: binding.owner, writeScope: binding.writeScope, ifVersion: op.ifVersion };
255
+ const resolve = (r) => {
256
+ const node = "id" in r ? addressing.byId(r.id) : addressing.byPath(r.path);
257
+ if (!node) throw new NodeNotFoundError(r);
258
+ return node;
259
+ };
260
+ switch (op.op) {
261
+ case "set": {
262
+ deps.mutator.set(ref, op.value, common);
263
+ const node = resolve(ref);
264
+ return { id: node.id, path: addressing.pathOf(node.id), version: node.meta.version };
265
+ }
266
+ case "insert": {
267
+ const id = deps.mutator.insert(ref, op.key, op.value, common);
268
+ const node = tree.get(id);
269
+ return { id, path: addressing.pathOf(id), version: node.meta.version };
270
+ }
271
+ case "remove": {
272
+ const node = resolve(ref);
273
+ const removed = { id: node.id, path: addressing.pathOf(node.id) };
274
+ const parentId = node.parentId;
275
+ deps.mutator.remove(ref, common);
276
+ const parent = parentId !== null ? tree.get(parentId) : void 0;
277
+ return { id: removed.id, path: removed.path, version: parent?.meta.version ?? 0 };
278
+ }
279
+ case "move": {
280
+ const node = resolve(ref);
281
+ deps.mutator.move(ref, op.to, op.key, common);
282
+ return { id: node.id, path: addressing.pathOf(node.id), version: node.meta.version };
283
+ }
284
+ }
285
+ }),
286
+ history: (ref, opts = {}) => run(() => {
287
+ let events = [...deps.log.entries()];
288
+ if (ref !== void 0) {
289
+ const node = "id" in ref ? addressing.byId(ref.id) : addressing.byPath(ref.path);
290
+ if (!node) throw new NodeNotFoundError(ref);
291
+ const path = addressing.pathOf(node.id);
292
+ if (!isWithin(path, binding.readScope)) throw new ScopeViolationError(path, binding.readScope);
293
+ const id = node.id;
294
+ events = events.filter((e) => e.targetId === id);
295
+ } else if (binding.readScope !== void 0) {
296
+ const scope = binding.readScope;
297
+ events = events.filter((e) => eventWithinScope(e, scope));
298
+ }
299
+ return structuredClone(opts.limit !== void 0 ? events.slice(-opts.limit) : events);
300
+ })
301
+ };
302
+ }
303
+ export {
304
+ makeToolset
305
+ };
306
+ //# sourceMappingURL=toolset.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/errors.ts","../src/decompose.ts","../src/jsonpointer.ts","../src/path-glob.ts","../src/navigator.ts","../src/toolset.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","import type { Json, NodeKind } from \"./types\";\r\n\r\n/** Policy deciding whether a value is stored whole (opaque leaf) or split into child nodes. */\r\nexport interface DecomposeDecision {\r\n /** `type` is the optional registered node type (used by the by-type override in a later milestone). */\r\n isOpaque(value: Json, type?: string): boolean;\r\n}\r\n\r\n/** UTF-8 byte length of the JSON serialization of a value. */\r\nexport function byteSize(value: Json): number {\r\n return Buffer.byteLength(JSON.stringify(value), \"utf8\");\r\n}\r\n\r\n/** Structural kind of a value given whether it is being stored opaquely. */\r\nexport function kindOf(value: Json, opaque: boolean): NodeKind {\r\n if (opaque) return \"leaf\";\r\n return Array.isArray(value) ? \"array\" : \"object\";\r\n}\r\n\r\n/**\r\n * Default policy: scalars are always opaque leaves; containers stay opaque\r\n * while their serialized size is within `maxOpaqueBytes`, otherwise they split.\r\n */\r\nexport function sizeBasedDecision(maxOpaqueBytes: number): DecomposeDecision {\r\n return {\r\n isOpaque(value: Json): boolean {\r\n if (value === null || typeof value !== \"object\") return true;\r\n return byteSize(value) <= maxOpaqueBytes;\r\n },\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 { parsePointer } from \"./jsonpointer\";\r\n\r\n/** Match parsed segment arrays. `*` = one segment; `**` = zero or more segments. */\r\nfunction matchSegments(pattern: string[], path: string[]): boolean {\r\n if (pattern.length === 0) return path.length === 0;\r\n const [head, ...rest] = pattern;\r\n if (head === \"**\") {\r\n for (let i = 0; i <= path.length; i++) {\r\n if (matchSegments(rest, path.slice(i))) return true;\r\n }\r\n return false;\r\n }\r\n if (path.length === 0) return false;\r\n if (head === \"*\" || head === path[0]) {\r\n return matchSegments(rest, path.slice(1));\r\n }\r\n return false;\r\n}\r\n\r\n/**\r\n * Parse a glob `pattern` ONCE and return a reusable matcher over JSON Pointer paths.\r\n * `*` matches exactly one path segment; `**` matches zero or more segments.\r\n */\r\nexport function compileGlob(pattern: string): (path: string) => boolean {\r\n const patternSegs = parsePointer(pattern);\r\n return (path: string) => matchSegments(patternSegs, parsePointer(path));\r\n}\r\n\r\n/**\r\n * Glob-match a JSON Pointer `path` against a `pattern`.\r\n * `*` matches exactly one path segment; `**` matches zero or more segments.\r\n * One-shot convenience over `compileGlob` — prefer compiling when matching many paths.\r\n */\r\nexport function matchGlob(pattern: string, path: string): boolean {\r\n return compileGlob(pattern)(path);\r\n}\r\n","import type { ArbNode, Json, NodeId, NodeKind, NodeMeta } from \"./types\";\r\nimport type { ArtifactTree } from \"./artifact-tree\";\r\nimport type { Addressing } from \"./addressing\";\r\nimport { type Ref, NodeNotFoundError } from \"./errors\";\r\nimport { byteSize } from \"./decompose\";\r\nimport { compileGlob } from \"./path-glob\";\r\nimport { isWithin, appendPointer } from \"./jsonpointer\";\r\n\r\nconst DEFAULT_LIMIT = 100;\r\nconst PREVIEW_MAX = 50;\r\n\r\nexport interface NodeSummary {\r\n id: NodeId;\r\n path: string;\r\n key: string | number | null;\r\n kind: NodeKind;\r\n type?: string;\r\n}\r\n\r\nexport interface ChildSummary {\r\n id: NodeId;\r\n key: string | number | null;\r\n kind: NodeKind;\r\n type?: string;\r\n hasChildren: boolean;\r\n size: number;\r\n preview: string;\r\n}\r\n\r\nexport interface DescribeOpts {\r\n offset?: number;\r\n limit?: number;\r\n}\r\n\r\nexport interface DescribeResult {\r\n node: NodeSummary;\r\n children: ChildSummary[];\r\n truncated?: { shown: number; total: number; nextOffset: number };\r\n}\r\n\r\nexport interface GetOpts {\r\n maxDepth?: number;\r\n}\r\n\r\nexport interface GetResult {\r\n id: NodeId;\r\n path: string;\r\n type?: string;\r\n content: Json;\r\n meta: NodeMeta;\r\n truncated?: boolean;\r\n}\r\n\r\nexport interface FindSelector {\r\n type?: string;\r\n tag?: string;\r\n pathPattern?: string;\r\n}\r\n\r\nexport interface FindOpts {\r\n limit?: number;\r\n /** JSON Pointer prefix: only nodes at/under it are hits — checked BEFORE the\r\n * limit is consumed, so out-of-scope matches never eat the budget. */\r\n within?: string;\r\n}\r\n\r\nexport interface FindHit {\r\n id: NodeId;\r\n path: string;\r\n type?: string;\r\n}\r\n\r\nexport interface FindResult {\r\n hits: FindHit[];\r\n /** True when the walk stopped at `limit` — there MAY be more matches (an exact-fit\r\n * final hit also reports true). */\r\n truncated: boolean;\r\n}\r\n\r\nfunction previewOf(node: ArbNode): string {\r\n if (node.kind === \"leaf\") {\r\n const s = JSON.stringify(node.content);\r\n return s.length <= PREVIEW_MAX ? s : s.slice(0, PREVIEW_MAX) + \"…\";\r\n }\r\n return node.kind === \"array\" ? `[${node.childIds.length} items]` : `{${node.childIds.length} keys}`;\r\n}\r\n\r\n/** Read-only navigation over the artifact tree: describe (cheap listing) and get (bounded content). */\r\nexport class Navigator {\r\n constructor(\r\n private readonly tree: ArtifactTree,\r\n private readonly addressing: Addressing,\r\n ) {}\r\n\r\n protected resolve(ref: Ref): ArbNode {\r\n const node = \"id\" in ref ? this.addressing.byId(ref.id) : this.addressing.byPath(ref.path);\r\n if (!node) throw new NodeNotFoundError(ref);\r\n return node;\r\n }\r\n\r\n private summarize(node: ArbNode): ChildSummary {\r\n return {\r\n id: node.id,\r\n key: node.key,\r\n kind: node.kind,\r\n type: node.type,\r\n hasChildren: node.childIds.length > 0,\r\n size: node.kind === \"leaf\" ? byteSize(node.content) : node.childIds.length,\r\n preview: previewOf(node),\r\n };\r\n }\r\n\r\n describe(ref: Ref = { path: \"\" }, opts: DescribeOpts = {}): DescribeResult {\r\n const node = this.resolve(ref);\r\n const all = this.tree.children(node.id);\r\n const total = all.length;\r\n const offset = opts.offset ?? 0;\r\n const limit = opts.limit ?? DEFAULT_LIMIT;\r\n const page = all.slice(offset, offset + limit);\r\n const result: DescribeResult = {\r\n node: {\r\n id: node.id,\r\n path: this.addressing.pathOf(node.id),\r\n key: node.key,\r\n kind: node.kind,\r\n type: node.type,\r\n },\r\n children: page.map((c) => this.summarize(c)),\r\n };\r\n const nextOffset = offset + page.length;\r\n if (nextOffset < total) {\r\n result.truncated = { shown: page.length, total, nextOffset };\r\n }\r\n return result;\r\n }\r\n\r\n get(ref: Ref, opts: GetOpts = {}): GetResult {\r\n const node = this.resolve(ref);\r\n const maxDepth = opts.maxDepth;\r\n let truncated = false;\r\n const reconstruct = (id: NodeId, depth: number): Json => {\r\n const n = this.tree.get(id)!;\r\n if (n.kind === \"leaf\") return n.content;\r\n if (maxDepth !== undefined && depth >= maxDepth) {\r\n truncated = true;\r\n const label = n.kind === \"array\" ? `${n.childIds.length} items` : `${n.childIds.length} keys`;\r\n return `[truncated: ${label}]`;\r\n }\r\n if (n.kind === \"array\") return n.childIds.map((cid) => reconstruct(cid, depth + 1));\r\n const obj: Record<string, Json> = {};\r\n for (const cid of n.childIds) {\r\n const c = this.tree.get(cid)!;\r\n obj[String(c.key)] = reconstruct(cid, depth + 1);\r\n }\r\n return obj;\r\n };\r\n const content = reconstruct(node.id, 0);\r\n const result: GetResult = {\r\n id: node.id,\r\n path: this.addressing.pathOf(node.id),\r\n type: node.type,\r\n content,\r\n meta: node.meta,\r\n };\r\n if (truncated) result.truncated = true;\r\n return result;\r\n }\r\n\r\n private matches(node: ArbNode, sel: FindSelector, path: string, glob?: (path: string) => boolean): boolean {\r\n if (sel.type !== undefined && node.type !== sel.type) return false;\r\n if (sel.tag !== undefined && !(node.tags?.includes(sel.tag) ?? false)) return false;\r\n if (glob !== undefined && !glob(path)) return false;\r\n return true;\r\n }\r\n\r\n /** Find nodes matching ALL provided selector fields (exact `type`, `tag` membership, glob `pathPattern`). */\r\n find(selector: FindSelector, opts: FindOpts = {}): FindResult {\r\n const limit = opts.limit ?? DEFAULT_LIMIT;\r\n const within = opts.within;\r\n // The pattern is parsed ONCE per find; each node's path is built incrementally\r\n // down the DFS (no per-node pathOf walk to the root).\r\n const glob = selector.pathPattern !== undefined ? compileGlob(selector.pathPattern) : undefined;\r\n const hits: FindHit[] = [];\r\n let truncated = false;\r\n const visit = (id: NodeId, path: string): void => {\r\n if (hits.length >= limit) {\r\n truncated = true;\r\n return;\r\n }\r\n const node = this.tree.get(id)!;\r\n if (this.matches(node, selector, path, glob)) {\r\n if (isWithin(path, within)) {\r\n hits.push({ id: node.id, path, type: node.type });\r\n }\r\n }\r\n for (const cid of node.childIds) {\r\n if (hits.length >= limit) {\r\n truncated = true;\r\n break;\r\n }\r\n const child = this.tree.get(cid)!;\r\n visit(cid, appendPointer(path, child.key as string | number));\r\n }\r\n };\r\n visit(this.tree.rootIdValue(), \"\");\r\n return { hits, truncated };\r\n }\r\n}\r\n","import 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 { SemanticIndex } from \"./semantic-index\";\r\nimport {\r\n Navigator,\r\n type DescribeOpts,\r\n type DescribeResult,\r\n type GetOpts,\r\n type GetResult,\r\n type FindSelector,\r\n type FindOpts,\r\n type FindResult,\r\n} from \"./navigator\";\r\nimport type { SearchOpts, SearchResult } from \"./semantic-index\";\r\nimport { type Ref, ArborError, ScopeViolationError, InvalidOpError, NodeNotFoundError } from \"./errors\";\r\nimport { isWithin } from \"./jsonpointer\";\r\nimport type { Json, NodeId } from \"./types\";\r\n\r\n/** Every toolset call returns this — errors are structured, never thrown across the agent boundary. */\r\nexport type ToolResult<T> = { ok: true; value: T } | { ok: false; error: { code: string; message: string } };\r\n\r\nexport type PatchOp =\r\n | { op: \"set\"; value: Json; ifVersion?: number }\r\n | { op: \"insert\"; key: string | number; value: Json; ifVersion?: number }\r\n | { op: \"remove\"; ifVersion?: number }\r\n | { op: \"move\"; to: Ref; key: string | number; ifVersion?: number };\r\n\r\nexport interface ToolsetDeps {\r\n tree: ArtifactTree;\r\n addressing: Addressing;\r\n log: EventLog;\r\n mutator: Mutator;\r\n index?: SemanticIndex;\r\n}\r\n\r\nexport interface ToolsetBinding {\r\n owner?: string;\r\n /** JSON Pointer prefix: writes must be at or under it (enforced by the Mutator). */\r\n writeScope?: string;\r\n /** JSON Pointer prefix: reads are confined to it. */\r\n readScope?: string;\r\n}\r\n\r\nexport interface PatchResult {\r\n id: NodeId;\r\n path: string;\r\n /** The affected node's version AFTER the op (remove: the parent's). Feed into the next ifVersion. */\r\n version: number;\r\n}\r\n\r\nexport interface Toolset {\r\n describe(ref?: Ref, opts?: DescribeOpts): Promise<ToolResult<DescribeResult>>;\r\n get(ref: Ref, opts?: GetOpts): Promise<ToolResult<GetResult>>;\r\n find(selector: FindSelector, opts?: FindOpts): Promise<ToolResult<FindResult>>;\r\n search(query: string, opts?: SearchOpts): Promise<ToolResult<SearchResult>>;\r\n patch(ref: Ref, op: PatchOp): Promise<ToolResult<PatchResult>>;\r\n history(ref?: Ref, opts?: { limit?: number }): Promise<ToolResult<MutationEvent[]>>;\r\n}\r\n\r\n/** An event is within scope if any of its recorded paths is within scope. */\r\nfunction eventWithinScope(e: MutationEvent, scope: string): boolean {\r\n if (e.path !== undefined && isWithin(e.path, scope)) return true;\r\n if (e.toPath !== undefined && isWithin(e.toPath, scope)) return true;\r\n if (e.fromPath !== undefined && isWithin(e.fromPath, scope)) return true;\r\n return false;\r\n}\r\n\r\n/** Run a tool body, converting thrown errors into a structured ToolResult. */\r\nasync function run<T>(fn: () => T | Promise<T>): Promise<ToolResult<T>> {\r\n try {\r\n return { ok: true, value: await fn() };\r\n } catch (e) {\r\n if (e instanceof ArborError) return { ok: false, error: { code: e.code, message: e.message } };\r\n const message = e instanceof Error ? e.message : String(e);\r\n return { ok: false, error: { code: \"ERROR\", message } };\r\n }\r\n}\r\n\r\n/** A scoped, agent-facing bundle of tools returning structured results. */\r\nexport function makeToolset(deps: ToolsetDeps, binding: ToolsetBinding = {}): Toolset {\r\n const { tree, addressing } = deps;\r\n const navigator = new Navigator(tree, addressing);\r\n\r\n return {\r\n describe: (ref, opts) =>\r\n run(() => {\r\n const target: Ref = ref ?? (binding.readScope !== undefined ? { path: binding.readScope } : { path: \"\" });\r\n const r = navigator.describe(target, opts);\r\n if (!isWithin(r.node.path, binding.readScope)) {\r\n throw new ScopeViolationError(r.node.path, binding.readScope!);\r\n }\r\n return r;\r\n }),\r\n\r\n get: (ref, opts) =>\r\n run(() => {\r\n const r = navigator.get(ref, opts);\r\n if (!isWithin(r.path, binding.readScope)) {\r\n throw new ScopeViolationError(r.path, binding.readScope!);\r\n }\r\n // Deep-clone the WHOLE result: `content` (and `meta`) come from the live\r\n // tree by reference; handing them across the agent boundary would let a\r\n // caller mutate the artifact without an event, bypassing write-scope.\r\n return structuredClone(r);\r\n }),\r\n\r\n find: (selector, opts) =>\r\n run(() => {\r\n if (binding.readScope !== undefined && opts?.within !== undefined && !isWithin(opts.within, binding.readScope)) {\r\n throw new ScopeViolationError(opts.within, binding.readScope);\r\n }\r\n return navigator.find(selector, { ...opts, within: opts?.within ?? binding.readScope });\r\n }),\r\n\r\n search: (query, opts = {}) =>\r\n run(async () => {\r\n if (!deps.index) throw new InvalidOpError(\"no semantic index configured for this toolset\");\r\n if (binding.readScope !== undefined && opts.under !== undefined && !isWithin(opts.under, binding.readScope)) {\r\n throw new ScopeViolationError(opts.under, binding.readScope);\r\n }\r\n const under = opts.under ?? binding.readScope;\r\n return deps.index.search(query, { ...opts, under });\r\n }),\r\n\r\n patch: (ref, op) =>\r\n run<PatchResult>(() => {\r\n const common = { owner: binding.owner, writeScope: binding.writeScope, ifVersion: op.ifVersion };\r\n const resolve = (r: Ref) => {\r\n const node = \"id\" in r ? addressing.byId(r.id) : addressing.byPath(r.path);\r\n if (!node) throw new NodeNotFoundError(r);\r\n return node;\r\n };\r\n switch (op.op) {\r\n case \"set\": {\r\n deps.mutator.set(ref, op.value, common);\r\n const node = resolve(ref);\r\n return { id: node.id, path: addressing.pathOf(node.id), version: node.meta.version };\r\n }\r\n case \"insert\": {\r\n const id = deps.mutator.insert(ref, op.key, op.value, common);\r\n const node = tree.get(id)!;\r\n return { id, path: addressing.pathOf(id), version: node.meta.version };\r\n }\r\n case \"remove\": {\r\n const node = resolve(ref);\r\n const removed = { id: node.id, path: addressing.pathOf(node.id) };\r\n const parentId = node.parentId;\r\n deps.mutator.remove(ref, common);\r\n const parent = parentId !== null ? tree.get(parentId) : undefined;\r\n return { id: removed.id, path: removed.path, version: parent?.meta.version ?? 0 };\r\n }\r\n case \"move\": {\r\n const node = resolve(ref);\r\n deps.mutator.move(ref, op.to, op.key, common);\r\n return { id: node.id, path: addressing.pathOf(node.id), version: node.meta.version };\r\n }\r\n }\r\n }),\r\n\r\n history: (ref, opts = {}) =>\r\n run(() => {\r\n let events = [...deps.log.entries()];\r\n if (ref !== undefined) {\r\n const node = \"id\" in ref ? addressing.byId(ref.id) : addressing.byPath(ref.path);\r\n if (!node) throw new NodeNotFoundError(ref);\r\n const path = addressing.pathOf(node.id);\r\n if (!isWithin(path, binding.readScope)) throw new ScopeViolationError(path, binding.readScope!);\r\n const id = node.id;\r\n events = events.filter((e) => e.targetId === id);\r\n } else if (binding.readScope !== undefined) {\r\n const scope = binding.readScope;\r\n events = events.filter((e) => eventWithinScope(e, scope));\r\n }\r\n return structuredClone(opts.limit !== undefined ? events.slice(-opts.limit) : events);\r\n }),\r\n };\r\n}\r\n"],"mappings":"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","names":[]}
@@ -0,0 +1,8 @@
1
+ import { DecomposeDecision } from './decompose.js';
2
+ import { TypeRegistry } from './type-registry.js';
3
+ import './types.js';
4
+
5
+ /** Wrap a base decision so a node's registered type can override the size heuristic. */
6
+ declare function typeAwareDecision(base: DecomposeDecision, registry: TypeRegistry): DecomposeDecision;
7
+
8
+ export { typeAwareDecision };
@@ -0,0 +1,17 @@
1
+ // src/type-aware-decision.ts
2
+ function typeAwareDecision(base, registry) {
3
+ return {
4
+ isOpaque(value, type) {
5
+ if (type !== void 0) {
6
+ const override = registry.get(type)?.decompose;
7
+ if (override === "opaque") return true;
8
+ if (override === "children") return false;
9
+ }
10
+ return base.isOpaque(value, type);
11
+ }
12
+ };
13
+ }
14
+ export {
15
+ typeAwareDecision
16
+ };
17
+ //# sourceMappingURL=type-aware-decision.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/type-aware-decision.ts"],"sourcesContent":["import type { Json } from \"./types\";\r\nimport type { DecomposeDecision } from \"./decompose\";\r\nimport type { TypeRegistry } from \"./type-registry\";\r\n\r\n/** Wrap a base decision so a node's registered type can override the size heuristic. */\r\nexport function typeAwareDecision(base: DecomposeDecision, registry: TypeRegistry): DecomposeDecision {\r\n return {\r\n isOpaque(value: Json, type?: string): boolean {\r\n if (type !== undefined) {\r\n const override = registry.get(type)?.decompose;\r\n if (override === \"opaque\") return true;\r\n if (override === \"children\") return false;\r\n }\r\n return base.isOpaque(value, type);\r\n },\r\n };\r\n}\r\n"],"mappings":";AAKO,SAAS,kBAAkB,MAAyB,UAA2C;AACpG,SAAO;AAAA,IACL,SAAS,OAAa,MAAwB;AAC5C,UAAI,SAAS,QAAW;AACtB,cAAM,WAAW,SAAS,IAAI,IAAI,GAAG;AACrC,YAAI,aAAa,SAAU,QAAO;AAClC,YAAI,aAAa,WAAY,QAAO;AAAA,MACtC;AACA,aAAO,KAAK,SAAS,OAAO,IAAI;AAAA,IAClC;AAAA,EACF;AACF;","names":[]}
@@ -0,0 +1,20 @@
1
+ import { Json } from './types.js';
2
+
3
+ /** Definition of a registered node type. */
4
+ interface TypeDef {
5
+ /** Validate a value about to be stored at a node of this type. Throw to reject. */
6
+ validate?: (value: Json) => void;
7
+ /** Override the size-based decomposition for nodes of this type. */
8
+ decompose?: "opaque" | "children";
9
+ /** Override the text used to embed nodes of this type. Return null to skip embedding. */
10
+ embedText?: (value: Json) => string | null;
11
+ }
12
+ /** A registry of named node types (validator + decompose override). */
13
+ declare class TypeRegistry {
14
+ private readonly types;
15
+ register(name: string, def: TypeDef): void;
16
+ get(name: string): TypeDef | undefined;
17
+ has(name: string): boolean;
18
+ }
19
+
20
+ export { type TypeDef, TypeRegistry };
@@ -0,0 +1,17 @@
1
+ // src/type-registry.ts
2
+ var TypeRegistry = class {
3
+ types = /* @__PURE__ */ new Map();
4
+ register(name, def) {
5
+ this.types.set(name, def);
6
+ }
7
+ get(name) {
8
+ return this.types.get(name);
9
+ }
10
+ has(name) {
11
+ return this.types.has(name);
12
+ }
13
+ };
14
+ export {
15
+ TypeRegistry
16
+ };
17
+ //# sourceMappingURL=type-registry.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/type-registry.ts"],"sourcesContent":["import type { Json } from \"./types\";\r\n\r\n/** Definition of a registered node type. */\r\nexport interface TypeDef {\r\n /** Validate a value about to be stored at a node of this type. Throw to reject. */\r\n validate?: (value: Json) => void;\r\n /** Override the size-based decomposition for nodes of this type. */\r\n decompose?: \"opaque\" | \"children\";\r\n /** Override the text used to embed nodes of this type. Return null to skip embedding. */\r\n embedText?: (value: Json) => string | null;\r\n}\r\n\r\n/** A registry of named node types (validator + decompose override). */\r\nexport class TypeRegistry {\r\n private readonly types = new Map<string, TypeDef>();\r\n\r\n register(name: string, def: TypeDef): void {\r\n this.types.set(name, def);\r\n }\r\n\r\n get(name: string): TypeDef | undefined {\r\n return this.types.get(name);\r\n }\r\n\r\n has(name: string): boolean {\r\n return this.types.has(name);\r\n }\r\n}\r\n"],"mappings":";AAaO,IAAM,eAAN,MAAmB;AAAA,EACP,QAAQ,oBAAI,IAAqB;AAAA,EAElD,SAAS,MAAc,KAAoB;AACzC,SAAK,MAAM,IAAI,MAAM,GAAG;AAAA,EAC1B;AAAA,EAEA,IAAI,MAAmC;AACrC,WAAO,KAAK,MAAM,IAAI,IAAI;AAAA,EAC5B;AAAA,EAEA,IAAI,MAAuB;AACzB,WAAO,KAAK,MAAM,IAAI,IAAI;AAAA,EAC5B;AACF;","names":[]}
@@ -0,0 +1,28 @@
1
+ type Json = null | boolean | number | string | Json[] | {
2
+ [key: string]: Json;
3
+ };
4
+ type NodeId = string;
5
+ type NodeKind = "object" | "array" | "leaf";
6
+ interface NodeMeta {
7
+ version: number;
8
+ updatedAt: number;
9
+ owner?: string;
10
+ embedding: {
11
+ state: "fresh" | "stale" | "none";
12
+ textHash?: string;
13
+ vecRef?: string;
14
+ };
15
+ }
16
+ interface ArbNode {
17
+ id: NodeId;
18
+ parentId: NodeId | null;
19
+ key: string | number | null;
20
+ kind: NodeKind;
21
+ content: Json | null;
22
+ childIds: NodeId[];
23
+ tags?: string[];
24
+ type?: string;
25
+ meta: NodeMeta;
26
+ }
27
+
28
+ export type { ArbNode, Json, NodeId, NodeKind, NodeMeta };
package/dist/types.js ADDED
@@ -0,0 +1 @@
1
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
@@ -0,0 +1,34 @@
1
+ import { NodeId } from './types.js';
2
+
3
+ interface VectorIndexEntry {
4
+ nodeId: NodeId;
5
+ vector: number[];
6
+ }
7
+ interface VectorHit {
8
+ nodeId: NodeId;
9
+ score: number;
10
+ }
11
+ /** Stores per-node vectors and ranks by similarity. Async so DB-backed adapters
12
+ * (pgvector, sqlite-vec) can implement it; the in-memory default is sync inside. */
13
+ interface VectorIndexPort {
14
+ upsert(entries: VectorIndexEntry[]): Promise<void>;
15
+ remove(nodeId: NodeId): Promise<void>;
16
+ search(query: number[], k: number): Promise<VectorHit[]>;
17
+ has(nodeId: NodeId): Promise<boolean>;
18
+ size(): Promise<number>;
19
+ entries(): Promise<VectorIndexEntry[]>;
20
+ }
21
+ /** In-memory brute-force index. Vectors are stored as Float32Arrays (raw for
22
+ * `entries()`, unit-normalized for search) so `search` is a plain dot product
23
+ * — the cosine of two normalized vectors. Correct and simple at current scale. */
24
+ declare class MemoryVectorIndex implements VectorIndexPort {
25
+ private readonly vectors;
26
+ upsert(entries: VectorIndexEntry[]): Promise<void>;
27
+ remove(nodeId: NodeId): Promise<void>;
28
+ has(nodeId: NodeId): Promise<boolean>;
29
+ size(): Promise<number>;
30
+ search(query: number[], k: number): Promise<VectorHit[]>;
31
+ entries(): Promise<VectorIndexEntry[]>;
32
+ }
33
+
34
+ export { MemoryVectorIndex, type VectorHit, type VectorIndexEntry, type VectorIndexPort };
@@ -0,0 +1,49 @@
1
+ // src/vector-index-port.ts
2
+ function normalize(v) {
3
+ const out = new Float32Array(v.length);
4
+ let n = 0;
5
+ for (let i = 0; i < v.length; i++) n += v[i] * v[i];
6
+ if (n === 0) return out;
7
+ const inv = 1 / Math.sqrt(n);
8
+ for (let i = 0; i < v.length; i++) out[i] = v[i] * inv;
9
+ return out;
10
+ }
11
+ function dot(a, b) {
12
+ const len = Math.min(a.length, b.length);
13
+ let d = 0;
14
+ for (let i = 0; i < len; i++) d += a[i] * b[i];
15
+ return d;
16
+ }
17
+ var MemoryVectorIndex = class {
18
+ vectors = /* @__PURE__ */ new Map();
19
+ async upsert(entries) {
20
+ for (const e of entries) {
21
+ this.vectors.set(e.nodeId, { raw: Float32Array.from(e.vector), unit: normalize(e.vector) });
22
+ }
23
+ }
24
+ async remove(nodeId) {
25
+ this.vectors.delete(nodeId);
26
+ }
27
+ async has(nodeId) {
28
+ return this.vectors.has(nodeId);
29
+ }
30
+ async size() {
31
+ return this.vectors.size;
32
+ }
33
+ async search(query, k) {
34
+ const q = normalize(query);
35
+ const hits = [];
36
+ for (const [nodeId, { unit }] of this.vectors) {
37
+ hits.push({ nodeId, score: dot(q, unit) });
38
+ }
39
+ hits.sort((a, b) => b.score - a.score);
40
+ return hits.slice(0, k);
41
+ }
42
+ async entries() {
43
+ return [...this.vectors].map(([nodeId, { raw }]) => ({ nodeId, vector: Array.from(raw) }));
44
+ }
45
+ };
46
+ export {
47
+ MemoryVectorIndex
48
+ };
49
+ //# sourceMappingURL=vector-index-port.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/vector-index-port.ts"],"sourcesContent":["import type { NodeId } from \"./types\";\r\n\r\nexport interface VectorIndexEntry {\r\n nodeId: NodeId;\r\n vector: number[];\r\n}\r\n\r\nexport interface VectorHit {\r\n nodeId: NodeId;\r\n score: number;\r\n}\r\n\r\n/** Stores per-node vectors and ranks by similarity. Async so DB-backed adapters\r\n * (pgvector, sqlite-vec) can implement it; the in-memory default is sync inside. */\r\nexport interface VectorIndexPort {\r\n upsert(entries: VectorIndexEntry[]): Promise<void>;\r\n remove(nodeId: NodeId): Promise<void>;\r\n search(query: number[], k: number): Promise<VectorHit[]>;\r\n has(nodeId: NodeId): Promise<boolean>;\r\n size(): Promise<number>;\r\n entries(): Promise<VectorIndexEntry[]>;\r\n}\r\n\r\n/** Unit-normalize into a Float32Array; zero-norm vectors stay all-zeros\r\n * (their dot with anything is 0 — matches cosine's zero-magnitude behavior). */\r\nfunction normalize(v: ArrayLike<number>): Float32Array {\r\n const out = new Float32Array(v.length);\r\n let n = 0;\r\n for (let i = 0; i < v.length; i++) n += v[i] * v[i];\r\n if (n === 0) return out;\r\n const inv = 1 / Math.sqrt(n);\r\n for (let i = 0; i < v.length; i++) out[i] = v[i] * inv;\r\n return out;\r\n}\r\n\r\nfunction dot(a: Float32Array, b: Float32Array): number {\r\n const len = Math.min(a.length, b.length);\r\n let d = 0;\r\n for (let i = 0; i < len; i++) d += a[i] * b[i];\r\n return d;\r\n}\r\n\r\n/** In-memory brute-force index. Vectors are stored as Float32Arrays (raw for\r\n * `entries()`, unit-normalized for search) so `search` is a plain dot product\r\n * — the cosine of two normalized vectors. Correct and simple at current scale. */\r\nexport class MemoryVectorIndex implements VectorIndexPort {\r\n private readonly vectors = new Map<NodeId, { raw: Float32Array; unit: Float32Array }>();\r\n\r\n async upsert(entries: VectorIndexEntry[]): Promise<void> {\r\n for (const e of entries) {\r\n this.vectors.set(e.nodeId, { raw: Float32Array.from(e.vector), unit: normalize(e.vector) });\r\n }\r\n }\r\n\r\n async remove(nodeId: NodeId): Promise<void> {\r\n this.vectors.delete(nodeId);\r\n }\r\n\r\n async has(nodeId: NodeId): Promise<boolean> {\r\n return this.vectors.has(nodeId);\r\n }\r\n\r\n async size(): Promise<number> {\r\n return this.vectors.size;\r\n }\r\n\r\n async search(query: number[], k: number): Promise<VectorHit[]> {\r\n const q = normalize(query);\r\n const hits: VectorHit[] = [];\r\n for (const [nodeId, { unit }] of this.vectors) {\r\n hits.push({ nodeId, score: dot(q, unit) });\r\n }\r\n hits.sort((a, b) => b.score - a.score);\r\n return hits.slice(0, k);\r\n }\r\n\r\n async entries(): Promise<VectorIndexEntry[]> {\r\n return [...this.vectors].map(([nodeId, { raw }]) => ({ nodeId, vector: Array.from(raw) }));\r\n }\r\n}\r\n"],"mappings":";AAyBA,SAAS,UAAU,GAAoC;AACrD,QAAM,MAAM,IAAI,aAAa,EAAE,MAAM;AACrC,MAAI,IAAI;AACR,WAAS,IAAI,GAAG,IAAI,EAAE,QAAQ,IAAK,MAAK,EAAE,CAAC,IAAI,EAAE,CAAC;AAClD,MAAI,MAAM,EAAG,QAAO;AACpB,QAAM,MAAM,IAAI,KAAK,KAAK,CAAC;AAC3B,WAAS,IAAI,GAAG,IAAI,EAAE,QAAQ,IAAK,KAAI,CAAC,IAAI,EAAE,CAAC,IAAI;AACnD,SAAO;AACT;AAEA,SAAS,IAAI,GAAiB,GAAyB;AACrD,QAAM,MAAM,KAAK,IAAI,EAAE,QAAQ,EAAE,MAAM;AACvC,MAAI,IAAI;AACR,WAAS,IAAI,GAAG,IAAI,KAAK,IAAK,MAAK,EAAE,CAAC,IAAI,EAAE,CAAC;AAC7C,SAAO;AACT;AAKO,IAAM,oBAAN,MAAmD;AAAA,EACvC,UAAU,oBAAI,IAAuD;AAAA,EAEtF,MAAM,OAAO,SAA4C;AACvD,eAAW,KAAK,SAAS;AACvB,WAAK,QAAQ,IAAI,EAAE,QAAQ,EAAE,KAAK,aAAa,KAAK,EAAE,MAAM,GAAG,MAAM,UAAU,EAAE,MAAM,EAAE,CAAC;AAAA,IAC5F;AAAA,EACF;AAAA,EAEA,MAAM,OAAO,QAA+B;AAC1C,SAAK,QAAQ,OAAO,MAAM;AAAA,EAC5B;AAAA,EAEA,MAAM,IAAI,QAAkC;AAC1C,WAAO,KAAK,QAAQ,IAAI,MAAM;AAAA,EAChC;AAAA,EAEA,MAAM,OAAwB;AAC5B,WAAO,KAAK,QAAQ;AAAA,EACtB;AAAA,EAEA,MAAM,OAAO,OAAiB,GAAiC;AAC7D,UAAM,IAAI,UAAU,KAAK;AACzB,UAAM,OAAoB,CAAC;AAC3B,eAAW,CAAC,QAAQ,EAAE,KAAK,CAAC,KAAK,KAAK,SAAS;AAC7C,WAAK,KAAK,EAAE,QAAQ,OAAO,IAAI,GAAG,IAAI,EAAE,CAAC;AAAA,IAC3C;AACA,SAAK,KAAK,CAAC,GAAG,MAAM,EAAE,QAAQ,EAAE,KAAK;AACrC,WAAO,KAAK,MAAM,GAAG,CAAC;AAAA,EACxB;AAAA,EAEA,MAAM,UAAuC;AAC3C,WAAO,CAAC,GAAG,KAAK,OAAO,EAAE,IAAI,CAAC,CAAC,QAAQ,EAAE,IAAI,CAAC,OAAO,EAAE,QAAQ,QAAQ,MAAM,KAAK,GAAG,EAAE,EAAE;AAAA,EAC3F;AACF;","names":[]}
@@ -0,0 +1,13 @@
1
+ import { Json } from './types.js';
2
+
3
+ /** Structural shape of a parser (e.g. a Zod schema). Avoids hard-coupling Arbor to a Zod version. */
4
+ interface ParseSchema {
5
+ parse(value: unknown): unknown;
6
+ }
7
+ /**
8
+ * Build a `TypeDef.validate` function from any Zod-compatible schema (anything with `.parse()`).
9
+ * Throws `ValidationError` (wrapping the parser's error message) when the value is invalid.
10
+ */
11
+ declare function zodValidate(schema: ParseSchema, typeName?: string): (value: Json) => void;
12
+
13
+ export { type ParseSchema, zodValidate };
@@ -0,0 +1,34 @@
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 ValidationError = class extends ArborError {
11
+ constructor(type, details) {
12
+ super("VALIDATION_ERROR", `Validation failed${type ? ` for type ${type}` : ""}: ${details}`);
13
+ this.type = type;
14
+ this.details = details;
15
+ }
16
+ type;
17
+ details;
18
+ };
19
+
20
+ // src/zod-adapter.ts
21
+ function zodValidate(schema, typeName) {
22
+ return (value) => {
23
+ try {
24
+ schema.parse(value);
25
+ } catch (err) {
26
+ const details = err instanceof Error ? err.message : String(err);
27
+ throw new ValidationError(typeName, details);
28
+ }
29
+ };
30
+ }
31
+ export {
32
+ zodValidate
33
+ };
34
+ //# sourceMappingURL=zod-adapter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/errors.ts","../src/zod-adapter.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","import type { Json } from \"./types\";\r\nimport { ValidationError } from \"./errors\";\r\n\r\n/** Structural shape of a parser (e.g. a Zod schema). Avoids hard-coupling Arbor to a Zod version. */\r\nexport interface ParseSchema {\r\n parse(value: unknown): unknown;\r\n}\r\n\r\n/**\r\n * Build a `TypeDef.validate` function from any Zod-compatible schema (anything with `.parse()`).\r\n * Throws `ValidationError` (wrapping the parser's error message) when the value is invalid.\r\n */\r\nexport function zodValidate(schema: ParseSchema, typeName?: string): (value: Json) => void {\r\n return (value: Json) => {\r\n try {\r\n schema.parse(value);\r\n } catch (err) {\r\n const details = err instanceof Error ? err.message : String(err);\r\n throw new ValidationError(typeName, details);\r\n }\r\n };\r\n}\r\n"],"mappings":";AAKO,IAAM,aAAN,cAAyB,MAAM;AAAA,EACpC,YACkB,MAChB,SACA;AACA,UAAM,OAAO;AAHG;AAIhB,SAAK,OAAO,WAAW;AAAA,EACzB;AAAA,EALkB;AAMpB;AAiCO,IAAM,kBAAN,cAA8B,WAAW;AAAA,EAC9C,YACkB,MACA,SAChB;AACA,UAAM,oBAAoB,oBAAoB,OAAO,aAAa,IAAI,KAAK,EAAE,KAAK,OAAO,EAAE;AAH3E;AACA;AAAA,EAGlB;AAAA,EAJkB;AAAA,EACA;AAIpB;;;ACzCO,SAAS,YAAY,QAAqB,UAA0C;AACzF,SAAO,CAAC,UAAgB;AACtB,QAAI;AACF,aAAO,MAAM,KAAK;AAAA,IACpB,SAAS,KAAK;AACZ,YAAM,UAAU,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAC/D,YAAM,IAAI,gBAAgB,UAAU,OAAO;AAAA,IAC7C;AAAA,EACF;AACF;","names":[]}