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.
- package/package.json +1 -1
- package/src/bmm/agents/analyst.agent.yaml +1 -1
- package/src/bmm/module-help.csv +1 -1
- package/src/bmm/workflows/1-analysis/create-product-brief/steps/step-02-vision.md +1 -1
- package/src/bmm/workflows/1-analysis/create-product-brief/steps/step-03-users.md +1 -1
- package/src/bmm/workflows/1-analysis/create-product-brief/steps/step-04-metrics.md +1 -1
- package/src/bmm/workflows/1-analysis/create-product-brief/steps/step-05-scope.md +1 -1
- package/src/bmm/workflows/2-plan-workflows/create-prd/steps-c/step-02-discovery.md +1 -1
- package/src/bmm/workflows/2-plan-workflows/create-prd/steps-c/step-02b-vision.md +1 -1
- package/src/bmm/workflows/2-plan-workflows/create-prd/steps-c/step-02c-executive-summary.md +1 -1
- package/src/bmm/workflows/2-plan-workflows/create-prd/steps-c/step-03-success.md +1 -1
- package/src/bmm/workflows/2-plan-workflows/create-prd/steps-c/step-04-journeys.md +1 -1
- package/src/bmm/workflows/2-plan-workflows/create-prd/steps-c/step-05-domain.md +1 -1
- package/src/bmm/workflows/2-plan-workflows/create-prd/steps-c/step-06-innovation.md +1 -1
- package/src/bmm/workflows/2-plan-workflows/create-prd/steps-c/step-07-project-type.md +1 -1
- package/src/bmm/workflows/2-plan-workflows/create-prd/steps-c/step-08-scoping.md +1 -1
- package/src/bmm/workflows/2-plan-workflows/create-prd/steps-c/step-09-functional.md +1 -1
- package/src/bmm/workflows/2-plan-workflows/create-prd/steps-c/step-10-nonfunctional.md +1 -1
- package/src/bmm/workflows/2-plan-workflows/create-prd/steps-c/step-11-polish.md +1 -1
- package/src/bmm/workflows/2-plan-workflows/create-prd/steps-e/step-e-01-discovery.md +1 -1
- package/src/bmm/workflows/2-plan-workflows/create-prd/steps-v/step-v-01-discovery.md +1 -1
- package/src/bmm/workflows/2-plan-workflows/create-ux-design/steps/step-02-discovery.md +1 -1
- package/src/bmm/workflows/2-plan-workflows/create-ux-design/steps/step-03-core-experience.md +2 -2
- package/src/bmm/workflows/2-plan-workflows/create-ux-design/steps/step-04-emotional-response.md +2 -2
- package/src/bmm/workflows/2-plan-workflows/create-ux-design/steps/step-05-inspiration.md +2 -2
- package/src/bmm/workflows/2-plan-workflows/create-ux-design/steps/step-06-design-system.md +2 -2
- package/src/bmm/workflows/2-plan-workflows/create-ux-design/steps/step-07-defining-experience.md +2 -2
- package/src/bmm/workflows/2-plan-workflows/create-ux-design/steps/step-08-visual-foundation.md +2 -2
- package/src/bmm/workflows/2-plan-workflows/create-ux-design/steps/step-09-design-directions.md +2 -2
- package/src/bmm/workflows/2-plan-workflows/create-ux-design/steps/step-10-user-journeys.md +2 -2
- package/src/bmm/workflows/2-plan-workflows/create-ux-design/steps/step-11-component-strategy.md +2 -2
- package/src/bmm/workflows/2-plan-workflows/create-ux-design/steps/step-12-ux-patterns.md +2 -2
- package/src/bmm/workflows/2-plan-workflows/create-ux-design/steps/step-13-responsive-accessibility.md +2 -2
- package/src/bmm/workflows/3-solutioning/create-architecture/steps/step-02-context.md +2 -2
- package/src/bmm/workflows/3-solutioning/create-architecture/steps/step-03-starter.md +2 -2
- package/src/bmm/workflows/3-solutioning/create-architecture/steps/step-04-decisions.md +2 -2
- package/src/bmm/workflows/3-solutioning/create-architecture/steps/step-05-patterns.md +2 -2
- package/src/bmm/workflows/3-solutioning/create-architecture/steps/step-06-structure.md +2 -2
- package/src/bmm/workflows/3-solutioning/create-architecture/steps/step-07-validation.md +2 -2
- package/src/bmm/workflows/3-solutioning/create-epics-and-stories/steps/step-01-validate-prerequisites.md +31 -13
- package/src/bmm/workflows/3-solutioning/create-epics-and-stories/steps/step-02-design-epics.md +1 -1
- package/src/bmm/workflows/3-solutioning/create-epics-and-stories/steps/step-03-create-stories.md +6 -2
- package/src/bmm/workflows/3-solutioning/create-epics-and-stories/steps/step-04-final-validation.md +1 -1
- package/src/bmm/workflows/3-solutioning/create-epics-and-stories/templates/epics-template.md +4 -0
- package/src/bmm/workflows/4-implementation/code-review/workflow.md +4 -7
- package/src/bmm/workflows/bmad-quick-flow/bmad-quick-dev-new-preview/workflow.md +0 -4
- package/src/bmm/workflows/bmad-quick-flow/quick-dev/workflow.md +1 -1
- package/src/bmm/workflows/bmad-quick-flow/quick-spec/workflow.md +1 -1
- package/src/bmm/workflows/generate-project-context/steps/step-02-generate.md +1 -1
- package/src/core/module-help.csv +2 -2
- package/src/core/workflows/bmad-brainstorming/SKILL.md +6 -0
- package/src/core/workflows/bmad-brainstorming/bmad-skill-manifest.yaml +1 -0
- package/src/core/workflows/{brainstorming → bmad-brainstorming}/workflow.md +2 -5
- package/src/core/workflows/bmad-party-mode/SKILL.md +6 -0
- package/src/core/workflows/bmad-party-mode/bmad-skill-manifest.yaml +1 -0
- package/src/core/workflows/{party-mode → bmad-party-mode}/steps/step-03-graceful-exit.md +0 -1
- package/src/core/workflows/{party-mode → bmad-party-mode}/workflow.md +0 -4
- package/tools/cli/external-official-modules.yaml +18 -18
- package/tools/cli/installers/lib/core/installer.js +25 -8
- package/tools/cli/installers/lib/ide/_base-ide.js +0 -1
- package/tools/cli/installers/lib/ide/_config-driven.js +9 -4
- package/tools/cli/installers/lib/ide/manager.js +3 -3
- package/tools/cli/lib/agent/compiler.js +1 -1
- package/.augment/code_review_guidelines.yaml +0 -231
- package/.coderabbit.yaml +0 -85
- package/.github/CODE_OF_CONDUCT.md +0 -128
- package/.github/FUNDING.yaml +0 -15
- package/.github/ISSUE_TEMPLATE/bug-report.yaml +0 -124
- package/.github/ISSUE_TEMPLATE/config.yaml +0 -8
- package/.github/ISSUE_TEMPLATE/documentation.yaml +0 -55
- package/.github/ISSUE_TEMPLATE/feature-request.md +0 -22
- package/.github/ISSUE_TEMPLATE/issue.md +0 -32
- package/.github/PULL_REQUEST_TEMPLATE.md +0 -13
- package/.github/scripts/discord-helpers.sh +0 -34
- package/.github/workflows/coderabbit-review.yaml +0 -22
- package/.github/workflows/discord.yaml +0 -90
- package/.github/workflows/docs.yaml +0 -64
- package/.github/workflows/publish.yaml +0 -133
- package/.github/workflows/quality.yaml +0 -116
- package/.husky/pre-commit +0 -20
- package/.markdownlint-cli2.yaml +0 -41
- package/.nvmrc +0 -1
- package/.prettierignore +0 -12
- package/.vscode/settings.json +0 -96
- package/CHANGELOG.md +0 -1785
- package/CNAME +0 -1
- package/CONTRIBUTING.md +0 -176
- package/CONTRIBUTORS.md +0 -32
- package/SECURITY.md +0 -85
- package/TRADEMARK.md +0 -55
- package/Wordmark.png +0 -0
- package/banner-bmad-method.png +0 -0
- package/docs/404.md +0 -9
- package/docs/_STYLE_GUIDE.md +0 -370
- package/docs/explanation/advanced-elicitation.md +0 -49
- package/docs/explanation/adversarial-review.md +0 -59
- package/docs/explanation/brainstorming.md +0 -33
- package/docs/explanation/established-projects-faq.md +0 -50
- package/docs/explanation/party-mode.md +0 -59
- package/docs/explanation/preventing-agent-conflicts.md +0 -112
- package/docs/explanation/project-context.md +0 -157
- package/docs/explanation/quick-dev-new-preview.md +0 -73
- package/docs/explanation/quick-flow.md +0 -77
- package/docs/explanation/why-solutioning-matters.md +0 -77
- package/docs/how-to/customize-bmad.md +0 -172
- package/docs/how-to/established-projects.md +0 -117
- package/docs/how-to/get-answers-about-bmad.md +0 -138
- package/docs/how-to/install-bmad.md +0 -116
- package/docs/how-to/non-interactive-installation.md +0 -171
- package/docs/how-to/project-context.md +0 -136
- package/docs/how-to/quick-fixes.md +0 -123
- package/docs/how-to/shard-large-documents.md +0 -78
- package/docs/how-to/upgrade-to-v6.md +0 -100
- package/docs/index.md +0 -60
- package/docs/reference/agents.md +0 -28
- package/docs/reference/commands.md +0 -145
- package/docs/reference/modules.md +0 -76
- package/docs/reference/testing.md +0 -106
- package/docs/reference/workflow-map.md +0 -89
- package/docs/roadmap.mdx +0 -136
- package/docs/tutorials/getting-started.md +0 -275
- package/docs/zh-cn/404.md +0 -9
- package/docs/zh-cn/_STYLE_GUIDE.md +0 -370
- package/docs/zh-cn/explanation/advanced-elicitation.md +0 -62
- package/docs/zh-cn/explanation/adversarial-review.md +0 -71
- package/docs/zh-cn/explanation/brainstorming.md +0 -43
- package/docs/zh-cn/explanation/established-projects-faq.md +0 -60
- package/docs/zh-cn/explanation/party-mode.md +0 -79
- package/docs/zh-cn/explanation/preventing-agent-conflicts.md +0 -137
- package/docs/zh-cn/explanation/project-context.md +0 -176
- package/docs/zh-cn/explanation/quick-flow.md +0 -93
- package/docs/zh-cn/explanation/why-solutioning-matters.md +0 -90
- package/docs/zh-cn/how-to/customize-bmad.md +0 -182
- package/docs/zh-cn/how-to/established-projects.md +0 -134
- package/docs/zh-cn/how-to/get-answers-about-bmad.md +0 -144
- package/docs/zh-cn/how-to/install-bmad.md +0 -105
- package/docs/zh-cn/how-to/non-interactive-installation.md +0 -181
- package/docs/zh-cn/how-to/project-context.md +0 -152
- package/docs/zh-cn/how-to/quick-fixes.md +0 -140
- package/docs/zh-cn/how-to/shard-large-documents.md +0 -86
- package/docs/zh-cn/how-to/upgrade-to-v6.md +0 -120
- package/docs/zh-cn/index.md +0 -69
- package/docs/zh-cn/reference/agents.md +0 -41
- package/docs/zh-cn/reference/commands.md +0 -166
- package/docs/zh-cn/reference/modules.md +0 -94
- package/docs/zh-cn/reference/testing.md +0 -122
- package/docs/zh-cn/reference/workflow-map.md +0 -104
- package/docs/zh-cn/roadmap.mdx +0 -152
- package/docs/zh-cn/tutorials/getting-started.md +0 -300
- package/eslint.config.mjs +0 -141
- package/prettier.config.mjs +0 -32
- package/src/core/workflows/brainstorming/bmad-skill-manifest.yaml +0 -3
- package/src/core/workflows/party-mode/bmad-skill-manifest.yaml +0 -3
- package/test/README.md +0 -295
- package/test/adversarial-review-tests/README.md +0 -56
- package/test/adversarial-review-tests/sample-content.md +0 -46
- package/test/adversarial-review-tests/test-cases.yaml +0 -103
- package/test/fixtures/agent-schema/invalid/critical-actions/actions-as-string.agent.yaml +0 -27
- package/test/fixtures/agent-schema/invalid/critical-actions/empty-string-in-actions.agent.yaml +0 -30
- package/test/fixtures/agent-schema/invalid/menu/empty-menu.agent.yaml +0 -22
- package/test/fixtures/agent-schema/invalid/menu/missing-menu.agent.yaml +0 -20
- package/test/fixtures/agent-schema/invalid/menu-commands/empty-command-target.agent.yaml +0 -25
- package/test/fixtures/agent-schema/invalid/menu-commands/no-command-target.agent.yaml +0 -24
- package/test/fixtures/agent-schema/invalid/menu-triggers/camel-case.agent.yaml +0 -25
- package/test/fixtures/agent-schema/invalid/menu-triggers/compound-invalid-format.agent.yaml +0 -25
- package/test/fixtures/agent-schema/invalid/menu-triggers/compound-mismatched-kebab.agent.yaml +0 -25
- package/test/fixtures/agent-schema/invalid/menu-triggers/duplicate-triggers.agent.yaml +0 -31
- package/test/fixtures/agent-schema/invalid/menu-triggers/empty-trigger.agent.yaml +0 -25
- package/test/fixtures/agent-schema/invalid/menu-triggers/leading-asterisk.agent.yaml +0 -25
- package/test/fixtures/agent-schema/invalid/menu-triggers/snake-case.agent.yaml +0 -25
- package/test/fixtures/agent-schema/invalid/menu-triggers/trigger-with-spaces.agent.yaml +0 -25
- package/test/fixtures/agent-schema/invalid/metadata/empty-module-string.agent.yaml +0 -26
- package/test/fixtures/agent-schema/invalid/metadata/empty-name.agent.yaml +0 -24
- package/test/fixtures/agent-schema/invalid/metadata/extra-metadata-fields.agent.yaml +0 -27
- package/test/fixtures/agent-schema/invalid/metadata/missing-id.agent.yaml +0 -23
- package/test/fixtures/agent-schema/invalid/persona/empty-principles-array.agent.yaml +0 -24
- package/test/fixtures/agent-schema/invalid/persona/empty-string-in-principles.agent.yaml +0 -27
- package/test/fixtures/agent-schema/invalid/persona/extra-persona-fields.agent.yaml +0 -27
- package/test/fixtures/agent-schema/invalid/persona/missing-role.agent.yaml +0 -24
- package/test/fixtures/agent-schema/invalid/prompts/empty-content.agent.yaml +0 -29
- package/test/fixtures/agent-schema/invalid/prompts/extra-prompt-fields.agent.yaml +0 -31
- package/test/fixtures/agent-schema/invalid/prompts/missing-content.agent.yaml +0 -28
- package/test/fixtures/agent-schema/invalid/prompts/missing-id.agent.yaml +0 -28
- package/test/fixtures/agent-schema/invalid/top-level/empty-file.agent.yaml +0 -5
- package/test/fixtures/agent-schema/invalid/top-level/extra-top-level-keys.agent.yaml +0 -28
- package/test/fixtures/agent-schema/invalid/top-level/missing-agent-key.agent.yaml +0 -11
- package/test/fixtures/agent-schema/invalid/yaml-errors/invalid-indentation.agent.yaml +0 -19
- package/test/fixtures/agent-schema/invalid/yaml-errors/malformed-yaml.agent.yaml +0 -18
- package/test/fixtures/agent-schema/valid/critical-actions/empty-critical-actions.agent.yaml +0 -24
- package/test/fixtures/agent-schema/valid/critical-actions/no-critical-actions.agent.yaml +0 -22
- package/test/fixtures/agent-schema/valid/critical-actions/valid-critical-actions.agent.yaml +0 -27
- package/test/fixtures/agent-schema/valid/menu/multiple-menu-items.agent.yaml +0 -31
- package/test/fixtures/agent-schema/valid/menu/single-menu-item.agent.yaml +0 -22
- package/test/fixtures/agent-schema/valid/menu-commands/all-command-types.agent.yaml +0 -38
- package/test/fixtures/agent-schema/valid/menu-commands/multiple-commands.agent.yaml +0 -23
- package/test/fixtures/agent-schema/valid/menu-triggers/compound-triggers.agent.yaml +0 -31
- package/test/fixtures/agent-schema/valid/menu-triggers/kebab-case-triggers.agent.yaml +0 -34
- package/test/fixtures/agent-schema/valid/metadata/core-agent-with-module.agent.yaml +0 -24
- package/test/fixtures/agent-schema/valid/metadata/empty-module-name-in-path.agent.yaml +0 -24
- package/test/fixtures/agent-schema/valid/metadata/malformed-path-treated-as-core.agent.yaml +0 -24
- package/test/fixtures/agent-schema/valid/metadata/module-agent-correct.agent.yaml +0 -24
- package/test/fixtures/agent-schema/valid/metadata/module-agent-missing-module.agent.yaml +0 -23
- package/test/fixtures/agent-schema/valid/metadata/wrong-module-value.agent.yaml +0 -24
- package/test/fixtures/agent-schema/valid/persona/complete-persona.agent.yaml +0 -24
- package/test/fixtures/agent-schema/valid/prompts/empty-prompts.agent.yaml +0 -24
- package/test/fixtures/agent-schema/valid/prompts/no-prompts.agent.yaml +0 -22
- package/test/fixtures/agent-schema/valid/prompts/valid-prompts-minimal.agent.yaml +0 -28
- package/test/fixtures/agent-schema/valid/prompts/valid-prompts-with-description.agent.yaml +0 -30
- package/test/fixtures/agent-schema/valid/top-level/minimal-core-agent.agent.yaml +0 -24
- package/test/fixtures/file-refs-csv/invalid/all-empty-workflow.csv +0 -3
- package/test/fixtures/file-refs-csv/invalid/empty-data.csv +0 -1
- package/test/fixtures/file-refs-csv/invalid/no-workflow-column.csv +0 -3
- package/test/fixtures/file-refs-csv/invalid/unresolvable-vars.csv +0 -3
- package/test/fixtures/file-refs-csv/valid/bmm-style.csv +0 -3
- package/test/fixtures/file-refs-csv/valid/core-style.csv +0 -3
- package/test/fixtures/file-refs-csv/valid/minimal.csv +0 -2
- package/test/test-agent-schema.js +0 -387
- package/test/test-cli-integration.sh +0 -159
- package/test/test-file-refs-csv.js +0 -133
- package/test/test-install-to-bmad.js +0 -154
- package/test/test-installation-components.js +0 -1796
- package/test/test-rehype-plugins.mjs +0 -1050
- package/test/test-workflow-path-regex.js +0 -88
- package/test/unit-test-schema.js +0 -133
- package/tools/build-docs.mjs +0 -464
- package/tools/docs/_prompt-external-modules-page.md +0 -59
- package/tools/docs/fix-refs.md +0 -91
- package/tools/docs/native-skills-migration-checklist.md +0 -281
- package/tools/fix-doc-links.js +0 -285
- package/tools/validate-agent-schema.js +0 -110
- package/tools/validate-doc-links.js +0 -407
- package/tools/validate-file-refs.js +0 -556
- package/website/README.md +0 -75
- package/website/astro.config.mjs +0 -157
- package/website/public/diagrams/quick-dev-diagram.png +0 -0
- package/website/public/favicon.ico +0 -0
- package/website/public/img/bmad-dark.png +0 -0
- package/website/public/img/bmad-light.png +0 -0
- package/website/public/workflow-map-diagram.html +0 -361
- package/website/src/components/Banner.astro +0 -62
- package/website/src/components/Header.astro +0 -96
- package/website/src/components/MobileMenuFooter.astro +0 -33
- package/website/src/content/config.ts +0 -7
- package/website/src/content/i18n/zh-CN.json +0 -28
- package/website/src/lib/site-url.mjs +0 -25
- package/website/src/pages/404.astro +0 -11
- package/website/src/pages/robots.txt.ts +0 -48
- package/website/src/rehype-base-paths.js +0 -112
- package/website/src/rehype-markdown-links.js +0 -119
- package/website/src/styles/custom.css +0 -805
- /package/src/core/workflows/{brainstorming → bmad-brainstorming}/brain-methods.csv +0 -0
- /package/src/core/workflows/{brainstorming → bmad-brainstorming}/steps/step-01-session-setup.md +0 -0
- /package/src/core/workflows/{brainstorming → bmad-brainstorming}/steps/step-01b-continue.md +0 -0
- /package/src/core/workflows/{brainstorming → bmad-brainstorming}/steps/step-02a-user-selected.md +0 -0
- /package/src/core/workflows/{brainstorming → bmad-brainstorming}/steps/step-02b-ai-recommended.md +0 -0
- /package/src/core/workflows/{brainstorming → bmad-brainstorming}/steps/step-02c-random-selection.md +0 -0
- /package/src/core/workflows/{brainstorming → bmad-brainstorming}/steps/step-02d-progressive-flow.md +0 -0
- /package/src/core/workflows/{brainstorming → bmad-brainstorming}/steps/step-03-technique-execution.md +0 -0
- /package/src/core/workflows/{brainstorming → bmad-brainstorming}/steps/step-04-idea-organization.md +0 -0
- /package/src/core/workflows/{brainstorming → bmad-brainstorming}/template.md +0 -0
- /package/src/core/workflows/{party-mode → bmad-party-mode}/steps/step-01-agent-loading.md +0 -0
- /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);
|