@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.
Files changed (87) hide show
  1. package/dist/commands/add-domain.js +65 -0
  2. package/dist/commands/add-domain.js.map +1 -1
  3. package/dist/commands/agent.js +51 -20
  4. package/dist/commands/agent.js.map +1 -1
  5. package/dist/commands/brain.d.ts +8 -0
  6. package/dist/commands/brain.js +83 -0
  7. package/dist/commands/brain.js.map +1 -0
  8. package/dist/commands/chat.d.ts +11 -0
  9. package/dist/commands/chat.js +295 -0
  10. package/dist/commands/chat.js.map +1 -0
  11. package/dist/commands/create.js +11 -9
  12. package/dist/commands/create.js.map +1 -1
  13. package/dist/commands/dev.js +19 -0
  14. package/dist/commands/dev.js.map +1 -1
  15. package/dist/commands/dream.js +1 -12
  16. package/dist/commands/dream.js.map +1 -1
  17. package/dist/commands/hooks.js +29 -0
  18. package/dist/commands/hooks.js.map +1 -1
  19. package/dist/commands/install.js +3 -9
  20. package/dist/commands/install.js.map +1 -1
  21. package/dist/commands/knowledge.d.ts +9 -0
  22. package/dist/commands/knowledge.js +99 -0
  23. package/dist/commands/knowledge.js.map +1 -0
  24. package/dist/commands/pack.js +164 -3
  25. package/dist/commands/pack.js.map +1 -1
  26. package/dist/commands/schedule.d.ts +11 -0
  27. package/dist/commands/schedule.js +130 -0
  28. package/dist/commands/schedule.js.map +1 -0
  29. package/dist/commands/staging.d.ts +2 -17
  30. package/dist/commands/staging.js +4 -4
  31. package/dist/commands/staging.js.map +1 -1
  32. package/dist/commands/validate-skills.d.ts +10 -0
  33. package/dist/commands/validate-skills.js +47 -0
  34. package/dist/commands/validate-skills.js.map +1 -0
  35. package/dist/commands/vault.js +2 -11
  36. package/dist/commands/vault.js.map +1 -1
  37. package/dist/main.js +10 -0
  38. package/dist/main.js.map +1 -1
  39. package/dist/utils/checks.js +17 -32
  40. package/dist/utils/checks.js.map +1 -1
  41. package/dist/utils/core-resolver.d.ts +3 -0
  42. package/dist/utils/core-resolver.js +38 -0
  43. package/dist/utils/core-resolver.js.map +1 -0
  44. package/dist/utils/vault-db.d.ts +5 -0
  45. package/dist/utils/vault-db.js +17 -0
  46. package/dist/utils/vault-db.js.map +1 -0
  47. package/package.json +1 -1
  48. package/src/__tests__/doctor.test.ts +46 -1
  49. package/src/__tests__/hook-packs.test.ts +0 -18
  50. package/src/__tests__/hooks-convert.test.ts +0 -28
  51. package/src/__tests__/hooks-sync.test.ts +109 -0
  52. package/src/__tests__/hooks.test.ts +0 -20
  53. package/src/__tests__/install-verify.test.ts +1 -1
  54. package/src/__tests__/install.test.ts +7 -10
  55. package/src/__tests__/update.test.ts +0 -19
  56. package/src/__tests__/validator.test.ts +0 -16
  57. package/src/commands/add-domain.ts +89 -1
  58. package/src/commands/agent.ts +53 -17
  59. package/src/commands/brain.ts +93 -0
  60. package/src/commands/chat.ts +373 -0
  61. package/src/commands/create.ts +11 -8
  62. package/src/commands/dev.ts +21 -0
  63. package/src/commands/dream.ts +1 -11
  64. package/src/commands/hooks.ts +32 -0
  65. package/src/commands/install.ts +3 -8
  66. package/src/commands/knowledge.ts +124 -0
  67. package/src/commands/pack.ts +219 -1
  68. package/src/commands/schedule.ts +150 -0
  69. package/src/commands/staging.ts +5 -5
  70. package/src/commands/validate-skills.ts +58 -0
  71. package/src/commands/vault.ts +2 -11
  72. package/src/main.ts +10 -0
  73. package/src/utils/checks.ts +18 -30
  74. package/src/utils/core-resolver.ts +39 -0
  75. package/src/utils/vault-db.ts +15 -0
  76. package/dist/hook-packs/converter/template.test.ts +0 -133
  77. package/dist/hook-packs/yolo-safety/scripts/anti-deletion.sh +0 -274
  78. package/dist/prompts/archetypes.d.ts +0 -22
  79. package/dist/prompts/archetypes.js +0 -298
  80. package/dist/prompts/archetypes.js.map +0 -1
  81. package/dist/prompts/playbook.d.ts +0 -64
  82. package/dist/prompts/playbook.js +0 -436
  83. package/dist/prompts/playbook.js.map +0 -1
  84. package/dist/utils/format-paths.d.ts +0 -14
  85. package/dist/utils/format-paths.js +0 -27
  86. package/dist/utils/format-paths.js.map +0 -1
  87. 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).toBeGreaterThan(0);
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).toBeGreaterThan(0);
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, writeFileSync, readFileSync } from 'node:fs';
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('node:module');
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('node:module', async () => {
90
- const actual = await vi.importActual<typeof import('node:module')>('node:module');
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
- createRequire: () =>
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
+ }
@@ -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
- let engineVersion = 'not installed';
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
- let installedVersion: string | null = null;
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