@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,491 @@
1
+ /**
2
+ * Skill Generator
3
+ *
4
+ * Generates Claude skills from templates based on project configuration.
5
+ * Skills are output to .claude/commands/ directory.
6
+ */
7
+
8
+ import { existsSync, mkdirSync, writeFileSync, readFileSync, readdirSync, statSync } from 'fs';
9
+ import { join, basename } from 'path';
10
+ import type { ResolvedCoreAIConfig } from '../config/types.js';
11
+ import type {
12
+ SkillTemplate,
13
+ SkillVariables,
14
+ GenerateSkillsOptions,
15
+ GenerateSkillsResult,
16
+ GeneratedSkill,
17
+ SkillDependency,
18
+ } from './types.js';
19
+ import { builtInSkills } from './templates.js';
20
+
21
+ /**
22
+ * Extract variables from config for template substitution
23
+ */
24
+ export function extractVariables(config?: ResolvedCoreAIConfig | null): SkillVariables {
25
+ const vars: SkillVariables = {};
26
+
27
+ if (!config) {
28
+ return vars;
29
+ }
30
+
31
+ // Project variables
32
+ vars.PROJECT_NAME = config.project.name;
33
+ vars.PROJECT_ROOT = config.project.root;
34
+ if (config.project.type) {
35
+ vars.PROJECT_TYPE = config.project.type;
36
+ }
37
+
38
+ // Issue tracker variables
39
+ if (config.integrations?.issue_tracker) {
40
+ const tracker = config.integrations.issue_tracker;
41
+ if (tracker.config?.project_key) {
42
+ vars.JIRA_PROJECT = tracker.config.project_key;
43
+ }
44
+ if (tracker.config?.base_url) {
45
+ vars.JIRA_URL = tracker.config.base_url;
46
+ }
47
+ }
48
+
49
+ // Git variables
50
+ if (config.integrations?.git) {
51
+ const git = config.integrations.git;
52
+ if (git.config?.repo) {
53
+ vars.GITHUB_REPO = git.config.repo;
54
+ }
55
+ if (git.config?.owner) {
56
+ vars.GITHUB_OWNER = git.config.owner;
57
+ }
58
+ if (git.config?.default_branch) {
59
+ vars.DEFAULT_BRANCH = git.config.default_branch;
60
+ }
61
+ }
62
+
63
+ // Documentation variables
64
+ if (config.integrations?.documentation) {
65
+ const docs = config.integrations.documentation;
66
+ if (docs.config?.space_key) {
67
+ vars.CONFLUENCE_SPACE = docs.config.space_key;
68
+ }
69
+ if (docs.config?.base_url) {
70
+ vars.CONFLUENCE_URL = docs.config.base_url;
71
+ }
72
+ if (docs.config?.base_path) {
73
+ vars.DOCS_PATH = docs.config.base_path;
74
+ }
75
+ }
76
+
77
+ // Quality gate commands
78
+ if (config.quality_gates) {
79
+ const gates = config.quality_gates;
80
+ // Map common gate names to variables
81
+ const gateMappings: Record<string, keyof SkillVariables> = {
82
+ lint: 'LINT_CMD',
83
+ test: 'TEST_CMD',
84
+ build: 'BUILD_CMD',
85
+ static_analysis: 'STATIC_ANALYSIS_CMD',
86
+ staticAnalysis: 'STATIC_ANALYSIS_CMD',
87
+ };
88
+
89
+ for (const [gateName, gate] of Object.entries(gates)) {
90
+ const varName = gateMappings[gateName];
91
+ if (varName) {
92
+ vars[varName] = gate.command;
93
+ }
94
+ }
95
+ }
96
+
97
+ // Tech stack
98
+ if (config.tech_stack?.primary_language) {
99
+ vars.PRIMARY_LANGUAGE = config.tech_stack.primary_language;
100
+ }
101
+
102
+ return vars;
103
+ }
104
+
105
+ /**
106
+ * Substitute variables in template content
107
+ */
108
+ export function substituteVariables(content: string, variables: SkillVariables): string {
109
+ let result = content;
110
+
111
+ for (const [key, value] of Object.entries(variables)) {
112
+ if (value !== undefined) {
113
+ // Replace {{VARIABLE}} pattern
114
+ const pattern = new RegExp(`\\{\\{${key}\\}\\}`, 'g');
115
+ result = result.replace(pattern, value);
116
+ }
117
+ }
118
+
119
+ return result;
120
+ }
121
+
122
+ /**
123
+ * Check if a skill's dependencies are satisfied
124
+ */
125
+ export function checkDependencies(
126
+ skill: SkillTemplate,
127
+ config?: ResolvedCoreAIConfig | null
128
+ ): { satisfied: boolean; missing: SkillDependency[] } {
129
+ if (!skill.dependencies || skill.dependencies.length === 0) {
130
+ return { satisfied: true, missing: [] };
131
+ }
132
+
133
+ const missing: SkillDependency[] = [];
134
+
135
+ for (const dep of skill.dependencies) {
136
+ if (!dep.required) {
137
+ // Optional dependencies don't block skill generation
138
+ continue;
139
+ }
140
+
141
+ // Check if the integration is configured
142
+ let hasIntegration = false;
143
+
144
+ if (config?.integrations) {
145
+ switch (dep.type) {
146
+ case 'issue_tracker':
147
+ hasIntegration = !!config.integrations.issue_tracker;
148
+ break;
149
+ case 'git':
150
+ hasIntegration = !!config.integrations.git;
151
+ break;
152
+ case 'documentation':
153
+ hasIntegration = !!config.integrations.documentation;
154
+ break;
155
+ case 'state':
156
+ hasIntegration = !!config.integrations.state;
157
+ break;
158
+ }
159
+ }
160
+
161
+ if (!hasIntegration) {
162
+ missing.push(dep);
163
+ }
164
+ }
165
+
166
+ return {
167
+ satisfied: missing.length === 0,
168
+ missing,
169
+ };
170
+ }
171
+
172
+ /**
173
+ * Load custom skill templates from a directory
174
+ */
175
+ export function loadCustomTemplates(templatesDir: string): SkillTemplate[] {
176
+ const templates: SkillTemplate[] = [];
177
+
178
+ if (!existsSync(templatesDir)) {
179
+ return templates;
180
+ }
181
+
182
+ const files = readdirSync(templatesDir);
183
+
184
+ for (const file of files) {
185
+ if (!file.endsWith('.md')) continue;
186
+
187
+ const filePath = join(templatesDir, file);
188
+ const stat = statSync(filePath);
189
+ if (!stat.isFile()) continue;
190
+
191
+ try {
192
+ const content = readFileSync(filePath, 'utf-8');
193
+ const template = parseSkillTemplate(file, content);
194
+ templates.push(template);
195
+ } catch {
196
+ // Skip invalid templates
197
+ }
198
+ }
199
+
200
+ return templates;
201
+ }
202
+
203
+ /**
204
+ * Parse a skill template from markdown content
205
+ */
206
+ function parseSkillTemplate(filename: string, content: string): SkillTemplate {
207
+ const name = basename(filename, '.md');
208
+
209
+ // Parse frontmatter
210
+ const frontmatterMatch = content.match(/^---\r?\n([\s\S]*?)\r?\n---\r?\n([\s\S]*)$/);
211
+
212
+ let description = name;
213
+ let argumentHint: string | undefined;
214
+ const dependencies: SkillDependency[] = [];
215
+
216
+ if (frontmatterMatch) {
217
+ const frontmatter = frontmatterMatch[1] ?? '';
218
+ const lines = frontmatter.split(/\r?\n/);
219
+
220
+ for (const line of lines) {
221
+ const colonIndex = line.indexOf(':');
222
+ if (colonIndex === -1) continue;
223
+
224
+ const key = line.slice(0, colonIndex).trim();
225
+ let value = line.slice(colonIndex + 1).trim();
226
+
227
+ // Remove quotes
228
+ if (
229
+ (value.startsWith('"') && value.endsWith('"')) ||
230
+ (value.startsWith("'") && value.endsWith("'"))
231
+ ) {
232
+ value = value.slice(1, -1);
233
+ }
234
+
235
+ switch (key) {
236
+ case 'description':
237
+ description = value;
238
+ break;
239
+ case 'argument-hint':
240
+ argumentHint = value;
241
+ break;
242
+ case 'requires':
243
+ if (value.startsWith('[') && value.endsWith(']')) {
244
+ const items = value
245
+ .slice(1, -1)
246
+ .split(',')
247
+ .map((s) => s.trim().replace(/['"]/g, ''));
248
+ for (const item of items) {
249
+ if (item) {
250
+ dependencies.push({
251
+ type: mapIntegrationName(item),
252
+ required: true,
253
+ });
254
+ }
255
+ }
256
+ }
257
+ break;
258
+ case 'optional':
259
+ if (value.startsWith('[') && value.endsWith(']')) {
260
+ const items = value
261
+ .slice(1, -1)
262
+ .split(',')
263
+ .map((s) => s.trim().replace(/['"]/g, ''));
264
+ for (const item of items) {
265
+ if (item) {
266
+ dependencies.push({
267
+ type: mapIntegrationName(item),
268
+ required: false,
269
+ });
270
+ }
271
+ }
272
+ }
273
+ break;
274
+ }
275
+ }
276
+ }
277
+
278
+ const template: SkillTemplate = {
279
+ name,
280
+ description,
281
+ category: 'custom',
282
+ content,
283
+ };
284
+
285
+ if (argumentHint) {
286
+ template.argumentHint = argumentHint;
287
+ }
288
+
289
+ if (dependencies.length > 0) {
290
+ template.dependencies = dependencies;
291
+ }
292
+
293
+ return template;
294
+ }
295
+
296
+ /**
297
+ * Map integration name to adapter type
298
+ */
299
+ function mapIntegrationName(name: string): 'issue_tracker' | 'git' | 'documentation' | 'state' {
300
+ const lower = name.toLowerCase();
301
+
302
+ if (['jira', 'linear', 'issue_tracker', 'issues', 'github-issues'].includes(lower)) {
303
+ return 'issue_tracker';
304
+ }
305
+ if (['git', 'github', 'gitlab', 'bitbucket'].includes(lower)) {
306
+ return 'git';
307
+ }
308
+ if (['docs', 'documentation', 'confluence', 'notion', 'wiki'].includes(lower)) {
309
+ return 'documentation';
310
+ }
311
+
312
+ return 'state';
313
+ }
314
+
315
+ /**
316
+ * Generate skills from templates
317
+ */
318
+ export function generateSkills(
319
+ config: ResolvedCoreAIConfig | undefined,
320
+ options: GenerateSkillsOptions = {}
321
+ ): GenerateSkillsResult {
322
+ const projectRoot = options.projectRoot ?? process.cwd();
323
+ const outputDir = options.outputDir ?? join(projectRoot, '.claude', 'commands');
324
+ const includeCoreSkills = options.includeCoreSkills ?? true;
325
+ const includeOptionalSkills = options.includeOptionalSkills ?? true;
326
+ const overwrite = options.overwrite ?? false;
327
+
328
+ const result: GenerateSkillsResult = {
329
+ generated: [],
330
+ errors: [],
331
+ variables: {},
332
+ };
333
+
334
+ // Extract variables from config
335
+ const variables = extractVariables(config);
336
+
337
+ // Merge custom variables
338
+ if (options.variables) {
339
+ Object.assign(variables, options.variables);
340
+ }
341
+
342
+ result.variables = variables;
343
+
344
+ // Collect templates to generate
345
+ let templates: SkillTemplate[] = [];
346
+
347
+ // Add built-in skills
348
+ if (includeCoreSkills) {
349
+ templates.push(...builtInSkills.filter((s) => s.category === 'core'));
350
+ }
351
+ if (includeOptionalSkills) {
352
+ templates.push(...builtInSkills.filter((s) => s.category === 'optional'));
353
+ }
354
+
355
+ // Add custom templates
356
+ if (options.customTemplatesDir && existsSync(options.customTemplatesDir)) {
357
+ templates.push(...loadCustomTemplates(options.customTemplatesDir));
358
+ }
359
+
360
+ // Filter to specific skills if requested
361
+ if (options.skills && options.skills.length > 0) {
362
+ const skillsToInclude = options.skills;
363
+ templates = templates.filter((t) => skillsToInclude.includes(t.name));
364
+ }
365
+
366
+ // Ensure output directory exists
367
+ if (!existsSync(outputDir)) {
368
+ mkdirSync(outputDir, { recursive: true });
369
+ }
370
+
371
+ // Generate each skill
372
+ for (const template of templates) {
373
+ try {
374
+ const generated = generateSkill(template, variables, config, outputDir, overwrite);
375
+ result.generated.push(generated);
376
+ } catch (error) {
377
+ result.errors.push({
378
+ name: template.name,
379
+ error: error instanceof Error ? error.message : String(error),
380
+ });
381
+ }
382
+ }
383
+
384
+ return result;
385
+ }
386
+
387
+ /**
388
+ * Generate a single skill from a template
389
+ */
390
+ function generateSkill(
391
+ template: SkillTemplate,
392
+ variables: SkillVariables,
393
+ config: ResolvedCoreAIConfig | undefined,
394
+ outputDir: string,
395
+ overwrite: boolean
396
+ ): GeneratedSkill {
397
+ const outputPath = join(outputDir, `${template.name}.md`);
398
+
399
+ // Check if file exists and we shouldn't overwrite
400
+ if (existsSync(outputPath) && !overwrite) {
401
+ return {
402
+ name: template.name,
403
+ category: template.category,
404
+ outputPath,
405
+ action: 'skipped',
406
+ skipReason: 'File already exists',
407
+ };
408
+ }
409
+
410
+ // Check dependencies
411
+ const { satisfied, missing } = checkDependencies(template, config);
412
+
413
+ if (!satisfied) {
414
+ const missingTypes = missing.map((d) => d.type).join(', ');
415
+ return {
416
+ name: template.name,
417
+ category: template.category,
418
+ outputPath,
419
+ action: 'skipped',
420
+ skipReason: `Missing required integrations: ${missingTypes}`,
421
+ };
422
+ }
423
+
424
+ // Substitute variables in content
425
+ const content = substituteVariables(template.content, variables);
426
+
427
+ // Check if content changed (for update vs create)
428
+ const isUpdate = existsSync(outputPath);
429
+
430
+ // Write the skill file
431
+ writeFileSync(outputPath, content, 'utf-8');
432
+
433
+ return {
434
+ name: template.name,
435
+ category: template.category,
436
+ outputPath,
437
+ action: isUpdate ? 'updated' : 'created',
438
+ };
439
+ }
440
+
441
+ /**
442
+ * Format skill generation result for display
443
+ */
444
+ export function formatGenerateResult(result: GenerateSkillsResult): string {
445
+ const lines: string[] = [];
446
+
447
+ const created = result.generated.filter((g) => g.action === 'created');
448
+ const updated = result.generated.filter((g) => g.action === 'updated');
449
+ const skipped = result.generated.filter((g) => g.action === 'skipped');
450
+
451
+ if (created.length > 0) {
452
+ lines.push(`Created ${created.length} skill(s):`);
453
+ for (const skill of created) {
454
+ lines.push(` ✓ ${skill.name}`);
455
+ }
456
+ lines.push('');
457
+ }
458
+
459
+ if (updated.length > 0) {
460
+ lines.push(`Updated ${updated.length} skill(s):`);
461
+ for (const skill of updated) {
462
+ lines.push(` ✓ ${skill.name}`);
463
+ }
464
+ lines.push('');
465
+ }
466
+
467
+ if (skipped.length > 0) {
468
+ lines.push(`Skipped ${skipped.length} skill(s):`);
469
+ for (const skill of skipped) {
470
+ lines.push(` - ${skill.name}: ${skill.skipReason}`);
471
+ }
472
+ lines.push('');
473
+ }
474
+
475
+ if (result.errors.length > 0) {
476
+ lines.push(`Failed to generate ${result.errors.length} skill(s):`);
477
+ for (const error of result.errors) {
478
+ lines.push(` ✗ ${error.name}: ${error.error}`);
479
+ }
480
+ lines.push('');
481
+ }
482
+
483
+ const total = created.length + updated.length;
484
+ if (total > 0) {
485
+ lines.push(`Generated ${total} skill(s) to .claude/commands/`);
486
+ } else if (result.generated.length === 0 && result.errors.length === 0) {
487
+ lines.push('No skills to generate.');
488
+ }
489
+
490
+ return lines.join('\n');
491
+ }
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Skills Module
3
+ *
4
+ * Generates Claude skills from templates based on project configuration.
5
+ */
6
+
7
+ export {
8
+ extractVariables,
9
+ substituteVariables,
10
+ checkDependencies,
11
+ loadCustomTemplates,
12
+ generateSkills,
13
+ formatGenerateResult,
14
+ } from './generator.js';
15
+
16
+ export { builtInSkills } from './templates.js';
17
+
18
+ export type {
19
+ SkillTemplate,
20
+ SkillVariables,
21
+ SkillCategory,
22
+ SkillDependency,
23
+ GenerateSkillsOptions,
24
+ GenerateSkillsResult,
25
+ GeneratedSkill,
26
+ SkillError,
27
+ } from './types.js';