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,237 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Tests for Story 8.3: Create BMAD Extension Module Structure
|
|
4
|
+
*
|
|
5
|
+
* Validates extension module files, directory structure, customize.yaml contents,
|
|
6
|
+
* and deployment logic in bmad.js.
|
|
7
|
+
*/
|
|
8
|
+
'use strict';
|
|
9
|
+
|
|
10
|
+
const assert = require('assert');
|
|
11
|
+
const fs = require('fs');
|
|
12
|
+
const path = require('path');
|
|
13
|
+
|
|
14
|
+
let passed = 0;
|
|
15
|
+
let failed = 0;
|
|
16
|
+
const errors = [];
|
|
17
|
+
|
|
18
|
+
function test(name, fn) {
|
|
19
|
+
try {
|
|
20
|
+
fn();
|
|
21
|
+
console.log(` \u2713 ${name}`);
|
|
22
|
+
passed++;
|
|
23
|
+
} catch (err) {
|
|
24
|
+
console.error(` \u2717 ${name}: ${err.message}`);
|
|
25
|
+
failed++;
|
|
26
|
+
errors.push({ name, error: err.message });
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const extDir = path.join(__dirname, '..', 'lib', 'bmad-extension');
|
|
31
|
+
const agentsDir = path.join(extDir, 'agents');
|
|
32
|
+
|
|
33
|
+
// ─── AC #1: module.yaml, module-help.csv, and 11 agent files exist ──────────
|
|
34
|
+
|
|
35
|
+
console.log('\nAC #1 — Extension module structure');
|
|
36
|
+
|
|
37
|
+
test('1.1: lib/bmad-extension/ directory exists', () => {
|
|
38
|
+
assert.ok(fs.existsSync(extDir), 'lib/bmad-extension/ must exist');
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
test('1.2: lib/bmad-extension/agents/ directory exists', () => {
|
|
42
|
+
assert.ok(fs.existsSync(agentsDir), 'lib/bmad-extension/agents/ must exist');
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
test('1.3: module.yaml exists with extends-module: bmm', () => {
|
|
46
|
+
const filePath = path.join(extDir, 'module.yaml');
|
|
47
|
+
assert.ok(fs.existsSync(filePath), 'module.yaml must exist');
|
|
48
|
+
const content = fs.readFileSync(filePath, 'utf-8');
|
|
49
|
+
assert.ok(content.includes('extends-module: bmm'), 'module.yaml must declare extends-module: bmm');
|
|
50
|
+
assert.ok(content.includes('name: ma-agents-skills'), 'module.yaml must have name: ma-agents-skills');
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
test('1.4: module-help.csv exists', () => {
|
|
54
|
+
const filePath = path.join(extDir, 'module-help.csv');
|
|
55
|
+
assert.ok(fs.existsSync(filePath), 'module-help.csv must exist');
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
test('1.5: agents/ contains exactly 11 .customize.yaml files', () => {
|
|
59
|
+
const files = fs.readdirSync(agentsDir).filter(f => f.endsWith('.customize.yaml'));
|
|
60
|
+
assert.strictEqual(files.length, 11, `Expected 11 .customize.yaml files, found ${files.length}`);
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
// ─── AC #2: 4 custom agents have full customization + critical_actions ──────
|
|
64
|
+
|
|
65
|
+
console.log('\nAC #2 — Custom agents: persona + menu + critical_actions');
|
|
66
|
+
|
|
67
|
+
const customAgents = [
|
|
68
|
+
{ id: 'bmm-sre', slug: 'sre' },
|
|
69
|
+
{ id: 'bmm-devops', slug: 'devops' },
|
|
70
|
+
{ id: 'bmm-cyber', slug: 'cyber' },
|
|
71
|
+
{ id: 'bmm-mil498', slug: 'mil498' }
|
|
72
|
+
];
|
|
73
|
+
|
|
74
|
+
for (const agent of customAgents) {
|
|
75
|
+
test(`2.${customAgents.indexOf(agent) + 1}: ${agent.id}.customize.yaml has persona, menu, AND critical_actions`, () => {
|
|
76
|
+
const filePath = path.join(agentsDir, `${agent.id}.customize.yaml`);
|
|
77
|
+
assert.ok(fs.existsSync(filePath), `${agent.id}.customize.yaml must exist`);
|
|
78
|
+
const content = fs.readFileSync(filePath, 'utf-8');
|
|
79
|
+
|
|
80
|
+
assert.ok(content.includes('persona:'), `${agent.id} must have persona section`);
|
|
81
|
+
assert.ok(content.includes('menu:'), `${agent.id} must have menu section`);
|
|
82
|
+
assert.ok(content.includes('critical_actions:'), `${agent.id} must have critical_actions section`);
|
|
83
|
+
assert.ok(content.includes(`skills/${agent.slug}/MANIFEST.yaml`),
|
|
84
|
+
`${agent.id} critical_actions must reference skills/${agent.slug}/MANIFEST.yaml`);
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
test('2.5: custom agent critical_actions have all 3 steps', () => {
|
|
89
|
+
for (const agent of customAgents) {
|
|
90
|
+
const content = fs.readFileSync(path.join(agentsDir, `${agent.id}.customize.yaml`), 'utf-8');
|
|
91
|
+
assert.ok(content.includes('1:'), `${agent.id} must have step 1`);
|
|
92
|
+
assert.ok(content.includes('2:'), `${agent.id} must have step 2`);
|
|
93
|
+
assert.ok(content.includes('3:'), `${agent.id} must have step 3`);
|
|
94
|
+
assert.ok(content.includes('always_load: true'), `${agent.id} step 2 must reference always_load`);
|
|
95
|
+
assert.ok(content.includes('Follow all skill directives'), `${agent.id} step 3 must follow directives`);
|
|
96
|
+
}
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
// ─── AC #3: 7 built-in agents have ONLY critical_actions ────────────────────
|
|
100
|
+
|
|
101
|
+
console.log('\nAC #3 — Built-in agents: critical_actions only');
|
|
102
|
+
|
|
103
|
+
const builtinAgents = [
|
|
104
|
+
'bmm-pm', 'bmm-architect', 'bmm-dev', 'bmm-qa',
|
|
105
|
+
'bmm-sm', 'bmm-tech-writer', 'bmm-ux-designer'
|
|
106
|
+
];
|
|
107
|
+
|
|
108
|
+
for (const agentId of builtinAgents) {
|
|
109
|
+
test(`3.${builtinAgents.indexOf(agentId) + 1}: ${agentId}.customize.yaml has ONLY critical_actions (no persona/menu)`, () => {
|
|
110
|
+
const filePath = path.join(agentsDir, `${agentId}.customize.yaml`);
|
|
111
|
+
assert.ok(fs.existsSync(filePath), `${agentId}.customize.yaml must exist`);
|
|
112
|
+
const content = fs.readFileSync(filePath, 'utf-8');
|
|
113
|
+
|
|
114
|
+
assert.ok(content.includes('critical_actions:'), `${agentId} must have critical_actions`);
|
|
115
|
+
assert.ok(!content.includes('persona:'), `${agentId} must NOT have persona section`);
|
|
116
|
+
assert.ok(!content.includes('menu:'), `${agentId} must NOT have menu section`);
|
|
117
|
+
assert.ok(content.includes('skills/MANIFEST.yaml'), `${agentId} must reference project-level skills/MANIFEST.yaml`);
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// ─── AC #4: bmad.js has extension deployment stage ──────────────────────────
|
|
122
|
+
|
|
123
|
+
console.log('\nAC #4 — bmad.js extension deployment');
|
|
124
|
+
|
|
125
|
+
test('4.1: bmad.js contains extension deployment code', () => {
|
|
126
|
+
const bmadSrc = fs.readFileSync(path.join(__dirname, '..', 'lib', 'bmad.js'), 'utf-8');
|
|
127
|
+
assert.ok(bmadSrc.includes("'bmad-extension'"), 'bmad.js must reference bmad-extension source dir');
|
|
128
|
+
assert.ok(bmadSrc.includes("'extensions'"), 'bmad.js must reference extensions target dir');
|
|
129
|
+
assert.ok(bmadSrc.includes("'ma-agents-skills'"), 'bmad.js must reference ma-agents-skills');
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
test('4.2: extension deployment uses path.join for all paths', () => {
|
|
133
|
+
const bmadSrc = fs.readFileSync(path.join(__dirname, '..', 'lib', 'bmad.js'), 'utf-8');
|
|
134
|
+
assert.ok(bmadSrc.includes("path.join(__dirname, 'bmad-extension')"),
|
|
135
|
+
'extensionSource must use path.join');
|
|
136
|
+
assert.ok(bmadSrc.includes("path.join(projectRoot, BMAD_DIR, 'extensions', 'ma-agents-skills')"),
|
|
137
|
+
'extensionTarget must use path.join');
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
test('4.3: extension deployment is AFTER recompile and BEFORE workflows', () => {
|
|
141
|
+
// Uses STAGE: markers in bmad.js comments — these are test-coupled anchors (see M2 review)
|
|
142
|
+
const bmadSrc = fs.readFileSync(path.join(__dirname, '..', 'lib', 'bmad.js'), 'utf-8');
|
|
143
|
+
const recompileIdx = bmadSrc.indexOf('STAGE:RECOMPILE');
|
|
144
|
+
const extensionIdx = bmadSrc.indexOf('STAGE:EXTENSION');
|
|
145
|
+
const workflowIdx = bmadSrc.indexOf('STAGE:WORKFLOWS');
|
|
146
|
+
|
|
147
|
+
assert.ok(recompileIdx > 0, 'STAGE:RECOMPILE marker must exist');
|
|
148
|
+
assert.ok(extensionIdx > 0, 'STAGE:EXTENSION marker must exist');
|
|
149
|
+
assert.ok(workflowIdx > 0, 'STAGE:WORKFLOWS marker must exist');
|
|
150
|
+
assert.ok(extensionIdx > recompileIdx, 'extension deployment must come after recompile');
|
|
151
|
+
assert.ok(extensionIdx < workflowIdx, 'extension deployment must come before workflows');
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
test('4.4: extension deployment logs with chalk.cyan', () => {
|
|
155
|
+
const bmadSrc = fs.readFileSync(path.join(__dirname, '..', 'lib', 'bmad.js'), 'utf-8');
|
|
156
|
+
assert.ok(bmadSrc.includes("chalk.cyan(' + Deployed BMAD extension module: ma-agents-skills')"),
|
|
157
|
+
'must log deployment with chalk.cyan');
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
test('4.5: existing bmad-customizations files are preserved (backward compatibility)', () => {
|
|
161
|
+
const customDir = path.join(__dirname, '..', 'lib', 'bmad-customizations');
|
|
162
|
+
for (const agent of customAgents) {
|
|
163
|
+
const filePath = path.join(customDir, `${agent.id}.customize.yaml`);
|
|
164
|
+
assert.ok(fs.existsSync(filePath), `${agent.id}.customize.yaml must still exist in bmad-customizations/`);
|
|
165
|
+
}
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
// ─── M3: Integration test — actual fs.copy deployment ───────────────────────
|
|
169
|
+
|
|
170
|
+
console.log('\nIntegration — applyCustomizations() deploys extension');
|
|
171
|
+
|
|
172
|
+
const fsExtra = require('fs-extra');
|
|
173
|
+
const os = require('os');
|
|
174
|
+
const { applyCustomizations } = require('../lib/bmad');
|
|
175
|
+
|
|
176
|
+
async function asyncTest(name, fn) {
|
|
177
|
+
try {
|
|
178
|
+
await fn();
|
|
179
|
+
console.log(` \u2713 ${name}`);
|
|
180
|
+
passed++;
|
|
181
|
+
} catch (err) {
|
|
182
|
+
console.error(` \u2717 ${name}: ${err.message}`);
|
|
183
|
+
failed++;
|
|
184
|
+
errors.push({ name, error: err.message });
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
async function runIntegrationTests() {
|
|
189
|
+
const tmpDir = path.join(os.tmpdir(), `bmad-ext-test-${Date.now()}`);
|
|
190
|
+
|
|
191
|
+
await asyncTest('INT-1: applyCustomizations deploys extension module to target directory', async () => {
|
|
192
|
+
// Set up minimal BMAD structure so applyCustomizations doesn't fail on missing dirs
|
|
193
|
+
await fsExtra.ensureDir(path.join(tmpDir, '_bmad', '_config', 'agents'));
|
|
194
|
+
await fsExtra.ensureDir(path.join(tmpDir, '_bmad', 'custom', 'agents'));
|
|
195
|
+
await fsExtra.ensureDir(path.join(tmpDir, '_bmad', 'bmm', 'agents'));
|
|
196
|
+
|
|
197
|
+
// Run applyCustomizations — recompile will fail (no BMAD installed in temp) but
|
|
198
|
+
// extension deployment happens after recompile regardless of recompile outcome
|
|
199
|
+
await applyCustomizations(tmpDir, ['bmm'], [], []);
|
|
200
|
+
|
|
201
|
+
// Verify extension was deployed
|
|
202
|
+
const deployedModule = path.join(tmpDir, '_bmad', 'extensions', 'ma-agents-skills', 'module.yaml');
|
|
203
|
+
assert.ok(fsExtra.existsSync(deployedModule),
|
|
204
|
+
`module.yaml must exist at ${deployedModule}`);
|
|
205
|
+
|
|
206
|
+
const content = fsExtra.readFileSync(deployedModule, 'utf-8');
|
|
207
|
+
assert.ok(content.includes('extends-module: bmm'),
|
|
208
|
+
'deployed module.yaml must contain extends-module: bmm');
|
|
209
|
+
|
|
210
|
+
// Verify agent files deployed
|
|
211
|
+
const deployedAgents = path.join(tmpDir, '_bmad', 'extensions', 'ma-agents-skills', 'agents');
|
|
212
|
+
assert.ok(fsExtra.existsSync(deployedAgents), 'agents/ directory must be deployed');
|
|
213
|
+
const files = fsExtra.readdirSync(deployedAgents).filter(f => f.endsWith('.customize.yaml'));
|
|
214
|
+
assert.strictEqual(files.length, 11, `Expected 11 deployed agent files, found ${files.length}`);
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
// Cleanup
|
|
218
|
+
await fsExtra.remove(tmpDir);
|
|
219
|
+
|
|
220
|
+
printSummary();
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
function printSummary() {
|
|
224
|
+
// ─── Summary ────────────────────────────────────────────────────────────────
|
|
225
|
+
|
|
226
|
+
console.log(`\n${'─'.repeat(60)}`);
|
|
227
|
+
console.log(`Results: ${passed} passed, ${failed} failed`);
|
|
228
|
+
if (errors.length > 0) {
|
|
229
|
+
console.log('\nFailures:');
|
|
230
|
+
for (const { name, error } of errors) {
|
|
231
|
+
console.log(` ${name}: ${error}`);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
process.exit(failed > 0 ? 1 : 0);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
runIntegrationTests();
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
const assert = require('assert');
|
|
2
|
+
const { test } = require('node:test');
|
|
3
|
+
const path = require('path');
|
|
4
|
+
const fs = require('fs');
|
|
5
|
+
const os = require('os');
|
|
6
|
+
|
|
7
|
+
// Helper: create a temp project dir with a .gitignore
|
|
8
|
+
function makeProject(gitignoreContent) {
|
|
9
|
+
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'bmad-test-'));
|
|
10
|
+
if (gitignoreContent !== null) {
|
|
11
|
+
fs.writeFileSync(path.join(dir, '.gitignore'), gitignoreContent, 'utf-8');
|
|
12
|
+
}
|
|
13
|
+
return dir;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const { ensureBmadOutputTracked } = require('../lib/installer');
|
|
17
|
+
|
|
18
|
+
const BMAD_OUTPUT_PATTERNS = ['_bmad-output', '_bmad-output/', '/_bmad-output', '/_bmad-output/'];
|
|
19
|
+
|
|
20
|
+
// ─── Story 10.1 — ensureBmadOutputTracked() unit tests ───────────────────────
|
|
21
|
+
|
|
22
|
+
test('AC1 — removes _bmad-output/ and prints message', () => {
|
|
23
|
+
const dir = makeProject('node_modules/\n_bmad-output/\ndist/\n');
|
|
24
|
+
const logs = [];
|
|
25
|
+
const orig = console.log;
|
|
26
|
+
console.log = (...args) => logs.push(args.join(' '));
|
|
27
|
+
ensureBmadOutputTracked(dir);
|
|
28
|
+
console.log = orig;
|
|
29
|
+
const written = fs.readFileSync(path.join(dir, '.gitignore'), 'utf-8');
|
|
30
|
+
assert.ok(!written.includes('_bmad-output'), 'entry must be removed');
|
|
31
|
+
assert.ok(
|
|
32
|
+
logs.some(l => l.includes('_bmad-output is now tracked as project knowledge (not gitignored)')),
|
|
33
|
+
'must print the tracking message'
|
|
34
|
+
);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
test('AC2 — no write when .gitignore has no _bmad-output entry', () => {
|
|
38
|
+
const dir = makeProject('node_modules/\ndist/\n');
|
|
39
|
+
const before = fs.statSync(path.join(dir, '.gitignore')).mtimeMs;
|
|
40
|
+
ensureBmadOutputTracked(dir);
|
|
41
|
+
const after = fs.statSync(path.join(dir, '.gitignore')).mtimeMs;
|
|
42
|
+
assert.strictEqual(before, after, 'file must not be written');
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
test('AC3 — no error and no file created when .gitignore absent', () => {
|
|
46
|
+
const dir = makeProject(null);
|
|
47
|
+
assert.doesNotThrow(() => ensureBmadOutputTracked(dir));
|
|
48
|
+
assert.ok(!fs.existsSync(path.join(dir, '.gitignore')), '.gitignore must not be created');
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
test('Windows CRLF — detects and removes _bmad-output/\\r\\n', () => {
|
|
52
|
+
const dir = makeProject('node_modules/\r\n_bmad-output/\r\ndist/\r\n');
|
|
53
|
+
ensureBmadOutputTracked(dir);
|
|
54
|
+
const written = fs.readFileSync(path.join(dir, '.gitignore'), 'utf-8');
|
|
55
|
+
assert.ok(!written.includes('_bmad-output'), 'CRLF entry must be removed');
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
test('Idempotency — second run does not write', () => {
|
|
59
|
+
const dir = makeProject('node_modules/\n_bmad-output/\n');
|
|
60
|
+
ensureBmadOutputTracked(dir); // first run — removes it
|
|
61
|
+
const after1 = fs.statSync(path.join(dir, '.gitignore')).mtimeMs;
|
|
62
|
+
ensureBmadOutputTracked(dir); // second run — no-op
|
|
63
|
+
const after2 = fs.statSync(path.join(dir, '.gitignore')).mtimeMs;
|
|
64
|
+
assert.strictEqual(after1, after2, 'second run must not write');
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
test('Own .gitignore contains no _bmad-output entry', () => {
|
|
68
|
+
const gitignore = fs.readFileSync(path.join(__dirname, '..', '.gitignore'), 'utf-8');
|
|
69
|
+
const active = gitignore.split(/\r?\n/).filter(l => !l.trim().startsWith('#'));
|
|
70
|
+
assert.ok(
|
|
71
|
+
!active.some(l => BMAD_OUTPUT_PATTERNS.includes(l.trim())),
|
|
72
|
+
'own .gitignore must not contain _bmad-output'
|
|
73
|
+
);
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
// ─── Story 10.2 — README _bmad-output/ policy documentation tests ─────────────
|
|
77
|
+
|
|
78
|
+
const README_PATH = path.join(__dirname, '..', 'README.md');
|
|
79
|
+
const readmeContent = fs.readFileSync(README_PATH, 'utf-8');
|
|
80
|
+
|
|
81
|
+
test('README contains _bmad-output/ policy section heading', () => {
|
|
82
|
+
assert.ok(
|
|
83
|
+
readmeContent.includes('## Project Knowledge: _bmad-output/'),
|
|
84
|
+
'README must contain heading "## Project Knowledge: _bmad-output/"'
|
|
85
|
+
);
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
test('README contains explicit do-not-gitignore directive', () => {
|
|
89
|
+
assert.ok(
|
|
90
|
+
readmeContent.includes('Do not add `_bmad-output/` to your `.gitignore`'),
|
|
91
|
+
'README must contain the phrase "Do not add `_bmad-output/` to your `.gitignore`"'
|
|
92
|
+
);
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
test('README explains why _bmad-output/ is tracked (team alignment)', () => {
|
|
96
|
+
const mentionsReason =
|
|
97
|
+
readmeContent.includes('team alignment') ||
|
|
98
|
+
readmeContent.includes('AI context') ||
|
|
99
|
+
readmeContent.includes('planning artifacts');
|
|
100
|
+
assert.ok(
|
|
101
|
+
mentionsReason,
|
|
102
|
+
'README must mention at least one reason for tracking (team alignment, AI context, planning artifacts)'
|
|
103
|
+
);
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
test('README mentions installer removes _bmad-output/ from .gitignore', () => {
|
|
107
|
+
assert.ok(
|
|
108
|
+
readmeContent.includes('installer') && readmeContent.includes('.gitignore') && readmeContent.includes('_bmad-output'),
|
|
109
|
+
'README must mention installer behaviour around .gitignore and _bmad-output/'
|
|
110
|
+
);
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
test('_bmad-output/ section appears within first two major sections after Installation', () => {
|
|
114
|
+
const sectionIdx = readmeContent.indexOf('## Project Knowledge: _bmad-output/');
|
|
115
|
+
// Count H2 headings that appear before this section
|
|
116
|
+
const before = readmeContent.slice(0, sectionIdx);
|
|
117
|
+
const h2sBefore = (before.match(/^## /gm) || []).length;
|
|
118
|
+
assert.ok(h2sBefore <= 3, `Section must be within the first few major sections, found ${h2sBefore} H2s before it`);
|
|
119
|
+
});
|