@soleri/core 7.0.0 → 8.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.
- package/dist/agency/agency-manager.d.ts +27 -1
- package/dist/agency/agency-manager.d.ts.map +1 -1
- package/dist/agency/agency-manager.js +180 -9
- package/dist/agency/agency-manager.js.map +1 -1
- package/dist/agency/default-rules.d.ts +7 -0
- package/dist/agency/default-rules.d.ts.map +1 -0
- package/dist/agency/default-rules.js +79 -0
- package/dist/agency/default-rules.js.map +1 -0
- package/dist/agency/types.d.ts +48 -0
- package/dist/agency/types.d.ts.map +1 -1
- package/dist/brain/brain.d.ts +17 -2
- package/dist/brain/brain.d.ts.map +1 -1
- package/dist/brain/brain.js +118 -8
- package/dist/brain/brain.js.map +1 -1
- package/dist/brain/knowledge-synthesizer.d.ts +37 -0
- package/dist/brain/knowledge-synthesizer.d.ts.map +1 -0
- package/dist/brain/knowledge-synthesizer.js +161 -0
- package/dist/brain/knowledge-synthesizer.js.map +1 -0
- package/dist/brain/learning-radar.d.ts +96 -0
- package/dist/brain/learning-radar.d.ts.map +1 -0
- package/dist/brain/learning-radar.js +202 -0
- package/dist/brain/learning-radar.js.map +1 -0
- package/dist/brain/types.d.ts +15 -0
- package/dist/brain/types.d.ts.map +1 -1
- package/dist/context/context-engine.d.ts.map +1 -1
- package/dist/context/context-engine.js +82 -17
- package/dist/context/context-engine.js.map +1 -1
- package/dist/context/types.d.ts +5 -0
- package/dist/context/types.d.ts.map +1 -1
- package/dist/control/intent-router.d.ts +12 -1
- package/dist/control/intent-router.d.ts.map +1 -1
- package/dist/control/intent-router.js +68 -0
- package/dist/control/intent-router.js.map +1 -1
- package/dist/control/types.d.ts +17 -0
- package/dist/control/types.d.ts.map +1 -1
- package/dist/curator/classifier.d.ts +18 -0
- package/dist/curator/classifier.d.ts.map +1 -0
- package/dist/curator/classifier.js +61 -0
- package/dist/curator/classifier.js.map +1 -0
- package/dist/curator/quality-gate.d.ts +29 -0
- package/dist/curator/quality-gate.d.ts.map +1 -0
- package/dist/curator/quality-gate.js +88 -0
- package/dist/curator/quality-gate.js.map +1 -0
- package/dist/engine/bin/soleri-engine.js +1 -0
- package/dist/engine/bin/soleri-engine.js.map +1 -1
- package/dist/events/event-bus.d.ts +30 -0
- package/dist/events/event-bus.d.ts.map +1 -0
- package/dist/events/event-bus.js +51 -0
- package/dist/events/event-bus.js.map +1 -0
- package/dist/flows/chain-runner.d.ts +46 -0
- package/dist/flows/chain-runner.d.ts.map +1 -0
- package/dist/flows/chain-runner.js +271 -0
- package/dist/flows/chain-runner.js.map +1 -0
- package/dist/flows/chain-types.d.ts +103 -0
- package/dist/flows/chain-types.d.ts.map +1 -0
- package/dist/flows/chain-types.js +23 -0
- package/dist/flows/chain-types.js.map +1 -0
- package/dist/health/doctor-checks.d.ts +15 -0
- package/dist/health/doctor-checks.d.ts.map +1 -0
- package/dist/health/doctor-checks.js +98 -0
- package/dist/health/doctor-checks.js.map +1 -0
- package/dist/intake/text-ingester.d.ts +52 -0
- package/dist/intake/text-ingester.d.ts.map +1 -0
- package/dist/intake/text-ingester.js +181 -0
- package/dist/intake/text-ingester.js.map +1 -0
- package/dist/llm/llm-client.d.ts.map +1 -1
- package/dist/llm/llm-client.js +37 -1
- package/dist/llm/llm-client.js.map +1 -1
- package/dist/llm/oauth-discovery.d.ts +26 -0
- package/dist/llm/oauth-discovery.d.ts.map +1 -0
- package/dist/llm/oauth-discovery.js +149 -0
- package/dist/llm/oauth-discovery.js.map +1 -0
- package/dist/planning/evidence-collector.d.ts +41 -0
- package/dist/planning/evidence-collector.d.ts.map +1 -0
- package/dist/planning/evidence-collector.js +194 -0
- package/dist/planning/evidence-collector.js.map +1 -0
- package/dist/planning/planner.d.ts +4 -0
- package/dist/planning/planner.d.ts.map +1 -1
- package/dist/planning/planner.js +11 -0
- package/dist/planning/planner.js.map +1 -1
- package/dist/queue/job-queue.d.ts +92 -0
- package/dist/queue/job-queue.d.ts.map +1 -0
- package/dist/queue/job-queue.js +180 -0
- package/dist/queue/job-queue.js.map +1 -0
- package/dist/queue/pipeline-runner.d.ts +62 -0
- package/dist/queue/pipeline-runner.d.ts.map +1 -0
- package/dist/queue/pipeline-runner.js +126 -0
- package/dist/queue/pipeline-runner.js.map +1 -0
- package/dist/runtime/admin-setup-ops.d.ts +20 -0
- package/dist/runtime/admin-setup-ops.d.ts.map +1 -0
- package/dist/runtime/admin-setup-ops.js +583 -0
- package/dist/runtime/admin-setup-ops.js.map +1 -0
- package/dist/runtime/chain-ops.d.ts +9 -0
- package/dist/runtime/chain-ops.d.ts.map +1 -0
- package/dist/runtime/chain-ops.js +107 -0
- package/dist/runtime/chain-ops.js.map +1 -0
- package/dist/runtime/claude-md-helpers.d.ts +65 -0
- package/dist/runtime/claude-md-helpers.d.ts.map +1 -0
- package/dist/runtime/claude-md-helpers.js +173 -0
- package/dist/runtime/claude-md-helpers.js.map +1 -0
- package/dist/runtime/curator-extra-ops.d.ts +3 -2
- package/dist/runtime/curator-extra-ops.d.ts.map +1 -1
- package/dist/runtime/curator-extra-ops.js +81 -3
- package/dist/runtime/curator-extra-ops.js.map +1 -1
- package/dist/runtime/facades/admin-facade.d.ts.map +1 -1
- package/dist/runtime/facades/admin-facade.js +4 -0
- package/dist/runtime/facades/admin-facade.js.map +1 -1
- package/dist/runtime/facades/agency-facade.d.ts.map +1 -1
- package/dist/runtime/facades/agency-facade.js +64 -0
- package/dist/runtime/facades/agency-facade.js.map +1 -1
- package/dist/runtime/facades/brain-facade.d.ts.map +1 -1
- package/dist/runtime/facades/brain-facade.js +122 -1
- package/dist/runtime/facades/brain-facade.js.map +1 -1
- package/dist/runtime/facades/control-facade.d.ts.map +1 -1
- package/dist/runtime/facades/control-facade.js +42 -0
- package/dist/runtime/facades/control-facade.js.map +1 -1
- package/dist/runtime/facades/memory-facade.d.ts.map +1 -1
- package/dist/runtime/facades/memory-facade.js +20 -2
- package/dist/runtime/facades/memory-facade.js.map +1 -1
- package/dist/runtime/facades/plan-facade.d.ts.map +1 -1
- package/dist/runtime/facades/plan-facade.js +2 -0
- package/dist/runtime/facades/plan-facade.js.map +1 -1
- package/dist/runtime/facades/vault-facade.d.ts.map +1 -1
- package/dist/runtime/facades/vault-facade.js +25 -5
- package/dist/runtime/facades/vault-facade.js.map +1 -1
- package/dist/runtime/intake-ops.d.ts +7 -5
- package/dist/runtime/intake-ops.d.ts.map +1 -1
- package/dist/runtime/intake-ops.js +98 -5
- package/dist/runtime/intake-ops.js.map +1 -1
- package/dist/runtime/memory-extra-ops.d.ts +6 -3
- package/dist/runtime/memory-extra-ops.d.ts.map +1 -1
- package/dist/runtime/memory-extra-ops.js +292 -4
- package/dist/runtime/memory-extra-ops.js.map +1 -1
- package/dist/runtime/planning-extra-ops.d.ts.map +1 -1
- package/dist/runtime/planning-extra-ops.js +85 -0
- package/dist/runtime/planning-extra-ops.js.map +1 -1
- package/dist/runtime/playbook-ops.js +1 -1
- package/dist/runtime/playbook-ops.js.map +1 -1
- package/dist/runtime/runtime.d.ts.map +1 -1
- package/dist/runtime/runtime.js +143 -2
- package/dist/runtime/runtime.js.map +1 -1
- package/dist/runtime/session-briefing.d.ts +23 -0
- package/dist/runtime/session-briefing.d.ts.map +1 -0
- package/dist/runtime/session-briefing.js +140 -0
- package/dist/runtime/session-briefing.js.map +1 -0
- package/dist/runtime/types.d.ts +23 -0
- package/dist/runtime/types.d.ts.map +1 -1
- package/dist/runtime/vault-linking-ops.d.ts.map +1 -1
- package/dist/runtime/vault-linking-ops.js +1 -3
- package/dist/runtime/vault-linking-ops.js.map +1 -1
- package/dist/vault/vault.d.ts +25 -0
- package/dist/vault/vault.d.ts.map +1 -1
- package/dist/vault/vault.js +67 -3
- package/dist/vault/vault.js.map +1 -1
- package/package.json +1 -1
- package/src/__tests__/admin-setup-ops.test.ts +355 -0
- package/src/__tests__/async-infrastructure.test.ts +307 -0
- package/src/__tests__/cognee-client-gaps.test.ts +6 -2
- package/src/__tests__/cognee-hybrid-search.test.ts +49 -35
- package/src/__tests__/cognee-sync-manager-deep.test.ts +89 -65
- package/src/__tests__/curator-extra-ops.test.ts +6 -2
- package/src/__tests__/curator-pipeline-e2e.test.ts +358 -0
- package/src/__tests__/memory-extra-ops.test.ts +2 -2
- package/src/__tests__/planning-extra-ops.test.ts +2 -2
- package/src/__tests__/second-brain-features.test.ts +583 -0
- package/src/agency/agency-manager.ts +217 -9
- package/src/agency/default-rules.ts +83 -0
- package/src/agency/types.ts +61 -0
- package/src/brain/brain.ts +110 -8
- package/src/brain/knowledge-synthesizer.ts +218 -0
- package/src/brain/learning-radar.ts +340 -0
- package/src/brain/types.ts +16 -0
- package/src/context/context-engine.ts +114 -15
- package/src/context/types.ts +5 -0
- package/src/control/intent-router.ts +107 -0
- package/src/control/types.ts +10 -0
- package/src/curator/classifier.ts +88 -0
- package/src/curator/quality-gate.ts +129 -0
- package/src/engine/bin/soleri-engine.ts +1 -0
- package/src/events/event-bus.ts +58 -0
- package/src/flows/chain-runner.ts +369 -0
- package/src/flows/chain-types.ts +57 -0
- package/src/health/doctor-checks.ts +115 -0
- package/src/intake/text-ingester.ts +234 -0
- package/src/llm/llm-client.ts +38 -1
- package/src/llm/oauth-discovery.ts +169 -0
- package/src/planning/evidence-collector.ts +247 -0
- package/src/planning/planner.ts +11 -0
- package/src/queue/job-queue.ts +281 -0
- package/src/queue/pipeline-runner.ts +149 -0
- package/src/runtime/admin-setup-ops.ts +664 -0
- package/src/runtime/chain-ops.ts +121 -0
- package/src/runtime/claude-md-helpers.ts +236 -0
- package/src/runtime/curator-extra-ops.ts +86 -3
- package/src/runtime/facades/admin-facade.ts +4 -0
- package/src/runtime/facades/agency-facade.ts +68 -0
- package/src/runtime/facades/brain-facade.ts +142 -1
- package/src/runtime/facades/control-facade.ts +45 -0
- package/src/runtime/facades/memory-facade.ts +20 -2
- package/src/runtime/facades/plan-facade.ts +2 -0
- package/src/runtime/facades/vault-facade.ts +28 -5
- package/src/runtime/intake-ops.ts +107 -5
- package/src/runtime/memory-extra-ops.ts +312 -4
- package/src/runtime/planning-extra-ops.ts +94 -0
- package/src/runtime/playbook-ops.ts +1 -1
- package/src/runtime/runtime.ts +138 -2
- package/src/runtime/session-briefing.ts +161 -0
- package/src/runtime/types.ts +23 -0
- package/src/runtime/vault-linking-ops.ts +1 -3
- package/src/vault/vault.ts +79 -4
|
@@ -15,9 +15,6 @@
|
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
17
|
import { describe, it, expect, beforeAll, afterAll, vi } from 'vitest';
|
|
18
|
-
import { mkdirSync, rmSync } from 'node:fs';
|
|
19
|
-
import { join } from 'node:path';
|
|
20
|
-
import { tmpdir } from 'node:os';
|
|
21
18
|
import { Brain } from '../brain/brain.js';
|
|
22
19
|
import { Vault } from '../vault/vault.js';
|
|
23
20
|
import { CogneeClient } from '../cognee/client.js';
|
|
@@ -46,16 +43,22 @@ function makeEntry(overrides: Partial<IntelligenceEntry> = {}): IntelligenceEntr
|
|
|
46
43
|
* Create a mock CogneeClient that returns controlled search results.
|
|
47
44
|
* isAvailable is a getter — can be toggled at runtime.
|
|
48
45
|
*/
|
|
49
|
-
function makeMockCognee(
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
46
|
+
function makeMockCognee(
|
|
47
|
+
opts: {
|
|
48
|
+
available?: boolean;
|
|
49
|
+
searchResults?: CogneeSearchResult[];
|
|
50
|
+
} = {},
|
|
51
|
+
): CogneeClient & { _setAvailable: (v: boolean) => void } {
|
|
53
52
|
let available = opts.available ?? true;
|
|
54
53
|
const searchResults = opts.searchResults ?? [];
|
|
55
54
|
|
|
56
55
|
return {
|
|
57
|
-
get isAvailable() {
|
|
58
|
-
|
|
56
|
+
get isAvailable() {
|
|
57
|
+
return available;
|
|
58
|
+
},
|
|
59
|
+
_setAvailable(v: boolean) {
|
|
60
|
+
available = v;
|
|
61
|
+
},
|
|
59
62
|
healthCheck: vi.fn().mockResolvedValue({ available, url: 'mock', latencyMs: 1 }),
|
|
60
63
|
addEntries: vi.fn().mockResolvedValue({ added: 0 }),
|
|
61
64
|
deleteEntries: vi.fn().mockResolvedValue({ deleted: 0 }),
|
|
@@ -74,7 +77,8 @@ const SEED_ENTRIES: IntelligenceEntry[] = [
|
|
|
74
77
|
makeEntry({
|
|
75
78
|
id: 'pattern-retry-backoff',
|
|
76
79
|
title: 'Retry with Exponential Backoff',
|
|
77
|
-
description:
|
|
80
|
+
description:
|
|
81
|
+
'Always use exponential backoff when retrying failed network requests to avoid thundering herd.',
|
|
78
82
|
domain: 'architecture',
|
|
79
83
|
severity: 'critical',
|
|
80
84
|
tags: ['networking', 'retry', 'resilience'],
|
|
@@ -83,7 +87,8 @@ const SEED_ENTRIES: IntelligenceEntry[] = [
|
|
|
83
87
|
makeEntry({
|
|
84
88
|
id: 'pattern-circuit-breaker',
|
|
85
89
|
title: 'Circuit Breaker for External Services',
|
|
86
|
-
description:
|
|
90
|
+
description:
|
|
91
|
+
'Wrap external service calls in a circuit breaker to prevent cascade failures when downstream is unhealthy.',
|
|
87
92
|
domain: 'architecture',
|
|
88
93
|
severity: 'critical',
|
|
89
94
|
tags: ['networking', 'resilience', 'microservices'],
|
|
@@ -93,7 +98,8 @@ const SEED_ENTRIES: IntelligenceEntry[] = [
|
|
|
93
98
|
id: 'anti-pattern-polling-no-timeout',
|
|
94
99
|
type: 'anti-pattern',
|
|
95
100
|
title: 'Polling Without Timeout',
|
|
96
|
-
description:
|
|
101
|
+
description:
|
|
102
|
+
'Never poll an external service without a maximum timeout or circuit breaker. Leads to resource exhaustion.',
|
|
97
103
|
domain: 'architecture',
|
|
98
104
|
severity: 'critical',
|
|
99
105
|
tags: ['networking', 'polling', 'timeout'],
|
|
@@ -101,7 +107,8 @@ const SEED_ENTRIES: IntelligenceEntry[] = [
|
|
|
101
107
|
makeEntry({
|
|
102
108
|
id: 'pattern-token-semantic',
|
|
103
109
|
title: 'Semantic Token Priority',
|
|
104
|
-
description:
|
|
110
|
+
description:
|
|
111
|
+
'Use semantic tokens over primitive tokens. Semantic tokens communicate intent, not just values.',
|
|
105
112
|
domain: 'design',
|
|
106
113
|
severity: 'warning',
|
|
107
114
|
tags: ['tokens', 'design-system', 'css'],
|
|
@@ -109,7 +116,8 @@ const SEED_ENTRIES: IntelligenceEntry[] = [
|
|
|
109
116
|
makeEntry({
|
|
110
117
|
id: 'pattern-fts5-search',
|
|
111
118
|
title: 'FTS5 Full-Text Search with Porter Stemming',
|
|
112
|
-
description:
|
|
119
|
+
description:
|
|
120
|
+
'Use SQLite FTS5 with porter tokenizer for all text search in the vault. BM25 ranking for relevance.',
|
|
113
121
|
domain: 'architecture',
|
|
114
122
|
severity: 'suggestion',
|
|
115
123
|
tags: ['search', 'sqlite', 'fts5'],
|
|
@@ -129,7 +137,11 @@ describe('Hybrid search: Cognee → FTS5 → Zettelkasten', () => {
|
|
|
129
137
|
|
|
130
138
|
// Create Zettelkasten links between related entries
|
|
131
139
|
linkManager.addLink('pattern-retry-backoff', 'pattern-circuit-breaker', 'extends');
|
|
132
|
-
linkManager.addLink(
|
|
140
|
+
linkManager.addLink(
|
|
141
|
+
'anti-pattern-polling-no-timeout',
|
|
142
|
+
'pattern-circuit-breaker',
|
|
143
|
+
'contradicts',
|
|
144
|
+
);
|
|
133
145
|
linkManager.addLink('pattern-retry-backoff', 'anti-pattern-polling-no-timeout', 'contradicts');
|
|
134
146
|
});
|
|
135
147
|
|
|
@@ -147,14 +159,14 @@ describe('Hybrid search: Cognee → FTS5 → Zettelkasten', () => {
|
|
|
147
159
|
const results = await brain.intelligentSearch('retry network requests');
|
|
148
160
|
expect(results.length).toBeGreaterThan(0);
|
|
149
161
|
// Vector score should be 0 for all results (no Cognee)
|
|
150
|
-
expect(results.every(r => r.breakdown.vector === 0)).toBe(true);
|
|
162
|
+
expect(results.every((r) => r.breakdown.vector === 0)).toBe(true);
|
|
151
163
|
});
|
|
152
164
|
|
|
153
165
|
it('should match by keyword in FTS5', async () => {
|
|
154
166
|
const brain = new Brain(vault); // No Cognee at all
|
|
155
167
|
|
|
156
168
|
const results = await brain.intelligentSearch('exponential backoff');
|
|
157
|
-
const ids = results.map(r => r.entry.id);
|
|
169
|
+
const ids = results.map((r) => r.entry.id);
|
|
158
170
|
expect(ids).toContain('pattern-retry-backoff');
|
|
159
171
|
});
|
|
160
172
|
|
|
@@ -163,8 +175,8 @@ describe('Hybrid search: Cognee → FTS5 → Zettelkasten', () => {
|
|
|
163
175
|
|
|
164
176
|
const results = await brain.intelligentSearch('networking resilience');
|
|
165
177
|
// Critical entries should rank above suggestion entries for same domain
|
|
166
|
-
const criticalIdx = results.findIndex(r => r.entry.id === 'pattern-retry-backoff');
|
|
167
|
-
const suggestionIdx = results.findIndex(r => r.entry.id === 'pattern-fts5-search');
|
|
178
|
+
const criticalIdx = results.findIndex((r) => r.entry.id === 'pattern-retry-backoff');
|
|
179
|
+
const suggestionIdx = results.findIndex((r) => r.entry.id === 'pattern-fts5-search');
|
|
168
180
|
if (criticalIdx >= 0 && suggestionIdx >= 0) {
|
|
169
181
|
expect(criticalIdx).toBeLessThan(suggestionIdx);
|
|
170
182
|
}
|
|
@@ -192,7 +204,7 @@ describe('Hybrid search: Cognee → FTS5 → Zettelkasten', () => {
|
|
|
192
204
|
const results = await brain.intelligentSearch('how to handle failing external services');
|
|
193
205
|
|
|
194
206
|
// Circuit breaker should rank high due to Cognee vector boost
|
|
195
|
-
const cbResult = results.find(r => r.entry.id === 'pattern-circuit-breaker');
|
|
207
|
+
const cbResult = results.find((r) => r.entry.id === 'pattern-circuit-breaker');
|
|
196
208
|
expect(cbResult).toBeDefined();
|
|
197
209
|
expect(cbResult!.breakdown.vector).toBeGreaterThan(0);
|
|
198
210
|
});
|
|
@@ -212,7 +224,7 @@ describe('Hybrid search: Cognee → FTS5 → Zettelkasten', () => {
|
|
|
212
224
|
const brain = new Brain(vault, mockCognee);
|
|
213
225
|
|
|
214
226
|
const results = await brain.intelligentSearch('retry strategy');
|
|
215
|
-
const retryResult = results.find(r => r.entry.id === 'pattern-retry-backoff');
|
|
227
|
+
const retryResult = results.find((r) => r.entry.id === 'pattern-retry-backoff');
|
|
216
228
|
expect(retryResult).toBeDefined();
|
|
217
229
|
// Should have vector score from Cognee cross-reference
|
|
218
230
|
expect(retryResult!.breakdown.vector).toBeGreaterThan(0);
|
|
@@ -234,7 +246,7 @@ describe('Hybrid search: Cognee → FTS5 → Zettelkasten', () => {
|
|
|
234
246
|
const brain = new Brain(vault, mockCognee);
|
|
235
247
|
|
|
236
248
|
const results = await brain.intelligentSearch('design tokens priority');
|
|
237
|
-
const tokenResult = results.find(r => r.entry.id === 'pattern-token-semantic');
|
|
249
|
+
const tokenResult = results.find((r) => r.entry.id === 'pattern-token-semantic');
|
|
238
250
|
expect(tokenResult).toBeDefined();
|
|
239
251
|
expect(tokenResult!.breakdown.vector).toBeGreaterThan(0);
|
|
240
252
|
});
|
|
@@ -254,7 +266,7 @@ describe('Hybrid search: Cognee → FTS5 → Zettelkasten', () => {
|
|
|
254
266
|
const brain = new Brain(vault, mockCognee);
|
|
255
267
|
|
|
256
268
|
const results = await brain.intelligentSearch('retry');
|
|
257
|
-
const retryResult = results.find(r => r.entry.id === 'pattern-retry-backoff');
|
|
269
|
+
const retryResult = results.find((r) => r.entry.id === 'pattern-retry-backoff');
|
|
258
270
|
expect(retryResult).toBeDefined();
|
|
259
271
|
// With COGNEE_WEIGHTS, vector component is 35% of total
|
|
260
272
|
// So vector score should contribute meaningfully
|
|
@@ -271,7 +283,7 @@ describe('Hybrid search: Cognee → FTS5 → Zettelkasten', () => {
|
|
|
271
283
|
|
|
272
284
|
const results = await brain.intelligentSearch('exponential backoff');
|
|
273
285
|
// All results should have vector=0 (no Cognee matches)
|
|
274
|
-
expect(results.every(r => r.breakdown.vector === 0)).toBe(true);
|
|
286
|
+
expect(results.every((r) => r.breakdown.vector === 0)).toBe(true);
|
|
275
287
|
});
|
|
276
288
|
});
|
|
277
289
|
|
|
@@ -280,14 +292,16 @@ describe('Hybrid search: Cognee → FTS5 → Zettelkasten', () => {
|
|
|
280
292
|
describe('graceful degradation', () => {
|
|
281
293
|
it('should fall back to FTS5 when Cognee search throws', async () => {
|
|
282
294
|
const mockCognee = makeMockCognee({ available: true });
|
|
283
|
-
(mockCognee.search as ReturnType<typeof vi.fn>).mockRejectedValue(
|
|
295
|
+
(mockCognee.search as ReturnType<typeof vi.fn>).mockRejectedValue(
|
|
296
|
+
new Error('Cognee crashed'),
|
|
297
|
+
);
|
|
284
298
|
const brain = new Brain(vault, mockCognee);
|
|
285
299
|
|
|
286
300
|
const results = await brain.intelligentSearch('retry backoff');
|
|
287
301
|
// Should still return FTS5 results despite Cognee failure
|
|
288
302
|
expect(results.length).toBeGreaterThan(0);
|
|
289
303
|
// All vector scores should be 0 (Cognee failed)
|
|
290
|
-
expect(results.every(r => r.breakdown.vector === 0)).toBe(true);
|
|
304
|
+
expect(results.every((r) => r.breakdown.vector === 0)).toBe(true);
|
|
291
305
|
});
|
|
292
306
|
|
|
293
307
|
it('should handle Cognee becoming unavailable mid-session', async () => {
|
|
@@ -306,7 +320,7 @@ describe('Hybrid search: Cognee → FTS5 → Zettelkasten', () => {
|
|
|
306
320
|
|
|
307
321
|
// First search — Cognee available
|
|
308
322
|
const results1 = await brain.intelligentSearch('retry');
|
|
309
|
-
const hasVector1 = results1.some(r => r.breakdown.vector > 0);
|
|
323
|
+
const hasVector1 = results1.some((r) => r.breakdown.vector > 0);
|
|
310
324
|
expect(hasVector1).toBe(true);
|
|
311
325
|
|
|
312
326
|
// Cognee goes down
|
|
@@ -315,7 +329,7 @@ describe('Hybrid search: Cognee → FTS5 → Zettelkasten', () => {
|
|
|
315
329
|
// Second search — should fall back gracefully
|
|
316
330
|
const results2 = await brain.intelligentSearch('retry');
|
|
317
331
|
expect(results2.length).toBeGreaterThan(0);
|
|
318
|
-
expect(results2.every(r => r.breakdown.vector === 0)).toBe(true);
|
|
332
|
+
expect(results2.every((r) => r.breakdown.vector === 0)).toBe(true);
|
|
319
333
|
});
|
|
320
334
|
});
|
|
321
335
|
|
|
@@ -356,8 +370,8 @@ describe('Hybrid search: Cognee → FTS5 → Zettelkasten', () => {
|
|
|
356
370
|
const withoutDomain = await brain.intelligentSearch('resilience');
|
|
357
371
|
|
|
358
372
|
// When domain filter matches, domainMatch should be higher
|
|
359
|
-
const archResult = withDomain.find(r => r.entry.domain === 'architecture');
|
|
360
|
-
const noFilterResult = withoutDomain.find(r => r.entry.id === archResult?.entry.id);
|
|
373
|
+
const archResult = withDomain.find((r) => r.entry.domain === 'architecture');
|
|
374
|
+
const noFilterResult = withoutDomain.find((r) => r.entry.id === archResult?.entry.id);
|
|
361
375
|
|
|
362
376
|
if (archResult && noFilterResult) {
|
|
363
377
|
expect(archResult.breakdown.domainMatch).toBeGreaterThanOrEqual(
|
|
@@ -373,7 +387,7 @@ describe('Hybrid search: Cognee → FTS5 → Zettelkasten', () => {
|
|
|
373
387
|
it('should find connected entries via link traversal', () => {
|
|
374
388
|
// Search finds retry-backoff → traverse links → discover circuit-breaker
|
|
375
389
|
const links = linkManager.getLinks('pattern-retry-backoff');
|
|
376
|
-
const linkedIds = links.map(l =>
|
|
390
|
+
const linkedIds = links.map((l) =>
|
|
377
391
|
l.sourceId === 'pattern-retry-backoff' ? l.targetId : l.sourceId,
|
|
378
392
|
);
|
|
379
393
|
expect(linkedIds).toContain('pattern-circuit-breaker');
|
|
@@ -383,14 +397,14 @@ describe('Hybrid search: Cognee → FTS5 → Zettelkasten', () => {
|
|
|
383
397
|
it('should traverse 2 hops to discover indirect connections', () => {
|
|
384
398
|
// retry-backoff → circuit-breaker → polling-no-timeout (via contradicts)
|
|
385
399
|
const traversed = linkManager.traverse('pattern-retry-backoff', 2);
|
|
386
|
-
const ids = traversed.map(e => e.id);
|
|
400
|
+
const ids = traversed.map((e) => e.id);
|
|
387
401
|
expect(ids).toContain('pattern-circuit-breaker');
|
|
388
402
|
expect(ids).toContain('anti-pattern-polling-no-timeout');
|
|
389
403
|
});
|
|
390
404
|
|
|
391
405
|
it('should identify contradicting anti-patterns', () => {
|
|
392
406
|
const links = linkManager.getLinks('anti-pattern-polling-no-timeout');
|
|
393
|
-
const contradictions = links.filter(l => l.linkType === 'contradicts');
|
|
407
|
+
const contradictions = links.filter((l) => l.linkType === 'contradicts');
|
|
394
408
|
expect(contradictions.length).toBeGreaterThan(0);
|
|
395
409
|
});
|
|
396
410
|
});
|
|
@@ -424,7 +438,7 @@ describe('Hybrid search: Cognee → FTS5 → Zettelkasten', () => {
|
|
|
424
438
|
type: 'anti-pattern',
|
|
425
439
|
});
|
|
426
440
|
if (results.length > 0) {
|
|
427
|
-
expect(results.every(r => r.entry.type === 'anti-pattern')).toBe(true);
|
|
441
|
+
expect(results.every((r) => r.entry.type === 'anti-pattern')).toBe(true);
|
|
428
442
|
}
|
|
429
443
|
});
|
|
430
444
|
|
|
@@ -450,7 +464,7 @@ describe('Hybrid search: Cognee → FTS5 → Zettelkasten', () => {
|
|
|
450
464
|
|
|
451
465
|
const results = await brain.intelligentSearch('retry');
|
|
452
466
|
// Should not crash and should use the higher score
|
|
453
|
-
const retryResult = results.find(r => r.entry.id === 'pattern-retry-backoff');
|
|
467
|
+
const retryResult = results.find((r) => r.entry.id === 'pattern-retry-backoff');
|
|
454
468
|
expect(retryResult).toBeDefined();
|
|
455
469
|
// Should use max score (0.9, not 0.7)
|
|
456
470
|
expect(retryResult!.breakdown.vector).toBeCloseTo(0.9, 1);
|
|
@@ -37,15 +37,19 @@ function makeEntry(overrides: Partial<IntelligenceEntry> = {}): IntelligenceEntr
|
|
|
37
37
|
/**
|
|
38
38
|
* Create a mock CogneeClient with controllable availability and behavior.
|
|
39
39
|
*/
|
|
40
|
-
function makeMockCognee(
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
40
|
+
function makeMockCognee(
|
|
41
|
+
overrides: {
|
|
42
|
+
available?: boolean;
|
|
43
|
+
addResult?: { added: number };
|
|
44
|
+
addShouldFail?: boolean;
|
|
45
|
+
deleteResult?: { deleted: number };
|
|
46
|
+
} = {},
|
|
47
|
+
): CogneeClient {
|
|
46
48
|
const available = overrides.available ?? true;
|
|
47
49
|
const client = {
|
|
48
|
-
get isAvailable() {
|
|
50
|
+
get isAvailable() {
|
|
51
|
+
return available;
|
|
52
|
+
},
|
|
49
53
|
healthCheck: vi.fn().mockResolvedValue({ available, url: 'http://mock:8000', latencyMs: 1 }),
|
|
50
54
|
addEntries: vi.fn().mockImplementation(async () => {
|
|
51
55
|
if (overrides.addShouldFail) throw new Error('Cognee ingest failed');
|
|
@@ -102,25 +106,19 @@ describe('CogneeSyncManager — deep coverage', () => {
|
|
|
102
106
|
it('should change when title changes', () => {
|
|
103
107
|
const base = makeEntry({ id: 'same-id' });
|
|
104
108
|
const modified = { ...base, title: 'Different Title' };
|
|
105
|
-
expect(CogneeSyncManager.contentHash(base)).not.toBe(
|
|
106
|
-
CogneeSyncManager.contentHash(modified),
|
|
107
|
-
);
|
|
109
|
+
expect(CogneeSyncManager.contentHash(base)).not.toBe(CogneeSyncManager.contentHash(modified));
|
|
108
110
|
});
|
|
109
111
|
|
|
110
112
|
it('should change when description changes', () => {
|
|
111
113
|
const base = makeEntry({ id: 'same-id' });
|
|
112
114
|
const modified = { ...base, description: 'Updated description' };
|
|
113
|
-
expect(CogneeSyncManager.contentHash(base)).not.toBe(
|
|
114
|
-
CogneeSyncManager.contentHash(modified),
|
|
115
|
-
);
|
|
115
|
+
expect(CogneeSyncManager.contentHash(base)).not.toBe(CogneeSyncManager.contentHash(modified));
|
|
116
116
|
});
|
|
117
117
|
|
|
118
118
|
it('should change when tags change', () => {
|
|
119
119
|
const base = makeEntry({ id: 'same-id', tags: ['a'] });
|
|
120
120
|
const modified = { ...base, tags: ['a', 'b'] };
|
|
121
|
-
expect(CogneeSyncManager.contentHash(base)).not.toBe(
|
|
122
|
-
CogneeSyncManager.contentHash(modified),
|
|
123
|
-
);
|
|
121
|
+
expect(CogneeSyncManager.contentHash(base)).not.toBe(CogneeSyncManager.contentHash(modified));
|
|
124
122
|
});
|
|
125
123
|
|
|
126
124
|
it('should NOT change when only updatedAt changes (not in hash)', () => {
|
|
@@ -147,18 +145,22 @@ describe('CogneeSyncManager — deep coverage', () => {
|
|
|
147
145
|
const syncMgr = runtime.syncManager;
|
|
148
146
|
const entry = makeEntry({ id: 'hash-test' });
|
|
149
147
|
syncMgr.enqueue('ingest', entry.id, entry);
|
|
150
|
-
const row = runtime.vault
|
|
151
|
-
|
|
152
|
-
|
|
148
|
+
const row = runtime.vault
|
|
149
|
+
.getProvider()
|
|
150
|
+
.get<{ content_hash: string }>(
|
|
151
|
+
`SELECT content_hash FROM cognee_sync_queue WHERE entry_id = 'hash-test'`,
|
|
152
|
+
);
|
|
153
153
|
expect(row?.content_hash).toMatch(/^[0-9a-f]{16}$/);
|
|
154
154
|
});
|
|
155
155
|
|
|
156
156
|
it('should store null hash when entry is not provided (delete op)', () => {
|
|
157
157
|
const syncMgr = runtime.syncManager;
|
|
158
158
|
syncMgr.enqueue('delete', 'deleted-entry');
|
|
159
|
-
const row = runtime.vault
|
|
160
|
-
|
|
161
|
-
|
|
159
|
+
const row = runtime.vault
|
|
160
|
+
.getProvider()
|
|
161
|
+
.get<{ content_hash: string | null }>(
|
|
162
|
+
`SELECT content_hash FROM cognee_sync_queue WHERE entry_id = 'deleted-entry'`,
|
|
163
|
+
);
|
|
162
164
|
expect(row?.content_hash).toBeNull();
|
|
163
165
|
});
|
|
164
166
|
});
|
|
@@ -186,10 +188,12 @@ describe('CogneeSyncManager — deep coverage', () => {
|
|
|
186
188
|
expect(mockCognee.addEntries).toHaveBeenCalledTimes(1);
|
|
187
189
|
|
|
188
190
|
// Verify ingested hash was updated on the entries table
|
|
189
|
-
const row = runtime.vault
|
|
190
|
-
|
|
191
|
-
{
|
|
192
|
-
|
|
191
|
+
const row = runtime.vault
|
|
192
|
+
.getProvider()
|
|
193
|
+
.get<{ cognee_ingested_hash: string }>(
|
|
194
|
+
`SELECT cognee_ingested_hash FROM entries WHERE id = @id`,
|
|
195
|
+
{ id: entry.id },
|
|
196
|
+
);
|
|
193
197
|
expect(row?.cognee_ingested_hash).toMatch(/^[0-9a-f]{16}$/);
|
|
194
198
|
|
|
195
199
|
// Verify queue item is marked completed
|
|
@@ -214,10 +218,12 @@ describe('CogneeSyncManager — deep coverage', () => {
|
|
|
214
218
|
expect(processed).toBe(1);
|
|
215
219
|
|
|
216
220
|
// Verify ingested hash was cleared
|
|
217
|
-
const row = runtime.vault
|
|
218
|
-
|
|
219
|
-
{
|
|
220
|
-
|
|
221
|
+
const row = runtime.vault
|
|
222
|
+
.getProvider()
|
|
223
|
+
.get<{ cognee_ingested_hash: string | null }>(
|
|
224
|
+
`SELECT cognee_ingested_hash FROM entries WHERE id = @id`,
|
|
225
|
+
{ id: entry.id },
|
|
226
|
+
);
|
|
221
227
|
expect(row?.cognee_ingested_hash).toBeNull();
|
|
222
228
|
});
|
|
223
229
|
|
|
@@ -321,9 +327,11 @@ describe('CogneeSyncManager — deep coverage', () => {
|
|
|
321
327
|
await syncMgr.drain();
|
|
322
328
|
await syncMgr.drain();
|
|
323
329
|
|
|
324
|
-
const row = runtime.vault
|
|
325
|
-
|
|
326
|
-
|
|
330
|
+
const row = runtime.vault
|
|
331
|
+
.getProvider()
|
|
332
|
+
.get<{ error: string; status: string }>(
|
|
333
|
+
`SELECT error, status FROM cognee_sync_queue WHERE entry_id = 'error-msg-test' AND dataset = 'test-dataset'`,
|
|
334
|
+
);
|
|
327
335
|
expect(row?.status).toBe('failed');
|
|
328
336
|
expect(row?.error).toContain('Cognee ingest failed');
|
|
329
337
|
});
|
|
@@ -336,13 +344,15 @@ describe('CogneeSyncManager — deep coverage', () => {
|
|
|
336
344
|
// Mock: fail only for bad-entry
|
|
337
345
|
let callCount = 0;
|
|
338
346
|
const mockCognee = makeMockCognee({ available: true });
|
|
339
|
-
(mockCognee.addEntries as ReturnType<typeof vi.fn>).mockImplementation(
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
347
|
+
(mockCognee.addEntries as ReturnType<typeof vi.fn>).mockImplementation(
|
|
348
|
+
async (entries: IntelligenceEntry[]) => {
|
|
349
|
+
callCount++;
|
|
350
|
+
if (entries.some((e) => e.id === 'bad-entry')) {
|
|
351
|
+
throw new Error('Selective failure');
|
|
352
|
+
}
|
|
353
|
+
return { added: entries.length };
|
|
354
|
+
},
|
|
355
|
+
);
|
|
346
356
|
|
|
347
357
|
const syncMgr = new CogneeSyncManager(
|
|
348
358
|
runtime.vault.getProvider(),
|
|
@@ -379,10 +389,11 @@ describe('CogneeSyncManager — deep coverage', () => {
|
|
|
379
389
|
runtime.vault.seed([entry]);
|
|
380
390
|
|
|
381
391
|
// Simulate a previously-synced entry by setting a hash
|
|
382
|
-
runtime.vault
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
392
|
+
runtime.vault
|
|
393
|
+
.getProvider()
|
|
394
|
+
.run(`UPDATE entries SET cognee_ingested_hash = 'old-hash-value-xx' WHERE id = @id`, {
|
|
395
|
+
id: entry.id,
|
|
396
|
+
});
|
|
386
397
|
|
|
387
398
|
// Clear queue
|
|
388
399
|
runtime.vault.getProvider().run('DELETE FROM cognee_sync_queue');
|
|
@@ -399,10 +410,12 @@ describe('CogneeSyncManager — deep coverage', () => {
|
|
|
399
410
|
|
|
400
411
|
// Set the correct hash
|
|
401
412
|
const correctHash = CogneeSyncManager.contentHash(entry);
|
|
402
|
-
runtime.vault
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
413
|
+
runtime.vault
|
|
414
|
+
.getProvider()
|
|
415
|
+
.run(`UPDATE entries SET cognee_ingested_hash = @hash WHERE id = @id`, {
|
|
416
|
+
hash: correctHash,
|
|
417
|
+
id: entry.id,
|
|
418
|
+
});
|
|
406
419
|
|
|
407
420
|
// Clear queue
|
|
408
421
|
runtime.vault.getProvider().run('DELETE FROM cognee_sync_queue');
|
|
@@ -423,9 +436,11 @@ describe('CogneeSyncManager — deep coverage', () => {
|
|
|
423
436
|
syncMgr.reconcile();
|
|
424
437
|
syncMgr.reconcile(); // Second reconcile should not create duplicates
|
|
425
438
|
|
|
426
|
-
const rows = runtime.vault
|
|
427
|
-
|
|
428
|
-
|
|
439
|
+
const rows = runtime.vault
|
|
440
|
+
.getProvider()
|
|
441
|
+
.all<{ id: number }>(
|
|
442
|
+
`SELECT id FROM cognee_sync_queue WHERE entry_id = 'no-dupes' AND status = 'pending'`,
|
|
443
|
+
);
|
|
429
444
|
expect(rows.length).toBe(1);
|
|
430
445
|
});
|
|
431
446
|
|
|
@@ -437,22 +452,25 @@ describe('CogneeSyncManager — deep coverage', () => {
|
|
|
437
452
|
// Entry 2: previously synced (stale hash)
|
|
438
453
|
const entry2 = makeEntry({ id: 'stale-hash-entry' });
|
|
439
454
|
runtime.vault.seed([entry2]);
|
|
440
|
-
runtime.vault
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
455
|
+
runtime.vault
|
|
456
|
+
.getProvider()
|
|
457
|
+
.run(`UPDATE entries SET cognee_ingested_hash = 'stale-value-xxxxx' WHERE id = @id`, {
|
|
458
|
+
id: entry2.id,
|
|
459
|
+
});
|
|
444
460
|
|
|
445
461
|
// Clear queue
|
|
446
462
|
runtime.vault.getProvider().run('DELETE FROM cognee_sync_queue');
|
|
447
463
|
|
|
448
464
|
runtime.syncManager.reconcile();
|
|
449
465
|
|
|
450
|
-
const row1 = runtime.vault
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
const row2 = runtime.vault
|
|
454
|
-
|
|
455
|
-
|
|
466
|
+
const row1 = runtime.vault
|
|
467
|
+
.getProvider()
|
|
468
|
+
.get<{ op: string }>(`SELECT op FROM cognee_sync_queue WHERE entry_id = 'null-hash-entry'`);
|
|
469
|
+
const row2 = runtime.vault
|
|
470
|
+
.getProvider()
|
|
471
|
+
.get<{ op: string }>(
|
|
472
|
+
`SELECT op FROM cognee_sync_queue WHERE entry_id = 'stale-hash-entry'`,
|
|
473
|
+
);
|
|
456
474
|
|
|
457
475
|
expect(row1?.op).toBe('ingest');
|
|
458
476
|
expect(row2?.op).toBe('update');
|
|
@@ -469,8 +487,12 @@ describe('CogneeSyncManager — deep coverage', () => {
|
|
|
469
487
|
// Start with unavailable Cognee
|
|
470
488
|
let isAvailable = false;
|
|
471
489
|
const mockCognee = {
|
|
472
|
-
get isAvailable() {
|
|
473
|
-
|
|
490
|
+
get isAvailable() {
|
|
491
|
+
return isAvailable;
|
|
492
|
+
},
|
|
493
|
+
healthCheck: vi
|
|
494
|
+
.fn()
|
|
495
|
+
.mockImplementation(async () => ({ available: isAvailable, url: 'mock', latencyMs: 1 })),
|
|
474
496
|
addEntries: vi.fn().mockResolvedValue({ added: 1 }),
|
|
475
497
|
deleteEntries: vi.fn().mockResolvedValue({ deleted: 1 }),
|
|
476
498
|
cognify: vi.fn().mockResolvedValue({ status: 'ok' }),
|
|
@@ -502,9 +524,11 @@ describe('CogneeSyncManager — deep coverage', () => {
|
|
|
502
524
|
});
|
|
503
525
|
|
|
504
526
|
it('should NOT drain when Cognee stays available (no flip)', async () => {
|
|
505
|
-
|
|
527
|
+
const isAvailable = true;
|
|
506
528
|
const mockCognee = {
|
|
507
|
-
get isAvailable() {
|
|
529
|
+
get isAvailable() {
|
|
530
|
+
return isAvailable;
|
|
531
|
+
},
|
|
508
532
|
healthCheck: vi.fn().mockResolvedValue({ available: true, url: 'mock', latencyMs: 1 }),
|
|
509
533
|
addEntries: vi.fn().mockResolvedValue({ added: 1 }),
|
|
510
534
|
deleteEntries: vi.fn(),
|
|
@@ -26,9 +26,9 @@ describe('createCuratorExtraOps', () => {
|
|
|
26
26
|
ops = createCuratorExtraOps(runtime);
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
-
it('should return
|
|
29
|
+
it('should return 9 ops', () => {
|
|
30
30
|
setup();
|
|
31
|
-
expect(ops).toHaveLength(
|
|
31
|
+
expect(ops).toHaveLength(9);
|
|
32
32
|
const names = ops.map((o) => o.name);
|
|
33
33
|
expect(names).toEqual([
|
|
34
34
|
'curator_entry_history',
|
|
@@ -36,6 +36,10 @@ describe('createCuratorExtraOps', () => {
|
|
|
36
36
|
'curator_queue_stats',
|
|
37
37
|
'curator_enrich',
|
|
38
38
|
'curator_hybrid_contradictions',
|
|
39
|
+
'curator_pipeline_status',
|
|
40
|
+
'curator_enqueue_pipeline',
|
|
41
|
+
'curator_schedule_start',
|
|
42
|
+
'curator_schedule_stop',
|
|
39
43
|
]);
|
|
40
44
|
});
|
|
41
45
|
|