@soleri/forge 4.2.1 → 5.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/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 +39 -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 +170 -429
- 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 +22 -15
- package/src/patching.ts +49 -11
- 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 +170 -431
- 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,196 @@ 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 (26)
|
|
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
|
+
// Agent-specific ops (5)
|
|
74
161
|
expect(opNames).toContain('health');
|
|
75
162
|
expect(opNames).toContain('identity');
|
|
76
163
|
expect(opNames).toContain('activate');
|
|
77
164
|
expect(opNames).toContain('inject_claude_md');
|
|
78
165
|
expect(opNames).toContain('setup');
|
|
79
|
-
|
|
80
|
-
expect(
|
|
166
|
+
// Total: 31
|
|
167
|
+
expect(facade.ops.length).toBe(31);
|
|
81
168
|
});
|
|
82
169
|
|
|
83
170
|
it('search should query across all domains with ranked results', async () => {
|
|
84
|
-
vault.seed([
|
|
171
|
+
runtime.vault.seed([
|
|
85
172
|
makeEntry({ id: 'c1', domain: 'alpha', title: 'Alpha pattern', tags: ['a'] }),
|
|
86
173
|
makeEntry({ id: 'c2', domain: 'beta', title: 'Beta pattern', tags: ['b'] }),
|
|
87
174
|
]);
|
|
88
|
-
|
|
89
|
-
|
|
175
|
+
runtime = createAgentRuntime({ agentId: '${config.id}', vaultPath: ':memory:', plansPath: join(plannerDir, 'plans2.json') });
|
|
176
|
+
runtime.vault.seed([
|
|
177
|
+
makeEntry({ id: 'c1', domain: 'alpha', title: 'Alpha pattern', tags: ['a'] }),
|
|
178
|
+
makeEntry({ id: 'c2', domain: 'beta', title: 'Beta pattern', tags: ['b'] }),
|
|
179
|
+
]);
|
|
180
|
+
const facade = buildCoreFacade();
|
|
90
181
|
const searchOp = facade.ops.find((o) => o.name === 'search')!;
|
|
91
182
|
const results = (await searchOp.handler({ query: 'pattern' })) as Array<{ entry: unknown; score: number; breakdown: unknown }>;
|
|
92
183
|
expect(Array.isArray(results)).toBe(true);
|
|
93
184
|
expect(results.length).toBe(2);
|
|
94
185
|
expect(results[0].score).toBeGreaterThan(0);
|
|
95
|
-
expect(results[0].breakdown).toBeDefined();
|
|
96
186
|
});
|
|
97
187
|
|
|
98
188
|
it('vault_stats should return counts', async () => {
|
|
99
|
-
vault.seed([
|
|
189
|
+
runtime.vault.seed([
|
|
100
190
|
makeEntry({ id: 'vs1', domain: 'd1', tags: ['x'] }),
|
|
101
191
|
makeEntry({ id: 'vs2', domain: 'd2', tags: ['y'] }),
|
|
102
192
|
]);
|
|
103
|
-
const facade =
|
|
193
|
+
const facade = buildCoreFacade();
|
|
104
194
|
const statsOp = facade.ops.find((o) => o.name === 'vault_stats')!;
|
|
105
195
|
const stats = (await statsOp.handler({})) as { totalEntries: number };
|
|
106
196
|
expect(stats.totalEntries).toBe(2);
|
|
107
197
|
});
|
|
108
198
|
|
|
109
199
|
it('health should return ok status', async () => {
|
|
110
|
-
const facade =
|
|
200
|
+
const facade = buildCoreFacade();
|
|
111
201
|
const healthOp = facade.ops.find((o) => o.name === 'health')!;
|
|
112
202
|
const health = (await healthOp.handler({})) as { status: string };
|
|
113
203
|
expect(health.status).toBe('ok');
|
|
114
204
|
});
|
|
115
205
|
|
|
116
206
|
it('identity should return persona', async () => {
|
|
117
|
-
const facade =
|
|
207
|
+
const facade = buildCoreFacade();
|
|
118
208
|
const identityOp = facade.ops.find((o) => o.name === 'identity')!;
|
|
119
209
|
const persona = (await identityOp.handler({})) as { name: string; role: string };
|
|
120
210
|
expect(persona.name).toBe('${escapeQuotes(config.name)}');
|
|
121
211
|
expect(persona.role).toBe('${escapeQuotes(config.role)}');
|
|
122
212
|
});
|
|
123
213
|
|
|
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
214
|
it('activate should return persona and setup status', async () => {
|
|
136
|
-
const facade =
|
|
215
|
+
const facade = buildCoreFacade();
|
|
137
216
|
const activateOp = facade.ops.find((o) => o.name === 'activate')!;
|
|
138
217
|
const result = (await activateOp.handler({ projectPath: '/tmp/nonexistent-test' })) as {
|
|
139
218
|
activated: boolean;
|
|
140
219
|
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
220
|
};
|
|
145
221
|
expect(result.activated).toBe(true);
|
|
146
222
|
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
223
|
});
|
|
153
224
|
|
|
154
225
|
it('activate with deactivate flag should return deactivation', async () => {
|
|
155
|
-
const facade =
|
|
226
|
+
const facade = buildCoreFacade();
|
|
156
227
|
const activateOp = facade.ops.find((o) => o.name === 'activate')!;
|
|
157
228
|
const result = (await activateOp.handler({ deactivate: true })) as { deactivated: boolean; message: string };
|
|
158
229
|
expect(result.deactivated).toBe(true);
|
|
@@ -163,7 +234,7 @@ ${domainDescribes}
|
|
|
163
234
|
const tempDir = join(tmpdir(), 'forge-inject-test-' + Date.now());
|
|
164
235
|
mkdirSync(tempDir, { recursive: true });
|
|
165
236
|
try {
|
|
166
|
-
const facade =
|
|
237
|
+
const facade = buildCoreFacade();
|
|
167
238
|
const injectOp = facade.ops.find((o) => o.name === 'inject_claude_md')!;
|
|
168
239
|
const result = (await injectOp.handler({ projectPath: tempDir })) as {
|
|
169
240
|
injected: boolean;
|
|
@@ -180,180 +251,21 @@ ${domainDescribes}
|
|
|
180
251
|
}
|
|
181
252
|
});
|
|
182
253
|
|
|
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
254
|
it('setup should return project and global CLAUDE.md status', async () => {
|
|
199
|
-
const facade =
|
|
255
|
+
const facade = buildCoreFacade();
|
|
200
256
|
const setupOp = facade.ops.find((o) => o.name === 'setup')!;
|
|
201
257
|
const result = (await setupOp.handler({ projectPath: '/tmp/nonexistent-test' })) as {
|
|
202
258
|
agent: { name: string };
|
|
203
|
-
claude_md: {
|
|
204
|
-
project: { exists: boolean; has_agent_section: boolean };
|
|
205
|
-
global: { exists: boolean; has_agent_section: boolean };
|
|
206
|
-
};
|
|
207
259
|
vault: { entries: number };
|
|
208
260
|
recommendations: string[];
|
|
209
261
|
};
|
|
210
262
|
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
263
|
expect(result.vault.entries).toBe(0);
|
|
214
264
|
expect(result.recommendations.length).toBeGreaterThan(0);
|
|
215
265
|
});
|
|
216
266
|
|
|
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
267
|
it('create_plan should create a draft plan', async () => {
|
|
356
|
-
const facade =
|
|
268
|
+
const facade = buildCoreFacade();
|
|
357
269
|
const createOp = facade.ops.find((o) => o.name === 'create_plan')!;
|
|
358
270
|
const result = (await createOp.handler({
|
|
359
271
|
objective: 'Add caching',
|
|
@@ -365,190 +277,34 @@ ${domainDescribes}
|
|
|
365
277
|
expect(result.plan.tasks).toHaveLength(1);
|
|
366
278
|
});
|
|
367
279
|
|
|
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
280
|
it('brain_stats should return intelligence stats', async () => {
|
|
484
|
-
const facade =
|
|
281
|
+
const facade = buildCoreFacade();
|
|
485
282
|
const statsOp = facade.ops.find((o) => o.name === 'brain_stats')!;
|
|
486
283
|
const result = (await statsOp.handler({})) as {
|
|
487
284
|
vocabularySize: number;
|
|
488
285
|
feedbackCount: number;
|
|
489
|
-
weights: { semantic: number; severity: number; recency: number; tagOverlap: number; domainMatch: number };
|
|
490
286
|
};
|
|
491
287
|
expect(result.vocabularySize).toBe(0);
|
|
492
288
|
expect(result.feedbackCount).toBe(0);
|
|
493
|
-
expect(result.weights.semantic).toBeCloseTo(0.40, 2);
|
|
494
|
-
expect(result.weights.severity).toBeCloseTo(0.15, 2);
|
|
495
289
|
});
|
|
496
290
|
|
|
497
|
-
it('
|
|
498
|
-
const facade =
|
|
499
|
-
const
|
|
500
|
-
await
|
|
501
|
-
|
|
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);
|
|
291
|
+
it('curator_status should return table counts', async () => {
|
|
292
|
+
const facade = buildCoreFacade();
|
|
293
|
+
const statusOp = facade.ops.find((o) => o.name === 'curator_status')!;
|
|
294
|
+
const result = (await statusOp.handler({})) as { initialized: boolean };
|
|
295
|
+
expect(result.initialized).toBe(true);
|
|
505
296
|
});
|
|
506
297
|
|
|
507
|
-
it('
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
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
|
-
});
|
|
522
|
-
|
|
523
|
-
it('llm_status should reflect key pool when keys present', async () => {
|
|
524
|
-
const oPool = new KeyPool({ keys: ['sk-test'] });
|
|
525
|
-
const aPool = new KeyPool({ keys: ['sk-ant-test'] });
|
|
526
|
-
const client = new LLMClient(oPool, aPool);
|
|
527
|
-
const facade = createCoreFacade(vault, planner, brain, undefined, client, oPool, aPool);
|
|
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);
|
|
539
|
-
});
|
|
540
|
-
|
|
541
|
-
it('search through brain should return ranked results with breakdowns', async () => {
|
|
542
|
-
vault.seed([
|
|
543
|
-
makeEntry({ id: 'bs-1', domain: 'security', title: 'Authentication token handling', severity: 'critical', description: 'Always validate JWT tokens.', tags: ['auth', 'jwt'] }),
|
|
298
|
+
it('curator_health_audit should return score', async () => {
|
|
299
|
+
runtime.vault.seed([
|
|
300
|
+
makeEntry({ id: 'ha1', type: 'pattern', tags: ['a', 'b'] }),
|
|
301
|
+
makeEntry({ id: 'ha2', type: 'anti-pattern', tags: ['c', 'd'] }),
|
|
544
302
|
]);
|
|
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);
|
|
303
|
+
runtime.curator.groomAll();
|
|
304
|
+
const facade = buildCoreFacade();
|
|
305
|
+
const healthOp = facade.ops.find((o) => o.name === 'curator_health_audit')!;
|
|
306
|
+
const result = (await healthOp.handler({})) as { score: number };
|
|
307
|
+
expect(result.score).toBeGreaterThan(0);
|
|
552
308
|
});
|
|
553
309
|
});
|
|
554
310
|
});
|
|
@@ -556,10 +312,13 @@ ${domainDescribes}
|
|
|
556
312
|
}
|
|
557
313
|
function generateDomainDescribe(agentId, domain) {
|
|
558
314
|
const facadeName = `${agentId}_${domain.replace(/-/g, '_')}`;
|
|
559
|
-
const factoryFn = `create${pascalCase(domain)}Facade`;
|
|
560
315
|
return ` describe('${facadeName}', () => {
|
|
316
|
+
function buildDomainFacade(): FacadeConfig {
|
|
317
|
+
return createDomainFacade(runtime, '${agentId}', '${domain}');
|
|
318
|
+
}
|
|
319
|
+
|
|
561
320
|
it('should create facade with expected ops', () => {
|
|
562
|
-
const facade =
|
|
321
|
+
const facade = buildDomainFacade();
|
|
563
322
|
expect(facade.name).toBe('${facadeName}');
|
|
564
323
|
const opNames = facade.ops.map((o) => o.name);
|
|
565
324
|
expect(opNames).toContain('get_patterns');
|
|
@@ -570,81 +329,63 @@ function generateDomainDescribe(agentId, domain) {
|
|
|
570
329
|
});
|
|
571
330
|
|
|
572
331
|
it('get_patterns should return entries for ${domain}', async () => {
|
|
573
|
-
vault.seed([
|
|
332
|
+
runtime.vault.seed([
|
|
574
333
|
makeEntry({ id: '${domain}-gp1', domain: '${domain}', tags: ['test'] }),
|
|
575
334
|
makeEntry({ id: 'other-gp1', domain: 'other-domain', tags: ['test'] }),
|
|
576
335
|
]);
|
|
577
|
-
const facade =
|
|
336
|
+
const facade = buildDomainFacade();
|
|
578
337
|
const op = facade.ops.find((o) => o.name === 'get_patterns')!;
|
|
579
338
|
const results = (await op.handler({})) as IntelligenceEntry[];
|
|
580
339
|
expect(results.every((e) => e.domain === '${domain}')).toBe(true);
|
|
581
340
|
});
|
|
582
341
|
|
|
583
342
|
it('search should scope to ${domain} with ranked results', async () => {
|
|
584
|
-
vault.seed([
|
|
343
|
+
runtime.vault.seed([
|
|
585
344
|
makeEntry({ id: '${domain}-s1', domain: '${domain}', title: 'Domain specific pattern', tags: ['find-me'] }),
|
|
586
345
|
makeEntry({ id: 'other-s1', domain: 'other', title: 'Other domain pattern', tags: ['nope'] }),
|
|
587
346
|
]);
|
|
588
|
-
brain
|
|
589
|
-
const facade =
|
|
347
|
+
runtime.brain.rebuildVocabulary();
|
|
348
|
+
const facade = buildDomainFacade();
|
|
590
349
|
const op = facade.ops.find((o) => o.name === 'search')!;
|
|
591
|
-
const results = (await op.handler({ query: 'pattern' })) as Array<{ entry: IntelligenceEntry; score: number
|
|
350
|
+
const results = (await op.handler({ query: 'pattern' })) as Array<{ entry: IntelligenceEntry; score: number }>;
|
|
592
351
|
expect(results.every((r) => r.entry.domain === '${domain}')).toBe(true);
|
|
593
|
-
if (results.length > 0) {
|
|
594
|
-
expect(results[0].score).toBeGreaterThan(0);
|
|
595
|
-
expect(results[0].breakdown).toBeDefined();
|
|
596
|
-
}
|
|
597
352
|
});
|
|
598
353
|
|
|
599
|
-
it('capture should add entry with ${domain} domain
|
|
600
|
-
const facade =
|
|
354
|
+
it('capture should add entry with ${domain} domain', async () => {
|
|
355
|
+
const facade = buildDomainFacade();
|
|
601
356
|
const captureOp = facade.ops.find((o) => o.name === 'capture')!;
|
|
602
357
|
const result = (await captureOp.handler({
|
|
603
358
|
id: '${domain}-cap1',
|
|
604
359
|
type: 'pattern',
|
|
605
|
-
title: 'Captured Pattern
|
|
360
|
+
title: 'Captured Pattern',
|
|
606
361
|
severity: 'warning',
|
|
607
|
-
description: 'A captured pattern
|
|
362
|
+
description: 'A captured pattern.',
|
|
608
363
|
tags: ['captured'],
|
|
609
|
-
})) as { captured: boolean
|
|
364
|
+
})) as { captured: boolean };
|
|
610
365
|
expect(result.captured).toBe(true);
|
|
611
|
-
|
|
612
|
-
const entry = vault.get('${domain}-cap1');
|
|
366
|
+
const entry = runtime.vault.get('${domain}-cap1');
|
|
613
367
|
expect(entry).not.toBeNull();
|
|
614
368
|
expect(entry!.domain).toBe('${domain}');
|
|
615
369
|
});
|
|
616
370
|
|
|
617
371
|
it('get_entry should return specific entry', async () => {
|
|
618
|
-
vault.seed([makeEntry({ id: '${domain}-ge1', domain: '${domain}', tags: ['test'] })]);
|
|
619
|
-
const facade =
|
|
372
|
+
runtime.vault.seed([makeEntry({ id: '${domain}-ge1', domain: '${domain}', tags: ['test'] })]);
|
|
373
|
+
const facade = buildDomainFacade();
|
|
620
374
|
const op = facade.ops.find((o) => o.name === 'get_entry')!;
|
|
621
375
|
const result = (await op.handler({ id: '${domain}-ge1' })) as IntelligenceEntry;
|
|
622
376
|
expect(result.id).toBe('${domain}-ge1');
|
|
623
377
|
});
|
|
624
378
|
|
|
625
|
-
it('get_entry should return error for missing entry', async () => {
|
|
626
|
-
const facade = ${factoryFn}(vault, brain);
|
|
627
|
-
const op = facade.ops.find((o) => o.name === 'get_entry')!;
|
|
628
|
-
const result = (await op.handler({ id: 'nonexistent' })) as { error: string };
|
|
629
|
-
expect(result.error).toBeDefined();
|
|
630
|
-
});
|
|
631
|
-
|
|
632
379
|
it('remove should delete entry', async () => {
|
|
633
|
-
vault.seed([makeEntry({ id: '${domain}-rm1', domain: '${domain}', tags: ['test'] })]);
|
|
634
|
-
const facade =
|
|
380
|
+
runtime.vault.seed([makeEntry({ id: '${domain}-rm1', domain: '${domain}', tags: ['test'] })]);
|
|
381
|
+
const facade = buildDomainFacade();
|
|
635
382
|
const op = facade.ops.find((o) => o.name === 'remove')!;
|
|
636
383
|
const result = (await op.handler({ id: '${domain}-rm1' })) as { removed: boolean };
|
|
637
384
|
expect(result.removed).toBe(true);
|
|
638
|
-
expect(vault.get('${domain}-rm1')).toBeNull();
|
|
385
|
+
expect(runtime.vault.get('${domain}-rm1')).toBeNull();
|
|
639
386
|
});
|
|
640
387
|
});`;
|
|
641
388
|
}
|
|
642
|
-
function pascalCase(s) {
|
|
643
|
-
return s
|
|
644
|
-
.split(/[-_\s]+/)
|
|
645
|
-
.map((w) => w.charAt(0).toUpperCase() + w.slice(1))
|
|
646
|
-
.join('');
|
|
647
|
-
}
|
|
648
389
|
function escapeQuotes(s) {
|
|
649
390
|
return s.replace(/\\/g, '\\\\').replace(/'/g, "\\'");
|
|
650
391
|
}
|