bmad-plus 0.12.0 → 0.12.2

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 (160) hide show
  1. package/CHANGELOG.md +612 -554
  2. package/README.md +557 -577
  3. package/osint-agent-package/agents/osint-investigator.md +12 -0
  4. package/osint-agent-package/skills/bmad-osint-investigate/osint/SKILL.md +491 -482
  5. package/osint-agent-package/skills/bmad-osint-investigate/osint/assets/dossier-template.md +126 -126
  6. package/osint-agent-package/skills/bmad-osint-investigate/osint/assets/lawful-basis-record.md +48 -48
  7. package/osint-agent-package/skills/bmad-osint-investigate/osint/references/content-extraction.md +100 -100
  8. package/osint-agent-package/skills/bmad-osint-investigate/osint/references/gdpr-osint.md +48 -48
  9. package/osint-agent-package/skills/bmad-osint-investigate/osint/references/platforms.md +130 -130
  10. package/osint-agent-package/skills/bmad-osint-investigate/osint/references/psychoprofile.md +69 -69
  11. package/osint-agent-package/skills/bmad-osint-investigate/osint/references/tools.md +281 -281
  12. package/osint-agent-package/skills/bmad-osint-investigate/osint/scripts/mcp-client.py +136 -136
  13. package/package.json +104 -91
  14. package/readme-international/README.de.md +596 -589
  15. package/readme-international/README.es.md +613 -606
  16. package/readme-international/README.fr.md +611 -604
  17. package/src/bmad-plus/agents/agent-shadow/SKILL.md +18 -0
  18. package/src/bmad-plus/data/role-triggers.yaml +52 -0
  19. package/src/bmad-plus/module.yaml +283 -283
  20. package/src/bmad-plus/packs/pack-animated/animated-website-agent.md +325 -325
  21. package/src/bmad-plus/packs/pack-animated/templates/animated-website-workflow.md +55 -55
  22. package/src/bmad-plus/packs/pack-backup/backup-agent.md +71 -71
  23. package/src/bmad-plus/packs/pack-backup/templates/backup-workflow.md +51 -51
  24. package/src/bmad-plus/packs/pack-dev-studio/README.md +162 -162
  25. package/src/bmad-plus/packs/pack-dev-studio/categories/analysis/analyst-agent.md +73 -73
  26. package/src/bmad-plus/packs/pack-dev-studio/categories/analysis/document-project.md +61 -61
  27. package/src/bmad-plus/packs/pack-dev-studio/categories/analysis/domain-research.md +95 -95
  28. package/src/bmad-plus/packs/pack-dev-studio/categories/analysis/market-research.md +95 -95
  29. package/src/bmad-plus/packs/pack-dev-studio/categories/analysis/prfaq.md +134 -134
  30. package/src/bmad-plus/packs/pack-dev-studio/categories/analysis/product-brief.md +80 -80
  31. package/src/bmad-plus/packs/pack-dev-studio/categories/analysis/tech-writer-agent.md +73 -73
  32. package/src/bmad-plus/packs/pack-dev-studio/categories/analysis/technical-research.md +95 -95
  33. package/src/bmad-plus/packs/pack-dev-studio/categories/architecture/architect-agent.md +73 -73
  34. package/src/bmad-plus/packs/pack-dev-studio/categories/architecture/create-architecture.md +73 -73
  35. package/src/bmad-plus/packs/pack-dev-studio/categories/architecture/create-epics-stories.md +92 -92
  36. package/src/bmad-plus/packs/pack-dev-studio/categories/architecture/generate-project-context.md +80 -80
  37. package/src/bmad-plus/packs/pack-dev-studio/categories/architecture/implementation-readiness.md +90 -90
  38. package/src/bmad-plus/packs/pack-dev-studio/categories/architecture/steps/step-01-init.md +153 -153
  39. package/src/bmad-plus/packs/pack-dev-studio/categories/architecture/steps/step-01b-continue.md +173 -173
  40. package/src/bmad-plus/packs/pack-dev-studio/categories/architecture/steps/step-02-context.md +224 -224
  41. package/src/bmad-plus/packs/pack-dev-studio/categories/architecture/steps/step-03-starter.md +329 -329
  42. package/src/bmad-plus/packs/pack-dev-studio/categories/architecture/steps/step-04-decisions.md +318 -318
  43. package/src/bmad-plus/packs/pack-dev-studio/categories/architecture/steps/step-05-patterns.md +359 -359
  44. package/src/bmad-plus/packs/pack-dev-studio/categories/architecture/steps/step-06-structure.md +379 -379
  45. package/src/bmad-plus/packs/pack-dev-studio/categories/architecture/steps/step-07-validation.md +361 -361
  46. package/src/bmad-plus/packs/pack-dev-studio/categories/architecture/steps/step-08-complete.md +81 -81
  47. package/src/bmad-plus/packs/pack-dev-studio/categories/implementation/checkpoint-preview.md +67 -67
  48. package/src/bmad-plus/packs/pack-dev-studio/categories/implementation/code-review-steps/step-01-gather-context.md +85 -85
  49. package/src/bmad-plus/packs/pack-dev-studio/categories/implementation/code-review-steps/step-02-review.md +35 -35
  50. package/src/bmad-plus/packs/pack-dev-studio/categories/implementation/code-review-steps/step-03-triage.md +49 -49
  51. package/src/bmad-plus/packs/pack-dev-studio/categories/implementation/code-review-steps/step-04-present.md +131 -131
  52. package/src/bmad-plus/packs/pack-dev-studio/categories/implementation/code-review.md +89 -89
  53. package/src/bmad-plus/packs/pack-dev-studio/categories/implementation/correct-course.md +300 -300
  54. package/src/bmad-plus/packs/pack-dev-studio/categories/implementation/create-story.md +428 -428
  55. package/src/bmad-plus/packs/pack-dev-studio/categories/implementation/dev-agent.md +73 -73
  56. package/src/bmad-plus/packs/pack-dev-studio/categories/implementation/dev-story-checklist.md +80 -80
  57. package/src/bmad-plus/packs/pack-dev-studio/categories/implementation/dev-story.md +484 -484
  58. package/src/bmad-plus/packs/pack-dev-studio/categories/implementation/investigate.md +193 -193
  59. package/src/bmad-plus/packs/pack-dev-studio/categories/implementation/qa-e2e-tests.md +175 -175
  60. package/src/bmad-plus/packs/pack-dev-studio/categories/implementation/quick-dev.md +110 -110
  61. package/src/bmad-plus/packs/pack-dev-studio/categories/implementation/retrospective.md +1511 -1511
  62. package/src/bmad-plus/packs/pack-dev-studio/categories/implementation/sprint-planning.md +298 -298
  63. package/src/bmad-plus/packs/pack-dev-studio/categories/implementation/sprint-status.md +296 -296
  64. package/src/bmad-plus/packs/pack-dev-studio/categories/planning/create-prd.md +29 -29
  65. package/src/bmad-plus/packs/pack-dev-studio/categories/planning/create-ux-design.md +74 -74
  66. package/src/bmad-plus/packs/pack-dev-studio/categories/planning/edit-prd.md +29 -29
  67. package/src/bmad-plus/packs/pack-dev-studio/categories/planning/pm-agent.md +73 -73
  68. package/src/bmad-plus/packs/pack-dev-studio/categories/planning/prd.md +89 -89
  69. package/src/bmad-plus/packs/pack-dev-studio/categories/planning/ux-designer-agent.md +73 -73
  70. package/src/bmad-plus/packs/pack-dev-studio/categories/planning/validate-prd.md +29 -29
  71. package/src/bmad-plus/packs/pack-dev-studio/categories/utilities/advanced-elicitation.md +141 -141
  72. package/src/bmad-plus/packs/pack-dev-studio/categories/utilities/adversarial-review.md +37 -37
  73. package/src/bmad-plus/packs/pack-dev-studio/categories/utilities/bmad-help.md +75 -75
  74. package/src/bmad-plus/packs/pack-dev-studio/categories/utilities/brainstorming.md +6 -6
  75. package/src/bmad-plus/packs/pack-dev-studio/categories/utilities/customize.md +110 -110
  76. package/src/bmad-plus/packs/pack-dev-studio/categories/utilities/distillator.md +176 -176
  77. package/src/bmad-plus/packs/pack-dev-studio/categories/utilities/edge-case-hunter.md +67 -67
  78. package/src/bmad-plus/packs/pack-dev-studio/categories/utilities/editorial-review-prose.md +86 -86
  79. package/src/bmad-plus/packs/pack-dev-studio/categories/utilities/editorial-review-structure.md +179 -179
  80. package/src/bmad-plus/packs/pack-dev-studio/categories/utilities/index-docs.md +66 -66
  81. package/src/bmad-plus/packs/pack-dev-studio/categories/utilities/party-mode.md +127 -127
  82. package/src/bmad-plus/packs/pack-dev-studio/categories/utilities/shard-doc.md +105 -105
  83. package/src/bmad-plus/packs/pack-dev-studio/dev-studio-orchestrator.md +120 -120
  84. package/src/bmad-plus/packs/pack-dev-studio/shared/architecture-decision-template.md +12 -12
  85. package/src/bmad-plus/packs/pack-dev-studio/shared/bwml-spec.md +328 -328
  86. package/src/bmad-plus/packs/pack-dev-studio/shared/module-help.csv +32 -32
  87. package/src/bmad-plus/packs/pack-dev-studio/upstream-sync.yaml +81 -81
  88. package/src/bmad-plus/packs/pack-memory/shared/memory-protocol.md +14 -0
  89. package/src/bmad-plus/packs/pack-seo/SKILL.md +171 -171
  90. package/src/bmad-plus/packs/pack-seo/checklist.md +140 -140
  91. package/src/bmad-plus/packs/pack-seo/pagespeed-playbook.md +320 -320
  92. package/src/bmad-plus/packs/pack-seo/ref/audit-schema.json +187 -187
  93. package/src/bmad-plus/packs/pack-seo/ref/cwv-thresholds.md +87 -87
  94. package/src/bmad-plus/packs/pack-seo/ref/eeat-criteria.md +123 -123
  95. package/src/bmad-plus/packs/pack-seo/ref/geo-signals.md +167 -167
  96. package/src/bmad-plus/packs/pack-seo/ref/hreflang-rules.md +153 -153
  97. package/src/bmad-plus/packs/pack-seo/ref/quality-gates.md +133 -133
  98. package/src/bmad-plus/packs/pack-seo/ref/schema-catalog.md +91 -91
  99. package/src/bmad-plus/packs/pack-seo/ref/schema-templates.json +356 -356
  100. package/src/bmad-plus/packs/pack-seo/requirements.txt +17 -0
  101. package/src/bmad-plus/packs/pack-seo/scripts/seo_apis.py +456 -0
  102. package/src/bmad-plus/packs/pack-seo/scripts/seo_crawl.py +359 -0
  103. package/src/bmad-plus/packs/pack-seo/scripts/seo_fetch.py +304 -0
  104. package/src/bmad-plus/packs/pack-seo/scripts/seo_parse.py +255 -0
  105. package/src/bmad-plus/packs/pack-seo/scripts/seo_report.py +410 -0
  106. package/src/bmad-plus/packs/pack-seo/scripts/seo_screenshot.py +202 -0
  107. package/src/bmad-plus/packs/pack-seo/seo-chief.md +294 -294
  108. package/src/bmad-plus/packs/pack-seo/seo-judge.md +241 -241
  109. package/src/bmad-plus/packs/pack-seo/seo-scout.md +171 -171
  110. package/src/bmad-plus/packs/pack-seo/templates/seo-audit-workflow.md +241 -241
  111. package/src/bmad-plus/packs/pack-shield/README.md +6 -6
  112. package/src/bmad-plus/packs/pack-shield/SKILL.md +2 -2
  113. package/src/bmad-plus/packs/pack-shield/categories/accessibility-esg/csrd-agent.md +11 -11
  114. package/src/bmad-plus/packs/pack-shield/categories/accessibility-esg/section508-agent.md +11 -11
  115. package/src/bmad-plus/packs/pack-shield/categories/accessibility-esg/wcag-agent.md +11 -11
  116. package/src/bmad-plus/packs/pack-shield/categories/ai-governance/eu-ai-act-agent.md +11 -11
  117. package/src/bmad-plus/packs/pack-shield/categories/ai-governance/iso42001-agent.md +11 -11
  118. package/src/bmad-plus/packs/pack-shield/categories/ai-governance/nist-ai-rmf-agent.md +11 -11
  119. package/src/bmad-plus/packs/pack-shield/categories/cybersecurity/cis-controls-agent.md +11 -11
  120. package/src/bmad-plus/packs/pack-shield/categories/cybersecurity/ism-agent.md +11 -11
  121. package/src/bmad-plus/packs/pack-shield/categories/cybersecurity/iso27001-agent.md +11 -11
  122. package/src/bmad-plus/packs/pack-shield/categories/cybersecurity/nis2-agent.md +11 -11
  123. package/src/bmad-plus/packs/pack-shield/categories/cybersecurity/nist-800-53-agent.md +11 -11
  124. package/src/bmad-plus/packs/pack-shield/categories/cybersecurity/nist-csf-agent.md +11 -11
  125. package/src/bmad-plus/packs/pack-shield/categories/defense-export/cmmc-agent.md +11 -11
  126. package/src/bmad-plus/packs/pack-shield/categories/defense-export/ear-agent.md +11 -11
  127. package/src/bmad-plus/packs/pack-shield/categories/defense-export/itar-agent.md +11 -11
  128. package/src/bmad-plus/packs/pack-shield/categories/defense-export/tsa-agent.md +11 -11
  129. package/src/bmad-plus/packs/pack-shield/categories/industry-compliance/dora-agent.md +11 -11
  130. package/src/bmad-plus/packs/pack-shield/categories/industry-compliance/fedramp-agent.md +11 -11
  131. package/src/bmad-plus/packs/pack-shield/categories/industry-compliance/hipaa-agent.md +11 -11
  132. package/src/bmad-plus/packs/pack-shield/categories/industry-compliance/pci-dss-agent.md +11 -11
  133. package/src/bmad-plus/packs/pack-shield/categories/industry-compliance/soc2-agent.md +11 -11
  134. package/src/bmad-plus/packs/pack-shield/categories/industry-compliance/swift-csp-agent.md +11 -11
  135. package/src/bmad-plus/packs/pack-shield/shield-orchestrator.md +1 -1
  136. package/tools/build/check-counts.js +628 -0
  137. package/tools/build/generated-adapters/.codex/AGENTS.md +1 -1
  138. package/tools/build/generated-adapters/.cursor/rules/bmad-plus.mdc +1 -1
  139. package/tools/build/generated-adapters/.opencode/AGENTS.md +1 -1
  140. package/tools/build/generated-adapters/AGENTS.md +1 -1
  141. package/tools/build/generated-adapters/CLAUDE.md +1 -1
  142. package/tools/build/generated-adapters/CONVENTIONS.md +1 -1
  143. package/tools/build/generated-adapters/GEMINI.md +1 -1
  144. package/tools/cli/commands/autoconfig.js +470 -472
  145. package/tools/cli/commands/doctor.js +233 -233
  146. package/tools/cli/commands/install.js +598 -495
  147. package/tools/cli/commands/memory-journal-cmd.js +311 -311
  148. package/tools/cli/commands/memory.js +195 -195
  149. package/tools/cli/commands/scan.js +348 -364
  150. package/tools/cli/commands/uninstall.js +101 -101
  151. package/tools/cli/commands/update.js +134 -134
  152. package/tools/cli/i18n.js +845 -845
  153. package/tools/cli/lib/README-memory-journal.md +125 -125
  154. package/tools/cli/lib/ide-config.js +267 -259
  155. package/tools/cli/lib/memory-init.js +204 -113
  156. package/tools/cli/lib/packs.js +209 -209
  157. package/tools/cli/lib/path-hash.js +39 -0
  158. package/tools/cli/lib/python-provision.js +508 -508
  159. package/tools/cli/lib/stack-detect.js +102 -102
  160. package/tools/cli/lib/validate.js +50 -50
@@ -0,0 +1,628 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * BMAD+ Build — hand-typed count drift checker (audit NS-01 / ARCH-04 / DOC-06)
4
+ *
5
+ * Every "N agents" / "N workflows" / "N packs" claim in a shipped surface is a
6
+ * fact that MUST be derivable from registry.yaml (+ the files on disk). This
7
+ * script derives the truth, scans the tracked truth surfaces for hand-typed
8
+ * numbers that contradict it, and exits non-zero listing every drift.
9
+ *
10
+ * Why this exists: `- **Shield** (GRC) — 38 compliance agents` shipped into
11
+ * every generated user config for months. 38 was not a typo — it was
12
+ * 27 agents + 11 workflows summed into one dimension. A definition error
13
+ * re-drifts on every pack change, so it needs a gate, not a one-off correction.
14
+ *
15
+ * DIMENSIONS ARE THE POINT. 38 is a legitimate number in this repo (Dev Studio
16
+ * has 38 workflows); it is only wrong when attached to `agents`. Every rule
17
+ * below therefore checks a (subject, dimension) pair, never a bare integer.
18
+ *
19
+ * Usage:
20
+ * node tools/build/check-counts.js # verify (exit 1 on drift)
21
+ * node tools/build/check-counts.js --print # print the derived truth and exit 0
22
+ *
23
+ * The test-count claim ("N tests") is the one number here that cannot be
24
+ * derived statically — it requires running Jest. So it is gated by an env var:
25
+ *
26
+ * BMAD_PLUS_TEST_COUNT unset/empty → test claims are listed as NOT GATED and
27
+ * the run says OK (PARTIAL). Never fails:
28
+ * a local `node tools/build/check-counts.js`
29
+ * must not need a full Jest run first.
30
+ * BMAD_PLUS_TEST_COUNT=<integer> → every "N tests" claim must equal it.
31
+ * CI arms this with the Jest-reported
32
+ * total, which is the only run that
33
+ * proves the README's test number.
34
+ * BMAD_PLUS_TEST_COUNT=<garbage> → exit 1. A misspelled total must not
35
+ * masquerade as coverage.
36
+ *
37
+ * Author: Laurent Rochetta
38
+ */
39
+
40
+ 'use strict';
41
+
42
+ const fs = require('node:fs');
43
+ const path = require('node:path');
44
+ const { execFileSync } = require('node:child_process');
45
+ const { loadRegistry, DEFAULT_REGISTRY_PATH } = require('./generate');
46
+
47
+ const REPO_ROOT = path.join(__dirname, '..', '..');
48
+
49
+ /**
50
+ * Files whose numeric claims describe the CURRENT product. Anything not listed
51
+ * is out of scope by construction, so the gate can never be "fixed" by quietly
52
+ * moving a lie into an unscanned file — adding a surface is an explicit edit.
53
+ */
54
+ const TRUTH_SURFACES = [
55
+ 'README.md',
56
+ 'README-DIST.md',
57
+ 'readme-international/README.fr.md',
58
+ 'readme-international/README.es.md',
59
+ 'readme-international/README.de.md',
60
+ 'src/bmad-plus/module.yaml',
61
+ 'src/bmad-plus/packs/pack-shield/README.md',
62
+ 'src/bmad-plus/packs/pack-shield/SKILL.md',
63
+ 'src/bmad-plus/packs/pack-shield/shield-orchestrator.md',
64
+ 'tools/cli/lib/ide-config.js',
65
+ 'tools/build/adapters.config.js',
66
+ ];
67
+
68
+ /**
69
+ * NOT scanned, deliberately:
70
+ * - CHANGELOG.md, audit/**, docs/research/** — historical records. They
71
+ * legitimately quote past and *wrong* numbers ("the advertised 38 agents")
72
+ * verbatim; gating them would force rewriting history to satisfy a linter.
73
+ * - .agents/** — gitignored local install output, not a source.
74
+ * - upstream/** — third-party clone.
75
+ * Release-history table rows inside a scanned README are skipped for the same
76
+ * reason (see isFrozenHistoryRow): v0.6.0 really did ship 30 workflows and
77
+ * v0.12.0 really did ship 333 tests. Those are true statements about the past.
78
+ */
79
+ const HISTORY_ROW_RE = /^\|\s*\*\*\d+\.\d+\.\d+\*\*\s*\|/;
80
+
81
+ function isFrozenHistoryRow(line) {
82
+ return HISTORY_ROW_RE.test(line);
83
+ }
84
+
85
+ /* ── Derivation ─────────────────────────────────────────────────────────── */
86
+
87
+ /** Sum of list lengths for a given key across a pack's categories. */
88
+ function sumCategories(pack, key) {
89
+ if (!Array.isArray(pack.categories)) return 0;
90
+ return pack.categories.reduce((n, c) => n + (Array.isArray(c[key]) ? c[key].length : 0), 0);
91
+ }
92
+
93
+ /** Count *.md agent files on disk for a packaged pack's categories/ tree. */
94
+ function countAgentFilesOnDisk(packDir) {
95
+ const root = path.join(REPO_ROOT, 'src', 'bmad-plus', 'packs', packDir, 'categories');
96
+ if (!fs.existsSync(root)) return null;
97
+ let n = 0;
98
+ for (const entry of fs.readdirSync(root, { withFileTypes: true })) {
99
+ // `workflows/` sits under categories/ but is NOT an agent category — it is
100
+ // the directory whose 11 files got summed into the agent total to make 38.
101
+ if (!entry.isDirectory() || entry.name === 'workflows') continue;
102
+ n += fs.readdirSync(path.join(root, entry.name)).filter((f) => f.endsWith('.md')).length;
103
+ }
104
+ return n;
105
+ }
106
+
107
+ /** Count *.md workflow files on disk for a packaged pack. */
108
+ function countWorkflowFilesOnDisk(packDir) {
109
+ const dir = path.join(REPO_ROOT, 'src', 'bmad-plus', 'packs', packDir, 'categories', 'workflows');
110
+ if (!fs.existsSync(dir)) return null;
111
+ return fs.readdirSync(dir).filter((f) => f.endsWith('.md')).length;
112
+ }
113
+
114
+ /** Installer languages, read from the CLI's own i18n module (its SSOT). */
115
+ function deriveLanguages() {
116
+ const { LANGUAGES } = require(path.join(REPO_ROOT, 'tools', 'cli', 'i18n.js'));
117
+ return Object.keys(LANGUAGES);
118
+ }
119
+
120
+ /**
121
+ * Derive every count this gate enforces, from registry.yaml + the filesystem.
122
+ * Cross-checks the registry against the files on disk: if they disagree, the
123
+ * derivation itself is untrustworthy and that is reported as a drift.
124
+ */
125
+ function deriveTruth(registryPath = DEFAULT_REGISTRY_PATH) {
126
+ const registry = loadRegistry(registryPath);
127
+ const packs = registry.packs;
128
+ const shield = packs.shield;
129
+ const devStudio = packs['dev-studio'];
130
+
131
+ const shieldAgents = sumCategories(shield, 'agents');
132
+ const shieldWorkflows = (shield.workflows || []).length;
133
+ const shieldCategories = (shield.categories || []).length;
134
+ const shieldFrameworks = (shield.compliance_tags || []).length;
135
+
136
+ const installerAgents = Object.values(packs).reduce((n, p) => n + p.agents.length, 0);
137
+ const devStudioSubAgents = (devStudio.sub_agents || []).length;
138
+ const devStudioWorkflows =
139
+ (devStudio.workflows || []).length + sumCategories(devStudio, 'workflows');
140
+
141
+ const mismatches = [];
142
+ const onDiskAgents = countAgentFilesOnDisk(shield.pack_dir);
143
+ const onDiskWorkflows = countWorkflowFilesOnDisk(shield.pack_dir);
144
+ if (onDiskAgents !== null && onDiskAgents !== shieldAgents) {
145
+ mismatches.push(
146
+ `registry.yaml declares ${shieldAgents} shield category agents but ${onDiskAgents} agent files exist on disk`
147
+ );
148
+ }
149
+ if (onDiskWorkflows !== null && onDiskWorkflows !== shieldWorkflows) {
150
+ mismatches.push(
151
+ `registry.yaml declares ${shieldWorkflows} shield workflows but ${onDiskWorkflows} workflow files exist on disk`
152
+ );
153
+ }
154
+
155
+ // Every distinct agent a user can talk to: the installer entry points plus
156
+ // the specialists the two packaged orchestrators route to.
157
+ const totalAgents = installerAgents + shieldAgents + devStudioSubAgents;
158
+ // Sizes of the declared per-category agent groups ("5 agents", "6 agents"…),
159
+ // which are legitimate claims wherever a category is described.
160
+ const perCategorySizes = new Set((shield.categories || []).map((c) => (c.agents || []).length));
161
+ // Sizes of the per-pack installer rosters ("3 agents (Scout, Chief, Judge)").
162
+ const perPackInstallerSizes = new Set(Object.values(packs).map((p) => p.agents.length));
163
+
164
+ return {
165
+ packCount: Object.keys(packs).length,
166
+ installerAgents,
167
+ shieldAgents,
168
+ shieldWorkflows,
169
+ shieldCategories,
170
+ shieldFrameworks,
171
+ devStudioSubAgents,
172
+ devStudioWorkflows,
173
+ totalAgents,
174
+ languages: deriveLanguages(),
175
+ perCategorySizes,
176
+ // The COMPLETE vocabulary of numbers that may legitimately precede "agents"
177
+ // anywhere in the product's own copy. Default-deny: a value outside this set
178
+ // is a number nobody can derive, so it is drift by definition. This is what
179
+ // gates the 47s — "47 agents" stays green only while 47 is still derivable,
180
+ // and every occurrence turns red the day the roster changes.
181
+ agentCounts: new Set([
182
+ installerAgents,
183
+ shieldAgents,
184
+ devStudioSubAgents,
185
+ totalAgents,
186
+ ...perCategorySizes,
187
+ ...perPackInstallerSizes,
188
+ ]),
189
+ // Same idea for the workflow dimension. Anchored lines get graded against
190
+ // the one pack they name; unanchored ones must still be *some* real total.
191
+ workflowCounts: new Set([shieldWorkflows, devStudioWorkflows]),
192
+ derivationMismatches: mismatches,
193
+ };
194
+ }
195
+
196
+ /* ── Rules ──────────────────────────────────────────────────────────────── */
197
+
198
+ const SHIELD_ANCHOR =
199
+ /shield|\bGRC\b|compliance[- ]agent|agents? de conformité|agentes de cumplimiento|Compliance-Agenten/i;
200
+ const DEV_STUDIO_ANCHOR = /dev[- ]studio/i;
201
+
202
+ /**
203
+ * Lines whose agent count describes the UPSTREAM project, not this one:
204
+ * "BMAD-METHOD ... 9 specialized agents" is a true statement about a different
205
+ * product, so the roster rule must not grade it. Scoped to the agent dimension
206
+ * only — a line may name BMAD-METHOD and still make a claim of its own
207
+ * ("Fork of BMAD-METHOD — ... CLI installer in 10 languages"), and that claim
208
+ * stays gated.
209
+ */
210
+ const FOREIGN_SUBJECT_ANCHOR = /BMAD-METHOD/i;
211
+
212
+ /**
213
+ * `| BMAD-METHOD | BMAD+ |` opens the upstream comparison table, whose rows
214
+ * carry a foreign count in the left column and ours in the right ("| 9
215
+ * specialized agents | **47 agents** across 9 packs |") without repeating the
216
+ * marker. The rows are recognised positionally, like isFrozenHistoryRow.
217
+ */
218
+ const FOREIGN_TABLE_HEADER_RE = /^\s*\|\s*BMAD-METHOD\s*\|/i;
219
+
220
+ /* A claim is `<number> <qualifiers…> <dimension noun>`. The three constants
221
+ * below define what may sit between the number and the noun; every rule is
222
+ * built from them by claimRe(), so a fix to the CLASS lands on every dimension
223
+ * at once instead of on one hand-listed adjective. */
224
+
225
+ /**
226
+ * What may SEPARATE the pieces of a claim: whitespace and markdown
227
+ * emphasis/code markers — "**38** agents", "`38` agents", "**38 agents**".
228
+ * `\s` already covers U+00A0/U+202F, which the French copy uses before
229
+ * punctuation.
230
+ *
231
+ * `-` and `_` are deliberately NOT separators. This repo is full of agent
232
+ * IDENTIFIERS — `iso27001-agent`, `nist-800-53-agent`, `section508-agent`,
233
+ * `iso42001-agent` — and a hyphen-tolerant gap would read every one of them as
234
+ * a claim of "27001 agents". A qualifier may still *contain* a hyphen
235
+ * ("multi-role", "GRC-Compliance-"); it just may not START with one.
236
+ */
237
+ const GAP = '[\\s*`]*';
238
+
239
+ /** One qualifier word: hyphenated and accented forms included. */
240
+ const WORD = '[A-Za-zÀ-ÖØ-öø-ÿ][A-Za-zÀ-ÖØ-öø-ÿ-]*';
241
+
242
+ /**
243
+ * How many qualifiers a claim may carry. The bound is load-bearing: it is what
244
+ * stops a match from walking across a whole sentence and inventing a claim
245
+ * ("5 reasons why you should hire agents").
246
+ */
247
+ const MAX_QUALIFIERS = 4;
248
+
249
+ /**
250
+ * Words that TERMINATE a qualifier run instead of decorating it:
251
+ * - dimension nouns — the number belongs to THAT dimension, not to the one
252
+ * further right. "11 workflow agents" is a *workflow* count (graded by the
253
+ * workflows rule), and summing it into the agent dimension is precisely how
254
+ * 27 + 11 became 38.
255
+ * - connectives and articles — without them a single match could span two
256
+ * independent claims ("9 packs and 27 agents").
257
+ */
258
+ const NOT_A_QUALIFIER = new Set(
259
+ `agent agents agente agentes agenten workflow workflows pack packs test tests
260
+ category categories catégorie catégories categoría categorías kategorie kategorien
261
+ framework frameworks language languages langue langues idioma idiomas sprache sprachen
262
+ skill skills phase phases level levels
263
+ and or plus with across of in on for to the a an
264
+ et ou des les la le du dans sur pour avec y con de en und oder mit für`.split(/\s+/)
265
+ );
266
+
267
+ /**
268
+ * Quantity approximators sitting in front of the number. "over 47 agents" is
269
+ * the same marketing hedge as "47+ agents" and must die the same way — the
270
+ * roster is an exact, derivable integer. Deliberately excludes "about" and
271
+ * "around", which read as "on the subject of" far too often to be safe.
272
+ */
273
+ const APPROX_PREFIX_RE = new RegExp(
274
+ `(?:over|more than|nearly|almost|approximately|approx\\.?|up to|~|≈` +
275
+ `|plus de|environ|más de|mehr als)${GAP}$`,
276
+ 'i'
277
+ );
278
+
279
+ /**
280
+ * Build a /g/ claim regex for one dimension's noun alternatives.
281
+ *
282
+ * The leading `\b` is load-bearing: it forbids the number from starting inside
283
+ * a word. Without it a qualifier run happily bridges the stray digit in "E2E
284
+ * tests" into a claim of "2 tests", and reads `section508-agent` as a count.
285
+ * A claim's number is always a whole token.
286
+ */
287
+ function claimRe(noun) {
288
+ return new RegExp(
289
+ `\\b(\\d+)${GAP}(\\+)?${GAP}((?:${WORD}${GAP}){0,${MAX_QUALIFIERS}}?)(?:${noun})\\b`,
290
+ 'gi'
291
+ );
292
+ }
293
+
294
+ const AGENTS_RE = claimRe('agents?|agentes|agenten');
295
+ const WORKFLOWS_RE = claimRe('workflows?');
296
+ const CATEGORIES_RE = claimRe('categories|catégories|categorías|Kategorien');
297
+ const PACKS_RE = claimRe('packs?');
298
+ const TESTS_RE = claimRe('tests?');
299
+ const FRAMEWORKS_RE = claimRe('frameworks?');
300
+ const LANGUAGES_RE = claimRe('languages?|langues?|idiomas?|Sprachen?');
301
+
302
+ /**
303
+ * Collect every claim `re` finds on `line`.
304
+ * `re` must be a /g/ regex; it is reset before use so it is safe to reuse.
305
+ * Matches whose qualifier run crosses a NOT_A_QUALIFIER word are dropped: the
306
+ * number belongs to a different dimension (or a different sentence).
307
+ * @returns {{value:number, plus:boolean, approx:boolean, text:string}[]}
308
+ */
309
+ function matchAll(re, line) {
310
+ re.lastIndex = 0;
311
+ const out = [];
312
+ let m;
313
+ while ((m = re.exec(line)) !== null) {
314
+ const qualifiers = (m[3] || '').split(/[^A-Za-zÀ-ÖØ-öø-ÿ]+/).filter(Boolean);
315
+ if (qualifiers.some((w) => NOT_A_QUALIFIER.has(w.toLowerCase()))) continue;
316
+ out.push({
317
+ value: Number(m[1]),
318
+ plus: m[2] === '+',
319
+ approx: APPROX_PREFIX_RE.test(line.slice(0, m.index)),
320
+ // Markers are dropped from the echoed text so a bolded claim reports as
321
+ // `38 compliance agents`, not `38** compliance agents`. file:line locates it.
322
+ text: m[0].replace(/[*`]+/g, ' ').replace(/\s+/g, ' ').trim(),
323
+ });
324
+ }
325
+ return out;
326
+ }
327
+
328
+ /**
329
+ * Check one line against the derived truth.
330
+ * @param {string} line
331
+ * @param {object} truth output of deriveTruth()
332
+ * @param {{foreignSubject?: boolean}} [opts] foreignSubject suppresses the
333
+ * product-roster rule only (the line counts a DIFFERENT product's agents).
334
+ * @returns {string[]} human-readable drift descriptions (empty when the line is fine)
335
+ */
336
+ function checkLine(line, truth, { foreignSubject = false } = {}) {
337
+ const problems = [];
338
+ const shieldLine = SHIELD_ANCHOR.test(line);
339
+ const devStudioLine = DEV_STUDIO_ANCHOR.test(line);
340
+
341
+ if (shieldLine) {
342
+ for (const { value, text } of matchAll(AGENTS_RE, line)) {
343
+ // Per-category headings ("5 agents") are legitimate; only reject the
344
+ // values that misstate the pack-wide total. Anything that is not a
345
+ // declared per-category size and not 27 is a conflation.
346
+ if (value !== truth.shieldAgents && !isPerCategorySize(value, truth)) {
347
+ problems.push(
348
+ `"${text.trim()}" — Shield has ${truth.shieldAgents} compliance agents` +
349
+ (value === truth.shieldAgents + truth.shieldWorkflows
350
+ ? ` (${truth.shieldAgents} agents + ${truth.shieldWorkflows} workflows conflated into one "agents" figure)`
351
+ : '')
352
+ );
353
+ }
354
+ }
355
+ for (const { value, text } of matchAll(WORKFLOWS_RE, line)) {
356
+ if (value !== truth.shieldWorkflows) {
357
+ problems.push(`"${text.trim()}" — Shield has ${truth.shieldWorkflows} workflows`);
358
+ }
359
+ }
360
+ for (const { value, text } of matchAll(CATEGORIES_RE, line)) {
361
+ if (value !== truth.shieldCategories) {
362
+ problems.push(
363
+ `"${text.trim()}" — Shield has ${truth.shieldCategories} agent categories` +
364
+ (value === truth.shieldCategories + 1
365
+ ? ' (the workflows/ directory is not a category)'
366
+ : '')
367
+ );
368
+ }
369
+ }
370
+ }
371
+
372
+ if (devStudioLine) {
373
+ for (const { value, text } of matchAll(WORKFLOWS_RE, line)) {
374
+ if (value !== truth.devStudioWorkflows) {
375
+ problems.push(`"${text.trim()}" — Dev Studio has ${truth.devStudioWorkflows} workflows`);
376
+ }
377
+ }
378
+ }
379
+
380
+ // A "+" (or an "over"/"more than") on an agent total is always a marketing
381
+ // approximation: the roster is an exact, derivable integer. This is the rule
382
+ // that kills "56+ agents" — and "over 56 agents", which is the same hedge
383
+ // wearing a different hat.
384
+ for (const { plus, approx, text } of matchAll(AGENTS_RE, line)) {
385
+ if (!plus && !approx) continue;
386
+ problems.push(
387
+ `"${text.trim()}" — agent totals are exact and derivable, not approximate` +
388
+ ` (the roster is ${truth.totalAgents}; "56+" came from Dev Studio's *skills* count)`
389
+ );
390
+ }
391
+
392
+ // The roster rule. Shield-anchored lines are graded by the stricter rule
393
+ // above; everywhere else, any number attached to "agents" must be one this
394
+ // repo can actually derive. Default-deny is the point: it is what makes the
395
+ // tracked "47 agents" claims fail the day the roster stops being 47, and what
396
+ // catches a number ("999") that corresponds to nothing at all.
397
+ if (!shieldLine && !foreignSubject) {
398
+ for (const { value, plus, approx, text } of matchAll(AGENTS_RE, line)) {
399
+ if (plus || approx) continue; // already reported as an approximation
400
+ if (!truth.agentCounts.has(value)) {
401
+ problems.push(
402
+ `"${text.trim()}" — no derived agent count equals ${value} (roster: ` +
403
+ `${truth.totalAgents} total = ${truth.installerAgents} installer + ` +
404
+ `${truth.shieldAgents} shield + ${truth.devStudioSubAgents} dev-studio sub-agents)`
405
+ );
406
+ }
407
+ }
408
+ }
409
+
410
+ // Same default-deny for the workflow dimension. Anchored lines are graded
411
+ // against the one pack they name; an unanchored line must still quote a real
412
+ // total (this is what catches a pack description whose own name sits on the
413
+ // line above it).
414
+ if (!shieldLine && !devStudioLine) {
415
+ for (const { value, text } of matchAll(WORKFLOWS_RE, line)) {
416
+ if (!truth.workflowCounts.has(value)) {
417
+ problems.push(
418
+ `"${text.trim()}" — no pack has ${value} workflows (Shield ${truth.shieldWorkflows},` +
419
+ ` Dev Studio ${truth.devStudioWorkflows})`
420
+ );
421
+ }
422
+ }
423
+ }
424
+
425
+ // Packs and languages are exact derivable integers, so — like the agent
426
+ // roster — they get no hedge. Modelling each hedge's arithmetic instead would
427
+ // mean conceding that "10+ languages" (>= 10, true) and "more than 10
428
+ // languages" (> 10, false) are different claims about the same 10; the honest
429
+ // rule is that a number you can derive is simply stated.
430
+ for (const { value, plus, approx, text } of matchAll(PACKS_RE, line)) {
431
+ if (plus || approx) {
432
+ problems.push(
433
+ `"${text.trim()}" — the pack count is exact and derivable, not approximate` +
434
+ ` (the registry declares ${truth.packCount})`
435
+ );
436
+ } else if (value !== truth.packCount) {
437
+ problems.push(`"${text.trim()}" — the registry declares ${truth.packCount} packs`);
438
+ }
439
+ }
440
+
441
+ for (const { value, plus, approx, text } of matchAll(LANGUAGES_RE, line)) {
442
+ if (plus || approx) {
443
+ problems.push(
444
+ `"${text.trim()}" — the language count is exact and derivable, not approximate` +
445
+ ` (tools/cli/i18n.js declares ${truth.languages.length})`
446
+ );
447
+ } else if (value !== truth.languages.length) {
448
+ problems.push(
449
+ `"${text.trim()}" — tools/cli/i18n.js declares ${truth.languages.length} languages` +
450
+ ` (${truth.languages.join(', ')})`
451
+ );
452
+ }
453
+ }
454
+
455
+ // Frameworks are the one dimension gated as a LOWER BOUND. Unlike the rosters
456
+ // above, the exact claims are per-category groupings ("AI Governance (3
457
+ // frameworks)") that are NOT derivable — registry categories carry agents,
458
+ // not compliance_tags — so an equality rule would fire on true statements.
459
+ // The defect that remains checkable is the overclaim: promising more
460
+ // frameworks than the pack actually tags. "25+" stays honest while 26 exist.
461
+ for (const { value, text } of matchAll(FRAMEWORKS_RE, line)) {
462
+ if (value > truth.shieldFrameworks) {
463
+ problems.push(
464
+ `"${text.trim()}" — Shield tags ${truth.shieldFrameworks} compliance frameworks`
465
+ );
466
+ }
467
+ }
468
+
469
+ return problems;
470
+ }
471
+
472
+ /** Per-category agent-group sizes that may legitimately appear near "agents". */
473
+ function isPerCategorySize(value, truth) {
474
+ return truth.perCategorySizes.has(value);
475
+ }
476
+
477
+ /* ── Scan ───────────────────────────────────────────────────────────────── */
478
+
479
+ /** Files tracked by git, restricted to the declared truth surfaces. */
480
+ function trackedTruthSurfaces() {
481
+ let tracked;
482
+ try {
483
+ tracked = new Set(
484
+ execFileSync('git', ['ls-files'], { cwd: REPO_ROOT, encoding: 'utf8' })
485
+ .split('\n')
486
+ .filter(Boolean)
487
+ );
488
+ } catch {
489
+ tracked = null; // not a git checkout (e.g. an npm tarball) → scan what exists
490
+ }
491
+ return TRUTH_SURFACES.filter((f) => {
492
+ if (tracked && !tracked.has(f)) return false;
493
+ return fs.existsSync(path.join(REPO_ROOT, f));
494
+ });
495
+ }
496
+
497
+ /**
498
+ * Scan the truth surfaces.
499
+ * @returns {{ ok: boolean, drifts: string[], unverified: string[], truth: object }}
500
+ */
501
+ function check({ registryPath = DEFAULT_REGISTRY_PATH, testCount = null } = {}) {
502
+ const truth = deriveTruth(registryPath);
503
+ const drifts = [...truth.derivationMismatches];
504
+ const unverified = [];
505
+
506
+ for (const file of trackedTruthSurfaces()) {
507
+ const lines = fs.readFileSync(path.join(REPO_ROOT, file), 'utf8').split(/\r?\n/);
508
+ let inForeignTable = false;
509
+ lines.forEach((line, i) => {
510
+ if (isFrozenHistoryRow(line)) return;
511
+ if (FOREIGN_TABLE_HEADER_RE.test(line)) {
512
+ inForeignTable = true;
513
+ return;
514
+ }
515
+ if (inForeignTable && !line.trimStart().startsWith('|')) inForeignTable = false;
516
+
517
+ const foreignSubject = inForeignTable || FOREIGN_SUBJECT_ANCHOR.test(line);
518
+ for (const problem of checkLine(line, truth, { foreignSubject })) {
519
+ drifts.push(`${file}:${i + 1}: ${problem}`);
520
+ }
521
+ for (const { value, text } of matchAll(TESTS_RE, line)) {
522
+ if (testCount === null) {
523
+ unverified.push(`${file}:${i + 1}: "${text.trim()}"`);
524
+ } else if (value !== testCount) {
525
+ drifts.push(`${file}:${i + 1}: "${text.trim()}" — the suite reports ${testCount} tests`);
526
+ }
527
+ }
528
+ });
529
+ }
530
+
531
+ return { ok: drifts.length === 0, drifts, unverified, truth };
532
+ }
533
+
534
+ /**
535
+ * Read the test-count rule's arming variable.
536
+ * Unset/empty means "not armed" (never a failure). Anything else must be a
537
+ * plain non-negative integer: `Number(" ")` is 0 and `Number("-1")` is an
538
+ * integer, so a bare Number()+isInteger check would silently gate against a
539
+ * garbage total.
540
+ * @returns {{armed: boolean, testCount: number|null, error: string|null}}
541
+ */
542
+ function readTestCountEnv(raw) {
543
+ if (typeof raw !== 'string' || raw.trim() === '') {
544
+ return { armed: false, testCount: null, error: null };
545
+ }
546
+ if (!/^\d+$/.test(raw.trim())) {
547
+ return {
548
+ armed: true,
549
+ testCount: null,
550
+ error: `BMAD_PLUS_TEST_COUNT must be a non-negative integer (got "${raw}")`,
551
+ };
552
+ }
553
+ return { armed: true, testCount: Number(raw.trim()), error: null };
554
+ }
555
+
556
+ /* ── CLI ────────────────────────────────────────────────────────────────── */
557
+
558
+ function main(argv) {
559
+ const args = argv.slice(2);
560
+ const { armed, testCount, error } = readTestCountEnv(process.env.BMAD_PLUS_TEST_COUNT);
561
+ if (error) {
562
+ console.error(error);
563
+ return 1;
564
+ }
565
+
566
+ const result = check({ testCount });
567
+
568
+ if (args.includes('--print')) {
569
+ const { derivationMismatches, ...facts } = result.truth;
570
+ void derivationMismatches;
571
+ const asArray = (_k, v) => (v instanceof Set ? [...v].sort((a, b) => a - b) : v);
572
+ console.log(JSON.stringify(facts, asArray, 2));
573
+ return 0;
574
+ }
575
+
576
+ const partial = result.unverified.length > 0;
577
+
578
+ if (result.ok) {
579
+ console.log(
580
+ `${partial ? 'OK (PARTIAL)' : 'OK'} — no count drift. Derived from registry.yaml + files on disk: ` +
581
+ `${result.truth.shieldAgents} shield agents + ${result.truth.shieldWorkflows} shield workflows, ` +
582
+ `${result.truth.devStudioWorkflows} dev-studio workflows, ${result.truth.totalAgents} agents total, ` +
583
+ `${result.truth.packCount} packs, ${result.truth.languages.length} languages.`
584
+ );
585
+ } else {
586
+ console.error('COUNT DRIFT DETECTED — hand-typed numbers contradict registry.yaml:');
587
+ for (const d of result.drifts) console.error(` - ${d}`);
588
+ console.error(
589
+ 'Fix the claim (or registry.yaml if the registry is what is wrong), then re-run:'
590
+ );
591
+ console.error(' node tools/build/check-counts.js');
592
+ }
593
+
594
+ // Reported on BOTH paths, on purpose. A green run with the variable unset
595
+ // must not read as full coverage — and a red run must not bury the fact that
596
+ // the test claims went unchecked on top of whatever else broke.
597
+ if (partial) {
598
+ console.warn('');
599
+ console.warn(
600
+ `!! NOT GATED — ${result.unverified.length} test-count claim(s) were NOT checked ` +
601
+ `(BMAD_PLUS_TEST_COUNT unset):`
602
+ );
603
+ for (const u of result.unverified) console.warn(` ${u}`);
604
+ console.warn(' This run does not cover those numbers. To gate them, pass the');
605
+ console.warn(' Jest-reported total:');
606
+ console.warn(' BMAD_PLUS_TEST_COUNT=<total> node tools/build/check-counts.js');
607
+ } else if (armed) {
608
+ console.log(`Test-count claims gated against BMAD_PLUS_TEST_COUNT=${testCount}.`);
609
+ }
610
+
611
+ return result.ok ? 0 : 1;
612
+ }
613
+
614
+ if (require.main === module) {
615
+ process.exitCode = main(process.argv);
616
+ }
617
+
618
+ module.exports = {
619
+ REPO_ROOT,
620
+ TRUTH_SURFACES,
621
+ deriveTruth,
622
+ checkLine,
623
+ isFrozenHistoryRow,
624
+ readTestCountEnv,
625
+ trackedTruthSurfaces,
626
+ check,
627
+ main,
628
+ };
@@ -100,7 +100,7 @@ Agents MUST follow these behavioral principles:
100
100
 
101
101
  ## Registry facts (computed from registry.yaml — never hand-typed)
102
102
 
103
- - Product: BMAD+ v0.12.0 (derived from BMAD-METHOD v6.6.0)
103
+ - Product: BMAD+ v0.12.2 (derived from BMAD-METHOD v6.6.0)
104
104
  - Models supported: claude, gpt, gemini, local (model-agnostic by contract)
105
105
  - Packs (9): Core, OSINT, Maker, Shield, SEO, Memory, Dev Studio, Backup, Animated
106
106
  - Installer agents (14 across all packs):
@@ -105,7 +105,7 @@ Agents MUST follow these behavioral principles:
105
105
 
106
106
  ## Registry facts (computed from registry.yaml — never hand-typed)
107
107
 
108
- - Product: BMAD+ v0.12.0 (derived from BMAD-METHOD v6.6.0)
108
+ - Product: BMAD+ v0.12.2 (derived from BMAD-METHOD v6.6.0)
109
109
  - Models supported: claude, gpt, gemini, local (model-agnostic by contract)
110
110
  - Packs (9): Core, OSINT, Maker, Shield, SEO, Memory, Dev Studio, Backup, Animated
111
111
  - Installer agents (14 across all packs):
@@ -100,7 +100,7 @@ Agents MUST follow these behavioral principles:
100
100
 
101
101
  ## Registry facts (computed from registry.yaml — never hand-typed)
102
102
 
103
- - Product: BMAD+ v0.12.0 (derived from BMAD-METHOD v6.6.0)
103
+ - Product: BMAD+ v0.12.2 (derived from BMAD-METHOD v6.6.0)
104
104
  - Models supported: claude, gpt, gemini, local (model-agnostic by contract)
105
105
  - Packs (9): Core, OSINT, Maker, Shield, SEO, Memory, Dev Studio, Backup, Animated
106
106
  - Installer agents (14 across all packs):
@@ -98,7 +98,7 @@ Agents MUST follow these behavioral principles:
98
98
 
99
99
  ## Registry facts (computed from registry.yaml — never hand-typed)
100
100
 
101
- - Product: BMAD+ v0.12.0 (derived from BMAD-METHOD v6.6.0)
101
+ - Product: BMAD+ v0.12.2 (derived from BMAD-METHOD v6.6.0)
102
102
  - Models supported: claude, gpt, gemini, local (model-agnostic by contract)
103
103
  - Packs (9): Core, OSINT, Maker, Shield, SEO, Memory, Dev Studio, Backup, Animated
104
104
  - Installer agents (14 across all packs):
@@ -101,7 +101,7 @@ Agents MUST follow these behavioral principles:
101
101
 
102
102
  ## Registry facts (computed from registry.yaml — never hand-typed)
103
103
 
104
- - Product: BMAD+ v0.12.0 (derived from BMAD-METHOD v6.6.0)
104
+ - Product: BMAD+ v0.12.2 (derived from BMAD-METHOD v6.6.0)
105
105
  - Models supported: claude, gpt, gemini, local (model-agnostic by contract)
106
106
  - Packs (9): Core, OSINT, Maker, Shield, SEO, Memory, Dev Studio, Backup, Animated
107
107
  - Installer agents (14 across all packs):