@deimoscloud/coreai 0.1.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.
Files changed (216) hide show
  1. package/.prettierrc +9 -0
  2. package/AGENT_SPEC.md +347 -0
  3. package/ARCHITECTURE.md +547 -0
  4. package/DRAFT_PRD.md +1440 -0
  5. package/IMPLEMENTATION_PLAN.md +256 -0
  6. package/PRODUCT.md +473 -0
  7. package/README.md +303 -0
  8. package/WORKFLOWS.md +295 -0
  9. package/agents/_templates/ic-engineer.md +185 -0
  10. package/agents/_templates/reviewer.md +182 -0
  11. package/agents/backend-engineer.yaml +72 -0
  12. package/agents/devops-engineer.yaml +72 -0
  13. package/agents/engineering-manager.yaml +70 -0
  14. package/agents/examples/android-engineer.md +302 -0
  15. package/agents/examples/backend-engineer.md +320 -0
  16. package/agents/examples/devops-engineer.md +742 -0
  17. package/agents/examples/engineering-manager.md +469 -0
  18. package/agents/examples/frontend-engineer.md +58 -0
  19. package/agents/examples/product-manager.md +315 -0
  20. package/agents/examples/qa-engineer.md +371 -0
  21. package/agents/examples/security-engineer.md +525 -0
  22. package/agents/examples/solutions-architect.md +351 -0
  23. package/agents/examples/wearos-engineer.md +359 -0
  24. package/agents/frontend-engineer.yaml +72 -0
  25. package/commands/core/check-inbox.md +34 -0
  26. package/commands/core/delegate.md +30 -0
  27. package/commands/core/git-commit.md +144 -0
  28. package/commands/core/pr-create.md +193 -0
  29. package/commands/core/review.md +56 -0
  30. package/commands/core/sprint-status.md +65 -0
  31. package/commands/optional/docs-update.md +200 -0
  32. package/commands/optional/jira-create.md +200 -0
  33. package/commands/optional/jira-transition.md +184 -0
  34. package/commands/optional/worktree-cleanup.md +167 -0
  35. package/commands/optional/worktree-setup.md +110 -0
  36. package/dist/cli/index.js +4037 -0
  37. package/dist/cli/index.js.map +1 -0
  38. package/dist/index.d.ts +2978 -0
  39. package/dist/index.js +3867 -0
  40. package/dist/index.js.map +1 -0
  41. package/eslint.config.js +29 -0
  42. package/jest.config.js +22 -0
  43. package/knowledge-library/README.md +118 -0
  44. package/knowledge-library/android-engineer/context/current.txt +42 -0
  45. package/knowledge-library/android-engineer/control/decisions.txt +9 -0
  46. package/knowledge-library/android-engineer/control/dependencies.txt +19 -0
  47. package/knowledge-library/android-engineer/control/objectives.txt +26 -0
  48. package/knowledge-library/android-engineer/history/.gitkeep +0 -0
  49. package/knowledge-library/android-engineer/inbox/processed/.gitkeep +0 -0
  50. package/knowledge-library/android-engineer/outbox/.gitkeep +0 -0
  51. package/knowledge-library/android-engineer/tech/.gitkeep +0 -0
  52. package/knowledge-library/architecture.txt +61 -0
  53. package/knowledge-library/backend-engineer/context/current.txt +42 -0
  54. package/knowledge-library/backend-engineer/control/decisions.txt +9 -0
  55. package/knowledge-library/backend-engineer/control/dependencies.txt +19 -0
  56. package/knowledge-library/backend-engineer/control/objectives.txt +26 -0
  57. package/knowledge-library/backend-engineer/history/.gitkeep +0 -0
  58. package/knowledge-library/backend-engineer/inbox/processed/.gitkeep +0 -0
  59. package/knowledge-library/backend-engineer/outbox/.gitkeep +0 -0
  60. package/knowledge-library/backend-engineer/tech/.gitkeep +0 -0
  61. package/knowledge-library/context.txt +52 -0
  62. package/knowledge-library/devops-engineer/context/current.txt +42 -0
  63. package/knowledge-library/devops-engineer/control/decisions.txt +9 -0
  64. package/knowledge-library/devops-engineer/control/dependencies.txt +19 -0
  65. package/knowledge-library/devops-engineer/control/objectives.txt +26 -0
  66. package/knowledge-library/devops-engineer/history/.gitkeep +0 -0
  67. package/knowledge-library/devops-engineer/inbox/processed/.gitkeep +0 -0
  68. package/knowledge-library/devops-engineer/outbox/.gitkeep +0 -0
  69. package/knowledge-library/devops-engineer/tech/.gitkeep +0 -0
  70. package/knowledge-library/engineering-manager/context/current.txt +40 -0
  71. package/knowledge-library/engineering-manager/control/decisions.txt +9 -0
  72. package/knowledge-library/engineering-manager/control/objectives.txt +27 -0
  73. package/knowledge-library/engineering-manager/history/.gitkeep +0 -0
  74. package/knowledge-library/engineering-manager/inbox/processed/.gitkeep +0 -0
  75. package/knowledge-library/engineering-manager/outbox/.gitkeep +0 -0
  76. package/knowledge-library/engineering-manager/tech/.gitkeep +0 -0
  77. package/knowledge-library/prd.txt +81 -0
  78. package/knowledge-library/product-manager/context/current.txt +42 -0
  79. package/knowledge-library/product-manager/control/decisions.txt +9 -0
  80. package/knowledge-library/product-manager/control/dependencies.txt +19 -0
  81. package/knowledge-library/product-manager/control/objectives.txt +26 -0
  82. package/knowledge-library/product-manager/history/.gitkeep +0 -0
  83. package/knowledge-library/product-manager/inbox/processed/.gitkeep +0 -0
  84. package/knowledge-library/product-manager/outbox/.gitkeep +0 -0
  85. package/knowledge-library/product-manager/tech/.gitkeep +0 -0
  86. package/knowledge-library/qa-engineer/context/current.txt +42 -0
  87. package/knowledge-library/qa-engineer/control/decisions.txt +9 -0
  88. package/knowledge-library/qa-engineer/control/dependencies.txt +19 -0
  89. package/knowledge-library/qa-engineer/control/objectives.txt +26 -0
  90. package/knowledge-library/qa-engineer/history/.gitkeep +0 -0
  91. package/knowledge-library/qa-engineer/inbox/processed/.gitkeep +0 -0
  92. package/knowledge-library/qa-engineer/outbox/.gitkeep +0 -0
  93. package/knowledge-library/qa-engineer/tech/.gitkeep +0 -0
  94. package/knowledge-library/security-engineer/context/current.txt +42 -0
  95. package/knowledge-library/security-engineer/control/decisions.txt +9 -0
  96. package/knowledge-library/security-engineer/control/dependencies.txt +19 -0
  97. package/knowledge-library/security-engineer/control/objectives.txt +26 -0
  98. package/knowledge-library/security-engineer/history/.gitkeep +0 -0
  99. package/knowledge-library/security-engineer/inbox/processed/.gitkeep +0 -0
  100. package/knowledge-library/security-engineer/outbox/.gitkeep +0 -0
  101. package/knowledge-library/security-engineer/tech/.gitkeep +0 -0
  102. package/knowledge-library/solutions-architect/context/current.txt +42 -0
  103. package/knowledge-library/solutions-architect/control/decisions.txt +9 -0
  104. package/knowledge-library/solutions-architect/control/dependencies.txt +19 -0
  105. package/knowledge-library/solutions-architect/control/objectives.txt +26 -0
  106. package/knowledge-library/solutions-architect/history/.gitkeep +0 -0
  107. package/knowledge-library/solutions-architect/inbox/processed/.gitkeep +0 -0
  108. package/knowledge-library/solutions-architect/outbox/.gitkeep +0 -0
  109. package/knowledge-library/solutions-architect/tech/.gitkeep +0 -0
  110. package/knowledge-library/wearos-engineer/context/current.txt +42 -0
  111. package/knowledge-library/wearos-engineer/control/decisions.txt +9 -0
  112. package/knowledge-library/wearos-engineer/control/dependencies.txt +19 -0
  113. package/knowledge-library/wearos-engineer/control/objectives.txt +26 -0
  114. package/knowledge-library/wearos-engineer/history/.gitkeep +0 -0
  115. package/knowledge-library/wearos-engineer/inbox/processed/.gitkeep +0 -0
  116. package/knowledge-library/wearos-engineer/outbox/.gitkeep +0 -0
  117. package/knowledge-library/wearos-engineer/tech/.gitkeep +0 -0
  118. package/package.json +66 -0
  119. package/schemas/agent.schema.json +171 -0
  120. package/schemas/coreai.config.schema.json +257 -0
  121. package/scripts/add-agent.sh +323 -0
  122. package/scripts/install.sh +354 -0
  123. package/src/adapters/factory.test.ts +386 -0
  124. package/src/adapters/factory.ts +305 -0
  125. package/src/adapters/index.ts +113 -0
  126. package/src/adapters/interfaces.ts +268 -0
  127. package/src/adapters/mcp/client.test.ts +130 -0
  128. package/src/adapters/mcp/client.ts +451 -0
  129. package/src/adapters/mcp/discovery.test.ts +315 -0
  130. package/src/adapters/mcp/discovery.ts +340 -0
  131. package/src/adapters/mcp/index.ts +66 -0
  132. package/src/adapters/mcp/mapper.test.ts +218 -0
  133. package/src/adapters/mcp/mapper.ts +536 -0
  134. package/src/adapters/mcp/registry.test.ts +433 -0
  135. package/src/adapters/mcp/registry.ts +550 -0
  136. package/src/adapters/mcp/types.ts +258 -0
  137. package/src/adapters/native/filesystem.test.ts +350 -0
  138. package/src/adapters/native/filesystem.ts +393 -0
  139. package/src/adapters/native/github.test.ts +173 -0
  140. package/src/adapters/native/github.ts +627 -0
  141. package/src/adapters/native/index.ts +22 -0
  142. package/src/adapters/native/selector.test.ts +224 -0
  143. package/src/adapters/native/selector.ts +150 -0
  144. package/src/adapters/types.ts +270 -0
  145. package/src/agents/compiler.test.ts +399 -0
  146. package/src/agents/compiler.ts +359 -0
  147. package/src/agents/index.ts +36 -0
  148. package/src/agents/loader.test.ts +319 -0
  149. package/src/agents/loader.ts +143 -0
  150. package/src/agents/resolver.test.ts +282 -0
  151. package/src/agents/resolver.ts +262 -0
  152. package/src/agents/types.ts +87 -0
  153. package/src/cache/index.ts +38 -0
  154. package/src/cache/interfaces.ts +283 -0
  155. package/src/cache/manager.test.ts +266 -0
  156. package/src/cache/manager.ts +388 -0
  157. package/src/cache/provider.test.ts +485 -0
  158. package/src/cache/provider.ts +745 -0
  159. package/src/cache/types.test.ts +192 -0
  160. package/src/cache/types.ts +313 -0
  161. package/src/cli/commands/build.test.ts +248 -0
  162. package/src/cli/commands/build.ts +244 -0
  163. package/src/cli/commands/cache.test.ts +221 -0
  164. package/src/cli/commands/cache.ts +229 -0
  165. package/src/cli/commands/index.ts +63 -0
  166. package/src/cli/commands/init.test.ts +173 -0
  167. package/src/cli/commands/init.ts +296 -0
  168. package/src/cli/commands/skills.test.ts +272 -0
  169. package/src/cli/commands/skills.ts +348 -0
  170. package/src/cli/commands/status.test.ts +392 -0
  171. package/src/cli/commands/status.ts +332 -0
  172. package/src/cli/commands/sync.test.ts +213 -0
  173. package/src/cli/commands/sync.ts +251 -0
  174. package/src/cli/commands/validate.test.ts +216 -0
  175. package/src/cli/commands/validate.ts +340 -0
  176. package/src/cli/index.test.ts +190 -0
  177. package/src/cli/index.ts +493 -0
  178. package/src/commands/context.test.ts +163 -0
  179. package/src/commands/context.ts +111 -0
  180. package/src/commands/index.ts +56 -0
  181. package/src/commands/loader.test.ts +273 -0
  182. package/src/commands/loader.ts +355 -0
  183. package/src/commands/registry.test.ts +384 -0
  184. package/src/commands/registry.ts +248 -0
  185. package/src/commands/runner.test.ts +297 -0
  186. package/src/commands/runner.ts +222 -0
  187. package/src/commands/types.ts +361 -0
  188. package/src/config/index.ts +19 -0
  189. package/src/config/loader.test.ts +262 -0
  190. package/src/config/loader.ts +188 -0
  191. package/src/config/types.ts +154 -0
  192. package/src/context/index.ts +14 -0
  193. package/src/context/loader.test.ts +334 -0
  194. package/src/context/loader.ts +357 -0
  195. package/src/index.test.ts +13 -0
  196. package/src/index.ts +244 -0
  197. package/src/knowledge-library/index.ts +44 -0
  198. package/src/knowledge-library/manager.test.ts +536 -0
  199. package/src/knowledge-library/manager.ts +804 -0
  200. package/src/knowledge-library/types.ts +432 -0
  201. package/src/skills/generator.test.ts +602 -0
  202. package/src/skills/generator.ts +491 -0
  203. package/src/skills/index.ts +27 -0
  204. package/src/skills/templates.ts +520 -0
  205. package/src/skills/types.ts +251 -0
  206. package/templates/completion-report.md +72 -0
  207. package/templates/feedback.md +56 -0
  208. package/templates/project-files/CLAUDE.md.template +109 -0
  209. package/templates/project-files/coreai.json.example +47 -0
  210. package/templates/project-files/mcp.json.template +20 -0
  211. package/templates/review-complete.md +64 -0
  212. package/templates/review-request.md +67 -0
  213. package/templates/task-assignment.md +51 -0
  214. package/tsconfig.build.json +4 -0
  215. package/tsconfig.json +26 -0
  216. package/tsup.config.ts +23 -0
@@ -0,0 +1,248 @@
1
+ /**
2
+ * Build Command Tests
3
+ */
4
+
5
+ import { promises as fs } from 'fs';
6
+ import { join } from 'path';
7
+ import { tmpdir } from 'os';
8
+ import { build, formatBuildResult } from './build.js';
9
+
10
+ describe('Build Command', () => {
11
+ let testDir: string;
12
+ let agentsDir: string;
13
+
14
+ beforeEach(async () => {
15
+ testDir = join(tmpdir(), `build-cmd-test-${Date.now()}-${Math.random().toString(36).slice(2)}`);
16
+ agentsDir = join(testDir, 'coreai', 'agents');
17
+ await fs.mkdir(agentsDir, { recursive: true });
18
+ });
19
+
20
+ afterEach(async () => {
21
+ try {
22
+ await fs.rm(testDir, { recursive: true, force: true });
23
+ } catch {
24
+ // Ignore cleanup errors
25
+ }
26
+ });
27
+
28
+ describe('build', () => {
29
+ it('should build without config file', async () => {
30
+ const result = build({
31
+ projectRoot: testDir,
32
+ coreAgentsDir: join(testDir, 'core-agents'), // Non-existent, will use custom only
33
+ });
34
+
35
+ expect(result.success).toBe(true);
36
+ expect(result.warnings).toBeDefined();
37
+ expect(result.warnings?.some((w) => w.includes('No configuration'))).toBe(true);
38
+ });
39
+
40
+ it('should build with config file', async () => {
41
+ const configPath = join(testDir, 'coreai.config.yaml');
42
+ await fs.writeFile(
43
+ configPath,
44
+ `version: "1.0"
45
+ project:
46
+ name: test-project
47
+ team:
48
+ agents: []
49
+ `
50
+ );
51
+
52
+ const result = build({
53
+ projectRoot: testDir,
54
+ coreAgentsDir: join(testDir, 'core-agents'),
55
+ });
56
+
57
+ expect(result.success).toBe(true);
58
+ expect(result.config).toBeDefined();
59
+ });
60
+
61
+ it('should report config errors', async () => {
62
+ const configPath = join(testDir, 'coreai.config.yaml');
63
+ await fs.writeFile(configPath, 'invalid: yaml: [');
64
+
65
+ const result = build({
66
+ projectRoot: testDir,
67
+ });
68
+
69
+ expect(result.success).toBe(false);
70
+ expect(result.error).toContain('Configuration error');
71
+ });
72
+
73
+ it('should build custom agents', async () => {
74
+ // Create a custom agent
75
+ await fs.writeFile(
76
+ join(agentsDir, 'test-agent.yaml'),
77
+ `role: test-agent
78
+ type: ic-engineer
79
+ display_name: Test Agent
80
+ description: A test agent
81
+ responsibilities:
82
+ - Testing
83
+ expertise:
84
+ primary:
85
+ - Testing
86
+ `
87
+ );
88
+
89
+ const result = build({
90
+ projectRoot: testDir,
91
+ coreAgentsDir: join(testDir, 'non-existent'),
92
+ });
93
+
94
+ expect(result.success).toBe(true);
95
+ expect(result.result?.compiled.length).toBeGreaterThan(0);
96
+ expect(result.result?.compiled.some((a) => a.role === 'test-agent')).toBe(true);
97
+ });
98
+
99
+ it('should filter agents when specified', async () => {
100
+ // Create multiple agents
101
+ await fs.writeFile(
102
+ join(agentsDir, 'agent-a.yaml'),
103
+ `role: agent-a
104
+ type: ic-engineer
105
+ display_name: Agent A
106
+ description: Agent A
107
+ responsibilities: []
108
+ expertise:
109
+ primary: []
110
+ `
111
+ );
112
+
113
+ await fs.writeFile(
114
+ join(agentsDir, 'agent-b.yaml'),
115
+ `role: agent-b
116
+ type: ic-engineer
117
+ display_name: Agent B
118
+ description: Agent B
119
+ responsibilities: []
120
+ expertise:
121
+ primary: []
122
+ `
123
+ );
124
+
125
+ const result = build({
126
+ projectRoot: testDir,
127
+ coreAgentsDir: join(testDir, 'non-existent'),
128
+ agents: ['agent-a'],
129
+ });
130
+
131
+ expect(result.success).toBe(true);
132
+ expect(result.result?.compiled.length).toBe(1);
133
+ expect(result.result?.compiled[0].role).toBe('agent-a');
134
+ });
135
+
136
+ it('should use custom output directory', async () => {
137
+ await fs.writeFile(
138
+ join(agentsDir, 'test-agent.yaml'),
139
+ `role: test-agent
140
+ type: ic-engineer
141
+ display_name: Test Agent
142
+ description: A test agent
143
+ responsibilities: []
144
+ expertise:
145
+ primary: []
146
+ `
147
+ );
148
+
149
+ const outputDir = join(testDir, 'custom-output');
150
+
151
+ const result = build({
152
+ projectRoot: testDir,
153
+ coreAgentsDir: join(testDir, 'non-existent'),
154
+ outputDir,
155
+ });
156
+
157
+ expect(result.success).toBe(true);
158
+ expect(result.result?.compiled[0].outputPath).toContain('custom-output');
159
+ });
160
+ });
161
+
162
+ describe('formatBuildResult', () => {
163
+ it('should format success result', () => {
164
+ const result = {
165
+ success: true,
166
+ result: {
167
+ compiled: [
168
+ { role: 'agent-a', source: 'custom' as const, outputPath: '/path/to/agent-a.md' },
169
+ ],
170
+ errors: [],
171
+ },
172
+ };
173
+
174
+ const output = formatBuildResult(result);
175
+
176
+ expect(output).toContain('Compiled');
177
+ expect(output).toContain('agent-a');
178
+ expect(output).toContain('Build complete');
179
+ });
180
+
181
+ it('should format error result', () => {
182
+ const result = {
183
+ success: false,
184
+ error: 'Build failed',
185
+ result: {
186
+ compiled: [],
187
+ errors: [{ role: 'agent-a', error: 'Invalid definition' }],
188
+ },
189
+ };
190
+
191
+ const output = formatBuildResult(result);
192
+
193
+ expect(output).toContain('Build failed');
194
+ expect(output).toContain('agent-a');
195
+ expect(output).toContain('Invalid definition');
196
+ });
197
+
198
+ it('should show warnings', () => {
199
+ const result = {
200
+ success: true,
201
+ warnings: ['No config file found'],
202
+ result: {
203
+ compiled: [],
204
+ errors: [],
205
+ },
206
+ };
207
+
208
+ const output = formatBuildResult(result);
209
+
210
+ expect(output).toContain('⚠');
211
+ expect(output).toContain('No config file found');
212
+ });
213
+
214
+ it('should group agents by source', () => {
215
+ const result = {
216
+ success: true,
217
+ result: {
218
+ compiled: [
219
+ { role: 'core-agent', source: 'core' as const, outputPath: '/core.md' },
220
+ { role: 'custom-agent', source: 'custom' as const, outputPath: '/custom.md' },
221
+ { role: 'override-agent', source: 'override' as const, outputPath: '/override.md' },
222
+ ],
223
+ errors: [],
224
+ },
225
+ };
226
+
227
+ const output = formatBuildResult(result);
228
+
229
+ expect(output).toContain('Core agents');
230
+ expect(output).toContain('Custom agents');
231
+ expect(output).toContain('Override agents');
232
+ });
233
+
234
+ it('should handle no agents to compile', () => {
235
+ const result = {
236
+ success: true,
237
+ result: {
238
+ compiled: [],
239
+ errors: [],
240
+ },
241
+ };
242
+
243
+ const output = formatBuildResult(result);
244
+
245
+ expect(output).toContain('No agents to compile');
246
+ });
247
+ });
248
+ });
@@ -0,0 +1,244 @@
1
+ /**
2
+ * Build Command
3
+ *
4
+ * Compiles agent definitions to Claude-compatible markdown files.
5
+ */
6
+
7
+ import { configExists, loadConfig, ConfigError } from '../../config/loader.js';
8
+ import type { ResolvedCoreAIConfig } from '../../config/types.js';
9
+ import { compileAgents, type CompileOptions, type CompileResult } from '../../agents/index.js';
10
+ import { initAgentKnowledgeLibrary } from '../../knowledge-library/index.js';
11
+
12
+ /**
13
+ * Options for build command
14
+ */
15
+ export interface BuildCommandOptions {
16
+ /**
17
+ * Project root directory
18
+ */
19
+ projectRoot?: string;
20
+
21
+ /**
22
+ * Path to core agents directory
23
+ */
24
+ coreAgentsDir?: string;
25
+
26
+ /**
27
+ * Output directory for compiled agents
28
+ */
29
+ outputDir?: string;
30
+
31
+ /**
32
+ * Watch for changes (not yet implemented)
33
+ */
34
+ watch?: boolean;
35
+
36
+ /**
37
+ * Only compile specific agents
38
+ */
39
+ agents?: string[];
40
+
41
+ /**
42
+ * Skip validation before build
43
+ */
44
+ skipValidation?: boolean;
45
+
46
+ /**
47
+ * Initialize KnowledgeLibrary directories for agents
48
+ */
49
+ initKnowledgeLibrary?: boolean;
50
+ }
51
+
52
+ /**
53
+ * Result of build command
54
+ */
55
+ export interface BuildCommandResult {
56
+ success: boolean;
57
+ result?: CompileResult;
58
+ config?: ResolvedCoreAIConfig;
59
+ error?: string;
60
+ warnings?: string[];
61
+ knowledgeLibraryInitialized?: string[];
62
+ }
63
+
64
+ /**
65
+ * Build agents
66
+ */
67
+ export function build(options: BuildCommandOptions = {}): BuildCommandResult {
68
+ const projectRoot = options.projectRoot ?? process.cwd();
69
+ const warnings: string[] = [];
70
+
71
+ // Load config if available
72
+ let config: ResolvedCoreAIConfig | undefined;
73
+ if (configExists(projectRoot)) {
74
+ try {
75
+ config = loadConfig(projectRoot);
76
+ } catch (error) {
77
+ if (error instanceof ConfigError) {
78
+ return {
79
+ success: false,
80
+ error: `Configuration error: ${error.message}`,
81
+ };
82
+ }
83
+ return {
84
+ success: false,
85
+ error: `Failed to load config: ${error instanceof Error ? error.message : String(error)}`,
86
+ };
87
+ }
88
+ } else {
89
+ warnings.push('No configuration file found. Building with defaults.');
90
+ }
91
+
92
+ // Set up compile options
93
+ const compileOptions: CompileOptions = {
94
+ projectRoot,
95
+ };
96
+
97
+ if (options.coreAgentsDir) {
98
+ compileOptions.coreAgentsDir = options.coreAgentsDir;
99
+ }
100
+
101
+ if (options.outputDir) {
102
+ compileOptions.outputDir = options.outputDir;
103
+ }
104
+
105
+ // Filter to specific agents if requested
106
+ if (options.agents && options.agents.length > 0) {
107
+ const agentsList = options.agents;
108
+ compileOptions.filter = (agent) => agentsList.includes(agent.role);
109
+ }
110
+
111
+ // Compile agents
112
+ try {
113
+ const result = compileAgents(config, compileOptions);
114
+
115
+ // Check for errors
116
+ if (result.errors.length > 0) {
117
+ return {
118
+ success: false,
119
+ result,
120
+ config,
121
+ error: `Failed to compile ${result.errors.length} agent(s)`,
122
+ warnings,
123
+ };
124
+ }
125
+
126
+ // Initialize KnowledgeLibrary for agents if requested
127
+ let knowledgeLibraryInitialized: string[] | undefined;
128
+ if (options.initKnowledgeLibrary) {
129
+ knowledgeLibraryInitialized = [];
130
+ for (const compiled of result.compiled) {
131
+ const initResult = initAgentKnowledgeLibrary(compiled.role, {
132
+ projectRoot,
133
+ createDefaults: true,
134
+ });
135
+ if (initResult.success && initResult.createdDirs.length > 0) {
136
+ knowledgeLibraryInitialized.push(compiled.role);
137
+ }
138
+ }
139
+ }
140
+
141
+ return {
142
+ success: true,
143
+ result,
144
+ config,
145
+ warnings: warnings.length > 0 ? warnings : undefined,
146
+ knowledgeLibraryInitialized,
147
+ };
148
+ } catch (error) {
149
+ return {
150
+ success: false,
151
+ error: `Build failed: ${error instanceof Error ? error.message : String(error)}`,
152
+ warnings: warnings.length > 0 ? warnings : undefined,
153
+ };
154
+ }
155
+ }
156
+
157
+ /**
158
+ * Format build result for display
159
+ */
160
+ export function formatBuildResult(result: BuildCommandResult): string {
161
+ const lines: string[] = [];
162
+
163
+ // Warnings
164
+ if (result.warnings && result.warnings.length > 0) {
165
+ for (const warning of result.warnings) {
166
+ lines.push(`⚠ ${warning}`);
167
+ }
168
+ lines.push('');
169
+ }
170
+
171
+ if (!result.success) {
172
+ lines.push(`Build failed: ${result.error}`);
173
+
174
+ // Show individual errors
175
+ if (result.result?.errors && result.result.errors.length > 0) {
176
+ lines.push('');
177
+ for (const error of result.result.errors) {
178
+ lines.push(` ✗ ${error.role}: ${error.error}`);
179
+ }
180
+ }
181
+
182
+ return lines.join('\n');
183
+ }
184
+
185
+ const compileResult = result.result;
186
+ if (!compileResult) {
187
+ lines.push('No agents to compile.');
188
+ return lines.join('\n');
189
+ }
190
+
191
+ if (compileResult.compiled.length === 0) {
192
+ lines.push('No agents to compile.');
193
+ return lines.join('\n');
194
+ }
195
+
196
+ lines.push(`Compiled ${compileResult.compiled.length} agent(s):\n`);
197
+
198
+ // Group by source
199
+ const coreAgents = compileResult.compiled.filter((a) => a.source === 'core');
200
+ const customAgents = compileResult.compiled.filter((a) => a.source === 'custom');
201
+ const overrideAgents = compileResult.compiled.filter((a) => a.source === 'override');
202
+
203
+ if (coreAgents.length > 0) {
204
+ lines.push('Core agents:');
205
+ for (const agent of coreAgents) {
206
+ lines.push(` ✓ ${agent.role}`);
207
+ lines.push(` → ${agent.outputPath}`);
208
+ }
209
+ lines.push('');
210
+ }
211
+
212
+ if (customAgents.length > 0) {
213
+ lines.push('Custom agents:');
214
+ for (const agent of customAgents) {
215
+ lines.push(` ✓ ${agent.role}`);
216
+ lines.push(` → ${agent.outputPath}`);
217
+ }
218
+ lines.push('');
219
+ }
220
+
221
+ if (overrideAgents.length > 0) {
222
+ lines.push('Override agents:');
223
+ for (const agent of overrideAgents) {
224
+ lines.push(` ✓ ${agent.role} (overrides core)`);
225
+ lines.push(` → ${agent.outputPath}`);
226
+ }
227
+ lines.push('');
228
+ }
229
+
230
+ // KnowledgeLibrary initialization
231
+ if (result.knowledgeLibraryInitialized && result.knowledgeLibraryInitialized.length > 0) {
232
+ lines.push(
233
+ `KnowledgeLibrary initialized for ${result.knowledgeLibraryInitialized.length} agent(s):`
234
+ );
235
+ for (const agent of result.knowledgeLibraryInitialized) {
236
+ lines.push(` ✓ ${agent}`);
237
+ }
238
+ lines.push('');
239
+ }
240
+
241
+ lines.push('Build complete!');
242
+
243
+ return lines.join('\n');
244
+ }
@@ -0,0 +1,221 @@
1
+ /**
2
+ * Cache Commands Tests
3
+ */
4
+
5
+ import { promises as fs } from 'fs';
6
+ import { join } from 'path';
7
+ import { tmpdir } from 'os';
8
+ import { FileCacheProvider } from '../../cache/provider.js';
9
+ import {
10
+ cacheStatus,
11
+ cacheClear,
12
+ cacheClearExpired,
13
+ formatCacheStatus,
14
+ formatBytes,
15
+ } from './cache.js';
16
+
17
+ describe('Cache Commands', () => {
18
+ let testDir: string;
19
+
20
+ beforeEach(async () => {
21
+ testDir = join(tmpdir(), `cache-cmd-test-${Date.now()}-${Math.random().toString(36).slice(2)}`);
22
+ await fs.mkdir(testDir, { recursive: true });
23
+ });
24
+
25
+ afterEach(async () => {
26
+ try {
27
+ await fs.rm(testDir, { recursive: true, force: true });
28
+ } catch {
29
+ // Ignore cleanup errors
30
+ }
31
+ });
32
+
33
+ describe('cacheStatus', () => {
34
+ it('should return initialized for new directory', async () => {
35
+ // Cache auto-initializes when called
36
+ const result = await cacheStatus({ cachePath: join(testDir, 'new-cache') });
37
+
38
+ expect(result.initialized).toBe(true);
39
+ expect(result.stats).not.toBeNull();
40
+ expect(result.entries).toHaveLength(0);
41
+ });
42
+
43
+ it('should return status for initialized cache', async () => {
44
+ // Initialize cache with some entries
45
+ const provider = new FileCacheProvider({ basePath: testDir });
46
+ await provider.initialize();
47
+ await provider.set('test-key', 'test content', {
48
+ source: 'github',
49
+ sourceUrl: 'https://github.com/test',
50
+ });
51
+
52
+ const result = await cacheStatus({ cachePath: testDir });
53
+
54
+ expect(result.initialized).toBe(true);
55
+ expect(result.stats).not.toBeNull();
56
+ expect(result.stats?.totalEntries).toBe(1);
57
+ expect(result.entries).toHaveLength(1);
58
+ expect(result.entries[0].key).toBe('test-key');
59
+ expect(result.entries[0].source).toBe('github');
60
+ });
61
+
62
+ it('should include entry status', async () => {
63
+ const provider = new FileCacheProvider({ basePath: testDir });
64
+ await provider.initialize();
65
+ await provider.set(
66
+ 'valid-entry',
67
+ 'content',
68
+ { source: 'local', sourceUrl: '' },
69
+ { ttl: 3600 }
70
+ );
71
+
72
+ const result = await cacheStatus({ cachePath: testDir });
73
+
74
+ expect(result.entries[0].status).toBe('valid');
75
+ });
76
+ });
77
+
78
+ describe('cacheClear', () => {
79
+ it('should clear all cache entries', async () => {
80
+ const provider = new FileCacheProvider({ basePath: testDir });
81
+ await provider.initialize();
82
+ await provider.set('key1', 'content1', { source: 'local', sourceUrl: '' });
83
+ await provider.set('key2', 'content2', { source: 'local', sourceUrl: '' });
84
+
85
+ const result = await cacheClear({ cachePath: testDir });
86
+
87
+ expect(result.success).toBe(true);
88
+ expect(result.cleared).toBe(2);
89
+
90
+ // Verify cache is empty
91
+ const status = await cacheStatus({ cachePath: testDir });
92
+ expect(status.entries).toHaveLength(0);
93
+ });
94
+
95
+ it('should handle empty cache', async () => {
96
+ const provider = new FileCacheProvider({ basePath: testDir });
97
+ await provider.initialize();
98
+
99
+ const result = await cacheClear({ cachePath: testDir });
100
+
101
+ expect(result.success).toBe(true);
102
+ expect(result.cleared).toBe(0);
103
+ });
104
+
105
+ it('should handle uninitialized cache directory', async () => {
106
+ const result = await cacheClear({ cachePath: join(testDir, 'new-cache') });
107
+
108
+ expect(result.success).toBe(true);
109
+ expect(result.cleared).toBe(0);
110
+ });
111
+ });
112
+
113
+ describe('cacheClearExpired', () => {
114
+ it('should only clear expired entries', async () => {
115
+ const provider = new FileCacheProvider({ basePath: testDir });
116
+ await provider.initialize();
117
+
118
+ // Add a valid entry
119
+ await provider.set('valid', 'content', { source: 'local', sourceUrl: '' }, { ttl: 3600 });
120
+
121
+ // Add an "expired" entry by manipulating the metadata
122
+ await provider.set('expired', 'content', { source: 'local', sourceUrl: '' }, { ttl: 1 });
123
+
124
+ // Wait for expiration
125
+ await new Promise((resolve) => setTimeout(resolve, 1100));
126
+
127
+ const result = await cacheClearExpired({ cachePath: testDir });
128
+
129
+ expect(result.success).toBe(true);
130
+ expect(result.cleared).toBe(1);
131
+
132
+ // Valid entry should still exist
133
+ const status = await cacheStatus({ cachePath: testDir });
134
+ expect(status.entries.map((e) => e.key)).toContain('valid');
135
+ });
136
+ });
137
+
138
+ describe('formatBytes', () => {
139
+ it('should format bytes correctly', () => {
140
+ expect(formatBytes(0)).toBe('0 B');
141
+ expect(formatBytes(100)).toBe('100 B');
142
+ expect(formatBytes(1024)).toBe('1 KB');
143
+ expect(formatBytes(1536)).toBe('1.5 KB');
144
+ expect(formatBytes(1048576)).toBe('1 MB');
145
+ expect(formatBytes(1073741824)).toBe('1 GB');
146
+ });
147
+ });
148
+
149
+ describe('formatCacheStatus', () => {
150
+ it('should format uninitialized cache', () => {
151
+ const result = formatCacheStatus({
152
+ initialized: false,
153
+ path: '/test/cache',
154
+ stats: null,
155
+ entries: [],
156
+ });
157
+
158
+ expect(result).toContain('Not initialized');
159
+ expect(result).toContain('/test/cache');
160
+ });
161
+
162
+ it('should format initialized cache with entries', () => {
163
+ const result = formatCacheStatus({
164
+ initialized: true,
165
+ path: '/test/cache',
166
+ stats: {
167
+ totalEntries: 2,
168
+ totalSize: 1024,
169
+ validEntries: 1,
170
+ staleEntries: 1,
171
+ expiredEntries: 0,
172
+ bySource: { github: 2, confluence: 0, notion: 0, local: 0, custom: 0 },
173
+ newestEntry: '2024-01-01T00:00:00Z',
174
+ },
175
+ entries: [
176
+ {
177
+ key: 'entry1',
178
+ source: 'github',
179
+ status: 'valid',
180
+ size: 512,
181
+ cachedAt: '2024-01-01T00:00:00Z',
182
+ expiresAt: '2024-01-02T00:00:00Z',
183
+ },
184
+ {
185
+ key: 'entry2',
186
+ source: 'github',
187
+ status: 'stale',
188
+ size: 512,
189
+ cachedAt: '2024-01-01T00:00:00Z',
190
+ expiresAt: '2024-01-02T00:00:00Z',
191
+ },
192
+ ],
193
+ });
194
+
195
+ expect(result).toContain('Initialized');
196
+ expect(result).toContain('Entries: 2');
197
+ expect(result).toContain('1 KB');
198
+ expect(result).toContain('github');
199
+ expect(result).toContain('entry1');
200
+ expect(result).toContain('entry2');
201
+ });
202
+
203
+ it('should format empty cache', () => {
204
+ const result = formatCacheStatus({
205
+ initialized: true,
206
+ path: '/test/cache',
207
+ stats: {
208
+ totalEntries: 0,
209
+ totalSize: 0,
210
+ validEntries: 0,
211
+ staleEntries: 0,
212
+ expiredEntries: 0,
213
+ bySource: { github: 0, confluence: 0, notion: 0, local: 0, custom: 0 },
214
+ },
215
+ entries: [],
216
+ });
217
+
218
+ expect(result).toContain('No entries in cache');
219
+ });
220
+ });
221
+ });