trellis 2.0.13 → 2.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/index.js +1 -1
- package/dist/embeddings/index.js +1 -1
- package/dist/{index-7gvjxt27.js → index-2917tjd8.js} +1 -1
- package/package.json +2 -10
- package/dist/transformers.node-bx3q9d7k.js +0 -33130
- package/src/cli/index.ts +0 -3356
- package/src/core/agents/harness.ts +0 -380
- package/src/core/agents/index.ts +0 -18
- package/src/core/agents/types.ts +0 -90
- package/src/core/index.ts +0 -118
- package/src/core/kernel/middleware.ts +0 -44
- package/src/core/kernel/trellis-kernel.ts +0 -593
- package/src/core/ontology/builtins.ts +0 -248
- package/src/core/ontology/index.ts +0 -34
- package/src/core/ontology/registry.ts +0 -209
- package/src/core/ontology/types.ts +0 -124
- package/src/core/ontology/validator.ts +0 -382
- package/src/core/persist/backend.ts +0 -74
- package/src/core/persist/sqlite-backend.ts +0 -298
- package/src/core/plugins/index.ts +0 -17
- package/src/core/plugins/registry.ts +0 -322
- package/src/core/plugins/types.ts +0 -126
- package/src/core/query/datalog.ts +0 -188
- package/src/core/query/engine.ts +0 -370
- package/src/core/query/index.ts +0 -34
- package/src/core/query/parser.ts +0 -481
- package/src/core/query/types.ts +0 -200
- package/src/core/store/eav-store.ts +0 -467
- package/src/decisions/auto-capture.ts +0 -136
- package/src/decisions/hooks.ts +0 -163
- package/src/decisions/index.ts +0 -261
- package/src/decisions/types.ts +0 -103
- package/src/embeddings/auto-embed.ts +0 -248
- package/src/embeddings/chunker.ts +0 -327
- package/src/embeddings/index.ts +0 -48
- package/src/embeddings/model.ts +0 -112
- package/src/embeddings/search.ts +0 -305
- package/src/embeddings/store.ts +0 -313
- package/src/embeddings/types.ts +0 -92
- package/src/engine.ts +0 -1125
- package/src/garden/cluster.ts +0 -330
- package/src/garden/garden.ts +0 -306
- package/src/garden/index.ts +0 -29
- package/src/git/git-exporter.ts +0 -286
- package/src/git/git-importer.ts +0 -329
- package/src/git/git-reader.ts +0 -189
- package/src/git/index.ts +0 -22
- package/src/identity/governance.ts +0 -211
- package/src/identity/identity.ts +0 -224
- package/src/identity/index.ts +0 -30
- package/src/identity/signing-middleware.ts +0 -97
- package/src/index.ts +0 -29
- package/src/links/index.ts +0 -49
- package/src/links/lifecycle.ts +0 -400
- package/src/links/parser.ts +0 -484
- package/src/links/ref-index.ts +0 -186
- package/src/links/resolver.ts +0 -314
- package/src/links/types.ts +0 -108
- package/src/mcp/index.ts +0 -22
- package/src/mcp/server.ts +0 -1278
- package/src/semantic/csharp-parser.ts +0 -493
- package/src/semantic/go-parser.ts +0 -585
- package/src/semantic/index.ts +0 -34
- package/src/semantic/java-parser.ts +0 -456
- package/src/semantic/python-parser.ts +0 -659
- package/src/semantic/ruby-parser.ts +0 -446
- package/src/semantic/rust-parser.ts +0 -784
- package/src/semantic/semantic-merge.ts +0 -210
- package/src/semantic/ts-parser.ts +0 -681
- package/src/semantic/types.ts +0 -175
- package/src/sync/http-transport.ts +0 -144
- package/src/sync/index.ts +0 -43
- package/src/sync/memory-transport.ts +0 -66
- package/src/sync/multi-repo.ts +0 -200
- package/src/sync/reconciler.ts +0 -237
- package/src/sync/sync-engine.ts +0 -258
- package/src/sync/types.ts +0 -104
- package/src/sync/ws-transport.ts +0 -145
- package/src/ui/client.html +0 -695
- package/src/ui/server.ts +0 -419
- package/src/vcs/blob-store.ts +0 -124
- package/src/vcs/branch.ts +0 -150
- package/src/vcs/checkpoint.ts +0 -64
- package/src/vcs/decompose.ts +0 -469
- package/src/vcs/diff.ts +0 -409
- package/src/vcs/engine-context.ts +0 -26
- package/src/vcs/index.ts +0 -23
- package/src/vcs/issue.ts +0 -800
- package/src/vcs/merge.ts +0 -425
- package/src/vcs/milestone.ts +0 -124
- package/src/vcs/ops.ts +0 -59
- package/src/vcs/types.ts +0 -213
- package/src/vcs/vcs-middleware.ts +0 -81
- package/src/watcher/fs-watcher.ts +0 -255
- package/src/watcher/index.ts +0 -9
- package/src/watcher/ingestion.ts +0 -116
|
@@ -1,467 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* EAV-based Datalog Engine with Path-Aware Ingestor
|
|
3
|
-
*
|
|
4
|
-
* Core types and fact storage for schema-agnostic data processing.
|
|
5
|
-
* Inlined from trellis-core for single-package publish.
|
|
6
|
-
*
|
|
7
|
-
* @module trellis/core
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
export type Atom = string | number | boolean | Date | EntityRef;
|
|
11
|
-
export type EntityRef = string;
|
|
12
|
-
|
|
13
|
-
export interface Fact {
|
|
14
|
-
e: string; // entity
|
|
15
|
-
a: string; // attribute (JSONPath)
|
|
16
|
-
v: Atom; // value
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
export interface Link {
|
|
20
|
-
e1: string; // source entity
|
|
21
|
-
a: string; // relationship attribute
|
|
22
|
-
e2: string; // target entity
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
export interface CatalogEntry {
|
|
26
|
-
attribute: string;
|
|
27
|
-
type: 'string' | 'number' | 'boolean' | 'date' | 'mixed';
|
|
28
|
-
cardinality: 'one' | 'many';
|
|
29
|
-
distinctCount: number;
|
|
30
|
-
examples: Atom[];
|
|
31
|
-
min?: number;
|
|
32
|
-
max?: number;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
export interface QueryTraceEntry {
|
|
36
|
-
goal: string;
|
|
37
|
-
bindingsCount: number;
|
|
38
|
-
durationMs: number;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
export interface QueryResult {
|
|
42
|
-
bindings: Record<string, Atom>[];
|
|
43
|
-
executionTime: number;
|
|
44
|
-
plan?: string;
|
|
45
|
-
trace?: QueryTraceEntry[];
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
/**
|
|
49
|
-
* Path-aware JSON flattener
|
|
50
|
-
* Converts nested JSON into attribute-value pairs with dot notation
|
|
51
|
-
*/
|
|
52
|
-
export function* flatten(obj: any, base = ''): Generator<[string, any]> {
|
|
53
|
-
if (Array.isArray(obj)) {
|
|
54
|
-
for (const v of obj) {
|
|
55
|
-
yield* flatten(v, base);
|
|
56
|
-
}
|
|
57
|
-
} else if (obj && typeof obj === 'object') {
|
|
58
|
-
for (const [k, v] of Object.entries(obj)) {
|
|
59
|
-
yield* flatten(v, base ? `${base}.${k}` : k);
|
|
60
|
-
}
|
|
61
|
-
} else {
|
|
62
|
-
yield [base, obj];
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
/**
|
|
67
|
-
* Convert JSON entity to EAV facts
|
|
68
|
-
*/
|
|
69
|
-
export function jsonEntityFacts(
|
|
70
|
-
entityId: string,
|
|
71
|
-
root: any,
|
|
72
|
-
type: string,
|
|
73
|
-
): Fact[] {
|
|
74
|
-
const facts: Fact[] = [{ e: entityId, a: 'type', v: type }];
|
|
75
|
-
|
|
76
|
-
for (const [a, v] of flatten(root)) {
|
|
77
|
-
if (v === undefined || v === null) continue;
|
|
78
|
-
|
|
79
|
-
if (Array.isArray(v)) {
|
|
80
|
-
for (const el of v) {
|
|
81
|
-
facts.push({ e: entityId, a, v: el as any });
|
|
82
|
-
}
|
|
83
|
-
} else if (typeof v === 'object') {
|
|
84
|
-
// Handled by flatten recursion
|
|
85
|
-
} else {
|
|
86
|
-
facts.push({ e: entityId, a, v: v as any });
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
return facts;
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
/**
|
|
94
|
-
* In-memory EAV triple store
|
|
95
|
-
*/
|
|
96
|
-
export class EAVStore {
|
|
97
|
-
private facts: Fact[] = [];
|
|
98
|
-
private links: Link[] = [];
|
|
99
|
-
private catalog: Map<string, CatalogEntry> = new Map();
|
|
100
|
-
|
|
101
|
-
// Indexes for fast lookups
|
|
102
|
-
private eavIndex: Map<string, Map<string, Set<number>>> = new Map();
|
|
103
|
-
private aevIndex: Map<string, Map<string, Set<number>>> = new Map();
|
|
104
|
-
private aveIndex: Map<string, Map<Atom, Set<number>>> = new Map();
|
|
105
|
-
|
|
106
|
-
// Link indexes for graph queries
|
|
107
|
-
private linkIndex: Map<string, Map<string, Set<string>>> = new Map(); // e1 -> a -> e2s
|
|
108
|
-
private linkReverseIndex: Map<string, Map<string, Set<string>>> = new Map(); // e2 -> a -> e1s
|
|
109
|
-
private linkAttrIndex: Map<string, Set<[string, string]>> = new Map(); // a -> [(e1, e2)]
|
|
110
|
-
|
|
111
|
-
// Distinct value tracking
|
|
112
|
-
private distinct = new Map<string, Set<string>>(); // attr -> set of valueKey
|
|
113
|
-
|
|
114
|
-
addFacts(facts: Fact[]): void {
|
|
115
|
-
for (let i = 0; i < facts.length; i++) {
|
|
116
|
-
const fact = facts[i];
|
|
117
|
-
if (fact) {
|
|
118
|
-
this.facts.push(fact);
|
|
119
|
-
this.updateIndexes(fact, this.facts.length - 1);
|
|
120
|
-
this.updateCatalog(fact);
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
addLinks(links: Link[]): void {
|
|
126
|
-
for (const link of links) {
|
|
127
|
-
this.links.push(link);
|
|
128
|
-
this.updateLinkIndexes(link);
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
deleteFacts(factsToDelete: Fact[]): void {
|
|
133
|
-
for (const fact of factsToDelete) {
|
|
134
|
-
// Find the fact index
|
|
135
|
-
const valueKey = this.valueKey(fact.v);
|
|
136
|
-
const indices = this.aveIndex.get(fact.a)?.get(valueKey);
|
|
137
|
-
if (!indices) continue;
|
|
138
|
-
|
|
139
|
-
let foundIdx = -1;
|
|
140
|
-
for (const idx of indices) {
|
|
141
|
-
const storedFact = this.facts[idx];
|
|
142
|
-
if (storedFact && storedFact.e === fact.e && storedFact.a === fact.a) {
|
|
143
|
-
foundIdx = idx;
|
|
144
|
-
break;
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
if (foundIdx !== -1) {
|
|
149
|
-
// Remove from main facts (set to null to maintain indices if needed, or just remove and rebuild)
|
|
150
|
-
// For simplicity and to keep indices valid without shifting, we'll set to undefined
|
|
151
|
-
this.facts[foundIdx] = undefined as any;
|
|
152
|
-
|
|
153
|
-
// Remove from indexes
|
|
154
|
-
this.eavIndex.get(fact.e)?.get(fact.a)?.delete(foundIdx);
|
|
155
|
-
this.aevIndex.get(fact.a)?.get(fact.e)?.delete(foundIdx);
|
|
156
|
-
this.aveIndex.get(fact.a)?.get(valueKey)?.delete(foundIdx);
|
|
157
|
-
|
|
158
|
-
// Update catalog (approximate, since we don't fully rebuild it)
|
|
159
|
-
const entry = this.catalog.get(fact.a);
|
|
160
|
-
if (entry) {
|
|
161
|
-
// If we had a specific count, we'd decrement, but distinctCount needs re-check
|
|
162
|
-
// For now we'll just leave it or let it be refreshed later
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
deleteLinks(linksToDelete: Link[]): void {
|
|
169
|
-
for (const link of linksToDelete) {
|
|
170
|
-
// Find and remove from main links list
|
|
171
|
-
const initialLen = this.links.length;
|
|
172
|
-
this.links = this.links.filter(
|
|
173
|
-
(l) => !(l.e1 === link.e1 && l.a === link.a && l.e2 === link.e2),
|
|
174
|
-
);
|
|
175
|
-
|
|
176
|
-
if (this.links.length < initialLen) {
|
|
177
|
-
// Remove from indexes
|
|
178
|
-
this.linkIndex.get(link.e1)?.get(link.a)?.delete(link.e2);
|
|
179
|
-
this.linkReverseIndex.get(link.e2)?.get(link.a)?.delete(link.e1);
|
|
180
|
-
|
|
181
|
-
const attrPairs = this.linkAttrIndex.get(link.a);
|
|
182
|
-
if (attrPairs) {
|
|
183
|
-
for (const pair of attrPairs) {
|
|
184
|
-
if (pair[0] === link.e1 && pair[1] === link.e2) {
|
|
185
|
-
attrPairs.delete(pair);
|
|
186
|
-
break;
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
private updateIndexes(fact: Fact, index: number): void {
|
|
195
|
-
// EAV index: entity -> attribute -> fact indices
|
|
196
|
-
if (!this.eavIndex.has(fact.e)) {
|
|
197
|
-
this.eavIndex.set(fact.e, new Map());
|
|
198
|
-
}
|
|
199
|
-
if (!this.eavIndex.get(fact.e)!.has(fact.a)) {
|
|
200
|
-
this.eavIndex.get(fact.e)!.set(fact.a, new Set());
|
|
201
|
-
}
|
|
202
|
-
this.eavIndex.get(fact.e)!.get(fact.a)!.add(index);
|
|
203
|
-
|
|
204
|
-
// AEV index: attribute -> entity -> fact indices
|
|
205
|
-
if (!this.aevIndex.has(fact.a)) {
|
|
206
|
-
this.aevIndex.set(fact.a, new Map());
|
|
207
|
-
}
|
|
208
|
-
if (!this.aevIndex.get(fact.a)!.has(fact.e)) {
|
|
209
|
-
this.aevIndex.get(fact.a)!.set(fact.e, new Set());
|
|
210
|
-
}
|
|
211
|
-
this.aevIndex.get(fact.a)!.get(fact.e)!.add(index);
|
|
212
|
-
|
|
213
|
-
// AVE index: attribute -> value -> fact indices
|
|
214
|
-
if (!this.aveIndex.has(fact.a)) {
|
|
215
|
-
this.aveIndex.set(fact.a, new Map());
|
|
216
|
-
}
|
|
217
|
-
const valueKey = this.valueKey(fact.v);
|
|
218
|
-
if (!this.aveIndex.get(fact.a)!.has(valueKey)) {
|
|
219
|
-
this.aveIndex.get(fact.a)!.set(valueKey, new Set());
|
|
220
|
-
}
|
|
221
|
-
this.aveIndex.get(fact.a)!.get(valueKey)!.add(index);
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
private updateLinkIndexes(link: Link): void {
|
|
225
|
-
// Forward index: e1 -> a -> e2s
|
|
226
|
-
if (!this.linkIndex.has(link.e1)) {
|
|
227
|
-
this.linkIndex.set(link.e1, new Map());
|
|
228
|
-
}
|
|
229
|
-
const e1Attrs = this.linkIndex.get(link.e1)!;
|
|
230
|
-
if (!e1Attrs.has(link.a)) {
|
|
231
|
-
e1Attrs.set(link.a, new Set());
|
|
232
|
-
}
|
|
233
|
-
e1Attrs.get(link.a)!.add(link.e2);
|
|
234
|
-
|
|
235
|
-
// Reverse index: e2 -> a -> e1s
|
|
236
|
-
if (!this.linkReverseIndex.has(link.e2)) {
|
|
237
|
-
this.linkReverseIndex.set(link.e2, new Map());
|
|
238
|
-
}
|
|
239
|
-
const e2Attrs = this.linkReverseIndex.get(link.e2)!;
|
|
240
|
-
if (!e2Attrs.has(link.a)) {
|
|
241
|
-
e2Attrs.set(link.a, new Set());
|
|
242
|
-
}
|
|
243
|
-
e2Attrs.get(link.a)!.add(link.e1);
|
|
244
|
-
|
|
245
|
-
// Attribute index: a -> [(e1, e2)]
|
|
246
|
-
if (!this.linkAttrIndex.has(link.a)) {
|
|
247
|
-
this.linkAttrIndex.set(link.a, new Set());
|
|
248
|
-
}
|
|
249
|
-
this.linkAttrIndex.get(link.a)!.add([link.e1, link.e2]);
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
private valueKey(v: Atom): string {
|
|
253
|
-
if (v instanceof Date) return `date:${v.toISOString()}`;
|
|
254
|
-
return `${typeof v}:${v}`;
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
private updateCatalog(fact: Fact): void {
|
|
258
|
-
const entry = this.catalog.get(fact.a) || {
|
|
259
|
-
attribute: fact.a,
|
|
260
|
-
type: this.inferType(fact.v),
|
|
261
|
-
cardinality: 'one',
|
|
262
|
-
distinctCount: 0,
|
|
263
|
-
examples: [],
|
|
264
|
-
};
|
|
265
|
-
|
|
266
|
-
// Update type (may become 'mixed')
|
|
267
|
-
const factType = this.inferType(fact.v);
|
|
268
|
-
if (entry.type !== factType && entry.type !== 'mixed') {
|
|
269
|
-
entry.type = 'mixed';
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
// Update cardinality (if we see multiple values for same entity+attribute)
|
|
273
|
-
const entityAttrs = this.eavIndex.get(fact.e)?.get(fact.a);
|
|
274
|
-
if (entityAttrs && entityAttrs.size > 1) {
|
|
275
|
-
entry.cardinality = 'many';
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
// Update distinct count
|
|
279
|
-
const k = this.valueKey(fact.v);
|
|
280
|
-
const s =
|
|
281
|
-
this.distinct.get(fact.a) ||
|
|
282
|
-
(this.distinct.set(fact.a, new Set()), this.distinct.get(fact.a)!);
|
|
283
|
-
s.add(k);
|
|
284
|
-
entry.distinctCount = s.size;
|
|
285
|
-
|
|
286
|
-
// Update examples (keep first 5)
|
|
287
|
-
if (entry.examples.length < 5 && !entry.examples.includes(fact.v)) {
|
|
288
|
-
entry.examples.push(fact.v);
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
// Update numeric ranges
|
|
292
|
-
if (typeof fact.v === 'number') {
|
|
293
|
-
entry.min = Math.min(entry.min ?? fact.v, fact.v);
|
|
294
|
-
entry.max = Math.max(entry.max ?? fact.v, fact.v);
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
this.catalog.set(fact.a, entry);
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
private inferType(
|
|
301
|
-
v: Atom,
|
|
302
|
-
): 'string' | 'number' | 'boolean' | 'date' | 'mixed' {
|
|
303
|
-
if (typeof v === 'string') return 'string';
|
|
304
|
-
if (typeof v === 'number') return 'number';
|
|
305
|
-
if (typeof v === 'boolean') return 'boolean';
|
|
306
|
-
if (v instanceof Date) return 'date';
|
|
307
|
-
return 'mixed';
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
// Query methods
|
|
311
|
-
getFactsByEntity(entity: string): Fact[] {
|
|
312
|
-
const indices = this.eavIndex.get(entity);
|
|
313
|
-
if (!indices) return [];
|
|
314
|
-
|
|
315
|
-
const result: Fact[] = [];
|
|
316
|
-
for (const attrIndices of indices.values()) {
|
|
317
|
-
for (const idx of attrIndices) {
|
|
318
|
-
const fact = this.facts[idx];
|
|
319
|
-
if (fact) {
|
|
320
|
-
result.push(fact);
|
|
321
|
-
}
|
|
322
|
-
}
|
|
323
|
-
}
|
|
324
|
-
return result;
|
|
325
|
-
}
|
|
326
|
-
|
|
327
|
-
getFactsByAttribute(attribute: string): Fact[] {
|
|
328
|
-
const indices = this.aevIndex.get(attribute);
|
|
329
|
-
if (!indices) return [];
|
|
330
|
-
|
|
331
|
-
const result: Fact[] = [];
|
|
332
|
-
for (const entityIndices of indices.values()) {
|
|
333
|
-
for (const idx of entityIndices) {
|
|
334
|
-
const fact = this.facts[idx];
|
|
335
|
-
if (fact) {
|
|
336
|
-
result.push(fact);
|
|
337
|
-
}
|
|
338
|
-
}
|
|
339
|
-
}
|
|
340
|
-
return result;
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
getFactsByValue(attribute: string, value: Atom): Fact[] {
|
|
344
|
-
const indices = this.aveIndex.get(attribute)?.get(this.valueKey(value));
|
|
345
|
-
if (!indices) return [];
|
|
346
|
-
|
|
347
|
-
return Array.from(indices)
|
|
348
|
-
.map((idx) => this.facts[idx])
|
|
349
|
-
.filter((fact): fact is Fact => fact !== undefined);
|
|
350
|
-
}
|
|
351
|
-
|
|
352
|
-
getCatalog(): CatalogEntry[] {
|
|
353
|
-
return Array.from(this.catalog.values());
|
|
354
|
-
}
|
|
355
|
-
|
|
356
|
-
getCatalogEntry(attribute: string): CatalogEntry | undefined {
|
|
357
|
-
return this.catalog.get(attribute);
|
|
358
|
-
}
|
|
359
|
-
|
|
360
|
-
// Statistics
|
|
361
|
-
getAllFacts(): Fact[] {
|
|
362
|
-
return this.facts.filter((f): f is Fact => f !== undefined);
|
|
363
|
-
}
|
|
364
|
-
|
|
365
|
-
getAllLinks(): Link[] {
|
|
366
|
-
return [...this.links];
|
|
367
|
-
}
|
|
368
|
-
|
|
369
|
-
getLinksByEntity(entity: string): Link[] {
|
|
370
|
-
const results: Link[] = [];
|
|
371
|
-
const forwardLinks = this.linkIndex.get(entity);
|
|
372
|
-
if (forwardLinks) {
|
|
373
|
-
for (const [attr, targets] of forwardLinks) {
|
|
374
|
-
for (const target of targets) {
|
|
375
|
-
results.push({ e1: entity, a: attr, e2: target });
|
|
376
|
-
}
|
|
377
|
-
}
|
|
378
|
-
}
|
|
379
|
-
const reverseLinks = this.linkReverseIndex.get(entity);
|
|
380
|
-
if (reverseLinks) {
|
|
381
|
-
for (const [attr, sources] of reverseLinks) {
|
|
382
|
-
for (const source of sources) {
|
|
383
|
-
results.push({ e1: source, a: attr, e2: entity });
|
|
384
|
-
}
|
|
385
|
-
}
|
|
386
|
-
}
|
|
387
|
-
return results;
|
|
388
|
-
}
|
|
389
|
-
|
|
390
|
-
getLinksByAttribute(attribute: string): Link[] {
|
|
391
|
-
const results: Link[] = [];
|
|
392
|
-
const links = this.linkAttrIndex.get(attribute);
|
|
393
|
-
if (links) {
|
|
394
|
-
for (const [e1, e2] of links) {
|
|
395
|
-
results.push({ e1, a: attribute, e2 });
|
|
396
|
-
}
|
|
397
|
-
}
|
|
398
|
-
return results;
|
|
399
|
-
}
|
|
400
|
-
|
|
401
|
-
getLinksByEntityAndAttribute(entity: string, attribute: string): Link[] {
|
|
402
|
-
const results: Link[] = [];
|
|
403
|
-
const attrs = this.linkIndex.get(entity);
|
|
404
|
-
if (attrs) {
|
|
405
|
-
const targets = attrs.get(attribute);
|
|
406
|
-
if (targets) {
|
|
407
|
-
for (const target of targets) {
|
|
408
|
-
results.push({ e1: entity, a: attribute, e2: target });
|
|
409
|
-
}
|
|
410
|
-
}
|
|
411
|
-
}
|
|
412
|
-
return results;
|
|
413
|
-
}
|
|
414
|
-
|
|
415
|
-
getStats() {
|
|
416
|
-
return {
|
|
417
|
-
totalFacts: this.facts.length,
|
|
418
|
-
totalLinks: this.links.length,
|
|
419
|
-
uniqueEntities: this.eavIndex.size,
|
|
420
|
-
uniqueAttributes: this.aevIndex.size,
|
|
421
|
-
catalogEntries: this.catalog.size,
|
|
422
|
-
};
|
|
423
|
-
}
|
|
424
|
-
|
|
425
|
-
/**
|
|
426
|
-
* Creates a serializable snapshot of the current store state.
|
|
427
|
-
*/
|
|
428
|
-
snapshot(): { facts: Fact[]; links: Link[]; catalog: CatalogEntry[] } {
|
|
429
|
-
return {
|
|
430
|
-
facts: this.facts.filter((f) => f !== undefined),
|
|
431
|
-
links: [...this.links],
|
|
432
|
-
catalog: this.getCatalog(),
|
|
433
|
-
};
|
|
434
|
-
}
|
|
435
|
-
|
|
436
|
-
/**
|
|
437
|
-
* Restores the store state from a snapshot and rebuilds all indexes.
|
|
438
|
-
*/
|
|
439
|
-
restore(snapshot: {
|
|
440
|
-
facts: Fact[];
|
|
441
|
-
links: Link[];
|
|
442
|
-
catalog: CatalogEntry[];
|
|
443
|
-
}): void {
|
|
444
|
-
// Clear current state
|
|
445
|
-
this.facts = [];
|
|
446
|
-
this.links = [];
|
|
447
|
-
this.catalog.clear();
|
|
448
|
-
this.eavIndex.clear();
|
|
449
|
-
this.aevIndex.clear();
|
|
450
|
-
this.aveIndex.clear();
|
|
451
|
-
this.linkIndex.clear();
|
|
452
|
-
this.linkReverseIndex.clear();
|
|
453
|
-
this.linkAttrIndex.clear();
|
|
454
|
-
this.distinct.clear();
|
|
455
|
-
|
|
456
|
-
// Re-add data
|
|
457
|
-
this.addFacts(snapshot.facts);
|
|
458
|
-
this.addLinks(snapshot.links);
|
|
459
|
-
|
|
460
|
-
// Explicitly restore catalog if provided (though addFacts rebuilds it partially)
|
|
461
|
-
if (snapshot.catalog) {
|
|
462
|
-
for (const entry of snapshot.catalog) {
|
|
463
|
-
this.catalog.set(entry.attribute, entry);
|
|
464
|
-
}
|
|
465
|
-
}
|
|
466
|
-
}
|
|
467
|
-
}
|
|
@@ -1,136 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Decision Auto-Capture Middleware
|
|
3
|
-
*
|
|
4
|
-
* Wraps MCP tool handlers to automatically emit vcs:decisionRecord ops
|
|
5
|
-
* for every tool invocation. Pre/post hooks can enrich the trace with
|
|
6
|
-
* rationale, alternatives, and prompt context.
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
import type { HookRegistry } from './hooks.js';
|
|
10
|
-
import type { DecisionInput, DecisionContext } from './types.js';
|
|
11
|
-
|
|
12
|
-
// ---------------------------------------------------------------------------
|
|
13
|
-
// Types
|
|
14
|
-
// ---------------------------------------------------------------------------
|
|
15
|
-
|
|
16
|
-
/** A generic MCP tool handler: receives params, returns a result. */
|
|
17
|
-
export type ToolHandler = (params: Record<string, unknown>) => Promise<unknown>;
|
|
18
|
-
|
|
19
|
-
/** Callback invoked after auto-capture builds the DecisionInput. */
|
|
20
|
-
export type DecisionRecorder = (decision: DecisionInput) => Promise<void>;
|
|
21
|
-
|
|
22
|
-
export interface AutoCaptureOpts {
|
|
23
|
-
/** The hook registry for pre/post enrichment. */
|
|
24
|
-
hooks: HookRegistry;
|
|
25
|
-
/** Called to persist the decision as a VcsOp. */
|
|
26
|
-
recorder: DecisionRecorder;
|
|
27
|
-
/** Tool names to exclude from auto-capture (e.g. read-only queries). */
|
|
28
|
-
exclude?: Set<string>;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
// ---------------------------------------------------------------------------
|
|
32
|
-
// Wrapper
|
|
33
|
-
// ---------------------------------------------------------------------------
|
|
34
|
-
|
|
35
|
-
/**
|
|
36
|
-
* Wrap an MCP tool handler for automatic decision trace capture.
|
|
37
|
-
*/
|
|
38
|
-
export function wrapToolHandler(
|
|
39
|
-
toolName: string,
|
|
40
|
-
handler: ToolHandler,
|
|
41
|
-
opts: AutoCaptureOpts,
|
|
42
|
-
): ToolHandler {
|
|
43
|
-
return async (params: Record<string, unknown>) => {
|
|
44
|
-
// Skip excluded tools
|
|
45
|
-
if (opts.exclude?.has(toolName)) {
|
|
46
|
-
return handler(params);
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
// Run pre-hooks
|
|
50
|
-
const preContext: DecisionContext = await opts.hooks.runPreHooks(
|
|
51
|
-
toolName,
|
|
52
|
-
params,
|
|
53
|
-
);
|
|
54
|
-
|
|
55
|
-
// Execute the actual tool
|
|
56
|
-
const result = await handler(params);
|
|
57
|
-
|
|
58
|
-
// Run post-hooks
|
|
59
|
-
const enrichment = await opts.hooks.runPostHooks(
|
|
60
|
-
toolName,
|
|
61
|
-
params,
|
|
62
|
-
result,
|
|
63
|
-
preContext,
|
|
64
|
-
);
|
|
65
|
-
|
|
66
|
-
// Build the decision input
|
|
67
|
-
const decision: DecisionInput = {
|
|
68
|
-
toolName,
|
|
69
|
-
input: sanitizeInput(params),
|
|
70
|
-
outputSummary: summarize(result),
|
|
71
|
-
context: preContext.prompt ?? preContext.conversationId,
|
|
72
|
-
rationale: enrichment.rationale,
|
|
73
|
-
alternatives: enrichment.alternatives,
|
|
74
|
-
confidence: enrichment.confidence,
|
|
75
|
-
relatedEntities: enrichment.relatedEntities,
|
|
76
|
-
custom: {
|
|
77
|
-
...preContext.custom,
|
|
78
|
-
...enrichment.custom,
|
|
79
|
-
agentModel: preContext.agentModel,
|
|
80
|
-
},
|
|
81
|
-
};
|
|
82
|
-
|
|
83
|
-
// Record asynchronously — don't block tool response
|
|
84
|
-
opts.recorder(decision).catch(() => {
|
|
85
|
-
// Silently ignore recording failures
|
|
86
|
-
});
|
|
87
|
-
|
|
88
|
-
return result;
|
|
89
|
-
};
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
// ---------------------------------------------------------------------------
|
|
93
|
-
// Helpers
|
|
94
|
-
// ---------------------------------------------------------------------------
|
|
95
|
-
|
|
96
|
-
/**
|
|
97
|
-
* Remove potentially large/sensitive fields from tool input before storing.
|
|
98
|
-
*/
|
|
99
|
-
function sanitizeInput(
|
|
100
|
-
params: Record<string, unknown>,
|
|
101
|
-
): Record<string, unknown> {
|
|
102
|
-
const sanitized: Record<string, unknown> = {};
|
|
103
|
-
for (const [key, value] of Object.entries(params)) {
|
|
104
|
-
if (typeof value === 'string' && value.length > 2000) {
|
|
105
|
-
sanitized[key] = value.slice(0, 2000) + '…';
|
|
106
|
-
} else {
|
|
107
|
-
sanitized[key] = value;
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
return sanitized;
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
/**
|
|
114
|
-
* Summarize a tool result to a concise string (max 500 chars).
|
|
115
|
-
*/
|
|
116
|
-
function summarize(result: unknown): string {
|
|
117
|
-
if (result === null || result === undefined) return '';
|
|
118
|
-
|
|
119
|
-
// MCP-style { content: [{ type: 'text', text: '...' }] }
|
|
120
|
-
if (typeof result === 'object' && result !== null && 'content' in result) {
|
|
121
|
-
const content = (result as any).content;
|
|
122
|
-
if (Array.isArray(content)) {
|
|
123
|
-
const texts = content
|
|
124
|
-
.filter((c: any) => c.type === 'text')
|
|
125
|
-
.map((c: any) => c.text)
|
|
126
|
-
.join('\n');
|
|
127
|
-
return texts.length > 500 ? texts.slice(0, 500) + '…' : texts;
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
const str =
|
|
132
|
-
typeof result === 'string' ? result : JSON.stringify(result, null, 0);
|
|
133
|
-
return str.length > 500 ? str.slice(0, 500) + '…' : str;
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
export { summarize as _summarizeForTest };
|