@soleri/forge 4.2.2 → 5.0.1
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/domain-manager.d.ts +2 -2
- package/dist/domain-manager.js +35 -16
- package/dist/domain-manager.js.map +1 -1
- package/dist/index.js +0 -0
- package/dist/knowledge-installer.js +18 -12
- package/dist/knowledge-installer.js.map +1 -1
- package/dist/patching.d.ts +15 -6
- package/dist/patching.js +37 -12
- package/dist/patching.js.map +1 -1
- package/dist/scaffolder.js +18 -28
- package/dist/scaffolder.js.map +1 -1
- package/dist/templates/brain.d.ts +6 -0
- package/dist/templates/brain.js +478 -0
- package/dist/templates/brain.js.map +1 -0
- package/dist/templates/core-facade.js +95 -47
- package/dist/templates/core-facade.js.map +1 -1
- package/dist/templates/entry-point.d.ts +4 -0
- package/dist/templates/entry-point.js +146 -89
- package/dist/templates/entry-point.js.map +1 -1
- package/dist/templates/facade-factory.d.ts +1 -0
- package/dist/templates/facade-factory.js +63 -0
- package/dist/templates/facade-factory.js.map +1 -0
- package/dist/templates/facade-types.d.ts +1 -0
- package/dist/templates/facade-types.js +46 -0
- package/dist/templates/facade-types.js.map +1 -0
- package/dist/templates/intelligence-loader.d.ts +1 -0
- package/dist/templates/intelligence-loader.js +43 -0
- package/dist/templates/intelligence-loader.js.map +1 -0
- package/dist/templates/intelligence-types.d.ts +1 -0
- package/dist/templates/intelligence-types.js +24 -0
- package/dist/templates/intelligence-types.js.map +1 -0
- package/dist/templates/llm-key-pool.d.ts +7 -0
- package/dist/templates/llm-key-pool.js +211 -0
- package/dist/templates/llm-key-pool.js.map +1 -0
- package/dist/templates/llm-types.d.ts +5 -0
- package/dist/templates/llm-types.js +161 -0
- package/dist/templates/llm-types.js.map +1 -0
- package/dist/templates/llm-utils.d.ts +5 -0
- package/dist/templates/llm-utils.js +260 -0
- package/dist/templates/llm-utils.js.map +1 -0
- package/dist/templates/package-json.js +3 -1
- package/dist/templates/package-json.js.map +1 -1
- package/dist/templates/planner.d.ts +5 -0
- package/dist/templates/planner.js +150 -0
- package/dist/templates/planner.js.map +1 -0
- package/dist/templates/test-brain.d.ts +6 -0
- package/dist/templates/test-brain.js +474 -0
- package/dist/templates/test-brain.js.map +1 -0
- package/dist/templates/test-facades.d.ts +1 -1
- package/dist/templates/test-facades.js +182 -456
- package/dist/templates/test-facades.js.map +1 -1
- package/dist/templates/test-llm.d.ts +7 -0
- package/dist/templates/test-llm.js +574 -0
- package/dist/templates/test-llm.js.map +1 -0
- package/dist/templates/test-loader.d.ts +5 -0
- package/dist/templates/test-loader.js +146 -0
- package/dist/templates/test-loader.js.map +1 -0
- package/dist/templates/test-planner.d.ts +5 -0
- package/dist/templates/test-planner.js +271 -0
- package/dist/templates/test-planner.js.map +1 -0
- package/dist/templates/test-vault.d.ts +5 -0
- package/dist/templates/test-vault.js +380 -0
- package/dist/templates/test-vault.js.map +1 -0
- package/dist/templates/vault.d.ts +5 -0
- package/dist/templates/vault.js +263 -0
- package/dist/templates/vault.js.map +1 -0
- package/dist/types.d.ts +2 -2
- package/package.json +1 -1
- package/src/__tests__/scaffolder.test.ts +52 -109
- package/src/domain-manager.ts +34 -15
- package/src/knowledge-installer.ts +23 -15
- package/src/patching.ts +44 -12
- package/src/scaffolder.ts +18 -29
- package/src/templates/entry-point.ts +146 -91
- package/src/templates/package-json.ts +3 -1
- package/src/templates/test-facades.ts +182 -458
- package/src/templates/core-facade.ts +0 -517
- package/src/templates/llm-client.ts +0 -301
|
@@ -1,26 +1,25 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Generates facade integration tests for a new agent.
|
|
3
|
-
*
|
|
3
|
+
* Uses runtime factories from @soleri/core — tests both core ops and domain ops.
|
|
4
4
|
*/
|
|
5
5
|
export function generateFacadesTest(config) {
|
|
6
|
-
const domainImports = config.domains
|
|
7
|
-
.map((d) => {
|
|
8
|
-
const fn = `create${pascalCase(d)}Facade`;
|
|
9
|
-
return `import { ${fn} } from '../facades/${d}.facade.js';`;
|
|
10
|
-
})
|
|
11
|
-
.join('\n');
|
|
12
6
|
const domainDescribes = config.domains
|
|
13
7
|
.map((d) => generateDomainDescribe(config.id, d))
|
|
14
8
|
.join('\n\n');
|
|
15
9
|
return `import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
|
16
|
-
import {
|
|
17
|
-
|
|
10
|
+
import {
|
|
11
|
+
createAgentRuntime,
|
|
12
|
+
createCoreOps,
|
|
13
|
+
createDomainFacade,
|
|
14
|
+
} from '@soleri/core';
|
|
15
|
+
import type { AgentRuntime, IntelligenceEntry, OpDefinition, FacadeConfig } from '@soleri/core';
|
|
16
|
+
import { z } from 'zod';
|
|
18
17
|
import { mkdirSync, readFileSync, rmSync, writeFileSync, existsSync } from 'node:fs';
|
|
19
18
|
import { join } from 'node:path';
|
|
20
19
|
import { tmpdir } from 'node:os';
|
|
21
|
-
|
|
22
|
-
import {
|
|
23
|
-
import {
|
|
20
|
+
import { PERSONA } from '../identity/persona.js';
|
|
21
|
+
import { activateAgent, deactivateAgent } from '../activation/activate.js';
|
|
22
|
+
import { injectClaudeMd, injectClaudeMdGlobal, hasAgentMarker } from '../activation/inject-claude-md.js';
|
|
24
23
|
|
|
25
24
|
function makeEntry(overrides: Partial<IntelligenceEntry> = {}): IntelligenceEntry {
|
|
26
25
|
return {
|
|
@@ -35,124 +34,208 @@ function makeEntry(overrides: Partial<IntelligenceEntry> = {}): IntelligenceEntr
|
|
|
35
34
|
}
|
|
36
35
|
|
|
37
36
|
describe('Facades', () => {
|
|
38
|
-
let
|
|
39
|
-
let brain: Brain;
|
|
37
|
+
let runtime: AgentRuntime;
|
|
40
38
|
let plannerDir: string;
|
|
41
|
-
let planner: Planner;
|
|
42
|
-
let openaiKeyPool: KeyPool;
|
|
43
|
-
let anthropicKeyPool: KeyPool;
|
|
44
|
-
let llmClient: LLMClient;
|
|
45
|
-
const makeCoreFacade = () =>
|
|
46
|
-
createCoreFacade(vault, planner, brain, undefined, llmClient, openaiKeyPool, anthropicKeyPool);
|
|
47
39
|
|
|
48
40
|
beforeEach(() => {
|
|
49
|
-
vault = new Vault(':memory:');
|
|
50
|
-
brain = new Brain(vault);
|
|
51
41
|
plannerDir = join(tmpdir(), 'forge-planner-test-' + Date.now());
|
|
52
42
|
mkdirSync(plannerDir, { recursive: true });
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
43
|
+
runtime = createAgentRuntime({
|
|
44
|
+
agentId: '${config.id}',
|
|
45
|
+
vaultPath: ':memory:',
|
|
46
|
+
plansPath: join(plannerDir, 'plans.json'),
|
|
47
|
+
});
|
|
57
48
|
});
|
|
58
49
|
|
|
59
50
|
afterEach(() => {
|
|
60
|
-
|
|
51
|
+
runtime.close();
|
|
61
52
|
rmSync(plannerDir, { recursive: true, force: true });
|
|
62
53
|
});
|
|
63
54
|
|
|
64
55
|
${domainDescribes}
|
|
65
56
|
|
|
66
57
|
describe('${config.id}_core', () => {
|
|
58
|
+
function buildCoreFacade(): FacadeConfig {
|
|
59
|
+
const coreOps = createCoreOps(runtime);
|
|
60
|
+
const agentOps: OpDefinition[] = [
|
|
61
|
+
{
|
|
62
|
+
name: 'health',
|
|
63
|
+
description: 'Health check',
|
|
64
|
+
auth: 'read',
|
|
65
|
+
handler: async () => {
|
|
66
|
+
const stats = runtime.vault.stats();
|
|
67
|
+
return {
|
|
68
|
+
status: 'ok',
|
|
69
|
+
agent: { name: PERSONA.name, role: PERSONA.role },
|
|
70
|
+
vault: { entries: stats.totalEntries, domains: Object.keys(stats.byDomain) },
|
|
71
|
+
};
|
|
72
|
+
},
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
name: 'identity',
|
|
76
|
+
description: 'Agent identity',
|
|
77
|
+
auth: 'read',
|
|
78
|
+
handler: async () => PERSONA,
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
name: 'activate',
|
|
82
|
+
description: 'Activate agent',
|
|
83
|
+
auth: 'read',
|
|
84
|
+
schema: z.object({
|
|
85
|
+
projectPath: z.string().optional().default('.'),
|
|
86
|
+
deactivate: z.boolean().optional(),
|
|
87
|
+
}),
|
|
88
|
+
handler: async (params) => {
|
|
89
|
+
if (params.deactivate) return deactivateAgent();
|
|
90
|
+
return activateAgent(runtime.vault, (params.projectPath as string) ?? '.', runtime.planner);
|
|
91
|
+
},
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
name: 'inject_claude_md',
|
|
95
|
+
description: 'Inject CLAUDE.md',
|
|
96
|
+
auth: 'write',
|
|
97
|
+
schema: z.object({
|
|
98
|
+
projectPath: z.string().optional().default('.'),
|
|
99
|
+
global: z.boolean().optional(),
|
|
100
|
+
}),
|
|
101
|
+
handler: async (params) => {
|
|
102
|
+
if (params.global) return injectClaudeMdGlobal();
|
|
103
|
+
return injectClaudeMd((params.projectPath as string) ?? '.');
|
|
104
|
+
},
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
name: 'setup',
|
|
108
|
+
description: 'Setup status',
|
|
109
|
+
auth: 'read',
|
|
110
|
+
schema: z.object({ projectPath: z.string().optional().default('.') }),
|
|
111
|
+
handler: async (params) => {
|
|
112
|
+
const { existsSync: exists } = await import('node:fs');
|
|
113
|
+
const { join: joinPath } = await import('node:path');
|
|
114
|
+
const { homedir } = await import('node:os');
|
|
115
|
+
const pp = (params.projectPath as string) ?? '.';
|
|
116
|
+
const projectClaudeMd = joinPath(pp, 'CLAUDE.md');
|
|
117
|
+
const globalClaudeMd = joinPath(homedir(), '.claude', 'CLAUDE.md');
|
|
118
|
+
const stats = runtime.vault.stats();
|
|
119
|
+
const recommendations: string[] = [];
|
|
120
|
+
if (!hasAgentMarker(globalClaudeMd) && !hasAgentMarker(projectClaudeMd)) {
|
|
121
|
+
recommendations.push('No CLAUDE.md configured');
|
|
122
|
+
}
|
|
123
|
+
if (stats.totalEntries === 0) {
|
|
124
|
+
recommendations.push('Vault is empty');
|
|
125
|
+
}
|
|
126
|
+
if (recommendations.length === 0) {
|
|
127
|
+
recommendations.push('${config.name} is fully set up and ready!');
|
|
128
|
+
}
|
|
129
|
+
return {
|
|
130
|
+
agent: { name: PERSONA.name, role: PERSONA.role },
|
|
131
|
+
claude_md: {
|
|
132
|
+
project: { exists: exists(projectClaudeMd), has_agent_section: hasAgentMarker(projectClaudeMd) },
|
|
133
|
+
global: { exists: exists(globalClaudeMd), has_agent_section: hasAgentMarker(globalClaudeMd) },
|
|
134
|
+
},
|
|
135
|
+
vault: { entries: stats.totalEntries, domains: Object.keys(stats.byDomain) },
|
|
136
|
+
recommendations,
|
|
137
|
+
};
|
|
138
|
+
},
|
|
139
|
+
},
|
|
140
|
+
];
|
|
141
|
+
return {
|
|
142
|
+
name: '${config.id}_core',
|
|
143
|
+
description: 'Core operations',
|
|
144
|
+
ops: [...coreOps, ...agentOps],
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
|
|
67
148
|
it('should create core facade with expected ops', () => {
|
|
68
|
-
const facade =
|
|
149
|
+
const facade = buildCoreFacade();
|
|
69
150
|
expect(facade.name).toBe('${config.id}_core');
|
|
70
151
|
const opNames = facade.ops.map((o) => o.name);
|
|
152
|
+
// Core ops (37)
|
|
71
153
|
expect(opNames).toContain('search');
|
|
72
154
|
expect(opNames).toContain('vault_stats');
|
|
73
155
|
expect(opNames).toContain('list_all');
|
|
156
|
+
expect(opNames).toContain('register');
|
|
157
|
+
expect(opNames).toContain('llm_status');
|
|
158
|
+
expect(opNames).toContain('curator_status');
|
|
159
|
+
expect(opNames).toContain('curator_health_audit');
|
|
160
|
+
// Brain Intelligence ops (11)
|
|
161
|
+
expect(opNames).toContain('brain_session_context');
|
|
162
|
+
expect(opNames).toContain('brain_strengths');
|
|
163
|
+
expect(opNames).toContain('brain_global_patterns');
|
|
164
|
+
expect(opNames).toContain('brain_recommend');
|
|
165
|
+
expect(opNames).toContain('brain_build_intelligence');
|
|
166
|
+
expect(opNames).toContain('brain_export');
|
|
167
|
+
expect(opNames).toContain('brain_import');
|
|
168
|
+
expect(opNames).toContain('brain_extract_knowledge');
|
|
169
|
+
expect(opNames).toContain('brain_archive_sessions');
|
|
170
|
+
expect(opNames).toContain('brain_promote_proposals');
|
|
171
|
+
expect(opNames).toContain('brain_lifecycle');
|
|
172
|
+
// Agent-specific ops (5)
|
|
74
173
|
expect(opNames).toContain('health');
|
|
75
174
|
expect(opNames).toContain('identity');
|
|
76
175
|
expect(opNames).toContain('activate');
|
|
77
176
|
expect(opNames).toContain('inject_claude_md');
|
|
78
177
|
expect(opNames).toContain('setup');
|
|
79
|
-
|
|
80
|
-
expect(
|
|
178
|
+
// Total: 42
|
|
179
|
+
expect(facade.ops.length).toBe(42);
|
|
81
180
|
});
|
|
82
181
|
|
|
83
182
|
it('search should query across all domains with ranked results', async () => {
|
|
84
|
-
vault.seed([
|
|
183
|
+
runtime.vault.seed([
|
|
184
|
+
makeEntry({ id: 'c1', domain: 'alpha', title: 'Alpha pattern', tags: ['a'] }),
|
|
185
|
+
makeEntry({ id: 'c2', domain: 'beta', title: 'Beta pattern', tags: ['b'] }),
|
|
186
|
+
]);
|
|
187
|
+
runtime = createAgentRuntime({ agentId: '${config.id}', vaultPath: ':memory:', plansPath: join(plannerDir, 'plans2.json') });
|
|
188
|
+
runtime.vault.seed([
|
|
85
189
|
makeEntry({ id: 'c1', domain: 'alpha', title: 'Alpha pattern', tags: ['a'] }),
|
|
86
190
|
makeEntry({ id: 'c2', domain: 'beta', title: 'Beta pattern', tags: ['b'] }),
|
|
87
191
|
]);
|
|
88
|
-
|
|
89
|
-
const facade = makeCoreFacade();
|
|
192
|
+
const facade = buildCoreFacade();
|
|
90
193
|
const searchOp = facade.ops.find((o) => o.name === 'search')!;
|
|
91
194
|
const results = (await searchOp.handler({ query: 'pattern' })) as Array<{ entry: unknown; score: number; breakdown: unknown }>;
|
|
92
195
|
expect(Array.isArray(results)).toBe(true);
|
|
93
196
|
expect(results.length).toBe(2);
|
|
94
197
|
expect(results[0].score).toBeGreaterThan(0);
|
|
95
|
-
expect(results[0].breakdown).toBeDefined();
|
|
96
198
|
});
|
|
97
199
|
|
|
98
200
|
it('vault_stats should return counts', async () => {
|
|
99
|
-
vault.seed([
|
|
201
|
+
runtime.vault.seed([
|
|
100
202
|
makeEntry({ id: 'vs1', domain: 'd1', tags: ['x'] }),
|
|
101
203
|
makeEntry({ id: 'vs2', domain: 'd2', tags: ['y'] }),
|
|
102
204
|
]);
|
|
103
|
-
const facade =
|
|
205
|
+
const facade = buildCoreFacade();
|
|
104
206
|
const statsOp = facade.ops.find((o) => o.name === 'vault_stats')!;
|
|
105
207
|
const stats = (await statsOp.handler({})) as { totalEntries: number };
|
|
106
208
|
expect(stats.totalEntries).toBe(2);
|
|
107
209
|
});
|
|
108
210
|
|
|
109
211
|
it('health should return ok status', async () => {
|
|
110
|
-
const facade =
|
|
212
|
+
const facade = buildCoreFacade();
|
|
111
213
|
const healthOp = facade.ops.find((o) => o.name === 'health')!;
|
|
112
214
|
const health = (await healthOp.handler({})) as { status: string };
|
|
113
215
|
expect(health.status).toBe('ok');
|
|
114
216
|
});
|
|
115
217
|
|
|
116
218
|
it('identity should return persona', async () => {
|
|
117
|
-
const facade =
|
|
219
|
+
const facade = buildCoreFacade();
|
|
118
220
|
const identityOp = facade.ops.find((o) => o.name === 'identity')!;
|
|
119
221
|
const persona = (await identityOp.handler({})) as { name: string; role: string };
|
|
120
222
|
expect(persona.name).toBe('${escapeQuotes(config.name)}');
|
|
121
223
|
expect(persona.role).toBe('${escapeQuotes(config.role)}');
|
|
122
224
|
});
|
|
123
225
|
|
|
124
|
-
it('list_all should support domain filter', async () => {
|
|
125
|
-
vault.seed([
|
|
126
|
-
makeEntry({ id: 'la1', domain: 'alpha', tags: ['a'] }),
|
|
127
|
-
makeEntry({ id: 'la2', domain: 'beta', tags: ['b'] }),
|
|
128
|
-
]);
|
|
129
|
-
const facade = makeCoreFacade();
|
|
130
|
-
const listOp = facade.ops.find((o) => o.name === 'list_all')!;
|
|
131
|
-
const filtered = (await listOp.handler({ domain: 'alpha' })) as unknown[];
|
|
132
|
-
expect(filtered).toHaveLength(1);
|
|
133
|
-
});
|
|
134
|
-
|
|
135
226
|
it('activate should return persona and setup status', async () => {
|
|
136
|
-
const facade =
|
|
227
|
+
const facade = buildCoreFacade();
|
|
137
228
|
const activateOp = facade.ops.find((o) => o.name === 'activate')!;
|
|
138
229
|
const result = (await activateOp.handler({ projectPath: '/tmp/nonexistent-test' })) as {
|
|
139
230
|
activated: boolean;
|
|
140
231
|
persona: { name: string; role: string };
|
|
141
|
-
guidelines: string[];
|
|
142
|
-
tool_recommendations: unknown[];
|
|
143
|
-
setup_status: { claude_md_injected: boolean; global_claude_md_injected: boolean; vault_has_entries: boolean };
|
|
144
232
|
};
|
|
145
233
|
expect(result.activated).toBe(true);
|
|
146
234
|
expect(result.persona.name).toBe('${escapeQuotes(config.name)}');
|
|
147
|
-
expect(result.persona.role).toBe('${escapeQuotes(config.role)}');
|
|
148
|
-
expect(result.guidelines.length).toBeGreaterThan(0);
|
|
149
|
-
expect(result.tool_recommendations.length).toBeGreaterThan(0);
|
|
150
|
-
expect(result.setup_status.claude_md_injected).toBe(false);
|
|
151
|
-
expect(typeof result.setup_status.global_claude_md_injected).toBe('boolean');
|
|
152
235
|
});
|
|
153
236
|
|
|
154
237
|
it('activate with deactivate flag should return deactivation', async () => {
|
|
155
|
-
const facade =
|
|
238
|
+
const facade = buildCoreFacade();
|
|
156
239
|
const activateOp = facade.ops.find((o) => o.name === 'activate')!;
|
|
157
240
|
const result = (await activateOp.handler({ deactivate: true })) as { deactivated: boolean; message: string };
|
|
158
241
|
expect(result.deactivated).toBe(true);
|
|
@@ -163,7 +246,7 @@ ${domainDescribes}
|
|
|
163
246
|
const tempDir = join(tmpdir(), 'forge-inject-test-' + Date.now());
|
|
164
247
|
mkdirSync(tempDir, { recursive: true });
|
|
165
248
|
try {
|
|
166
|
-
const facade =
|
|
249
|
+
const facade = buildCoreFacade();
|
|
167
250
|
const injectOp = facade.ops.find((o) => o.name === 'inject_claude_md')!;
|
|
168
251
|
const result = (await injectOp.handler({ projectPath: tempDir })) as {
|
|
169
252
|
injected: boolean;
|
|
@@ -180,180 +263,21 @@ ${domainDescribes}
|
|
|
180
263
|
}
|
|
181
264
|
});
|
|
182
265
|
|
|
183
|
-
it('inject_claude_md with global flag should target ~/.claude/CLAUDE.md', async () => {
|
|
184
|
-
// We test the global path resolution by checking the returned path
|
|
185
|
-
// contains .claude/CLAUDE.md (actual write may or may not happen depending on env)
|
|
186
|
-
const facade = makeCoreFacade();
|
|
187
|
-
const injectOp = facade.ops.find((o) => o.name === 'inject_claude_md')!;
|
|
188
|
-
const result = (await injectOp.handler({ global: true })) as {
|
|
189
|
-
injected: boolean;
|
|
190
|
-
path: string;
|
|
191
|
-
action: string;
|
|
192
|
-
};
|
|
193
|
-
expect(result.injected).toBe(true);
|
|
194
|
-
expect(result.path).toContain('.claude');
|
|
195
|
-
expect(result.path).toContain('CLAUDE.md');
|
|
196
|
-
});
|
|
197
|
-
|
|
198
266
|
it('setup should return project and global CLAUDE.md status', async () => {
|
|
199
|
-
const facade =
|
|
267
|
+
const facade = buildCoreFacade();
|
|
200
268
|
const setupOp = facade.ops.find((o) => o.name === 'setup')!;
|
|
201
269
|
const result = (await setupOp.handler({ projectPath: '/tmp/nonexistent-test' })) as {
|
|
202
270
|
agent: { name: string };
|
|
203
|
-
claude_md: {
|
|
204
|
-
project: { exists: boolean; has_agent_section: boolean };
|
|
205
|
-
global: { exists: boolean; has_agent_section: boolean };
|
|
206
|
-
};
|
|
207
271
|
vault: { entries: number };
|
|
208
272
|
recommendations: string[];
|
|
209
273
|
};
|
|
210
274
|
expect(result.agent.name).toBe('${escapeQuotes(config.name)}');
|
|
211
|
-
expect(result.claude_md.project.exists).toBe(false);
|
|
212
|
-
expect(typeof result.claude_md.global.exists).toBe('boolean');
|
|
213
275
|
expect(result.vault.entries).toBe(0);
|
|
214
276
|
expect(result.recommendations.length).toBeGreaterThan(0);
|
|
215
277
|
});
|
|
216
278
|
|
|
217
|
-
it('register should track new project', async () => {
|
|
218
|
-
const facade = makeCoreFacade();
|
|
219
|
-
const registerOp = facade.ops.find((o) => o.name === 'register')!;
|
|
220
|
-
const result = (await registerOp.handler({ projectPath: '/tmp/reg-test-project', name: 'reg-test' })) as {
|
|
221
|
-
project: { path: string; name: string; sessionCount: number };
|
|
222
|
-
is_new: boolean;
|
|
223
|
-
message: string;
|
|
224
|
-
vault: { entries: number };
|
|
225
|
-
};
|
|
226
|
-
expect(result.is_new).toBe(true);
|
|
227
|
-
expect(result.project.name).toBe('reg-test');
|
|
228
|
-
expect(result.project.sessionCount).toBe(1);
|
|
229
|
-
expect(result.message).toContain('Welcome');
|
|
230
|
-
});
|
|
231
|
-
|
|
232
|
-
it('register should increment session count for returning project', async () => {
|
|
233
|
-
const facade = makeCoreFacade();
|
|
234
|
-
const registerOp = facade.ops.find((o) => o.name === 'register')!;
|
|
235
|
-
await registerOp.handler({ projectPath: '/tmp/reg-test-returning', name: 'returning' });
|
|
236
|
-
const result = (await registerOp.handler({ projectPath: '/tmp/reg-test-returning', name: 'returning' })) as {
|
|
237
|
-
project: { sessionCount: number };
|
|
238
|
-
is_new: boolean;
|
|
239
|
-
message: string;
|
|
240
|
-
};
|
|
241
|
-
expect(result.is_new).toBe(false);
|
|
242
|
-
expect(result.project.sessionCount).toBe(2);
|
|
243
|
-
expect(result.message).toContain('Welcome back');
|
|
244
|
-
});
|
|
245
|
-
|
|
246
|
-
it('memory_capture should store a memory', async () => {
|
|
247
|
-
const facade = makeCoreFacade();
|
|
248
|
-
const captureOp = facade.ops.find((o) => o.name === 'memory_capture')!;
|
|
249
|
-
const result = (await captureOp.handler({
|
|
250
|
-
projectPath: '/test',
|
|
251
|
-
type: 'lesson',
|
|
252
|
-
context: 'Testing facades',
|
|
253
|
-
summary: 'Facade tests work great',
|
|
254
|
-
topics: ['testing'],
|
|
255
|
-
filesModified: [],
|
|
256
|
-
toolsUsed: [],
|
|
257
|
-
})) as { captured: boolean; memory: { id: string; type: string } };
|
|
258
|
-
expect(result.captured).toBe(true);
|
|
259
|
-
expect(result.memory.id).toMatch(/^mem-/);
|
|
260
|
-
expect(result.memory.type).toBe('lesson');
|
|
261
|
-
});
|
|
262
|
-
|
|
263
|
-
it('memory_search should find captured memories', async () => {
|
|
264
|
-
const facade = makeCoreFacade();
|
|
265
|
-
const captureOp = facade.ops.find((o) => o.name === 'memory_capture')!;
|
|
266
|
-
await captureOp.handler({
|
|
267
|
-
projectPath: '/test',
|
|
268
|
-
type: 'lesson',
|
|
269
|
-
context: 'Database optimization',
|
|
270
|
-
summary: 'Learned about index strategies for PostgreSQL',
|
|
271
|
-
topics: ['database'],
|
|
272
|
-
filesModified: [],
|
|
273
|
-
toolsUsed: [],
|
|
274
|
-
});
|
|
275
|
-
const searchOp = facade.ops.find((o) => o.name === 'memory_search')!;
|
|
276
|
-
const results = (await searchOp.handler({ query: 'index strategies' })) as Array<{ summary: string }>;
|
|
277
|
-
expect(results.length).toBeGreaterThan(0);
|
|
278
|
-
expect(results[0].summary).toContain('index strategies');
|
|
279
|
-
});
|
|
280
|
-
|
|
281
|
-
it('memory_list should return all memories with stats', async () => {
|
|
282
|
-
const facade = makeCoreFacade();
|
|
283
|
-
const captureOp = facade.ops.find((o) => o.name === 'memory_capture')!;
|
|
284
|
-
await captureOp.handler({
|
|
285
|
-
projectPath: '/test',
|
|
286
|
-
type: 'session',
|
|
287
|
-
context: 'ctx',
|
|
288
|
-
summary: 'Session summary',
|
|
289
|
-
topics: [],
|
|
290
|
-
filesModified: [],
|
|
291
|
-
toolsUsed: [],
|
|
292
|
-
});
|
|
293
|
-
const listOp = facade.ops.find((o) => o.name === 'memory_list')!;
|
|
294
|
-
const result = (await listOp.handler({})) as {
|
|
295
|
-
memories: unknown[];
|
|
296
|
-
stats: { total: number };
|
|
297
|
-
};
|
|
298
|
-
expect(result.memories).toHaveLength(1);
|
|
299
|
-
expect(result.stats.total).toBe(1);
|
|
300
|
-
});
|
|
301
|
-
|
|
302
|
-
it('session_capture should store a session memory', async () => {
|
|
303
|
-
const facade = makeCoreFacade();
|
|
304
|
-
const sessionOp = facade.ops.find((o) => o.name === 'session_capture')!;
|
|
305
|
-
const result = (await sessionOp.handler({
|
|
306
|
-
projectPath: '/tmp/test-session',
|
|
307
|
-
summary: 'Worked on vault memory system',
|
|
308
|
-
topics: ['vault', 'memory'],
|
|
309
|
-
filesModified: ['vault.ts'],
|
|
310
|
-
toolsUsed: ['Edit', 'Bash'],
|
|
311
|
-
})) as { captured: boolean; memory: { type: string; summary: string }; message: string };
|
|
312
|
-
expect(result.captured).toBe(true);
|
|
313
|
-
expect(result.memory.type).toBe('session');
|
|
314
|
-
expect(result.memory.summary).toBe('Worked on vault memory system');
|
|
315
|
-
expect(result.message).toContain('saved');
|
|
316
|
-
});
|
|
317
|
-
|
|
318
|
-
it('export should return vault entries as bundles', async () => {
|
|
319
|
-
vault.seed([
|
|
320
|
-
makeEntry({ id: 'exp1', domain: 'security', tags: ['auth'] }),
|
|
321
|
-
makeEntry({ id: 'exp2', domain: 'security', tags: ['xss'] }),
|
|
322
|
-
makeEntry({ id: 'exp3', domain: 'api-design', tags: ['rest'] }),
|
|
323
|
-
]);
|
|
324
|
-
const facade = makeCoreFacade();
|
|
325
|
-
const exportOp = facade.ops.find((o) => o.name === 'export')!;
|
|
326
|
-
const result = (await exportOp.handler({})) as {
|
|
327
|
-
exported: boolean;
|
|
328
|
-
bundles: Array<{ domain: string; entries: unknown[] }>;
|
|
329
|
-
totalEntries: number;
|
|
330
|
-
domains: string[];
|
|
331
|
-
};
|
|
332
|
-
expect(result.exported).toBe(true);
|
|
333
|
-
expect(result.totalEntries).toBe(3);
|
|
334
|
-
expect(result.domains).toContain('security');
|
|
335
|
-
expect(result.domains).toContain('api-design');
|
|
336
|
-
expect(result.bundles.find((b) => b.domain === 'security')!.entries).toHaveLength(2);
|
|
337
|
-
});
|
|
338
|
-
|
|
339
|
-
it('export should filter by domain', async () => {
|
|
340
|
-
vault.seed([
|
|
341
|
-
makeEntry({ id: 'exp-d1', domain: 'security', tags: ['auth'] }),
|
|
342
|
-
makeEntry({ id: 'exp-d2', domain: 'api-design', tags: ['rest'] }),
|
|
343
|
-
]);
|
|
344
|
-
const facade = makeCoreFacade();
|
|
345
|
-
const exportOp = facade.ops.find((o) => o.name === 'export')!;
|
|
346
|
-
const result = (await exportOp.handler({ domain: 'security' })) as {
|
|
347
|
-
bundles: Array<{ domain: string; entries: unknown[] }>;
|
|
348
|
-
totalEntries: number;
|
|
349
|
-
};
|
|
350
|
-
expect(result.totalEntries).toBe(1);
|
|
351
|
-
expect(result.bundles).toHaveLength(1);
|
|
352
|
-
expect(result.bundles[0].domain).toBe('security');
|
|
353
|
-
});
|
|
354
|
-
|
|
355
279
|
it('create_plan should create a draft plan', async () => {
|
|
356
|
-
const facade =
|
|
280
|
+
const facade = buildCoreFacade();
|
|
357
281
|
const createOp = facade.ops.find((o) => o.name === 'create_plan')!;
|
|
358
282
|
const result = (await createOp.handler({
|
|
359
283
|
objective: 'Add caching',
|
|
@@ -365,217 +289,34 @@ ${domainDescribes}
|
|
|
365
289
|
expect(result.plan.tasks).toHaveLength(1);
|
|
366
290
|
});
|
|
367
291
|
|
|
368
|
-
it('get_plan should return a plan by id', async () => {
|
|
369
|
-
const facade = makeCoreFacade();
|
|
370
|
-
const createOp = facade.ops.find((o) => o.name === 'create_plan')!;
|
|
371
|
-
const created = (await createOp.handler({ objective: 'Test', scope: 'test' })) as { plan: { id: string } };
|
|
372
|
-
const getOp = facade.ops.find((o) => o.name === 'get_plan')!;
|
|
373
|
-
const result = (await getOp.handler({ planId: created.plan.id })) as { id: string; objective: string };
|
|
374
|
-
expect(result.id).toBe(created.plan.id);
|
|
375
|
-
expect(result.objective).toBe('Test');
|
|
376
|
-
});
|
|
377
|
-
|
|
378
|
-
it('get_plan without id should return active plans', async () => {
|
|
379
|
-
const facade = makeCoreFacade();
|
|
380
|
-
const createOp = facade.ops.find((o) => o.name === 'create_plan')!;
|
|
381
|
-
await createOp.handler({ objective: 'Plan A', scope: 'a' });
|
|
382
|
-
const getOp = facade.ops.find((o) => o.name === 'get_plan')!;
|
|
383
|
-
const result = (await getOp.handler({})) as { active: unknown[]; executing: unknown[] };
|
|
384
|
-
expect(result.active).toHaveLength(1);
|
|
385
|
-
expect(result.executing).toHaveLength(0);
|
|
386
|
-
});
|
|
387
|
-
|
|
388
|
-
it('approve_plan should approve and optionally start execution', async () => {
|
|
389
|
-
const facade = makeCoreFacade();
|
|
390
|
-
const createOp = facade.ops.find((o) => o.name === 'create_plan')!;
|
|
391
|
-
const created = (await createOp.handler({ objective: 'Approve', scope: 'test' })) as { plan: { id: string } };
|
|
392
|
-
const approveOp = facade.ops.find((o) => o.name === 'approve_plan')!;
|
|
393
|
-
const result = (await approveOp.handler({ planId: created.plan.id, startExecution: true })) as {
|
|
394
|
-
approved: boolean;
|
|
395
|
-
executing: boolean;
|
|
396
|
-
plan: { status: string };
|
|
397
|
-
};
|
|
398
|
-
expect(result.approved).toBe(true);
|
|
399
|
-
expect(result.executing).toBe(true);
|
|
400
|
-
expect(result.plan.status).toBe('executing');
|
|
401
|
-
});
|
|
402
|
-
|
|
403
|
-
it('update_task should update task status', async () => {
|
|
404
|
-
const facade = makeCoreFacade();
|
|
405
|
-
const createOp = facade.ops.find((o) => o.name === 'create_plan')!;
|
|
406
|
-
const created = (await createOp.handler({
|
|
407
|
-
objective: 'Task update',
|
|
408
|
-
scope: 'test',
|
|
409
|
-
tasks: [{ title: 'T1', description: 'Do thing' }],
|
|
410
|
-
})) as { plan: { id: string } };
|
|
411
|
-
const approveOp = facade.ops.find((o) => o.name === 'approve_plan')!;
|
|
412
|
-
await approveOp.handler({ planId: created.plan.id, startExecution: true });
|
|
413
|
-
const updateOp = facade.ops.find((o) => o.name === 'update_task')!;
|
|
414
|
-
const result = (await updateOp.handler({
|
|
415
|
-
planId: created.plan.id,
|
|
416
|
-
taskId: 'task-1',
|
|
417
|
-
status: 'completed',
|
|
418
|
-
})) as { updated: boolean; task: { status: string } };
|
|
419
|
-
expect(result.updated).toBe(true);
|
|
420
|
-
expect(result.task!.status).toBe('completed');
|
|
421
|
-
});
|
|
422
|
-
|
|
423
|
-
it('complete_plan should complete with task summary', async () => {
|
|
424
|
-
const facade = makeCoreFacade();
|
|
425
|
-
const createOp = facade.ops.find((o) => o.name === 'create_plan')!;
|
|
426
|
-
const created = (await createOp.handler({
|
|
427
|
-
objective: 'Complete me',
|
|
428
|
-
scope: 'test',
|
|
429
|
-
tasks: [
|
|
430
|
-
{ title: 'T1', description: 'Do thing' },
|
|
431
|
-
{ title: 'T2', description: 'Another' },
|
|
432
|
-
],
|
|
433
|
-
})) as { plan: { id: string } };
|
|
434
|
-
const approveOp = facade.ops.find((o) => o.name === 'approve_plan')!;
|
|
435
|
-
await approveOp.handler({ planId: created.plan.id, startExecution: true });
|
|
436
|
-
const updateOp = facade.ops.find((o) => o.name === 'update_task')!;
|
|
437
|
-
await updateOp.handler({ planId: created.plan.id, taskId: 'task-1', status: 'completed' });
|
|
438
|
-
await updateOp.handler({ planId: created.plan.id, taskId: 'task-2', status: 'skipped' });
|
|
439
|
-
const completeOp = facade.ops.find((o) => o.name === 'complete_plan')!;
|
|
440
|
-
const result = (await completeOp.handler({ planId: created.plan.id })) as {
|
|
441
|
-
completed: boolean;
|
|
442
|
-
taskSummary: { completed: number; skipped: number; total: number };
|
|
443
|
-
};
|
|
444
|
-
expect(result.completed).toBe(true);
|
|
445
|
-
expect(result.taskSummary.completed).toBe(1);
|
|
446
|
-
expect(result.taskSummary.skipped).toBe(1);
|
|
447
|
-
expect(result.taskSummary.total).toBe(2);
|
|
448
|
-
});
|
|
449
|
-
|
|
450
|
-
it('should have brain ops in core facade', () => {
|
|
451
|
-
const facade = makeCoreFacade();
|
|
452
|
-
const opNames = facade.ops.map((o) => o.name);
|
|
453
|
-
expect(opNames).toContain('record_feedback');
|
|
454
|
-
expect(opNames).toContain('rebuild_vocabulary');
|
|
455
|
-
expect(opNames).toContain('brain_stats');
|
|
456
|
-
});
|
|
457
|
-
|
|
458
|
-
it('record_feedback should record search feedback', async () => {
|
|
459
|
-
const facade = makeCoreFacade();
|
|
460
|
-
const feedbackOp = facade.ops.find((o) => o.name === 'record_feedback')!;
|
|
461
|
-
const result = (await feedbackOp.handler({
|
|
462
|
-
query: 'test query',
|
|
463
|
-
entryId: 'test-entry',
|
|
464
|
-
action: 'accepted',
|
|
465
|
-
})) as { recorded: boolean; query: string; action: string };
|
|
466
|
-
expect(result.recorded).toBe(true);
|
|
467
|
-
expect(result.query).toBe('test query');
|
|
468
|
-
expect(result.action).toBe('accepted');
|
|
469
|
-
});
|
|
470
|
-
|
|
471
|
-
it('rebuild_vocabulary should rebuild and return size', async () => {
|
|
472
|
-
vault.seed([
|
|
473
|
-
makeEntry({ id: 'rv1', title: 'Rebuild vocab test', description: 'Testing vocabulary rebuild.', tags: ['rebuild'] }),
|
|
474
|
-
]);
|
|
475
|
-
brain = new Brain(vault);
|
|
476
|
-
const facade = makeCoreFacade();
|
|
477
|
-
const rebuildOp = facade.ops.find((o) => o.name === 'rebuild_vocabulary')!;
|
|
478
|
-
const result = (await rebuildOp.handler({})) as { rebuilt: boolean; vocabularySize: number };
|
|
479
|
-
expect(result.rebuilt).toBe(true);
|
|
480
|
-
expect(result.vocabularySize).toBeGreaterThan(0);
|
|
481
|
-
});
|
|
482
|
-
|
|
483
292
|
it('brain_stats should return intelligence stats', async () => {
|
|
484
|
-
const facade =
|
|
293
|
+
const facade = buildCoreFacade();
|
|
485
294
|
const statsOp = facade.ops.find((o) => o.name === 'brain_stats')!;
|
|
486
295
|
const result = (await statsOp.handler({})) as {
|
|
487
296
|
vocabularySize: number;
|
|
488
297
|
feedbackCount: number;
|
|
489
|
-
weights: { semantic: number; severity: number; recency: number; tagOverlap: number; domainMatch: number };
|
|
490
298
|
};
|
|
491
299
|
expect(result.vocabularySize).toBe(0);
|
|
492
300
|
expect(result.feedbackCount).toBe(0);
|
|
493
|
-
expect(result.weights.semantic).toBeCloseTo(0.40, 2);
|
|
494
|
-
expect(result.weights.severity).toBeCloseTo(0.15, 2);
|
|
495
|
-
});
|
|
496
|
-
|
|
497
|
-
it('brain_stats should reflect feedback count after recording', async () => {
|
|
498
|
-
const facade = makeCoreFacade();
|
|
499
|
-
const feedbackOp = facade.ops.find((o) => o.name === 'record_feedback')!;
|
|
500
|
-
await feedbackOp.handler({ query: 'q1', entryId: 'e1', action: 'accepted' });
|
|
501
|
-
await feedbackOp.handler({ query: 'q2', entryId: 'e2', action: 'dismissed' });
|
|
502
|
-
const statsOp = facade.ops.find((o) => o.name === 'brain_stats')!;
|
|
503
|
-
const result = (await statsOp.handler({})) as { feedbackCount: number };
|
|
504
|
-
expect(result.feedbackCount).toBe(2);
|
|
505
|
-
});
|
|
506
|
-
|
|
507
|
-
it('llm_status should return provider availability', async () => {
|
|
508
|
-
const facade = makeCoreFacade();
|
|
509
|
-
const llmStatusOp = facade.ops.find((o) => o.name === 'llm_status')!;
|
|
510
|
-
const result = (await llmStatusOp.handler({})) as {
|
|
511
|
-
providers: {
|
|
512
|
-
openai: { available: boolean; keyPool: unknown };
|
|
513
|
-
anthropic: { available: boolean; keyPool: unknown };
|
|
514
|
-
};
|
|
515
|
-
routes: unknown[];
|
|
516
|
-
};
|
|
517
|
-
expect(result.providers.openai.available).toBe(false);
|
|
518
|
-
expect(result.providers.anthropic.available).toBe(false);
|
|
519
|
-
expect(result.providers.openai.keyPool).toBeNull();
|
|
520
|
-
expect(Array.isArray(result.routes)).toBe(true);
|
|
521
301
|
});
|
|
522
302
|
|
|
523
|
-
it('
|
|
524
|
-
const
|
|
525
|
-
const
|
|
526
|
-
const
|
|
527
|
-
|
|
528
|
-
const llmStatusOp = facade.ops.find((o) => o.name === 'llm_status')!;
|
|
529
|
-
const result = (await llmStatusOp.handler({})) as {
|
|
530
|
-
providers: {
|
|
531
|
-
openai: { available: boolean; keyPool: { poolSize: number } };
|
|
532
|
-
anthropic: { available: boolean; keyPool: { poolSize: number } };
|
|
533
|
-
};
|
|
534
|
-
};
|
|
535
|
-
expect(result.providers.openai.available).toBe(true);
|
|
536
|
-
expect(result.providers.openai.keyPool.poolSize).toBe(1);
|
|
537
|
-
expect(result.providers.anthropic.available).toBe(true);
|
|
538
|
-
expect(result.providers.anthropic.keyPool.poolSize).toBe(1);
|
|
303
|
+
it('curator_status should return table counts', async () => {
|
|
304
|
+
const facade = buildCoreFacade();
|
|
305
|
+
const statusOp = facade.ops.find((o) => o.name === 'curator_status')!;
|
|
306
|
+
const result = (await statusOp.handler({})) as { initialized: boolean };
|
|
307
|
+
expect(result.initialized).toBe(true);
|
|
539
308
|
});
|
|
540
309
|
|
|
541
|
-
it('
|
|
542
|
-
vault.seed([
|
|
543
|
-
makeEntry({ id: '
|
|
310
|
+
it('curator_health_audit should return score', async () => {
|
|
311
|
+
runtime.vault.seed([
|
|
312
|
+
makeEntry({ id: 'ha1', type: 'pattern', tags: ['a', 'b'] }),
|
|
313
|
+
makeEntry({ id: 'ha2', type: 'anti-pattern', tags: ['c', 'd'] }),
|
|
544
314
|
]);
|
|
545
|
-
|
|
546
|
-
const facade =
|
|
547
|
-
const
|
|
548
|
-
const
|
|
549
|
-
expect(
|
|
550
|
-
expect(results[0].score).toBeGreaterThan(0);
|
|
551
|
-
expect(results[0].breakdown.total).toBe(results[0].score);
|
|
552
|
-
});
|
|
553
|
-
|
|
554
|
-
// ─── Cognee ops (graceful degradation without Docker) ─────────
|
|
555
|
-
|
|
556
|
-
it('cognee_status should report not configured when cognee is undefined', async () => {
|
|
557
|
-
const facade = makeCoreFacade();
|
|
558
|
-
const op = facade.ops.find((o) => o.name === 'cognee_status')!;
|
|
559
|
-
expect(op).toBeDefined();
|
|
560
|
-
const result = (await op.handler({})) as { available: boolean };
|
|
561
|
-
expect(result.available).toBe(false);
|
|
562
|
-
});
|
|
563
|
-
|
|
564
|
-
it('cognee_sync should return helpful message when cognee is unavailable', async () => {
|
|
565
|
-
const facade = makeCoreFacade();
|
|
566
|
-
const op = facade.ops.find((o) => o.name === 'cognee_sync')!;
|
|
567
|
-
expect(op).toBeDefined();
|
|
568
|
-
const result = (await op.handler({})) as { synced: boolean; message: string };
|
|
569
|
-
expect(result.synced).toBe(false);
|
|
570
|
-
expect(result.message).toContain('Cognee not available');
|
|
571
|
-
});
|
|
572
|
-
|
|
573
|
-
it('graph_search should return empty results when cognee is unavailable', async () => {
|
|
574
|
-
const facade = makeCoreFacade();
|
|
575
|
-
const op = facade.ops.find((o) => o.name === 'graph_search')!;
|
|
576
|
-
expect(op).toBeDefined();
|
|
577
|
-
const result = (await op.handler({ query: 'test' })) as { results: unknown[] };
|
|
578
|
-
expect(result.results).toEqual([]);
|
|
315
|
+
runtime.curator.groomAll();
|
|
316
|
+
const facade = buildCoreFacade();
|
|
317
|
+
const healthOp = facade.ops.find((o) => o.name === 'curator_health_audit')!;
|
|
318
|
+
const result = (await healthOp.handler({})) as { score: number };
|
|
319
|
+
expect(result.score).toBeGreaterThan(0);
|
|
579
320
|
});
|
|
580
321
|
});
|
|
581
322
|
});
|
|
@@ -583,10 +324,13 @@ ${domainDescribes}
|
|
|
583
324
|
}
|
|
584
325
|
function generateDomainDescribe(agentId, domain) {
|
|
585
326
|
const facadeName = `${agentId}_${domain.replace(/-/g, '_')}`;
|
|
586
|
-
const factoryFn = `create${pascalCase(domain)}Facade`;
|
|
587
327
|
return ` describe('${facadeName}', () => {
|
|
328
|
+
function buildDomainFacade(): FacadeConfig {
|
|
329
|
+
return createDomainFacade(runtime, '${agentId}', '${domain}');
|
|
330
|
+
}
|
|
331
|
+
|
|
588
332
|
it('should create facade with expected ops', () => {
|
|
589
|
-
const facade =
|
|
333
|
+
const facade = buildDomainFacade();
|
|
590
334
|
expect(facade.name).toBe('${facadeName}');
|
|
591
335
|
const opNames = facade.ops.map((o) => o.name);
|
|
592
336
|
expect(opNames).toContain('get_patterns');
|
|
@@ -597,81 +341,63 @@ function generateDomainDescribe(agentId, domain) {
|
|
|
597
341
|
});
|
|
598
342
|
|
|
599
343
|
it('get_patterns should return entries for ${domain}', async () => {
|
|
600
|
-
vault.seed([
|
|
344
|
+
runtime.vault.seed([
|
|
601
345
|
makeEntry({ id: '${domain}-gp1', domain: '${domain}', tags: ['test'] }),
|
|
602
346
|
makeEntry({ id: 'other-gp1', domain: 'other-domain', tags: ['test'] }),
|
|
603
347
|
]);
|
|
604
|
-
const facade =
|
|
348
|
+
const facade = buildDomainFacade();
|
|
605
349
|
const op = facade.ops.find((o) => o.name === 'get_patterns')!;
|
|
606
350
|
const results = (await op.handler({})) as IntelligenceEntry[];
|
|
607
351
|
expect(results.every((e) => e.domain === '${domain}')).toBe(true);
|
|
608
352
|
});
|
|
609
353
|
|
|
610
354
|
it('search should scope to ${domain} with ranked results', async () => {
|
|
611
|
-
vault.seed([
|
|
355
|
+
runtime.vault.seed([
|
|
612
356
|
makeEntry({ id: '${domain}-s1', domain: '${domain}', title: 'Domain specific pattern', tags: ['find-me'] }),
|
|
613
357
|
makeEntry({ id: 'other-s1', domain: 'other', title: 'Other domain pattern', tags: ['nope'] }),
|
|
614
358
|
]);
|
|
615
|
-
brain
|
|
616
|
-
const facade =
|
|
359
|
+
runtime.brain.rebuildVocabulary();
|
|
360
|
+
const facade = buildDomainFacade();
|
|
617
361
|
const op = facade.ops.find((o) => o.name === 'search')!;
|
|
618
|
-
const results = (await op.handler({ query: 'pattern' })) as Array<{ entry: IntelligenceEntry; score: number
|
|
362
|
+
const results = (await op.handler({ query: 'pattern' })) as Array<{ entry: IntelligenceEntry; score: number }>;
|
|
619
363
|
expect(results.every((r) => r.entry.domain === '${domain}')).toBe(true);
|
|
620
|
-
if (results.length > 0) {
|
|
621
|
-
expect(results[0].score).toBeGreaterThan(0);
|
|
622
|
-
expect(results[0].breakdown).toBeDefined();
|
|
623
|
-
}
|
|
624
364
|
});
|
|
625
365
|
|
|
626
|
-
it('capture should add entry with ${domain} domain
|
|
627
|
-
const facade =
|
|
366
|
+
it('capture should add entry with ${domain} domain', async () => {
|
|
367
|
+
const facade = buildDomainFacade();
|
|
628
368
|
const captureOp = facade.ops.find((o) => o.name === 'capture')!;
|
|
629
369
|
const result = (await captureOp.handler({
|
|
630
370
|
id: '${domain}-cap1',
|
|
631
371
|
type: 'pattern',
|
|
632
|
-
title: 'Captured Pattern
|
|
372
|
+
title: 'Captured Pattern',
|
|
633
373
|
severity: 'warning',
|
|
634
|
-
description: 'A captured pattern
|
|
374
|
+
description: 'A captured pattern.',
|
|
635
375
|
tags: ['captured'],
|
|
636
|
-
})) as { captured: boolean
|
|
376
|
+
})) as { captured: boolean };
|
|
637
377
|
expect(result.captured).toBe(true);
|
|
638
|
-
|
|
639
|
-
const entry = vault.get('${domain}-cap1');
|
|
378
|
+
const entry = runtime.vault.get('${domain}-cap1');
|
|
640
379
|
expect(entry).not.toBeNull();
|
|
641
380
|
expect(entry!.domain).toBe('${domain}');
|
|
642
381
|
});
|
|
643
382
|
|
|
644
383
|
it('get_entry should return specific entry', async () => {
|
|
645
|
-
vault.seed([makeEntry({ id: '${domain}-ge1', domain: '${domain}', tags: ['test'] })]);
|
|
646
|
-
const facade =
|
|
384
|
+
runtime.vault.seed([makeEntry({ id: '${domain}-ge1', domain: '${domain}', tags: ['test'] })]);
|
|
385
|
+
const facade = buildDomainFacade();
|
|
647
386
|
const op = facade.ops.find((o) => o.name === 'get_entry')!;
|
|
648
387
|
const result = (await op.handler({ id: '${domain}-ge1' })) as IntelligenceEntry;
|
|
649
388
|
expect(result.id).toBe('${domain}-ge1');
|
|
650
389
|
});
|
|
651
390
|
|
|
652
|
-
it('get_entry should return error for missing entry', async () => {
|
|
653
|
-
const facade = ${factoryFn}(vault, brain);
|
|
654
|
-
const op = facade.ops.find((o) => o.name === 'get_entry')!;
|
|
655
|
-
const result = (await op.handler({ id: 'nonexistent' })) as { error: string };
|
|
656
|
-
expect(result.error).toBeDefined();
|
|
657
|
-
});
|
|
658
|
-
|
|
659
391
|
it('remove should delete entry', async () => {
|
|
660
|
-
vault.seed([makeEntry({ id: '${domain}-rm1', domain: '${domain}', tags: ['test'] })]);
|
|
661
|
-
const facade =
|
|
392
|
+
runtime.vault.seed([makeEntry({ id: '${domain}-rm1', domain: '${domain}', tags: ['test'] })]);
|
|
393
|
+
const facade = buildDomainFacade();
|
|
662
394
|
const op = facade.ops.find((o) => o.name === 'remove')!;
|
|
663
395
|
const result = (await op.handler({ id: '${domain}-rm1' })) as { removed: boolean };
|
|
664
396
|
expect(result.removed).toBe(true);
|
|
665
|
-
expect(vault.get('${domain}-rm1')).toBeNull();
|
|
397
|
+
expect(runtime.vault.get('${domain}-rm1')).toBeNull();
|
|
666
398
|
});
|
|
667
399
|
});`;
|
|
668
400
|
}
|
|
669
|
-
function pascalCase(s) {
|
|
670
|
-
return s
|
|
671
|
-
.split(/[-_\s]+/)
|
|
672
|
-
.map((w) => w.charAt(0).toUpperCase() + w.slice(1))
|
|
673
|
-
.join('');
|
|
674
|
-
}
|
|
675
401
|
function escapeQuotes(s) {
|
|
676
402
|
return s.replace(/\\/g, '\\\\').replace(/'/g, "\\'");
|
|
677
403
|
}
|