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