@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,296 @@
1
+ /**
2
+ * Init Command
3
+ *
4
+ * Initializes a new CoreAI project with configuration file and directory structure.
5
+ */
6
+
7
+ import { existsSync, writeFileSync, mkdirSync, readFileSync } from 'fs';
8
+ import { join, basename } from 'path';
9
+ import { execSync } from 'child_process';
10
+ import type { ProjectType, GitProvider } from '../../config/types.js';
11
+ import { configExists } from '../../config/loader.js';
12
+
13
+ /**
14
+ * Options for init command
15
+ */
16
+ export interface InitCommandOptions {
17
+ /**
18
+ * Project root directory
19
+ */
20
+ projectRoot?: string;
21
+
22
+ /**
23
+ * Overwrite existing configuration
24
+ */
25
+ force?: boolean;
26
+
27
+ /**
28
+ * Skip interactive prompts
29
+ */
30
+ nonInteractive?: boolean;
31
+
32
+ /**
33
+ * Project name (for non-interactive mode)
34
+ */
35
+ name?: string;
36
+
37
+ /**
38
+ * Project type (for non-interactive mode)
39
+ */
40
+ type?: ProjectType;
41
+
42
+ /**
43
+ * Skip creating directory structure
44
+ */
45
+ skipDirs?: boolean;
46
+ }
47
+
48
+ /**
49
+ * Result of init command
50
+ */
51
+ export interface InitCommandResult {
52
+ success: boolean;
53
+ configPath?: string;
54
+ createdDirs?: string[];
55
+ error?: string;
56
+ gitInfo?: {
57
+ provider?: GitProvider;
58
+ owner?: string;
59
+ repo?: string;
60
+ };
61
+ }
62
+
63
+ /**
64
+ * Detect git remote information
65
+ */
66
+ function detectGitInfo(): { provider?: GitProvider; owner?: string; repo?: string } | null {
67
+ try {
68
+ const remoteUrl = execSync('git remote get-url origin', { encoding: 'utf-8' }).trim();
69
+
70
+ // GitHub HTTPS: https://github.com/owner/repo.git
71
+ const httpsMatch = remoteUrl.match(/https:\/\/github\.com\/([^/]+)\/([^/.]+)/);
72
+ if (httpsMatch) {
73
+ return { provider: 'github', owner: httpsMatch[1], repo: httpsMatch[2] };
74
+ }
75
+
76
+ // GitHub SSH: git@github.com:owner/repo.git
77
+ const sshMatch = remoteUrl.match(/git@github\.com:([^/]+)\/([^/.]+)/);
78
+ if (sshMatch) {
79
+ return { provider: 'github', owner: sshMatch[1], repo: sshMatch[2] };
80
+ }
81
+
82
+ // GitLab HTTPS
83
+ const gitlabHttps = remoteUrl.match(/https:\/\/gitlab\.com\/([^/]+)\/([^/.]+)/);
84
+ if (gitlabHttps) {
85
+ return { provider: 'gitlab', owner: gitlabHttps[1], repo: gitlabHttps[2] };
86
+ }
87
+
88
+ // GitLab SSH
89
+ const gitlabSsh = remoteUrl.match(/git@gitlab\.com:([^/]+)\/([^/.]+)/);
90
+ if (gitlabSsh) {
91
+ return { provider: 'gitlab', owner: gitlabSsh[1], repo: gitlabSsh[2] };
92
+ }
93
+
94
+ return null;
95
+ } catch {
96
+ // Not a git repo or no remote
97
+ return null;
98
+ }
99
+ }
100
+
101
+ /**
102
+ * Detect project name from directory or package.json
103
+ */
104
+ function detectProjectName(projectRoot: string): string {
105
+ // Try package.json first
106
+ const packageJsonPath = join(projectRoot, 'package.json');
107
+ if (existsSync(packageJsonPath)) {
108
+ try {
109
+ const content = JSON.parse(readFileSync(packageJsonPath, 'utf-8')) as { name?: string };
110
+ if (content.name) {
111
+ return content.name;
112
+ }
113
+ } catch {
114
+ // Ignore parse errors
115
+ }
116
+ }
117
+
118
+ // Fall back to directory name
119
+ return basename(projectRoot);
120
+ }
121
+
122
+ /**
123
+ * Generate config file content
124
+ */
125
+ function generateConfigYaml(options: {
126
+ name: string;
127
+ type: ProjectType;
128
+ gitInfo?: { provider?: GitProvider; owner?: string; repo?: string } | null;
129
+ }): string {
130
+ let yaml = `# CoreAI Configuration
131
+ # See https://coreai.dev/docs/config for full schema
132
+
133
+ version: "1.0"
134
+
135
+ project:
136
+ name: "${options.name}"
137
+ type: ${options.type}
138
+
139
+ team:
140
+ agents:
141
+ - backend-engineer
142
+ - frontend-engineer
143
+ - devops-engineer
144
+ - engineering-manager
145
+ `;
146
+
147
+ if (options.gitInfo?.provider) {
148
+ yaml += `
149
+ integrations:
150
+ git:
151
+ provider: ${options.gitInfo.provider}
152
+ config:
153
+ owner: "${options.gitInfo.owner}"
154
+ repo: "${options.gitInfo.repo}"
155
+ `;
156
+ }
157
+
158
+ yaml += `
159
+ # quality_gates:
160
+ # lint:
161
+ # command: npm run lint
162
+ # required: true
163
+ # test:
164
+ # command: npm test
165
+ # required: true
166
+ # build:
167
+ # command: npm run build
168
+ # required: true
169
+
170
+ # tech_stack:
171
+ # primary_language: typescript
172
+ # frameworks:
173
+ # - node.js
174
+ # - express
175
+ `;
176
+
177
+ return yaml;
178
+ }
179
+
180
+ /**
181
+ * Create directory structure for CoreAI
182
+ */
183
+ function createDirectories(projectRoot: string): string[] {
184
+ const dirs = [
185
+ join(projectRoot, 'coreai', 'agents'),
186
+ join(projectRoot, 'coreai', 'commands'),
187
+ join(projectRoot, '.coreai', 'cache'),
188
+ ];
189
+
190
+ const created: string[] = [];
191
+
192
+ for (const dir of dirs) {
193
+ if (!existsSync(dir)) {
194
+ mkdirSync(dir, { recursive: true });
195
+ created.push(dir);
196
+ }
197
+ }
198
+
199
+ return created;
200
+ }
201
+
202
+ /**
203
+ * Initialize a new CoreAI project
204
+ */
205
+ export function init(options: InitCommandOptions = {}): InitCommandResult {
206
+ const projectRoot = options.projectRoot ?? process.cwd();
207
+
208
+ // Check if config already exists
209
+ if (configExists(projectRoot) && !options.force) {
210
+ return {
211
+ success: false,
212
+ error: 'CoreAI configuration already exists. Use --force to overwrite.',
213
+ };
214
+ }
215
+
216
+ // Detect git info
217
+ const gitInfo = detectGitInfo();
218
+
219
+ // Determine project name
220
+ const name = options.name ?? detectProjectName(projectRoot);
221
+
222
+ // Determine project type
223
+ const type = options.type ?? 'software';
224
+
225
+ // Generate config
226
+ const configContent = generateConfigYaml({ name, type, gitInfo });
227
+
228
+ // Write config file
229
+ const configPath = join(projectRoot, 'coreai.config.yaml');
230
+ try {
231
+ writeFileSync(configPath, configContent, 'utf-8');
232
+ } catch (error) {
233
+ return {
234
+ success: false,
235
+ error: `Failed to write config file: ${error instanceof Error ? error.message : String(error)}`,
236
+ };
237
+ }
238
+
239
+ // Create directories unless skipped
240
+ let createdDirs: string[] = [];
241
+ if (!options.skipDirs) {
242
+ try {
243
+ createdDirs = createDirectories(projectRoot);
244
+ } catch (error) {
245
+ return {
246
+ success: false,
247
+ configPath,
248
+ error: `Failed to create directories: ${error instanceof Error ? error.message : String(error)}`,
249
+ };
250
+ }
251
+ }
252
+
253
+ return {
254
+ success: true,
255
+ configPath,
256
+ createdDirs,
257
+ gitInfo: gitInfo ?? undefined,
258
+ };
259
+ }
260
+
261
+ /**
262
+ * Format init result for display
263
+ */
264
+ export function formatInitResult(result: InitCommandResult): string {
265
+ if (!result.success) {
266
+ return `Error: ${result.error}`;
267
+ }
268
+
269
+ const lines: string[] = [];
270
+
271
+ lines.push('CoreAI project initialized successfully!\n');
272
+
273
+ if (result.configPath) {
274
+ lines.push(`Created: ${result.configPath}`);
275
+ }
276
+
277
+ if (result.createdDirs && result.createdDirs.length > 0) {
278
+ lines.push('\nCreated directories:');
279
+ for (const dir of result.createdDirs) {
280
+ lines.push(` - ${dir}`);
281
+ }
282
+ }
283
+
284
+ if (result.gitInfo?.provider) {
285
+ lines.push(
286
+ `\nDetected ${result.gitInfo.provider} repository: ${result.gitInfo.owner}/${result.gitInfo.repo}`
287
+ );
288
+ }
289
+
290
+ lines.push('\nNext steps:');
291
+ lines.push(' 1. Edit coreai.config.yaml to configure your project');
292
+ lines.push(' 2. Run `coreai build` to compile agents');
293
+ lines.push(' 3. Run `coreai validate` to check your setup');
294
+
295
+ return lines.join('\n');
296
+ }
@@ -0,0 +1,272 @@
1
+ /**
2
+ * Skills Command Tests
3
+ */
4
+
5
+ import { promises as fs } from 'fs';
6
+ import { join } from 'path';
7
+ import { tmpdir } from 'os';
8
+ import {
9
+ skillsGenerate,
10
+ formatSkillsGenerateResult,
11
+ skillsList,
12
+ formatSkillsListResult,
13
+ } from './skills.js';
14
+
15
+ describe('Skills Command', () => {
16
+ let testDir: string;
17
+
18
+ beforeEach(async () => {
19
+ testDir = join(
20
+ tmpdir(),
21
+ `skills-cmd-test-${Date.now()}-${Math.random().toString(36).slice(2)}`
22
+ );
23
+ await fs.mkdir(testDir, { recursive: true });
24
+ });
25
+
26
+ afterEach(async () => {
27
+ try {
28
+ await fs.rm(testDir, { recursive: true, force: true });
29
+ } catch {
30
+ // Ignore cleanup errors
31
+ }
32
+ });
33
+
34
+ describe('skillsGenerate', () => {
35
+ it('should generate skills with defaults', () => {
36
+ const result = skillsGenerate({
37
+ projectRoot: testDir,
38
+ });
39
+
40
+ expect(result.success).toBe(true);
41
+ expect(result.result).toBeDefined();
42
+ expect(result.result?.generated.length).toBeGreaterThan(0);
43
+ });
44
+
45
+ it('should generate skills without config', () => {
46
+ const result = skillsGenerate({
47
+ projectRoot: testDir,
48
+ });
49
+
50
+ expect(result.success).toBe(true);
51
+ expect(result.warnings).toContain(
52
+ 'No configuration file found. Generating skills with defaults.'
53
+ );
54
+ });
55
+
56
+ it('should generate only core skills', () => {
57
+ const result = skillsGenerate({
58
+ projectRoot: testDir,
59
+ includeCoreSkills: true,
60
+ includeOptionalSkills: false,
61
+ });
62
+
63
+ expect(result.success).toBe(true);
64
+ const generated = result.result?.generated ?? [];
65
+ expect(generated.every((g) => g.category === 'core')).toBe(true);
66
+ });
67
+
68
+ it('should generate specific skills', () => {
69
+ const result = skillsGenerate({
70
+ projectRoot: testDir,
71
+ skills: ['check-inbox', 'delegate'],
72
+ });
73
+
74
+ expect(result.success).toBe(true);
75
+ expect(result.result?.generated).toHaveLength(2);
76
+ });
77
+
78
+ it('should fail with invalid config', async () => {
79
+ // Create invalid config
80
+ await fs.writeFile(join(testDir, 'coreai.config.yaml'), 'invalid: yaml: syntax:::');
81
+
82
+ const result = skillsGenerate({
83
+ projectRoot: testDir,
84
+ });
85
+
86
+ expect(result.success).toBe(false);
87
+ expect(result.error).toContain('Configuration error');
88
+ });
89
+
90
+ it('should use custom output directory', async () => {
91
+ const customOutputDir = join(testDir, 'custom', 'skills');
92
+
93
+ const result = skillsGenerate({
94
+ projectRoot: testDir,
95
+ outputDir: customOutputDir,
96
+ skills: ['check-inbox'],
97
+ });
98
+
99
+ expect(result.success).toBe(true);
100
+
101
+ // Verify file created in custom directory
102
+ const stat = await fs.stat(join(customOutputDir, 'check-inbox.md'));
103
+ expect(stat.isFile()).toBe(true);
104
+ });
105
+
106
+ it('should pass custom variables', () => {
107
+ const result = skillsGenerate({
108
+ projectRoot: testDir,
109
+ skills: ['check-inbox'],
110
+ variables: {
111
+ CUSTOM_VAR: 'custom-value',
112
+ },
113
+ });
114
+
115
+ expect(result.success).toBe(true);
116
+ expect(result.result?.variables.CUSTOM_VAR).toBe('custom-value');
117
+ });
118
+ });
119
+
120
+ describe('formatSkillsGenerateResult', () => {
121
+ it('should format success result', () => {
122
+ const result = skillsGenerate({
123
+ projectRoot: testDir,
124
+ skills: ['check-inbox'],
125
+ });
126
+
127
+ const output = formatSkillsGenerateResult(result);
128
+ expect(output).toContain('Created');
129
+ expect(output).toContain('check-inbox');
130
+ });
131
+
132
+ it('should format error result', () => {
133
+ const result = {
134
+ success: false,
135
+ error: 'Something went wrong',
136
+ };
137
+
138
+ const output = formatSkillsGenerateResult(result);
139
+ expect(output).toContain('failed');
140
+ expect(output).toContain('Something went wrong');
141
+ });
142
+
143
+ it('should include warnings', () => {
144
+ const result = skillsGenerate({
145
+ projectRoot: testDir,
146
+ skills: ['check-inbox'],
147
+ });
148
+
149
+ const output = formatSkillsGenerateResult(result);
150
+ // Should show warning about missing config
151
+ expect(output.includes('warning') || output.includes('No configuration')).toBe(false);
152
+ // This should have the created message
153
+ expect(output).toContain('Created');
154
+ });
155
+ });
156
+
157
+ describe('skillsList', () => {
158
+ it('should list all built-in skills', () => {
159
+ const result = skillsList();
160
+
161
+ expect(result.success).toBe(true);
162
+ expect(result.skills).toBeDefined();
163
+ expect(result.skills?.length).toBeGreaterThan(0);
164
+ });
165
+
166
+ it('should list only core skills', () => {
167
+ const result = skillsList({
168
+ includeCoreSkills: true,
169
+ includeOptionalSkills: false,
170
+ });
171
+
172
+ expect(result.success).toBe(true);
173
+ expect(result.skills?.every((s) => s.category === 'core')).toBe(true);
174
+ });
175
+
176
+ it('should list only optional skills', () => {
177
+ const result = skillsList({
178
+ includeCoreSkills: false,
179
+ includeOptionalSkills: true,
180
+ });
181
+
182
+ expect(result.success).toBe(true);
183
+ expect(result.skills?.every((s) => s.category === 'optional')).toBe(true);
184
+ });
185
+
186
+ it('should include skill metadata', () => {
187
+ const result = skillsList();
188
+
189
+ expect(result.success).toBe(true);
190
+ const skill = result.skills?.[0];
191
+ expect(skill?.name).toBeDefined();
192
+ expect(skill?.description).toBeDefined();
193
+ expect(skill?.category).toBeDefined();
194
+ });
195
+
196
+ it('should load custom templates when directory provided', async () => {
197
+ const customDir = join(testDir, 'templates');
198
+ await fs.mkdir(customDir, { recursive: true });
199
+ await fs.writeFile(
200
+ join(customDir, 'custom-skill.md'),
201
+ '---\ndescription: My custom skill\n---\nContent'
202
+ );
203
+
204
+ const result = skillsList({
205
+ customTemplatesDir: customDir,
206
+ });
207
+
208
+ expect(result.success).toBe(true);
209
+ const customSkill = result.skills?.find((s) => s.name === 'custom-skill');
210
+ expect(customSkill).toBeDefined();
211
+ expect(customSkill?.description).toBe('My custom skill');
212
+ });
213
+ });
214
+
215
+ describe('formatSkillsListResult', () => {
216
+ it('should format skills list', () => {
217
+ const result = skillsList();
218
+ const output = formatSkillsListResult(result);
219
+
220
+ expect(output).toContain('Available skills');
221
+ expect(output).toContain('Core skills');
222
+ });
223
+
224
+ it('should format error result', () => {
225
+ const result = {
226
+ success: false,
227
+ error: 'Failed to list skills',
228
+ };
229
+
230
+ const output = formatSkillsListResult(result);
231
+ expect(output).toContain('Error');
232
+ expect(output).toContain('Failed to list skills');
233
+ });
234
+
235
+ it('should show no skills message', () => {
236
+ const result = {
237
+ success: true,
238
+ skills: [],
239
+ };
240
+
241
+ const output = formatSkillsListResult(result);
242
+ expect(output).toContain('No skills available');
243
+ });
244
+
245
+ it('should group skills by category', () => {
246
+ const result = skillsList();
247
+ const output = formatSkillsListResult(result);
248
+
249
+ // Should have at least core skills section
250
+ expect(output).toContain('Core skills');
251
+ });
252
+
253
+ it('should show argument hints', () => {
254
+ const result = skillsList();
255
+ const output = formatSkillsListResult(result);
256
+
257
+ // Many skills have argument hints
258
+ expect(output).toContain('Argument:');
259
+ });
260
+
261
+ it('should show dependencies for optional skills', () => {
262
+ const result = skillsList({
263
+ includeCoreSkills: false,
264
+ includeOptionalSkills: true,
265
+ });
266
+
267
+ const output = formatSkillsListResult(result);
268
+ // Optional skills have dependencies
269
+ expect(output).toContain('requires:');
270
+ });
271
+ });
272
+ });