@soleri/core 2.0.2 → 2.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/brain/brain.d.ts +14 -50
- package/dist/brain/brain.d.ts.map +1 -1
- package/dist/brain/brain.js +207 -16
- package/dist/brain/brain.js.map +1 -1
- package/dist/brain/intelligence.d.ts +86 -0
- package/dist/brain/intelligence.d.ts.map +1 -0
- package/dist/brain/intelligence.js +771 -0
- package/dist/brain/intelligence.js.map +1 -0
- package/dist/brain/types.d.ts +197 -0
- package/dist/brain/types.d.ts.map +1 -0
- package/dist/brain/types.js +2 -0
- package/dist/brain/types.js.map +1 -0
- package/dist/cognee/client.d.ts +35 -0
- package/dist/cognee/client.d.ts.map +1 -0
- package/dist/cognee/client.js +291 -0
- package/dist/cognee/client.js.map +1 -0
- package/dist/cognee/types.d.ts +46 -0
- package/dist/cognee/types.d.ts.map +1 -0
- package/dist/cognee/types.js +3 -0
- package/dist/cognee/types.js.map +1 -0
- package/dist/control/identity-manager.d.ts +22 -0
- package/dist/control/identity-manager.d.ts.map +1 -0
- package/dist/control/identity-manager.js +233 -0
- package/dist/control/identity-manager.js.map +1 -0
- package/dist/control/intent-router.d.ts +32 -0
- package/dist/control/intent-router.d.ts.map +1 -0
- package/dist/control/intent-router.js +242 -0
- package/dist/control/intent-router.js.map +1 -0
- package/dist/control/types.d.ts +68 -0
- package/dist/control/types.d.ts.map +1 -0
- package/dist/control/types.js +9 -0
- package/dist/control/types.js.map +1 -0
- package/dist/curator/curator.d.ts +29 -0
- package/dist/curator/curator.d.ts.map +1 -1
- package/dist/curator/curator.js +142 -5
- package/dist/curator/curator.js.map +1 -1
- package/dist/facades/types.d.ts +1 -1
- package/dist/governance/governance.d.ts +42 -0
- package/dist/governance/governance.d.ts.map +1 -0
- package/dist/governance/governance.js +488 -0
- package/dist/governance/governance.js.map +1 -0
- package/dist/governance/index.d.ts +3 -0
- package/dist/governance/index.d.ts.map +1 -0
- package/dist/governance/index.js +2 -0
- package/dist/governance/index.js.map +1 -0
- package/dist/governance/types.d.ts +102 -0
- package/dist/governance/types.d.ts.map +1 -0
- package/dist/governance/types.js +3 -0
- package/dist/governance/types.js.map +1 -0
- package/dist/index.d.ts +35 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +32 -1
- package/dist/index.js.map +1 -1
- package/dist/llm/llm-client.d.ts.map +1 -1
- package/dist/llm/llm-client.js +9 -2
- package/dist/llm/llm-client.js.map +1 -1
- package/dist/logging/logger.d.ts +37 -0
- package/dist/logging/logger.d.ts.map +1 -0
- package/dist/logging/logger.js +145 -0
- package/dist/logging/logger.js.map +1 -0
- package/dist/logging/types.d.ts +19 -0
- package/dist/logging/types.d.ts.map +1 -0
- package/dist/logging/types.js +2 -0
- package/dist/logging/types.js.map +1 -0
- package/dist/loop/loop-manager.d.ts +49 -0
- package/dist/loop/loop-manager.d.ts.map +1 -0
- package/dist/loop/loop-manager.js +105 -0
- package/dist/loop/loop-manager.js.map +1 -0
- package/dist/loop/types.d.ts +35 -0
- package/dist/loop/types.d.ts.map +1 -0
- package/dist/loop/types.js +8 -0
- package/dist/loop/types.js.map +1 -0
- package/dist/planning/gap-analysis.d.ts +29 -0
- package/dist/planning/gap-analysis.d.ts.map +1 -0
- package/dist/planning/gap-analysis.js +265 -0
- package/dist/planning/gap-analysis.js.map +1 -0
- package/dist/planning/gap-types.d.ts +29 -0
- package/dist/planning/gap-types.d.ts.map +1 -0
- package/dist/planning/gap-types.js +28 -0
- package/dist/planning/gap-types.js.map +1 -0
- package/dist/planning/planner.d.ts +150 -1
- package/dist/planning/planner.d.ts.map +1 -1
- package/dist/planning/planner.js +365 -2
- package/dist/planning/planner.js.map +1 -1
- package/dist/project/project-registry.d.ts +79 -0
- package/dist/project/project-registry.d.ts.map +1 -0
- package/dist/project/project-registry.js +276 -0
- package/dist/project/project-registry.js.map +1 -0
- package/dist/project/types.d.ts +28 -0
- package/dist/project/types.d.ts.map +1 -0
- package/dist/project/types.js +5 -0
- package/dist/project/types.js.map +1 -0
- package/dist/runtime/admin-extra-ops.d.ts +13 -0
- package/dist/runtime/admin-extra-ops.d.ts.map +1 -0
- package/dist/runtime/admin-extra-ops.js +284 -0
- package/dist/runtime/admin-extra-ops.js.map +1 -0
- package/dist/runtime/admin-ops.d.ts +15 -0
- package/dist/runtime/admin-ops.d.ts.map +1 -0
- package/dist/runtime/admin-ops.js +322 -0
- package/dist/runtime/admin-ops.js.map +1 -0
- package/dist/runtime/capture-ops.d.ts +15 -0
- package/dist/runtime/capture-ops.d.ts.map +1 -0
- package/dist/runtime/capture-ops.js +345 -0
- package/dist/runtime/capture-ops.js.map +1 -0
- package/dist/runtime/core-ops.d.ts +7 -3
- package/dist/runtime/core-ops.d.ts.map +1 -1
- package/dist/runtime/core-ops.js +646 -15
- package/dist/runtime/core-ops.js.map +1 -1
- package/dist/runtime/curator-extra-ops.d.ts +9 -0
- package/dist/runtime/curator-extra-ops.d.ts.map +1 -0
- package/dist/runtime/curator-extra-ops.js +59 -0
- package/dist/runtime/curator-extra-ops.js.map +1 -0
- package/dist/runtime/domain-ops.d.ts.map +1 -1
- package/dist/runtime/domain-ops.js +59 -13
- package/dist/runtime/domain-ops.js.map +1 -1
- package/dist/runtime/grading-ops.d.ts +14 -0
- package/dist/runtime/grading-ops.d.ts.map +1 -0
- package/dist/runtime/grading-ops.js +105 -0
- package/dist/runtime/grading-ops.js.map +1 -0
- package/dist/runtime/loop-ops.d.ts +13 -0
- package/dist/runtime/loop-ops.d.ts.map +1 -0
- package/dist/runtime/loop-ops.js +179 -0
- package/dist/runtime/loop-ops.js.map +1 -0
- package/dist/runtime/memory-cross-project-ops.d.ts +12 -0
- package/dist/runtime/memory-cross-project-ops.d.ts.map +1 -0
- package/dist/runtime/memory-cross-project-ops.js +165 -0
- package/dist/runtime/memory-cross-project-ops.js.map +1 -0
- package/dist/runtime/memory-extra-ops.d.ts +13 -0
- package/dist/runtime/memory-extra-ops.d.ts.map +1 -0
- package/dist/runtime/memory-extra-ops.js +173 -0
- package/dist/runtime/memory-extra-ops.js.map +1 -0
- package/dist/runtime/orchestrate-ops.d.ts +17 -0
- package/dist/runtime/orchestrate-ops.d.ts.map +1 -0
- package/dist/runtime/orchestrate-ops.js +240 -0
- package/dist/runtime/orchestrate-ops.js.map +1 -0
- package/dist/runtime/planning-extra-ops.d.ts +17 -0
- package/dist/runtime/planning-extra-ops.d.ts.map +1 -0
- package/dist/runtime/planning-extra-ops.js +300 -0
- package/dist/runtime/planning-extra-ops.js.map +1 -0
- package/dist/runtime/project-ops.d.ts +15 -0
- package/dist/runtime/project-ops.d.ts.map +1 -0
- package/dist/runtime/project-ops.js +181 -0
- package/dist/runtime/project-ops.js.map +1 -0
- package/dist/runtime/runtime.d.ts.map +1 -1
- package/dist/runtime/runtime.js +48 -1
- package/dist/runtime/runtime.js.map +1 -1
- package/dist/runtime/types.d.ts +23 -0
- package/dist/runtime/types.d.ts.map +1 -1
- package/dist/runtime/vault-extra-ops.d.ts +9 -0
- package/dist/runtime/vault-extra-ops.d.ts.map +1 -0
- package/dist/runtime/vault-extra-ops.js +195 -0
- package/dist/runtime/vault-extra-ops.js.map +1 -0
- package/dist/telemetry/telemetry.d.ts +48 -0
- package/dist/telemetry/telemetry.d.ts.map +1 -0
- package/dist/telemetry/telemetry.js +87 -0
- package/dist/telemetry/telemetry.js.map +1 -0
- package/dist/vault/vault.d.ts +94 -0
- package/dist/vault/vault.d.ts.map +1 -1
- package/dist/vault/vault.js +340 -1
- package/dist/vault/vault.js.map +1 -1
- package/package.json +1 -1
- package/src/__tests__/admin-extra-ops.test.ts +420 -0
- package/src/__tests__/admin-ops.test.ts +271 -0
- package/src/__tests__/brain-intelligence.test.ts +828 -0
- package/src/__tests__/brain.test.ts +396 -27
- package/src/__tests__/capture-ops.test.ts +509 -0
- package/src/__tests__/cognee-client.test.ts +524 -0
- package/src/__tests__/core-ops.test.ts +341 -49
- package/src/__tests__/curator-extra-ops.test.ts +359 -0
- package/src/__tests__/curator.test.ts +126 -31
- package/src/__tests__/domain-ops.test.ts +111 -9
- package/src/__tests__/governance.test.ts +522 -0
- package/src/__tests__/grading-ops.test.ts +340 -0
- package/src/__tests__/identity-manager.test.ts +243 -0
- package/src/__tests__/intent-router.test.ts +222 -0
- package/src/__tests__/logger.test.ts +200 -0
- package/src/__tests__/loop-ops.test.ts +398 -0
- package/src/__tests__/memory-cross-project-ops.test.ts +246 -0
- package/src/__tests__/memory-extra-ops.test.ts +352 -0
- package/src/__tests__/orchestrate-ops.test.ts +284 -0
- package/src/__tests__/planner.test.ts +331 -0
- package/src/__tests__/planning-extra-ops.test.ts +548 -0
- package/src/__tests__/project-ops.test.ts +367 -0
- package/src/__tests__/runtime.test.ts +13 -11
- package/src/__tests__/vault-extra-ops.test.ts +407 -0
- package/src/brain/brain.ts +308 -72
- package/src/brain/intelligence.ts +1230 -0
- package/src/brain/types.ts +214 -0
- package/src/cognee/client.ts +352 -0
- package/src/cognee/types.ts +62 -0
- package/src/control/identity-manager.ts +354 -0
- package/src/control/intent-router.ts +326 -0
- package/src/control/types.ts +102 -0
- package/src/curator/curator.ts +265 -15
- package/src/governance/governance.ts +698 -0
- package/src/governance/index.ts +18 -0
- package/src/governance/types.ts +111 -0
- package/src/index.ts +128 -3
- package/src/llm/llm-client.ts +18 -24
- package/src/logging/logger.ts +154 -0
- package/src/logging/types.ts +21 -0
- package/src/loop/loop-manager.ts +130 -0
- package/src/loop/types.ts +44 -0
- package/src/planning/gap-analysis.ts +506 -0
- package/src/planning/gap-types.ts +58 -0
- package/src/planning/planner.ts +478 -2
- package/src/project/project-registry.ts +358 -0
- package/src/project/types.ts +31 -0
- package/src/runtime/admin-extra-ops.ts +307 -0
- package/src/runtime/admin-ops.ts +329 -0
- package/src/runtime/capture-ops.ts +385 -0
- package/src/runtime/core-ops.ts +747 -26
- package/src/runtime/curator-extra-ops.ts +71 -0
- package/src/runtime/domain-ops.ts +65 -13
- package/src/runtime/grading-ops.ts +121 -0
- package/src/runtime/loop-ops.ts +194 -0
- package/src/runtime/memory-cross-project-ops.ts +192 -0
- package/src/runtime/memory-extra-ops.ts +186 -0
- package/src/runtime/orchestrate-ops.ts +272 -0
- package/src/runtime/planning-extra-ops.ts +327 -0
- package/src/runtime/project-ops.ts +196 -0
- package/src/runtime/runtime.ts +54 -1
- package/src/runtime/types.ts +23 -0
- package/src/runtime/vault-extra-ops.ts +225 -0
- package/src/telemetry/telemetry.ts +118 -0
- package/src/vault/vault.ts +412 -1
|
@@ -0,0 +1,359 @@
|
|
|
1
|
+
import { describe, it, expect, afterEach } from 'vitest';
|
|
2
|
+
import { createAgentRuntime } from '../runtime/runtime.js';
|
|
3
|
+
import { createCuratorExtraOps } from '../runtime/curator-extra-ops.js';
|
|
4
|
+
import type { AgentRuntime } from '../runtime/types.js';
|
|
5
|
+
import type { OpDefinition } from '../facades/types.js';
|
|
6
|
+
|
|
7
|
+
describe('createCuratorExtraOps', () => {
|
|
8
|
+
let runtime: AgentRuntime;
|
|
9
|
+
let ops: OpDefinition[];
|
|
10
|
+
|
|
11
|
+
function findOp(name: string): OpDefinition {
|
|
12
|
+
const op = ops.find((o) => o.name === name);
|
|
13
|
+
if (!op) throw new Error(`Op "${name}" not found`);
|
|
14
|
+
return op;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
afterEach(() => {
|
|
18
|
+
runtime?.close();
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
function setup() {
|
|
22
|
+
runtime = createAgentRuntime({
|
|
23
|
+
agentId: 'test-curator-extra',
|
|
24
|
+
vaultPath: ':memory:',
|
|
25
|
+
});
|
|
26
|
+
ops = createCuratorExtraOps(runtime);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
it('should return 4 ops', () => {
|
|
30
|
+
setup();
|
|
31
|
+
expect(ops).toHaveLength(4);
|
|
32
|
+
const names = ops.map((o) => o.name);
|
|
33
|
+
expect(names).toEqual([
|
|
34
|
+
'curator_entry_history',
|
|
35
|
+
'curator_record_snapshot',
|
|
36
|
+
'curator_queue_stats',
|
|
37
|
+
'curator_enrich',
|
|
38
|
+
]);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
// ─── curator_record_snapshot ───────────────────────────────────
|
|
42
|
+
|
|
43
|
+
describe('curator_record_snapshot', () => {
|
|
44
|
+
it('should record a snapshot and return historyId', async () => {
|
|
45
|
+
setup();
|
|
46
|
+
runtime.vault.seed([
|
|
47
|
+
{
|
|
48
|
+
id: 'snap-1',
|
|
49
|
+
type: 'pattern',
|
|
50
|
+
domain: 'testing',
|
|
51
|
+
title: 'Snapshot Test',
|
|
52
|
+
severity: 'warning',
|
|
53
|
+
description: 'A test entry for snapshots.',
|
|
54
|
+
tags: ['test'],
|
|
55
|
+
},
|
|
56
|
+
]);
|
|
57
|
+
const result = (await findOp('curator_record_snapshot').handler({
|
|
58
|
+
entryId: 'snap-1',
|
|
59
|
+
changedBy: 'user',
|
|
60
|
+
changeReason: 'manual snapshot',
|
|
61
|
+
})) as { recorded: boolean; historyId: number };
|
|
62
|
+
|
|
63
|
+
expect(result.recorded).toBe(true);
|
|
64
|
+
expect(result.historyId).toBeGreaterThan(0);
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it('should return recorded false for missing entry', async () => {
|
|
68
|
+
setup();
|
|
69
|
+
const result = (await findOp('curator_record_snapshot').handler({
|
|
70
|
+
entryId: 'nonexistent',
|
|
71
|
+
})) as { recorded: boolean; historyId: number };
|
|
72
|
+
|
|
73
|
+
expect(result.recorded).toBe(false);
|
|
74
|
+
expect(result.historyId).toBe(-1);
|
|
75
|
+
});
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
// ─── curator_entry_history ─────────────────────────────────────
|
|
79
|
+
|
|
80
|
+
describe('curator_entry_history', () => {
|
|
81
|
+
it('should return version history with 2 snapshots in order', async () => {
|
|
82
|
+
setup();
|
|
83
|
+
runtime.vault.seed([
|
|
84
|
+
{
|
|
85
|
+
id: 'hist-1',
|
|
86
|
+
type: 'pattern',
|
|
87
|
+
domain: 'testing',
|
|
88
|
+
title: 'History Test',
|
|
89
|
+
severity: 'warning',
|
|
90
|
+
description: 'Entry for history test.',
|
|
91
|
+
tags: ['test'],
|
|
92
|
+
},
|
|
93
|
+
]);
|
|
94
|
+
|
|
95
|
+
// Record two snapshots
|
|
96
|
+
runtime.curator.recordSnapshot('hist-1', 'user', 'first snapshot');
|
|
97
|
+
runtime.curator.recordSnapshot('hist-1', 'system', 'second snapshot');
|
|
98
|
+
|
|
99
|
+
const result = (await findOp('curator_entry_history').handler({
|
|
100
|
+
entryId: 'hist-1',
|
|
101
|
+
})) as {
|
|
102
|
+
entryId: string;
|
|
103
|
+
history: Array<{
|
|
104
|
+
historyId: number;
|
|
105
|
+
entryId: string;
|
|
106
|
+
snapshot: { title: string };
|
|
107
|
+
changedBy: string;
|
|
108
|
+
changeReason: string | null;
|
|
109
|
+
createdAt: number;
|
|
110
|
+
}>;
|
|
111
|
+
count: number;
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
expect(result.entryId).toBe('hist-1');
|
|
115
|
+
expect(result.count).toBe(2);
|
|
116
|
+
expect(result.history).toHaveLength(2);
|
|
117
|
+
// First snapshot first (ASC order)
|
|
118
|
+
expect(result.history[0].changedBy).toBe('user');
|
|
119
|
+
expect(result.history[0].changeReason).toBe('first snapshot');
|
|
120
|
+
expect(result.history[0].snapshot.title).toBe('History Test');
|
|
121
|
+
expect(result.history[1].changedBy).toBe('system');
|
|
122
|
+
expect(result.history[1].changeReason).toBe('second snapshot');
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
it('should return empty history for entry with no snapshots', async () => {
|
|
126
|
+
setup();
|
|
127
|
+
runtime.vault.seed([
|
|
128
|
+
{
|
|
129
|
+
id: 'hist-2',
|
|
130
|
+
type: 'pattern',
|
|
131
|
+
domain: 'testing',
|
|
132
|
+
title: 'No History',
|
|
133
|
+
severity: 'warning',
|
|
134
|
+
description: 'Entry with no history.',
|
|
135
|
+
tags: ['test'],
|
|
136
|
+
},
|
|
137
|
+
]);
|
|
138
|
+
|
|
139
|
+
const result = (await findOp('curator_entry_history').handler({
|
|
140
|
+
entryId: 'hist-2',
|
|
141
|
+
})) as { count: number };
|
|
142
|
+
|
|
143
|
+
expect(result.count).toBe(0);
|
|
144
|
+
});
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
// ─── curator_queue_stats ───────────────────────────────────────
|
|
148
|
+
|
|
149
|
+
describe('curator_queue_stats', () => {
|
|
150
|
+
it('should return correct grooming stats', async () => {
|
|
151
|
+
setup();
|
|
152
|
+
// Seed 3 entries
|
|
153
|
+
runtime.vault.seed([
|
|
154
|
+
{
|
|
155
|
+
id: 'qs-1',
|
|
156
|
+
type: 'pattern',
|
|
157
|
+
domain: 'testing',
|
|
158
|
+
title: 'Queue A',
|
|
159
|
+
severity: 'warning',
|
|
160
|
+
description: 'Test.',
|
|
161
|
+
tags: ['test'],
|
|
162
|
+
},
|
|
163
|
+
{
|
|
164
|
+
id: 'qs-2',
|
|
165
|
+
type: 'pattern',
|
|
166
|
+
domain: 'testing',
|
|
167
|
+
title: 'Queue B',
|
|
168
|
+
severity: 'warning',
|
|
169
|
+
description: 'Test.',
|
|
170
|
+
tags: ['test'],
|
|
171
|
+
},
|
|
172
|
+
{
|
|
173
|
+
id: 'qs-3',
|
|
174
|
+
type: 'pattern',
|
|
175
|
+
domain: 'testing',
|
|
176
|
+
title: 'Queue C',
|
|
177
|
+
severity: 'warning',
|
|
178
|
+
description: 'Test.',
|
|
179
|
+
tags: ['test'],
|
|
180
|
+
},
|
|
181
|
+
]);
|
|
182
|
+
|
|
183
|
+
// Groom only 2 of the 3
|
|
184
|
+
runtime.curator.groomEntry('qs-1');
|
|
185
|
+
runtime.curator.groomEntry('qs-2');
|
|
186
|
+
|
|
187
|
+
const result = (await findOp('curator_queue_stats').handler({})) as {
|
|
188
|
+
totalEntries: number;
|
|
189
|
+
groomedEntries: number;
|
|
190
|
+
ungroomedEntries: number;
|
|
191
|
+
staleEntries: number;
|
|
192
|
+
freshEntries: number;
|
|
193
|
+
avgDaysSinceGroom: number;
|
|
194
|
+
};
|
|
195
|
+
|
|
196
|
+
expect(result.totalEntries).toBe(3);
|
|
197
|
+
expect(result.groomedEntries).toBe(2);
|
|
198
|
+
expect(result.ungroomedEntries).toBe(1);
|
|
199
|
+
// Just groomed, so they should be fresh
|
|
200
|
+
expect(result.freshEntries).toBe(2);
|
|
201
|
+
expect(result.staleEntries).toBe(0);
|
|
202
|
+
expect(result.avgDaysSinceGroom).toBeGreaterThanOrEqual(0);
|
|
203
|
+
expect(result.avgDaysSinceGroom).toBeLessThan(1);
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
it('should return zeroes for empty vault', async () => {
|
|
207
|
+
setup();
|
|
208
|
+
const result = (await findOp('curator_queue_stats').handler({})) as {
|
|
209
|
+
totalEntries: number;
|
|
210
|
+
groomedEntries: number;
|
|
211
|
+
ungroomedEntries: number;
|
|
212
|
+
};
|
|
213
|
+
|
|
214
|
+
expect(result.totalEntries).toBe(0);
|
|
215
|
+
expect(result.groomedEntries).toBe(0);
|
|
216
|
+
expect(result.ungroomedEntries).toBe(0);
|
|
217
|
+
});
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
// ─── curator_enrich ────────────────────────────────────────────
|
|
221
|
+
|
|
222
|
+
describe('curator_enrich', () => {
|
|
223
|
+
it('should enrich entry with messy metadata', async () => {
|
|
224
|
+
setup();
|
|
225
|
+
runtime.vault.seed([
|
|
226
|
+
{
|
|
227
|
+
id: 'enrich-1',
|
|
228
|
+
type: 'pattern',
|
|
229
|
+
domain: 'testing',
|
|
230
|
+
title: 'avoid using any types',
|
|
231
|
+
severity: 'suggestion',
|
|
232
|
+
description: ' You should avoid using any types in TypeScript. ',
|
|
233
|
+
tags: ['TypeScript', ' testing ', 'typescript'],
|
|
234
|
+
},
|
|
235
|
+
]);
|
|
236
|
+
|
|
237
|
+
const result = (await findOp('curator_enrich').handler({
|
|
238
|
+
entryId: 'enrich-1',
|
|
239
|
+
})) as {
|
|
240
|
+
enriched: boolean;
|
|
241
|
+
changes: Array<{ field: string; before: string; after: string }>;
|
|
242
|
+
};
|
|
243
|
+
|
|
244
|
+
expect(result.enriched).toBe(true);
|
|
245
|
+
expect(result.changes.length).toBeGreaterThan(0);
|
|
246
|
+
|
|
247
|
+
// Check specific changes
|
|
248
|
+
const fieldNames = result.changes.map((c) => c.field);
|
|
249
|
+
|
|
250
|
+
// Title should be capitalized
|
|
251
|
+
expect(fieldNames).toContain('title');
|
|
252
|
+
const titleChange = result.changes.find((c) => c.field === 'title')!;
|
|
253
|
+
expect(titleChange.after).toBe('Avoid using any types');
|
|
254
|
+
|
|
255
|
+
// Tags should be normalized (lowercase, trimmed, deduped)
|
|
256
|
+
expect(fieldNames).toContain('tags');
|
|
257
|
+
const tagChange = result.changes.find((c) => c.field === 'tags')!;
|
|
258
|
+
const normalizedTags = JSON.parse(tagChange.after);
|
|
259
|
+
expect(normalizedTags).toEqual(['typescript', 'testing']);
|
|
260
|
+
|
|
261
|
+
// Type should be inferred as anti-pattern (starts with "avoid")
|
|
262
|
+
expect(fieldNames).toContain('type');
|
|
263
|
+
const typeChange = result.changes.find((c) => c.field === 'type')!;
|
|
264
|
+
expect(typeChange.after).toBe('anti-pattern');
|
|
265
|
+
|
|
266
|
+
// Description should be trimmed
|
|
267
|
+
expect(fieldNames).toContain('description');
|
|
268
|
+
const descChange = result.changes.find((c) => c.field === 'description')!;
|
|
269
|
+
expect(descChange.after).toBe('You should avoid using any types in TypeScript.');
|
|
270
|
+
|
|
271
|
+
// Verify the entry was actually updated in the vault
|
|
272
|
+
const updated = runtime.vault.get('enrich-1');
|
|
273
|
+
expect(updated).not.toBeNull();
|
|
274
|
+
expect(updated!.type).toBe('anti-pattern');
|
|
275
|
+
expect(updated!.title).toBe('Avoid using any types');
|
|
276
|
+
|
|
277
|
+
// Verify a snapshot was recorded
|
|
278
|
+
const history = runtime.curator.getVersionHistory('enrich-1');
|
|
279
|
+
expect(history.length).toBeGreaterThan(0);
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
it('should return enriched false for clean entry', async () => {
|
|
283
|
+
setup();
|
|
284
|
+
runtime.vault.seed([
|
|
285
|
+
{
|
|
286
|
+
id: 'enrich-2',
|
|
287
|
+
type: 'pattern',
|
|
288
|
+
domain: 'testing',
|
|
289
|
+
title: 'Clean entry with proper metadata',
|
|
290
|
+
severity: 'warning',
|
|
291
|
+
description: 'This entry is already clean.',
|
|
292
|
+
tags: ['clean', 'testing'],
|
|
293
|
+
},
|
|
294
|
+
]);
|
|
295
|
+
|
|
296
|
+
const result = (await findOp('curator_enrich').handler({
|
|
297
|
+
entryId: 'enrich-2',
|
|
298
|
+
})) as {
|
|
299
|
+
enriched: boolean;
|
|
300
|
+
changes: Array<{ field: string; before: string; after: string }>;
|
|
301
|
+
};
|
|
302
|
+
|
|
303
|
+
expect(result.enriched).toBe(false);
|
|
304
|
+
expect(result.changes).toEqual([]);
|
|
305
|
+
});
|
|
306
|
+
|
|
307
|
+
it('should return enriched false for missing entry', async () => {
|
|
308
|
+
setup();
|
|
309
|
+
const result = (await findOp('curator_enrich').handler({
|
|
310
|
+
entryId: 'nonexistent',
|
|
311
|
+
})) as { enriched: boolean };
|
|
312
|
+
|
|
313
|
+
expect(result.enriched).toBe(false);
|
|
314
|
+
});
|
|
315
|
+
|
|
316
|
+
it('should infer severity from critical keywords', async () => {
|
|
317
|
+
setup();
|
|
318
|
+
runtime.vault.seed([
|
|
319
|
+
{
|
|
320
|
+
id: 'enrich-3',
|
|
321
|
+
type: 'rule',
|
|
322
|
+
domain: 'security',
|
|
323
|
+
title: 'Never expose API keys',
|
|
324
|
+
severity: 'suggestion',
|
|
325
|
+
description: 'API keys must not be committed to version control.',
|
|
326
|
+
tags: ['security'],
|
|
327
|
+
},
|
|
328
|
+
]);
|
|
329
|
+
|
|
330
|
+
const result = (await findOp('curator_enrich').handler({
|
|
331
|
+
entryId: 'enrich-3',
|
|
332
|
+
})) as {
|
|
333
|
+
enriched: boolean;
|
|
334
|
+
changes: Array<{ field: string; before: string; after: string }>;
|
|
335
|
+
};
|
|
336
|
+
|
|
337
|
+
expect(result.enriched).toBe(true);
|
|
338
|
+
const severityChange = result.changes.find((c) => c.field === 'severity');
|
|
339
|
+
expect(severityChange).toBeDefined();
|
|
340
|
+
expect(severityChange!.after).toBe('critical');
|
|
341
|
+
});
|
|
342
|
+
});
|
|
343
|
+
|
|
344
|
+
// ─── Auth levels ───────────────────────────────────────────────
|
|
345
|
+
|
|
346
|
+
describe('auth levels', () => {
|
|
347
|
+
it('should use read auth for query ops', () => {
|
|
348
|
+
setup();
|
|
349
|
+
expect(findOp('curator_entry_history').auth).toBe('read');
|
|
350
|
+
expect(findOp('curator_queue_stats').auth).toBe('read');
|
|
351
|
+
});
|
|
352
|
+
|
|
353
|
+
it('should use write auth for mutation ops', () => {
|
|
354
|
+
setup();
|
|
355
|
+
expect(findOp('curator_record_snapshot').auth).toBe('write');
|
|
356
|
+
expect(findOp('curator_enrich').auth).toBe('write');
|
|
357
|
+
});
|
|
358
|
+
});
|
|
359
|
+
});
|
|
@@ -121,8 +121,16 @@ describe('Curator', () => {
|
|
|
121
121
|
describe('Duplicate Detection', () => {
|
|
122
122
|
it('should detect duplicates above threshold', () => {
|
|
123
123
|
vault.seed([
|
|
124
|
-
makeEntry({
|
|
125
|
-
|
|
124
|
+
makeEntry({
|
|
125
|
+
id: 'dup-1',
|
|
126
|
+
title: 'Use semantic tokens for colors',
|
|
127
|
+
description: 'Always use semantic tokens instead of raw hex values for color styling.',
|
|
128
|
+
}),
|
|
129
|
+
makeEntry({
|
|
130
|
+
id: 'dup-2',
|
|
131
|
+
title: 'Use semantic tokens for color values',
|
|
132
|
+
description: 'Prefer semantic color tokens over raw hex or rgb values in styling.',
|
|
133
|
+
}),
|
|
126
134
|
]);
|
|
127
135
|
const results = curator.detectDuplicates(undefined, 0.3);
|
|
128
136
|
expect(results.length).toBeGreaterThan(0);
|
|
@@ -134,8 +142,16 @@ describe('Curator', () => {
|
|
|
134
142
|
|
|
135
143
|
it('should not detect duplicates below threshold', () => {
|
|
136
144
|
vault.seed([
|
|
137
|
-
makeEntry({
|
|
138
|
-
|
|
145
|
+
makeEntry({
|
|
146
|
+
id: 'uniq-1',
|
|
147
|
+
title: 'Database indexing strategies',
|
|
148
|
+
description: 'Create indices on frequently queried columns.',
|
|
149
|
+
}),
|
|
150
|
+
makeEntry({
|
|
151
|
+
id: 'uniq-2',
|
|
152
|
+
title: 'React component lifecycle',
|
|
153
|
+
description: 'Use useEffect for side effects in functional components.',
|
|
154
|
+
}),
|
|
139
155
|
]);
|
|
140
156
|
const results = curator.detectDuplicates(undefined, 0.8);
|
|
141
157
|
expect(results.length).toBe(0);
|
|
@@ -143,9 +159,21 @@ describe('Curator', () => {
|
|
|
143
159
|
|
|
144
160
|
it('should detect duplicates for a specific entry', () => {
|
|
145
161
|
vault.seed([
|
|
146
|
-
makeEntry({
|
|
147
|
-
|
|
148
|
-
|
|
162
|
+
makeEntry({
|
|
163
|
+
id: 'spec-1',
|
|
164
|
+
title: 'Authentication with JWT tokens',
|
|
165
|
+
description: 'Use JSON Web Tokens for stateless authentication.',
|
|
166
|
+
}),
|
|
167
|
+
makeEntry({
|
|
168
|
+
id: 'spec-2',
|
|
169
|
+
title: 'JWT token authentication pattern',
|
|
170
|
+
description: 'Implement JWT-based authentication for API endpoints.',
|
|
171
|
+
}),
|
|
172
|
+
makeEntry({
|
|
173
|
+
id: 'spec-3',
|
|
174
|
+
title: 'Database connection pooling',
|
|
175
|
+
description: 'Use connection pools for efficient database access.',
|
|
176
|
+
}),
|
|
149
177
|
]);
|
|
150
178
|
const results = curator.detectDuplicates('spec-1', 0.3);
|
|
151
179
|
expect(results.length).toBe(1);
|
|
@@ -154,8 +182,16 @@ describe('Curator', () => {
|
|
|
154
182
|
|
|
155
183
|
it('should suggest merge for high similarity', () => {
|
|
156
184
|
vault.seed([
|
|
157
|
-
makeEntry({
|
|
158
|
-
|
|
185
|
+
makeEntry({
|
|
186
|
+
id: 'merge-1',
|
|
187
|
+
title: 'Validate user input',
|
|
188
|
+
description: 'Always validate and sanitize user input before processing.',
|
|
189
|
+
}),
|
|
190
|
+
makeEntry({
|
|
191
|
+
id: 'merge-2',
|
|
192
|
+
title: 'Validate user input',
|
|
193
|
+
description: 'Always validate and sanitize user input before processing.',
|
|
194
|
+
}),
|
|
159
195
|
]);
|
|
160
196
|
const results = curator.detectDuplicates(undefined, 0.3);
|
|
161
197
|
if (results.length > 0 && results[0].matches.length > 0) {
|
|
@@ -185,7 +221,8 @@ describe('Curator', () => {
|
|
|
185
221
|
id: 'ap-inline',
|
|
186
222
|
type: 'anti-pattern',
|
|
187
223
|
title: 'Avoid inline styles for styling',
|
|
188
|
-
description:
|
|
224
|
+
description:
|
|
225
|
+
'Never use inline styles — prefer CSS classes or Tailwind utilities for styling.',
|
|
189
226
|
tags: ['styling'],
|
|
190
227
|
}),
|
|
191
228
|
]);
|
|
@@ -219,8 +256,18 @@ describe('Curator', () => {
|
|
|
219
256
|
|
|
220
257
|
it('should respect UNIQUE constraint — no duplicate contradictions', () => {
|
|
221
258
|
vault.seed([
|
|
222
|
-
makeEntry({
|
|
223
|
-
|
|
259
|
+
makeEntry({
|
|
260
|
+
id: 'p-dup',
|
|
261
|
+
type: 'pattern',
|
|
262
|
+
title: 'Use inline styles',
|
|
263
|
+
description: 'Apply inline styles for dynamic values.',
|
|
264
|
+
}),
|
|
265
|
+
makeEntry({
|
|
266
|
+
id: 'ap-dup',
|
|
267
|
+
type: 'anti-pattern',
|
|
268
|
+
title: 'Avoid inline styles',
|
|
269
|
+
description: 'Do not use inline styles.',
|
|
270
|
+
}),
|
|
224
271
|
]);
|
|
225
272
|
curator.detectContradictions(0.2);
|
|
226
273
|
const first = curator.getContradictions();
|
|
@@ -231,8 +278,18 @@ describe('Curator', () => {
|
|
|
231
278
|
|
|
232
279
|
it('should resolve a contradiction', () => {
|
|
233
280
|
vault.seed([
|
|
234
|
-
makeEntry({
|
|
235
|
-
|
|
281
|
+
makeEntry({
|
|
282
|
+
id: 'p-res',
|
|
283
|
+
type: 'pattern',
|
|
284
|
+
title: 'Use inline styles',
|
|
285
|
+
description: 'Apply inline styles.',
|
|
286
|
+
}),
|
|
287
|
+
makeEntry({
|
|
288
|
+
id: 'ap-res',
|
|
289
|
+
type: 'anti-pattern',
|
|
290
|
+
title: 'Avoid inline styles',
|
|
291
|
+
description: 'Do not use inline styles.',
|
|
292
|
+
}),
|
|
236
293
|
]);
|
|
237
294
|
curator.detectContradictions(0.2);
|
|
238
295
|
const all = curator.getContradictions();
|
|
@@ -245,8 +302,18 @@ describe('Curator', () => {
|
|
|
245
302
|
|
|
246
303
|
it('should dismiss a contradiction', () => {
|
|
247
304
|
vault.seed([
|
|
248
|
-
makeEntry({
|
|
249
|
-
|
|
305
|
+
makeEntry({
|
|
306
|
+
id: 'p-dis',
|
|
307
|
+
type: 'pattern',
|
|
308
|
+
title: 'Use inline styles',
|
|
309
|
+
description: 'Apply inline styles.',
|
|
310
|
+
}),
|
|
311
|
+
makeEntry({
|
|
312
|
+
id: 'ap-dis',
|
|
313
|
+
type: 'anti-pattern',
|
|
314
|
+
title: 'Avoid inline styles',
|
|
315
|
+
description: 'Do not use inline styles.',
|
|
316
|
+
}),
|
|
250
317
|
]);
|
|
251
318
|
curator.detectContradictions(0.2);
|
|
252
319
|
const all = curator.getContradictions();
|
|
@@ -256,8 +323,18 @@ describe('Curator', () => {
|
|
|
256
323
|
|
|
257
324
|
it('should list by status', () => {
|
|
258
325
|
vault.seed([
|
|
259
|
-
makeEntry({
|
|
260
|
-
|
|
326
|
+
makeEntry({
|
|
327
|
+
id: 'p-ls',
|
|
328
|
+
type: 'pattern',
|
|
329
|
+
title: 'Use inline styles',
|
|
330
|
+
description: 'Apply inline styles.',
|
|
331
|
+
}),
|
|
332
|
+
makeEntry({
|
|
333
|
+
id: 'ap-ls',
|
|
334
|
+
type: 'anti-pattern',
|
|
335
|
+
title: 'Avoid inline styles',
|
|
336
|
+
description: 'Do not use inline styles.',
|
|
337
|
+
}),
|
|
261
338
|
]);
|
|
262
339
|
curator.detectContradictions(0.2);
|
|
263
340
|
const open = curator.getContradictions('open');
|
|
@@ -295,7 +372,9 @@ describe('Curator', () => {
|
|
|
295
372
|
curator.groomEntry('groom-state');
|
|
296
373
|
|
|
297
374
|
const db = vault.getDb();
|
|
298
|
-
const row = db
|
|
375
|
+
const row = db
|
|
376
|
+
.prepare('SELECT * FROM curator_entry_state WHERE entry_id = ?')
|
|
377
|
+
.get('groom-state') as Record<string, unknown>;
|
|
299
378
|
expect(row).toBeDefined();
|
|
300
379
|
expect(row.status).toBe('active');
|
|
301
380
|
expect(row.last_groomed_at).not.toBeNull();
|
|
@@ -336,8 +415,16 @@ describe('Curator', () => {
|
|
|
336
415
|
|
|
337
416
|
it('should find issues in dry-run', () => {
|
|
338
417
|
vault.seed([
|
|
339
|
-
makeEntry({
|
|
340
|
-
|
|
418
|
+
makeEntry({
|
|
419
|
+
id: 'con-1',
|
|
420
|
+
title: 'Validate user input thoroughly',
|
|
421
|
+
description: 'Always validate and sanitize all user input before processing.',
|
|
422
|
+
}),
|
|
423
|
+
makeEntry({
|
|
424
|
+
id: 'con-2',
|
|
425
|
+
title: 'Validate user input thoroughly',
|
|
426
|
+
description: 'Always validate and sanitize all user input before processing.',
|
|
427
|
+
}),
|
|
341
428
|
]);
|
|
342
429
|
const result = curator.consolidate({ dryRun: true, duplicateThreshold: 0.3 });
|
|
343
430
|
expect(result.dryRun).toBe(true);
|
|
@@ -369,14 +456,24 @@ describe('Curator', () => {
|
|
|
369
456
|
expect(result.mutations).toBeGreaterThan(0);
|
|
370
457
|
|
|
371
458
|
// Check that entry state was archived
|
|
372
|
-
const row = db
|
|
459
|
+
const row = db
|
|
460
|
+
.prepare('SELECT status FROM curator_entry_state WHERE entry_id = ?')
|
|
461
|
+
.get('stale-con') as { status: string };
|
|
373
462
|
expect(row.status).toBe('archived');
|
|
374
463
|
});
|
|
375
464
|
|
|
376
465
|
it('should remove duplicates when not dry-run', () => {
|
|
377
466
|
vault.seed([
|
|
378
|
-
makeEntry({
|
|
379
|
-
|
|
467
|
+
makeEntry({
|
|
468
|
+
id: 'rem-1',
|
|
469
|
+
title: 'Duplicate pattern for removal',
|
|
470
|
+
description: 'This is a duplicate pattern that should be removed during consolidation.',
|
|
471
|
+
}),
|
|
472
|
+
makeEntry({
|
|
473
|
+
id: 'rem-2',
|
|
474
|
+
title: 'Duplicate pattern for removal',
|
|
475
|
+
description: 'This is a duplicate pattern that should be removed during consolidation.',
|
|
476
|
+
}),
|
|
380
477
|
]);
|
|
381
478
|
const result = curator.consolidate({ dryRun: false, duplicateThreshold: 0.3 });
|
|
382
479
|
expect(result.mutations).toBeGreaterThan(0);
|
|
@@ -440,9 +537,7 @@ describe('Curator', () => {
|
|
|
440
537
|
});
|
|
441
538
|
|
|
442
539
|
it('should penalize for missing entry types', () => {
|
|
443
|
-
vault.seed([
|
|
444
|
-
makeEntry({ id: 'h-p1', type: 'pattern', tags: ['x'] }),
|
|
445
|
-
]);
|
|
540
|
+
vault.seed([makeEntry({ id: 'h-p1', type: 'pattern', tags: ['x'] })]);
|
|
446
541
|
curator.groomAll();
|
|
447
542
|
const result = curator.healthAudit();
|
|
448
543
|
expect(result.score).toBeLessThan(100);
|
|
@@ -451,9 +546,7 @@ describe('Curator', () => {
|
|
|
451
546
|
});
|
|
452
547
|
|
|
453
548
|
it('should recommend actions for issues', () => {
|
|
454
|
-
vault.seed([
|
|
455
|
-
makeEntry({ id: 'h-rec', type: 'pattern', tags: [] }),
|
|
456
|
-
]);
|
|
549
|
+
vault.seed([makeEntry({ id: 'h-rec', type: 'pattern', tags: [] })]);
|
|
457
550
|
const result = curator.healthAudit();
|
|
458
551
|
expect(result.recommendations.length).toBeGreaterThan(0);
|
|
459
552
|
});
|
|
@@ -461,7 +554,9 @@ describe('Curator', () => {
|
|
|
461
554
|
it('should handle empty vault gracefully', () => {
|
|
462
555
|
const result = curator.healthAudit();
|
|
463
556
|
expect(result.score).toBe(100);
|
|
464
|
-
expect(result.recommendations).toContain(
|
|
557
|
+
expect(result.recommendations).toContain(
|
|
558
|
+
'Vault is empty — add knowledge entries to get started.',
|
|
559
|
+
);
|
|
465
560
|
});
|
|
466
561
|
|
|
467
562
|
it('should include tag health metrics', () => {
|