Conversation
Read `output_folder` from `_bmad/core/config.yaml` (with fallback to the
`_bmad-output` default) so projects with a custom BMAD output path render
correctly. Strip the `{project-root}/` placeholder when present.
- New parse-config module with both a pure parser and a provider-aware
loader, plus 15 tests covering valid/missing/malformed config.
- LocalProvider's BMAD whitelist becomes instance-level and exposes
`extendBmadDirs(name)` so the parser can grant access to a
config-declared output folder at runtime.
- Refresh flows (local + GitHub) now compute the file count against the
resolved output dir instead of the hardcoded `_bmad-output/` prefix.
Password manager extensions (1Password, etc.) inject attributes like `data-com-onepassword-filled` on inputs before React hydrates, triggering a console error. `suppressHydrationWarning` is the canonical escape hatch for this exact case.
Detect `<output>/planning-artifacts/epics/epic-N/` as a folder-based epic.
When `epic.md` is present inside, use it as the metadata source; otherwise
derive id and title from the folder name (e.g. `epic-1-platform-setup` →
`{id: "1", title: "Platform Setup"}`). Inner `.md` files become stories
with their epicId injected from the folder context, so a bare
`story-1.md` inside `epic-3/` resolves as story `3.1`.
Both layouts coexist during the BMAD V1 transition:
- Single `epics.md` keeps absolute priority (back-compat).
- Otherwise, flat `epic-N.md` files and `epic-N/` folders both contribute,
with stories collected from `implementation-artifacts/` and from inside
epic folders.
Side fixes:
- Sort epics by numeric id at parser level so order is consistent across
the dashboard, the Epics page, the Stories page, and the Library.
- Filter `.DS_Store`, `Thumbs.db`, and `desktop.ini` in LocalProvider so
OS metadata never reaches the file tree.
- parse-story now normalizes `-` separators in legacy filename ids
(`story-2-1.md` → `2.1`, not `2-1`).
Invert the priority between story markdown and sprint-status.yaml. Story status: - Markdown wins when the story declared a status explicitly (frontmatter `status:` or a `Status:` line in the body), tracked via a new `statusExplicit` flag on `StoryDetail`. - Otherwise, sprint-status.yaml fills in as a fallback (back-compat with projects whose stories carry no in-file status). - Stub stories that exist only in sprint-status are unchanged. Epic status: - Computed from the epic's stories (done / in-progress / not-started). - Sprint-status epic-level entries are now used only when the epic has no stories at all — otherwise the computed status wins. The `epicId` merge no longer overwrites a story's existing epic id with the sprint-status entry's epicId; the markdown is authoritative there too. Tests cover the new explicit/non-explicit/stub matrix and the epic computed-vs-fallback paths.
Three fixes from the cloud review: 1. Library actions now use the resolved output dir. `fetchBmadFilesLocal`, `fetchBmadFilesGitHub`, and `fetchFileContent` read `_bmad/core/config.yaml` and filter / build the artifact tree against the configured output folder. Local file reads also extend the provider whitelist before opening a file in a custom output dir, so the security guard does not deny legitimate reads. 2. Nested output paths (e.g. `output_folder: "custom/out"`) work on local. The provider whitelist accepts only single-segment names, so we now pass the top-level segment (`custom`) to `extendBmadDirs` while the parser still filters paths by the full prefix. The walker descends recursively, so nested files become visible. 3. Epic-folder stories follow the canonical id from `epic.md`. When a folder named `epic-2-foo/` contains an `epic.md` whose frontmatter declares `id: 10`, stories inside that folder are now re-tagged with epicId `10` (instead of the folder-derived `2`), so correlation links them to the right epic. Refactored: shared `resolveBmadOutputDir(provider, paths)` helper in `parse-config.ts` consolidates the read-config-then-extend-whitelist dance used by the parser and by the Library actions. Tests: 2 new integration cases — nested `custom/out/` output dir and epic.md frontmatter id reconciliation. Total: 145/145 passing.
Three sites missed in the previous review-fix pass: - `refreshLocalRepo` was passing the full `outputDir` (which can be a nested path like `custom/out`) to `LocalProvider.extendBmadDirs`, which only accepts single-segment names. The whitelist extension silently failed for nested output folders, so the file count fell to 0. Now uses the shared `resolveBmadOutputDir` helper. - `importLocalFolder` was counting BMAD files against a hardcoded `_bmad-output/` prefix at import time, so a local repo with a custom `output_folder` was created with `totalFiles: 0`. Same helper. - The `BMAD_OUTPUT` constant became unused after threading the dynamic resolution through all sites. Removed.
When `output_folder: custom/out` is configured, the LocalProvider's whitelist is extended at the top-level segment (`custom`) because `extendBmadDirs` only accepts single-segment names. Without an additional check, this lets `fetchFileContent` read any file under `custom/` — e.g. `custom/secret.txt` — even though only `custom/out/...` should be reachable. `isPathOutsideNestedOutput(requestedPath, outputDir)` returns true when the path lives under the top segment of a nested output dir but outside the configured prefix; the local branch of `fetchFileContent` denies with `ACCESS_DENIED` in that case. The check is a no-op for the default `_bmad-output` and for any single-segment custom dir (no nesting to escape from). 6 unit tests cover the helper: single-segment passthrough, allowed reads under the prefix, sibling denial, unrelated top segments, the bare top segment, and prefix-vs-substring boundary cases.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Brings the parser in line with the direction BMAD V1 is taking, while keeping the current layout fully working. Three independent capabilities, each shipped behind its own commit.
1. Dynamic
output_folder(commit2f94717)Read
output_folderfrom_bmad/core/config.yamlinstead of hardcoding_bmad-output/. Strips the{project-root}/placeholder. Falls back to the current default when the config is missing or malformed.parse-config.tsmodule (pure parser + provider-aware loader)LocalProvider's scan whitelist becomes instance-level and exposesextendBmadDirs(name)so the parser can grant access to a config-declared folder at runtime2. Frontmatter is the source of truth for status (commit
ce0c4ef)The story markdown's
status:(frontmatter) orStatus:(body line) now wins oversprint-status.yaml. Sprint-status remains a back-compat fallback for stories that declared no explicit status, plus the existing stub mechanism.statusExplicitflag onStoryDetaildistinguishes "intentionally backlog" from "no status declared"3. Epic-folder layout (commit
f5c9d45)Auto-detect
<output>/planning-artifacts/epics/epic-N/as a folder-based epic. Whenepic.mdis present inside, it provides the metadata; otherwise id and title are derived from the folder name (epic-1-platform-setup→{id: "1", title: "Platform Setup"}). Inner.mdfiles become stories with theirepicIdinjected from the folder context.Both layouts coexist during the V1 transition:
epics.mdkeeps absolute priority (back-compat)epic-N.mdfiles andepic-N/folders both contribute, with stories collected fromimplementation-artifacts/and from inside epic foldersPlus side fixes folded in:
.DS_Store,Thumbs.db,desktop.iniinLocalProvider-separators in legacy filename ids (story-2-1.md→2.1, not2-1)Hydration fix (commit
d459973)Drive-by:
suppressHydrationWarningon theInputcomponent to silence the console error caused by 1Password and similar password manager extensions injecting attributes pre-hydration.Impact on production users
Verified by inspecting two real BMAD-generated repos (
bmad-saas-main,bmad-ecommerce-main):sprint-status.yaml→ status inversion is observably a no-op for canonical projectsTest plan
pnpm test— 143 tests passing (70 new tests added acrossparse-config,parse-epic-folder,parser.epic-folder,statusExplicit,.DS_Storefiltering, and inverted correlate priorities)pnpm tsc --noEmit— no errorspnpm lint— clean (only pre-existing warnings)output_folderon a renamed local folder — 6 epics / 20 stories rendered correctlyepic.md-based and folder-name-derived epics render with correct ids, titles, and story epicIdsstatus: blockedreflects on the dashboard whilesprint-status.yamlstill saysdone