@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
|
@@ -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,196 @@ 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 (26)
|
|
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
|
+
// Agent-specific ops (5)
|
|
78
164
|
expect(opNames).toContain('health');
|
|
79
165
|
expect(opNames).toContain('identity');
|
|
80
166
|
expect(opNames).toContain('activate');
|
|
81
167
|
expect(opNames).toContain('inject_claude_md');
|
|
82
168
|
expect(opNames).toContain('setup');
|
|
83
|
-
|
|
84
|
-
expect(
|
|
169
|
+
// Total: 31
|
|
170
|
+
expect(facade.ops.length).toBe(31);
|
|
85
171
|
});
|
|
86
172
|
|
|
87
173
|
it('search should query across all domains with ranked results', async () => {
|
|
88
|
-
vault.seed([
|
|
174
|
+
runtime.vault.seed([
|
|
175
|
+
makeEntry({ id: 'c1', domain: 'alpha', title: 'Alpha pattern', tags: ['a'] }),
|
|
176
|
+
makeEntry({ id: 'c2', domain: 'beta', title: 'Beta pattern', tags: ['b'] }),
|
|
177
|
+
]);
|
|
178
|
+
runtime = createAgentRuntime({ agentId: '${config.id}', vaultPath: ':memory:', plansPath: join(plannerDir, 'plans2.json') });
|
|
179
|
+
runtime.vault.seed([
|
|
89
180
|
makeEntry({ id: 'c1', domain: 'alpha', title: 'Alpha pattern', tags: ['a'] }),
|
|
90
181
|
makeEntry({ id: 'c2', domain: 'beta', title: 'Beta pattern', tags: ['b'] }),
|
|
91
182
|
]);
|
|
92
|
-
|
|
93
|
-
const facade = makeCoreFacade();
|
|
183
|
+
const facade = buildCoreFacade();
|
|
94
184
|
const searchOp = facade.ops.find((o) => o.name === 'search')!;
|
|
95
185
|
const results = (await searchOp.handler({ query: 'pattern' })) as Array<{ entry: unknown; score: number; breakdown: unknown }>;
|
|
96
186
|
expect(Array.isArray(results)).toBe(true);
|
|
97
187
|
expect(results.length).toBe(2);
|
|
98
188
|
expect(results[0].score).toBeGreaterThan(0);
|
|
99
|
-
expect(results[0].breakdown).toBeDefined();
|
|
100
189
|
});
|
|
101
190
|
|
|
102
191
|
it('vault_stats should return counts', async () => {
|
|
103
|
-
vault.seed([
|
|
192
|
+
runtime.vault.seed([
|
|
104
193
|
makeEntry({ id: 'vs1', domain: 'd1', tags: ['x'] }),
|
|
105
194
|
makeEntry({ id: 'vs2', domain: 'd2', tags: ['y'] }),
|
|
106
195
|
]);
|
|
107
|
-
const facade =
|
|
196
|
+
const facade = buildCoreFacade();
|
|
108
197
|
const statsOp = facade.ops.find((o) => o.name === 'vault_stats')!;
|
|
109
198
|
const stats = (await statsOp.handler({})) as { totalEntries: number };
|
|
110
199
|
expect(stats.totalEntries).toBe(2);
|
|
111
200
|
});
|
|
112
201
|
|
|
113
202
|
it('health should return ok status', async () => {
|
|
114
|
-
const facade =
|
|
203
|
+
const facade = buildCoreFacade();
|
|
115
204
|
const healthOp = facade.ops.find((o) => o.name === 'health')!;
|
|
116
205
|
const health = (await healthOp.handler({})) as { status: string };
|
|
117
206
|
expect(health.status).toBe('ok');
|
|
118
207
|
});
|
|
119
208
|
|
|
120
209
|
it('identity should return persona', async () => {
|
|
121
|
-
const facade =
|
|
210
|
+
const facade = buildCoreFacade();
|
|
122
211
|
const identityOp = facade.ops.find((o) => o.name === 'identity')!;
|
|
123
212
|
const persona = (await identityOp.handler({})) as { name: string; role: string };
|
|
124
213
|
expect(persona.name).toBe('${escapeQuotes(config.name)}');
|
|
125
214
|
expect(persona.role).toBe('${escapeQuotes(config.role)}');
|
|
126
215
|
});
|
|
127
216
|
|
|
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
217
|
it('activate should return persona and setup status', async () => {
|
|
140
|
-
const facade =
|
|
218
|
+
const facade = buildCoreFacade();
|
|
141
219
|
const activateOp = facade.ops.find((o) => o.name === 'activate')!;
|
|
142
220
|
const result = (await activateOp.handler({ projectPath: '/tmp/nonexistent-test' })) as {
|
|
143
221
|
activated: boolean;
|
|
144
222
|
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
223
|
};
|
|
149
224
|
expect(result.activated).toBe(true);
|
|
150
225
|
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
226
|
});
|
|
157
227
|
|
|
158
228
|
it('activate with deactivate flag should return deactivation', async () => {
|
|
159
|
-
const facade =
|
|
229
|
+
const facade = buildCoreFacade();
|
|
160
230
|
const activateOp = facade.ops.find((o) => o.name === 'activate')!;
|
|
161
231
|
const result = (await activateOp.handler({ deactivate: true })) as { deactivated: boolean; message: string };
|
|
162
232
|
expect(result.deactivated).toBe(true);
|
|
@@ -167,7 +237,7 @@ ${domainDescribes}
|
|
|
167
237
|
const tempDir = join(tmpdir(), 'forge-inject-test-' + Date.now());
|
|
168
238
|
mkdirSync(tempDir, { recursive: true });
|
|
169
239
|
try {
|
|
170
|
-
const facade =
|
|
240
|
+
const facade = buildCoreFacade();
|
|
171
241
|
const injectOp = facade.ops.find((o) => o.name === 'inject_claude_md')!;
|
|
172
242
|
const result = (await injectOp.handler({ projectPath: tempDir })) as {
|
|
173
243
|
injected: boolean;
|
|
@@ -184,180 +254,21 @@ ${domainDescribes}
|
|
|
184
254
|
}
|
|
185
255
|
});
|
|
186
256
|
|
|
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
257
|
it('setup should return project and global CLAUDE.md status', async () => {
|
|
203
|
-
const facade =
|
|
258
|
+
const facade = buildCoreFacade();
|
|
204
259
|
const setupOp = facade.ops.find((o) => o.name === 'setup')!;
|
|
205
260
|
const result = (await setupOp.handler({ projectPath: '/tmp/nonexistent-test' })) as {
|
|
206
261
|
agent: { name: string };
|
|
207
|
-
claude_md: {
|
|
208
|
-
project: { exists: boolean; has_agent_section: boolean };
|
|
209
|
-
global: { exists: boolean; has_agent_section: boolean };
|
|
210
|
-
};
|
|
211
262
|
vault: { entries: number };
|
|
212
263
|
recommendations: string[];
|
|
213
264
|
};
|
|
214
265
|
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
266
|
expect(result.vault.entries).toBe(0);
|
|
218
267
|
expect(result.recommendations.length).toBeGreaterThan(0);
|
|
219
268
|
});
|
|
220
269
|
|
|
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
270
|
it('create_plan should create a draft plan', async () => {
|
|
360
|
-
const facade =
|
|
271
|
+
const facade = buildCoreFacade();
|
|
361
272
|
const createOp = facade.ops.find((o) => o.name === 'create_plan')!;
|
|
362
273
|
const result = (await createOp.handler({
|
|
363
274
|
objective: 'Add caching',
|
|
@@ -369,190 +280,34 @@ ${domainDescribes}
|
|
|
369
280
|
expect(result.plan.tasks).toHaveLength(1);
|
|
370
281
|
});
|
|
371
282
|
|
|
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
283
|
it('brain_stats should return intelligence stats', async () => {
|
|
488
|
-
const facade =
|
|
284
|
+
const facade = buildCoreFacade();
|
|
489
285
|
const statsOp = facade.ops.find((o) => o.name === 'brain_stats')!;
|
|
490
286
|
const result = (await statsOp.handler({})) as {
|
|
491
287
|
vocabularySize: number;
|
|
492
288
|
feedbackCount: number;
|
|
493
|
-
weights: { semantic: number; severity: number; recency: number; tagOverlap: number; domainMatch: number };
|
|
494
289
|
};
|
|
495
290
|
expect(result.vocabularySize).toBe(0);
|
|
496
291
|
expect(result.feedbackCount).toBe(0);
|
|
497
|
-
expect(result.weights.semantic).toBeCloseTo(0.40, 2);
|
|
498
|
-
expect(result.weights.severity).toBeCloseTo(0.15, 2);
|
|
499
|
-
});
|
|
500
|
-
|
|
501
|
-
it('brain_stats should reflect feedback count after recording', async () => {
|
|
502
|
-
const facade = makeCoreFacade();
|
|
503
|
-
const feedbackOp = facade.ops.find((o) => o.name === 'record_feedback')!;
|
|
504
|
-
await feedbackOp.handler({ query: 'q1', entryId: 'e1', action: 'accepted' });
|
|
505
|
-
await feedbackOp.handler({ query: 'q2', entryId: 'e2', action: 'dismissed' });
|
|
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);
|
|
509
|
-
});
|
|
510
|
-
|
|
511
|
-
it('llm_status should return provider availability', async () => {
|
|
512
|
-
const facade = makeCoreFacade();
|
|
513
|
-
const llmStatusOp = facade.ops.find((o) => o.name === 'llm_status')!;
|
|
514
|
-
const result = (await llmStatusOp.handler({})) as {
|
|
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
292
|
});
|
|
526
293
|
|
|
527
|
-
it('
|
|
528
|
-
const
|
|
529
|
-
const
|
|
530
|
-
const
|
|
531
|
-
|
|
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);
|
|
294
|
+
it('curator_status should return table counts', async () => {
|
|
295
|
+
const facade = buildCoreFacade();
|
|
296
|
+
const statusOp = facade.ops.find((o) => o.name === 'curator_status')!;
|
|
297
|
+
const result = (await statusOp.handler({})) as { initialized: boolean };
|
|
298
|
+
expect(result.initialized).toBe(true);
|
|
543
299
|
});
|
|
544
300
|
|
|
545
|
-
it('
|
|
546
|
-
vault.seed([
|
|
547
|
-
makeEntry({ id: '
|
|
301
|
+
it('curator_health_audit should return score', async () => {
|
|
302
|
+
runtime.vault.seed([
|
|
303
|
+
makeEntry({ id: 'ha1', type: 'pattern', tags: ['a', 'b'] }),
|
|
304
|
+
makeEntry({ id: 'ha2', type: 'anti-pattern', tags: ['c', 'd'] }),
|
|
548
305
|
]);
|
|
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);
|
|
306
|
+
runtime.curator.groomAll();
|
|
307
|
+
const facade = buildCoreFacade();
|
|
308
|
+
const healthOp = facade.ops.find((o) => o.name === 'curator_health_audit')!;
|
|
309
|
+
const result = (await healthOp.handler({})) as { score: number };
|
|
310
|
+
expect(result.score).toBeGreaterThan(0);
|
|
556
311
|
});
|
|
557
312
|
});
|
|
558
313
|
});
|
|
@@ -561,11 +316,14 @@ ${domainDescribes}
|
|
|
561
316
|
|
|
562
317
|
function generateDomainDescribe(agentId: string, domain: string): string {
|
|
563
318
|
const facadeName = `${agentId}_${domain.replace(/-/g, '_')}`;
|
|
564
|
-
const factoryFn = `create${pascalCase(domain)}Facade`;
|
|
565
319
|
|
|
566
320
|
return ` describe('${facadeName}', () => {
|
|
321
|
+
function buildDomainFacade(): FacadeConfig {
|
|
322
|
+
return createDomainFacade(runtime, '${agentId}', '${domain}');
|
|
323
|
+
}
|
|
324
|
+
|
|
567
325
|
it('should create facade with expected ops', () => {
|
|
568
|
-
const facade =
|
|
326
|
+
const facade = buildDomainFacade();
|
|
569
327
|
expect(facade.name).toBe('${facadeName}');
|
|
570
328
|
const opNames = facade.ops.map((o) => o.name);
|
|
571
329
|
expect(opNames).toContain('get_patterns');
|
|
@@ -576,83 +334,64 @@ function generateDomainDescribe(agentId: string, domain: string): string {
|
|
|
576
334
|
});
|
|
577
335
|
|
|
578
336
|
it('get_patterns should return entries for ${domain}', async () => {
|
|
579
|
-
vault.seed([
|
|
337
|
+
runtime.vault.seed([
|
|
580
338
|
makeEntry({ id: '${domain}-gp1', domain: '${domain}', tags: ['test'] }),
|
|
581
339
|
makeEntry({ id: 'other-gp1', domain: 'other-domain', tags: ['test'] }),
|
|
582
340
|
]);
|
|
583
|
-
const facade =
|
|
341
|
+
const facade = buildDomainFacade();
|
|
584
342
|
const op = facade.ops.find((o) => o.name === 'get_patterns')!;
|
|
585
343
|
const results = (await op.handler({})) as IntelligenceEntry[];
|
|
586
344
|
expect(results.every((e) => e.domain === '${domain}')).toBe(true);
|
|
587
345
|
});
|
|
588
346
|
|
|
589
347
|
it('search should scope to ${domain} with ranked results', async () => {
|
|
590
|
-
vault.seed([
|
|
348
|
+
runtime.vault.seed([
|
|
591
349
|
makeEntry({ id: '${domain}-s1', domain: '${domain}', title: 'Domain specific pattern', tags: ['find-me'] }),
|
|
592
350
|
makeEntry({ id: 'other-s1', domain: 'other', title: 'Other domain pattern', tags: ['nope'] }),
|
|
593
351
|
]);
|
|
594
|
-
brain
|
|
595
|
-
const facade =
|
|
352
|
+
runtime.brain.rebuildVocabulary();
|
|
353
|
+
const facade = buildDomainFacade();
|
|
596
354
|
const op = facade.ops.find((o) => o.name === 'search')!;
|
|
597
|
-
const results = (await op.handler({ query: 'pattern' })) as Array<{ entry: IntelligenceEntry; score: number
|
|
355
|
+
const results = (await op.handler({ query: 'pattern' })) as Array<{ entry: IntelligenceEntry; score: number }>;
|
|
598
356
|
expect(results.every((r) => r.entry.domain === '${domain}')).toBe(true);
|
|
599
|
-
if (results.length > 0) {
|
|
600
|
-
expect(results[0].score).toBeGreaterThan(0);
|
|
601
|
-
expect(results[0].breakdown).toBeDefined();
|
|
602
|
-
}
|
|
603
357
|
});
|
|
604
358
|
|
|
605
|
-
it('capture should add entry with ${domain} domain
|
|
606
|
-
const facade =
|
|
359
|
+
it('capture should add entry with ${domain} domain', async () => {
|
|
360
|
+
const facade = buildDomainFacade();
|
|
607
361
|
const captureOp = facade.ops.find((o) => o.name === 'capture')!;
|
|
608
362
|
const result = (await captureOp.handler({
|
|
609
363
|
id: '${domain}-cap1',
|
|
610
364
|
type: 'pattern',
|
|
611
|
-
title: 'Captured Pattern
|
|
365
|
+
title: 'Captured Pattern',
|
|
612
366
|
severity: 'warning',
|
|
613
|
-
description: 'A captured pattern
|
|
367
|
+
description: 'A captured pattern.',
|
|
614
368
|
tags: ['captured'],
|
|
615
|
-
})) as { captured: boolean
|
|
369
|
+
})) as { captured: boolean };
|
|
616
370
|
expect(result.captured).toBe(true);
|
|
617
|
-
|
|
618
|
-
const entry = vault.get('${domain}-cap1');
|
|
371
|
+
const entry = runtime.vault.get('${domain}-cap1');
|
|
619
372
|
expect(entry).not.toBeNull();
|
|
620
373
|
expect(entry!.domain).toBe('${domain}');
|
|
621
374
|
});
|
|
622
375
|
|
|
623
376
|
it('get_entry should return specific entry', async () => {
|
|
624
|
-
vault.seed([makeEntry({ id: '${domain}-ge1', domain: '${domain}', tags: ['test'] })]);
|
|
625
|
-
const facade =
|
|
377
|
+
runtime.vault.seed([makeEntry({ id: '${domain}-ge1', domain: '${domain}', tags: ['test'] })]);
|
|
378
|
+
const facade = buildDomainFacade();
|
|
626
379
|
const op = facade.ops.find((o) => o.name === 'get_entry')!;
|
|
627
380
|
const result = (await op.handler({ id: '${domain}-ge1' })) as IntelligenceEntry;
|
|
628
381
|
expect(result.id).toBe('${domain}-ge1');
|
|
629
382
|
});
|
|
630
383
|
|
|
631
|
-
it('get_entry should return error for missing entry', async () => {
|
|
632
|
-
const facade = ${factoryFn}(vault, brain);
|
|
633
|
-
const op = facade.ops.find((o) => o.name === 'get_entry')!;
|
|
634
|
-
const result = (await op.handler({ id: 'nonexistent' })) as { error: string };
|
|
635
|
-
expect(result.error).toBeDefined();
|
|
636
|
-
});
|
|
637
|
-
|
|
638
384
|
it('remove should delete entry', async () => {
|
|
639
|
-
vault.seed([makeEntry({ id: '${domain}-rm1', domain: '${domain}', tags: ['test'] })]);
|
|
640
|
-
const facade =
|
|
385
|
+
runtime.vault.seed([makeEntry({ id: '${domain}-rm1', domain: '${domain}', tags: ['test'] })]);
|
|
386
|
+
const facade = buildDomainFacade();
|
|
641
387
|
const op = facade.ops.find((o) => o.name === 'remove')!;
|
|
642
388
|
const result = (await op.handler({ id: '${domain}-rm1' })) as { removed: boolean };
|
|
643
389
|
expect(result.removed).toBe(true);
|
|
644
|
-
expect(vault.get('${domain}-rm1')).toBeNull();
|
|
390
|
+
expect(runtime.vault.get('${domain}-rm1')).toBeNull();
|
|
645
391
|
});
|
|
646
392
|
});`;
|
|
647
393
|
}
|
|
648
394
|
|
|
649
|
-
function pascalCase(s: string): string {
|
|
650
|
-
return s
|
|
651
|
-
.split(/[-_\s]+/)
|
|
652
|
-
.map((w) => w.charAt(0).toUpperCase() + w.slice(1))
|
|
653
|
-
.join('');
|
|
654
|
-
}
|
|
655
|
-
|
|
656
395
|
function escapeQuotes(s: string): string {
|
|
657
396
|
return s.replace(/\\/g, '\\\\').replace(/'/g, "\\'");
|
|
658
397
|
}
|