@soleri/forge 0.0.1 → 4.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/CHANGELOG.md +98 -0
- package/README.md +199 -0
- package/dist/facades/forge.facade.d.ts +9 -0
- package/dist/facades/forge.facade.js +134 -0
- package/dist/facades/forge.facade.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +81 -0
- package/dist/index.js.map +1 -0
- package/dist/knowledge-installer.d.ts +31 -0
- package/dist/knowledge-installer.js +437 -0
- package/dist/knowledge-installer.js.map +1 -0
- package/dist/scaffolder.d.ts +13 -0
- package/dist/scaffolder.js +330 -0
- package/dist/scaffolder.js.map +1 -0
- package/dist/templates/activate.d.ts +9 -0
- package/dist/templates/activate.js +139 -0
- package/dist/templates/activate.js.map +1 -0
- 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/claude-md-template.d.ts +11 -0
- package/dist/templates/claude-md-template.js +73 -0
- package/dist/templates/claude-md-template.js.map +1 -0
- package/dist/templates/core-facade.d.ts +6 -0
- package/dist/templates/core-facade.js +456 -0
- package/dist/templates/core-facade.js.map +1 -0
- package/dist/templates/domain-facade.d.ts +7 -0
- package/dist/templates/domain-facade.js +119 -0
- package/dist/templates/domain-facade.js.map +1 -0
- package/dist/templates/entry-point.d.ts +5 -0
- package/dist/templates/entry-point.js +116 -0
- package/dist/templates/entry-point.js.map +1 -0
- 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/inject-claude-md.d.ts +11 -0
- package/dist/templates/inject-claude-md.js +92 -0
- package/dist/templates/inject-claude-md.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-client.d.ts +7 -0
- package/dist/templates/llm-client.js +300 -0
- package/dist/templates/llm-client.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.d.ts +2 -0
- package/dist/templates/package-json.js +37 -0
- package/dist/templates/package-json.js.map +1 -0
- package/dist/templates/persona.d.ts +2 -0
- package/dist/templates/persona.js +42 -0
- package/dist/templates/persona.js.map +1 -0
- 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/readme.d.ts +5 -0
- package/dist/templates/readme.js +316 -0
- package/dist/templates/readme.js.map +1 -0
- package/dist/templates/setup-script.d.ts +6 -0
- package/dist/templates/setup-script.js +112 -0
- package/dist/templates/setup-script.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 +6 -0
- package/dist/templates/test-facades.js +649 -0
- package/dist/templates/test-facades.js.map +1 -0
- 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/tsconfig.d.ts +1 -0
- package/dist/templates/tsconfig.js +25 -0
- package/dist/templates/tsconfig.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/templates/vitest-config.d.ts +1 -0
- package/dist/templates/vitest-config.js +27 -0
- package/dist/templates/vitest-config.js.map +1 -0
- package/dist/types.d.ts +89 -0
- package/dist/types.js +21 -0
- package/dist/types.js.map +1 -0
- package/package.json +42 -4
- package/src/__tests__/knowledge-installer.test.ts +805 -0
- package/src/__tests__/scaffolder.test.ts +323 -0
- package/src/facades/forge.facade.ts +150 -0
- package/src/index.ts +101 -0
- package/src/knowledge-installer.ts +532 -0
- package/src/scaffolder.ts +386 -0
- package/src/templates/activate.ts +145 -0
- package/src/templates/claude-md-template.ts +137 -0
- package/src/templates/core-facade.ts +457 -0
- package/src/templates/domain-facade.ts +121 -0
- package/src/templates/entry-point.ts +120 -0
- package/src/templates/inject-claude-md.ts +94 -0
- package/src/templates/llm-client.ts +301 -0
- package/src/templates/package-json.ts +39 -0
- package/src/templates/persona.ts +45 -0
- package/src/templates/readme.ts +319 -0
- package/src/templates/setup-script.ts +113 -0
- package/src/templates/test-facades.ts +656 -0
- package/src/templates/tsconfig.ts +25 -0
- package/src/templates/vitest-config.ts +26 -0
- package/src/types.ts +68 -0
- package/tsconfig.json +21 -0
- package/vitest.config.ts +15 -0
|
@@ -0,0 +1,805 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
|
2
|
+
import { mkdirSync, rmSync, writeFileSync, readFileSync, existsSync, readdirSync } from 'node:fs';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
import { tmpdir } from 'node:os';
|
|
5
|
+
import {
|
|
6
|
+
installKnowledge,
|
|
7
|
+
patchIndexTs,
|
|
8
|
+
patchClaudeMdContent,
|
|
9
|
+
generateVaultOnlyDomainFacade,
|
|
10
|
+
} from '../knowledge-installer.js';
|
|
11
|
+
|
|
12
|
+
describe('Knowledge Installer', () => {
|
|
13
|
+
let tempDir: string;
|
|
14
|
+
let agentDir: string;
|
|
15
|
+
let bundleDir: string;
|
|
16
|
+
|
|
17
|
+
// --- Helpers to build a minimal agent structure ---
|
|
18
|
+
|
|
19
|
+
function createMinimalAgent(opts: { hasBrain?: boolean } = {}): void {
|
|
20
|
+
const { hasBrain = true } = opts;
|
|
21
|
+
|
|
22
|
+
// package.json
|
|
23
|
+
writeFileSync(
|
|
24
|
+
join(agentDir, 'package.json'),
|
|
25
|
+
JSON.stringify({ name: 'test-agent-mcp', version: '1.0.0' }),
|
|
26
|
+
);
|
|
27
|
+
|
|
28
|
+
// src/intelligence/data/ with one existing domain
|
|
29
|
+
mkdirSync(join(agentDir, 'src', 'intelligence', 'data'), { recursive: true });
|
|
30
|
+
writeFileSync(
|
|
31
|
+
join(agentDir, 'src', 'intelligence', 'data', 'existing-domain.json'),
|
|
32
|
+
JSON.stringify({ domain: 'existing-domain', version: '1.0.0', entries: [] }),
|
|
33
|
+
);
|
|
34
|
+
|
|
35
|
+
// src/facades/
|
|
36
|
+
mkdirSync(join(agentDir, 'src', 'facades'), { recursive: true });
|
|
37
|
+
|
|
38
|
+
// src/activation/
|
|
39
|
+
mkdirSync(join(agentDir, 'src', 'activation'), { recursive: true });
|
|
40
|
+
|
|
41
|
+
// Brain directory (if applicable)
|
|
42
|
+
if (hasBrain) {
|
|
43
|
+
mkdirSync(join(agentDir, 'src', 'brain'), { recursive: true });
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function createMockBundle(domain: string, entryCount: number = 2): string {
|
|
48
|
+
const entries = Array.from({ length: entryCount }, (_, i) => ({
|
|
49
|
+
id: `${domain}-${i + 1}`,
|
|
50
|
+
type: 'pattern',
|
|
51
|
+
domain,
|
|
52
|
+
title: `${domain} pattern ${i + 1}`,
|
|
53
|
+
severity: 'warning',
|
|
54
|
+
description: `Description for ${domain} pattern ${i + 1}`,
|
|
55
|
+
tags: [domain, 'test'],
|
|
56
|
+
}));
|
|
57
|
+
|
|
58
|
+
const file = join(bundleDir, `${domain}.json`);
|
|
59
|
+
writeFileSync(file, JSON.stringify({ domain, version: '1.0.0', entries }));
|
|
60
|
+
return file;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function createMockIndexTs(): void {
|
|
64
|
+
const content = `#!/usr/bin/env node
|
|
65
|
+
|
|
66
|
+
import { registerAllFacades } from './facades/facade-factory.js';
|
|
67
|
+
import { createExistingDomainFacade } from './facades/existing-domain.facade.js';
|
|
68
|
+
import { createCoreFacade } from './facades/core.facade.js';
|
|
69
|
+
import { Vault } from './vault/vault.js';
|
|
70
|
+
import { Brain } from './brain/brain.js';
|
|
71
|
+
|
|
72
|
+
async function main(): Promise<void> {
|
|
73
|
+
const vault = new Vault('test');
|
|
74
|
+
const brain = new Brain(vault);
|
|
75
|
+
|
|
76
|
+
const facades = [
|
|
77
|
+
createExistingDomainFacade(vault, brain),
|
|
78
|
+
createCoreFacade(vault, planner, brain),
|
|
79
|
+
];
|
|
80
|
+
|
|
81
|
+
registerAllFacades(server, facades);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
main();
|
|
85
|
+
`;
|
|
86
|
+
writeFileSync(join(agentDir, 'src', 'index.ts'), content);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function createMockClaudeMdContent(): void {
|
|
90
|
+
const content = `export function getClaudeMdContent(): string {
|
|
91
|
+
return [
|
|
92
|
+
'# Test Agent',
|
|
93
|
+
'| existing-domain patterns | \`test_agent_existing_domain\` | \`get_patterns\` |',
|
|
94
|
+
'| Search existing-domain | \`test_agent_existing_domain\` | \`search\` |',
|
|
95
|
+
'| Capture existing-domain | \`test_agent_existing_domain\` | \`capture\` |',
|
|
96
|
+
'| Memory search | \`test_agent_core\` | \`memory_search\` |',
|
|
97
|
+
'| Memory capture | \`test_agent_core\` | \`memory_capture\` |',
|
|
98
|
+
].join('\\n');
|
|
99
|
+
}
|
|
100
|
+
`;
|
|
101
|
+
writeFileSync(join(agentDir, 'src', 'activation', 'claude-md-content.ts'), content);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
beforeEach(() => {
|
|
105
|
+
tempDir = join(tmpdir(), `forge-ki-test-${Date.now()}`);
|
|
106
|
+
agentDir = join(tempDir, 'test-agent');
|
|
107
|
+
bundleDir = join(tempDir, 'bundles');
|
|
108
|
+
mkdirSync(agentDir, { recursive: true });
|
|
109
|
+
mkdirSync(bundleDir, { recursive: true });
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
afterEach(() => {
|
|
113
|
+
rmSync(tempDir, { recursive: true, force: true });
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
// ── Validation tests ──────────────────────────────────────────
|
|
117
|
+
|
|
118
|
+
describe('validation', () => {
|
|
119
|
+
it('should fail if package.json is missing', async () => {
|
|
120
|
+
const result = await installKnowledge({
|
|
121
|
+
agentPath: agentDir,
|
|
122
|
+
bundlePath: bundleDir,
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
expect(result.success).toBe(false);
|
|
126
|
+
expect(result.summary).toContain('No package.json found');
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
it('should fail if package name does not end with -mcp', async () => {
|
|
130
|
+
writeFileSync(join(agentDir, 'package.json'), JSON.stringify({ name: 'not-an-agent' }));
|
|
131
|
+
|
|
132
|
+
const result = await installKnowledge({
|
|
133
|
+
agentPath: agentDir,
|
|
134
|
+
bundlePath: bundleDir,
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
expect(result.success).toBe(false);
|
|
138
|
+
expect(result.summary).toContain('does not end with -mcp');
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
it('should fail if intelligence data directory is missing', async () => {
|
|
142
|
+
writeFileSync(join(agentDir, 'package.json'), JSON.stringify({ name: 'test-agent-mcp' }));
|
|
143
|
+
|
|
144
|
+
const result = await installKnowledge({
|
|
145
|
+
agentPath: agentDir,
|
|
146
|
+
bundlePath: bundleDir,
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
expect(result.success).toBe(false);
|
|
150
|
+
expect(result.summary).toContain('src/intelligence/data/ directory not found');
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
it('should fail if no bundle files found', async () => {
|
|
154
|
+
createMinimalAgent();
|
|
155
|
+
|
|
156
|
+
const result = await installKnowledge({
|
|
157
|
+
agentPath: agentDir,
|
|
158
|
+
bundlePath: join(tempDir, 'nonexistent'),
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
expect(result.success).toBe(false);
|
|
162
|
+
expect(result.summary).toContain('No .json bundle files');
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
it('should fail if all bundles have validation errors', async () => {
|
|
166
|
+
createMinimalAgent();
|
|
167
|
+
writeFileSync(join(bundleDir, 'bad.json'), JSON.stringify({ entries: 'not-array' }));
|
|
168
|
+
|
|
169
|
+
const result = await installKnowledge({
|
|
170
|
+
agentPath: agentDir,
|
|
171
|
+
bundlePath: bundleDir,
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
expect(result.success).toBe(false);
|
|
175
|
+
expect(result.summary).toContain('All bundles failed validation');
|
|
176
|
+
});
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
// ── Bundle validation ─────────────────────────────────────────
|
|
180
|
+
|
|
181
|
+
describe('bundle validation', () => {
|
|
182
|
+
it('should validate entry required fields', async () => {
|
|
183
|
+
createMinimalAgent();
|
|
184
|
+
writeFileSync(
|
|
185
|
+
join(bundleDir, 'bad.json'),
|
|
186
|
+
JSON.stringify({
|
|
187
|
+
domain: 'test',
|
|
188
|
+
version: '1.0.0',
|
|
189
|
+
entries: [{ id: '', type: 'invalid', tags: 'not-array' }],
|
|
190
|
+
}),
|
|
191
|
+
);
|
|
192
|
+
|
|
193
|
+
const result = await installKnowledge({
|
|
194
|
+
agentPath: agentDir,
|
|
195
|
+
bundlePath: bundleDir,
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
expect(result.success).toBe(false);
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
it('should accept a single .json file as bundlePath', async () => {
|
|
202
|
+
createMinimalAgent();
|
|
203
|
+
const file = createMockBundle('new-domain');
|
|
204
|
+
|
|
205
|
+
const result = await installKnowledge({
|
|
206
|
+
agentPath: agentDir,
|
|
207
|
+
bundlePath: file,
|
|
208
|
+
generateFacades: false,
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
// Will fail at build step since there's no real build setup, but bundles should be installed
|
|
212
|
+
expect(result.bundlesInstalled).toBe(1);
|
|
213
|
+
expect(result.domainsAdded).toContain('new-domain');
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
it('should skip invalid bundles but install valid ones', async () => {
|
|
217
|
+
createMinimalAgent();
|
|
218
|
+
createMockBundle('valid-domain', 3);
|
|
219
|
+
writeFileSync(
|
|
220
|
+
join(bundleDir, 'invalid.json'),
|
|
221
|
+
JSON.stringify({ domain: 'bad', version: '1.0.0', entries: [{ id: '' }] }),
|
|
222
|
+
);
|
|
223
|
+
|
|
224
|
+
const result = await installKnowledge({
|
|
225
|
+
agentPath: agentDir,
|
|
226
|
+
bundlePath: bundleDir,
|
|
227
|
+
generateFacades: false,
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
expect(result.bundlesInstalled).toBe(1);
|
|
231
|
+
expect(result.warnings.length).toBeGreaterThan(0);
|
|
232
|
+
});
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
// ── Bundle copying ────────────────────────────────────────────
|
|
236
|
+
|
|
237
|
+
describe('bundle copying', () => {
|
|
238
|
+
it('should copy bundles to intelligence data directory', async () => {
|
|
239
|
+
createMinimalAgent();
|
|
240
|
+
createMockBundle('new-domain', 5);
|
|
241
|
+
|
|
242
|
+
await installKnowledge({
|
|
243
|
+
agentPath: agentDir,
|
|
244
|
+
bundlePath: bundleDir,
|
|
245
|
+
generateFacades: false,
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
const dest = join(agentDir, 'src', 'intelligence', 'data', 'new-domain.json');
|
|
249
|
+
expect(existsSync(dest)).toBe(true);
|
|
250
|
+
|
|
251
|
+
const copied = JSON.parse(readFileSync(dest, 'utf-8'));
|
|
252
|
+
expect(copied.domain).toBe('new-domain');
|
|
253
|
+
expect(copied.entries).toHaveLength(5);
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
it('should overwrite existing bundle files (upsert)', async () => {
|
|
257
|
+
createMinimalAgent();
|
|
258
|
+
createMockBundle('existing-domain', 3);
|
|
259
|
+
|
|
260
|
+
await installKnowledge({
|
|
261
|
+
agentPath: agentDir,
|
|
262
|
+
bundlePath: bundleDir,
|
|
263
|
+
generateFacades: false,
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
const dest = join(agentDir, 'src', 'intelligence', 'data', 'existing-domain.json');
|
|
267
|
+
const copied = JSON.parse(readFileSync(dest, 'utf-8'));
|
|
268
|
+
expect(copied.entries).toHaveLength(3); // overwritten with 3 entries
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
it('should copy to dist directory if it exists', async () => {
|
|
272
|
+
createMinimalAgent();
|
|
273
|
+
mkdirSync(join(agentDir, 'dist'), { recursive: true });
|
|
274
|
+
createMockBundle('new-domain');
|
|
275
|
+
|
|
276
|
+
await installKnowledge({
|
|
277
|
+
agentPath: agentDir,
|
|
278
|
+
bundlePath: bundleDir,
|
|
279
|
+
generateFacades: false,
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
const distFile = join(agentDir, 'dist', 'intelligence', 'data', 'new-domain.json');
|
|
283
|
+
expect(existsSync(distFile)).toBe(true);
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
it('should classify domains as added vs updated', async () => {
|
|
287
|
+
createMinimalAgent();
|
|
288
|
+
createMockBundle('existing-domain', 1);
|
|
289
|
+
createMockBundle('brand-new', 2);
|
|
290
|
+
|
|
291
|
+
const result = await installKnowledge({
|
|
292
|
+
agentPath: agentDir,
|
|
293
|
+
bundlePath: bundleDir,
|
|
294
|
+
generateFacades: false,
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
expect(result.domainsAdded).toContain('brand-new');
|
|
298
|
+
expect(result.domainsUpdated).toContain('existing-domain');
|
|
299
|
+
});
|
|
300
|
+
});
|
|
301
|
+
|
|
302
|
+
// ── Facade generation ─────────────────────────────────────────
|
|
303
|
+
|
|
304
|
+
describe('facade generation', () => {
|
|
305
|
+
it('should generate vault+brain facades when brain dir exists', async () => {
|
|
306
|
+
createMinimalAgent({ hasBrain: true });
|
|
307
|
+
createMockBundle('new-domain');
|
|
308
|
+
|
|
309
|
+
await installKnowledge({
|
|
310
|
+
agentPath: agentDir,
|
|
311
|
+
bundlePath: bundleDir,
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
const facadePath = join(agentDir, 'src', 'facades', 'new-domain.facade.ts');
|
|
315
|
+
expect(existsSync(facadePath)).toBe(true);
|
|
316
|
+
|
|
317
|
+
const content = readFileSync(facadePath, 'utf-8');
|
|
318
|
+
expect(content).toContain('brain: Brain');
|
|
319
|
+
expect(content).toContain('brain.intelligentSearch');
|
|
320
|
+
expect(content).toContain('brain.enrichAndCapture');
|
|
321
|
+
});
|
|
322
|
+
|
|
323
|
+
it('should generate vault-only facades when brain dir is absent', async () => {
|
|
324
|
+
createMinimalAgent({ hasBrain: false });
|
|
325
|
+
createMockBundle('new-domain');
|
|
326
|
+
|
|
327
|
+
await installKnowledge({
|
|
328
|
+
agentPath: agentDir,
|
|
329
|
+
bundlePath: bundleDir,
|
|
330
|
+
});
|
|
331
|
+
|
|
332
|
+
const facadePath = join(agentDir, 'src', 'facades', 'new-domain.facade.ts');
|
|
333
|
+
expect(existsSync(facadePath)).toBe(true);
|
|
334
|
+
|
|
335
|
+
const content = readFileSync(facadePath, 'utf-8');
|
|
336
|
+
expect(content).toContain('vault: Vault');
|
|
337
|
+
expect(content).not.toContain('brain: Brain');
|
|
338
|
+
expect(content).toContain('vault.search');
|
|
339
|
+
expect(content).toContain('vault.add');
|
|
340
|
+
});
|
|
341
|
+
|
|
342
|
+
it('should not generate facades for updated (existing) domains', async () => {
|
|
343
|
+
createMinimalAgent();
|
|
344
|
+
createMockBundle('existing-domain');
|
|
345
|
+
|
|
346
|
+
const result = await installKnowledge({
|
|
347
|
+
agentPath: agentDir,
|
|
348
|
+
bundlePath: bundleDir,
|
|
349
|
+
});
|
|
350
|
+
|
|
351
|
+
expect(result.facadesGenerated).toEqual([]);
|
|
352
|
+
});
|
|
353
|
+
|
|
354
|
+
it('should skip facade generation when generateFacades is false', async () => {
|
|
355
|
+
createMinimalAgent();
|
|
356
|
+
createMockBundle('new-domain');
|
|
357
|
+
|
|
358
|
+
const result = await installKnowledge({
|
|
359
|
+
agentPath: agentDir,
|
|
360
|
+
bundlePath: bundleDir,
|
|
361
|
+
generateFacades: false,
|
|
362
|
+
});
|
|
363
|
+
|
|
364
|
+
expect(result.facadesGenerated).toEqual([]);
|
|
365
|
+
const facadePath = join(agentDir, 'src', 'facades', 'new-domain.facade.ts');
|
|
366
|
+
expect(existsSync(facadePath)).toBe(false);
|
|
367
|
+
});
|
|
368
|
+
});
|
|
369
|
+
|
|
370
|
+
// ── generateVaultOnlyDomainFacade ─────────────────────────────
|
|
371
|
+
|
|
372
|
+
describe('generateVaultOnlyDomainFacade', () => {
|
|
373
|
+
it('should generate valid facade code', () => {
|
|
374
|
+
const code = generateVaultOnlyDomainFacade('gaudi', 'api-design');
|
|
375
|
+
|
|
376
|
+
expect(code).toContain('createApiDesignFacade');
|
|
377
|
+
expect(code).toContain("name: 'gaudi_api_design'");
|
|
378
|
+
expect(code).toContain('vault: Vault');
|
|
379
|
+
expect(code).not.toContain('Brain');
|
|
380
|
+
expect(code).toContain('vault.search');
|
|
381
|
+
expect(code).toContain('vault.add');
|
|
382
|
+
});
|
|
383
|
+
|
|
384
|
+
it('should use correct facade name with underscores', () => {
|
|
385
|
+
const code = generateVaultOnlyDomainFacade('my-agent', 'web-components');
|
|
386
|
+
|
|
387
|
+
expect(code).toContain("name: 'my-agent_web_components'");
|
|
388
|
+
expect(code).toContain('createWebComponentsFacade');
|
|
389
|
+
});
|
|
390
|
+
});
|
|
391
|
+
|
|
392
|
+
// ── patchIndexTs ──────────────────────────────────────────────
|
|
393
|
+
|
|
394
|
+
describe('patchIndexTs', () => {
|
|
395
|
+
const sampleSource = `import { registerAllFacades } from './facades/facade-factory.js';
|
|
396
|
+
import { createExistingFacade } from './facades/existing.facade.js';
|
|
397
|
+
import { createCoreFacade } from './facades/core.facade.js';
|
|
398
|
+
import { Vault } from './vault/vault.js';
|
|
399
|
+
|
|
400
|
+
const facades = [
|
|
401
|
+
createExistingFacade(vault, brain),
|
|
402
|
+
createCoreFacade(vault, planner, brain),
|
|
403
|
+
];
|
|
404
|
+
`;
|
|
405
|
+
|
|
406
|
+
it('should insert imports before createCoreFacade import', () => {
|
|
407
|
+
const result = patchIndexTs(sampleSource, ['api-design', 'security'], true);
|
|
408
|
+
expect(result).not.toBeNull();
|
|
409
|
+
|
|
410
|
+
const lines = result!.split('\n');
|
|
411
|
+
const apiImportIdx = lines.findIndex((l) => l.includes('createApiDesignFacade'));
|
|
412
|
+
const secImportIdx = lines.findIndex((l) => l.includes('createSecurityFacade'));
|
|
413
|
+
const coreImportIdx = lines.findIndex((l) => l.includes('createCoreFacade'));
|
|
414
|
+
|
|
415
|
+
expect(apiImportIdx).toBeLessThan(coreImportIdx);
|
|
416
|
+
expect(secImportIdx).toBeLessThan(coreImportIdx);
|
|
417
|
+
});
|
|
418
|
+
|
|
419
|
+
it('should insert facade creations before createCoreFacade call', () => {
|
|
420
|
+
const result = patchIndexTs(sampleSource, ['api-design'], true);
|
|
421
|
+
expect(result).not.toBeNull();
|
|
422
|
+
|
|
423
|
+
const lines = result!.split('\n');
|
|
424
|
+
const newFacadeIdx = lines.findIndex((l) =>
|
|
425
|
+
l.includes('createApiDesignFacade(vault, brain)'),
|
|
426
|
+
);
|
|
427
|
+
const coreFacadeIdx = lines.findIndex((l) => l.includes('createCoreFacade(vault'));
|
|
428
|
+
|
|
429
|
+
expect(newFacadeIdx).toBeLessThan(coreFacadeIdx);
|
|
430
|
+
});
|
|
431
|
+
|
|
432
|
+
it('should use vault-only args when hasBrain is false', () => {
|
|
433
|
+
const vaultOnlySource = sampleSource
|
|
434
|
+
.replace('brain: Brain', '')
|
|
435
|
+
.replace('createExistingFacade(vault, brain)', 'createExistingFacade(vault)');
|
|
436
|
+
|
|
437
|
+
const result = patchIndexTs(vaultOnlySource, ['api-design'], false);
|
|
438
|
+
expect(result).not.toBeNull();
|
|
439
|
+
expect(result).toContain('createApiDesignFacade(vault),');
|
|
440
|
+
});
|
|
441
|
+
|
|
442
|
+
it('should return null if anchor patterns are missing', () => {
|
|
443
|
+
const badSource = 'const x = 1;';
|
|
444
|
+
expect(patchIndexTs(badSource, ['test'], true)).toBeNull();
|
|
445
|
+
});
|
|
446
|
+
});
|
|
447
|
+
|
|
448
|
+
// ── patchClaudeMdContent ──────────────────────────────────────
|
|
449
|
+
|
|
450
|
+
describe('patchClaudeMdContent', () => {
|
|
451
|
+
const sampleSource = `export function getClaudeMdContent(): string {
|
|
452
|
+
return [
|
|
453
|
+
'| existing patterns | \`test_existing\` | \`get_patterns\` |',
|
|
454
|
+
'| Memory search | \`test_core\` | \`memory_search\` |',
|
|
455
|
+
'| Memory capture | \`test_core\` | \`memory_capture\` |',
|
|
456
|
+
].join('\\n');
|
|
457
|
+
}
|
|
458
|
+
`;
|
|
459
|
+
|
|
460
|
+
it('should insert domain rows before Memory search', () => {
|
|
461
|
+
const result = patchClaudeMdContent(sampleSource, 'test', ['api-design']);
|
|
462
|
+
expect(result).not.toBeNull();
|
|
463
|
+
|
|
464
|
+
const lines = result!.split('\n');
|
|
465
|
+
const newRowIdx = lines.findIndex((l) => l.includes('api-design patterns'));
|
|
466
|
+
const memoryIdx = lines.findIndex((l) => l.includes('Memory search'));
|
|
467
|
+
|
|
468
|
+
expect(newRowIdx).toBeGreaterThan(-1);
|
|
469
|
+
expect(newRowIdx).toBeLessThan(memoryIdx);
|
|
470
|
+
});
|
|
471
|
+
|
|
472
|
+
it('should generate 3 rows per domain (patterns, search, capture)', () => {
|
|
473
|
+
const result = patchClaudeMdContent(sampleSource, 'test', ['api-design']);
|
|
474
|
+
expect(result).not.toBeNull();
|
|
475
|
+
|
|
476
|
+
expect(result).toContain('api-design patterns');
|
|
477
|
+
expect(result).toContain('Search api-design');
|
|
478
|
+
expect(result).toContain('Capture api-design');
|
|
479
|
+
});
|
|
480
|
+
|
|
481
|
+
it('should use correct facade name with agentId', () => {
|
|
482
|
+
const result = patchClaudeMdContent(sampleSource, 'my-agent', ['web-components']);
|
|
483
|
+
expect(result).not.toBeNull();
|
|
484
|
+
|
|
485
|
+
expect(result).toContain('my_agent_web_components');
|
|
486
|
+
});
|
|
487
|
+
|
|
488
|
+
it('should handle multiple new domains', () => {
|
|
489
|
+
const result = patchClaudeMdContent(sampleSource, 'test', ['api-design', 'security']);
|
|
490
|
+
expect(result).not.toBeNull();
|
|
491
|
+
|
|
492
|
+
expect(result).toContain('api-design patterns');
|
|
493
|
+
expect(result).toContain('security patterns');
|
|
494
|
+
|
|
495
|
+
// All new rows should be before Memory search
|
|
496
|
+
const lines = result!.split('\n');
|
|
497
|
+
const memoryIdx = lines.findIndex((l) => l.includes('Memory search'));
|
|
498
|
+
const apiIdx = lines.findIndex((l) => l.includes('api-design'));
|
|
499
|
+
const secIdx = lines.findIndex((l) => l.includes('security'));
|
|
500
|
+
|
|
501
|
+
expect(apiIdx).toBeLessThan(memoryIdx);
|
|
502
|
+
expect(secIdx).toBeLessThan(memoryIdx);
|
|
503
|
+
});
|
|
504
|
+
|
|
505
|
+
it('should return null if anchor is missing', () => {
|
|
506
|
+
const badSource = 'export const x = 1;';
|
|
507
|
+
expect(patchClaudeMdContent(badSource, 'test', ['api'])).toBeNull();
|
|
508
|
+
});
|
|
509
|
+
|
|
510
|
+
it('should use fallback anchor (## Intent Detection) for older agents', () => {
|
|
511
|
+
// Older agents don't have Memory search rows — they go straight to Intent Detection
|
|
512
|
+
const olderSource = `export function getClaudeMdContent(): string {
|
|
513
|
+
return [
|
|
514
|
+
'| existing patterns | \`test_existing\` | \`get_patterns\` |',
|
|
515
|
+
'',
|
|
516
|
+
'## Intent Detection',
|
|
517
|
+
'',
|
|
518
|
+
].join('\\n');
|
|
519
|
+
}
|
|
520
|
+
`;
|
|
521
|
+
const result = patchClaudeMdContent(olderSource, 'test', ['api-design']);
|
|
522
|
+
expect(result).not.toBeNull();
|
|
523
|
+
|
|
524
|
+
// New rows should appear before Intent Detection
|
|
525
|
+
const lines = result!.split('\n');
|
|
526
|
+
const newRowIdx = lines.findIndex((l) => l.includes('api-design patterns'));
|
|
527
|
+
const intentIdx = lines.findIndex((l) => l.includes('Intent Detection'));
|
|
528
|
+
|
|
529
|
+
expect(newRowIdx).toBeGreaterThan(-1);
|
|
530
|
+
expect(newRowIdx).toBeLessThan(intentIdx);
|
|
531
|
+
});
|
|
532
|
+
});
|
|
533
|
+
|
|
534
|
+
// ── Integration (source patching) ─────────────────────────────
|
|
535
|
+
|
|
536
|
+
describe('integration - source patching', () => {
|
|
537
|
+
it('should patch index.ts when new domains are added', async () => {
|
|
538
|
+
createMinimalAgent();
|
|
539
|
+
createMockIndexTs();
|
|
540
|
+
createMockBundle('new-domain');
|
|
541
|
+
|
|
542
|
+
const result = await installKnowledge({
|
|
543
|
+
agentPath: agentDir,
|
|
544
|
+
bundlePath: bundleDir,
|
|
545
|
+
});
|
|
546
|
+
|
|
547
|
+
expect(result.sourceFilesPatched).toContain('src/index.ts');
|
|
548
|
+
|
|
549
|
+
const patched = readFileSync(join(agentDir, 'src', 'index.ts'), 'utf-8');
|
|
550
|
+
expect(patched).toContain('createNewDomainFacade');
|
|
551
|
+
expect(patched).toContain("'./facades/new-domain.facade.js'");
|
|
552
|
+
});
|
|
553
|
+
|
|
554
|
+
it('should patch claude-md-content.ts when new domains are added', async () => {
|
|
555
|
+
createMinimalAgent();
|
|
556
|
+
createMockClaudeMdContent();
|
|
557
|
+
createMockBundle('new-domain');
|
|
558
|
+
|
|
559
|
+
const result = await installKnowledge({
|
|
560
|
+
agentPath: agentDir,
|
|
561
|
+
bundlePath: bundleDir,
|
|
562
|
+
});
|
|
563
|
+
|
|
564
|
+
expect(result.sourceFilesPatched).toContain('src/activation/claude-md-content.ts');
|
|
565
|
+
|
|
566
|
+
const patched = readFileSync(
|
|
567
|
+
join(agentDir, 'src', 'activation', 'claude-md-content.ts'),
|
|
568
|
+
'utf-8',
|
|
569
|
+
);
|
|
570
|
+
expect(patched).toContain('new-domain patterns');
|
|
571
|
+
expect(patched).toContain('test_agent_new_domain');
|
|
572
|
+
});
|
|
573
|
+
|
|
574
|
+
it('should not patch source files when only updating existing domains', async () => {
|
|
575
|
+
createMinimalAgent();
|
|
576
|
+
createMockIndexTs();
|
|
577
|
+
createMockClaudeMdContent();
|
|
578
|
+
createMockBundle('existing-domain');
|
|
579
|
+
|
|
580
|
+
const result = await installKnowledge({
|
|
581
|
+
agentPath: agentDir,
|
|
582
|
+
bundlePath: bundleDir,
|
|
583
|
+
});
|
|
584
|
+
|
|
585
|
+
expect(result.sourceFilesPatched).toEqual([]);
|
|
586
|
+
expect(result.facadesGenerated).toEqual([]);
|
|
587
|
+
});
|
|
588
|
+
|
|
589
|
+
it('should warn if index.ts patching fails', async () => {
|
|
590
|
+
createMinimalAgent();
|
|
591
|
+
// Write a non-standard index.ts that won't match anchors
|
|
592
|
+
writeFileSync(join(agentDir, 'src', 'index.ts'), 'const x = 1;');
|
|
593
|
+
createMockBundle('new-domain');
|
|
594
|
+
|
|
595
|
+
const result = await installKnowledge({
|
|
596
|
+
agentPath: agentDir,
|
|
597
|
+
bundlePath: bundleDir,
|
|
598
|
+
});
|
|
599
|
+
|
|
600
|
+
expect(result.warnings).toContainEqual(
|
|
601
|
+
expect.stringContaining('Could not patch src/index.ts'),
|
|
602
|
+
);
|
|
603
|
+
});
|
|
604
|
+
});
|
|
605
|
+
|
|
606
|
+
// ── Idempotency ────────────────────────────────────────────────
|
|
607
|
+
|
|
608
|
+
describe('idempotency', () => {
|
|
609
|
+
it('patchIndexTs should not duplicate imports already present', () => {
|
|
610
|
+
const sourceWithExisting = `import { registerAllFacades } from './facades/facade-factory.js';
|
|
611
|
+
import { createApiDesignFacade } from './facades/api-design.facade.js';
|
|
612
|
+
import { createCoreFacade } from './facades/core.facade.js';
|
|
613
|
+
|
|
614
|
+
const facades = [
|
|
615
|
+
createApiDesignFacade(vault, brain),
|
|
616
|
+
createCoreFacade(vault, planner, brain),
|
|
617
|
+
];
|
|
618
|
+
`;
|
|
619
|
+
// api-design already exists — should not be duplicated
|
|
620
|
+
const result = patchIndexTs(sourceWithExisting, ['api-design'], true);
|
|
621
|
+
expect(result).not.toBeNull();
|
|
622
|
+
|
|
623
|
+
const importCount = (result!.match(/createApiDesignFacade/g) || []).length;
|
|
624
|
+
// 1 import + 1 call = 2 occurrences, not 4
|
|
625
|
+
expect(importCount).toBe(2);
|
|
626
|
+
});
|
|
627
|
+
|
|
628
|
+
it('patchIndexTs should add only missing domains from a mixed set', () => {
|
|
629
|
+
const sourceWithExisting = `import { registerAllFacades } from './facades/facade-factory.js';
|
|
630
|
+
import { createApiDesignFacade } from './facades/api-design.facade.js';
|
|
631
|
+
import { createCoreFacade } from './facades/core.facade.js';
|
|
632
|
+
|
|
633
|
+
const facades = [
|
|
634
|
+
createApiDesignFacade(vault, brain),
|
|
635
|
+
createCoreFacade(vault, planner, brain),
|
|
636
|
+
];
|
|
637
|
+
`;
|
|
638
|
+
// api-design exists, security is new
|
|
639
|
+
const result = patchIndexTs(sourceWithExisting, ['api-design', 'security'], true);
|
|
640
|
+
expect(result).not.toBeNull();
|
|
641
|
+
expect(result).toContain('createSecurityFacade');
|
|
642
|
+
|
|
643
|
+
const apiCount = (result!.match(/import \{ createApiDesignFacade \}/g) || []).length;
|
|
644
|
+
expect(apiCount).toBe(1); // not duplicated
|
|
645
|
+
});
|
|
646
|
+
|
|
647
|
+
it('patchIndexTs should return unchanged source when all domains exist', () => {
|
|
648
|
+
const sourceWithAll = `import { createApiDesignFacade } from './facades/api-design.facade.js';
|
|
649
|
+
import { createCoreFacade } from './facades/core.facade.js';
|
|
650
|
+
|
|
651
|
+
const facades = [
|
|
652
|
+
createApiDesignFacade(vault, brain),
|
|
653
|
+
createCoreFacade(vault, planner, brain),
|
|
654
|
+
];
|
|
655
|
+
`;
|
|
656
|
+
const result = patchIndexTs(sourceWithAll, ['api-design'], true);
|
|
657
|
+
expect(result).toBe(sourceWithAll);
|
|
658
|
+
});
|
|
659
|
+
|
|
660
|
+
it('patchClaudeMdContent should not duplicate rows already present', () => {
|
|
661
|
+
const sourceWithExisting = `export function getClaudeMdContent(): string {
|
|
662
|
+
return [
|
|
663
|
+
'| api-design patterns | \`test_api_design\` | \`get_patterns\` |',
|
|
664
|
+
'| Search api-design | \`test_api_design\` | \`search\` |',
|
|
665
|
+
'| Capture api-design | \`test_api_design\` | \`capture\` |',
|
|
666
|
+
'| Memory search | \`test_core\` | \`memory_search\` |',
|
|
667
|
+
].join('\\n');
|
|
668
|
+
}
|
|
669
|
+
`;
|
|
670
|
+
const result = patchClaudeMdContent(sourceWithExisting, 'test', ['api-design']);
|
|
671
|
+
expect(result).toBe(sourceWithExisting); // unchanged
|
|
672
|
+
});
|
|
673
|
+
|
|
674
|
+
it('patchClaudeMdContent should add only missing domains', () => {
|
|
675
|
+
const sourceWithExisting = `export function getClaudeMdContent(): string {
|
|
676
|
+
return [
|
|
677
|
+
'| api-design patterns | \`test_api_design\` | \`get_patterns\` |',
|
|
678
|
+
'| Memory search | \`test_core\` | \`memory_search\` |',
|
|
679
|
+
].join('\\n');
|
|
680
|
+
}
|
|
681
|
+
`;
|
|
682
|
+
const result = patchClaudeMdContent(sourceWithExisting, 'test', ['api-design', 'security']);
|
|
683
|
+
expect(result).not.toBeNull();
|
|
684
|
+
expect(result).toContain('security patterns');
|
|
685
|
+
|
|
686
|
+
// api-design should not be duplicated
|
|
687
|
+
const apiCount = (result!.match(/api-design patterns/g) || []).length;
|
|
688
|
+
expect(apiCount).toBe(1);
|
|
689
|
+
});
|
|
690
|
+
|
|
691
|
+
it('should skip facade generation if facade file already exists', async () => {
|
|
692
|
+
createMinimalAgent();
|
|
693
|
+
createMockBundle('new-domain');
|
|
694
|
+
|
|
695
|
+
// Pre-create the facade file
|
|
696
|
+
writeFileSync(join(agentDir, 'src', 'facades', 'new-domain.facade.ts'), '// existing facade');
|
|
697
|
+
|
|
698
|
+
const result = await installKnowledge({
|
|
699
|
+
agentPath: agentDir,
|
|
700
|
+
bundlePath: bundleDir,
|
|
701
|
+
});
|
|
702
|
+
|
|
703
|
+
expect(result.facadesGenerated).toEqual([]);
|
|
704
|
+
expect(result.warnings).toContainEqual(expect.stringContaining('already exists'));
|
|
705
|
+
|
|
706
|
+
// Should not overwrite
|
|
707
|
+
const content = readFileSync(
|
|
708
|
+
join(agentDir, 'src', 'facades', 'new-domain.facade.ts'),
|
|
709
|
+
'utf-8',
|
|
710
|
+
);
|
|
711
|
+
expect(content).toBe('// existing facade');
|
|
712
|
+
});
|
|
713
|
+
|
|
714
|
+
it('should be safe to run twice — full double-run', async () => {
|
|
715
|
+
createMinimalAgent();
|
|
716
|
+
createMockIndexTs();
|
|
717
|
+
createMockClaudeMdContent();
|
|
718
|
+
createMockBundle('new-domain', 5);
|
|
719
|
+
|
|
720
|
+
// First run
|
|
721
|
+
const first = await installKnowledge({
|
|
722
|
+
agentPath: agentDir,
|
|
723
|
+
bundlePath: bundleDir,
|
|
724
|
+
});
|
|
725
|
+
expect(first.domainsAdded).toContain('new-domain');
|
|
726
|
+
expect(first.facadesGenerated).toContain('new-domain.facade.ts');
|
|
727
|
+
expect(first.sourceFilesPatched).toContain('src/index.ts');
|
|
728
|
+
|
|
729
|
+
const indexAfterFirst = readFileSync(join(agentDir, 'src', 'index.ts'), 'utf-8');
|
|
730
|
+
const mdAfterFirst = readFileSync(
|
|
731
|
+
join(agentDir, 'src', 'activation', 'claude-md-content.ts'),
|
|
732
|
+
'utf-8',
|
|
733
|
+
);
|
|
734
|
+
|
|
735
|
+
// Second run — same bundles
|
|
736
|
+
const second = await installKnowledge({
|
|
737
|
+
agentPath: agentDir,
|
|
738
|
+
bundlePath: bundleDir,
|
|
739
|
+
});
|
|
740
|
+
|
|
741
|
+
// Data files should be updated (upsert)
|
|
742
|
+
expect(second.domainsUpdated).toContain('new-domain');
|
|
743
|
+
expect(second.domainsAdded).toEqual([]);
|
|
744
|
+
|
|
745
|
+
// Facade should not be regenerated (file exists now)
|
|
746
|
+
expect(second.facadesGenerated).toEqual([]);
|
|
747
|
+
|
|
748
|
+
// Source files should be unchanged (no duplicates)
|
|
749
|
+
const indexAfterSecond = readFileSync(join(agentDir, 'src', 'index.ts'), 'utf-8');
|
|
750
|
+
const mdAfterSecond = readFileSync(
|
|
751
|
+
join(agentDir, 'src', 'activation', 'claude-md-content.ts'),
|
|
752
|
+
'utf-8',
|
|
753
|
+
);
|
|
754
|
+
expect(indexAfterSecond).toBe(indexAfterFirst);
|
|
755
|
+
expect(mdAfterSecond).toBe(mdAfterFirst);
|
|
756
|
+
});
|
|
757
|
+
});
|
|
758
|
+
|
|
759
|
+
// ── Result shape ──────────────────────────────────────────────
|
|
760
|
+
|
|
761
|
+
describe('result', () => {
|
|
762
|
+
it('should return correct entry totals', async () => {
|
|
763
|
+
createMinimalAgent();
|
|
764
|
+
createMockBundle('domain-a', 10);
|
|
765
|
+
createMockBundle('domain-b', 5);
|
|
766
|
+
|
|
767
|
+
const result = await installKnowledge({
|
|
768
|
+
agentPath: agentDir,
|
|
769
|
+
bundlePath: bundleDir,
|
|
770
|
+
generateFacades: false,
|
|
771
|
+
});
|
|
772
|
+
|
|
773
|
+
expect(result.bundlesInstalled).toBe(2);
|
|
774
|
+
expect(result.entriesTotal).toBe(15);
|
|
775
|
+
});
|
|
776
|
+
|
|
777
|
+
it('should include a summary string', async () => {
|
|
778
|
+
createMinimalAgent();
|
|
779
|
+
createMockBundle('new-domain', 3);
|
|
780
|
+
|
|
781
|
+
const result = await installKnowledge({
|
|
782
|
+
agentPath: agentDir,
|
|
783
|
+
bundlePath: bundleDir,
|
|
784
|
+
generateFacades: false,
|
|
785
|
+
});
|
|
786
|
+
|
|
787
|
+
expect(result.summary).toContain('Installed 1 bundle(s)');
|
|
788
|
+
expect(result.summary).toContain('3 entries');
|
|
789
|
+
expect(result.summary).toContain('test-agent');
|
|
790
|
+
});
|
|
791
|
+
|
|
792
|
+
it('should extract agentId from package name', async () => {
|
|
793
|
+
createMinimalAgent();
|
|
794
|
+
createMockBundle('new-domain');
|
|
795
|
+
|
|
796
|
+
const result = await installKnowledge({
|
|
797
|
+
agentPath: agentDir,
|
|
798
|
+
bundlePath: bundleDir,
|
|
799
|
+
generateFacades: false,
|
|
800
|
+
});
|
|
801
|
+
|
|
802
|
+
expect(result.agentId).toBe('test-agent');
|
|
803
|
+
});
|
|
804
|
+
});
|
|
805
|
+
});
|