bmad-method 6.7.1 → 6.8.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 (114) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/README.md +10 -0
  3. package/package.json +3 -2
  4. package/removals.txt +8 -0
  5. package/src/bmm-skills/1-analysis/bmad-agent-analyst/SKILL.md +2 -0
  6. package/src/bmm-skills/1-analysis/bmad-agent-tech-writer/SKILL.md +2 -0
  7. package/src/bmm-skills/1-analysis/bmad-document-project/SKILL.md +1 -1
  8. package/src/bmm-skills/1-analysis/bmad-prfaq/SKILL.md +1 -1
  9. package/src/bmm-skills/1-analysis/bmad-product-brief/SKILL.md +5 -2
  10. package/src/bmm-skills/1-analysis/research/bmad-domain-research/SKILL.md +1 -1
  11. package/src/bmm-skills/1-analysis/research/bmad-market-research/SKILL.md +1 -1
  12. package/src/bmm-skills/1-analysis/research/bmad-technical-research/SKILL.md +1 -1
  13. package/src/bmm-skills/2-plan-workflows/bmad-agent-pm/SKILL.md +2 -0
  14. package/src/bmm-skills/2-plan-workflows/bmad-agent-ux-designer/SKILL.md +2 -0
  15. package/src/bmm-skills/2-plan-workflows/bmad-agent-ux-designer/customize.toml +1 -1
  16. package/src/bmm-skills/2-plan-workflows/bmad-prd/SKILL.md +9 -4
  17. package/src/bmm-skills/2-plan-workflows/bmad-prd/assets/prd-template.md +4 -7
  18. package/src/bmm-skills/2-plan-workflows/bmad-prd/assets/prd-validation-checklist.md +4 -4
  19. package/src/bmm-skills/2-plan-workflows/bmad-prd/references/headless.md +2 -2
  20. package/src/bmm-skills/2-plan-workflows/bmad-ux/SKILL.md +90 -0
  21. package/src/bmm-skills/2-plan-workflows/bmad-ux/assets/color-themes.md +9 -0
  22. package/src/bmm-skills/2-plan-workflows/bmad-ux/assets/design-directions.md +9 -0
  23. package/src/bmm-skills/2-plan-workflows/bmad-ux/assets/design-example-editorial.md +158 -0
  24. package/src/bmm-skills/2-plan-workflows/bmad-ux/assets/design-example-mobile.md +93 -0
  25. package/src/bmm-skills/2-plan-workflows/bmad-ux/assets/design-example-shadcn.md +109 -0
  26. package/src/bmm-skills/2-plan-workflows/bmad-ux/assets/excalidraw-wireframe.md +19 -0
  27. package/src/bmm-skills/2-plan-workflows/bmad-ux/assets/experience-example-mobile.md +112 -0
  28. package/src/bmm-skills/2-plan-workflows/bmad-ux/assets/experience-example-shadcn.md +133 -0
  29. package/src/bmm-skills/2-plan-workflows/bmad-ux/assets/headless-schemas.md +84 -0
  30. package/src/bmm-skills/2-plan-workflows/bmad-ux/assets/key-screens.md +29 -0
  31. package/src/bmm-skills/2-plan-workflows/bmad-ux/assets/validation-report-template.html +319 -0
  32. package/src/bmm-skills/2-plan-workflows/bmad-ux/customize.toml +100 -0
  33. package/src/bmm-skills/2-plan-workflows/bmad-ux/references/creative-tools.md +19 -0
  34. package/src/bmm-skills/2-plan-workflows/bmad-ux/references/design-md-spec.md +50 -0
  35. package/src/bmm-skills/2-plan-workflows/bmad-ux/references/headless.md +37 -0
  36. package/src/bmm-skills/2-plan-workflows/bmad-ux/references/validate.md +115 -0
  37. package/src/bmm-skills/3-solutioning/bmad-agent-architect/SKILL.md +2 -0
  38. package/src/bmm-skills/3-solutioning/bmad-check-implementation-readiness/SKILL.md +1 -1
  39. package/src/bmm-skills/3-solutioning/bmad-create-architecture/SKILL.md +1 -1
  40. package/src/bmm-skills/3-solutioning/bmad-create-epics-and-stories/SKILL.md +1 -1
  41. package/src/bmm-skills/3-solutioning/bmad-generate-project-context/SKILL.md +1 -1
  42. package/src/bmm-skills/4-implementation/bmad-agent-dev/SKILL.md +2 -0
  43. package/src/bmm-skills/4-implementation/bmad-checkpoint-preview/SKILL.md +1 -1
  44. package/src/bmm-skills/4-implementation/bmad-code-review/SKILL.md +1 -1
  45. package/src/bmm-skills/4-implementation/bmad-correct-course/SKILL.md +1 -1
  46. package/src/bmm-skills/4-implementation/bmad-create-story/SKILL.md +1 -1
  47. package/src/bmm-skills/4-implementation/bmad-dev-story/SKILL.md +23 -8
  48. package/src/bmm-skills/4-implementation/bmad-investigate/SKILL.md +2 -0
  49. package/src/bmm-skills/4-implementation/bmad-qa-generate-e2e-tests/SKILL.md +1 -1
  50. package/src/bmm-skills/4-implementation/bmad-quick-dev/SKILL.md +1 -1
  51. package/src/bmm-skills/4-implementation/bmad-retrospective/SKILL.md +1 -1
  52. package/src/bmm-skills/4-implementation/bmad-sprint-planning/SKILL.md +2 -1
  53. package/src/bmm-skills/4-implementation/bmad-sprint-status/SKILL.md +2 -1
  54. package/src/bmm-skills/module-help.csv +1 -1
  55. package/src/core-skills/bmad-advanced-elicitation/methods.csv +69 -50
  56. package/src/core-skills/bmad-brainstorming/steps/step-03-technique-execution.md +6 -4
  57. package/src/core-skills/bmad-brainstorming/workflow.md +1 -1
  58. package/src/core-skills/bmad-spec/SKILL.md +129 -0
  59. package/src/core-skills/bmad-spec/assets/headless-schemas.md +33 -0
  60. package/src/core-skills/bmad-spec/assets/spec-template.md +49 -0
  61. package/src/core-skills/bmad-spec/customize.toml +53 -0
  62. package/src/core-skills/module-help.csv +1 -1
  63. package/src/scripts/resolve_customization.py +9 -1
  64. package/src/scripts/tests/test_resolve_customization.py +50 -0
  65. package/tools/bundle-web-bundles.js +117 -0
  66. package/tools/installer/modules/custom-module-manager.js +113 -4
  67. package/tools/installer/modules/official-modules.js +83 -3
  68. package/tools/skill-validator.md +1 -19
  69. package/tools/validate-sidebar-order.js +388 -0
  70. package/tools/validate-skills.js +1 -40
  71. package/web-bundles/README.md +46 -0
  72. package/web-bundles/brainstorming-coach/INSTRUCTIONS.md +86 -0
  73. package/web-bundles/brainstorming-coach/SKILL.md +83 -0
  74. package/web-bundles/brainstorming-coach/brain-methods.csv +62 -0
  75. package/web-bundles/bundles.json +139 -0
  76. package/web-bundles/market-and-industry-research/INSTRUCTIONS.md +88 -0
  77. package/web-bundles/market-and-industry-research/SKILL.md +59 -0
  78. package/web-bundles/prd-coach/INSTRUCTIONS.md +86 -0
  79. package/web-bundles/prd-coach/SKILL.md +101 -0
  80. package/web-bundles/prd-coach/prd-template.md +165 -0
  81. package/web-bundles/prd-coach/prd-validation-checklist.md +135 -0
  82. package/web-bundles/prfaq-coach/INSTRUCTIONS.md +86 -0
  83. package/web-bundles/prfaq-coach/SKILL.md +139 -0
  84. package/web-bundles/product-brief-coach/INSTRUCTIONS.md +86 -0
  85. package/web-bundles/product-brief-coach/SKILL.md +113 -0
  86. package/web-bundles/ux-coach/INSTRUCTIONS.md +92 -0
  87. package/web-bundles/ux-coach/SKILL.md +187 -0
  88. package/web-bundles/ux-coach/ux-validation.md +100 -0
  89. package/src/bmm-skills/2-plan-workflows/bmad-create-ux-design/SKILL.md +0 -75
  90. package/src/bmm-skills/2-plan-workflows/bmad-create-ux-design/customize.toml +0 -41
  91. package/src/bmm-skills/2-plan-workflows/bmad-create-ux-design/steps/step-01-init.md +0 -135
  92. package/src/bmm-skills/2-plan-workflows/bmad-create-ux-design/steps/step-01b-continue.md +0 -127
  93. package/src/bmm-skills/2-plan-workflows/bmad-create-ux-design/steps/step-02-discovery.md +0 -190
  94. package/src/bmm-skills/2-plan-workflows/bmad-create-ux-design/steps/step-03-core-experience.md +0 -217
  95. package/src/bmm-skills/2-plan-workflows/bmad-create-ux-design/steps/step-04-emotional-response.md +0 -220
  96. package/src/bmm-skills/2-plan-workflows/bmad-create-ux-design/steps/step-05-inspiration.md +0 -235
  97. package/src/bmm-skills/2-plan-workflows/bmad-create-ux-design/steps/step-06-design-system.md +0 -253
  98. package/src/bmm-skills/2-plan-workflows/bmad-create-ux-design/steps/step-07-defining-experience.md +0 -255
  99. package/src/bmm-skills/2-plan-workflows/bmad-create-ux-design/steps/step-08-visual-foundation.md +0 -225
  100. package/src/bmm-skills/2-plan-workflows/bmad-create-ux-design/steps/step-09-design-directions.md +0 -225
  101. package/src/bmm-skills/2-plan-workflows/bmad-create-ux-design/steps/step-10-user-journeys.md +0 -242
  102. package/src/bmm-skills/2-plan-workflows/bmad-create-ux-design/steps/step-11-component-strategy.md +0 -249
  103. package/src/bmm-skills/2-plan-workflows/bmad-create-ux-design/steps/step-12-ux-patterns.md +0 -238
  104. package/src/bmm-skills/2-plan-workflows/bmad-create-ux-design/steps/step-13-responsive-accessibility.md +0 -265
  105. package/src/bmm-skills/2-plan-workflows/bmad-create-ux-design/steps/step-14-complete.md +0 -177
  106. package/src/bmm-skills/2-plan-workflows/bmad-create-ux-design/ux-design-template.md +0 -13
  107. package/src/core-skills/bmad-distillator/SKILL.md +0 -177
  108. package/src/core-skills/bmad-distillator/agents/distillate-compressor.md +0 -116
  109. package/src/core-skills/bmad-distillator/agents/round-trip-reconstructor.md +0 -68
  110. package/src/core-skills/bmad-distillator/resources/compression-rules.md +0 -51
  111. package/src/core-skills/bmad-distillator/resources/distillate-format-reference.md +0 -227
  112. package/src/core-skills/bmad-distillator/resources/splitting-strategy.md +0 -78
  113. package/src/core-skills/bmad-distillator/scripts/analyze_sources.py +0 -300
  114. package/src/core-skills/bmad-distillator/scripts/tests/test_analyze_sources.py +0 -204
@@ -846,11 +846,35 @@ class OfficialModules {
846
846
  return false;
847
847
  }
848
848
 
849
- // Dynamically discover all installed modules by scanning bmad directory
850
- // A directory is a module ONLY if it contains a config.yaml file
849
+ // Primary source: installer-written config.toml + config.user.toml (v6+).
850
+ // Both files together hold all install answers; config.user.toml carries
851
+ // user-scoped keys like user_name that would otherwise be re-prompted on
852
+ // every reinstall.
851
853
  let foundAny = false;
852
- const entries = await fs.readdir(bmadDir, { withFileTypes: true });
854
+ for (const fileName of ['config.toml', 'config.user.toml']) {
855
+ const tomlPath = path.join(bmadDir, fileName);
856
+ if (!(await fs.pathExists(tomlPath))) continue;
857
+ try {
858
+ const content = await fs.readFile(tomlPath, 'utf8');
859
+ const parsed = parseCentralToml(content);
860
+ for (const [section, values] of Object.entries(parsed)) {
861
+ if (values && typeof values === 'object' && !Array.isArray(values)) {
862
+ if (!this._existingConfig[section]) this._existingConfig[section] = {};
863
+ Object.assign(this._existingConfig[section], values);
864
+ foundAny = true;
865
+ }
866
+ }
867
+ } catch {
868
+ // Ignore parse errors
869
+ }
870
+ }
853
871
 
872
+ if (foundAny) {
873
+ return true;
874
+ }
875
+
876
+ // Fallback: legacy per-module config.yaml files (pre-v6 installations).
877
+ const entries = await fs.readdir(bmadDir, { withFileTypes: true });
854
878
  const nonModuleDirs = new Set(['_config', '_memory', 'memory', 'docs', 'scripts', 'custom']);
855
879
  for (const entry of entries) {
856
880
  if (entry.isDirectory()) {
@@ -2127,4 +2151,60 @@ class OfficialModules {
2127
2151
  }
2128
2152
  }
2129
2153
 
2154
+ /**
2155
+ * Parse a config.toml or config.user.toml written by writeCentralConfig.
2156
+ * Only handles the subset of TOML the installer produces: [core],
2157
+ * [modules.<code>], string/bool/number scalar values. [agents.*] and other
2158
+ * sections are ignored. Returns a plain object keyed by section name where
2159
+ * module sections use the bare code (e.g. "bmm"), not the full "modules.bmm".
2160
+ */
2161
+ function parseCentralToml(content) {
2162
+ const result = {};
2163
+ let currentSection = null;
2164
+
2165
+ for (const rawLine of content.split('\n')) {
2166
+ const line = rawLine.trim();
2167
+ if (!line || line.startsWith('#')) continue;
2168
+
2169
+ const sectionMatch = line.match(/^\[([^\]]+)\]\s*$/);
2170
+ if (sectionMatch) {
2171
+ const name = sectionMatch[1];
2172
+ if (name === 'core') {
2173
+ currentSection = 'core';
2174
+ } else if (name.startsWith('modules.')) {
2175
+ currentSection = name.slice('modules.'.length);
2176
+ } else {
2177
+ currentSection = null;
2178
+ }
2179
+ if (currentSection && !result[currentSection]) {
2180
+ result[currentSection] = {};
2181
+ }
2182
+ continue;
2183
+ }
2184
+
2185
+ if (!currentSection) continue;
2186
+
2187
+ const kvMatch = line.match(/^([a-zA-Z_][a-zA-Z0-9_]*)\s*=\s*(.+)$/);
2188
+ if (!kvMatch) continue;
2189
+
2190
+ const key = kvMatch[1];
2191
+ const raw = kvMatch[2].trim();
2192
+ let value;
2193
+ if (raw.startsWith('"') && raw.endsWith('"')) {
2194
+ value = raw.slice(1, -1).replaceAll(/\\(["\\nrbt])/g, (_, c) => ({ '"': '"', '\\': '\\', n: '\n', r: '\r', b: '\b', t: '\t' })[c]);
2195
+ } else if (raw === 'true') {
2196
+ value = true;
2197
+ } else if (raw === 'false') {
2198
+ value = false;
2199
+ } else if (raw !== '' && !isNaN(raw)) {
2200
+ value = Number(raw);
2201
+ } else {
2202
+ value = raw;
2203
+ }
2204
+ result[currentSection][key] = value;
2205
+ }
2206
+
2207
+ return result;
2208
+ }
2209
+
2130
2210
  module.exports = { OfficialModules };
@@ -10,7 +10,7 @@ Before running inference-based validation, run the deterministic validator:
10
10
  node tools/validate-skills.js --json path/to/skill-dir
11
11
  ```
12
12
 
13
- This checks 14 rules deterministically: SKILL-01, SKILL-02, SKILL-03, SKILL-04, SKILL-05, SKILL-06, SKILL-07, WF-01, WF-02, PATH-02, STEP-01, STEP-06, STEP-07, SEQ-02.
13
+ This checks 12 rules deterministically: SKILL-01, SKILL-02, SKILL-03, SKILL-04, SKILL-05, SKILL-06, SKILL-07, PATH-02, STEP-01, STEP-06, STEP-07, SEQ-02.
14
14
 
15
15
  Review its JSON output. For any rule that produced **zero findings** in the first pass, **skip it** during inference-based validation below — it has already been verified. If a rule produced any findings, the inference validator should still review that rule (some rules like SKILL-04 and SKILL-06 have sub-checks that benefit from judgment). Focus your inference effort on the remaining rules that require judgment (PATH-01, PATH-03, PATH-04, PATH-05, WF-03, STEP-02, STEP-03, STEP-04, STEP-05, SEQ-01, REF-01, REF-02, REF-03).
16
16
 
@@ -98,24 +98,6 @@ If no findings are generated (from either pass), the skill passes validation.
98
98
 
99
99
  ---
100
100
 
101
- ### WF-01 — Only SKILL.md May Have `name` in Frontmatter
102
-
103
- - **Severity:** HIGH
104
- - **Applies to:** all `.md` files except `SKILL.md`
105
- - **Rule:** The `name` field belongs only in `SKILL.md`. No other markdown file in the skill directory may have `name:` in its frontmatter.
106
- - **Detection:** Parse frontmatter of every non-SKILL.md markdown file and check for `name:` key.
107
- - **Fix:** Remove the `name:` line from the file's frontmatter.
108
- - **Exception:** `bmad-agent-tech-writer` — has sub-skill files with intentional `name` fields (to be revisited).
109
-
110
- ### WF-02 — Only SKILL.md May Have `description` in Frontmatter
111
-
112
- - **Severity:** HIGH
113
- - **Applies to:** all `.md` files except `SKILL.md`
114
- - **Rule:** The `description` field belongs only in `SKILL.md`. No other markdown file in the skill directory may have `description:` in its frontmatter.
115
- - **Detection:** Parse frontmatter of every non-SKILL.md markdown file and check for `description:` key.
116
- - **Fix:** Remove the `description:` line from the file's frontmatter.
117
- - **Exception:** `bmad-agent-tech-writer` — has sub-skill files with intentional `description` fields (to be revisited).
118
-
119
101
  ### WF-03 — workflow.md Frontmatter Variables Must Be Config or Runtime Only
120
102
 
121
103
  - **Severity:** HIGH
@@ -0,0 +1,388 @@
1
+ /**
2
+ * Sidebar Order Validator
3
+ *
4
+ * Validates sidebar.order values in YAML frontmatter of markdown doc files.
5
+ *
6
+ * English docs — strict (errors):
7
+ * - Duplicate sidebar.order values within the same directory
8
+ * - Gaps in the ordering sequence
9
+ * - sidebar: block present but missing or invalid order: field
10
+ *
11
+ * Translations — errors + warnings:
12
+ * - Same structural rules as English (duplicates, gaps) — errors
13
+ * - Order drift from English counterpart — warnings (non-blocking)
14
+ *
15
+ * Usage:
16
+ * node tools/validate-sidebar-order.js
17
+ */
18
+
19
+ const fs = require('node:fs');
20
+ const path = require('node:path');
21
+
22
+ const DOCS_ROOT = path.resolve(__dirname, '../docs');
23
+ const FRONTMATTER_RE = /^---\r?\n([\s\S]*?)\r?\n---[ \t]*(?:\r?\n|$)/;
24
+ const LOCALE_RE = /^[a-z]{2}(?:-[a-zA-Z0-9]+)*$/;
25
+ const MAX_GAPS = 50;
26
+
27
+ // ── Main ─────────────────────────────────────────────────────────────────
28
+
29
+ /**
30
+ * Scan all docs, validate sidebar orders, and report errors/warnings.
31
+ * Exits 0 on success, 1 if any errors found.
32
+ */
33
+ function main() {
34
+ if (!fs.existsSync(DOCS_ROOT)) {
35
+ console.error(`Error: docs directory not found at ${DOCS_ROOT}`);
36
+ process.exit(1);
37
+ }
38
+
39
+ const { languageDirs, englishSections } = classifyDocsDirs();
40
+ console.log(`\nValidating sidebar ordering in: ${DOCS_ROOT}\n`);
41
+ console.log(`English sections: ${englishSections.join(', ')}`);
42
+ console.log(`Translation languages: ${languageDirs.join(', ')}\n`);
43
+
44
+ const allErrors = [];
45
+ const allWarnings = [];
46
+ const englishOrderMaps = new Map();
47
+
48
+ for (const section of englishSections) {
49
+ const sectionDir = path.join(DOCS_ROOT, section);
50
+ if (!fs.existsSync(sectionDir)) continue;
51
+
52
+ console.log(`\nChecking English docs/${section}/`);
53
+ const { orderMap, issues } = checkDirectory(sectionDir);
54
+ englishOrderMaps.set(section, orderMap);
55
+
56
+ for (const issue of issues) {
57
+ allErrors.push(issue);
58
+ reportIssue(issue, ' ', `docs/${section}`);
59
+ }
60
+ if (issues.length === 0) {
61
+ console.log(` [OK] docs/${section}/ — all orders valid`);
62
+ }
63
+ }
64
+
65
+ for (const lang of languageDirs) {
66
+ const langDir = path.join(DOCS_ROOT, lang);
67
+ const langSections = fs
68
+ .readdirSync(langDir, { withFileTypes: true })
69
+ .filter((e) => e.isDirectory() && !e.name.startsWith('_'))
70
+ .map((e) => e.name);
71
+
72
+ console.log(`\nChecking ${lang}/ docs`);
73
+
74
+ for (const section of langSections) {
75
+ const sectionDir = path.join(langDir, section);
76
+ if (!fs.existsSync(sectionDir)) continue;
77
+
78
+ console.log(` ${lang}/${section}/`);
79
+ const { issues } = checkDirectory(sectionDir);
80
+
81
+ for (const issue of issues) {
82
+ allErrors.push(issue);
83
+ reportIssue(issue, ' ', `${lang}/${section}`);
84
+ }
85
+ if (issues.length === 0) {
86
+ console.log(` [OK] ${lang}/${section}/ — all orders valid`);
87
+ }
88
+ }
89
+
90
+ for (const w of checkTranslationDrift(lang, langSections, englishOrderMaps)) {
91
+ allWarnings.push(w);
92
+ const langDisplay = w.langOrder === null ? 'no order' : `order ${w.langOrder}`;
93
+ console.log(` [WARN] ${rel(w.file)}: ${langDisplay} (English: ${w.englishOrder})`);
94
+ }
95
+ }
96
+
97
+ printSummary(allErrors, allWarnings);
98
+ process.exit(allErrors.length > 0 ? 1 : 0);
99
+ }
100
+
101
+ // ── Directory classification ─────────────────────────────────────────────
102
+
103
+ /**
104
+ * Classify top-level docs/ subdirectories as language dirs or English sections.
105
+ * Language dirs match BCP 47 locale pattern; everything else is English.
106
+ * @returns {{ languageDirs: string[], englishSections: string[] }}
107
+ */
108
+ function classifyDocsDirs() {
109
+ const dirs = fs.readdirSync(DOCS_ROOT, { withFileTypes: true }).filter((e) => e.isDirectory() && !e.name.startsWith('_'));
110
+
111
+ const languageDirs = [];
112
+ const englishSections = [];
113
+
114
+ for (const d of dirs) {
115
+ (LOCALE_RE.test(d.name) ? languageDirs : englishSections).push(d.name);
116
+ }
117
+
118
+ return { languageDirs, englishSections };
119
+ }
120
+
121
+ // ── Per-directory validation ─────────────────────────────────────────────
122
+
123
+ /**
124
+ * Validate sidebar.order values for all markdown files in a directory.
125
+ * Detects duplicates, gaps in sequence, missing-order, and invalid-order fields.
126
+ * @param {string} dirPath - Absolute path to the directory to scan.
127
+ * @returns {{ orderMap: Map<number, string[]>, issues: object[] }}
128
+ */
129
+ function checkDirectory(dirPath) {
130
+ const issues = [];
131
+ const orderMap = new Map();
132
+ const missingOrder = [];
133
+ const invalidOrder = [];
134
+
135
+ for (const entry of listMdEntries(dirPath)) {
136
+ const fullPath = path.join(dirPath, entry.name);
137
+ const result = extractSidebarOrder(fs.readFileSync(fullPath, 'utf-8'));
138
+
139
+ if (!result.hasSidebar) continue;
140
+ if (result.order === null) {
141
+ if (result.orderInvalid) {
142
+ invalidOrder.push(fullPath);
143
+ } else {
144
+ missingOrder.push(fullPath);
145
+ }
146
+ continue;
147
+ }
148
+
149
+ if (!orderMap.has(result.order)) orderMap.set(result.order, []);
150
+ orderMap.get(result.order).push(fullPath);
151
+ }
152
+
153
+ for (const file of missingOrder) {
154
+ issues.push({ level: 'error', type: 'missing-order', file, message: 'Has sidebar: block but no order: field' });
155
+ }
156
+
157
+ for (const file of invalidOrder) {
158
+ issues.push({ level: 'error', type: 'invalid-order', file, message: 'Invalid sidebar.order: must be a positive integer' });
159
+ }
160
+
161
+ for (const [order, files] of orderMap) {
162
+ if (files.length > 1) {
163
+ for (const file of files) {
164
+ issues.push({ level: 'error', type: 'duplicate-order', file, order, message: `Duplicate sidebar.order: ${order}` });
165
+ }
166
+ }
167
+ }
168
+
169
+ if (orderMap.size > 0) {
170
+ let max = -Infinity;
171
+ for (const k of orderMap.keys()) if (k > max) max = k;
172
+
173
+ let gapCount = 0;
174
+ for (let i = 1; i <= max; i++) {
175
+ if (!orderMap.has(i)) {
176
+ issues.push({
177
+ level: 'error',
178
+ type: 'gap',
179
+ directory: dirPath,
180
+ missing: i,
181
+ message: `Gap in sidebar order: missing position ${i}`,
182
+ });
183
+ gapCount++;
184
+ if (gapCount >= MAX_GAPS) {
185
+ issues.push({
186
+ level: 'error',
187
+ type: 'gap-truncated',
188
+ directory: dirPath,
189
+ message: `Too many gaps (stopped after ${MAX_GAPS}) — check for typos in sidebar.order values`,
190
+ });
191
+ break;
192
+ }
193
+ }
194
+ }
195
+ }
196
+
197
+ return { orderMap, issues };
198
+ }
199
+
200
+ // ── Cross-language drift ─────────────────────────────────────────────────
201
+
202
+ /**
203
+ * Compare translated sidebar orders against English counterparts and warn on drift.
204
+ * Warns on numeric drift and on translation having sidebar but missing order.
205
+ * Files without an English counterpart are skipped silently.
206
+ * @param {string} lang - Language directory name (e.g. "cs", "zh-cn").
207
+ * @param {string[]} langSections - Section subdirectories within the language folder.
208
+ * @param {Map<string, Map<number, string[]>>} englishOrderMaps - English order maps keyed by section name.
209
+ * @returns {object[]} Drift warnings.
210
+ */
211
+ function checkTranslationDrift(lang, langSections, englishOrderMaps) {
212
+ const warnings = [];
213
+
214
+ for (const section of langSections) {
215
+ const sectionDir = path.join(DOCS_ROOT, lang, section);
216
+ if (!fs.existsSync(sectionDir)) continue;
217
+
218
+ const englishMap = englishOrderMaps.get(section);
219
+ if (!englishMap) continue;
220
+
221
+ for (const entry of listMdEntries(sectionDir)) {
222
+ const langFile = path.join(sectionDir, entry.name);
223
+ const englishFile = path.join(DOCS_ROOT, section, entry.name);
224
+ if (!fs.existsSync(englishFile)) continue;
225
+
226
+ const langResult = extractSidebarOrder(fs.readFileSync(langFile, 'utf-8'));
227
+ const engResult = extractSidebarOrder(fs.readFileSync(englishFile, 'utf-8'));
228
+
229
+ const langHasOrder = typeof langResult.order === 'number';
230
+ const engHasOrder = typeof engResult.order === 'number';
231
+
232
+ if (langHasOrder && engHasOrder && langResult.order !== engResult.order) {
233
+ warnings.push({
234
+ level: 'warning',
235
+ type: 'order-drift',
236
+ file: langFile,
237
+ englishFile,
238
+ langOrder: langResult.order,
239
+ englishOrder: engResult.order,
240
+ });
241
+ } else if (engHasOrder && langResult.hasSidebar && !langHasOrder) {
242
+ warnings.push({
243
+ level: 'warning',
244
+ type: 'order-drift',
245
+ file: langFile,
246
+ englishFile,
247
+ langOrder: null,
248
+ englishOrder: engResult.order,
249
+ });
250
+ }
251
+ }
252
+ }
253
+
254
+ return warnings;
255
+ }
256
+
257
+ // ── Output ───────────────────────────────────────────────────────────────
258
+
259
+ /**
260
+ * Print a single validation issue to stdout.
261
+ * @param {object} issue - Issue object with type, file/order/message fields.
262
+ * @param {string} indent - Whitespace prefix for indentation.
263
+ * @param {string} ctxPath - Display path for gap issues (e.g. "docs/explanation").
264
+ */
265
+ function reportIssue(issue, indent, ctxPath) {
266
+ switch (issue.type) {
267
+ case 'duplicate-order': {
268
+ console.log(`${indent}[ERROR] Duplicate order ${issue.order}: ${rel(issue.file)}`);
269
+ break;
270
+ }
271
+ case 'gap': {
272
+ console.log(`${indent}[ERROR] ${issue.message} in ${ctxPath}/`);
273
+ break;
274
+ }
275
+ case 'gap-truncated': {
276
+ console.log(`${indent}[ERROR] ${issue.message}`);
277
+ break;
278
+ }
279
+ case 'missing-order': {
280
+ console.log(`${indent}[ERROR] ${issue.message}: ${rel(issue.file)}`);
281
+ break;
282
+ }
283
+ case 'invalid-order': {
284
+ console.log(`${indent}[ERROR] ${issue.message}: ${rel(issue.file)}`);
285
+ break;
286
+ }
287
+ }
288
+ }
289
+
290
+ /**
291
+ * Print summary with error/warning counts and error type breakdown.
292
+ * @param {object[]} errors - All collected errors.
293
+ * @param {object[]} warnings - All collected warnings.
294
+ */
295
+ function printSummary(errors, warnings) {
296
+ console.log(`\n${'─'.repeat(60)}`);
297
+ console.log('\nSummary:');
298
+ console.log(` Errors: ${errors.length}`);
299
+ console.log(` Warnings: ${warnings.length}`);
300
+
301
+ if (errors.length > 0) {
302
+ const breakdown = {};
303
+ for (const e of errors) breakdown[e.type] = (breakdown[e.type] || 0) + 1;
304
+ console.log('\n Error breakdown:');
305
+ for (const [type, count] of Object.entries(breakdown)) console.log(` ${type}: ${count}`);
306
+ }
307
+
308
+ if (errors.length === 0 && warnings.length === 0) {
309
+ console.log('\n All sidebar orders valid!');
310
+ }
311
+
312
+ console.log('');
313
+ }
314
+
315
+ // ── Leaf helpers ─────────────────────────────────────────────────────────
316
+
317
+ /**
318
+ * Convert an absolute path to one relative to DOCS_ROOT.
319
+ * @param {string} filePath - Absolute file path.
320
+ * @returns {string} Relative path from docs root.
321
+ */
322
+ function rel(filePath) {
323
+ return path.relative(DOCS_ROOT, filePath);
324
+ }
325
+
326
+ /**
327
+ * Extract sidebar.order from YAML frontmatter.
328
+ * Handles block mapping (sidebar:\n order: 5) and flow mapping (sidebar: { order: 5 }).
329
+ * Only matches order: as a direct child of sidebar:, not from nested blocks.
330
+ * @param {string} content - Full file contents of a markdown file.
331
+ * @returns {{ hasSidebar: boolean, order?: number|null, orderInvalid?: boolean }}
332
+ */
333
+ function extractSidebarOrder(content) {
334
+ const match = content.match(FRONTMATTER_RE);
335
+ if (!match) return { hasSidebar: false };
336
+
337
+ const frontmatter = match[1];
338
+
339
+ // Flow mapping: sidebar: { order: 5 }
340
+ const inline = frontmatter.match(/^sidebar:[ \t]*\{[^}]*\border:[ \t]*(\d+)/m);
341
+ if (inline) return validateOrder(inline[1]);
342
+
343
+ // Block mapping: sidebar:\n order: 5
344
+ if (!/^sidebar:[ \t]*$/m.test(frontmatter)) return { hasSidebar: false };
345
+
346
+ const lines = frontmatter.split(/\r?\n/);
347
+ const start = lines.findIndex((l) => /^sidebar:[ \t]*$/.test(l));
348
+ let baseIndent = null;
349
+
350
+ for (let i = start + 1; i < lines.length; i++) {
351
+ const line = lines[i];
352
+ if (/^\s*$/.test(line)) continue;
353
+
354
+ const indent = line.search(/\S/);
355
+ if (indent === 0) break;
356
+ if (baseIndent === null) baseIndent = indent;
357
+ if (indent < baseIndent) break;
358
+ if (indent > baseIndent) continue;
359
+
360
+ const m = line.match(/^\s+order:[ \t]*(\d+)/);
361
+ if (m) return validateOrder(m[1]);
362
+ }
363
+
364
+ return { hasSidebar: true, order: null };
365
+ }
366
+
367
+ /**
368
+ * Validate a parsed order value and return a result object.
369
+ * Rejects non-finite values (Infinity, NaN) and non-positive values (0, negative).
370
+ * @param {string} raw - Raw digit string from frontmatter.
371
+ * @returns {{ hasSidebar: boolean, order?: number|null, orderInvalid?: boolean }}
372
+ */
373
+ function validateOrder(raw) {
374
+ const n = parseInt(raw, 10);
375
+ if (!Number.isFinite(n) || n < 1) return { hasSidebar: true, order: null, orderInvalid: true };
376
+ return { hasSidebar: true, order: n };
377
+ }
378
+
379
+ /**
380
+ * List markdown files (.md/.mdx) in a directory, excluding subdirectories.
381
+ * @param {string} dirPath - Absolute path to the directory.
382
+ * @returns {fs.Dirent[]} Dirent entries for markdown files.
383
+ */
384
+ function listMdEntries(dirPath) {
385
+ return fs.readdirSync(dirPath, { withFileTypes: true }).filter((e) => e.isFile() && (e.name.endsWith('.md') || e.name.endsWith('.mdx')));
386
+ }
387
+
388
+ main();
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Deterministic Skill Validator
3
3
  *
4
- * Validates 14 deterministic rules across all skill directories.
4
+ * Validates 12 deterministic rules across all skill directories.
5
5
  * Acts as a fast first-pass complement to the inference-based skill validator.
6
6
  *
7
7
  * What it checks:
@@ -12,8 +12,6 @@
12
12
  * - SKILL-05: name matches directory basename
13
13
  * - SKILL-06: description quality (length, "Use when"/"Use if")
14
14
  * - SKILL-07: SKILL.md has body content after frontmatter
15
- * - WF-01: workflow.md frontmatter has no name
16
- * - WF-02: workflow.md frontmatter has no description
17
15
  * - PATH-02: no installed_path variable
18
16
  * - STEP-01: step filename format
19
17
  * - STEP-06: step frontmatter has no name/description
@@ -390,43 +388,6 @@ function validateSkill(skillDir) {
390
388
  }
391
389
  }
392
390
 
393
- // --- WF-01 / WF-02: non-SKILL.md files must NOT have name/description ---
394
- // TODO: bmad-agent-tech-writer has sub-skill files with intentional name/description
395
- const WF_SKIP_SKILLS = new Set(['bmad-agent-tech-writer']);
396
- for (const filePath of allFiles) {
397
- if (path.extname(filePath) !== '.md') continue;
398
- if (path.basename(filePath) === 'SKILL.md') continue;
399
- if (WF_SKIP_SKILLS.has(dirName)) continue;
400
-
401
- const relFile = path.relative(skillDir, filePath);
402
- const content = safeReadFile(filePath, findings, relFile);
403
- if (content === null) continue;
404
- const fm = parseFrontmatter(content);
405
- if (!fm) continue;
406
-
407
- if ('name' in fm) {
408
- findings.push({
409
- rule: 'WF-01',
410
- title: 'Only SKILL.md May Have name in Frontmatter',
411
- severity: 'HIGH',
412
- file: relFile,
413
- detail: `${relFile} frontmatter contains \`name\` — this belongs only in SKILL.md.`,
414
- fix: "Remove the `name:` line from this file's frontmatter.",
415
- });
416
- }
417
-
418
- if ('description' in fm) {
419
- findings.push({
420
- rule: 'WF-02',
421
- title: 'Only SKILL.md May Have description in Frontmatter',
422
- severity: 'HIGH',
423
- file: relFile,
424
- detail: `${relFile} frontmatter contains \`description\` — this belongs only in SKILL.md.`,
425
- fix: "Remove the `description:` line from this file's frontmatter.",
426
- });
427
- }
428
- }
429
-
430
391
  // --- PATH-02: no installed_path ---
431
392
  for (const filePath of allFiles) {
432
393
  // Only check markdown and yaml files
@@ -0,0 +1,46 @@
1
+ # BMad Web Bundles
2
+
3
+ V4 shipped web bundles. V6 brings them back, new and improved. Each bundle packages a BMad skill as a self-contained install for **Google Gemini Gems** and **ChatGPT Custom GPTs**, so you can run the planning work in your web LLM subscription before opening your IDE.
4
+
5
+ ## Install
6
+
7
+ **Go to [bmadcode.com/web-bundles](https://bmadcode.com/web-bundles/).**
8
+
9
+ The site lists every bundle in a card grid, walks you through the Gemini and ChatGPT setup inline, and hands you the ZIP download in one click. That is the only supported install path.
10
+
11
+ Why a single front door:
12
+
13
+ - One place to keep install steps current as Gemini and ChatGPT evolve.
14
+ - Versioned releases. Every shelf update ships as a tagged GitHub Release; the site always points at the newest tag.
15
+ - One signup gets you on the list for new bundles as they ship.
16
+
17
+ ## Why use them
18
+
19
+ - **Cost.** Web LLM subscriptions are flat-rate. Run brainstorming, briefs, PRDs, and research there instead of burning IDE tokens.
20
+ - **Right tool for the job.** Planning conversations want Canvas, image generation, and Deep Research. Implementation wants the codebase and a terminal. Use each where it's strongest.
21
+ - **Persona swapping.** Every bundle ships a default persona and a contrasting swap example. Change voices without touching the protocol.
22
+
23
+ ## The shelf
24
+
25
+ | Bundle | Purpose |
26
+ | --- | --- |
27
+ | Brainstorming Coach | Facilitated ideation across 60 techniques. Defaults to **Carson** (Osborn lineage); swap to **Mary** for analyst rigor. |
28
+ | Product Brief Coach | Build a product brief through guided discovery. Create, Update, or Validate modes. |
29
+ | PRFAQ Coach | Working Backwards PRFAQ challenge (Bezos lineage) to forge and stress-test product concepts. |
30
+ | PRD Coach | Product Requirements Document with built-in validation (Cagan lineage). |
31
+ | UX Coach | UX patterns, flows, and design specifications. Pairs with Google Stitch. |
32
+ | Market & Industry Research | Market research, customer JTBD, competitive landscape, regulatory and technical lenses. Deep Research mode integrated. |
33
+
34
+ Requires Gemini Advanced (for Gems) or ChatGPT Plus / Pro / Business / Enterprise (for Custom GPTs). Deep Research has its own plan limits.
35
+
36
+ ## Build your own
37
+
38
+ Web bundles are generated from BMad skills using the [`bmad-os-skill-to-bundle`](https://github.com/bmad-code-org/bmad-utility-skills) utility skill. Point it at any BMad skill folder and it produces a `SKILL.md`, an `INSTRUCTIONS.md`, and any required data files, with persona inheritance from the owning agent.
39
+
40
+ ## What's in this folder
41
+
42
+ This folder is the **source** for the shelf, packaged into ZIPs and attached to GitHub Releases. End users do not install from here. If you are a contributor working on a bundle, the bundle directories and `bundles.json` are the files you edit; the [release packager](../tools/bundle-web-bundles.js) zips them and updates the release.
43
+
44
+ ## Concept docs
45
+
46
+ [What web bundles are and when to use them](https://docs.bmad-method.org/explanation/web-bundles/).