ma-agents 2.20.3 → 2.22.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/.opencode/skills/.ma-agents.json +241 -0
- package/.opencode/skills/MANIFEST.yaml +254 -0
- package/.opencode/skills/ai-audit-trail/SKILL.md +23 -0
- package/.opencode/skills/auto-bug-detection/SKILL.md +169 -0
- package/.opencode/skills/cmake-best-practices/SKILL.md +64 -0
- package/.opencode/skills/cmake-best-practices/examples/cmake.md +59 -0
- package/.opencode/skills/code-documentation/SKILL.md +57 -0
- package/.opencode/skills/code-documentation/examples/cpp.md +29 -0
- package/.opencode/skills/code-documentation/examples/csharp.md +28 -0
- package/.opencode/skills/code-documentation/examples/javascript_typescript.md +28 -0
- package/.opencode/skills/code-documentation/examples/python.md +57 -0
- package/.opencode/skills/code-review/SKILL.md +43 -0
- package/.opencode/skills/commit-message/SKILL.md +79 -0
- package/.opencode/skills/cpp-best-practices/SKILL.md +234 -0
- package/.opencode/skills/cpp-best-practices/examples/modern-idioms.md +189 -0
- package/.opencode/skills/cpp-best-practices/examples/naming-and-organization.md +102 -0
- package/.opencode/skills/cpp-concurrency-safety/SKILL.md +60 -0
- package/.opencode/skills/cpp-concurrency-safety/examples/concurrency.md +73 -0
- package/.opencode/skills/cpp-const-correctness/SKILL.md +63 -0
- package/.opencode/skills/cpp-const-correctness/examples/const_correctness.md +54 -0
- package/.opencode/skills/cpp-memory-handling/SKILL.md +42 -0
- package/.opencode/skills/cpp-memory-handling/examples/modern-cpp.md +49 -0
- package/.opencode/skills/cpp-memory-handling/examples/smart-pointers.md +46 -0
- package/.opencode/skills/cpp-modern-composition/SKILL.md +64 -0
- package/.opencode/skills/cpp-modern-composition/examples/composition.md +51 -0
- package/.opencode/skills/cpp-robust-interfaces/SKILL.md +55 -0
- package/.opencode/skills/cpp-robust-interfaces/examples/interfaces.md +56 -0
- package/.opencode/skills/create-hardened-docker-skill/SKILL.md +637 -0
- package/.opencode/skills/create-hardened-docker-skill/scripts/create-all.sh +489 -0
- package/.opencode/skills/csharp-best-practices/SKILL.md +278 -0
- package/.opencode/skills/docker-hardening-verification/SKILL.md +28 -0
- package/.opencode/skills/docker-hardening-verification/scripts/verify-hardening.sh +39 -0
- package/.opencode/skills/docker-image-signing/SKILL.md +28 -0
- package/.opencode/skills/docker-image-signing/scripts/sign-image.sh +33 -0
- package/.opencode/skills/document-revision-history/SKILL.md +104 -0
- package/.opencode/skills/git-workflow-skill/SKILL.md +194 -0
- package/.opencode/skills/git-workflow-skill/hooks/commit-msg +61 -0
- package/.opencode/skills/git-workflow-skill/hooks/pre-commit +38 -0
- package/.opencode/skills/git-workflow-skill/hooks/prepare-commit-msg +56 -0
- package/.opencode/skills/git-workflow-skill/scripts/finish-feature.sh +192 -0
- package/.opencode/skills/git-workflow-skill/scripts/install-hooks.sh +55 -0
- package/.opencode/skills/git-workflow-skill/scripts/start-feature.sh +110 -0
- package/.opencode/skills/git-workflow-skill/scripts/validate-workflow.sh +229 -0
- package/.opencode/skills/js-ts-dependency-mgmt/SKILL.md +49 -0
- package/.opencode/skills/js-ts-dependency-mgmt/examples/dependency_mgmt.md +60 -0
- package/.opencode/skills/js-ts-security-skill/SKILL.md +64 -0
- package/.opencode/skills/js-ts-security-skill/scripts/verify-security.sh +136 -0
- package/.opencode/skills/logging-best-practices/SKILL.md +50 -0
- package/.opencode/skills/logging-best-practices/examples/cpp.md +36 -0
- package/.opencode/skills/logging-best-practices/examples/csharp.md +49 -0
- package/.opencode/skills/logging-best-practices/examples/javascript.md +77 -0
- package/.opencode/skills/logging-best-practices/examples/python.md +57 -0
- package/.opencode/skills/logging-best-practices/references/logging-standards.md +29 -0
- package/.opencode/skills/open-presentation/SKILL.md +35 -0
- package/.opencode/skills/opentelemetry-best-practices/SKILL.md +34 -0
- package/.opencode/skills/opentelemetry-best-practices/examples/go.md +32 -0
- package/.opencode/skills/opentelemetry-best-practices/examples/javascript.md +58 -0
- package/.opencode/skills/opentelemetry-best-practices/examples/python.md +37 -0
- package/.opencode/skills/opentelemetry-best-practices/references/otel-standards.md +37 -0
- package/.opencode/skills/python-best-practices/SKILL.md +385 -0
- package/.opencode/skills/python-dependency-mgmt/SKILL.md +42 -0
- package/.opencode/skills/python-dependency-mgmt/examples/dependency_mgmt.md +67 -0
- package/.opencode/skills/python-security-skill/SKILL.md +56 -0
- package/.opencode/skills/python-security-skill/examples/security.md +56 -0
- package/.opencode/skills/self-signed-cert/SKILL.md +42 -0
- package/.opencode/skills/self-signed-cert/scripts/generate-cert.ps1 +45 -0
- package/.opencode/skills/self-signed-cert/scripts/generate-cert.sh +43 -0
- package/.opencode/skills/skill-creator/SKILL.md +196 -0
- package/.opencode/skills/skill-creator/references/output-patterns.md +82 -0
- package/.opencode/skills/skill-creator/references/workflows.md +28 -0
- package/.opencode/skills/skill-creator/scripts/init_skill.py +208 -0
- package/.opencode/skills/skill-creator/scripts/package_skill.py +99 -0
- package/.opencode/skills/skill-creator/scripts/quick_validate.py +113 -0
- package/.opencode/skills/story-status-lookup/SKILL.md +78 -0
- package/.opencode/skills/test-accompanied-development/SKILL.md +50 -0
- package/.opencode/skills/test-generator/SKILL.md +65 -0
- package/.opencode/skills/vercel-react-best-practices/SKILL.md +109 -0
- package/.opencode/skills/verify-hardened-docker-skill/SKILL.md +442 -0
- package/.opencode/skills/verify-hardened-docker-skill/scripts/verify-docker-hardening.sh +439 -0
- package/AiAudit.md +5 -0
- package/QUICK_START.md +11 -5
- package/README.md +52 -1
- package/bin/cli.js +31 -4
- package/docs/BMAD_AI_Development_Training.pptx +0 -0
- package/docs/technical-notes/context-persistence-research.md +434 -0
- package/docs/technical-notes/enforcement-hooks-research.md +415 -0
- package/lib/agents.js +34 -0
- package/lib/bmad-extension/agents/bmm-architect.customize.yaml +5 -0
- package/lib/bmad-extension/agents/bmm-bmad-master.customize.yaml +5 -0
- package/lib/bmad-extension/agents/bmm-cyber.customize.yaml +30 -0
- package/lib/bmad-extension/agents/bmm-dev.customize.yaml +5 -0
- package/lib/bmad-extension/agents/bmm-devops.customize.yaml +30 -0
- package/lib/bmad-extension/agents/bmm-mil498.customize.yaml +42 -0
- package/lib/bmad-extension/agents/bmm-pm.customize.yaml +5 -0
- package/lib/bmad-extension/agents/bmm-qa.customize.yaml +5 -0
- package/lib/bmad-extension/agents/bmm-sm.customize.yaml +5 -0
- package/lib/bmad-extension/agents/bmm-sre.customize.yaml +30 -0
- package/lib/bmad-extension/agents/bmm-tech-writer.customize.yaml +5 -0
- package/lib/bmad-extension/agents/bmm-ux-designer.customize.yaml +5 -0
- package/lib/bmad-extension/module-help.csv +7 -0
- package/lib/bmad-extension/module.yaml +3 -0
- package/lib/bmad-extension/workflows/add-sprint/workflow.md +112 -0
- package/lib/bmad-extension/workflows/add-to-sprint/workflow.md +206 -0
- package/lib/bmad-extension/workflows/create-bug-story/workflow.md +186 -0
- package/lib/bmad-extension/workflows/modify-sprint/workflow.md +250 -0
- package/lib/bmad-extension/workflows/project-context-expansion/workflow.md +229 -0
- package/lib/bmad-extension/workflows/sprint-status-view/workflow.md +193 -0
- package/lib/bmad.js +168 -36
- package/lib/hooks/claude-code/verify-manifest.js +56 -0
- package/lib/installer.js +282 -1
- package/lib/methodology/BMAD_AI_Development_Training.pptx +0 -0
- package/lib/methodology/version.json +7 -0
- package/lib/skill-authoring.js +732 -0
- package/lib/templates/project-context.template.md +47 -0
- package/opencode.json +8 -0
- package/package.json +2 -2
- package/skills/auto-bug-detection/SKILL.md +165 -0
- package/skills/auto-bug-detection/skill.json +8 -0
- package/skills/code-review/SKILL.md +40 -0
- package/skills/cpp-best-practices/SKILL.md +230 -0
- package/skills/cpp-best-practices/examples/modern-idioms.md +189 -0
- package/skills/cpp-best-practices/examples/naming-and-organization.md +102 -0
- package/skills/cpp-best-practices/skill.json +25 -0
- package/skills/csharp-best-practices/SKILL.md +274 -0
- package/skills/csharp-best-practices/skill.json +23 -0
- package/skills/git-workflow-skill/skill.json +1 -1
- package/skills/open-presentation/SKILL.md +31 -0
- package/skills/open-presentation/skill.json +11 -0
- package/skills/python-best-practices/SKILL.md +381 -0
- package/skills/python-best-practices/skill.json +26 -0
- package/skills/story-status-lookup/SKILL.md +74 -0
- package/skills/story-status-lookup/skill.json +8 -0
- package/test/agent-injection-strategy.test.js +13 -7
- package/test/bmad-extension.test.js +237 -0
- package/test/bmad-output-policy.test.js +119 -0
- package/test/build-bmad-args.test.js +361 -0
- package/test/create-agent.test.js +232 -0
- package/test/enforcement-hooks.test.js +324 -0
- package/test/generate-project-context.test.js +337 -0
- package/test/integration-verification.test.js +402 -0
- package/test/opencode-agent.test.js +150 -0
- package/test/opencode-json-error.test.js +260 -0
- package/test/opencode-json-injection.test.js +256 -0
- package/test/opencode-json-merge.test.js +299 -0
- package/test/skill-authoring.test.js +272 -0
- package/test/skill-customize-agent.test.js +253 -0
- package/test/skill-mandatory.test.js +235 -0
- package/test/skill-validation.test.js +378 -0
- package/test/yes-flag.test.js +1 -1
|
@@ -0,0 +1,378 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Tests for validate-skill command implementation
|
|
4
|
+
* Story 3.2: Skill Validation Command
|
|
5
|
+
*
|
|
6
|
+
* Task 6.1: Valid skill reports VALID
|
|
7
|
+
* Task 6.2: Broken skill reports all errors
|
|
8
|
+
* Task 6.3: Unknown agent template triggers warning
|
|
9
|
+
*/
|
|
10
|
+
'use strict';
|
|
11
|
+
|
|
12
|
+
const assert = require('assert');
|
|
13
|
+
const { spawnSync } = require('child_process');
|
|
14
|
+
const path = require('path');
|
|
15
|
+
const fs = require('fs');
|
|
16
|
+
const os = require('os');
|
|
17
|
+
|
|
18
|
+
const CLI_PATH = path.join(__dirname, '..', 'bin', 'cli.js');
|
|
19
|
+
const PROJECT_ROOT = path.join(__dirname, '..');
|
|
20
|
+
|
|
21
|
+
let passed = 0;
|
|
22
|
+
let failed = 0;
|
|
23
|
+
const errors = [];
|
|
24
|
+
|
|
25
|
+
function test(name, fn) {
|
|
26
|
+
try {
|
|
27
|
+
fn();
|
|
28
|
+
console.log(` ✓ ${name}`);
|
|
29
|
+
passed++;
|
|
30
|
+
} catch (err) {
|
|
31
|
+
console.error(` ✗ ${name}: ${err.message}`);
|
|
32
|
+
failed++;
|
|
33
|
+
errors.push({ name, error: err.message });
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// ─── Module imports ────────────────────────────────────────────────────────────
|
|
38
|
+
let validateSkill;
|
|
39
|
+
try {
|
|
40
|
+
({ validateSkill } = require('../lib/skill-authoring'));
|
|
41
|
+
} catch (e) {
|
|
42
|
+
console.error('\n FATAL: Cannot load skill-authoring module');
|
|
43
|
+
console.error(' ', e.message);
|
|
44
|
+
process.exit(1);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// ─── Helper: build a temporary skill dir ─────────────────────────────────────
|
|
48
|
+
function makeTmpSkill(fields = {}) {
|
|
49
|
+
const tmpBase = fs.mkdtempSync(path.join(os.tmpdir(), 'ma-validate-test-'));
|
|
50
|
+
const skillName = fields.skillName || 'test-skill';
|
|
51
|
+
const skillDir = path.join(tmpBase, skillName);
|
|
52
|
+
fs.mkdirSync(skillDir, { recursive: true });
|
|
53
|
+
|
|
54
|
+
if (fields.skillJson !== undefined) {
|
|
55
|
+
fs.writeFileSync(path.join(skillDir, 'skill.json'), fields.skillJson, 'utf8');
|
|
56
|
+
} else if (fields.skillJson !== null) {
|
|
57
|
+
// Default valid skill.json
|
|
58
|
+
const json = Object.assign({
|
|
59
|
+
name: 'Test Skill',
|
|
60
|
+
description: 'A test skill',
|
|
61
|
+
version: '1.0.0',
|
|
62
|
+
category: 'workflow',
|
|
63
|
+
author: 'ma-agents',
|
|
64
|
+
tags: ['test'],
|
|
65
|
+
always_load: false
|
|
66
|
+
}, fields.skillJsonOverride || {});
|
|
67
|
+
fs.writeFileSync(path.join(skillDir, 'skill.json'), JSON.stringify(json, null, 2), 'utf8');
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (fields.skillMd !== false) {
|
|
71
|
+
fs.writeFileSync(path.join(skillDir, 'SKILL.md'), '# Test Skill\n', 'utf8');
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (fields.templates) {
|
|
75
|
+
const templatesDir = path.join(skillDir, 'templates');
|
|
76
|
+
fs.mkdirSync(templatesDir, { recursive: true });
|
|
77
|
+
for (const [filename, content] of Object.entries(fields.templates)) {
|
|
78
|
+
fs.writeFileSync(path.join(templatesDir, filename), content, 'utf8');
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return { tmpBase, skillDir, skillName };
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// ─── Unit tests: validateSkill() ─────────────────────────────────────────────
|
|
86
|
+
console.log('\n validateSkill unit tests\n');
|
|
87
|
+
|
|
88
|
+
// AC #1: Valid skill
|
|
89
|
+
test('valid skill returns valid=true with metadata', () => {
|
|
90
|
+
const { tmpBase, skillName } = makeTmpSkill({});
|
|
91
|
+
try {
|
|
92
|
+
const result = validateSkill(skillName, tmpBase);
|
|
93
|
+
assert.strictEqual(result.valid, true, `Expected valid=true, got: ${JSON.stringify(result)}`);
|
|
94
|
+
assert.ok(Array.isArray(result.errors), 'Expected errors array');
|
|
95
|
+
assert.strictEqual(result.errors.length, 0, `Expected no errors, got: ${JSON.stringify(result.errors)}`);
|
|
96
|
+
assert.ok(result.metadata, 'Expected metadata object');
|
|
97
|
+
assert.strictEqual(result.metadata.name, 'Test Skill');
|
|
98
|
+
assert.strictEqual(result.metadata.version, '1.0.0');
|
|
99
|
+
} finally {
|
|
100
|
+
fs.rmSync(tmpBase, { recursive: true, force: true });
|
|
101
|
+
}
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
// AC #2: Missing skill.json
|
|
105
|
+
test('missing skill.json produces error', () => {
|
|
106
|
+
const { tmpBase, skillName } = makeTmpSkill({ skillJson: undefined });
|
|
107
|
+
// Remove the default skill.json by passing null-ish — need to NOT write it
|
|
108
|
+
fs.rmSync(path.join(tmpBase, skillName, 'skill.json'), { force: true });
|
|
109
|
+
try {
|
|
110
|
+
const result = validateSkill(skillName, tmpBase);
|
|
111
|
+
assert.strictEqual(result.valid, false);
|
|
112
|
+
assert.ok(result.errors.some(e => e.includes('skill.json')), `Expected skill.json error, got: ${JSON.stringify(result.errors)}`);
|
|
113
|
+
} finally {
|
|
114
|
+
fs.rmSync(tmpBase, { recursive: true, force: true });
|
|
115
|
+
}
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
// AC #3: Missing SKILL.md
|
|
119
|
+
test('missing SKILL.md produces error', () => {
|
|
120
|
+
const { tmpBase, skillName } = makeTmpSkill({ skillMd: false });
|
|
121
|
+
try {
|
|
122
|
+
const result = validateSkill(skillName, tmpBase);
|
|
123
|
+
assert.strictEqual(result.valid, false);
|
|
124
|
+
assert.ok(result.errors.some(e => e.includes('SKILL.md')), `Expected SKILL.md error, got: ${JSON.stringify(result.errors)}`);
|
|
125
|
+
} finally {
|
|
126
|
+
fs.rmSync(tmpBase, { recursive: true, force: true });
|
|
127
|
+
}
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
// Skill not found
|
|
131
|
+
test('non-existent skill returns error (not found)', () => {
|
|
132
|
+
const tmpBase = fs.mkdtempSync(path.join(os.tmpdir(), 'ma-validate-empty-'));
|
|
133
|
+
try {
|
|
134
|
+
const result = validateSkill('no-such-skill', tmpBase);
|
|
135
|
+
assert.strictEqual(result.valid, false);
|
|
136
|
+
assert.ok(result.notFound, 'Expected notFound flag');
|
|
137
|
+
} finally {
|
|
138
|
+
fs.rmSync(tmpBase, { recursive: true, force: true });
|
|
139
|
+
}
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
// AC #4: Invalid JSON
|
|
143
|
+
test('corrupt skill.json produces error', () => {
|
|
144
|
+
const { tmpBase, skillName } = makeTmpSkill({ skillJson: 'not valid json {{{' });
|
|
145
|
+
try {
|
|
146
|
+
const result = validateSkill(skillName, tmpBase);
|
|
147
|
+
assert.strictEqual(result.valid, false);
|
|
148
|
+
assert.ok(result.errors.some(e => e.toLowerCase().includes('json')), `Expected JSON error, got: ${JSON.stringify(result.errors)}`);
|
|
149
|
+
} finally {
|
|
150
|
+
fs.rmSync(tmpBase, { recursive: true, force: true });
|
|
151
|
+
}
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
// AC #4: Missing required fields
|
|
155
|
+
test('missing name field produces field error', () => {
|
|
156
|
+
const { tmpBase, skillName } = makeTmpSkill({ skillJsonOverride: { name: '' } });
|
|
157
|
+
try {
|
|
158
|
+
const result = validateSkill(skillName, tmpBase);
|
|
159
|
+
assert.strictEqual(result.valid, false);
|
|
160
|
+
assert.ok(result.errors.some(e => e.includes('name')), `Expected name error, got: ${JSON.stringify(result.errors)}`);
|
|
161
|
+
} finally {
|
|
162
|
+
fs.rmSync(tmpBase, { recursive: true, force: true });
|
|
163
|
+
}
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
test('missing version field produces field error', () => {
|
|
167
|
+
const { tmpBase, skillName } = makeTmpSkill({ skillJsonOverride: { version: undefined } });
|
|
168
|
+
// Remove version from written JSON
|
|
169
|
+
const skillDir = path.join(tmpBase, skillName);
|
|
170
|
+
const json = JSON.parse(fs.readFileSync(path.join(skillDir, 'skill.json'), 'utf8'));
|
|
171
|
+
delete json.version;
|
|
172
|
+
fs.writeFileSync(path.join(skillDir, 'skill.json'), JSON.stringify(json), 'utf8');
|
|
173
|
+
try {
|
|
174
|
+
const result = validateSkill(skillName, tmpBase);
|
|
175
|
+
assert.strictEqual(result.valid, false);
|
|
176
|
+
assert.ok(result.errors.some(e => e.includes('version')), `Expected version error, got: ${JSON.stringify(result.errors)}`);
|
|
177
|
+
} finally {
|
|
178
|
+
fs.rmSync(tmpBase, { recursive: true, force: true });
|
|
179
|
+
}
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
test('invalid version format produces field error', () => {
|
|
183
|
+
const { tmpBase, skillName } = makeTmpSkill({ skillJsonOverride: { version: 'v1.0' } });
|
|
184
|
+
try {
|
|
185
|
+
const result = validateSkill(skillName, tmpBase);
|
|
186
|
+
assert.strictEqual(result.valid, false);
|
|
187
|
+
assert.ok(result.errors.some(e => e.includes('version')), `Expected version error, got: ${JSON.stringify(result.errors)}`);
|
|
188
|
+
} finally {
|
|
189
|
+
fs.rmSync(tmpBase, { recursive: true, force: true });
|
|
190
|
+
}
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
test('invalid category produces field error', () => {
|
|
194
|
+
const { tmpBase, skillName } = makeTmpSkill({ skillJsonOverride: { category: 'invalid-cat' } });
|
|
195
|
+
try {
|
|
196
|
+
const result = validateSkill(skillName, tmpBase);
|
|
197
|
+
assert.strictEqual(result.valid, false);
|
|
198
|
+
assert.ok(result.errors.some(e => e.includes('category')), `Expected category error, got: ${JSON.stringify(result.errors)}`);
|
|
199
|
+
} finally {
|
|
200
|
+
fs.rmSync(tmpBase, { recursive: true, force: true });
|
|
201
|
+
}
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
test('non-boolean always_load produces field error', () => {
|
|
205
|
+
const { tmpBase, skillName } = makeTmpSkill({ skillJsonOverride: { always_load: 'yes' } });
|
|
206
|
+
try {
|
|
207
|
+
const result = validateSkill(skillName, tmpBase);
|
|
208
|
+
assert.strictEqual(result.valid, false);
|
|
209
|
+
assert.ok(result.errors.some(e => e.includes('always_load')), `Expected always_load error, got: ${JSON.stringify(result.errors)}`);
|
|
210
|
+
} finally {
|
|
211
|
+
fs.rmSync(tmpBase, { recursive: true, force: true });
|
|
212
|
+
}
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
test('non-array tags produces field error', () => {
|
|
216
|
+
const { tmpBase, skillName } = makeTmpSkill({ skillJsonOverride: { tags: 'not-an-array' } });
|
|
217
|
+
try {
|
|
218
|
+
const result = validateSkill(skillName, tmpBase);
|
|
219
|
+
assert.strictEqual(result.valid, false);
|
|
220
|
+
assert.ok(result.errors.some(e => e.includes('tags')), `Expected tags error, got: ${JSON.stringify(result.errors)}`);
|
|
221
|
+
} finally {
|
|
222
|
+
fs.rmSync(tmpBase, { recursive: true, force: true });
|
|
223
|
+
}
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
// AC #5: Template validation
|
|
227
|
+
test('valid agent template produces no warning', () => {
|
|
228
|
+
const { tmpBase, skillName } = makeTmpSkill({
|
|
229
|
+
templates: { 'claude-code.md': '# Template' }
|
|
230
|
+
});
|
|
231
|
+
try {
|
|
232
|
+
const result = validateSkill(skillName, tmpBase);
|
|
233
|
+
assert.strictEqual(result.valid, true, `Expected valid, got errors: ${JSON.stringify(result.errors)}`);
|
|
234
|
+
assert.ok(!result.warnings.some(w => w.includes('claude-code')), 'Expected no warning for known agent');
|
|
235
|
+
} finally {
|
|
236
|
+
fs.rmSync(tmpBase, { recursive: true, force: true });
|
|
237
|
+
}
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
test('unknown agent template produces warning', () => {
|
|
241
|
+
const { tmpBase, skillName } = makeTmpSkill({
|
|
242
|
+
templates: { 'unknown-agent.md': '# Template' }
|
|
243
|
+
});
|
|
244
|
+
try {
|
|
245
|
+
const result = validateSkill(skillName, tmpBase);
|
|
246
|
+
assert.ok(Array.isArray(result.warnings), 'Expected warnings array');
|
|
247
|
+
assert.ok(result.warnings.some(w => w.includes('unknown-agent')), `Expected unknown-agent warning, got: ${JSON.stringify(result.warnings)}`);
|
|
248
|
+
} finally {
|
|
249
|
+
fs.rmSync(tmpBase, { recursive: true, force: true });
|
|
250
|
+
}
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
test('unknown agent template does not make skill invalid', () => {
|
|
254
|
+
const { tmpBase, skillName } = makeTmpSkill({
|
|
255
|
+
templates: { 'unknown-agent.md': '# Template' }
|
|
256
|
+
});
|
|
257
|
+
try {
|
|
258
|
+
const result = validateSkill(skillName, tmpBase);
|
|
259
|
+
assert.strictEqual(result.valid, true, 'Warnings should not make skill invalid');
|
|
260
|
+
} finally {
|
|
261
|
+
fs.rmSync(tmpBase, { recursive: true, force: true });
|
|
262
|
+
}
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
// ─── CLI integration tests ─────────────────────────────────────────────────────
|
|
266
|
+
console.log('\n validate-skill CLI integration tests\n');
|
|
267
|
+
|
|
268
|
+
// Task 6.1: Valid skill
|
|
269
|
+
test('validate-skill: git-workflow-skill reports VALID with exit 0', () => {
|
|
270
|
+
const result = spawnSync('node', [CLI_PATH, 'validate-skill', 'git-workflow-skill'], {
|
|
271
|
+
encoding: 'utf8', cwd: PROJECT_ROOT
|
|
272
|
+
});
|
|
273
|
+
assert.strictEqual(result.status, 0, `Expected exit 0. stderr: ${result.stderr}`);
|
|
274
|
+
assert.ok(result.stdout.includes('VALID'), `Expected VALID in output: ${result.stdout}`);
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
test('validate-skill: valid skill prints metadata summary', () => {
|
|
278
|
+
const result = spawnSync('node', [CLI_PATH, 'validate-skill', 'git-workflow-skill'], {
|
|
279
|
+
encoding: 'utf8', cwd: PROJECT_ROOT
|
|
280
|
+
});
|
|
281
|
+
assert.ok(result.stdout.includes('Version'), 'Expected Version in output');
|
|
282
|
+
assert.ok(result.stdout.includes('Category') || result.stdout.includes('Name'), 'Expected metadata fields');
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
// Non-existent skill
|
|
286
|
+
test('validate-skill: unknown skill exits 1 with error', () => {
|
|
287
|
+
const result = spawnSync('node', [CLI_PATH, 'validate-skill', 'no-such-skill'], {
|
|
288
|
+
encoding: 'utf8', cwd: PROJECT_ROOT
|
|
289
|
+
});
|
|
290
|
+
assert.strictEqual(result.status, 1, 'Expected exit 1 for missing skill');
|
|
291
|
+
const output = result.stdout + result.stderr;
|
|
292
|
+
assert.ok(output.includes('not found') || output.includes('Error'), 'Expected not-found error');
|
|
293
|
+
});
|
|
294
|
+
|
|
295
|
+
// Task 6.2: Broken skill — create deliberately invalid skill, validate, cleanup
|
|
296
|
+
test('validate-skill: INVALID skill exits 1', () => {
|
|
297
|
+
const brokenSkill = 'broken-test-skill';
|
|
298
|
+
const brokenDir = path.join(PROJECT_ROOT, 'skills', brokenSkill);
|
|
299
|
+
try {
|
|
300
|
+
fs.mkdirSync(brokenDir, { recursive: true });
|
|
301
|
+
// Deliberately invalid: missing SKILL.md, bad skill.json
|
|
302
|
+
fs.writeFileSync(path.join(brokenDir, 'skill.json'), JSON.stringify({
|
|
303
|
+
name: '',
|
|
304
|
+
version: 'bad-version',
|
|
305
|
+
category: 'invalid-cat'
|
|
306
|
+
}), 'utf8');
|
|
307
|
+
|
|
308
|
+
const result = spawnSync('node', [CLI_PATH, 'validate-skill', brokenSkill], {
|
|
309
|
+
encoding: 'utf8', cwd: PROJECT_ROOT
|
|
310
|
+
});
|
|
311
|
+
assert.strictEqual(result.status, 1, `Expected exit 1. stdout: ${result.stdout}`);
|
|
312
|
+
assert.ok(result.stdout.includes('INVALID'), `Expected INVALID in output: ${result.stdout}`);
|
|
313
|
+
} finally {
|
|
314
|
+
fs.rmSync(brokenDir, { recursive: true, force: true });
|
|
315
|
+
}
|
|
316
|
+
});
|
|
317
|
+
|
|
318
|
+
test('validate-skill: INVALID output lists individual field errors', () => {
|
|
319
|
+
const brokenSkill = 'broken-test-skill2';
|
|
320
|
+
const brokenDir = path.join(PROJECT_ROOT, 'skills', brokenSkill);
|
|
321
|
+
try {
|
|
322
|
+
fs.mkdirSync(brokenDir, { recursive: true });
|
|
323
|
+
fs.writeFileSync(path.join(brokenDir, 'skill.json'), JSON.stringify({
|
|
324
|
+
name: '',
|
|
325
|
+
version: 'bad',
|
|
326
|
+
category: 'invalid-cat'
|
|
327
|
+
}), 'utf8');
|
|
328
|
+
|
|
329
|
+
const result = spawnSync('node', [CLI_PATH, 'validate-skill', brokenSkill], {
|
|
330
|
+
encoding: 'utf8', cwd: PROJECT_ROOT
|
|
331
|
+
});
|
|
332
|
+
assert.ok(result.stdout.includes('name'), `Expected name error in output: ${result.stdout}`);
|
|
333
|
+
assert.ok(result.stdout.includes('version'), `Expected version error in output: ${result.stdout}`);
|
|
334
|
+
assert.ok(result.stdout.includes('category'), `Expected category error in output: ${result.stdout}`);
|
|
335
|
+
} finally {
|
|
336
|
+
fs.rmSync(brokenDir, { recursive: true, force: true });
|
|
337
|
+
}
|
|
338
|
+
});
|
|
339
|
+
|
|
340
|
+
// Task 6.3: Unknown agent template warning
|
|
341
|
+
test('validate-skill: warns about unknown agent template', () => {
|
|
342
|
+
const warnSkill = 'warn-template-skill';
|
|
343
|
+
const warnDir = path.join(PROJECT_ROOT, 'skills', warnSkill);
|
|
344
|
+
try {
|
|
345
|
+
fs.mkdirSync(path.join(warnDir, 'templates'), { recursive: true });
|
|
346
|
+
fs.writeFileSync(path.join(warnDir, 'skill.json'), JSON.stringify({
|
|
347
|
+
name: 'Warn Skill', description: 'test', version: '1.0.0'
|
|
348
|
+
}), 'utf8');
|
|
349
|
+
fs.writeFileSync(path.join(warnDir, 'SKILL.md'), '# Warn Skill\n', 'utf8');
|
|
350
|
+
fs.writeFileSync(path.join(warnDir, 'templates', 'unknown-agent.md'), '# Template\n', 'utf8');
|
|
351
|
+
|
|
352
|
+
const result = spawnSync('node', [CLI_PATH, 'validate-skill', warnSkill], {
|
|
353
|
+
encoding: 'utf8', cwd: PROJECT_ROOT
|
|
354
|
+
});
|
|
355
|
+
assert.strictEqual(result.status, 0, `Expected exit 0 (warnings don't fail). stderr: ${result.stderr}`);
|
|
356
|
+
assert.ok(
|
|
357
|
+
result.stdout.includes('unknown-agent') || result.stdout.includes('Warning'),
|
|
358
|
+
`Expected warning about unknown-agent: ${result.stdout}`
|
|
359
|
+
);
|
|
360
|
+
} finally {
|
|
361
|
+
fs.rmSync(warnDir, { recursive: true, force: true });
|
|
362
|
+
}
|
|
363
|
+
});
|
|
364
|
+
|
|
365
|
+
// No skill name provided
|
|
366
|
+
test('validate-skill: no skill name exits 1 with usage hint', () => {
|
|
367
|
+
const result = spawnSync('node', [CLI_PATH, 'validate-skill'], {
|
|
368
|
+
encoding: 'utf8', cwd: PROJECT_ROOT
|
|
369
|
+
});
|
|
370
|
+
assert.strictEqual(result.status, 1, 'Expected exit 1 for missing skill name');
|
|
371
|
+
});
|
|
372
|
+
|
|
373
|
+
// ─── Results ──────────────────────────────────────────────────────────────────
|
|
374
|
+
console.log(`\n ${passed} passed, ${failed} failed\n`);
|
|
375
|
+
if (failed > 0) {
|
|
376
|
+
errors.forEach(e => console.error(` ✗ ${e.name}: ${e.error}`));
|
|
377
|
+
process.exit(1);
|
|
378
|
+
}
|
package/test/yes-flag.test.js
CHANGED
|
@@ -175,7 +175,7 @@ test('--yes exits 0 with piped stdin (no interactive prompts)', () => {
|
|
|
175
175
|
// Note: this performs actual skill installation in the current project directory.
|
|
176
176
|
// It verifies that the process exits without hanging on interactive prompts.
|
|
177
177
|
const result = spawnSync(process.execPath, [CLI_PATH, 'install', '--yes'], {
|
|
178
|
-
timeout:
|
|
178
|
+
timeout: 300000, // 5 min — BMAD module installation copies hundreds of files
|
|
179
179
|
encoding: 'utf-8',
|
|
180
180
|
input: '',
|
|
181
181
|
stdio: ['pipe', 'pipe', 'pipe'],
|