@deimoscloud/coreai 0.1.9 → 0.1.11
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/dist/cli/index.js +7 -0
- package/dist/cli/index.js.map +1 -1
- package/dist/index.js +7 -0
- package/dist/index.js.map +1 -1
- package/package.json +6 -1
- package/.prettierrc +0 -9
- package/AGENT_SPEC.md +0 -347
- package/ARCHITECTURE.md +0 -547
- package/DRAFT_PRD.md +0 -1440
- package/IMPLEMENTATION_PLAN.md +0 -256
- package/PRODUCT.md +0 -473
- package/WORKFLOWS.md +0 -295
- package/commands/core/check-inbox.md +0 -34
- package/commands/core/delegate.md +0 -30
- package/commands/core/git-commit.md +0 -144
- package/commands/core/pr-create.md +0 -193
- package/commands/core/review.md +0 -56
- package/commands/core/sprint-status.md +0 -65
- package/commands/optional/docs-update.md +0 -200
- package/commands/optional/jira-create.md +0 -200
- package/commands/optional/jira-transition.md +0 -184
- package/commands/optional/worktree-cleanup.md +0 -167
- package/commands/optional/worktree-setup.md +0 -110
- package/eslint.config.js +0 -29
- package/jest.config.js +0 -22
- package/knowledge-library/README.md +0 -118
- package/knowledge-library/android-engineer/context/current.txt +0 -42
- package/knowledge-library/android-engineer/control/decisions.txt +0 -9
- package/knowledge-library/android-engineer/control/dependencies.txt +0 -19
- package/knowledge-library/android-engineer/control/objectives.txt +0 -26
- 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 +0 -61
- package/knowledge-library/backend-engineer/context/current.txt +0 -42
- package/knowledge-library/backend-engineer/control/decisions.txt +0 -9
- package/knowledge-library/backend-engineer/control/dependencies.txt +0 -19
- package/knowledge-library/backend-engineer/control/objectives.txt +0 -26
- 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 +0 -52
- package/knowledge-library/devops-engineer/context/current.txt +0 -42
- package/knowledge-library/devops-engineer/control/decisions.txt +0 -9
- package/knowledge-library/devops-engineer/control/dependencies.txt +0 -19
- package/knowledge-library/devops-engineer/control/objectives.txt +0 -26
- 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 +0 -40
- package/knowledge-library/engineering-manager/control/decisions.txt +0 -9
- package/knowledge-library/engineering-manager/control/objectives.txt +0 -27
- 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 +0 -81
- package/knowledge-library/product-manager/context/current.txt +0 -42
- package/knowledge-library/product-manager/control/decisions.txt +0 -9
- package/knowledge-library/product-manager/control/dependencies.txt +0 -19
- package/knowledge-library/product-manager/control/objectives.txt +0 -26
- 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 +0 -42
- package/knowledge-library/qa-engineer/control/decisions.txt +0 -9
- package/knowledge-library/qa-engineer/control/dependencies.txt +0 -19
- package/knowledge-library/qa-engineer/control/objectives.txt +0 -26
- 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 +0 -42
- package/knowledge-library/security-engineer/control/decisions.txt +0 -9
- package/knowledge-library/security-engineer/control/dependencies.txt +0 -19
- package/knowledge-library/security-engineer/control/objectives.txt +0 -26
- 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 +0 -42
- package/knowledge-library/solutions-architect/control/decisions.txt +0 -9
- package/knowledge-library/solutions-architect/control/dependencies.txt +0 -19
- package/knowledge-library/solutions-architect/control/objectives.txt +0 -26
- 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 +0 -42
- package/knowledge-library/wearos-engineer/control/decisions.txt +0 -9
- package/knowledge-library/wearos-engineer/control/dependencies.txt +0 -19
- package/knowledge-library/wearos-engineer/control/objectives.txt +0 -26
- 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/scripts/add-agent.sh +0 -323
- package/scripts/install.sh +0 -354
- package/src/adapters/factory.test.ts +0 -386
- package/src/adapters/factory.ts +0 -305
- package/src/adapters/index.ts +0 -113
- package/src/adapters/interfaces.ts +0 -268
- package/src/adapters/mcp/client.test.ts +0 -130
- package/src/adapters/mcp/client.ts +0 -451
- package/src/adapters/mcp/discovery.test.ts +0 -315
- package/src/adapters/mcp/discovery.ts +0 -340
- package/src/adapters/mcp/index.ts +0 -66
- package/src/adapters/mcp/mapper.test.ts +0 -218
- package/src/adapters/mcp/mapper.ts +0 -536
- package/src/adapters/mcp/registry.test.ts +0 -433
- package/src/adapters/mcp/registry.ts +0 -550
- package/src/adapters/mcp/types.ts +0 -258
- package/src/adapters/native/filesystem.test.ts +0 -350
- package/src/adapters/native/filesystem.ts +0 -393
- package/src/adapters/native/github.test.ts +0 -173
- package/src/adapters/native/github.ts +0 -627
- package/src/adapters/native/index.ts +0 -22
- package/src/adapters/native/selector.test.ts +0 -224
- package/src/adapters/native/selector.ts +0 -150
- package/src/adapters/types.ts +0 -270
- package/src/agents/compiler.test.ts +0 -410
- package/src/agents/compiler.ts +0 -424
- package/src/agents/index.ts +0 -37
- package/src/agents/loader.test.ts +0 -319
- package/src/agents/loader.ts +0 -143
- package/src/agents/resolver.test.ts +0 -282
- package/src/agents/resolver.ts +0 -262
- package/src/agents/types.ts +0 -97
- package/src/cache/index.ts +0 -38
- package/src/cache/interfaces.ts +0 -283
- package/src/cache/manager.test.ts +0 -266
- package/src/cache/manager.ts +0 -388
- package/src/cache/provider.test.ts +0 -485
- package/src/cache/provider.ts +0 -745
- package/src/cache/types.test.ts +0 -192
- package/src/cache/types.ts +0 -313
- package/src/cli/commands/build.test.ts +0 -248
- package/src/cli/commands/build.ts +0 -284
- package/src/cli/commands/cache.test.ts +0 -221
- package/src/cli/commands/cache.ts +0 -229
- package/src/cli/commands/index.ts +0 -63
- package/src/cli/commands/init.test.ts +0 -173
- package/src/cli/commands/init.ts +0 -296
- package/src/cli/commands/skills.test.ts +0 -272
- package/src/cli/commands/skills.ts +0 -348
- package/src/cli/commands/status.test.ts +0 -392
- package/src/cli/commands/status.ts +0 -332
- package/src/cli/commands/sync.test.ts +0 -213
- package/src/cli/commands/sync.ts +0 -251
- package/src/cli/commands/validate.test.ts +0 -216
- package/src/cli/commands/validate.ts +0 -340
- package/src/cli/index.test.ts +0 -190
- package/src/cli/index.ts +0 -493
- package/src/commands/context.test.ts +0 -163
- package/src/commands/context.ts +0 -111
- package/src/commands/index.ts +0 -56
- package/src/commands/loader.test.ts +0 -273
- package/src/commands/loader.ts +0 -355
- package/src/commands/registry.test.ts +0 -384
- package/src/commands/registry.ts +0 -248
- package/src/commands/runner.test.ts +0 -297
- package/src/commands/runner.ts +0 -222
- package/src/commands/types.ts +0 -361
- package/src/config/index.ts +0 -19
- package/src/config/loader.test.ts +0 -262
- package/src/config/loader.ts +0 -188
- package/src/config/types.ts +0 -154
- package/src/context/index.ts +0 -14
- package/src/context/loader.test.ts +0 -334
- package/src/context/loader.ts +0 -357
- package/src/index.test.ts +0 -13
- package/src/index.ts +0 -268
- package/src/knowledge-library/index.ts +0 -44
- package/src/knowledge-library/manager.test.ts +0 -536
- package/src/knowledge-library/manager.ts +0 -804
- package/src/knowledge-library/types.ts +0 -432
- package/src/skills/generator.test.ts +0 -602
- package/src/skills/generator.ts +0 -491
- package/src/skills/index.ts +0 -27
- package/src/skills/templates.ts +0 -520
- package/src/skills/types.ts +0 -251
- package/templates/completion-report.md +0 -72
- package/templates/feedback.md +0 -56
- package/templates/project-files/CLAUDE.md.template +0 -109
- package/templates/project-files/coreai.json.example +0 -47
- package/templates/project-files/mcp.json.template +0 -20
- package/templates/review-complete.md +0 -64
- package/templates/review-request.md +0 -67
- package/templates/task-assignment.md +0 -51
- package/tsconfig.build.json +0 -4
- package/tsconfig.json +0 -26
- package/tsup.config.ts +0 -23
|
@@ -1,602 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Skill Generator Tests
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
import { promises as fs } from 'fs';
|
|
6
|
-
import { join } from 'path';
|
|
7
|
-
import { tmpdir } from 'os';
|
|
8
|
-
import type { ResolvedCoreAIConfig } from '../config/types.js';
|
|
9
|
-
import {
|
|
10
|
-
extractVariables,
|
|
11
|
-
substituteVariables,
|
|
12
|
-
checkDependencies,
|
|
13
|
-
loadCustomTemplates,
|
|
14
|
-
generateSkills,
|
|
15
|
-
formatGenerateResult,
|
|
16
|
-
} from './generator.js';
|
|
17
|
-
import type { SkillTemplate, GenerateSkillsResult } from './types.js';
|
|
18
|
-
|
|
19
|
-
describe('Skill Generator', () => {
|
|
20
|
-
describe('extractVariables', () => {
|
|
21
|
-
it('should return empty object for null config', () => {
|
|
22
|
-
const vars = extractVariables(null);
|
|
23
|
-
expect(vars).toEqual({});
|
|
24
|
-
});
|
|
25
|
-
|
|
26
|
-
it('should return empty object for undefined config', () => {
|
|
27
|
-
const vars = extractVariables(undefined);
|
|
28
|
-
expect(vars).toEqual({});
|
|
29
|
-
});
|
|
30
|
-
|
|
31
|
-
it('should extract project variables', () => {
|
|
32
|
-
const config = {
|
|
33
|
-
project: {
|
|
34
|
-
name: 'TestProject',
|
|
35
|
-
root: '/path/to/project',
|
|
36
|
-
type: 'software' as const,
|
|
37
|
-
},
|
|
38
|
-
} as ResolvedCoreAIConfig;
|
|
39
|
-
|
|
40
|
-
const vars = extractVariables(config);
|
|
41
|
-
expect(vars.PROJECT_NAME).toBe('TestProject');
|
|
42
|
-
expect(vars.PROJECT_ROOT).toBe('/path/to/project');
|
|
43
|
-
expect(vars.PROJECT_TYPE).toBe('software');
|
|
44
|
-
});
|
|
45
|
-
|
|
46
|
-
it('should extract issue tracker variables', () => {
|
|
47
|
-
const config = {
|
|
48
|
-
project: { name: 'Test', root: '/test' },
|
|
49
|
-
integrations: {
|
|
50
|
-
issue_tracker: {
|
|
51
|
-
provider: 'jira',
|
|
52
|
-
config: {
|
|
53
|
-
project_key: 'TEST',
|
|
54
|
-
base_url: 'https://jira.example.com',
|
|
55
|
-
},
|
|
56
|
-
},
|
|
57
|
-
},
|
|
58
|
-
} as ResolvedCoreAIConfig;
|
|
59
|
-
|
|
60
|
-
const vars = extractVariables(config);
|
|
61
|
-
expect(vars.JIRA_PROJECT).toBe('TEST');
|
|
62
|
-
expect(vars.JIRA_URL).toBe('https://jira.example.com');
|
|
63
|
-
});
|
|
64
|
-
|
|
65
|
-
it('should extract git variables', () => {
|
|
66
|
-
const config = {
|
|
67
|
-
project: { name: 'Test', root: '/test' },
|
|
68
|
-
integrations: {
|
|
69
|
-
git: {
|
|
70
|
-
provider: 'github',
|
|
71
|
-
config: {
|
|
72
|
-
owner: 'testowner',
|
|
73
|
-
repo: 'testrepo',
|
|
74
|
-
default_branch: 'main',
|
|
75
|
-
},
|
|
76
|
-
},
|
|
77
|
-
},
|
|
78
|
-
} as ResolvedCoreAIConfig;
|
|
79
|
-
|
|
80
|
-
const vars = extractVariables(config);
|
|
81
|
-
expect(vars.GITHUB_OWNER).toBe('testowner');
|
|
82
|
-
expect(vars.GITHUB_REPO).toBe('testrepo');
|
|
83
|
-
expect(vars.DEFAULT_BRANCH).toBe('main');
|
|
84
|
-
});
|
|
85
|
-
|
|
86
|
-
it('should extract documentation variables', () => {
|
|
87
|
-
const config = {
|
|
88
|
-
project: { name: 'Test', root: '/test' },
|
|
89
|
-
integrations: {
|
|
90
|
-
documentation: {
|
|
91
|
-
provider: 'confluence',
|
|
92
|
-
config: {
|
|
93
|
-
space_key: 'DOCS',
|
|
94
|
-
base_url: 'https://wiki.example.com',
|
|
95
|
-
base_path: '/docs',
|
|
96
|
-
},
|
|
97
|
-
},
|
|
98
|
-
},
|
|
99
|
-
} as ResolvedCoreAIConfig;
|
|
100
|
-
|
|
101
|
-
const vars = extractVariables(config);
|
|
102
|
-
expect(vars.CONFLUENCE_SPACE).toBe('DOCS');
|
|
103
|
-
expect(vars.CONFLUENCE_URL).toBe('https://wiki.example.com');
|
|
104
|
-
expect(vars.DOCS_PATH).toBe('/docs');
|
|
105
|
-
});
|
|
106
|
-
|
|
107
|
-
it('should extract quality gate variables', () => {
|
|
108
|
-
const config = {
|
|
109
|
-
project: { name: 'Test', root: '/test' },
|
|
110
|
-
quality_gates: {
|
|
111
|
-
lint: { command: 'npm run lint', required: true },
|
|
112
|
-
test: { command: 'npm test', required: true },
|
|
113
|
-
build: { command: 'npm run build', required: true },
|
|
114
|
-
static_analysis: { command: 'npm run analyze', required: false },
|
|
115
|
-
},
|
|
116
|
-
} as ResolvedCoreAIConfig;
|
|
117
|
-
|
|
118
|
-
const vars = extractVariables(config);
|
|
119
|
-
expect(vars.LINT_CMD).toBe('npm run lint');
|
|
120
|
-
expect(vars.TEST_CMD).toBe('npm test');
|
|
121
|
-
expect(vars.BUILD_CMD).toBe('npm run build');
|
|
122
|
-
expect(vars.STATIC_ANALYSIS_CMD).toBe('npm run analyze');
|
|
123
|
-
});
|
|
124
|
-
|
|
125
|
-
it('should extract tech stack variables', () => {
|
|
126
|
-
const config = {
|
|
127
|
-
project: { name: 'Test', root: '/test' },
|
|
128
|
-
tech_stack: {
|
|
129
|
-
primary_language: 'typescript',
|
|
130
|
-
frameworks: ['node.js', 'express'],
|
|
131
|
-
},
|
|
132
|
-
} as ResolvedCoreAIConfig;
|
|
133
|
-
|
|
134
|
-
const vars = extractVariables(config);
|
|
135
|
-
expect(vars.PRIMARY_LANGUAGE).toBe('typescript');
|
|
136
|
-
});
|
|
137
|
-
});
|
|
138
|
-
|
|
139
|
-
describe('substituteVariables', () => {
|
|
140
|
-
it('should replace single variable', () => {
|
|
141
|
-
const content = 'Hello, {{NAME}}!';
|
|
142
|
-
const result = substituteVariables(content, { NAME: 'World' });
|
|
143
|
-
expect(result).toBe('Hello, World!');
|
|
144
|
-
});
|
|
145
|
-
|
|
146
|
-
it('should replace multiple occurrences', () => {
|
|
147
|
-
const content = '{{NAME}} says {{NAME}}';
|
|
148
|
-
const result = substituteVariables(content, { NAME: 'Alice' });
|
|
149
|
-
expect(result).toBe('Alice says Alice');
|
|
150
|
-
});
|
|
151
|
-
|
|
152
|
-
it('should replace multiple variables', () => {
|
|
153
|
-
const content = '{{PROJECT}} by {{AUTHOR}}';
|
|
154
|
-
const result = substituteVariables(content, {
|
|
155
|
-
PROJECT: 'CoreAI',
|
|
156
|
-
AUTHOR: 'Team',
|
|
157
|
-
});
|
|
158
|
-
expect(result).toBe('CoreAI by Team');
|
|
159
|
-
});
|
|
160
|
-
|
|
161
|
-
it('should leave unreplaced variables intact', () => {
|
|
162
|
-
const content = '{{KNOWN}} and {{UNKNOWN}}';
|
|
163
|
-
const result = substituteVariables(content, { KNOWN: 'value' });
|
|
164
|
-
expect(result).toBe('value and {{UNKNOWN}}');
|
|
165
|
-
});
|
|
166
|
-
|
|
167
|
-
it('should handle empty variables', () => {
|
|
168
|
-
const content = 'No variables here';
|
|
169
|
-
const result = substituteVariables(content, {});
|
|
170
|
-
expect(result).toBe('No variables here');
|
|
171
|
-
});
|
|
172
|
-
|
|
173
|
-
it('should skip undefined values', () => {
|
|
174
|
-
const content = '{{A}} {{B}}';
|
|
175
|
-
const result = substituteVariables(content, {
|
|
176
|
-
A: 'defined',
|
|
177
|
-
B: undefined,
|
|
178
|
-
});
|
|
179
|
-
expect(result).toBe('defined {{B}}');
|
|
180
|
-
});
|
|
181
|
-
});
|
|
182
|
-
|
|
183
|
-
describe('checkDependencies', () => {
|
|
184
|
-
it('should return satisfied for skill without dependencies', () => {
|
|
185
|
-
const skill: SkillTemplate = {
|
|
186
|
-
name: 'test',
|
|
187
|
-
description: 'Test skill',
|
|
188
|
-
category: 'core',
|
|
189
|
-
content: 'content',
|
|
190
|
-
};
|
|
191
|
-
|
|
192
|
-
const result = checkDependencies(skill, null);
|
|
193
|
-
expect(result.satisfied).toBe(true);
|
|
194
|
-
expect(result.missing).toEqual([]);
|
|
195
|
-
});
|
|
196
|
-
|
|
197
|
-
it('should return satisfied for empty dependencies array', () => {
|
|
198
|
-
const skill: SkillTemplate = {
|
|
199
|
-
name: 'test',
|
|
200
|
-
description: 'Test skill',
|
|
201
|
-
category: 'core',
|
|
202
|
-
dependencies: [],
|
|
203
|
-
content: 'content',
|
|
204
|
-
};
|
|
205
|
-
|
|
206
|
-
const result = checkDependencies(skill, null);
|
|
207
|
-
expect(result.satisfied).toBe(true);
|
|
208
|
-
});
|
|
209
|
-
|
|
210
|
-
it('should skip optional dependencies', () => {
|
|
211
|
-
const skill: SkillTemplate = {
|
|
212
|
-
name: 'test',
|
|
213
|
-
description: 'Test skill',
|
|
214
|
-
category: 'core',
|
|
215
|
-
dependencies: [{ type: 'issue_tracker', required: false }],
|
|
216
|
-
content: 'content',
|
|
217
|
-
};
|
|
218
|
-
|
|
219
|
-
const result = checkDependencies(skill, null);
|
|
220
|
-
expect(result.satisfied).toBe(true);
|
|
221
|
-
});
|
|
222
|
-
|
|
223
|
-
it('should detect missing required integration', () => {
|
|
224
|
-
const skill: SkillTemplate = {
|
|
225
|
-
name: 'test',
|
|
226
|
-
description: 'Test skill',
|
|
227
|
-
category: 'optional',
|
|
228
|
-
dependencies: [{ type: 'issue_tracker', required: true }],
|
|
229
|
-
content: 'content',
|
|
230
|
-
};
|
|
231
|
-
|
|
232
|
-
const config = {
|
|
233
|
-
project: { name: 'Test', root: '/test' },
|
|
234
|
-
} as ResolvedCoreAIConfig;
|
|
235
|
-
|
|
236
|
-
const result = checkDependencies(skill, config);
|
|
237
|
-
expect(result.satisfied).toBe(false);
|
|
238
|
-
expect(result.missing).toHaveLength(1);
|
|
239
|
-
expect(result.missing[0]?.type).toBe('issue_tracker');
|
|
240
|
-
});
|
|
241
|
-
|
|
242
|
-
it('should satisfy when integration exists', () => {
|
|
243
|
-
const skill: SkillTemplate = {
|
|
244
|
-
name: 'test',
|
|
245
|
-
description: 'Test skill',
|
|
246
|
-
category: 'optional',
|
|
247
|
-
dependencies: [{ type: 'git', required: true }],
|
|
248
|
-
content: 'content',
|
|
249
|
-
};
|
|
250
|
-
|
|
251
|
-
const config = {
|
|
252
|
-
project: { name: 'Test', root: '/test' },
|
|
253
|
-
integrations: {
|
|
254
|
-
git: { provider: 'github', config: {} },
|
|
255
|
-
},
|
|
256
|
-
} as ResolvedCoreAIConfig;
|
|
257
|
-
|
|
258
|
-
const result = checkDependencies(skill, config);
|
|
259
|
-
expect(result.satisfied).toBe(true);
|
|
260
|
-
});
|
|
261
|
-
|
|
262
|
-
it('should check documentation dependency', () => {
|
|
263
|
-
const skill: SkillTemplate = {
|
|
264
|
-
name: 'test',
|
|
265
|
-
description: 'Test skill',
|
|
266
|
-
category: 'optional',
|
|
267
|
-
dependencies: [{ type: 'documentation', required: true }],
|
|
268
|
-
content: 'content',
|
|
269
|
-
};
|
|
270
|
-
|
|
271
|
-
const config = {
|
|
272
|
-
project: { name: 'Test', root: '/test' },
|
|
273
|
-
integrations: {
|
|
274
|
-
documentation: { provider: 'confluence', config: {} },
|
|
275
|
-
},
|
|
276
|
-
} as ResolvedCoreAIConfig;
|
|
277
|
-
|
|
278
|
-
const result = checkDependencies(skill, config);
|
|
279
|
-
expect(result.satisfied).toBe(true);
|
|
280
|
-
});
|
|
281
|
-
|
|
282
|
-
it('should check state dependency', () => {
|
|
283
|
-
const skill: SkillTemplate = {
|
|
284
|
-
name: 'test',
|
|
285
|
-
description: 'Test skill',
|
|
286
|
-
category: 'optional',
|
|
287
|
-
dependencies: [{ type: 'state', required: true }],
|
|
288
|
-
content: 'content',
|
|
289
|
-
};
|
|
290
|
-
|
|
291
|
-
const config = {
|
|
292
|
-
project: { name: 'Test', root: '/test' },
|
|
293
|
-
integrations: {
|
|
294
|
-
state: { provider: 'filesystem', config: {} },
|
|
295
|
-
},
|
|
296
|
-
} as ResolvedCoreAIConfig;
|
|
297
|
-
|
|
298
|
-
const result = checkDependencies(skill, config);
|
|
299
|
-
expect(result.satisfied).toBe(true);
|
|
300
|
-
});
|
|
301
|
-
});
|
|
302
|
-
|
|
303
|
-
describe('loadCustomTemplates', () => {
|
|
304
|
-
let testDir: string;
|
|
305
|
-
|
|
306
|
-
beforeEach(async () => {
|
|
307
|
-
testDir = join(
|
|
308
|
-
tmpdir(),
|
|
309
|
-
`skill-templates-test-${Date.now()}-${Math.random().toString(36).slice(2)}`
|
|
310
|
-
);
|
|
311
|
-
await fs.mkdir(testDir, { recursive: true });
|
|
312
|
-
});
|
|
313
|
-
|
|
314
|
-
afterEach(async () => {
|
|
315
|
-
try {
|
|
316
|
-
await fs.rm(testDir, { recursive: true, force: true });
|
|
317
|
-
} catch {
|
|
318
|
-
// Ignore cleanup errors
|
|
319
|
-
}
|
|
320
|
-
});
|
|
321
|
-
|
|
322
|
-
it('should return empty array for non-existent directory', () => {
|
|
323
|
-
const templates = loadCustomTemplates('/non/existent/path');
|
|
324
|
-
expect(templates).toEqual([]);
|
|
325
|
-
});
|
|
326
|
-
|
|
327
|
-
it('should load skill template from markdown file', async () => {
|
|
328
|
-
const content = `---
|
|
329
|
-
description: Custom skill
|
|
330
|
-
argument-hint: <argument>
|
|
331
|
-
---
|
|
332
|
-
|
|
333
|
-
# Custom Skill
|
|
334
|
-
|
|
335
|
-
This is a custom skill.
|
|
336
|
-
`;
|
|
337
|
-
await fs.writeFile(join(testDir, 'custom-skill.md'), content);
|
|
338
|
-
|
|
339
|
-
const templates = loadCustomTemplates(testDir);
|
|
340
|
-
expect(templates).toHaveLength(1);
|
|
341
|
-
expect(templates[0]?.name).toBe('custom-skill');
|
|
342
|
-
expect(templates[0]?.description).toBe('Custom skill');
|
|
343
|
-
expect(templates[0]?.argumentHint).toBe('<argument>');
|
|
344
|
-
expect(templates[0]?.category).toBe('custom');
|
|
345
|
-
});
|
|
346
|
-
|
|
347
|
-
it('should parse required dependencies', async () => {
|
|
348
|
-
const content = `---
|
|
349
|
-
description: Skill with deps
|
|
350
|
-
requires: [jira, github]
|
|
351
|
-
---
|
|
352
|
-
|
|
353
|
-
Content
|
|
354
|
-
`;
|
|
355
|
-
await fs.writeFile(join(testDir, 'with-deps.md'), content);
|
|
356
|
-
|
|
357
|
-
const templates = loadCustomTemplates(testDir);
|
|
358
|
-
expect(templates).toHaveLength(1);
|
|
359
|
-
expect(templates[0]?.dependencies).toBeDefined();
|
|
360
|
-
expect(templates[0]?.dependencies).toHaveLength(2);
|
|
361
|
-
expect(templates[0]?.dependencies?.[0]?.type).toBe('issue_tracker');
|
|
362
|
-
expect(templates[0]?.dependencies?.[0]?.required).toBe(true);
|
|
363
|
-
expect(templates[0]?.dependencies?.[1]?.type).toBe('git');
|
|
364
|
-
});
|
|
365
|
-
|
|
366
|
-
it('should parse optional dependencies', async () => {
|
|
367
|
-
const content = `---
|
|
368
|
-
description: Skill with optional deps
|
|
369
|
-
optional: [docs]
|
|
370
|
-
---
|
|
371
|
-
|
|
372
|
-
Content
|
|
373
|
-
`;
|
|
374
|
-
await fs.writeFile(join(testDir, 'optional-deps.md'), content);
|
|
375
|
-
|
|
376
|
-
const templates = loadCustomTemplates(testDir);
|
|
377
|
-
expect(templates).toHaveLength(1);
|
|
378
|
-
expect(templates[0]?.dependencies).toHaveLength(1);
|
|
379
|
-
expect(templates[0]?.dependencies?.[0]?.type).toBe('documentation');
|
|
380
|
-
expect(templates[0]?.dependencies?.[0]?.required).toBe(false);
|
|
381
|
-
});
|
|
382
|
-
|
|
383
|
-
it('should skip non-markdown files', async () => {
|
|
384
|
-
await fs.writeFile(join(testDir, 'readme.txt'), 'Not a template');
|
|
385
|
-
await fs.writeFile(join(testDir, 'skill.md'), '---\ndescription: Test\n---\nContent');
|
|
386
|
-
|
|
387
|
-
const templates = loadCustomTemplates(testDir);
|
|
388
|
-
expect(templates).toHaveLength(1);
|
|
389
|
-
expect(templates[0]?.name).toBe('skill');
|
|
390
|
-
});
|
|
391
|
-
});
|
|
392
|
-
|
|
393
|
-
describe('generateSkills', () => {
|
|
394
|
-
let testDir: string;
|
|
395
|
-
|
|
396
|
-
beforeEach(async () => {
|
|
397
|
-
testDir = join(
|
|
398
|
-
tmpdir(),
|
|
399
|
-
`skill-gen-test-${Date.now()}-${Math.random().toString(36).slice(2)}`
|
|
400
|
-
);
|
|
401
|
-
await fs.mkdir(testDir, { recursive: true });
|
|
402
|
-
});
|
|
403
|
-
|
|
404
|
-
afterEach(async () => {
|
|
405
|
-
try {
|
|
406
|
-
await fs.rm(testDir, { recursive: true, force: true });
|
|
407
|
-
} catch {
|
|
408
|
-
// Ignore cleanup errors
|
|
409
|
-
}
|
|
410
|
-
});
|
|
411
|
-
|
|
412
|
-
it('should generate skills to output directory', () => {
|
|
413
|
-
const result = generateSkills(undefined, {
|
|
414
|
-
projectRoot: testDir,
|
|
415
|
-
includeCoreSkills: true,
|
|
416
|
-
includeOptionalSkills: false,
|
|
417
|
-
});
|
|
418
|
-
|
|
419
|
-
expect(result.generated.length).toBeGreaterThan(0);
|
|
420
|
-
expect(result.errors).toEqual([]);
|
|
421
|
-
|
|
422
|
-
// Check a file was created
|
|
423
|
-
const created = result.generated.filter((g) => g.action === 'created');
|
|
424
|
-
expect(created.length).toBeGreaterThan(0);
|
|
425
|
-
});
|
|
426
|
-
|
|
427
|
-
it('should skip existing files without overwrite', async () => {
|
|
428
|
-
const outputDir = join(testDir, '.claude', 'commands');
|
|
429
|
-
await fs.mkdir(outputDir, { recursive: true });
|
|
430
|
-
await fs.writeFile(join(outputDir, 'check-inbox.md'), 'existing content');
|
|
431
|
-
|
|
432
|
-
const result = generateSkills(undefined, {
|
|
433
|
-
projectRoot: testDir,
|
|
434
|
-
includeCoreSkills: true,
|
|
435
|
-
includeOptionalSkills: false,
|
|
436
|
-
skills: ['check-inbox'],
|
|
437
|
-
});
|
|
438
|
-
|
|
439
|
-
const skipped = result.generated.filter((g) => g.action === 'skipped');
|
|
440
|
-
expect(skipped).toHaveLength(1);
|
|
441
|
-
expect(skipped[0]?.name).toBe('check-inbox');
|
|
442
|
-
expect(skipped[0]?.skipReason).toContain('already exists');
|
|
443
|
-
});
|
|
444
|
-
|
|
445
|
-
it('should overwrite existing files with overwrite option', async () => {
|
|
446
|
-
const outputDir = join(testDir, '.claude', 'commands');
|
|
447
|
-
await fs.mkdir(outputDir, { recursive: true });
|
|
448
|
-
await fs.writeFile(join(outputDir, 'check-inbox.md'), 'old content');
|
|
449
|
-
|
|
450
|
-
const result = generateSkills(undefined, {
|
|
451
|
-
projectRoot: testDir,
|
|
452
|
-
includeCoreSkills: true,
|
|
453
|
-
includeOptionalSkills: false,
|
|
454
|
-
skills: ['check-inbox'],
|
|
455
|
-
overwrite: true,
|
|
456
|
-
});
|
|
457
|
-
|
|
458
|
-
const updated = result.generated.filter((g) => g.action === 'updated');
|
|
459
|
-
expect(updated).toHaveLength(1);
|
|
460
|
-
expect(updated[0]?.name).toBe('check-inbox');
|
|
461
|
-
});
|
|
462
|
-
|
|
463
|
-
it('should filter to specific skills', () => {
|
|
464
|
-
const result = generateSkills(undefined, {
|
|
465
|
-
projectRoot: testDir,
|
|
466
|
-
skills: ['check-inbox', 'delegate'],
|
|
467
|
-
});
|
|
468
|
-
|
|
469
|
-
expect(result.generated).toHaveLength(2);
|
|
470
|
-
const names = result.generated.map((g) => g.name);
|
|
471
|
-
expect(names).toContain('check-inbox');
|
|
472
|
-
expect(names).toContain('delegate');
|
|
473
|
-
});
|
|
474
|
-
|
|
475
|
-
it('should skip skills with missing dependencies', () => {
|
|
476
|
-
const config = {
|
|
477
|
-
project: { name: 'Test', root: testDir },
|
|
478
|
-
} as ResolvedCoreAIConfig;
|
|
479
|
-
|
|
480
|
-
const result = generateSkills(config, {
|
|
481
|
-
projectRoot: testDir,
|
|
482
|
-
includeCoreSkills: false,
|
|
483
|
-
includeOptionalSkills: true,
|
|
484
|
-
skills: ['jira-create'], // Requires issue_tracker
|
|
485
|
-
});
|
|
486
|
-
|
|
487
|
-
const skipped = result.generated.filter((g) => g.action === 'skipped');
|
|
488
|
-
expect(skipped.length).toBeGreaterThan(0);
|
|
489
|
-
const jiraSkill = skipped.find((s) => s.name === 'jira-create');
|
|
490
|
-
expect(jiraSkill?.skipReason).toContain('Missing required integrations');
|
|
491
|
-
});
|
|
492
|
-
|
|
493
|
-
it('should substitute variables in templates', () => {
|
|
494
|
-
const config = {
|
|
495
|
-
project: { name: 'TestProject', root: testDir },
|
|
496
|
-
quality_gates: {
|
|
497
|
-
lint: { command: 'npm run lint', required: true },
|
|
498
|
-
test: { command: 'npm test', required: true },
|
|
499
|
-
build: { command: 'npm run build', required: true },
|
|
500
|
-
},
|
|
501
|
-
} as ResolvedCoreAIConfig;
|
|
502
|
-
|
|
503
|
-
const result = generateSkills(config, {
|
|
504
|
-
projectRoot: testDir,
|
|
505
|
-
includeCoreSkills: true,
|
|
506
|
-
includeOptionalSkills: false,
|
|
507
|
-
skills: ['git-commit'],
|
|
508
|
-
});
|
|
509
|
-
|
|
510
|
-
expect(result.generated).toHaveLength(1);
|
|
511
|
-
expect(result.variables.LINT_CMD).toBe('npm run lint');
|
|
512
|
-
});
|
|
513
|
-
|
|
514
|
-
it('should merge custom variables', () => {
|
|
515
|
-
const result = generateSkills(undefined, {
|
|
516
|
-
projectRoot: testDir,
|
|
517
|
-
includeCoreSkills: true,
|
|
518
|
-
includeOptionalSkills: false,
|
|
519
|
-
variables: {
|
|
520
|
-
CUSTOM_VAR: 'custom value',
|
|
521
|
-
},
|
|
522
|
-
});
|
|
523
|
-
|
|
524
|
-
expect(result.variables.CUSTOM_VAR).toBe('custom value');
|
|
525
|
-
});
|
|
526
|
-
});
|
|
527
|
-
|
|
528
|
-
describe('formatGenerateResult', () => {
|
|
529
|
-
it('should format result with created skills', () => {
|
|
530
|
-
const result: GenerateSkillsResult = {
|
|
531
|
-
generated: [
|
|
532
|
-
{ name: 'skill1', category: 'core', outputPath: '/out/skill1.md', action: 'created' },
|
|
533
|
-
{ name: 'skill2', category: 'core', outputPath: '/out/skill2.md', action: 'created' },
|
|
534
|
-
],
|
|
535
|
-
errors: [],
|
|
536
|
-
variables: {},
|
|
537
|
-
};
|
|
538
|
-
|
|
539
|
-
const output = formatGenerateResult(result);
|
|
540
|
-
expect(output).toContain('Created 2 skill(s)');
|
|
541
|
-
expect(output).toContain('skill1');
|
|
542
|
-
expect(output).toContain('skill2');
|
|
543
|
-
});
|
|
544
|
-
|
|
545
|
-
it('should format result with updated skills', () => {
|
|
546
|
-
const result: GenerateSkillsResult = {
|
|
547
|
-
generated: [
|
|
548
|
-
{ name: 'skill1', category: 'core', outputPath: '/out/skill1.md', action: 'updated' },
|
|
549
|
-
],
|
|
550
|
-
errors: [],
|
|
551
|
-
variables: {},
|
|
552
|
-
};
|
|
553
|
-
|
|
554
|
-
const output = formatGenerateResult(result);
|
|
555
|
-
expect(output).toContain('Updated 1 skill(s)');
|
|
556
|
-
});
|
|
557
|
-
|
|
558
|
-
it('should format result with skipped skills', () => {
|
|
559
|
-
const result: GenerateSkillsResult = {
|
|
560
|
-
generated: [
|
|
561
|
-
{
|
|
562
|
-
name: 'skill1',
|
|
563
|
-
category: 'core',
|
|
564
|
-
outputPath: '/out/skill1.md',
|
|
565
|
-
action: 'skipped',
|
|
566
|
-
skipReason: 'File exists',
|
|
567
|
-
},
|
|
568
|
-
],
|
|
569
|
-
errors: [],
|
|
570
|
-
variables: {},
|
|
571
|
-
};
|
|
572
|
-
|
|
573
|
-
const output = formatGenerateResult(result);
|
|
574
|
-
expect(output).toContain('Skipped 1 skill(s)');
|
|
575
|
-
expect(output).toContain('File exists');
|
|
576
|
-
});
|
|
577
|
-
|
|
578
|
-
it('should format result with errors', () => {
|
|
579
|
-
const result: GenerateSkillsResult = {
|
|
580
|
-
generated: [],
|
|
581
|
-
errors: [{ name: 'bad-skill', error: 'Parse error' }],
|
|
582
|
-
variables: {},
|
|
583
|
-
};
|
|
584
|
-
|
|
585
|
-
const output = formatGenerateResult(result);
|
|
586
|
-
expect(output).toContain('Failed to generate 1 skill(s)');
|
|
587
|
-
expect(output).toContain('bad-skill');
|
|
588
|
-
expect(output).toContain('Parse error');
|
|
589
|
-
});
|
|
590
|
-
|
|
591
|
-
it('should show message when nothing to generate', () => {
|
|
592
|
-
const result: GenerateSkillsResult = {
|
|
593
|
-
generated: [],
|
|
594
|
-
errors: [],
|
|
595
|
-
variables: {},
|
|
596
|
-
};
|
|
597
|
-
|
|
598
|
-
const output = formatGenerateResult(result);
|
|
599
|
-
expect(output).toContain('No skills to generate');
|
|
600
|
-
});
|
|
601
|
-
});
|
|
602
|
-
});
|