@soleri/cli 9.14.3 → 9.16.7
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/commands/add-domain.js +65 -0
- package/dist/commands/add-domain.js.map +1 -1
- package/dist/commands/agent.js +51 -20
- package/dist/commands/agent.js.map +1 -1
- package/dist/commands/brain.d.ts +8 -0
- package/dist/commands/brain.js +83 -0
- package/dist/commands/brain.js.map +1 -0
- package/dist/commands/chat.d.ts +11 -0
- package/dist/commands/chat.js +295 -0
- package/dist/commands/chat.js.map +1 -0
- package/dist/commands/create.js +11 -9
- package/dist/commands/create.js.map +1 -1
- package/dist/commands/dev.js +19 -0
- package/dist/commands/dev.js.map +1 -1
- package/dist/commands/dream.js +1 -12
- package/dist/commands/dream.js.map +1 -1
- package/dist/commands/hooks.js +29 -0
- package/dist/commands/hooks.js.map +1 -1
- package/dist/commands/install.js +3 -9
- package/dist/commands/install.js.map +1 -1
- package/dist/commands/knowledge.d.ts +9 -0
- package/dist/commands/knowledge.js +99 -0
- package/dist/commands/knowledge.js.map +1 -0
- package/dist/commands/pack.js +164 -3
- package/dist/commands/pack.js.map +1 -1
- package/dist/commands/schedule.d.ts +11 -0
- package/dist/commands/schedule.js +130 -0
- package/dist/commands/schedule.js.map +1 -0
- package/dist/commands/staging.d.ts +2 -17
- package/dist/commands/staging.js +4 -4
- package/dist/commands/staging.js.map +1 -1
- package/dist/commands/validate-skills.d.ts +10 -0
- package/dist/commands/validate-skills.js +47 -0
- package/dist/commands/validate-skills.js.map +1 -0
- package/dist/commands/vault.js +2 -11
- package/dist/commands/vault.js.map +1 -1
- package/dist/main.js +10 -0
- package/dist/main.js.map +1 -1
- package/dist/utils/checks.js +17 -32
- package/dist/utils/checks.js.map +1 -1
- package/dist/utils/core-resolver.d.ts +3 -0
- package/dist/utils/core-resolver.js +38 -0
- package/dist/utils/core-resolver.js.map +1 -0
- package/dist/utils/vault-db.d.ts +5 -0
- package/dist/utils/vault-db.js +17 -0
- package/dist/utils/vault-db.js.map +1 -0
- package/package.json +1 -1
- package/src/__tests__/doctor.test.ts +46 -1
- package/src/__tests__/hook-packs.test.ts +0 -18
- package/src/__tests__/hooks-convert.test.ts +0 -28
- package/src/__tests__/hooks-sync.test.ts +109 -0
- package/src/__tests__/hooks.test.ts +0 -20
- package/src/__tests__/install-verify.test.ts +1 -1
- package/src/__tests__/install.test.ts +7 -10
- package/src/__tests__/update.test.ts +0 -19
- package/src/__tests__/validator.test.ts +0 -16
- package/src/commands/add-domain.ts +89 -1
- package/src/commands/agent.ts +53 -17
- package/src/commands/brain.ts +93 -0
- package/src/commands/chat.ts +373 -0
- package/src/commands/create.ts +11 -8
- package/src/commands/dev.ts +21 -0
- package/src/commands/dream.ts +1 -11
- package/src/commands/hooks.ts +32 -0
- package/src/commands/install.ts +3 -8
- package/src/commands/knowledge.ts +124 -0
- package/src/commands/pack.ts +219 -1
- package/src/commands/schedule.ts +150 -0
- package/src/commands/staging.ts +5 -5
- package/src/commands/validate-skills.ts +58 -0
- package/src/commands/vault.ts +2 -11
- package/src/main.ts +10 -0
- package/src/utils/checks.ts +18 -30
- package/src/utils/core-resolver.ts +39 -0
- package/src/utils/vault-db.ts +15 -0
- package/dist/hook-packs/converter/template.test.ts +0 -133
- package/dist/hook-packs/yolo-safety/scripts/anti-deletion.sh +0 -274
- package/dist/prompts/archetypes.d.ts +0 -22
- package/dist/prompts/archetypes.js +0 -298
- package/dist/prompts/archetypes.js.map +0 -1
- package/dist/prompts/playbook.d.ts +0 -64
- package/dist/prompts/playbook.js +0 -436
- package/dist/prompts/playbook.js.map +0 -1
- package/dist/utils/format-paths.d.ts +0 -14
- package/dist/utils/format-paths.js +0 -27
- package/dist/utils/format-paths.js.map +0 -1
- package/src/__tests__/dream.test.ts +0 -119
|
@@ -104,7 +104,7 @@ describe('doctor command', () => {
|
|
|
104
104
|
describe('runAllChecks', () => {
|
|
105
105
|
it('should return array of check results', { timeout: 20_000 }, () => {
|
|
106
106
|
const results = runAllChecks(tempDir);
|
|
107
|
-
expect(results.length).
|
|
107
|
+
expect(results.length).toBe(9); // 4 common + 2 non-filetree (nodeModules + agentBuild) + 3 shared (mcpReg + hookPacks + cognee)
|
|
108
108
|
for (const r of results) {
|
|
109
109
|
expect(['pass', 'fail', 'warn']).toContain(r.status);
|
|
110
110
|
expect(r.label).toBeTruthy();
|
|
@@ -117,5 +117,50 @@ describe('doctor command', () => {
|
|
|
117
117
|
expect(labels).toContain('Node.js');
|
|
118
118
|
expect(labels).toContain('npm');
|
|
119
119
|
});
|
|
120
|
+
|
|
121
|
+
it('should detect Codex MCP registration for file-tree agents', { timeout: 20_000 }, () => {
|
|
122
|
+
const originalHome = process.env.HOME ?? '';
|
|
123
|
+
const originalUserProfile = process.env.USERPROFILE ?? '';
|
|
124
|
+
process.env.HOME = tempDir;
|
|
125
|
+
process.env.USERPROFILE = tempDir;
|
|
126
|
+
|
|
127
|
+
try {
|
|
128
|
+
const agentDir = join(tempDir, 'agent');
|
|
129
|
+
mkdirSync(join(agentDir, 'instructions'), { recursive: true });
|
|
130
|
+
mkdirSync(join(tempDir, '.codex'), { recursive: true });
|
|
131
|
+
|
|
132
|
+
writeFileSync(
|
|
133
|
+
join(agentDir, 'agent.yaml'),
|
|
134
|
+
[
|
|
135
|
+
'id: test-agent',
|
|
136
|
+
'name: Test Agent',
|
|
137
|
+
'role: A test agent',
|
|
138
|
+
'description: A minimal file-tree agent for doctor testing',
|
|
139
|
+
'domains: []',
|
|
140
|
+
'principles: []',
|
|
141
|
+
'',
|
|
142
|
+
].join('\n'),
|
|
143
|
+
);
|
|
144
|
+
writeFileSync(join(agentDir, 'instructions', 'usage.md'), '# Usage');
|
|
145
|
+
writeFileSync(
|
|
146
|
+
join(tempDir, '.codex', 'config.toml'),
|
|
147
|
+
[
|
|
148
|
+
'[mcp_servers.test-agent]',
|
|
149
|
+
'command = "node"',
|
|
150
|
+
'args = ["engine.js", "--agent", "agent.yaml"]',
|
|
151
|
+
'',
|
|
152
|
+
].join('\n'),
|
|
153
|
+
);
|
|
154
|
+
|
|
155
|
+
const results = runAllChecks(agentDir);
|
|
156
|
+
const registration = results.find((r) => r.label === 'MCP registration');
|
|
157
|
+
expect(registration).toBeDefined();
|
|
158
|
+
expect(registration!.status).toBe('pass');
|
|
159
|
+
expect(registration!.detail).toContain('codex');
|
|
160
|
+
} finally {
|
|
161
|
+
process.env.HOME = originalHome;
|
|
162
|
+
process.env.USERPROFILE = originalUserProfile;
|
|
163
|
+
}
|
|
164
|
+
});
|
|
120
165
|
});
|
|
121
166
|
});
|
|
@@ -23,24 +23,6 @@ describe('hook-packs', () => {
|
|
|
23
23
|
});
|
|
24
24
|
|
|
25
25
|
describe('registry', () => {
|
|
26
|
-
it('should list all 10 built-in packs', () => {
|
|
27
|
-
const packs = listPacks();
|
|
28
|
-
expect(packs.length).toBe(10);
|
|
29
|
-
const names = packs.map((p) => p.name).sort();
|
|
30
|
-
expect(names).toEqual([
|
|
31
|
-
'a11y',
|
|
32
|
-
'clean-commits',
|
|
33
|
-
'css-discipline',
|
|
34
|
-
'flock-guard',
|
|
35
|
-
'full',
|
|
36
|
-
'marketing-research',
|
|
37
|
-
'rtk',
|
|
38
|
-
'safety',
|
|
39
|
-
'typescript-safety',
|
|
40
|
-
'yolo-safety',
|
|
41
|
-
]);
|
|
42
|
-
});
|
|
43
|
-
|
|
44
26
|
it('should get a specific pack by name', () => {
|
|
45
27
|
const pack = getPack('typescript-safety');
|
|
46
28
|
expect(pack).not.toBeNull();
|
|
@@ -222,22 +222,6 @@ describe('hooks convert', () => {
|
|
|
222
222
|
});
|
|
223
223
|
});
|
|
224
224
|
|
|
225
|
-
describe('constants', () => {
|
|
226
|
-
it('HOOK_EVENTS should contain all 5 events', () => {
|
|
227
|
-
expect(HOOK_EVENTS).toEqual([
|
|
228
|
-
'PreToolUse',
|
|
229
|
-
'PostToolUse',
|
|
230
|
-
'PreCompact',
|
|
231
|
-
'Notification',
|
|
232
|
-
'Stop',
|
|
233
|
-
]);
|
|
234
|
-
});
|
|
235
|
-
|
|
236
|
-
it('ACTION_LEVELS should contain all 3 levels', () => {
|
|
237
|
-
expect(ACTION_LEVELS).toEqual(['remind', 'warn', 'block']);
|
|
238
|
-
});
|
|
239
|
-
});
|
|
240
|
-
|
|
241
225
|
describe('validation', () => {
|
|
242
226
|
it('should reject invalid event', () => {
|
|
243
227
|
const invalidEvent = 'InvalidEvent';
|
|
@@ -248,18 +232,6 @@ describe('hooks convert', () => {
|
|
|
248
232
|
const invalidAction = 'destroy';
|
|
249
233
|
expect(ACTION_LEVELS.includes(invalidAction as any)).toBe(false);
|
|
250
234
|
});
|
|
251
|
-
|
|
252
|
-
it('should accept all valid events', () => {
|
|
253
|
-
for (const event of HOOK_EVENTS) {
|
|
254
|
-
expect(HOOK_EVENTS.includes(event)).toBe(true);
|
|
255
|
-
}
|
|
256
|
-
});
|
|
257
|
-
|
|
258
|
-
it('should accept all valid action levels', () => {
|
|
259
|
-
for (const action of ACTION_LEVELS) {
|
|
260
|
-
expect(ACTION_LEVELS.includes(action)).toBe(true);
|
|
261
|
-
}
|
|
262
|
-
});
|
|
263
235
|
});
|
|
264
236
|
|
|
265
237
|
describe('directory structure', () => {
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
2
|
+
import { Command } from 'commander';
|
|
3
|
+
|
|
4
|
+
// ─── Mocks ─────────────────────────────────────────────────────────────────
|
|
5
|
+
|
|
6
|
+
const mockSyncHooksToClaudeSettings = vi.fn();
|
|
7
|
+
|
|
8
|
+
vi.mock('@soleri/core', () => ({
|
|
9
|
+
syncHooksToClaudeSettings: mockSyncHooksToClaudeSettings,
|
|
10
|
+
}));
|
|
11
|
+
|
|
12
|
+
const mockDetectAgent = vi.fn();
|
|
13
|
+
|
|
14
|
+
vi.mock('../utils/agent-context.js', () => ({
|
|
15
|
+
detectAgent: mockDetectAgent,
|
|
16
|
+
}));
|
|
17
|
+
|
|
18
|
+
// Silence logger output during tests
|
|
19
|
+
vi.mock('../utils/logger.js', () => ({
|
|
20
|
+
pass: vi.fn(),
|
|
21
|
+
fail: vi.fn(),
|
|
22
|
+
warn: vi.fn(),
|
|
23
|
+
info: vi.fn(),
|
|
24
|
+
dim: vi.fn(),
|
|
25
|
+
heading: vi.fn(),
|
|
26
|
+
}));
|
|
27
|
+
|
|
28
|
+
// ─── Helpers ────────────────────────────────────────────────────────────────
|
|
29
|
+
|
|
30
|
+
async function runSyncCommand(): Promise<void> {
|
|
31
|
+
// Import after mocks are established
|
|
32
|
+
const { registerHooks } = await import('../commands/hooks.js');
|
|
33
|
+
const program = new Command();
|
|
34
|
+
program.exitOverride(); // prevent process.exit from killing the test runner
|
|
35
|
+
registerHooks(program);
|
|
36
|
+
await program.parseAsync(['node', 'cli', 'hooks', 'sync']);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// ─── Tests ──────────────────────────────────────────────────────────────────
|
|
40
|
+
|
|
41
|
+
describe('hooks sync command', () => {
|
|
42
|
+
beforeEach(() => {
|
|
43
|
+
vi.resetModules();
|
|
44
|
+
vi.clearAllMocks();
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it('calls syncHooksToClaudeSettings with the agentId when detectAgent returns a valid context', async () => {
|
|
48
|
+
mockDetectAgent.mockReturnValue({
|
|
49
|
+
agentId: 'myagent',
|
|
50
|
+
agentPath: '/some/path',
|
|
51
|
+
format: 'typescript',
|
|
52
|
+
packageName: 'myagent-mcp',
|
|
53
|
+
hasBrain: false,
|
|
54
|
+
});
|
|
55
|
+
mockSyncHooksToClaudeSettings.mockReturnValue({
|
|
56
|
+
installed: ['SessionStart', 'PreCompact', 'Stop'],
|
|
57
|
+
updated: [],
|
|
58
|
+
skipped: [],
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
await runSyncCommand();
|
|
62
|
+
|
|
63
|
+
expect(mockSyncHooksToClaudeSettings).toHaveBeenCalledTimes(1);
|
|
64
|
+
expect(mockSyncHooksToClaudeSettings).toHaveBeenCalledWith('myagent');
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it('does NOT call syncHooksToClaudeSettings and exits non-zero when detectAgent returns null', async () => {
|
|
68
|
+
mockDetectAgent.mockReturnValue(null);
|
|
69
|
+
|
|
70
|
+
let exitCode: number | undefined;
|
|
71
|
+
const exitSpy = vi
|
|
72
|
+
.spyOn(process, 'exit')
|
|
73
|
+
.mockImplementation((code?: number | string | null) => {
|
|
74
|
+
exitCode = typeof code === 'number' ? code : 1;
|
|
75
|
+
throw new Error(`process.exit(${exitCode})`);
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
try {
|
|
79
|
+
await runSyncCommand();
|
|
80
|
+
} catch {
|
|
81
|
+
// Expected — process.exit throws via our mock
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
expect(mockSyncHooksToClaudeSettings).not.toHaveBeenCalled();
|
|
85
|
+
expect(exitCode).toBeGreaterThan(0);
|
|
86
|
+
|
|
87
|
+
exitSpy.mockRestore();
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
it('calls syncHooksToClaudeSettings with correct agentId for a file-tree agent context', async () => {
|
|
91
|
+
mockDetectAgent.mockReturnValue({
|
|
92
|
+
agentId: 'my-filetree-agent',
|
|
93
|
+
agentPath: '/path/to/agent',
|
|
94
|
+
format: 'filetree',
|
|
95
|
+
packageName: 'my-filetree-agent',
|
|
96
|
+
hasBrain: true,
|
|
97
|
+
});
|
|
98
|
+
mockSyncHooksToClaudeSettings.mockReturnValue({
|
|
99
|
+
installed: ['SessionStart'],
|
|
100
|
+
updated: ['Stop'],
|
|
101
|
+
skipped: ['PreCompact'],
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
await runSyncCommand();
|
|
105
|
+
|
|
106
|
+
expect(mockSyncHooksToClaudeSettings).toHaveBeenCalledTimes(1);
|
|
107
|
+
expect(mockSyncHooksToClaudeSettings).toHaveBeenCalledWith('my-filetree-agent');
|
|
108
|
+
});
|
|
109
|
+
});
|
|
@@ -3,7 +3,6 @@ import { mkdirSync, rmSync, writeFileSync, existsSync, readFileSync } from 'node
|
|
|
3
3
|
import { join } from 'node:path';
|
|
4
4
|
import { tmpdir } from 'node:os';
|
|
5
5
|
import { installHooks, removeHooks, detectInstalledHooks } from '../hooks/generator.js';
|
|
6
|
-
import { SUPPORTED_EDITORS } from '../hooks/templates.js';
|
|
7
6
|
|
|
8
7
|
describe('hooks commands', () => {
|
|
9
8
|
let tempDir: string;
|
|
@@ -110,24 +109,5 @@ describe('hooks commands', () => {
|
|
|
110
109
|
expect(installed).toContain('claude-code');
|
|
111
110
|
expect(installed).not.toContain('windsurf');
|
|
112
111
|
});
|
|
113
|
-
|
|
114
|
-
it('should detect all 4 editors when all installed', () => {
|
|
115
|
-
for (const editor of SUPPORTED_EDITORS) {
|
|
116
|
-
installHooks(editor, agentDir);
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
const installed = detectInstalledHooks(agentDir);
|
|
120
|
-
expect(installed).toHaveLength(4);
|
|
121
|
-
});
|
|
122
|
-
});
|
|
123
|
-
|
|
124
|
-
describe('SUPPORTED_EDITORS', () => {
|
|
125
|
-
it('should have 4 editors', () => {
|
|
126
|
-
expect(SUPPORTED_EDITORS).toHaveLength(4);
|
|
127
|
-
expect(SUPPORTED_EDITORS).toContain('claude-code');
|
|
128
|
-
expect(SUPPORTED_EDITORS).toContain('cursor');
|
|
129
|
-
expect(SUPPORTED_EDITORS).toContain('windsurf');
|
|
130
|
-
expect(SUPPORTED_EDITORS).toContain('copilot');
|
|
131
|
-
});
|
|
132
112
|
});
|
|
133
113
|
});
|
|
@@ -98,7 +98,7 @@ describe('verifyInstall', () => {
|
|
|
98
98
|
const checks = verifyInstall('test-agent', agentDir, 'claude');
|
|
99
99
|
|
|
100
100
|
expect(Array.isArray(checks)).toBe(true);
|
|
101
|
-
expect(checks.length).
|
|
101
|
+
expect(checks.length).toBe(3); // claude config entry + engine binary + agent.yaml
|
|
102
102
|
for (const check of checks) {
|
|
103
103
|
expect(check).toHaveProperty('label');
|
|
104
104
|
expect(check).toHaveProperty('passed');
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
|
|
2
|
-
import { mkdirSync, rmSync,
|
|
2
|
+
import { mkdirSync, rmSync, readFileSync } from 'node:fs';
|
|
3
3
|
import { join } from 'node:path';
|
|
4
4
|
import { tmpdir } from 'node:os';
|
|
5
5
|
import { toPosix, getNextStepMessage, resolveEngineBin } from '../commands/install.js';
|
|
@@ -82,20 +82,17 @@ describe('resolveEngineBin', () => {
|
|
|
82
82
|
describe('npx fallback warning', () => {
|
|
83
83
|
afterEach(() => {
|
|
84
84
|
vi.resetModules();
|
|
85
|
-
vi.doUnmock('
|
|
85
|
+
vi.doUnmock('../utils/core-resolver.js');
|
|
86
86
|
});
|
|
87
87
|
|
|
88
88
|
it('returns npx fallback when core resolution fails', async () => {
|
|
89
|
-
vi.doMock('
|
|
90
|
-
const actual = await vi.importActual<typeof import('
|
|
89
|
+
vi.doMock('../utils/core-resolver.js', async () => {
|
|
90
|
+
const actual = await vi.importActual<typeof import('../utils/core-resolver.js')>(
|
|
91
|
+
'../utils/core-resolver.js',
|
|
92
|
+
);
|
|
91
93
|
return {
|
|
92
94
|
...actual,
|
|
93
|
-
|
|
94
|
-
({
|
|
95
|
-
resolve: () => {
|
|
96
|
-
throw new Error('MODULE_NOT_FOUND');
|
|
97
|
-
},
|
|
98
|
-
}) as ReturnType<typeof import('node:module').createRequire>,
|
|
95
|
+
resolveInstalledEngineBin: () => null,
|
|
99
96
|
};
|
|
100
97
|
});
|
|
101
98
|
|
|
@@ -45,25 +45,6 @@ describe('update command', () => {
|
|
|
45
45
|
vi.clearAllMocks();
|
|
46
46
|
});
|
|
47
47
|
|
|
48
|
-
it('exports registerUpdate function', () => {
|
|
49
|
-
expect(typeof registerUpdate).toBe('function');
|
|
50
|
-
});
|
|
51
|
-
|
|
52
|
-
it('registers a command named "update" on the program', () => {
|
|
53
|
-
const mockCommand = {
|
|
54
|
-
command: vi.fn().mockReturnThis(),
|
|
55
|
-
description: vi.fn().mockReturnThis(),
|
|
56
|
-
action: vi.fn().mockReturnThis(),
|
|
57
|
-
};
|
|
58
|
-
const mockProgram = { command: vi.fn(() => mockCommand) };
|
|
59
|
-
|
|
60
|
-
registerUpdate(mockProgram as never);
|
|
61
|
-
|
|
62
|
-
expect(mockProgram.command).toHaveBeenCalledWith('update');
|
|
63
|
-
expect(mockCommand.description).toHaveBeenCalledWith(expect.stringContaining('Update'));
|
|
64
|
-
expect(mockCommand.action).toHaveBeenCalledWith(expect.any(Function));
|
|
65
|
-
});
|
|
66
|
-
|
|
67
48
|
it('prints already-on-latest when current version matches latest', async () => {
|
|
68
49
|
// getCurrentVersion() reads ../../package.json → packages/cli/package.json.
|
|
69
50
|
// Return the same version from npm view to trigger the "already on latest" branch.
|
|
@@ -52,22 +52,6 @@ describe('validator', () => {
|
|
|
52
52
|
expect(fixtures).toHaveLength(15);
|
|
53
53
|
});
|
|
54
54
|
|
|
55
|
-
it('matching PreToolUse fixtures should have shouldMatch: true', () => {
|
|
56
|
-
const fixtures = generateFixtures('PreToolUse', 'Bash');
|
|
57
|
-
const matching = fixtures.filter((f) => f.shouldMatch);
|
|
58
|
-
for (const f of matching) {
|
|
59
|
-
expect(f.shouldMatch).toBe(true);
|
|
60
|
-
}
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
it('non-matching PreToolUse fixtures should have shouldMatch: false', () => {
|
|
64
|
-
const fixtures = generateFixtures('PreToolUse', 'Write');
|
|
65
|
-
const nonMatching = fixtures.filter((f) => !f.shouldMatch);
|
|
66
|
-
for (const f of nonMatching) {
|
|
67
|
-
expect(f.shouldMatch).toBe(false);
|
|
68
|
-
}
|
|
69
|
-
});
|
|
70
|
-
|
|
71
55
|
it('PreToolUse matching fixtures should contain tool_name and tool_input', () => {
|
|
72
56
|
const fixtures = generateFixtures('PreToolUse', 'Write|Edit');
|
|
73
57
|
const matching = fixtures.filter((f) => f.shouldMatch);
|
|
@@ -1,15 +1,19 @@
|
|
|
1
|
+
import { readFileSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
1
3
|
import type { Command } from 'commander';
|
|
2
4
|
import * as p from '@clack/prompts';
|
|
3
5
|
import { addDomain } from '@soleri/forge/lib';
|
|
4
6
|
import { detectAgent } from '../utils/agent-context.js';
|
|
7
|
+
import { resolveVaultDbPath } from '../utils/vault-db.js';
|
|
5
8
|
|
|
6
9
|
export function registerAddDomain(program: Command): void {
|
|
7
10
|
program
|
|
8
11
|
.command('add-domain')
|
|
9
12
|
.argument('<domain>', 'Domain name in kebab-case (e.g., "security")')
|
|
10
13
|
.option('--no-build', 'Skip the build step after adding the domain')
|
|
14
|
+
.option('--yes', 'Auto-seed vault entries into the knowledge bundle without prompting')
|
|
11
15
|
.description('Add a new knowledge domain to the agent in the current directory')
|
|
12
|
-
.action(async (domain: string, opts: { build: boolean }) => {
|
|
16
|
+
.action(async (domain: string, opts: { build: boolean; yes?: boolean }) => {
|
|
13
17
|
const ctx = detectAgent();
|
|
14
18
|
if (!ctx) {
|
|
15
19
|
p.log.error('No agent project detected in current directory. Run this from an agent root.');
|
|
@@ -38,6 +42,9 @@ export function registerAddDomain(program: Command): void {
|
|
|
38
42
|
if (!result.success) {
|
|
39
43
|
process.exit(1);
|
|
40
44
|
}
|
|
45
|
+
|
|
46
|
+
// ── Vault auto-seed ────────────────────────────────────────────────
|
|
47
|
+
await trySeedFromVault(ctx.agentId, ctx.agentPath, domain, opts.yes ?? false);
|
|
41
48
|
} catch (err) {
|
|
42
49
|
s.stop('Failed');
|
|
43
50
|
p.log.error(err instanceof Error ? err.message : String(err));
|
|
@@ -45,3 +52,84 @@ export function registerAddDomain(program: Command): void {
|
|
|
45
52
|
}
|
|
46
53
|
});
|
|
47
54
|
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Query the agent's vault for entries matching the domain.
|
|
58
|
+
* If found, prompt the user (or auto-seed with --yes) to populate the bundle.
|
|
59
|
+
*/
|
|
60
|
+
async function trySeedFromVault(
|
|
61
|
+
agentId: string,
|
|
62
|
+
agentPath: string,
|
|
63
|
+
domain: string,
|
|
64
|
+
autoYes: boolean,
|
|
65
|
+
): Promise<void> {
|
|
66
|
+
const vaultDbPath = resolveVaultDbPath(agentId);
|
|
67
|
+
if (!vaultDbPath) return; // Vault not initialized yet — skip silently
|
|
68
|
+
|
|
69
|
+
try {
|
|
70
|
+
const { Vault } = await import('@soleri/core');
|
|
71
|
+
const vault = new Vault(vaultDbPath);
|
|
72
|
+
|
|
73
|
+
let entries: Array<{
|
|
74
|
+
id: string;
|
|
75
|
+
type: string;
|
|
76
|
+
domain?: string;
|
|
77
|
+
title: string;
|
|
78
|
+
description: string;
|
|
79
|
+
tags?: string[];
|
|
80
|
+
severity?: string;
|
|
81
|
+
}>;
|
|
82
|
+
try {
|
|
83
|
+
entries = vault.list({ domain, limit: 200 });
|
|
84
|
+
} finally {
|
|
85
|
+
vault.close();
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (entries.length === 0) return; // No matching entries
|
|
89
|
+
|
|
90
|
+
// Ask user (or auto-seed)
|
|
91
|
+
let shouldSeed = autoYes;
|
|
92
|
+
if (!autoYes) {
|
|
93
|
+
const answer = await p.confirm({
|
|
94
|
+
message: `Found ${entries.length} vault entries for domain "${domain}". Seed into knowledge bundle?`,
|
|
95
|
+
initialValue: true,
|
|
96
|
+
});
|
|
97
|
+
if (p.isCancel(answer)) return;
|
|
98
|
+
shouldSeed = answer;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
if (!shouldSeed) return;
|
|
102
|
+
|
|
103
|
+
// Populate knowledge/{domain}.json
|
|
104
|
+
const bundlePath = join(agentPath, 'knowledge', `${domain}.json`);
|
|
105
|
+
const bundleEntries = entries.map((e) => ({
|
|
106
|
+
id: e.id,
|
|
107
|
+
type: e.type,
|
|
108
|
+
domain: e.domain,
|
|
109
|
+
title: e.title,
|
|
110
|
+
description: e.description,
|
|
111
|
+
...(e.severity ? { severity: e.severity } : {}),
|
|
112
|
+
...(e.tags?.length ? { tags: e.tags } : {}),
|
|
113
|
+
}));
|
|
114
|
+
|
|
115
|
+
try {
|
|
116
|
+
const existing = JSON.parse(readFileSync(bundlePath, 'utf-8')) as {
|
|
117
|
+
domain: string;
|
|
118
|
+
entries: unknown[];
|
|
119
|
+
};
|
|
120
|
+
existing.entries = bundleEntries;
|
|
121
|
+
writeFileSync(bundlePath, JSON.stringify(existing, null, 2) + '\n', 'utf-8');
|
|
122
|
+
} catch {
|
|
123
|
+
// File not readable — write fresh
|
|
124
|
+
writeFileSync(
|
|
125
|
+
bundlePath,
|
|
126
|
+
JSON.stringify({ domain, entries: bundleEntries }, null, 2) + '\n',
|
|
127
|
+
'utf-8',
|
|
128
|
+
);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
p.log.success(`Seeded ${entries.length} entries into knowledge/${domain}.json`);
|
|
132
|
+
} catch {
|
|
133
|
+
// Vault query failed — don't block the domain addition
|
|
134
|
+
}
|
|
135
|
+
}
|
package/src/commands/agent.ts
CHANGED
|
@@ -6,7 +6,6 @@
|
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import { join, dirname } from 'node:path';
|
|
9
|
-
import { createRequire } from 'node:module';
|
|
10
9
|
import {
|
|
11
10
|
existsSync,
|
|
12
11
|
readFileSync,
|
|
@@ -26,6 +25,7 @@ import { PackLockfile, checkNpmVersion, checkVersionCompat, SOLERI_HOME } from '
|
|
|
26
25
|
import {
|
|
27
26
|
generateClaudeMdTemplate,
|
|
28
27
|
generateInjectClaudeMd,
|
|
28
|
+
generateAgentsMd,
|
|
29
29
|
generateSkills,
|
|
30
30
|
} from '@soleri/forge/lib';
|
|
31
31
|
import { composeClaudeMd, getModularEngineRules, AgentYamlSchema } from '@soleri/forge/lib';
|
|
@@ -48,6 +48,42 @@ function readEngineFeatures(agentPath: string): EngineFeature[] | undefined {
|
|
|
48
48
|
}
|
|
49
49
|
}
|
|
50
50
|
|
|
51
|
+
function readFileTreeAgentConfig(agentPath: string): AgentConfig | null {
|
|
52
|
+
try {
|
|
53
|
+
const yamlPath = join(agentPath, 'agent.yaml');
|
|
54
|
+
if (!existsSync(yamlPath)) return null;
|
|
55
|
+
|
|
56
|
+
const parsed = AgentYamlSchema.parse(parseYaml(readFileSync(yamlPath, 'utf-8')));
|
|
57
|
+
return {
|
|
58
|
+
id: parsed.id,
|
|
59
|
+
name: parsed.name,
|
|
60
|
+
role: parsed.role,
|
|
61
|
+
description: parsed.description,
|
|
62
|
+
domains: parsed.domains,
|
|
63
|
+
principles: parsed.principles,
|
|
64
|
+
tone: parsed.tone,
|
|
65
|
+
greeting: parsed.greeting,
|
|
66
|
+
persona: parsed.persona,
|
|
67
|
+
vaults: parsed.vaults,
|
|
68
|
+
setupTarget: parsed.setup?.target ?? 'claude',
|
|
69
|
+
} as AgentConfig;
|
|
70
|
+
} catch {
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function readAgentCoreVersion(agentPath: string): string | null {
|
|
76
|
+
const corePkgPath = join(agentPath, 'node_modules', '@soleri', 'core', 'package.json');
|
|
77
|
+
if (!existsSync(corePkgPath)) return null;
|
|
78
|
+
|
|
79
|
+
try {
|
|
80
|
+
const pkg = JSON.parse(readFileSync(corePkgPath, 'utf-8')) as { version?: unknown };
|
|
81
|
+
return typeof pkg.version === 'string' ? pkg.version : null;
|
|
82
|
+
} catch {
|
|
83
|
+
return null;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
51
87
|
export function registerAgent(program: Command): void {
|
|
52
88
|
const agent = program.command('agent').description('Agent lifecycle management');
|
|
53
89
|
|
|
@@ -83,14 +119,7 @@ export function registerAgent(program: Command): void {
|
|
|
83
119
|
}
|
|
84
120
|
|
|
85
121
|
// Resolve engine version via require.resolve
|
|
86
|
-
|
|
87
|
-
try {
|
|
88
|
-
const req = createRequire(join(ctx.agentPath, 'package.json'));
|
|
89
|
-
const corePkgPath = req.resolve('@soleri/core/package.json');
|
|
90
|
-
engineVersion = JSON.parse(readFileSync(corePkgPath, 'utf-8')).version || 'unknown';
|
|
91
|
-
} catch {
|
|
92
|
-
engineVersion = 'not installed';
|
|
93
|
-
}
|
|
122
|
+
const engineVersion = readAgentCoreVersion(ctx.agentPath) ?? 'not installed';
|
|
94
123
|
|
|
95
124
|
// Check for core update
|
|
96
125
|
const latestCore = checkNpmVersion('@soleri/core');
|
|
@@ -219,14 +248,7 @@ export function registerAgent(program: Command): void {
|
|
|
219
248
|
// ─── File-tree agent (v7+) ────────────────────────────────
|
|
220
249
|
if (ctx.format === 'filetree') {
|
|
221
250
|
// Resolve installed @soleri/core version
|
|
222
|
-
|
|
223
|
-
try {
|
|
224
|
-
const req = createRequire(import.meta.url);
|
|
225
|
-
const corePkgPath = req.resolve('@soleri/core/package.json');
|
|
226
|
-
installedVersion = JSON.parse(readFileSync(corePkgPath, 'utf-8')).version ?? null;
|
|
227
|
-
} catch {
|
|
228
|
-
// @soleri/core not resolvable — will show as unknown
|
|
229
|
-
}
|
|
251
|
+
const installedVersion = readAgentCoreVersion(ctx.agentPath);
|
|
230
252
|
|
|
231
253
|
// Check latest version on npm
|
|
232
254
|
const latestCore = checkNpmVersion('@soleri/core');
|
|
@@ -382,6 +404,14 @@ export function registerAgent(program: Command): void {
|
|
|
382
404
|
if (ctx.format === 'filetree') {
|
|
383
405
|
const enginePath = join(ctx.agentPath, 'instructions', '_engine.md');
|
|
384
406
|
const claudeMdPath = join(ctx.agentPath, 'CLAUDE.md');
|
|
407
|
+
const agentsMdPath = join(ctx.agentPath, 'AGENTS.md');
|
|
408
|
+
const config = readFileTreeAgentConfig(ctx.agentPath);
|
|
409
|
+
|
|
410
|
+
if (!config) {
|
|
411
|
+
p.log.error('Could not parse agent.yaml for AGENTS.md regeneration.');
|
|
412
|
+
process.exit(1);
|
|
413
|
+
return;
|
|
414
|
+
}
|
|
385
415
|
|
|
386
416
|
// Generate skills from latest forge templates
|
|
387
417
|
const skillFiles = opts.skipSkills
|
|
@@ -391,6 +421,7 @@ export function registerAgent(program: Command): void {
|
|
|
391
421
|
if (opts.dryRun) {
|
|
392
422
|
p.log.info(`Would regenerate: ${enginePath}`);
|
|
393
423
|
p.log.info(`Would regenerate: ${claudeMdPath}`);
|
|
424
|
+
p.log.info(`Would regenerate: ${agentsMdPath}`);
|
|
394
425
|
if (skillFiles.length > 0) {
|
|
395
426
|
const newSkills = skillFiles.filter(
|
|
396
427
|
([relPath]) => !existsSync(join(ctx.agentPath, relPath)),
|
|
@@ -442,6 +473,11 @@ export function registerAgent(program: Command): void {
|
|
|
442
473
|
p.log.success(
|
|
443
474
|
`Regenerated ${claudeMdPath} (${result.sources.length} sources, ${result.content.length} bytes)`,
|
|
444
475
|
);
|
|
476
|
+
|
|
477
|
+
// 4. Regenerate AGENTS.md for Codex/OpenCode
|
|
478
|
+
const agentsMd = generateAgentsMd(config);
|
|
479
|
+
writeFileSync(agentsMdPath, agentsMd, 'utf-8');
|
|
480
|
+
p.log.success(`Regenerated ${agentsMdPath} (${agentsMd.length} bytes)`);
|
|
445
481
|
return;
|
|
446
482
|
}
|
|
447
483
|
|