@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,359 @@
1
+ /**
2
+ * Agent Compiler
3
+ *
4
+ * Transforms YAML agent definitions into Claude-compatible Markdown files.
5
+ */
6
+
7
+ import { existsSync, mkdirSync, writeFileSync } from 'fs';
8
+ import { join, dirname } from 'path';
9
+ import type { CoreAIConfig } from '../config/types.js';
10
+ import type { AgentDefinition, AgentMetadata, AgentSource } from './types.js';
11
+ import { loadAgentsFromDirectory } from './loader.js';
12
+ import { resolveAgentDefinition } from './resolver.js';
13
+
14
+ /**
15
+ * Options for compiling agents
16
+ */
17
+ export interface CompileOptions {
18
+ /**
19
+ * Output directory for compiled markdown files.
20
+ * Default: .claude/agents
21
+ */
22
+ outputDir?: string;
23
+
24
+ /**
25
+ * Core agents directory (built-in agents).
26
+ * Default: uses package's agents/ directory
27
+ */
28
+ coreAgentsDir?: string;
29
+
30
+ /**
31
+ * Custom agents directory (project-specific agents).
32
+ * Default: coreai/agents in project root
33
+ */
34
+ customAgentsDir?: string;
35
+
36
+ /**
37
+ * Project root directory.
38
+ * Default: process.cwd()
39
+ */
40
+ projectRoot?: string;
41
+
42
+ /**
43
+ * Filter function to select which agents to compile.
44
+ * Returns true to include the agent, false to exclude.
45
+ */
46
+ filter?: (agent: AgentDefinition) => boolean;
47
+ }
48
+
49
+ /**
50
+ * Result of compiling agents
51
+ */
52
+ export interface CompileResult {
53
+ /**
54
+ * Successfully compiled agents
55
+ */
56
+ compiled: {
57
+ role: string;
58
+ source: AgentSource;
59
+ outputPath: string;
60
+ }[];
61
+
62
+ /**
63
+ * Agents that failed to compile
64
+ */
65
+ errors: {
66
+ role: string;
67
+ source: AgentSource;
68
+ error: string;
69
+ }[];
70
+ }
71
+
72
+ /**
73
+ * Generate markdown content from a resolved agent definition
74
+ */
75
+ export function generateAgentMarkdown(agent: AgentDefinition): string {
76
+ const lines: string[] = [];
77
+
78
+ // Header
79
+ lines.push(`# ${agent.display_name}`);
80
+ lines.push('');
81
+ lines.push(`**Role:** ${agent.role}`);
82
+ lines.push(`**Type:** ${agent.type}`);
83
+ lines.push('');
84
+
85
+ // Description
86
+ lines.push('## Description');
87
+ lines.push('');
88
+ lines.push(agent.description.trim());
89
+ lines.push('');
90
+
91
+ // Responsibilities
92
+ if (agent.responsibilities && agent.responsibilities.length > 0) {
93
+ lines.push('## Responsibilities');
94
+ lines.push('');
95
+ for (const responsibility of agent.responsibilities) {
96
+ lines.push(`- ${responsibility}`);
97
+ }
98
+ lines.push('');
99
+ }
100
+
101
+ // Expertise
102
+ if (agent.expertise) {
103
+ lines.push('## Expertise');
104
+ lines.push('');
105
+
106
+ if (agent.expertise.primary && agent.expertise.primary.length > 0) {
107
+ lines.push('### Primary Areas');
108
+ lines.push('');
109
+ for (const area of agent.expertise.primary) {
110
+ lines.push(`- ${area}`);
111
+ }
112
+ lines.push('');
113
+ }
114
+
115
+ if (agent.expertise.tech_stack) {
116
+ lines.push('### Tech Stack');
117
+ lines.push('');
118
+ const techStack = agent.expertise.tech_stack;
119
+ if (typeof techStack === 'string') {
120
+ lines.push(techStack);
121
+ } else if (typeof techStack === 'object') {
122
+ lines.push('```json');
123
+ lines.push(JSON.stringify(techStack, null, 2));
124
+ lines.push('```');
125
+ }
126
+ lines.push('');
127
+ }
128
+ }
129
+
130
+ // Skills
131
+ if (agent.skills && agent.skills.length > 0) {
132
+ lines.push('## Skills');
133
+ lines.push('');
134
+ for (const skill of agent.skills) {
135
+ lines.push(`- ${skill}`);
136
+ }
137
+ lines.push('');
138
+ }
139
+
140
+ // Principles
141
+ if (agent.principles) {
142
+ lines.push('## Principles');
143
+ lines.push('');
144
+
145
+ for (const [category, items] of Object.entries(agent.principles)) {
146
+ if (items && Array.isArray(items) && items.length > 0) {
147
+ const title = formatTitle(category);
148
+ lines.push(`### ${title}`);
149
+ lines.push('');
150
+ for (const item of items) {
151
+ lines.push(`- ${item}`);
152
+ }
153
+ lines.push('');
154
+ }
155
+ }
156
+ }
157
+
158
+ // Behaviors
159
+ if (agent.behaviors) {
160
+ lines.push('## Behaviors');
161
+ lines.push('');
162
+
163
+ if (agent.behaviors.workflow) {
164
+ lines.push(`**Workflow:** ${agent.behaviors.workflow}`);
165
+ lines.push('');
166
+ }
167
+
168
+ if (agent.behaviors.quality_gates) {
169
+ lines.push('### Quality Gates');
170
+ lines.push('');
171
+ const gates = agent.behaviors.quality_gates;
172
+ if (typeof gates === 'string') {
173
+ lines.push(gates);
174
+ } else if (typeof gates === 'object') {
175
+ lines.push('```json');
176
+ lines.push(JSON.stringify(gates, null, 2));
177
+ lines.push('```');
178
+ }
179
+ lines.push('');
180
+ }
181
+ }
182
+
183
+ // Context Sources
184
+ if (agent.context_sources) {
185
+ lines.push('## Context Sources');
186
+ lines.push('');
187
+
188
+ if (agent.context_sources.shared && agent.context_sources.shared.length > 0) {
189
+ lines.push('### Shared');
190
+ lines.push('');
191
+ for (const source of agent.context_sources.shared) {
192
+ lines.push(`- ${source}`);
193
+ }
194
+ lines.push('');
195
+ }
196
+
197
+ if (agent.context_sources.personal && agent.context_sources.personal.length > 0) {
198
+ lines.push('### Personal');
199
+ lines.push('');
200
+ for (const source of agent.context_sources.personal) {
201
+ lines.push(`- ${source}`);
202
+ }
203
+ lines.push('');
204
+ }
205
+ }
206
+
207
+ // Communication
208
+ if (agent.communication) {
209
+ lines.push('## Communication');
210
+ lines.push('');
211
+ if (agent.communication.inbox) {
212
+ lines.push(`**Inbox:** ${agent.communication.inbox}`);
213
+ }
214
+ if (agent.communication.outbox) {
215
+ lines.push(`**Outbox:** ${agent.communication.outbox}`);
216
+ }
217
+ lines.push('');
218
+ }
219
+
220
+ // Footer
221
+ lines.push('---');
222
+ lines.push('');
223
+ lines.push('*Generated by CoreAI*');
224
+ lines.push('');
225
+
226
+ return lines.join('\n');
227
+ }
228
+
229
+ /**
230
+ * Format a snake_case or kebab-case string as a title
231
+ */
232
+ function formatTitle(str: string): string {
233
+ return str.replace(/[_-]/g, ' ').replace(/\b\w/g, (c) => c.toUpperCase());
234
+ }
235
+
236
+ /**
237
+ * Compile a single agent to markdown
238
+ */
239
+ export function compileAgent(agent: AgentDefinition, config?: CoreAIConfig): string {
240
+ const resolved = resolveAgentDefinition(agent, config);
241
+ return generateAgentMarkdown(resolved);
242
+ }
243
+
244
+ /**
245
+ * Load all agents from core and custom directories
246
+ */
247
+ export function loadAllAgents(options: CompileOptions = {}): Map<string, AgentMetadata> {
248
+ const agents = new Map<string, AgentMetadata>();
249
+
250
+ // Load core agents
251
+ if (options.coreAgentsDir && existsSync(options.coreAgentsDir)) {
252
+ const coreAgents = loadAgentsFromDirectory(options.coreAgentsDir, 'core');
253
+ for (const [role, metadata] of coreAgents) {
254
+ agents.set(role, metadata);
255
+ }
256
+ }
257
+
258
+ // Load custom agents (can override core agents)
259
+ if (options.customAgentsDir && existsSync(options.customAgentsDir)) {
260
+ const customAgents = loadAgentsFromDirectory(options.customAgentsDir, 'custom');
261
+ for (const [role, metadata] of customAgents) {
262
+ // Check if this is overriding a core agent
263
+ if (agents.has(role)) {
264
+ metadata.source = 'override';
265
+ }
266
+ agents.set(role, metadata);
267
+ }
268
+ }
269
+
270
+ return agents;
271
+ }
272
+
273
+ /**
274
+ * Filter agents based on team configuration
275
+ */
276
+ export function filterAgentsByTeam(
277
+ agents: Map<string, AgentMetadata>,
278
+ config?: CoreAIConfig
279
+ ): Map<string, AgentMetadata> {
280
+ if (!config?.team?.agents || config.team.agents.length === 0) {
281
+ // No team filter, return all agents
282
+ return agents;
283
+ }
284
+
285
+ const filtered = new Map<string, AgentMetadata>();
286
+ for (const role of config.team.agents) {
287
+ const metadata = agents.get(role);
288
+ if (metadata) {
289
+ filtered.set(role, metadata);
290
+ }
291
+ }
292
+
293
+ return filtered;
294
+ }
295
+
296
+ /**
297
+ * Compile all agents and write to output directory
298
+ */
299
+ export function compileAgents(config?: CoreAIConfig, options: CompileOptions = {}): CompileResult {
300
+ const projectRoot = options.projectRoot ?? process.cwd();
301
+ const outputDir = options.outputDir ?? join(projectRoot, '.claude', 'agents');
302
+ const customAgentsDir = options.customAgentsDir ?? join(projectRoot, 'coreai', 'agents');
303
+
304
+ const result: CompileResult = {
305
+ compiled: [],
306
+ errors: [],
307
+ };
308
+
309
+ // Load all agents
310
+ const allAgents = loadAllAgents({
311
+ ...options,
312
+ customAgentsDir,
313
+ });
314
+
315
+ // Filter by team if configured
316
+ const agents = filterAgentsByTeam(allAgents, config);
317
+
318
+ // Ensure output directory exists
319
+ if (!existsSync(outputDir)) {
320
+ mkdirSync(outputDir, { recursive: true });
321
+ }
322
+
323
+ // Compile each agent
324
+ for (const [role, metadata] of agents) {
325
+ // Apply custom filter if provided
326
+ if (options.filter && !options.filter(metadata.definition)) {
327
+ continue;
328
+ }
329
+
330
+ try {
331
+ const markdown = compileAgent(metadata.definition, config);
332
+ const outputPath = join(outputDir, `${role}.md`);
333
+
334
+ writeFileSync(outputPath, markdown, 'utf-8');
335
+
336
+ result.compiled.push({
337
+ role,
338
+ source: metadata.source,
339
+ outputPath,
340
+ });
341
+ } catch (error) {
342
+ result.errors.push({
343
+ role,
344
+ source: metadata.source,
345
+ error: error instanceof Error ? error.message : String(error),
346
+ });
347
+ }
348
+ }
349
+
350
+ return result;
351
+ }
352
+
353
+ /**
354
+ * Get the default core agents directory
355
+ */
356
+ export function getCoreAgentsDir(): string {
357
+ // Navigate from dist/agents/compiler.js to agents/
358
+ return join(dirname(dirname(dirname(import.meta.url.replace('file://', '')))), 'agents');
359
+ }
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Agents Module
3
+ *
4
+ * Provides agent loading, validation, and management for CoreAI.
5
+ */
6
+
7
+ export * from './types.js';
8
+ export {
9
+ loadAgentFromFile,
10
+ loadAgentsFromDirectory,
11
+ parseAgentYaml,
12
+ validateAgentDefinition,
13
+ getRoleFromFilename,
14
+ AgentError,
15
+ type AgentErrorCode,
16
+ } from './loader.js';
17
+ export {
18
+ resolveString,
19
+ resolveObject,
20
+ resolveAgentDefinition,
21
+ hasVariables,
22
+ extractVariables,
23
+ ResolutionError,
24
+ type ResolutionContext,
25
+ type ResolutionOptions,
26
+ } from './resolver.js';
27
+ export {
28
+ generateAgentMarkdown,
29
+ compileAgent,
30
+ compileAgents,
31
+ loadAllAgents,
32
+ filterAgentsByTeam,
33
+ getCoreAgentsDir,
34
+ type CompileOptions,
35
+ type CompileResult,
36
+ } from './compiler.js';
@@ -0,0 +1,319 @@
1
+ import { mkdtempSync, writeFileSync, rmSync } from 'fs';
2
+ import { join, dirname } from 'path';
3
+ import { tmpdir } from 'os';
4
+ import { fileURLToPath } from 'url';
5
+ import { jest } from '@jest/globals';
6
+
7
+ const __filename = fileURLToPath(import.meta.url);
8
+ const __dirname = dirname(__filename);
9
+ import {
10
+ parseAgentYaml,
11
+ validateAgentDefinition,
12
+ loadAgentFromFile,
13
+ loadAgentsFromDirectory,
14
+ getRoleFromFilename,
15
+ AgentError,
16
+ } from './loader.js';
17
+ import type { AgentDefinition } from './types.js';
18
+
19
+ /**
20
+ * Helper to check AgentError with specific code
21
+ */
22
+ function expectAgentError(fn: () => unknown, code: string): void {
23
+ try {
24
+ fn();
25
+ fail('Expected AgentError to be thrown');
26
+ } catch (error) {
27
+ expect(error).toBeInstanceOf(AgentError);
28
+ expect((error as AgentError).code).toBe(code);
29
+ }
30
+ }
31
+
32
+ /**
33
+ * Create a valid minimal agent definition
34
+ */
35
+ function createMinimalAgent(overrides: Partial<AgentDefinition> = {}): AgentDefinition {
36
+ return {
37
+ role: 'test-agent',
38
+ type: 'ic-engineer',
39
+ display_name: 'Test Agent',
40
+ description: 'A test agent for unit tests',
41
+ ...overrides,
42
+ };
43
+ }
44
+
45
+ describe('Agent Loader', () => {
46
+ let tempDir: string;
47
+
48
+ beforeEach(() => {
49
+ tempDir = mkdtempSync(join(tmpdir(), 'coreai-agent-test-'));
50
+ });
51
+
52
+ afterEach(() => {
53
+ rmSync(tempDir, { recursive: true, force: true });
54
+ });
55
+
56
+ describe('parseAgentYaml', () => {
57
+ it('should parse valid YAML', () => {
58
+ const yaml = `
59
+ role: backend-engineer
60
+ type: ic-engineer
61
+ display_name: Backend Engineer
62
+ description: Backend development specialist
63
+ `;
64
+ const result = parseAgentYaml(yaml);
65
+ expect(result).toEqual({
66
+ role: 'backend-engineer',
67
+ type: 'ic-engineer',
68
+ display_name: 'Backend Engineer',
69
+ description: 'Backend development specialist',
70
+ });
71
+ });
72
+
73
+ it('should throw AgentError for invalid YAML', () => {
74
+ const invalidYaml = `
75
+ role: "test
76
+ invalid: yaml
77
+ `;
78
+ expectAgentError(() => parseAgentYaml(invalidYaml), 'PARSE_ERROR');
79
+ });
80
+ });
81
+
82
+ describe('validateAgentDefinition', () => {
83
+ it('should validate a minimal valid agent', () => {
84
+ const agent = createMinimalAgent();
85
+ const result = validateAgentDefinition(agent);
86
+ expect(result).toEqual(agent);
87
+ });
88
+
89
+ it('should validate a full agent definition', () => {
90
+ const agent: AgentDefinition = {
91
+ role: 'backend-engineer',
92
+ type: 'ic-engineer',
93
+ display_name: 'Backend Engineer',
94
+ description: 'Backend development specialist',
95
+ responsibilities: ['Implement APIs', 'Write tests'],
96
+ expertise: {
97
+ primary: ['API design', 'Database design'],
98
+ tech_stack: '${config.tech_stack}',
99
+ },
100
+ skills: ['Code review', 'Debugging'],
101
+ principles: {
102
+ code_quality: ['Write clean code', 'Follow SOLID'],
103
+ testing: ['Write tests first'],
104
+ },
105
+ behaviors: {
106
+ workflow: 'ticket-implementation',
107
+ quality_gates: '${config.quality_gates}',
108
+ },
109
+ context_sources: {
110
+ shared: ['${remote.documentation}/architecture'],
111
+ personal: ['KnowledgeLibrary/${agent.name}/context'],
112
+ },
113
+ communication: {
114
+ inbox: 'KnowledgeLibrary/${agent.name}/inbox',
115
+ outbox: 'KnowledgeLibrary/${agent.name}/outbox',
116
+ },
117
+ };
118
+
119
+ const result = validateAgentDefinition(agent);
120
+ expect(result).toEqual(agent);
121
+ });
122
+
123
+ it('should throw for missing required fields', () => {
124
+ const agent = { type: 'ic-engineer' }; // missing role, display_name, description
125
+ expectAgentError(() => validateAgentDefinition(agent), 'VALIDATION_ERROR');
126
+ });
127
+
128
+ it('should throw for invalid role format', () => {
129
+ const agent = createMinimalAgent({ role: 'Invalid Role' }); // uppercase and space
130
+ expectAgentError(() => validateAgentDefinition(agent), 'VALIDATION_ERROR');
131
+ });
132
+
133
+ it('should throw for invalid type', () => {
134
+ const agent = { ...createMinimalAgent(), type: 'invalid-type' };
135
+ expectAgentError(() => validateAgentDefinition(agent), 'VALIDATION_ERROR');
136
+ });
137
+
138
+ it('should throw for invalid workflow', () => {
139
+ const agent = createMinimalAgent();
140
+ agent.behaviors = { workflow: 'invalid-workflow' as never };
141
+ expectAgentError(() => validateAgentDefinition(agent), 'VALIDATION_ERROR');
142
+ });
143
+ });
144
+
145
+ describe('loadAgentFromFile', () => {
146
+ it('should load and validate an agent file', () => {
147
+ const agentPath = join(tempDir, 'test-agent.yaml');
148
+ writeFileSync(
149
+ agentPath,
150
+ `
151
+ role: test-agent
152
+ type: ic-engineer
153
+ display_name: Test Agent
154
+ description: A test agent
155
+ `
156
+ );
157
+
158
+ const result = loadAgentFromFile(agentPath);
159
+ expect(result.role).toBe('test-agent');
160
+ expect(result.type).toBe('ic-engineer');
161
+ });
162
+
163
+ it('should throw for non-existent file', () => {
164
+ const agentPath = join(tempDir, 'nonexistent.yaml');
165
+ expectAgentError(() => loadAgentFromFile(agentPath), 'NOT_FOUND');
166
+ });
167
+
168
+ it('should throw for invalid agent', () => {
169
+ const agentPath = join(tempDir, 'invalid.yaml');
170
+ writeFileSync(agentPath, 'invalid: content');
171
+ expectAgentError(() => loadAgentFromFile(agentPath), 'VALIDATION_ERROR');
172
+ });
173
+ });
174
+
175
+ describe('loadAgentsFromDirectory', () => {
176
+ it('should load all agents from a directory', () => {
177
+ // Create multiple agent files
178
+ writeFileSync(
179
+ join(tempDir, 'agent-one.yaml'),
180
+ `
181
+ role: agent-one
182
+ type: ic-engineer
183
+ display_name: Agent One
184
+ description: First agent
185
+ `
186
+ );
187
+ writeFileSync(
188
+ join(tempDir, 'agent-two.yaml'),
189
+ `
190
+ role: agent-two
191
+ type: manager
192
+ display_name: Agent Two
193
+ description: Second agent
194
+ `
195
+ );
196
+
197
+ const agents = loadAgentsFromDirectory(tempDir, 'core');
198
+
199
+ expect(agents.size).toBe(2);
200
+ expect(agents.get('agent-one')?.definition.role).toBe('agent-one');
201
+ expect(agents.get('agent-two')?.definition.role).toBe('agent-two');
202
+ expect(agents.get('agent-one')?.source).toBe('core');
203
+ });
204
+
205
+ it('should return empty map for non-existent directory', () => {
206
+ const agents = loadAgentsFromDirectory(join(tempDir, 'nonexistent'), 'core');
207
+ expect(agents.size).toBe(0);
208
+ });
209
+
210
+ it('should skip invalid files and continue loading', () => {
211
+ writeFileSync(
212
+ join(tempDir, 'valid.yaml'),
213
+ `
214
+ role: valid-agent
215
+ type: ic-engineer
216
+ display_name: Valid Agent
217
+ description: A valid agent
218
+ `
219
+ );
220
+ writeFileSync(join(tempDir, 'invalid.yaml'), 'invalid: content');
221
+
222
+ // Suppress console.warn for this test
223
+ const warnSpy = jest.spyOn(console, 'warn').mockImplementation(() => undefined);
224
+
225
+ const agents = loadAgentsFromDirectory(tempDir, 'core');
226
+
227
+ expect(agents.size).toBe(1);
228
+ expect(agents.get('valid-agent')).toBeDefined();
229
+ expect(warnSpy).toHaveBeenCalled();
230
+
231
+ warnSpy.mockRestore();
232
+ });
233
+
234
+ it('should handle .yml extension', () => {
235
+ writeFileSync(
236
+ join(tempDir, 'agent.yml'),
237
+ `
238
+ role: yml-agent
239
+ type: specialist
240
+ display_name: YML Agent
241
+ description: Agent with .yml extension
242
+ `
243
+ );
244
+
245
+ const agents = loadAgentsFromDirectory(tempDir, 'custom');
246
+ expect(agents.size).toBe(1);
247
+ expect(agents.get('yml-agent')?.source).toBe('custom');
248
+ });
249
+ });
250
+
251
+ describe('getRoleFromFilename', () => {
252
+ it('should extract role from .yaml filename', () => {
253
+ expect(getRoleFromFilename('/path/to/backend-engineer.yaml')).toBe('backend-engineer');
254
+ });
255
+
256
+ it('should extract role from .yml filename', () => {
257
+ expect(getRoleFromFilename('/path/to/frontend-engineer.yml')).toBe('frontend-engineer');
258
+ });
259
+
260
+ it('should handle simple filename', () => {
261
+ expect(getRoleFromFilename('devops.yaml')).toBe('devops');
262
+ });
263
+ });
264
+
265
+ describe('Core Agent Definitions', () => {
266
+ const coreAgentsDir = join(__dirname, '../../agents');
267
+
268
+ it('should load all core agents from agents/ directory', () => {
269
+ const agents = loadAgentsFromDirectory(coreAgentsDir, 'core');
270
+
271
+ expect(agents.size).toBe(4);
272
+ expect(agents.has('backend-engineer')).toBe(true);
273
+ expect(agents.has('frontend-engineer')).toBe(true);
274
+ expect(agents.has('devops-engineer')).toBe(true);
275
+ expect(agents.has('engineering-manager')).toBe(true);
276
+ });
277
+
278
+ it('should validate backend-engineer agent', () => {
279
+ const agent = loadAgentFromFile(join(coreAgentsDir, 'backend-engineer.yaml'));
280
+
281
+ expect(agent.role).toBe('backend-engineer');
282
+ expect(agent.type).toBe('ic-engineer');
283
+ expect(agent.display_name).toBe('Backend Engineer');
284
+ expect(agent.responsibilities).toBeDefined();
285
+ expect(agent.expertise?.primary).toBeDefined();
286
+ expect(agent.behaviors?.workflow).toBe('ticket-implementation');
287
+ });
288
+
289
+ it('should validate frontend-engineer agent', () => {
290
+ const agent = loadAgentFromFile(join(coreAgentsDir, 'frontend-engineer.yaml'));
291
+
292
+ expect(agent.role).toBe('frontend-engineer');
293
+ expect(agent.type).toBe('ic-engineer');
294
+ expect(agent.display_name).toBe('Frontend Engineer');
295
+ expect(agent.responsibilities).toBeDefined();
296
+ expect(agent.behaviors?.workflow).toBe('ticket-implementation');
297
+ });
298
+
299
+ it('should validate devops-engineer agent', () => {
300
+ const agent = loadAgentFromFile(join(coreAgentsDir, 'devops-engineer.yaml'));
301
+
302
+ expect(agent.role).toBe('devops-engineer');
303
+ expect(agent.type).toBe('specialist');
304
+ expect(agent.display_name).toBe('DevOps Engineer');
305
+ expect(agent.responsibilities).toBeDefined();
306
+ expect(agent.behaviors?.workflow).toBe('ticket-implementation');
307
+ });
308
+
309
+ it('should validate engineering-manager agent', () => {
310
+ const agent = loadAgentFromFile(join(coreAgentsDir, 'engineering-manager.yaml'));
311
+
312
+ expect(agent.role).toBe('engineering-manager');
313
+ expect(agent.type).toBe('manager');
314
+ expect(agent.display_name).toBe('Engineering Manager');
315
+ expect(agent.responsibilities).toBeDefined();
316
+ expect(agent.behaviors?.workflow).toBe('planning-estimation');
317
+ });
318
+ });
319
+ });