@soleri/core 7.0.0 → 8.1.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 +159 -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 +59 -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 +86 -0
- package/dist/curator/quality-gate.js.map +1 -0
- package/dist/domain-packs/index.d.ts +0 -3
- package/dist/domain-packs/index.d.ts.map +1 -1
- package/dist/domain-packs/index.js +0 -3
- package/dist/domain-packs/index.js.map +1 -1
- package/dist/domain-packs/loader.d.ts.map +1 -1
- package/dist/domain-packs/loader.js +20 -4
- package/dist/domain-packs/loader.js.map +1 -1
- package/dist/domain-packs/pack-runtime.d.ts +5 -5
- package/dist/domain-packs/pack-runtime.d.ts.map +1 -1
- package/dist/domain-packs/pack-runtime.js +2 -2
- package/dist/domain-packs/pack-runtime.js.map +1 -1
- package/dist/domain-packs/types.d.ts +8 -2
- package/dist/domain-packs/types.d.ts.map +1 -1
- package/dist/domain-packs/types.js.map +1 -1
- package/dist/engine/bin/soleri-engine.js +13 -2
- package/dist/engine/bin/soleri-engine.js.map +1 -1
- package/dist/engine/index.d.ts +2 -0
- package/dist/engine/index.d.ts.map +1 -1
- package/dist/engine/index.js +1 -0
- package/dist/engine/index.js.map +1 -1
- package/dist/engine/module-manifest.d.ts +28 -0
- package/dist/engine/module-manifest.d.ts.map +1 -0
- package/dist/engine/module-manifest.js +85 -0
- package/dist/engine/module-manifest.js.map +1 -0
- package/dist/engine/register-engine.d.ts +19 -0
- package/dist/engine/register-engine.d.ts.map +1 -1
- package/dist/engine/register-engine.js +15 -2
- package/dist/engine/register-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/index.d.ts +0 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +0 -1
- package/dist/index.js.map +1 -1
- package/dist/intake/content-classifier.d.ts.map +1 -1
- package/dist/intake/content-classifier.js +0 -2
- package/dist/intake/content-classifier.js.map +1 -1
- 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 +45 -5
- package/dist/llm/llm-client.js.map +1 -1
- package/dist/llm/oauth-discovery.d.ts +18 -0
- package/dist/llm/oauth-discovery.d.ts.map +1 -0
- package/dist/llm/oauth-discovery.js +130 -0
- package/dist/llm/oauth-discovery.js.map +1 -0
- package/dist/llm/types.d.ts +4 -2
- package/dist/llm/types.d.ts.map +1 -1
- package/dist/packs/pack-installer.d.ts +2 -1
- package/dist/packs/pack-installer.d.ts.map +1 -1
- package/dist/packs/pack-installer.js +10 -1
- package/dist/packs/pack-installer.js.map +1 -1
- package/dist/persistence/index.d.ts +0 -1
- package/dist/persistence/index.d.ts.map +1 -1
- package/dist/persistence/index.js +0 -1
- package/dist/persistence/index.js.map +1 -1
- package/dist/persistence/types.d.ts +2 -6
- package/dist/persistence/types.d.ts.map +1 -1
- 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/plugins/index.d.ts +4 -0
- package/dist/plugins/index.d.ts.map +1 -1
- package/dist/plugins/index.js +4 -0
- package/dist/plugins/index.js.map +1 -1
- package/dist/plugins/plugin-registry.d.ts +4 -0
- package/dist/plugins/plugin-registry.d.ts.map +1 -1
- package/dist/plugins/plugin-registry.js +4 -0
- package/dist/plugins/plugin-registry.js.map +1 -1
- package/dist/plugins/types.d.ts +32 -27
- package/dist/plugins/types.d.ts.map +1 -1
- package/dist/plugins/types.js +6 -3
- package/dist/plugins/types.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 +56 -0
- package/dist/runtime/claude-md-helpers.d.ts.map +1 -0
- package/dist/runtime/claude-md-helpers.js +160 -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 +5 -2
- 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/pack-ops.d.ts +3 -0
- package/dist/runtime/pack-ops.d.ts.map +1 -1
- package/dist/runtime/pack-ops.js +18 -1
- package/dist/runtime/pack-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/plugin-ops.d.ts.map +1 -1
- package/dist/runtime/plugin-ops.js +3 -0
- package/dist/runtime/plugin-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 +154 -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 +3 -7
- package/dist/runtime/vault-linking-ops.js.map +1 -1
- package/dist/vault/vault.d.ts +34 -0
- package/dist/vault/vault.d.ts.map +1 -1
- package/dist/vault/vault.js +89 -3
- package/dist/vault/vault.js.map +1 -1
- package/package.json +6 -4
- 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 +545 -0
- package/src/__tests__/memory-extra-ops.test.ts +2 -2
- package/src/__tests__/module-manifest-drift.test.ts +59 -0
- 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 +216 -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 +86 -0
- package/src/curator/quality-gate.ts +127 -0
- package/src/domain-packs/index.ts +0 -6
- package/src/domain-packs/loader.ts +25 -5
- package/src/domain-packs/pack-runtime.ts +6 -6
- package/src/domain-packs/types.ts +8 -2
- package/src/engine/bin/soleri-engine.ts +18 -2
- package/src/engine/index.ts +2 -0
- package/src/engine/module-manifest.ts +99 -0
- package/src/engine/register-engine.ts +21 -2
- 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/index.ts +0 -1
- package/src/intake/content-classifier.ts +0 -2
- package/src/intake/text-ingester.ts +234 -0
- package/src/llm/llm-client.ts +50 -7
- package/src/llm/oauth-discovery.ts +151 -0
- package/src/llm/types.ts +4 -2
- package/src/packs/pack-installer.ts +16 -1
- package/src/persistence/index.ts +0 -1
- package/src/persistence/types.ts +2 -6
- package/src/planning/evidence-collector.ts +247 -0
- package/src/planning/planner.ts +11 -0
- package/src/plugins/index.ts +4 -0
- package/src/plugins/plugin-registry.ts +6 -1
- package/src/plugins/types.ts +10 -5
- 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 +218 -0
- package/src/runtime/curator-extra-ops.ts +86 -3
- package/src/runtime/facades/admin-facade.ts +5 -2
- 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/pack-ops.ts +26 -1
- package/src/runtime/planning-extra-ops.ts +94 -0
- package/src/runtime/playbook-ops.ts +1 -1
- package/src/runtime/plugin-ops.ts +3 -0
- package/src/runtime/runtime.ts +138 -2
- package/src/runtime/session-briefing.ts +175 -0
- package/src/runtime/types.ts +23 -0
- package/src/runtime/vault-linking-ops.ts +3 -7
- package/src/vault/vault.ts +105 -4
- package/src/__tests__/postgres-provider.test.ts +0 -116
- package/src/persistence/postgres-provider.ts +0 -310
|
@@ -0,0 +1,307 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for async infrastructure — event bus, job queue, pipeline runner.
|
|
3
|
+
* Phase 1 of #210: generic infrastructure modules.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { describe, it, expect, beforeAll, afterAll, vi } from 'vitest';
|
|
7
|
+
import { TypedEventBus } from '../events/event-bus.js';
|
|
8
|
+
import { JobQueue } from '../queue/job-queue.js';
|
|
9
|
+
import { PipelineRunner } from '../queue/pipeline-runner.js';
|
|
10
|
+
import { Vault } from '../vault/vault.js';
|
|
11
|
+
|
|
12
|
+
// ─── Event Bus ───────────────────────────────────────────────────────
|
|
13
|
+
|
|
14
|
+
describe('TypedEventBus', () => {
|
|
15
|
+
type TestEvents = {
|
|
16
|
+
'item:created': { id: string; title: string };
|
|
17
|
+
'item:deleted': { id: string };
|
|
18
|
+
tick: { count: number };
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
it('emits and receives typed events', () => {
|
|
22
|
+
const bus = new TypedEventBus<TestEvents>();
|
|
23
|
+
let received: TestEvents['item:created'] | null = null;
|
|
24
|
+
|
|
25
|
+
bus.on('item:created', (payload) => {
|
|
26
|
+
received = payload;
|
|
27
|
+
});
|
|
28
|
+
bus.emit('item:created', { id: '1', title: 'Hello' });
|
|
29
|
+
|
|
30
|
+
expect(received).toEqual({ id: '1', title: 'Hello' });
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it('once listener fires only once', () => {
|
|
34
|
+
const bus = new TypedEventBus<TestEvents>();
|
|
35
|
+
let count = 0;
|
|
36
|
+
|
|
37
|
+
bus.once('tick', () => {
|
|
38
|
+
count++;
|
|
39
|
+
});
|
|
40
|
+
bus.emit('tick', { count: 1 });
|
|
41
|
+
bus.emit('tick', { count: 2 });
|
|
42
|
+
|
|
43
|
+
expect(count).toBe(1);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it('off removes a listener', () => {
|
|
47
|
+
const bus = new TypedEventBus<TestEvents>();
|
|
48
|
+
let count = 0;
|
|
49
|
+
const listener = () => {
|
|
50
|
+
count++;
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
bus.on('tick', listener);
|
|
54
|
+
bus.emit('tick', { count: 1 });
|
|
55
|
+
bus.off('tick', listener);
|
|
56
|
+
bus.emit('tick', { count: 2 });
|
|
57
|
+
|
|
58
|
+
expect(count).toBe(1);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it('listenerCount tracks total listeners', () => {
|
|
62
|
+
const bus = new TypedEventBus<TestEvents>();
|
|
63
|
+
expect(bus.listenerCount()).toBe(0);
|
|
64
|
+
|
|
65
|
+
bus.on('item:created', () => {});
|
|
66
|
+
bus.on('item:deleted', () => {});
|
|
67
|
+
expect(bus.listenerCount()).toBe(2);
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it('removeAllListeners clears everything', () => {
|
|
71
|
+
const bus = new TypedEventBus<TestEvents>();
|
|
72
|
+
bus.on('item:created', () => {});
|
|
73
|
+
bus.on('tick', () => {});
|
|
74
|
+
bus.removeAllListeners();
|
|
75
|
+
expect(bus.listenerCount()).toBe(0);
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
it('supports multiple listeners on same event', () => {
|
|
79
|
+
const bus = new TypedEventBus<TestEvents>();
|
|
80
|
+
const results: string[] = [];
|
|
81
|
+
|
|
82
|
+
bus.on('item:created', (p) => results.push('A:' + p.id));
|
|
83
|
+
bus.on('item:created', (p) => results.push('B:' + p.id));
|
|
84
|
+
bus.emit('item:created', { id: '1', title: 'Test' });
|
|
85
|
+
|
|
86
|
+
expect(results).toEqual(['A:1', 'B:1']);
|
|
87
|
+
});
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
// ─── Job Queue ───────────────────────────────────────────────────────
|
|
91
|
+
|
|
92
|
+
describe('JobQueue', () => {
|
|
93
|
+
let vault: Vault;
|
|
94
|
+
let queue: JobQueue;
|
|
95
|
+
|
|
96
|
+
beforeAll(() => {
|
|
97
|
+
vault = new Vault(':memory:');
|
|
98
|
+
queue = new JobQueue(vault.getProvider());
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
afterAll(() => {
|
|
102
|
+
vault.close();
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
it('enqueue creates a job and returns ID', () => {
|
|
106
|
+
const id = queue.enqueue('tag-normalize', { entryId: 'e1' });
|
|
107
|
+
expect(id).toBeTruthy();
|
|
108
|
+
expect(id.length).toBeGreaterThan(0);
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
it('dequeue returns oldest pending job', () => {
|
|
112
|
+
const id = queue.enqueue('dedup-check');
|
|
113
|
+
const job = queue.dequeue();
|
|
114
|
+
expect(job).not.toBeNull();
|
|
115
|
+
expect(job!.type).toBe('tag-normalize'); // First enqueued from previous test
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
it('dequeue marks job as running', () => {
|
|
119
|
+
const job = queue.get(queue.enqueue('test-job'));
|
|
120
|
+
expect(job!.status).toBe('pending');
|
|
121
|
+
|
|
122
|
+
const dequeued = queue.dequeue();
|
|
123
|
+
expect(dequeued!.status).toBe('running');
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
it('complete marks job as completed with result', () => {
|
|
127
|
+
const id = queue.enqueue('test-complete');
|
|
128
|
+
queue.dequeue(); // Mark running
|
|
129
|
+
queue.complete(id, { ok: true });
|
|
130
|
+
|
|
131
|
+
const job = queue.get(id);
|
|
132
|
+
expect(job!.status).toBe('completed');
|
|
133
|
+
expect(job!.result).toEqual({ ok: true });
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
it('fail marks job as failed with error', () => {
|
|
137
|
+
const id = queue.enqueue('test-fail');
|
|
138
|
+
queue.dequeue();
|
|
139
|
+
queue.fail(id, 'something went wrong');
|
|
140
|
+
|
|
141
|
+
const job = queue.get(id);
|
|
142
|
+
expect(job!.status).toBe('failed');
|
|
143
|
+
expect(job!.error).toBe('something went wrong');
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
it('retry resets failed job to pending', () => {
|
|
147
|
+
const id = queue.enqueue('test-retry');
|
|
148
|
+
queue.dequeue();
|
|
149
|
+
queue.fail(id, 'transient error');
|
|
150
|
+
|
|
151
|
+
const retried = queue.retry(id);
|
|
152
|
+
expect(retried).toBe(true);
|
|
153
|
+
|
|
154
|
+
const job = queue.get(id);
|
|
155
|
+
expect(job!.status).toBe('pending');
|
|
156
|
+
expect(job!.retryCount).toBe(1);
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
it('retry returns false when max retries exceeded', () => {
|
|
160
|
+
const id = queue.enqueue('test-max-retry', { maxRetries: 1 });
|
|
161
|
+
queue.dequeue();
|
|
162
|
+
queue.fail(id, 'fail 1');
|
|
163
|
+
queue.retry(id);
|
|
164
|
+
queue.dequeue();
|
|
165
|
+
queue.fail(id, 'fail 2');
|
|
166
|
+
|
|
167
|
+
const retried = queue.retry(id);
|
|
168
|
+
expect(retried).toBe(false);
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
it('respects DAG dependencies', () => {
|
|
172
|
+
const dep1 = queue.enqueue('dep-job-1');
|
|
173
|
+
const dep2 = queue.enqueue('dep-job-2', { dependsOn: [dep1] });
|
|
174
|
+
|
|
175
|
+
// dep2 should not dequeue because dep1 is not completed
|
|
176
|
+
// Clear running jobs first
|
|
177
|
+
const ready = queue.dequeueReady(10);
|
|
178
|
+
const readyIds = ready.map((j) => j.id);
|
|
179
|
+
expect(readyIds).toContain(dep1);
|
|
180
|
+
expect(readyIds).not.toContain(dep2);
|
|
181
|
+
|
|
182
|
+
// Complete dep1
|
|
183
|
+
queue.complete(dep1, {});
|
|
184
|
+
|
|
185
|
+
// Now dep2 should be ready
|
|
186
|
+
const ready2 = queue.dequeueReady(10);
|
|
187
|
+
const readyIds2 = ready2.map((j) => j.id);
|
|
188
|
+
expect(readyIds2).toContain(dep2);
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
it('groups jobs by pipeline', () => {
|
|
192
|
+
const pid = 'pipeline-123';
|
|
193
|
+
queue.enqueue('step-1', { pipelineId: pid });
|
|
194
|
+
queue.enqueue('step-2', { pipelineId: pid });
|
|
195
|
+
|
|
196
|
+
const jobs = queue.getByPipeline(pid);
|
|
197
|
+
expect(jobs.length).toBe(2);
|
|
198
|
+
expect(jobs.every((j) => j.pipelineId === pid)).toBe(true);
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
it('getStats returns correct counts', () => {
|
|
202
|
+
const stats = queue.getStats();
|
|
203
|
+
expect(stats.total).toBeGreaterThan(0);
|
|
204
|
+
expect(typeof stats.pending).toBe('number');
|
|
205
|
+
expect(typeof stats.running).toBe('number');
|
|
206
|
+
expect(typeof stats.completed).toBe('number');
|
|
207
|
+
expect(typeof stats.failed).toBe('number');
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
it('purge removes old completed/failed jobs', () => {
|
|
211
|
+
// purge with 0 days should remove all completed/failed
|
|
212
|
+
const purged = queue.purge(0);
|
|
213
|
+
expect(purged).toBeGreaterThanOrEqual(0);
|
|
214
|
+
});
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
// ─── Pipeline Runner ─────────────────────────────────────────────────
|
|
218
|
+
|
|
219
|
+
describe('PipelineRunner', () => {
|
|
220
|
+
let vault: Vault;
|
|
221
|
+
let queue: JobQueue;
|
|
222
|
+
let runner: PipelineRunner;
|
|
223
|
+
|
|
224
|
+
beforeAll(() => {
|
|
225
|
+
vault = new Vault(':memory:');
|
|
226
|
+
queue = new JobQueue(vault.getProvider());
|
|
227
|
+
runner = new PipelineRunner(queue, 100); // 100ms poll for tests
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
afterAll(() => {
|
|
231
|
+
runner.stop();
|
|
232
|
+
vault.close();
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
it('registerHandler stores a handler for a job type', () => {
|
|
236
|
+
runner.registerHandler('test-type', async () => ({ done: true }));
|
|
237
|
+
expect(runner.hasHandler('test-type')).toBe(true);
|
|
238
|
+
expect(runner.hasHandler('unknown-type')).toBe(false);
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
it('processOnce dequeues and completes jobs', async () => {
|
|
242
|
+
const handler = vi.fn().mockResolvedValue({ processed: true });
|
|
243
|
+
runner.registerHandler('process-test', handler);
|
|
244
|
+
|
|
245
|
+
queue.enqueue('process-test', { payload: { key: 'value' } });
|
|
246
|
+
|
|
247
|
+
const processed = await runner.processOnce();
|
|
248
|
+
expect(processed).toBe(1);
|
|
249
|
+
expect(handler).toHaveBeenCalledTimes(1);
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
it('processOnce fails jobs with no handler', async () => {
|
|
253
|
+
const id = queue.enqueue('no-handler-type');
|
|
254
|
+
await runner.processOnce();
|
|
255
|
+
|
|
256
|
+
const job = queue.get(id);
|
|
257
|
+
expect(job!.status).toBe('failed');
|
|
258
|
+
expect(job!.error).toContain('No handler registered');
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
it('processOnce retries on handler error', async () => {
|
|
262
|
+
const handler = vi.fn().mockRejectedValue(new Error('transient'));
|
|
263
|
+
runner.registerHandler('flaky-type', handler);
|
|
264
|
+
|
|
265
|
+
const id = queue.enqueue('flaky-type');
|
|
266
|
+
await runner.processOnce();
|
|
267
|
+
|
|
268
|
+
const job = queue.get(id);
|
|
269
|
+
// Should be pending again (retried), not failed
|
|
270
|
+
expect(job!.status).toBe('pending');
|
|
271
|
+
expect(job!.retryCount).toBe(1);
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
it('getStatus returns runner state', () => {
|
|
275
|
+
const status = runner.getStatus();
|
|
276
|
+
expect(status.running).toBe(false); // Not started yet
|
|
277
|
+
expect(status.pollIntervalMs).toBe(100);
|
|
278
|
+
expect(status.jobsProcessed).toBeGreaterThanOrEqual(1);
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
it('start/stop controls background polling', async () => {
|
|
282
|
+
runner.start();
|
|
283
|
+
expect(runner.getStatus().running).toBe(true);
|
|
284
|
+
|
|
285
|
+
runner.stop();
|
|
286
|
+
expect(runner.getStatus().running).toBe(false);
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
it('respects DAG in pipeline execution', async () => {
|
|
290
|
+
const order: string[] = [];
|
|
291
|
+
runner.registerHandler('dag-step', async (job) => {
|
|
292
|
+
order.push(job.id);
|
|
293
|
+
return { step: job.id };
|
|
294
|
+
});
|
|
295
|
+
|
|
296
|
+
const step1 = queue.enqueue('dag-step', { pipelineId: 'dag-test' });
|
|
297
|
+
const step2 = queue.enqueue('dag-step', { pipelineId: 'dag-test', dependsOn: [step1] });
|
|
298
|
+
|
|
299
|
+
// First batch: only step1 should process
|
|
300
|
+
await runner.processOnce();
|
|
301
|
+
expect(order).toEqual([step1]);
|
|
302
|
+
|
|
303
|
+
// Second batch: step2 should now be ready
|
|
304
|
+
await runner.processOnce();
|
|
305
|
+
expect(order).toEqual([step1, step2]);
|
|
306
|
+
});
|
|
307
|
+
});
|
|
@@ -357,7 +357,10 @@ describe('CogneeClient — gap coverage', () => {
|
|
|
357
357
|
mockWithAuth(
|
|
358
358
|
async () =>
|
|
359
359
|
new Response(
|
|
360
|
-
JSON.stringify([
|
|
360
|
+
JSON.stringify([
|
|
361
|
+
{ id: 'r1', text: 42, score: 0.8 },
|
|
362
|
+
{ id: 'r2', score: 0.7 },
|
|
363
|
+
]),
|
|
361
364
|
{ status: 200 },
|
|
362
365
|
),
|
|
363
366
|
);
|
|
@@ -440,7 +443,8 @@ describe('CogneeClient — gap coverage', () => {
|
|
|
440
443
|
|
|
441
444
|
it('should give single result score 1.0', async () => {
|
|
442
445
|
mockWithAuth(
|
|
443
|
-
async () =>
|
|
446
|
+
async () =>
|
|
447
|
+
new Response(JSON.stringify([{ id: 'only', text: 'Sole result' }]), { status: 200 }),
|
|
444
448
|
);
|
|
445
449
|
const client = new CogneeClient();
|
|
446
450
|
await client.healthCheck();
|
|
@@ -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);
|