@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.
- package/.prettierrc +9 -0
- package/AGENT_SPEC.md +347 -0
- package/ARCHITECTURE.md +547 -0
- package/DRAFT_PRD.md +1440 -0
- package/IMPLEMENTATION_PLAN.md +256 -0
- package/PRODUCT.md +473 -0
- package/README.md +303 -0
- package/WORKFLOWS.md +295 -0
- package/agents/_templates/ic-engineer.md +185 -0
- package/agents/_templates/reviewer.md +182 -0
- package/agents/backend-engineer.yaml +72 -0
- package/agents/devops-engineer.yaml +72 -0
- package/agents/engineering-manager.yaml +70 -0
- package/agents/examples/android-engineer.md +302 -0
- package/agents/examples/backend-engineer.md +320 -0
- package/agents/examples/devops-engineer.md +742 -0
- package/agents/examples/engineering-manager.md +469 -0
- package/agents/examples/frontend-engineer.md +58 -0
- package/agents/examples/product-manager.md +315 -0
- package/agents/examples/qa-engineer.md +371 -0
- package/agents/examples/security-engineer.md +525 -0
- package/agents/examples/solutions-architect.md +351 -0
- package/agents/examples/wearos-engineer.md +359 -0
- package/agents/frontend-engineer.yaml +72 -0
- package/commands/core/check-inbox.md +34 -0
- package/commands/core/delegate.md +30 -0
- package/commands/core/git-commit.md +144 -0
- package/commands/core/pr-create.md +193 -0
- package/commands/core/review.md +56 -0
- package/commands/core/sprint-status.md +65 -0
- package/commands/optional/docs-update.md +200 -0
- package/commands/optional/jira-create.md +200 -0
- package/commands/optional/jira-transition.md +184 -0
- package/commands/optional/worktree-cleanup.md +167 -0
- package/commands/optional/worktree-setup.md +110 -0
- package/dist/cli/index.js +4037 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/index.d.ts +2978 -0
- package/dist/index.js +3867 -0
- package/dist/index.js.map +1 -0
- package/eslint.config.js +29 -0
- package/jest.config.js +22 -0
- package/knowledge-library/README.md +118 -0
- package/knowledge-library/android-engineer/context/current.txt +42 -0
- package/knowledge-library/android-engineer/control/decisions.txt +9 -0
- package/knowledge-library/android-engineer/control/dependencies.txt +19 -0
- package/knowledge-library/android-engineer/control/objectives.txt +26 -0
- package/knowledge-library/android-engineer/history/.gitkeep +0 -0
- package/knowledge-library/android-engineer/inbox/processed/.gitkeep +0 -0
- package/knowledge-library/android-engineer/outbox/.gitkeep +0 -0
- package/knowledge-library/android-engineer/tech/.gitkeep +0 -0
- package/knowledge-library/architecture.txt +61 -0
- package/knowledge-library/backend-engineer/context/current.txt +42 -0
- package/knowledge-library/backend-engineer/control/decisions.txt +9 -0
- package/knowledge-library/backend-engineer/control/dependencies.txt +19 -0
- package/knowledge-library/backend-engineer/control/objectives.txt +26 -0
- package/knowledge-library/backend-engineer/history/.gitkeep +0 -0
- package/knowledge-library/backend-engineer/inbox/processed/.gitkeep +0 -0
- package/knowledge-library/backend-engineer/outbox/.gitkeep +0 -0
- package/knowledge-library/backend-engineer/tech/.gitkeep +0 -0
- package/knowledge-library/context.txt +52 -0
- package/knowledge-library/devops-engineer/context/current.txt +42 -0
- package/knowledge-library/devops-engineer/control/decisions.txt +9 -0
- package/knowledge-library/devops-engineer/control/dependencies.txt +19 -0
- package/knowledge-library/devops-engineer/control/objectives.txt +26 -0
- package/knowledge-library/devops-engineer/history/.gitkeep +0 -0
- package/knowledge-library/devops-engineer/inbox/processed/.gitkeep +0 -0
- package/knowledge-library/devops-engineer/outbox/.gitkeep +0 -0
- package/knowledge-library/devops-engineer/tech/.gitkeep +0 -0
- package/knowledge-library/engineering-manager/context/current.txt +40 -0
- package/knowledge-library/engineering-manager/control/decisions.txt +9 -0
- package/knowledge-library/engineering-manager/control/objectives.txt +27 -0
- package/knowledge-library/engineering-manager/history/.gitkeep +0 -0
- package/knowledge-library/engineering-manager/inbox/processed/.gitkeep +0 -0
- package/knowledge-library/engineering-manager/outbox/.gitkeep +0 -0
- package/knowledge-library/engineering-manager/tech/.gitkeep +0 -0
- package/knowledge-library/prd.txt +81 -0
- package/knowledge-library/product-manager/context/current.txt +42 -0
- package/knowledge-library/product-manager/control/decisions.txt +9 -0
- package/knowledge-library/product-manager/control/dependencies.txt +19 -0
- package/knowledge-library/product-manager/control/objectives.txt +26 -0
- package/knowledge-library/product-manager/history/.gitkeep +0 -0
- package/knowledge-library/product-manager/inbox/processed/.gitkeep +0 -0
- package/knowledge-library/product-manager/outbox/.gitkeep +0 -0
- package/knowledge-library/product-manager/tech/.gitkeep +0 -0
- package/knowledge-library/qa-engineer/context/current.txt +42 -0
- package/knowledge-library/qa-engineer/control/decisions.txt +9 -0
- package/knowledge-library/qa-engineer/control/dependencies.txt +19 -0
- package/knowledge-library/qa-engineer/control/objectives.txt +26 -0
- package/knowledge-library/qa-engineer/history/.gitkeep +0 -0
- package/knowledge-library/qa-engineer/inbox/processed/.gitkeep +0 -0
- package/knowledge-library/qa-engineer/outbox/.gitkeep +0 -0
- package/knowledge-library/qa-engineer/tech/.gitkeep +0 -0
- package/knowledge-library/security-engineer/context/current.txt +42 -0
- package/knowledge-library/security-engineer/control/decisions.txt +9 -0
- package/knowledge-library/security-engineer/control/dependencies.txt +19 -0
- package/knowledge-library/security-engineer/control/objectives.txt +26 -0
- package/knowledge-library/security-engineer/history/.gitkeep +0 -0
- package/knowledge-library/security-engineer/inbox/processed/.gitkeep +0 -0
- package/knowledge-library/security-engineer/outbox/.gitkeep +0 -0
- package/knowledge-library/security-engineer/tech/.gitkeep +0 -0
- package/knowledge-library/solutions-architect/context/current.txt +42 -0
- package/knowledge-library/solutions-architect/control/decisions.txt +9 -0
- package/knowledge-library/solutions-architect/control/dependencies.txt +19 -0
- package/knowledge-library/solutions-architect/control/objectives.txt +26 -0
- package/knowledge-library/solutions-architect/history/.gitkeep +0 -0
- package/knowledge-library/solutions-architect/inbox/processed/.gitkeep +0 -0
- package/knowledge-library/solutions-architect/outbox/.gitkeep +0 -0
- package/knowledge-library/solutions-architect/tech/.gitkeep +0 -0
- package/knowledge-library/wearos-engineer/context/current.txt +42 -0
- package/knowledge-library/wearos-engineer/control/decisions.txt +9 -0
- package/knowledge-library/wearos-engineer/control/dependencies.txt +19 -0
- package/knowledge-library/wearos-engineer/control/objectives.txt +26 -0
- package/knowledge-library/wearos-engineer/history/.gitkeep +0 -0
- package/knowledge-library/wearos-engineer/inbox/processed/.gitkeep +0 -0
- package/knowledge-library/wearos-engineer/outbox/.gitkeep +0 -0
- package/knowledge-library/wearos-engineer/tech/.gitkeep +0 -0
- package/package.json +66 -0
- package/schemas/agent.schema.json +171 -0
- package/schemas/coreai.config.schema.json +257 -0
- package/scripts/add-agent.sh +323 -0
- package/scripts/install.sh +354 -0
- package/src/adapters/factory.test.ts +386 -0
- package/src/adapters/factory.ts +305 -0
- package/src/adapters/index.ts +113 -0
- package/src/adapters/interfaces.ts +268 -0
- package/src/adapters/mcp/client.test.ts +130 -0
- package/src/adapters/mcp/client.ts +451 -0
- package/src/adapters/mcp/discovery.test.ts +315 -0
- package/src/adapters/mcp/discovery.ts +340 -0
- package/src/adapters/mcp/index.ts +66 -0
- package/src/adapters/mcp/mapper.test.ts +218 -0
- package/src/adapters/mcp/mapper.ts +536 -0
- package/src/adapters/mcp/registry.test.ts +433 -0
- package/src/adapters/mcp/registry.ts +550 -0
- package/src/adapters/mcp/types.ts +258 -0
- package/src/adapters/native/filesystem.test.ts +350 -0
- package/src/adapters/native/filesystem.ts +393 -0
- package/src/adapters/native/github.test.ts +173 -0
- package/src/adapters/native/github.ts +627 -0
- package/src/adapters/native/index.ts +22 -0
- package/src/adapters/native/selector.test.ts +224 -0
- package/src/adapters/native/selector.ts +150 -0
- package/src/adapters/types.ts +270 -0
- package/src/agents/compiler.test.ts +399 -0
- package/src/agents/compiler.ts +359 -0
- package/src/agents/index.ts +36 -0
- package/src/agents/loader.test.ts +319 -0
- package/src/agents/loader.ts +143 -0
- package/src/agents/resolver.test.ts +282 -0
- package/src/agents/resolver.ts +262 -0
- package/src/agents/types.ts +87 -0
- package/src/cache/index.ts +38 -0
- package/src/cache/interfaces.ts +283 -0
- package/src/cache/manager.test.ts +266 -0
- package/src/cache/manager.ts +388 -0
- package/src/cache/provider.test.ts +485 -0
- package/src/cache/provider.ts +745 -0
- package/src/cache/types.test.ts +192 -0
- package/src/cache/types.ts +313 -0
- package/src/cli/commands/build.test.ts +248 -0
- package/src/cli/commands/build.ts +244 -0
- package/src/cli/commands/cache.test.ts +221 -0
- package/src/cli/commands/cache.ts +229 -0
- package/src/cli/commands/index.ts +63 -0
- package/src/cli/commands/init.test.ts +173 -0
- package/src/cli/commands/init.ts +296 -0
- package/src/cli/commands/skills.test.ts +272 -0
- package/src/cli/commands/skills.ts +348 -0
- package/src/cli/commands/status.test.ts +392 -0
- package/src/cli/commands/status.ts +332 -0
- package/src/cli/commands/sync.test.ts +213 -0
- package/src/cli/commands/sync.ts +251 -0
- package/src/cli/commands/validate.test.ts +216 -0
- package/src/cli/commands/validate.ts +340 -0
- package/src/cli/index.test.ts +190 -0
- package/src/cli/index.ts +493 -0
- package/src/commands/context.test.ts +163 -0
- package/src/commands/context.ts +111 -0
- package/src/commands/index.ts +56 -0
- package/src/commands/loader.test.ts +273 -0
- package/src/commands/loader.ts +355 -0
- package/src/commands/registry.test.ts +384 -0
- package/src/commands/registry.ts +248 -0
- package/src/commands/runner.test.ts +297 -0
- package/src/commands/runner.ts +222 -0
- package/src/commands/types.ts +361 -0
- package/src/config/index.ts +19 -0
- package/src/config/loader.test.ts +262 -0
- package/src/config/loader.ts +188 -0
- package/src/config/types.ts +154 -0
- package/src/context/index.ts +14 -0
- package/src/context/loader.test.ts +334 -0
- package/src/context/loader.ts +357 -0
- package/src/index.test.ts +13 -0
- package/src/index.ts +244 -0
- package/src/knowledge-library/index.ts +44 -0
- package/src/knowledge-library/manager.test.ts +536 -0
- package/src/knowledge-library/manager.ts +804 -0
- package/src/knowledge-library/types.ts +432 -0
- package/src/skills/generator.test.ts +602 -0
- package/src/skills/generator.ts +491 -0
- package/src/skills/index.ts +27 -0
- package/src/skills/templates.ts +520 -0
- package/src/skills/types.ts +251 -0
- package/templates/completion-report.md +72 -0
- package/templates/feedback.md +56 -0
- package/templates/project-files/CLAUDE.md.template +109 -0
- package/templates/project-files/coreai.json.example +47 -0
- package/templates/project-files/mcp.json.template +20 -0
- package/templates/review-complete.md +64 -0
- package/templates/review-request.md +67 -0
- package/templates/task-assignment.md +51 -0
- package/tsconfig.build.json +4 -0
- package/tsconfig.json +26 -0
- 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
|
+
});
|