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.
Files changed (149) hide show
  1. package/.opencode/skills/.ma-agents.json +241 -0
  2. package/.opencode/skills/MANIFEST.yaml +254 -0
  3. package/.opencode/skills/ai-audit-trail/SKILL.md +23 -0
  4. package/.opencode/skills/auto-bug-detection/SKILL.md +169 -0
  5. package/.opencode/skills/cmake-best-practices/SKILL.md +64 -0
  6. package/.opencode/skills/cmake-best-practices/examples/cmake.md +59 -0
  7. package/.opencode/skills/code-documentation/SKILL.md +57 -0
  8. package/.opencode/skills/code-documentation/examples/cpp.md +29 -0
  9. package/.opencode/skills/code-documentation/examples/csharp.md +28 -0
  10. package/.opencode/skills/code-documentation/examples/javascript_typescript.md +28 -0
  11. package/.opencode/skills/code-documentation/examples/python.md +57 -0
  12. package/.opencode/skills/code-review/SKILL.md +43 -0
  13. package/.opencode/skills/commit-message/SKILL.md +79 -0
  14. package/.opencode/skills/cpp-best-practices/SKILL.md +234 -0
  15. package/.opencode/skills/cpp-best-practices/examples/modern-idioms.md +189 -0
  16. package/.opencode/skills/cpp-best-practices/examples/naming-and-organization.md +102 -0
  17. package/.opencode/skills/cpp-concurrency-safety/SKILL.md +60 -0
  18. package/.opencode/skills/cpp-concurrency-safety/examples/concurrency.md +73 -0
  19. package/.opencode/skills/cpp-const-correctness/SKILL.md +63 -0
  20. package/.opencode/skills/cpp-const-correctness/examples/const_correctness.md +54 -0
  21. package/.opencode/skills/cpp-memory-handling/SKILL.md +42 -0
  22. package/.opencode/skills/cpp-memory-handling/examples/modern-cpp.md +49 -0
  23. package/.opencode/skills/cpp-memory-handling/examples/smart-pointers.md +46 -0
  24. package/.opencode/skills/cpp-modern-composition/SKILL.md +64 -0
  25. package/.opencode/skills/cpp-modern-composition/examples/composition.md +51 -0
  26. package/.opencode/skills/cpp-robust-interfaces/SKILL.md +55 -0
  27. package/.opencode/skills/cpp-robust-interfaces/examples/interfaces.md +56 -0
  28. package/.opencode/skills/create-hardened-docker-skill/SKILL.md +637 -0
  29. package/.opencode/skills/create-hardened-docker-skill/scripts/create-all.sh +489 -0
  30. package/.opencode/skills/csharp-best-practices/SKILL.md +278 -0
  31. package/.opencode/skills/docker-hardening-verification/SKILL.md +28 -0
  32. package/.opencode/skills/docker-hardening-verification/scripts/verify-hardening.sh +39 -0
  33. package/.opencode/skills/docker-image-signing/SKILL.md +28 -0
  34. package/.opencode/skills/docker-image-signing/scripts/sign-image.sh +33 -0
  35. package/.opencode/skills/document-revision-history/SKILL.md +104 -0
  36. package/.opencode/skills/git-workflow-skill/SKILL.md +194 -0
  37. package/.opencode/skills/git-workflow-skill/hooks/commit-msg +61 -0
  38. package/.opencode/skills/git-workflow-skill/hooks/pre-commit +38 -0
  39. package/.opencode/skills/git-workflow-skill/hooks/prepare-commit-msg +56 -0
  40. package/.opencode/skills/git-workflow-skill/scripts/finish-feature.sh +192 -0
  41. package/.opencode/skills/git-workflow-skill/scripts/install-hooks.sh +55 -0
  42. package/.opencode/skills/git-workflow-skill/scripts/start-feature.sh +110 -0
  43. package/.opencode/skills/git-workflow-skill/scripts/validate-workflow.sh +229 -0
  44. package/.opencode/skills/js-ts-dependency-mgmt/SKILL.md +49 -0
  45. package/.opencode/skills/js-ts-dependency-mgmt/examples/dependency_mgmt.md +60 -0
  46. package/.opencode/skills/js-ts-security-skill/SKILL.md +64 -0
  47. package/.opencode/skills/js-ts-security-skill/scripts/verify-security.sh +136 -0
  48. package/.opencode/skills/logging-best-practices/SKILL.md +50 -0
  49. package/.opencode/skills/logging-best-practices/examples/cpp.md +36 -0
  50. package/.opencode/skills/logging-best-practices/examples/csharp.md +49 -0
  51. package/.opencode/skills/logging-best-practices/examples/javascript.md +77 -0
  52. package/.opencode/skills/logging-best-practices/examples/python.md +57 -0
  53. package/.opencode/skills/logging-best-practices/references/logging-standards.md +29 -0
  54. package/.opencode/skills/open-presentation/SKILL.md +35 -0
  55. package/.opencode/skills/opentelemetry-best-practices/SKILL.md +34 -0
  56. package/.opencode/skills/opentelemetry-best-practices/examples/go.md +32 -0
  57. package/.opencode/skills/opentelemetry-best-practices/examples/javascript.md +58 -0
  58. package/.opencode/skills/opentelemetry-best-practices/examples/python.md +37 -0
  59. package/.opencode/skills/opentelemetry-best-practices/references/otel-standards.md +37 -0
  60. package/.opencode/skills/python-best-practices/SKILL.md +385 -0
  61. package/.opencode/skills/python-dependency-mgmt/SKILL.md +42 -0
  62. package/.opencode/skills/python-dependency-mgmt/examples/dependency_mgmt.md +67 -0
  63. package/.opencode/skills/python-security-skill/SKILL.md +56 -0
  64. package/.opencode/skills/python-security-skill/examples/security.md +56 -0
  65. package/.opencode/skills/self-signed-cert/SKILL.md +42 -0
  66. package/.opencode/skills/self-signed-cert/scripts/generate-cert.ps1 +45 -0
  67. package/.opencode/skills/self-signed-cert/scripts/generate-cert.sh +43 -0
  68. package/.opencode/skills/skill-creator/SKILL.md +196 -0
  69. package/.opencode/skills/skill-creator/references/output-patterns.md +82 -0
  70. package/.opencode/skills/skill-creator/references/workflows.md +28 -0
  71. package/.opencode/skills/skill-creator/scripts/init_skill.py +208 -0
  72. package/.opencode/skills/skill-creator/scripts/package_skill.py +99 -0
  73. package/.opencode/skills/skill-creator/scripts/quick_validate.py +113 -0
  74. package/.opencode/skills/story-status-lookup/SKILL.md +78 -0
  75. package/.opencode/skills/test-accompanied-development/SKILL.md +50 -0
  76. package/.opencode/skills/test-generator/SKILL.md +65 -0
  77. package/.opencode/skills/vercel-react-best-practices/SKILL.md +109 -0
  78. package/.opencode/skills/verify-hardened-docker-skill/SKILL.md +442 -0
  79. package/.opencode/skills/verify-hardened-docker-skill/scripts/verify-docker-hardening.sh +439 -0
  80. package/AiAudit.md +5 -0
  81. package/QUICK_START.md +11 -5
  82. package/README.md +52 -1
  83. package/bin/cli.js +31 -4
  84. package/docs/BMAD_AI_Development_Training.pptx +0 -0
  85. package/docs/technical-notes/context-persistence-research.md +434 -0
  86. package/docs/technical-notes/enforcement-hooks-research.md +415 -0
  87. package/lib/agents.js +34 -0
  88. package/lib/bmad-extension/agents/bmm-architect.customize.yaml +5 -0
  89. package/lib/bmad-extension/agents/bmm-bmad-master.customize.yaml +5 -0
  90. package/lib/bmad-extension/agents/bmm-cyber.customize.yaml +30 -0
  91. package/lib/bmad-extension/agents/bmm-dev.customize.yaml +5 -0
  92. package/lib/bmad-extension/agents/bmm-devops.customize.yaml +30 -0
  93. package/lib/bmad-extension/agents/bmm-mil498.customize.yaml +42 -0
  94. package/lib/bmad-extension/agents/bmm-pm.customize.yaml +5 -0
  95. package/lib/bmad-extension/agents/bmm-qa.customize.yaml +5 -0
  96. package/lib/bmad-extension/agents/bmm-sm.customize.yaml +5 -0
  97. package/lib/bmad-extension/agents/bmm-sre.customize.yaml +30 -0
  98. package/lib/bmad-extension/agents/bmm-tech-writer.customize.yaml +5 -0
  99. package/lib/bmad-extension/agents/bmm-ux-designer.customize.yaml +5 -0
  100. package/lib/bmad-extension/module-help.csv +7 -0
  101. package/lib/bmad-extension/module.yaml +3 -0
  102. package/lib/bmad-extension/workflows/add-sprint/workflow.md +112 -0
  103. package/lib/bmad-extension/workflows/add-to-sprint/workflow.md +206 -0
  104. package/lib/bmad-extension/workflows/create-bug-story/workflow.md +186 -0
  105. package/lib/bmad-extension/workflows/modify-sprint/workflow.md +250 -0
  106. package/lib/bmad-extension/workflows/project-context-expansion/workflow.md +229 -0
  107. package/lib/bmad-extension/workflows/sprint-status-view/workflow.md +193 -0
  108. package/lib/bmad.js +168 -36
  109. package/lib/hooks/claude-code/verify-manifest.js +56 -0
  110. package/lib/installer.js +282 -1
  111. package/lib/methodology/BMAD_AI_Development_Training.pptx +0 -0
  112. package/lib/methodology/version.json +7 -0
  113. package/lib/skill-authoring.js +732 -0
  114. package/lib/templates/project-context.template.md +47 -0
  115. package/opencode.json +8 -0
  116. package/package.json +2 -2
  117. package/skills/auto-bug-detection/SKILL.md +165 -0
  118. package/skills/auto-bug-detection/skill.json +8 -0
  119. package/skills/code-review/SKILL.md +40 -0
  120. package/skills/cpp-best-practices/SKILL.md +230 -0
  121. package/skills/cpp-best-practices/examples/modern-idioms.md +189 -0
  122. package/skills/cpp-best-practices/examples/naming-and-organization.md +102 -0
  123. package/skills/cpp-best-practices/skill.json +25 -0
  124. package/skills/csharp-best-practices/SKILL.md +274 -0
  125. package/skills/csharp-best-practices/skill.json +23 -0
  126. package/skills/git-workflow-skill/skill.json +1 -1
  127. package/skills/open-presentation/SKILL.md +31 -0
  128. package/skills/open-presentation/skill.json +11 -0
  129. package/skills/python-best-practices/SKILL.md +381 -0
  130. package/skills/python-best-practices/skill.json +26 -0
  131. package/skills/story-status-lookup/SKILL.md +74 -0
  132. package/skills/story-status-lookup/skill.json +8 -0
  133. package/test/agent-injection-strategy.test.js +13 -7
  134. package/test/bmad-extension.test.js +237 -0
  135. package/test/bmad-output-policy.test.js +119 -0
  136. package/test/build-bmad-args.test.js +361 -0
  137. package/test/create-agent.test.js +232 -0
  138. package/test/enforcement-hooks.test.js +324 -0
  139. package/test/generate-project-context.test.js +337 -0
  140. package/test/integration-verification.test.js +402 -0
  141. package/test/opencode-agent.test.js +150 -0
  142. package/test/opencode-json-error.test.js +260 -0
  143. package/test/opencode-json-injection.test.js +256 -0
  144. package/test/opencode-json-merge.test.js +299 -0
  145. package/test/skill-authoring.test.js +272 -0
  146. package/test/skill-customize-agent.test.js +253 -0
  147. package/test/skill-mandatory.test.js +235 -0
  148. package/test/skill-validation.test.js +378 -0
  149. package/test/yes-flag.test.js +1 -1
package/lib/installer.js CHANGED
@@ -6,6 +6,12 @@ const { getAgent, getAllAgents } = require('./agents');
6
6
 
7
7
  const MANIFEST_FILE = '.ma-agents.json';
8
8
  const MANIFEST_VERSION = '1.1.0';
9
+ const MA_AGENTS_SOURCE = 'ma-agents';
10
+ const TEMPLATE_PATH = path.join(__dirname, 'templates', 'project-context.template.md');
11
+
12
+ // Claude Code hook configuration for MANIFEST verification
13
+ const CLAUDE_CODE_HOOK_ID = 'ma-agents-verify-manifest';
14
+ const CLAUDE_CODE_SETTINGS_FILE = '.claude/settings.json';
9
15
 
10
16
  function getPackageVersion() {
11
17
  const pkg = JSON.parse(fs.readFileSync(path.join(__dirname, '..', 'package.json'), 'utf-8'));
@@ -31,6 +37,27 @@ function writeManifest(installPath, manifest) {
31
37
  fs.writeFileSync(manifestPath, JSON.stringify(manifest, null, 2) + '\n', 'utf-8');
32
38
  }
33
39
 
40
+ const BMAD_OUTPUT_PATTERNS = ['_bmad-output', '_bmad-output/', '/_bmad-output', '/_bmad-output/'];
41
+
42
+ function ensureBmadOutputTracked(projectRoot) {
43
+ const gitignorePath = path.join(projectRoot, '.gitignore');
44
+ let content;
45
+ try {
46
+ content = fs.readFileSync(gitignorePath, 'utf-8');
47
+ } catch (err) {
48
+ if (err.code === 'ENOENT') return; // no .gitignore — nothing to do
49
+ throw err;
50
+ }
51
+
52
+ const lines = content.split(/\r?\n/);
53
+ const filtered = lines.filter(line => !BMAD_OUTPUT_PATTERNS.includes(line.trim()));
54
+
55
+ if (filtered.length === lines.length) return; // nothing removed — do not write
56
+
57
+ fs.writeFileSync(gitignorePath, filtered.join('\n'), 'utf-8');
58
+ console.log(chalk.green('_bmad-output is now tracked as project knowledge (not gitignored)'));
59
+ }
60
+
34
61
  function ensureManifest(installPath, agentId, scope) {
35
62
  let manifest = readManifest(installPath);
36
63
  if (!manifest) {
@@ -107,6 +134,72 @@ async function generateSkillsManifest(installPath) {
107
134
  console.log(chalk.cyan(` + Generated ${manifestYamlPath}`));
108
135
  }
109
136
 
137
+ /**
138
+ * Generate project-context.md content by stamping the template with installed agent MANIFEST paths.
139
+ * @param {string} projectRoot - Absolute path to project root (unused for path generation but part of API)
140
+ * @param {Array<{skillsDir: string}>} installedAgents - Agent objects with skillsDir property
141
+ * @returns {Promise<string>} Stamped template content string (does NOT write any file)
142
+ */
143
+ async function generateProjectContext(projectRoot, installedAgents) {
144
+ let template;
145
+ try {
146
+ template = await fs.readFile(TEMPLATE_PATH, 'utf8');
147
+ } catch (err) {
148
+ throw new Error(`project-context template not found at ${TEMPLATE_PATH} — ma-agents installation may be corrupted: ${err.message}`);
149
+ }
150
+
151
+ const validAgents = installedAgents ? installedAgents.filter(a => a && a.skillsDir) : [];
152
+ let manifestList;
153
+ if (validAgents.length === 0) {
154
+ manifestList = ' - (no agents installed — run ma-agents to install skills)';
155
+ } else {
156
+ manifestList = validAgents
157
+ .map(a => ` - \`${a.skillsDir}/MANIFEST.yaml\``)
158
+ .join('\n');
159
+ }
160
+
161
+ return template.replace('{{MANIFEST_PATHS_LIST}}', manifestList);
162
+ }
163
+
164
+ /**
165
+ * Update the MANIFEST paths section in an existing project-context.md.
166
+ * Locates content between <!-- ma-agents:manifest-paths-start --> and
167
+ * <!-- ma-agents:manifest-paths-end --> markers and replaces it with the
168
+ * current agent list. Returns true if the file was updated, false if
169
+ * markers were not found (old-format file — backward compatible) or if
170
+ * the content was already up to date.
171
+ * @param {string} outputPath - Absolute path to the existing project-context.md
172
+ * @param {Array<{skillsDir: string}>} installedAgents - Agent objects with skillsDir property
173
+ * @returns {Promise<boolean>} true if file was written, false otherwise
174
+ */
175
+ async function updateProjectContextManifestPaths(outputPath, installedAgents) {
176
+ const content = await fs.readFile(outputPath, 'utf8');
177
+ const validAgents = installedAgents ? installedAgents.filter(a => a && a.skillsDir) : [];
178
+ const newList = validAgents.length === 0
179
+ ? ' - (no agents installed — run ma-agents to install skills)'
180
+ : validAgents.map(a => ` - \`${a.skillsDir}/MANIFEST.yaml\``).join('\n');
181
+
182
+ const START = '<!-- ma-agents:manifest-paths-start -->';
183
+ const END = '<!-- ma-agents:manifest-paths-end -->';
184
+ const startIdx = content.indexOf(START);
185
+ const endIdx = content.indexOf(END);
186
+
187
+ if (startIdx === -1 || endIdx === -1 || endIdx <= startIdx) {
188
+ return false; // no markers — old-format file, skip silently (backward compatible)
189
+ }
190
+
191
+ const before = content.slice(0, startIdx + START.length);
192
+ const after = content.slice(endIdx);
193
+ const newContent = `${before}\n${newList}\n${after}`;
194
+
195
+ if (newContent === content) {
196
+ return false; // already up to date, no write needed
197
+ }
198
+
199
+ await fs.writeFile(outputPath, newContent, 'utf8');
200
+ return true;
201
+ }
202
+
110
203
  /**
111
204
  * Find the insertion point in content after all skipped headers.
112
205
  * For '---' pattern, skips YAML frontmatter block (opening --- on first line through closing ---).
@@ -160,6 +253,48 @@ function findInsertionPoint(content, skipPatterns) {
160
253
  async function updateAgentInstructions(agent, projectRoot) {
161
254
  if (!agent.instructionFiles || agent.instructionFiles.length === 0) return;
162
255
 
256
+ // JSON merge strategy (e.g., OpenCode)
257
+ if (agent.injectionStrategy?.position === 'json-merge') {
258
+ const targetKey = agent.injectionStrategy.targetKey || 'instructions';
259
+ const filePath = path.join(projectRoot, agent.instructionFiles[0]);
260
+ const agentProjectPath = agent.getProjectPath();
261
+ const relManifestPath = path.relative(projectRoot, path.join(agentProjectPath, 'MANIFEST.yaml')).replace(/\\/g, '/');
262
+ const instructionText = `# AI Agent Skills - Planning Instruction\n\nYou have access to a library of skills in your skills directory. Before starting any task:\n\n1. Read the skill manifest at ${relManifestPath}\n2. Based on the task description, select which skills are relevant\n3. Read only the selected skill files\n4. Then proceed with the task\n\nAlways load skills marked with always_load: true.\nDo not load skills that are not relevant to the current task.`;
263
+
264
+ if (!fs.existsSync(filePath)) {
265
+ // File absent: create fresh (atomic write)
266
+ const data = { [targetKey]: [] };
267
+ data[targetKey].push({ _source: MA_AGENTS_SOURCE, text: instructionText });
268
+ const tmpPath = filePath + '.tmp';
269
+ await fs.outputFile(tmpPath, JSON.stringify(data, null, 2), 'utf-8');
270
+ await fs.rename(tmpPath, filePath);
271
+ console.log(chalk.cyan(` + Created ${agent.instructionFiles[0]}`));
272
+ return;
273
+ }
274
+ // File present: read and merge
275
+ try {
276
+ const content = await fs.readFile(filePath, 'utf-8');
277
+ const data = JSON.parse(content);
278
+ if (!Array.isArray(data[targetKey])) {
279
+ data[targetKey] = [];
280
+ }
281
+ // Filter out stale ma-agents entries, keep user entries (null-safe)
282
+ const userEntries = data[targetKey].filter(entry => entry != null && entry._source !== MA_AGENTS_SOURCE);
283
+ // Append fresh ma-agents entries
284
+ const freshEntries = [{ _source: MA_AGENTS_SOURCE, text: instructionText }];
285
+ data[targetKey] = [...userEntries, ...freshEntries];
286
+ // Atomic write: temp file then rename
287
+ const tmpPath = filePath + '.tmp';
288
+ await fs.outputFile(tmpPath, JSON.stringify(data, null, 2), 'utf-8');
289
+ await fs.rename(tmpPath, filePath);
290
+ console.log(chalk.cyan(` + Updated ${agent.instructionFiles[0]}`));
291
+ } catch (err) {
292
+ console.error(`[${MA_AGENTS_SOURCE}] ERROR: Could not parse ${filePath} — ${err.message}. File not modified.`);
293
+ return; // non-fatal: do NOT re-throw
294
+ }
295
+ return;
296
+ }
297
+
163
298
  const agentProjectPath = agent.getProjectPath();
164
299
  const relManifestPath = path.relative(projectRoot, path.join(agentProjectPath, 'MANIFEST.yaml')).replace(/\\/g, '/');
165
300
 
@@ -362,6 +497,11 @@ async function installSkill(skillId, agentIds, customPath = '', scope = 'project
362
497
  pathGroups.get(installPath).push({ agentId, agent });
363
498
  }
364
499
 
500
+ // Guard: project-context generation runs at most once per installSkill() call.
501
+ // pathGroups can have multiple entries when agents share paths; without this flag
502
+ // the generation would fire once per path group, producing redundant file I/O.
503
+ let projectContextHandled = false;
504
+
365
505
  for (const [installPath, agentEntries] of pathGroups) {
366
506
  const primaryAgent = agentEntries[0].agent;
367
507
  const agentNames = agentEntries.map(e => e.agent.name).join(', ');
@@ -516,6 +656,7 @@ async function installSkill(skillId, agentIds, customPath = '', scope = 'project
516
656
  agentVersion: primaryAgent.version
517
657
  };
518
658
  writeManifest(installPath, manifest);
659
+ ensureBmadOutputTracked(installPath);
519
660
 
520
661
  // Generate MANIFEST.yaml and update instruction files for ALL agents
521
662
  await generateSkillsManifest(installPath);
@@ -523,6 +664,36 @@ async function installSkill(skillId, agentIds, customPath = '', scope = 'project
523
664
  for (const entry of agentEntries) {
524
665
  await updateAgentInstructions(entry.agent, process.cwd());
525
666
  }
667
+ // Deploy Claude Code hook when skills are installed for claude-code
668
+ if (includesClaudeCode(agentEntries)) {
669
+ await deployClaudeCodeHook(process.cwd());
670
+ }
671
+ // Generate project-context.md on first install; update manifest paths on subsequent installs.
672
+ // Guard: runs at most once per installSkill() call — pathGroups may have multiple entries
673
+ // when agents share install paths, so without this flag generation would fire redundantly.
674
+ if (!projectContextHandled) {
675
+ projectContextHandled = true;
676
+ const projectRoot = process.cwd();
677
+ const outputPath = path.join(projectRoot, '_bmad-output', 'project-context.md');
678
+ const allAgents = getManifestAgents(manifest).map(id => getAgent(id)).filter(Boolean);
679
+ try {
680
+ await fs.ensureDir(path.join(projectRoot, '_bmad-output'));
681
+ if (await fs.pathExists(outputPath)) {
682
+ const updated = await updateProjectContextManifestPaths(outputPath, allAgents);
683
+ if (updated) {
684
+ console.log(chalk.green('✓ project-context.md manifest paths updated'));
685
+ } else {
686
+ console.log(chalk.blue('ℹ project-context.md already up to date'));
687
+ }
688
+ } else {
689
+ const content = await generateProjectContext(projectRoot, allAgents);
690
+ await fs.writeFile(outputPath, content, 'utf8');
691
+ console.log(chalk.green('✓ project-context.md generated at _bmad-output/project-context.md'));
692
+ }
693
+ } catch (err) {
694
+ console.log(chalk.yellow(`⚠ project-context generation failed: ${err.message} — continuing install`));
695
+ }
696
+ }
526
697
  }
527
698
  }
528
699
  } catch (error) {
@@ -602,6 +773,14 @@ async function uninstallSkill(skillId, agentIds, customPath = '', scope = 'proje
602
773
  for (const entry of agentEntries) {
603
774
  await updateAgentInstructions(entry.agent, process.cwd());
604
775
  }
776
+ // Remove Claude Code hook when no skills remain for claude-code
777
+ if (includesClaudeCode(agentEntries)) {
778
+ const currentManifest = readManifest(installPath);
779
+ const hasRemainingSkills = currentManifest && currentManifest.skills && Object.keys(currentManifest.skills).length > 0;
780
+ if (!hasRemainingSkills) {
781
+ await removeClaudeCodeHook(process.cwd());
782
+ }
783
+ }
605
784
  }
606
785
  } catch (error) {
607
786
  console.log(chalk.red(` x Failed: ${error.message}`));
@@ -655,6 +834,101 @@ function getStatus(agentIds, customPath = '', scope = 'project') {
655
834
  return results;
656
835
  }
657
836
 
837
+ // --- Claude Code Hook Management ---
838
+
839
+ /**
840
+ * Deploy the verify-manifest SessionStart hook into .claude/settings.json.
841
+ * Performs a JSON merge — preserves existing settings and hooks.
842
+ */
843
+ async function deployClaudeCodeHook(projectRoot) {
844
+ const settingsPath = path.join(projectRoot, CLAUDE_CODE_SETTINGS_FILE);
845
+ let settings = {};
846
+
847
+ if (fs.existsSync(settingsPath)) {
848
+ try {
849
+ settings = JSON.parse(await fs.readFile(settingsPath, 'utf-8'));
850
+ } catch {
851
+ console.log(chalk.yellow(' Warning: Could not parse .claude/settings.json, skipping hook deployment'));
852
+ return;
853
+ }
854
+ }
855
+
856
+ if (!settings.hooks) {
857
+ settings.hooks = {};
858
+ }
859
+ if (!settings.hooks.SessionStart) {
860
+ settings.hooks.SessionStart = [];
861
+ }
862
+
863
+ // Check if our hook is already present
864
+ const hookCommand = `node "$CLAUDE_PROJECT_DIR/lib/hooks/claude-code/verify-manifest.js"`;
865
+ const alreadyInstalled = settings.hooks.SessionStart.some(group =>
866
+ group.hooks && group.hooks.some(h => h.command === hookCommand)
867
+ );
868
+
869
+ if (alreadyInstalled) {
870
+ return; // Already deployed
871
+ }
872
+
873
+ settings.hooks.SessionStart.push({
874
+ matcher: 'startup',
875
+ hooks: [
876
+ {
877
+ type: 'command',
878
+ command: hookCommand,
879
+ _id: CLAUDE_CODE_HOOK_ID
880
+ }
881
+ ]
882
+ });
883
+
884
+ await fs.ensureDir(path.dirname(settingsPath));
885
+ await fs.writeFile(settingsPath, JSON.stringify(settings, null, 2) + '\n', 'utf-8');
886
+ console.log(chalk.cyan(' + Deployed Claude Code verify-manifest hook'));
887
+ }
888
+
889
+ /**
890
+ * Remove the verify-manifest hook from .claude/settings.json.
891
+ */
892
+ async function removeClaudeCodeHook(projectRoot) {
893
+ const settingsPath = path.join(projectRoot, CLAUDE_CODE_SETTINGS_FILE);
894
+
895
+ if (!fs.existsSync(settingsPath)) return;
896
+
897
+ let settings;
898
+ try {
899
+ settings = JSON.parse(await fs.readFile(settingsPath, 'utf-8'));
900
+ } catch {
901
+ return;
902
+ }
903
+
904
+ if (!settings.hooks || !settings.hooks.SessionStart) return;
905
+
906
+ const hookCommand = `node "$CLAUDE_PROJECT_DIR/lib/hooks/claude-code/verify-manifest.js"`;
907
+ settings.hooks.SessionStart = settings.hooks.SessionStart.filter(group => {
908
+ if (!group.hooks) return true;
909
+ group.hooks = group.hooks.filter(h => h.command !== hookCommand && h._id !== CLAUDE_CODE_HOOK_ID);
910
+ return group.hooks.length > 0;
911
+ });
912
+
913
+ // Clean up empty arrays
914
+ if (settings.hooks.SessionStart.length === 0) {
915
+ delete settings.hooks.SessionStart;
916
+ }
917
+ if (Object.keys(settings.hooks).length === 0) {
918
+ delete settings.hooks;
919
+ }
920
+
921
+ await fs.writeFile(settingsPath, JSON.stringify(settings, null, 2) + '\n', 'utf-8');
922
+ console.log(chalk.cyan(' - Removed Claude Code verify-manifest hook'));
923
+ }
924
+
925
+ /**
926
+ * Check if any agents in the list include claude-code.
927
+ */
928
+ function includesClaudeCode(agentEntries) {
929
+ return agentEntries.some(e => e.agentId === 'claude-code');
930
+ }
931
+
658
932
  module.exports = {
659
933
  listSkills,
660
934
  listAgents,
@@ -666,5 +940,12 @@ module.exports = {
666
940
  getManifestAgents,
667
941
  compareSemver,
668
942
  findInsertionPoint,
669
- _testUpdateAgentInstructions: updateAgentInstructions
943
+ deployClaudeCodeHook,
944
+ removeClaudeCodeHook,
945
+ ensureBmadOutputTracked,
946
+ generateSkillsManifest,
947
+ generateProjectContext,
948
+ _updateProjectContextManifestPaths: updateProjectContextManifestPaths,
949
+ _testUpdateAgentInstructions: updateAgentInstructions,
950
+ _MA_AGENTS_SOURCE: MA_AGENTS_SOURCE
670
951
  };
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": "1.0.0",
3
+ "description": "BMAD-METHOD AI Development Training Presentation",
4
+ "files": [
5
+ "BMAD_AI_Development_Training.pptx"
6
+ ]
7
+ }