bmad-method 6.7.1-next.5 → 6.7.1-next.7
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 +3 -2
- package/removals.txt +3 -0
- package/src/bmm-skills/1-analysis/bmad-agent-analyst/SKILL.md +2 -0
- package/src/bmm-skills/1-analysis/bmad-agent-tech-writer/SKILL.md +2 -0
- package/src/bmm-skills/1-analysis/bmad-document-project/SKILL.md +1 -1
- package/src/bmm-skills/1-analysis/bmad-prfaq/SKILL.md +1 -1
- package/src/bmm-skills/1-analysis/bmad-product-brief/SKILL.md +4 -1
- package/src/bmm-skills/1-analysis/research/bmad-domain-research/SKILL.md +1 -1
- package/src/bmm-skills/1-analysis/research/bmad-market-research/SKILL.md +1 -1
- package/src/bmm-skills/1-analysis/research/bmad-technical-research/SKILL.md +1 -1
- package/src/bmm-skills/2-plan-workflows/bmad-agent-pm/SKILL.md +2 -0
- package/src/bmm-skills/2-plan-workflows/bmad-agent-ux-designer/SKILL.md +2 -0
- package/src/bmm-skills/2-plan-workflows/bmad-prd/SKILL.md +4 -1
- package/src/bmm-skills/2-plan-workflows/bmad-ux/SKILL.md +4 -1
- package/src/bmm-skills/3-solutioning/bmad-agent-architect/SKILL.md +2 -0
- package/src/bmm-skills/3-solutioning/bmad-check-implementation-readiness/SKILL.md +1 -1
- package/src/bmm-skills/3-solutioning/bmad-create-architecture/SKILL.md +1 -1
- package/src/bmm-skills/3-solutioning/bmad-create-epics-and-stories/SKILL.md +1 -1
- package/src/bmm-skills/3-solutioning/bmad-generate-project-context/SKILL.md +1 -1
- package/src/bmm-skills/4-implementation/bmad-agent-dev/SKILL.md +2 -0
- package/src/bmm-skills/4-implementation/bmad-checkpoint-preview/SKILL.md +1 -1
- package/src/bmm-skills/4-implementation/bmad-code-review/SKILL.md +1 -1
- package/src/bmm-skills/4-implementation/bmad-correct-course/SKILL.md +1 -1
- package/src/bmm-skills/4-implementation/bmad-create-story/SKILL.md +1 -1
- package/src/bmm-skills/4-implementation/bmad-dev-story/SKILL.md +1 -1
- package/src/bmm-skills/4-implementation/bmad-investigate/SKILL.md +2 -0
- package/src/bmm-skills/4-implementation/bmad-qa-generate-e2e-tests/SKILL.md +1 -1
- package/src/bmm-skills/4-implementation/bmad-quick-dev/SKILL.md +1 -1
- package/src/bmm-skills/4-implementation/bmad-retrospective/SKILL.md +1 -1
- package/src/bmm-skills/4-implementation/bmad-sprint-planning/SKILL.md +1 -1
- package/src/bmm-skills/4-implementation/bmad-sprint-status/SKILL.md +1 -1
- package/src/core-skills/bmad-spec/SKILL.md +4 -1
- package/tools/validate-sidebar-order.js +388 -0
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://json.schemastore.org/package.json",
|
|
3
3
|
"name": "bmad-method",
|
|
4
|
-
"version": "6.7.1-next.
|
|
4
|
+
"version": "6.7.1-next.7",
|
|
5
5
|
"description": "Breakthrough Method of Agile AI-driven Development",
|
|
6
6
|
"keywords": [
|
|
7
7
|
"agile",
|
|
@@ -31,6 +31,7 @@
|
|
|
31
31
|
"docs:fix-links": "node tools/fix-doc-links.js",
|
|
32
32
|
"docs:preview": "astro preview --root website",
|
|
33
33
|
"docs:validate-links": "node tools/validate-doc-links.js",
|
|
34
|
+
"docs:validate-sidebar": "node tools/validate-sidebar-order.js",
|
|
34
35
|
"format:check": "prettier --check \"**/*.{js,cjs,mjs,json,yaml}\"",
|
|
35
36
|
"format:fix": "prettier --write \"**/*.{js,cjs,mjs,json,yaml}\"",
|
|
36
37
|
"format:fix:staged": "prettier --write",
|
|
@@ -39,7 +40,7 @@
|
|
|
39
40
|
"lint:fix": "eslint . --ext .js,.cjs,.mjs,.yaml --fix",
|
|
40
41
|
"lint:md": "markdownlint-cli2 \"**/*.md\"",
|
|
41
42
|
"prepare": "command -v husky >/dev/null 2>&1 && husky || exit 0",
|
|
42
|
-
"quality": "npm run format:check && npm run lint && npm run lint:md && npm run docs:build && npm run test:install && npm run test:urls && npm run validate:refs && npm run validate:skills",
|
|
43
|
+
"quality": "npm run format:check && npm run lint && npm run lint:md && npm run docs:build && npm run test:install && npm run test:urls && npm run validate:refs && npm run validate:skills && npm run docs:validate-sidebar",
|
|
43
44
|
"rebundle": "node tools/installer/bundlers/bundle-web.js rebundle",
|
|
44
45
|
"test": "npm run test:refs && npm run test:install && npm run test:urls && npm run test:channels && npm run lint && npm run lint:md && npm run format:check",
|
|
45
46
|
"test:channels": "node test/test-installer-channels.js",
|
package/removals.txt
CHANGED
|
@@ -57,3 +57,6 @@ bmad-bmm-validate-prd
|
|
|
57
57
|
# bmad-distillator: superseded by bmad-spec (universal intent distiller with
|
|
58
58
|
# preservation-validated contract for downstream skills).
|
|
59
59
|
bmad-distillator
|
|
60
|
+
# bmad-create-ux-design: renamed to bmad-ux (spine-based skill with separate
|
|
61
|
+
# DESIGN.md and EXPERIENCE.md outputs).
|
|
62
|
+
bmad-create-ux-design
|
|
@@ -63,6 +63,8 @@ Continue to prefix your messages with `{agent.icon}` throughout the session so t
|
|
|
63
63
|
|
|
64
64
|
Execute each entry in `{agent.activation_steps_append}` in order.
|
|
65
65
|
|
|
66
|
+
Activation is complete. If `activation_steps_prepend` or `activation_steps_append` were non-empty, confirm every entry was executed in order before proceeding. Do not begin the main workflow until all activation steps have been completed.
|
|
67
|
+
|
|
66
68
|
### Step 8: Dispatch or Present the Menu
|
|
67
69
|
|
|
68
70
|
If the user's initial message already names an intent that clearly maps to a menu item (e.g. "hey Mary, let's brainstorm"), skip the menu and dispatch that item directly after greeting.
|
|
@@ -63,6 +63,8 @@ Continue to prefix your messages with `{agent.icon}` throughout the session so t
|
|
|
63
63
|
|
|
64
64
|
Execute each entry in `{agent.activation_steps_append}` in order.
|
|
65
65
|
|
|
66
|
+
Activation is complete. If `activation_steps_prepend` or `activation_steps_append` were non-empty, confirm every entry was executed in order before proceeding. Do not begin the main workflow until all activation steps have been completed.
|
|
67
|
+
|
|
66
68
|
### Step 8: Dispatch or Present the Menu
|
|
67
69
|
|
|
68
70
|
If the user's initial message already names an intent that clearly maps to a menu item (e.g. "hey Paige, let's document this codebase"), skip the menu and dispatch that item directly after greeting.
|
|
@@ -55,7 +55,7 @@ Greet `{user_name}` (if you have not already), speaking in `{communication_langu
|
|
|
55
55
|
|
|
56
56
|
Execute each entry in `{workflow.activation_steps_append}` in order.
|
|
57
57
|
|
|
58
|
-
Activation is complete.
|
|
58
|
+
Activation is complete. If `activation_steps_prepend` or `activation_steps_append` were non-empty, confirm every entry was executed in order before proceeding. Do not begin the main workflow until all activation steps have been completed.
|
|
59
59
|
|
|
60
60
|
## Execution
|
|
61
61
|
|
|
@@ -65,7 +65,7 @@ Greet `{user_name}`, speaking in `{communication_language}`. Be warm but efficie
|
|
|
65
65
|
|
|
66
66
|
Execute each entry in `{workflow.activation_steps_append}` in order.
|
|
67
67
|
|
|
68
|
-
Activation is complete.
|
|
68
|
+
Activation is complete. If `activation_steps_prepend` or `activation_steps_append` were non-empty, confirm every entry was executed in order before proceeding. Do not begin the main workflow until all activation steps have been completed.
|
|
69
69
|
|
|
70
70
|
## Pre-workflow Setup
|
|
71
71
|
|
|
@@ -21,7 +21,10 @@ At the opening greeting, let the user know they can invoke `bmad-party-mode` for
|
|
|
21
21
|
4. `{workflow.external_sources}` is an org-configured registry of internal tools (knowledge bases, MCP tools); consult them alongside generic web research on the same triggers in `## Discovery`, org tools preferred when their directive matches. If a named tool is unavailable at runtime, fall back to standard behavior and note the gap when relevant.
|
|
22
22
|
5. Load `{project-root}/_bmad/bmm/config.yaml` (and `config.user.yaml` if present). Resolve `{user_name}`, `{communication_language}`, `{document_output_language}`, `{planning_artifacts}`, `{project_name}`, `{date}`.
|
|
23
23
|
6. Greet `{user_name}` in `{communication_language}` — and stay in `{communication_language}` for every turn for the entire run, not just the greeting. Detect intent (create / update / validate). If interactive and intent is unclear, ask; for headless behavior see `## Headless Mode`.
|
|
24
|
-
|
|
24
|
+
|
|
25
|
+
Execute each entry in `{workflow.activation_steps_append}` in order.
|
|
26
|
+
|
|
27
|
+
Activation is complete. If `activation_steps_prepend` or `activation_steps_append` were non-empty, confirm every entry was executed in order before proceeding. Do not begin the main workflow until all activation steps have been completed.
|
|
25
28
|
|
|
26
29
|
## Intent Operating Modes
|
|
27
30
|
|
|
@@ -59,7 +59,7 @@ Greet `{user_name}`, speaking in `{communication_language}`.
|
|
|
59
59
|
|
|
60
60
|
Execute each entry in `{workflow.activation_steps_append}` in order.
|
|
61
61
|
|
|
62
|
-
Activation is complete.
|
|
62
|
+
Activation is complete. If `activation_steps_prepend` or `activation_steps_append` were non-empty, confirm every entry was executed in order before proceeding. Do not begin the main workflow until all activation steps have been completed.
|
|
63
63
|
|
|
64
64
|
## QUICK TOPIC DISCOVERY
|
|
65
65
|
|
|
@@ -59,7 +59,7 @@ Greet `{user_name}`, speaking in `{communication_language}`.
|
|
|
59
59
|
|
|
60
60
|
Execute each entry in `{workflow.activation_steps_append}` in order.
|
|
61
61
|
|
|
62
|
-
Activation is complete.
|
|
62
|
+
Activation is complete. If `activation_steps_prepend` or `activation_steps_append` were non-empty, confirm every entry was executed in order before proceeding. Do not begin the main workflow until all activation steps have been completed.
|
|
63
63
|
|
|
64
64
|
## QUICK TOPIC DISCOVERY
|
|
65
65
|
|
|
@@ -59,7 +59,7 @@ Greet `{user_name}`, speaking in `{communication_language}`.
|
|
|
59
59
|
|
|
60
60
|
Execute each entry in `{workflow.activation_steps_append}` in order.
|
|
61
61
|
|
|
62
|
-
Activation is complete.
|
|
62
|
+
Activation is complete. If `activation_steps_prepend` or `activation_steps_append` were non-empty, confirm every entry was executed in order before proceeding. Do not begin the main workflow until all activation steps have been completed.
|
|
63
63
|
|
|
64
64
|
## QUICK TOPIC DISCOVERY
|
|
65
65
|
|
|
@@ -63,6 +63,8 @@ Continue to prefix your messages with `{agent.icon}` throughout the session so t
|
|
|
63
63
|
|
|
64
64
|
Execute each entry in `{agent.activation_steps_append}` in order.
|
|
65
65
|
|
|
66
|
+
Activation is complete. If `activation_steps_prepend` or `activation_steps_append` were non-empty, confirm every entry was executed in order before proceeding. Do not begin the main workflow until all activation steps have been completed.
|
|
67
|
+
|
|
66
68
|
### Step 8: Dispatch or Present the Menu
|
|
67
69
|
|
|
68
70
|
If the user's initial message already names an intent that clearly maps to a menu item (e.g. "hey John, let's write the PRD"), skip the menu and dispatch that item directly after greeting.
|
|
@@ -63,6 +63,8 @@ Continue to prefix your messages with `{agent.icon}` throughout the session so t
|
|
|
63
63
|
|
|
64
64
|
Execute each entry in `{agent.activation_steps_append}` in order.
|
|
65
65
|
|
|
66
|
+
Activation is complete. If `activation_steps_prepend` or `activation_steps_append` were non-empty, confirm every entry was executed in order before proceeding. Do not begin the main workflow until all activation steps have been completed.
|
|
67
|
+
|
|
66
68
|
### Step 8: Dispatch or Present the Menu
|
|
67
69
|
|
|
68
70
|
If the user's initial message already names an intent that clearly maps to a menu item (e.g. "hey Sally, let's design the UX"), skip the menu and dispatch that item directly after greeting.
|
|
@@ -20,7 +20,10 @@ You are a master facilitator and coach helping the user create, edit, or validat
|
|
|
20
20
|
3. Load `{project-root}/_bmad/bmm/config.yaml` (+ `config.user.yaml` if present). Resolve `{user_name}`, `{communication_language}`, `{document_output_language}`, `{planning_artifacts}`, `{project_name}`, `{date}`. Missing keys → neutral defaults; never block.
|
|
21
21
|
4. If headless, follow `references/headless.md` for the whole run. Otherwise greet the user **by name** using `{user_name}` and **in their language** using `{communication_language}` — and stay in `{communication_language}` for every turn for the entire run, not just the greeting. In the greeting, let the user know that at any point they can invoke `bmad-party-mode` for multi-agent perspectives or `bmad-advanced-elicitation` for deeper exploration on a specific section. Then scan for misroute on the first message: if the signal points elsewhere (game → BMad GDS; express build → `bmad-quick-dev`; one-pager → `bmad-product-brief`; vet product idea → `bmad-prfaq`; agent skill or custom agent → `bmad-workflow-builder`), suggest they might want the other options before continuing.
|
|
22
22
|
5. Detect intent: **Create** (no PRD), **Update** (existing PRD), **Validate** (critique only). If ambiguous, ask. For Create intent, before binding a fresh workspace, scan `{workflow.prd_output_path}` for prior in-progress runs (folders matching `{workflow.run_folder_pattern}` whose `prd.md` frontmatter `status` is not `final`); if any exist, offer to resume rather than starting over.
|
|
23
|
-
|
|
23
|
+
|
|
24
|
+
Run `{workflow.activation_steps_append}`.
|
|
25
|
+
|
|
26
|
+
Activation is complete. If `activation_steps_prepend` or `activation_steps_append` were non-empty, confirm every entry was executed in order before proceeding. Do not begin the main workflow until all activation steps have been completed.
|
|
24
27
|
|
|
25
28
|
## Intent Modes
|
|
26
29
|
|
|
@@ -35,7 +35,10 @@ UX may lead, follow, or stand alone. Inherit `sources:` by reference; the spines
|
|
|
35
35
|
3. Load `{project-root}/_bmad/bmm/config.yaml` (+ `config.user.yaml` if present). Resolve `{user_name}`, `{communication_language}`, `{document_output_language}`, `{planning_artifacts}`, `{project_name}`, `{date}`. Missing keys → neutral defaults; never block.
|
|
36
36
|
4. If headless, follow `references/headless.md` for the whole run. Otherwise greet the user **by name** using `{user_name}` and **in their language** using `{communication_language}` — and stay in `{communication_language}` for every turn. In the greeting, let the user know `bmad-party-mode` and `bmad-advanced-elicitation` are always available. Then scan for misroute on the first message: PRD → `bmad-prd`; architecture → `bmad-create-architecture`; game UX → BMad GDS; agent/skill → `bmad-workflow-builder`; brief → `bmad-product-brief`.
|
|
37
37
|
5. Detect intent: **Create**, **Update**, **Validate**. For Create, before binding a fresh workspace, scan `{workflow.ux_output_path}` for prior in-progress runs (folders matching `{workflow.run_folder_pattern}` whose `DESIGN.md` frontmatter `status` is not `final`) and offer to resume rather than starting over.
|
|
38
|
-
|
|
38
|
+
|
|
39
|
+
Run `{workflow.activation_steps_append}`.
|
|
40
|
+
|
|
41
|
+
Activation is complete. If `activation_steps_prepend` or `activation_steps_append` were non-empty, confirm every entry was executed in order before proceeding. Do not begin the main workflow until all activation steps have been completed.
|
|
39
42
|
|
|
40
43
|
## Modes
|
|
41
44
|
|
|
@@ -63,6 +63,8 @@ Continue to prefix your messages with `{agent.icon}` throughout the session so t
|
|
|
63
63
|
|
|
64
64
|
Execute each entry in `{agent.activation_steps_append}` in order.
|
|
65
65
|
|
|
66
|
+
Activation is complete. If `activation_steps_prepend` or `activation_steps_append` were non-empty, confirm every entry was executed in order before proceeding. Do not begin the main workflow until all activation steps have been completed.
|
|
67
|
+
|
|
66
68
|
### Step 8: Dispatch or Present the Menu
|
|
67
69
|
|
|
68
70
|
If the user's initial message already names an intent that clearly maps to a menu item (e.g. "hey Winston, let's architect this"), skip the menu and dispatch that item directly after greeting.
|
|
@@ -84,7 +84,7 @@ Greet `{user_name}`, speaking in `{communication_language}`.
|
|
|
84
84
|
|
|
85
85
|
Execute each entry in `{workflow.activation_steps_append}` in order.
|
|
86
86
|
|
|
87
|
-
Activation is complete.
|
|
87
|
+
Activation is complete. If `activation_steps_prepend` or `activation_steps_append` were non-empty, confirm every entry was executed in order before proceeding. Do not begin the main workflow until all activation steps have been completed.
|
|
88
88
|
|
|
89
89
|
## Execution
|
|
90
90
|
|
|
@@ -65,7 +65,7 @@ Greet `{user_name}`, speaking in `{communication_language}`.
|
|
|
65
65
|
|
|
66
66
|
Execute each entry in `{workflow.activation_steps_append}` in order.
|
|
67
67
|
|
|
68
|
-
Activation is complete.
|
|
68
|
+
Activation is complete. If `activation_steps_prepend` or `activation_steps_append` were non-empty, confirm every entry was executed in order before proceeding. Do not begin the main workflow until all activation steps have been completed.
|
|
69
69
|
|
|
70
70
|
## Execution
|
|
71
71
|
|
|
@@ -86,7 +86,7 @@ Greet `{user_name}`, speaking in `{communication_language}`.
|
|
|
86
86
|
|
|
87
87
|
Execute each entry in `{workflow.activation_steps_append}` in order.
|
|
88
88
|
|
|
89
|
-
Activation is complete.
|
|
89
|
+
Activation is complete. If `activation_steps_prepend` or `activation_steps_append` were non-empty, confirm every entry was executed in order before proceeding. Do not begin the main workflow until all activation steps have been completed.
|
|
90
90
|
|
|
91
91
|
## Execution
|
|
92
92
|
|
|
@@ -65,7 +65,7 @@ Greet `{user_name}`, speaking in `{communication_language}`.
|
|
|
65
65
|
|
|
66
66
|
Execute each entry in `{workflow.activation_steps_append}` in order.
|
|
67
67
|
|
|
68
|
-
Activation is complete.
|
|
68
|
+
Activation is complete. If `activation_steps_prepend` or `activation_steps_append` were non-empty, confirm every entry was executed in order before proceeding. Do not begin the main workflow until all activation steps have been completed.
|
|
69
69
|
|
|
70
70
|
## Paths
|
|
71
71
|
|
|
@@ -63,6 +63,8 @@ Continue to prefix your messages with `{agent.icon}` throughout the session so t
|
|
|
63
63
|
|
|
64
64
|
Execute each entry in `{agent.activation_steps_append}` in order.
|
|
65
65
|
|
|
66
|
+
Activation is complete. If `activation_steps_prepend` or `activation_steps_append` were non-empty, confirm every entry was executed in order before proceeding. Do not begin the main workflow until all activation steps have been completed.
|
|
67
|
+
|
|
66
68
|
### Step 8: Dispatch or Present the Menu
|
|
67
69
|
|
|
68
70
|
If the user's initial message already names an intent that clearly maps to a menu item (e.g. "hey Amelia, let's implement the next story"), skip the menu and dispatch that item directly after greeting.
|
|
@@ -55,7 +55,7 @@ Greet the user, speaking in `{communication_language}`.
|
|
|
55
55
|
|
|
56
56
|
Execute each entry in `{workflow.activation_steps_append}` in order.
|
|
57
57
|
|
|
58
|
-
Activation is complete.
|
|
58
|
+
Activation is complete. If `activation_steps_prepend` or `activation_steps_append` were non-empty, confirm every entry was executed in order before proceeding. Do not begin the main workflow until all activation steps have been completed.
|
|
59
59
|
|
|
60
60
|
## Global Step Rules (apply to every step)
|
|
61
61
|
|
|
@@ -58,7 +58,7 @@ Greet `{user_name}`, speaking in `{communication_language}`.
|
|
|
58
58
|
|
|
59
59
|
Execute each entry in `{workflow.activation_steps_append}` in order.
|
|
60
60
|
|
|
61
|
-
Activation is complete.
|
|
61
|
+
Activation is complete. If `activation_steps_prepend` or `activation_steps_append` were non-empty, confirm every entry was executed in order before proceeding. Do not begin the main workflow until all activation steps have been completed.
|
|
62
62
|
|
|
63
63
|
## WORKFLOW ARCHITECTURE
|
|
64
64
|
|
|
@@ -62,7 +62,7 @@ Greet `{user_name}`, speaking in `{communication_language}`.
|
|
|
62
62
|
|
|
63
63
|
Execute each entry in `{workflow.activation_steps_append}` in order.
|
|
64
64
|
|
|
65
|
-
Activation is complete.
|
|
65
|
+
Activation is complete. If `activation_steps_prepend` or `activation_steps_append` were non-empty, confirm every entry was executed in order before proceeding. Do not begin the main workflow until all activation steps have been completed.
|
|
66
66
|
|
|
67
67
|
## Paths
|
|
68
68
|
|
|
@@ -63,7 +63,7 @@ Greet `{user_name}`, speaking in `{communication_language}`.
|
|
|
63
63
|
|
|
64
64
|
Execute each entry in `{workflow.activation_steps_append}` in order.
|
|
65
65
|
|
|
66
|
-
Activation is complete.
|
|
66
|
+
Activation is complete. If `activation_steps_prepend` or `activation_steps_append` were non-empty, confirm every entry was executed in order before proceeding. Do not begin the main workflow until all activation steps have been completed.
|
|
67
67
|
|
|
68
68
|
## Paths
|
|
69
69
|
|
|
@@ -64,7 +64,7 @@ Greet `{user_name}`, speaking in `{communication_language}`.
|
|
|
64
64
|
|
|
65
65
|
Execute each entry in `{workflow.activation_steps_append}` in order.
|
|
66
66
|
|
|
67
|
-
Activation is complete.
|
|
67
|
+
Activation is complete. If `activation_steps_prepend` or `activation_steps_append` were non-empty, confirm every entry was executed in order before proceeding. Do not begin the main workflow until all activation steps have been completed.
|
|
68
68
|
|
|
69
69
|
## Paths
|
|
70
70
|
|
|
@@ -79,6 +79,8 @@ Greet `{user_name}` in `{communication_language}`.
|
|
|
79
79
|
|
|
80
80
|
Run each entry in `{workflow.activation_steps_append}` in order.
|
|
81
81
|
|
|
82
|
+
Activation is complete. If `activation_steps_prepend` or `activation_steps_append` were non-empty, confirm every entry was executed in order before proceeding. Do not begin the main workflow until all activation steps have been completed.
|
|
83
|
+
|
|
82
84
|
### Step 7: Acknowledge and route
|
|
83
85
|
|
|
84
86
|
Acknowledge the input as a reference (record paths and IDs; don't read raw content). Path to an existing case file →
|
|
@@ -56,7 +56,7 @@ Greet `{user_name}`, speaking in `{communication_language}`.
|
|
|
56
56
|
|
|
57
57
|
Execute each entry in `{workflow.activation_steps_append}` in order.
|
|
58
58
|
|
|
59
|
-
Activation is complete.
|
|
59
|
+
Activation is complete. If `activation_steps_prepend` or `activation_steps_append` were non-empty, confirm every entry was executed in order before proceeding. Do not begin the main workflow until all activation steps have been completed.
|
|
60
60
|
|
|
61
61
|
## Paths
|
|
62
62
|
|
|
@@ -79,7 +79,7 @@ Greet `{user_name}`, speaking in `{communication_language}`.
|
|
|
79
79
|
|
|
80
80
|
Execute each entry in `{workflow.activation_steps_append}` in order.
|
|
81
81
|
|
|
82
|
-
Activation is complete.
|
|
82
|
+
Activation is complete. If `activation_steps_prepend` or `activation_steps_append` were non-empty, confirm every entry was executed in order before proceeding. Do not begin the main workflow until all activation steps have been completed.
|
|
83
83
|
|
|
84
84
|
## WORKFLOW ARCHITECTURE
|
|
85
85
|
|
|
@@ -73,7 +73,7 @@ Greet `{user_name}`, speaking in `{communication_language}`.
|
|
|
73
73
|
|
|
74
74
|
Execute each entry in `{workflow.activation_steps_append}` in order.
|
|
75
75
|
|
|
76
|
-
Activation is complete.
|
|
76
|
+
Activation is complete. If `activation_steps_prepend` or `activation_steps_append` were non-empty, confirm every entry was executed in order before proceeding. Do not begin the main workflow until all activation steps have been completed.
|
|
77
77
|
|
|
78
78
|
## Paths
|
|
79
79
|
|
|
@@ -59,7 +59,7 @@ Greet `{user_name}`, speaking in `{communication_language}`.
|
|
|
59
59
|
|
|
60
60
|
Execute each entry in `{workflow.activation_steps_append}` in order.
|
|
61
61
|
|
|
62
|
-
Activation is complete.
|
|
62
|
+
Activation is complete. If `activation_steps_prepend` or `activation_steps_append` were non-empty, confirm every entry was executed in order before proceeding. Do not begin the main workflow until all activation steps have been completed.
|
|
63
63
|
|
|
64
64
|
## Paths
|
|
65
65
|
|
|
@@ -57,7 +57,7 @@ Greet `{user_name}`, speaking in `{communication_language}`.
|
|
|
57
57
|
|
|
58
58
|
Execute each entry in `{workflow.activation_steps_append}` in order.
|
|
59
59
|
|
|
60
|
-
Activation is complete.
|
|
60
|
+
Activation is complete. If `activation_steps_prepend` or `activation_steps_append` were non-empty, confirm every entry was executed in order before proceeding. Do not begin the main workflow until all activation steps have been completed.
|
|
61
61
|
|
|
62
62
|
## Paths
|
|
63
63
|
|
|
@@ -22,7 +22,10 @@ Multiple skills may call to update the same spec over time.
|
|
|
22
22
|
2. Run `{workflow.activation_steps_prepend}`. Treat `{workflow.persistent_facts}` as foundational context (`file:` entries are loaded).
|
|
23
23
|
3. Load `{project-root}/_bmad/core/config.yaml` (and `config.user.yaml` if present), root level and `bmm` section. Resolve `{user_name}`, `{communication_language}`, `{document_output_language}`, `{planning_artifacts}`, `{project_name}`, `{date}`.
|
|
24
24
|
4. Detect mode. **Headless** when any of: no TTY, programmatic caller (another skill or non-interactive runner), or the first message pre-supplies all inputs and asks for an artifact path back. **Interactive** otherwise. In interactive mode, greet by `{user_name}` in `{communication_language}`, stay in that language, and mention that `bmad-party-mode` and `bmad-advanced-elicitation` are available for deeper exploration on any field.
|
|
25
|
-
|
|
25
|
+
|
|
26
|
+
Run `{workflow.activation_steps_append}`.
|
|
27
|
+
|
|
28
|
+
Activation is complete. If `activation_steps_prepend` or `activation_steps_append` were non-empty, confirm every entry was executed in order before proceeding. Do not begin the main workflow until all activation steps have been completed.
|
|
26
29
|
|
|
27
30
|
## Workspace
|
|
28
31
|
|
|
@@ -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();
|