bmad-method 6.0.5-next.8 → 6.1.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 (262) hide show
  1. package/package.json +1 -1
  2. package/src/bmm/agents/analyst.agent.yaml +1 -1
  3. package/src/bmm/module-help.csv +1 -1
  4. package/src/bmm/workflows/1-analysis/create-product-brief/steps/step-02-vision.md +1 -1
  5. package/src/bmm/workflows/1-analysis/create-product-brief/steps/step-03-users.md +1 -1
  6. package/src/bmm/workflows/1-analysis/create-product-brief/steps/step-04-metrics.md +1 -1
  7. package/src/bmm/workflows/1-analysis/create-product-brief/steps/step-05-scope.md +1 -1
  8. package/src/bmm/workflows/2-plan-workflows/create-prd/steps-c/step-02-discovery.md +1 -1
  9. package/src/bmm/workflows/2-plan-workflows/create-prd/steps-c/step-02b-vision.md +1 -1
  10. package/src/bmm/workflows/2-plan-workflows/create-prd/steps-c/step-02c-executive-summary.md +1 -1
  11. package/src/bmm/workflows/2-plan-workflows/create-prd/steps-c/step-03-success.md +1 -1
  12. package/src/bmm/workflows/2-plan-workflows/create-prd/steps-c/step-04-journeys.md +1 -1
  13. package/src/bmm/workflows/2-plan-workflows/create-prd/steps-c/step-05-domain.md +1 -1
  14. package/src/bmm/workflows/2-plan-workflows/create-prd/steps-c/step-06-innovation.md +1 -1
  15. package/src/bmm/workflows/2-plan-workflows/create-prd/steps-c/step-07-project-type.md +1 -1
  16. package/src/bmm/workflows/2-plan-workflows/create-prd/steps-c/step-08-scoping.md +1 -1
  17. package/src/bmm/workflows/2-plan-workflows/create-prd/steps-c/step-09-functional.md +1 -1
  18. package/src/bmm/workflows/2-plan-workflows/create-prd/steps-c/step-10-nonfunctional.md +1 -1
  19. package/src/bmm/workflows/2-plan-workflows/create-prd/steps-c/step-11-polish.md +1 -1
  20. package/src/bmm/workflows/2-plan-workflows/create-prd/steps-e/step-e-01-discovery.md +1 -1
  21. package/src/bmm/workflows/2-plan-workflows/create-prd/steps-v/step-v-01-discovery.md +1 -1
  22. package/src/bmm/workflows/2-plan-workflows/create-ux-design/steps/step-02-discovery.md +1 -1
  23. package/src/bmm/workflows/2-plan-workflows/create-ux-design/steps/step-03-core-experience.md +2 -2
  24. package/src/bmm/workflows/2-plan-workflows/create-ux-design/steps/step-04-emotional-response.md +2 -2
  25. package/src/bmm/workflows/2-plan-workflows/create-ux-design/steps/step-05-inspiration.md +2 -2
  26. package/src/bmm/workflows/2-plan-workflows/create-ux-design/steps/step-06-design-system.md +2 -2
  27. package/src/bmm/workflows/2-plan-workflows/create-ux-design/steps/step-07-defining-experience.md +2 -2
  28. package/src/bmm/workflows/2-plan-workflows/create-ux-design/steps/step-08-visual-foundation.md +2 -2
  29. package/src/bmm/workflows/2-plan-workflows/create-ux-design/steps/step-09-design-directions.md +2 -2
  30. package/src/bmm/workflows/2-plan-workflows/create-ux-design/steps/step-10-user-journeys.md +2 -2
  31. package/src/bmm/workflows/2-plan-workflows/create-ux-design/steps/step-11-component-strategy.md +2 -2
  32. package/src/bmm/workflows/2-plan-workflows/create-ux-design/steps/step-12-ux-patterns.md +2 -2
  33. package/src/bmm/workflows/2-plan-workflows/create-ux-design/steps/step-13-responsive-accessibility.md +2 -2
  34. package/src/bmm/workflows/3-solutioning/create-architecture/steps/step-02-context.md +2 -2
  35. package/src/bmm/workflows/3-solutioning/create-architecture/steps/step-03-starter.md +2 -2
  36. package/src/bmm/workflows/3-solutioning/create-architecture/steps/step-04-decisions.md +2 -2
  37. package/src/bmm/workflows/3-solutioning/create-architecture/steps/step-05-patterns.md +2 -2
  38. package/src/bmm/workflows/3-solutioning/create-architecture/steps/step-06-structure.md +2 -2
  39. package/src/bmm/workflows/3-solutioning/create-architecture/steps/step-07-validation.md +2 -2
  40. package/src/bmm/workflows/3-solutioning/create-epics-and-stories/steps/step-01-validate-prerequisites.md +31 -13
  41. package/src/bmm/workflows/3-solutioning/create-epics-and-stories/steps/step-02-design-epics.md +1 -1
  42. package/src/bmm/workflows/3-solutioning/create-epics-and-stories/steps/step-03-create-stories.md +6 -2
  43. package/src/bmm/workflows/3-solutioning/create-epics-and-stories/steps/step-04-final-validation.md +1 -1
  44. package/src/bmm/workflows/3-solutioning/create-epics-and-stories/templates/epics-template.md +4 -0
  45. package/src/bmm/workflows/4-implementation/code-review/workflow.md +4 -7
  46. package/src/bmm/workflows/bmad-quick-flow/bmad-quick-dev-new-preview/workflow.md +0 -4
  47. package/src/bmm/workflows/bmad-quick-flow/quick-dev/workflow.md +1 -1
  48. package/src/bmm/workflows/bmad-quick-flow/quick-spec/workflow.md +1 -1
  49. package/src/bmm/workflows/generate-project-context/steps/step-02-generate.md +1 -1
  50. package/src/core/module-help.csv +2 -2
  51. package/src/core/workflows/bmad-brainstorming/SKILL.md +6 -0
  52. package/src/core/workflows/bmad-brainstorming/bmad-skill-manifest.yaml +1 -0
  53. package/src/core/workflows/{brainstorming → bmad-brainstorming}/workflow.md +2 -5
  54. package/src/core/workflows/bmad-party-mode/SKILL.md +6 -0
  55. package/src/core/workflows/bmad-party-mode/bmad-skill-manifest.yaml +1 -0
  56. package/src/core/workflows/{party-mode → bmad-party-mode}/steps/step-03-graceful-exit.md +0 -1
  57. package/src/core/workflows/{party-mode → bmad-party-mode}/workflow.md +0 -4
  58. package/tools/cli/external-official-modules.yaml +18 -18
  59. package/tools/cli/installers/lib/core/installer.js +25 -8
  60. package/tools/cli/installers/lib/ide/_base-ide.js +0 -1
  61. package/tools/cli/installers/lib/ide/_config-driven.js +9 -4
  62. package/tools/cli/installers/lib/ide/manager.js +3 -3
  63. package/tools/cli/lib/agent/compiler.js +1 -1
  64. package/.augment/code_review_guidelines.yaml +0 -231
  65. package/.coderabbit.yaml +0 -85
  66. package/.github/CODE_OF_CONDUCT.md +0 -128
  67. package/.github/FUNDING.yaml +0 -15
  68. package/.github/ISSUE_TEMPLATE/bug-report.yaml +0 -124
  69. package/.github/ISSUE_TEMPLATE/config.yaml +0 -8
  70. package/.github/ISSUE_TEMPLATE/documentation.yaml +0 -55
  71. package/.github/ISSUE_TEMPLATE/feature-request.md +0 -22
  72. package/.github/ISSUE_TEMPLATE/issue.md +0 -32
  73. package/.github/PULL_REQUEST_TEMPLATE.md +0 -13
  74. package/.github/scripts/discord-helpers.sh +0 -34
  75. package/.github/workflows/coderabbit-review.yaml +0 -22
  76. package/.github/workflows/discord.yaml +0 -90
  77. package/.github/workflows/docs.yaml +0 -64
  78. package/.github/workflows/publish.yaml +0 -133
  79. package/.github/workflows/quality.yaml +0 -116
  80. package/.husky/pre-commit +0 -20
  81. package/.markdownlint-cli2.yaml +0 -41
  82. package/.nvmrc +0 -1
  83. package/.prettierignore +0 -12
  84. package/.vscode/settings.json +0 -96
  85. package/CHANGELOG.md +0 -1785
  86. package/CNAME +0 -1
  87. package/CONTRIBUTING.md +0 -176
  88. package/CONTRIBUTORS.md +0 -32
  89. package/SECURITY.md +0 -85
  90. package/TRADEMARK.md +0 -55
  91. package/Wordmark.png +0 -0
  92. package/banner-bmad-method.png +0 -0
  93. package/docs/404.md +0 -9
  94. package/docs/_STYLE_GUIDE.md +0 -370
  95. package/docs/explanation/advanced-elicitation.md +0 -49
  96. package/docs/explanation/adversarial-review.md +0 -59
  97. package/docs/explanation/brainstorming.md +0 -33
  98. package/docs/explanation/established-projects-faq.md +0 -50
  99. package/docs/explanation/party-mode.md +0 -59
  100. package/docs/explanation/preventing-agent-conflicts.md +0 -112
  101. package/docs/explanation/project-context.md +0 -157
  102. package/docs/explanation/quick-dev-new-preview.md +0 -73
  103. package/docs/explanation/quick-flow.md +0 -77
  104. package/docs/explanation/why-solutioning-matters.md +0 -77
  105. package/docs/how-to/customize-bmad.md +0 -172
  106. package/docs/how-to/established-projects.md +0 -117
  107. package/docs/how-to/get-answers-about-bmad.md +0 -138
  108. package/docs/how-to/install-bmad.md +0 -116
  109. package/docs/how-to/non-interactive-installation.md +0 -171
  110. package/docs/how-to/project-context.md +0 -136
  111. package/docs/how-to/quick-fixes.md +0 -123
  112. package/docs/how-to/shard-large-documents.md +0 -78
  113. package/docs/how-to/upgrade-to-v6.md +0 -100
  114. package/docs/index.md +0 -60
  115. package/docs/reference/agents.md +0 -28
  116. package/docs/reference/commands.md +0 -145
  117. package/docs/reference/modules.md +0 -76
  118. package/docs/reference/testing.md +0 -106
  119. package/docs/reference/workflow-map.md +0 -89
  120. package/docs/roadmap.mdx +0 -136
  121. package/docs/tutorials/getting-started.md +0 -275
  122. package/docs/zh-cn/404.md +0 -9
  123. package/docs/zh-cn/_STYLE_GUIDE.md +0 -370
  124. package/docs/zh-cn/explanation/advanced-elicitation.md +0 -62
  125. package/docs/zh-cn/explanation/adversarial-review.md +0 -71
  126. package/docs/zh-cn/explanation/brainstorming.md +0 -43
  127. package/docs/zh-cn/explanation/established-projects-faq.md +0 -60
  128. package/docs/zh-cn/explanation/party-mode.md +0 -79
  129. package/docs/zh-cn/explanation/preventing-agent-conflicts.md +0 -137
  130. package/docs/zh-cn/explanation/project-context.md +0 -176
  131. package/docs/zh-cn/explanation/quick-flow.md +0 -93
  132. package/docs/zh-cn/explanation/why-solutioning-matters.md +0 -90
  133. package/docs/zh-cn/how-to/customize-bmad.md +0 -182
  134. package/docs/zh-cn/how-to/established-projects.md +0 -134
  135. package/docs/zh-cn/how-to/get-answers-about-bmad.md +0 -144
  136. package/docs/zh-cn/how-to/install-bmad.md +0 -105
  137. package/docs/zh-cn/how-to/non-interactive-installation.md +0 -181
  138. package/docs/zh-cn/how-to/project-context.md +0 -152
  139. package/docs/zh-cn/how-to/quick-fixes.md +0 -140
  140. package/docs/zh-cn/how-to/shard-large-documents.md +0 -86
  141. package/docs/zh-cn/how-to/upgrade-to-v6.md +0 -120
  142. package/docs/zh-cn/index.md +0 -69
  143. package/docs/zh-cn/reference/agents.md +0 -41
  144. package/docs/zh-cn/reference/commands.md +0 -166
  145. package/docs/zh-cn/reference/modules.md +0 -94
  146. package/docs/zh-cn/reference/testing.md +0 -122
  147. package/docs/zh-cn/reference/workflow-map.md +0 -104
  148. package/docs/zh-cn/roadmap.mdx +0 -152
  149. package/docs/zh-cn/tutorials/getting-started.md +0 -300
  150. package/eslint.config.mjs +0 -141
  151. package/prettier.config.mjs +0 -32
  152. package/src/core/workflows/brainstorming/bmad-skill-manifest.yaml +0 -3
  153. package/src/core/workflows/party-mode/bmad-skill-manifest.yaml +0 -3
  154. package/test/README.md +0 -295
  155. package/test/adversarial-review-tests/README.md +0 -56
  156. package/test/adversarial-review-tests/sample-content.md +0 -46
  157. package/test/adversarial-review-tests/test-cases.yaml +0 -103
  158. package/test/fixtures/agent-schema/invalid/critical-actions/actions-as-string.agent.yaml +0 -27
  159. package/test/fixtures/agent-schema/invalid/critical-actions/empty-string-in-actions.agent.yaml +0 -30
  160. package/test/fixtures/agent-schema/invalid/menu/empty-menu.agent.yaml +0 -22
  161. package/test/fixtures/agent-schema/invalid/menu/missing-menu.agent.yaml +0 -20
  162. package/test/fixtures/agent-schema/invalid/menu-commands/empty-command-target.agent.yaml +0 -25
  163. package/test/fixtures/agent-schema/invalid/menu-commands/no-command-target.agent.yaml +0 -24
  164. package/test/fixtures/agent-schema/invalid/menu-triggers/camel-case.agent.yaml +0 -25
  165. package/test/fixtures/agent-schema/invalid/menu-triggers/compound-invalid-format.agent.yaml +0 -25
  166. package/test/fixtures/agent-schema/invalid/menu-triggers/compound-mismatched-kebab.agent.yaml +0 -25
  167. package/test/fixtures/agent-schema/invalid/menu-triggers/duplicate-triggers.agent.yaml +0 -31
  168. package/test/fixtures/agent-schema/invalid/menu-triggers/empty-trigger.agent.yaml +0 -25
  169. package/test/fixtures/agent-schema/invalid/menu-triggers/leading-asterisk.agent.yaml +0 -25
  170. package/test/fixtures/agent-schema/invalid/menu-triggers/snake-case.agent.yaml +0 -25
  171. package/test/fixtures/agent-schema/invalid/menu-triggers/trigger-with-spaces.agent.yaml +0 -25
  172. package/test/fixtures/agent-schema/invalid/metadata/empty-module-string.agent.yaml +0 -26
  173. package/test/fixtures/agent-schema/invalid/metadata/empty-name.agent.yaml +0 -24
  174. package/test/fixtures/agent-schema/invalid/metadata/extra-metadata-fields.agent.yaml +0 -27
  175. package/test/fixtures/agent-schema/invalid/metadata/missing-id.agent.yaml +0 -23
  176. package/test/fixtures/agent-schema/invalid/persona/empty-principles-array.agent.yaml +0 -24
  177. package/test/fixtures/agent-schema/invalid/persona/empty-string-in-principles.agent.yaml +0 -27
  178. package/test/fixtures/agent-schema/invalid/persona/extra-persona-fields.agent.yaml +0 -27
  179. package/test/fixtures/agent-schema/invalid/persona/missing-role.agent.yaml +0 -24
  180. package/test/fixtures/agent-schema/invalid/prompts/empty-content.agent.yaml +0 -29
  181. package/test/fixtures/agent-schema/invalid/prompts/extra-prompt-fields.agent.yaml +0 -31
  182. package/test/fixtures/agent-schema/invalid/prompts/missing-content.agent.yaml +0 -28
  183. package/test/fixtures/agent-schema/invalid/prompts/missing-id.agent.yaml +0 -28
  184. package/test/fixtures/agent-schema/invalid/top-level/empty-file.agent.yaml +0 -5
  185. package/test/fixtures/agent-schema/invalid/top-level/extra-top-level-keys.agent.yaml +0 -28
  186. package/test/fixtures/agent-schema/invalid/top-level/missing-agent-key.agent.yaml +0 -11
  187. package/test/fixtures/agent-schema/invalid/yaml-errors/invalid-indentation.agent.yaml +0 -19
  188. package/test/fixtures/agent-schema/invalid/yaml-errors/malformed-yaml.agent.yaml +0 -18
  189. package/test/fixtures/agent-schema/valid/critical-actions/empty-critical-actions.agent.yaml +0 -24
  190. package/test/fixtures/agent-schema/valid/critical-actions/no-critical-actions.agent.yaml +0 -22
  191. package/test/fixtures/agent-schema/valid/critical-actions/valid-critical-actions.agent.yaml +0 -27
  192. package/test/fixtures/agent-schema/valid/menu/multiple-menu-items.agent.yaml +0 -31
  193. package/test/fixtures/agent-schema/valid/menu/single-menu-item.agent.yaml +0 -22
  194. package/test/fixtures/agent-schema/valid/menu-commands/all-command-types.agent.yaml +0 -38
  195. package/test/fixtures/agent-schema/valid/menu-commands/multiple-commands.agent.yaml +0 -23
  196. package/test/fixtures/agent-schema/valid/menu-triggers/compound-triggers.agent.yaml +0 -31
  197. package/test/fixtures/agent-schema/valid/menu-triggers/kebab-case-triggers.agent.yaml +0 -34
  198. package/test/fixtures/agent-schema/valid/metadata/core-agent-with-module.agent.yaml +0 -24
  199. package/test/fixtures/agent-schema/valid/metadata/empty-module-name-in-path.agent.yaml +0 -24
  200. package/test/fixtures/agent-schema/valid/metadata/malformed-path-treated-as-core.agent.yaml +0 -24
  201. package/test/fixtures/agent-schema/valid/metadata/module-agent-correct.agent.yaml +0 -24
  202. package/test/fixtures/agent-schema/valid/metadata/module-agent-missing-module.agent.yaml +0 -23
  203. package/test/fixtures/agent-schema/valid/metadata/wrong-module-value.agent.yaml +0 -24
  204. package/test/fixtures/agent-schema/valid/persona/complete-persona.agent.yaml +0 -24
  205. package/test/fixtures/agent-schema/valid/prompts/empty-prompts.agent.yaml +0 -24
  206. package/test/fixtures/agent-schema/valid/prompts/no-prompts.agent.yaml +0 -22
  207. package/test/fixtures/agent-schema/valid/prompts/valid-prompts-minimal.agent.yaml +0 -28
  208. package/test/fixtures/agent-schema/valid/prompts/valid-prompts-with-description.agent.yaml +0 -30
  209. package/test/fixtures/agent-schema/valid/top-level/minimal-core-agent.agent.yaml +0 -24
  210. package/test/fixtures/file-refs-csv/invalid/all-empty-workflow.csv +0 -3
  211. package/test/fixtures/file-refs-csv/invalid/empty-data.csv +0 -1
  212. package/test/fixtures/file-refs-csv/invalid/no-workflow-column.csv +0 -3
  213. package/test/fixtures/file-refs-csv/invalid/unresolvable-vars.csv +0 -3
  214. package/test/fixtures/file-refs-csv/valid/bmm-style.csv +0 -3
  215. package/test/fixtures/file-refs-csv/valid/core-style.csv +0 -3
  216. package/test/fixtures/file-refs-csv/valid/minimal.csv +0 -2
  217. package/test/test-agent-schema.js +0 -387
  218. package/test/test-cli-integration.sh +0 -159
  219. package/test/test-file-refs-csv.js +0 -133
  220. package/test/test-install-to-bmad.js +0 -154
  221. package/test/test-installation-components.js +0 -1796
  222. package/test/test-rehype-plugins.mjs +0 -1050
  223. package/test/test-workflow-path-regex.js +0 -88
  224. package/test/unit-test-schema.js +0 -133
  225. package/tools/build-docs.mjs +0 -464
  226. package/tools/docs/_prompt-external-modules-page.md +0 -59
  227. package/tools/docs/fix-refs.md +0 -91
  228. package/tools/docs/native-skills-migration-checklist.md +0 -281
  229. package/tools/fix-doc-links.js +0 -285
  230. package/tools/validate-agent-schema.js +0 -110
  231. package/tools/validate-doc-links.js +0 -407
  232. package/tools/validate-file-refs.js +0 -556
  233. package/website/README.md +0 -75
  234. package/website/astro.config.mjs +0 -157
  235. package/website/public/diagrams/quick-dev-diagram.png +0 -0
  236. package/website/public/favicon.ico +0 -0
  237. package/website/public/img/bmad-dark.png +0 -0
  238. package/website/public/img/bmad-light.png +0 -0
  239. package/website/public/workflow-map-diagram.html +0 -361
  240. package/website/src/components/Banner.astro +0 -62
  241. package/website/src/components/Header.astro +0 -96
  242. package/website/src/components/MobileMenuFooter.astro +0 -33
  243. package/website/src/content/config.ts +0 -7
  244. package/website/src/content/i18n/zh-CN.json +0 -28
  245. package/website/src/lib/site-url.mjs +0 -25
  246. package/website/src/pages/404.astro +0 -11
  247. package/website/src/pages/robots.txt.ts +0 -48
  248. package/website/src/rehype-base-paths.js +0 -112
  249. package/website/src/rehype-markdown-links.js +0 -119
  250. package/website/src/styles/custom.css +0 -805
  251. /package/src/core/workflows/{brainstorming → bmad-brainstorming}/brain-methods.csv +0 -0
  252. /package/src/core/workflows/{brainstorming → bmad-brainstorming}/steps/step-01-session-setup.md +0 -0
  253. /package/src/core/workflows/{brainstorming → bmad-brainstorming}/steps/step-01b-continue.md +0 -0
  254. /package/src/core/workflows/{brainstorming → bmad-brainstorming}/steps/step-02a-user-selected.md +0 -0
  255. /package/src/core/workflows/{brainstorming → bmad-brainstorming}/steps/step-02b-ai-recommended.md +0 -0
  256. /package/src/core/workflows/{brainstorming → bmad-brainstorming}/steps/step-02c-random-selection.md +0 -0
  257. /package/src/core/workflows/{brainstorming → bmad-brainstorming}/steps/step-02d-progressive-flow.md +0 -0
  258. /package/src/core/workflows/{brainstorming → bmad-brainstorming}/steps/step-03-technique-execution.md +0 -0
  259. /package/src/core/workflows/{brainstorming → bmad-brainstorming}/steps/step-04-idea-organization.md +0 -0
  260. /package/src/core/workflows/{brainstorming → bmad-brainstorming}/template.md +0 -0
  261. /package/src/core/workflows/{party-mode → bmad-party-mode}/steps/step-01-agent-loading.md +0 -0
  262. /package/src/core/workflows/{party-mode → bmad-party-mode}/steps/step-02-discussion-orchestration.md +0 -0
@@ -1,407 +0,0 @@
1
- /**
2
- * Documentation Link Validator
3
- *
4
- * Validates site-relative links in markdown files and attempts to fix broken ones.
5
- *
6
- * What it checks:
7
- * - All site-relative links (starting with /) point to existing .md files
8
- * - Anchor links (#section) point to valid headings
9
- *
10
- * What it fixes:
11
- * - Broken links where the target file can be found elsewhere in /docs
12
- *
13
- * Usage:
14
- * node tools/validate-doc-links.js # Dry run (validate and show issues)
15
- * node tools/validate-doc-links.js --write # Fix auto-fixable issues
16
- */
17
-
18
- const fs = require('node:fs');
19
- const path = require('node:path');
20
-
21
- const DOCS_ROOT = path.resolve(__dirname, '../docs');
22
- const DRY_RUN = !process.argv.includes('--write');
23
-
24
- // Regex to match markdown links with site-relative paths or bare .md references
25
- const LINK_REGEX = /\[([^\]]*)\]\(((?:\.{1,2}\/|\/)[^)]+|[\w][^)\s]*\.md(?:[?#][^)]*)?)\)/g;
26
-
27
- // File extensions that are static assets, not markdown docs
28
- const STATIC_ASSET_EXTENSIONS = ['.zip', '.txt', '.pdf', '.png', '.jpg', '.jpeg', '.gif', '.svg', '.webp', '.ico'];
29
-
30
- // Custom Astro page routes (not part of the docs content collection)
31
- const CUSTOM_PAGE_ROUTES = new Set([]);
32
-
33
- // Regex to extract headings for anchor validation
34
- const HEADING_PATTERN = /^#{1,6}\s+(.+)$/gm;
35
-
36
- /**
37
- * Get all markdown files in docs directory, excluding _* directories/files
38
- */
39
- function getMarkdownFiles(dir) {
40
- const files = [];
41
-
42
- function walk(currentDir) {
43
- const entries = fs.readdirSync(currentDir, { withFileTypes: true });
44
-
45
- for (const entry of entries) {
46
- const fullPath = path.join(currentDir, entry.name);
47
-
48
- if (entry.name.startsWith('_')) {
49
- continue;
50
- }
51
-
52
- if (entry.isDirectory()) {
53
- walk(fullPath);
54
- } else if (entry.isFile() && (entry.name.endsWith('.md') || entry.name.endsWith('.mdx'))) {
55
- files.push(fullPath);
56
- }
57
- }
58
- }
59
-
60
- walk(dir);
61
- return files;
62
- }
63
-
64
- /**
65
- * Strip fenced code blocks from content
66
- */
67
- function stripCodeBlocks(content) {
68
- return content.replaceAll(/```[\s\S]*?```/g, '');
69
- }
70
-
71
- /**
72
- * Convert a heading to its anchor slug
73
- */
74
- function headingToAnchor(heading) {
75
- return heading
76
- .toLowerCase()
77
- .replaceAll(/[\u{1F300}-\u{1F9FF}]/gu, '') // Remove emojis
78
- .replaceAll(/[^\w\s-]/g, '') // Remove special chars
79
- .replaceAll(/\s+/g, '-') // Spaces to hyphens
80
- .replaceAll(/-+/g, '-') // Collapse hyphens
81
- .replaceAll(/^-+|-+$/g, ''); // Trim hyphens
82
- }
83
-
84
- /**
85
- * Extract anchor slugs from a markdown file
86
- */
87
- function extractAnchors(content) {
88
- const anchors = new Set();
89
- let match;
90
-
91
- HEADING_PATTERN.lastIndex = 0;
92
- while ((match = HEADING_PATTERN.exec(content)) !== null) {
93
- const headingText = match[1]
94
- .trim()
95
- .replaceAll(/`[^`]+`/g, '')
96
- .replaceAll(/\*\*([^*]+)\*\*/g, '$1')
97
- .replaceAll(/\*([^*]+)\*/g, '$1')
98
- .replaceAll(/\[([^\]]+)\]\([^)]+\)/g, '$1')
99
- .trim();
100
- anchors.add(headingToAnchor(headingText));
101
- }
102
-
103
- return anchors;
104
- }
105
-
106
- /**
107
- * Resolve a site-relative link to a file path
108
- * /docs/how-to/installation/install-bmad.md -> docs/how-to/installation/install-bmad.md
109
- * /how-to/installation/install-bmad/ -> docs/how-to/installation/install-bmad.md or .../index.md
110
- */
111
- function resolveLink(siteRelativePath, sourceFile) {
112
- // Strip anchor and query
113
- let checkPath = siteRelativePath.split('#')[0].split('?')[0];
114
-
115
- // Handle relative paths (including bare .md): resolve from source file's directory
116
- if (checkPath.startsWith('./') || checkPath.startsWith('../') || (!checkPath.startsWith('/') && checkPath.endsWith('.md'))) {
117
- const sourceDir = path.dirname(sourceFile);
118
- const resolved = path.resolve(sourceDir, checkPath);
119
- // Ensure the resolved path stays within DOCS_ROOT
120
- if (!resolved.startsWith(DOCS_ROOT + path.sep) && resolved !== DOCS_ROOT) return null;
121
- if (fs.existsSync(resolved) && fs.statSync(resolved).isFile()) return resolved;
122
- if (fs.existsSync(resolved + '.md')) return resolved + '.md';
123
- if (fs.existsSync(resolved + '.mdx')) return resolved + '.mdx';
124
- // Directory: check for index.md or index.mdx
125
- if (fs.existsSync(resolved) && fs.statSync(resolved).isDirectory()) {
126
- const indexFile = path.join(resolved, 'index.md');
127
- const indexMdxFile = path.join(resolved, 'index.mdx');
128
- if (fs.existsSync(indexFile)) return indexFile;
129
- if (fs.existsSync(indexMdxFile)) return indexMdxFile;
130
- }
131
- return null;
132
- }
133
-
134
- // Strip /docs/ prefix if present (legacy absolute links)
135
- if (checkPath.startsWith('/docs/')) {
136
- checkPath = checkPath.slice(5); // Remove '/docs' but keep leading '/'
137
- }
138
-
139
- if (checkPath.endsWith('/')) {
140
- // Could be file.md, file.mdx, or directory/index.md/mdx
141
- const baseName = checkPath.slice(0, -1);
142
- const asMd = path.join(DOCS_ROOT, baseName + '.md');
143
- const asMdx = path.join(DOCS_ROOT, baseName + '.mdx');
144
- const asIndex = path.join(DOCS_ROOT, checkPath, 'index.md');
145
- const asIndexMdx = path.join(DOCS_ROOT, checkPath, 'index.mdx');
146
-
147
- if (fs.existsSync(asMd)) return asMd;
148
- if (fs.existsSync(asMdx)) return asMdx;
149
- if (fs.existsSync(asIndex)) return asIndex;
150
- if (fs.existsSync(asIndexMdx)) return asIndexMdx;
151
- return null;
152
- }
153
-
154
- // Direct path (e.g., /path/file.md)
155
- const direct = path.join(DOCS_ROOT, checkPath);
156
- if (fs.existsSync(direct) && fs.statSync(direct).isFile()) return direct;
157
-
158
- // Try with .md extension
159
- const withMd = direct + '.md';
160
- if (fs.existsSync(withMd)) return withMd;
161
-
162
- // Try with .mdx extension
163
- const withMdx = direct + '.mdx';
164
- if (fs.existsSync(withMdx)) return withMdx;
165
-
166
- // Directory without trailing slash: check for index.md or index.mdx
167
- if (fs.existsSync(direct) && fs.statSync(direct).isDirectory()) {
168
- const indexFile = path.join(direct, 'index.md');
169
- const indexMdxFile = path.join(direct, 'index.mdx');
170
- if (fs.existsSync(indexFile)) return indexFile;
171
- if (fs.existsSync(indexMdxFile)) return indexMdxFile;
172
- }
173
-
174
- return null;
175
- }
176
-
177
- /**
178
- * Search for a file with directory context
179
- */
180
- function findFileWithContext(brokenPath) {
181
- // Extract filename and parent directory from the broken path
182
- // e.g., /tutorials/getting-started/foo/ -> parent: getting-started, file: foo.md
183
- const cleanPath = brokenPath.replace(/\/$/, '').replace(/^(\.\.\/|\.\/|\/)+/, '');
184
- const parts = cleanPath.split('/');
185
- const fileName = parts.at(-1) + '.md';
186
- const parentDir = parts.length > 1 ? parts.at(-2) : null;
187
-
188
- const allFiles = getMarkdownFiles(DOCS_ROOT);
189
- const matches = [];
190
-
191
- for (const file of allFiles) {
192
- const fileBaseName = path.basename(file);
193
- const fileParentDir = path.basename(path.dirname(file));
194
-
195
- // Exact filename match with parent directory context
196
- if (fileBaseName === fileName) {
197
- if (parentDir && fileParentDir === parentDir) {
198
- // Strong match: both filename and parent dir match
199
- return [file];
200
- }
201
- matches.push(file);
202
- }
203
-
204
- // Also check for index.md in a matching directory
205
- if (fileBaseName === 'index.md' && fileParentDir === fileName.replace('.md', '')) {
206
- matches.push(file);
207
- }
208
- }
209
-
210
- return matches;
211
- }
212
-
213
- /**
214
- * Convert absolute file path to site-relative URL
215
- */
216
- function fileToSiteRelative(filePath) {
217
- let relative = '/' + path.relative(DOCS_ROOT, filePath);
218
- relative = relative.split(path.sep).join('/');
219
-
220
- if (relative.endsWith('/index.md')) {
221
- return relative.replace(/\/index\.md$/, '/');
222
- }
223
- return relative.replace(/\.md$/, '/');
224
- }
225
-
226
- /**
227
- * Process a single file and find issues
228
- */
229
- function processFile(filePath) {
230
- const content = fs.readFileSync(filePath, 'utf-8');
231
- const strippedContent = stripCodeBlocks(content);
232
- const issues = [];
233
-
234
- let match;
235
- LINK_REGEX.lastIndex = 0;
236
-
237
- while ((match = LINK_REGEX.exec(strippedContent)) !== null) {
238
- const linkText = match[1];
239
- const href = match[2];
240
-
241
- // Extract path and anchor
242
- const hashIndex = href.indexOf('#');
243
- const linkPath = hashIndex === -1 ? href : href.slice(0, hashIndex);
244
- const anchor = hashIndex === -1 ? null : href.slice(hashIndex + 1);
245
-
246
- // Skip static asset links (zip, txt, images, etc.)
247
- const linkLower = linkPath.toLowerCase();
248
- if (STATIC_ASSET_EXTENSIONS.some((ext) => linkLower.endsWith(ext))) {
249
- continue;
250
- }
251
-
252
- // Skip custom Astro page routes
253
- if (CUSTOM_PAGE_ROUTES.has(linkPath)) {
254
- continue;
255
- }
256
-
257
- // Validate the link target exists
258
- const targetFile = resolveLink(linkPath, filePath);
259
-
260
- if (!targetFile) {
261
- // Link is broken - try to find the file
262
- const candidates = findFileWithContext(linkPath);
263
-
264
- const issue = {
265
- type: 'broken-link',
266
- linkText,
267
- href,
268
- linkPath,
269
- fullMatch: match[0],
270
- };
271
-
272
- if (candidates.length === 1) {
273
- issue.status = 'auto-fixable';
274
- issue.suggestedFix = fileToSiteRelative(candidates[0]) + (anchor ? '#' + anchor : '');
275
- issue.foundAt = path.relative(DOCS_ROOT, candidates[0]);
276
- } else if (candidates.length > 1) {
277
- issue.status = 'needs-review';
278
- issue.candidates = candidates.map((c) => path.relative(DOCS_ROOT, c));
279
- } else {
280
- issue.status = 'manual-check';
281
- }
282
-
283
- issues.push(issue);
284
- continue;
285
- }
286
-
287
- // Validate anchor if present
288
- if (anchor) {
289
- const targetContent = fs.readFileSync(targetFile, 'utf-8');
290
- const anchors = extractAnchors(targetContent);
291
-
292
- if (!anchors.has(anchor)) {
293
- issues.push({
294
- type: 'broken-anchor',
295
- linkText,
296
- href,
297
- anchor,
298
- status: 'manual-check',
299
- message: `Anchor "#${anchor}" not found`,
300
- });
301
- }
302
- }
303
- }
304
-
305
- return { content, issues };
306
- }
307
-
308
- /**
309
- * Apply fixes to file content
310
- */
311
- function applyFixes(content, issues) {
312
- let updated = content;
313
-
314
- for (const issue of issues) {
315
- if (issue.status === 'auto-fixable' && issue.suggestedFix) {
316
- const oldLink = `[${issue.linkText}](${issue.href})`;
317
- const newLink = `[${issue.linkText}](${issue.suggestedFix})`;
318
- updated = updated.replace(oldLink, newLink);
319
- }
320
- }
321
-
322
- return updated;
323
- }
324
-
325
- // Main execution
326
- console.log(`\nValidating docs in: ${DOCS_ROOT}`);
327
- console.log(`Mode: ${DRY_RUN ? 'DRY RUN (use --write to fix)' : 'WRITE MODE'}\n`);
328
-
329
- const files = getMarkdownFiles(DOCS_ROOT);
330
- console.log(`Found ${files.length} markdown files\n`);
331
-
332
- let totalIssues = 0;
333
- let autoFixable = 0;
334
- let needsReview = 0;
335
- let manualCheck = 0;
336
- let filesWithIssues = 0;
337
-
338
- const allIssues = [];
339
-
340
- for (const filePath of files) {
341
- const relativePath = path.relative(DOCS_ROOT, filePath);
342
- const { content, issues } = processFile(filePath);
343
-
344
- if (issues.length > 0) {
345
- filesWithIssues++;
346
- totalIssues += issues.length;
347
-
348
- console.log(`\n${relativePath}`);
349
-
350
- for (const issue of issues) {
351
- if (issue.status === 'auto-fixable') {
352
- autoFixable++;
353
- console.log(` [FIX] ${issue.href}`);
354
- console.log(` -> ${issue.suggestedFix}`);
355
- } else if (issue.status === 'needs-review') {
356
- needsReview++;
357
- console.log(` [REVIEW] ${issue.href}`);
358
- console.log(` Multiple matches found:`);
359
- for (const candidate of issue.candidates) {
360
- console.log(` - ${candidate}`);
361
- }
362
- } else if (issue.type === 'broken-anchor') {
363
- manualCheck++;
364
- console.log(` [MANUAL] ${issue.href}`);
365
- console.log(` ${issue.message}`);
366
- } else {
367
- manualCheck++;
368
- console.log(` [MANUAL] ${issue.href}`);
369
- console.log(` File not found anywhere - may need to remove link`);
370
- }
371
-
372
- allIssues.push({ file: relativePath, ...issue });
373
- }
374
-
375
- // Apply fixes if not dry run
376
- if (!DRY_RUN) {
377
- const fixableIssues = issues.filter((i) => i.status === 'auto-fixable');
378
- if (fixableIssues.length > 0) {
379
- const updated = applyFixes(content, fixableIssues);
380
- fs.writeFileSync(filePath, updated, 'utf-8');
381
- }
382
- }
383
- }
384
- }
385
-
386
- console.log(`\n${'─'.repeat(60)}`);
387
- console.log(`\nSummary:`);
388
- console.log(` Files scanned: ${files.length}`);
389
- console.log(` Files with issues: ${filesWithIssues}`);
390
- console.log(` Total issues: ${totalIssues}`);
391
-
392
- if (totalIssues > 0) {
393
- console.log(`\n Breakdown:`);
394
- console.log(` Auto-fixable: ${autoFixable}`);
395
- console.log(` Needs review: ${needsReview}`);
396
- console.log(` Manual check: ${manualCheck}`);
397
- }
398
-
399
- if (totalIssues === 0) {
400
- console.log(`\n All links valid!`);
401
- } else if (DRY_RUN && autoFixable > 0) {
402
- console.log(`\nRun with --write to auto-fix ${autoFixable} issue(s)`);
403
- }
404
-
405
- console.log('');
406
-
407
- process.exit(totalIssues > 0 ? 1 : 0);