Skip to content

feat(bmad): BMAD V1 compatibility — dynamic output, frontmatter-driven status, epic-folder layout#7

Merged
DevHDI merged 7 commits intomainfrom
feature/dynamic-output-dir
May 8, 2026
Merged

feat(bmad): BMAD V1 compatibility — dynamic output, frontmatter-driven status, epic-folder layout#7
DevHDI merged 7 commits intomainfrom
feature/dynamic-output-dir

Conversation

@DevHDI
Copy link
Copy Markdown
Owner

@DevHDI DevHDI commented May 8, 2026

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 (commit 2f94717)

Read output_folder from _bmad/core/config.yaml instead of hardcoding _bmad-output/. Strips the {project-root}/ placeholder. Falls back to the current default when the config is missing or malformed.

  • New parse-config.ts module (pure parser + provider-aware loader)
  • LocalProvider's scan whitelist becomes instance-level and exposes extendBmadDirs(name) so the parser can grant access to a config-declared folder at runtime
  • Refresh flows (local + GitHub) count files against the resolved output dir, not a hardcoded prefix

2. Frontmatter is the source of truth for status (commit ce0c4ef)

The story markdown's status: (frontmatter) or Status: (body line) now wins over sprint-status.yaml. Sprint-status remains a back-compat fallback for stories that declared no explicit status, plus the existing stub mechanism.

  • New statusExplicit flag on StoryDetail distinguishes "intentionally backlog" from "no status declared"
  • Epic status is computed from its stories; sprint-status epic-level is fallback only when the epic has no stories at all
  • Verified on real BMAD-generated repos: frontmatter and sprint-status are already synced, so observable behavior is unchanged for normal users

3. Epic-folder layout (commit f5c9d45)

Auto-detect <output>/planning-artifacts/epics/epic-N/ as a folder-based epic. When epic.md is 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 .md files become stories with their epicId injected from the folder context.

Both layouts coexist during the 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

Plus side fixes folded in:

  • Sort epics by numeric id at parser level so order is consistent across all pages
  • Filter .DS_Store, Thumbs.db, desktop.ini in LocalProvider
  • Normalize - separators in legacy filename ids (story-2-1.md2.1, not 2-1)

Hydration fix (commit d459973)

Drive-by: suppressHydrationWarning on the Input component 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):

  • 38/38 stories have explicit frontmatter status, 0 mismatches with sprint-status.yaml → status inversion is observably a no-op for canonical projects
  • All other changes are additive (new layouts) or restricted to the local provider (.DS_Store filter)

Test plan

  • pnpm test — 143 tests passing (70 new tests added across parse-config, parse-epic-folder, parser.epic-folder, statusExplicit, .DS_Store filtering, and inverted correlate priorities)
  • pnpm tsc --noEmit — no errors
  • pnpm lint — clean (only pre-existing warnings)
  • Live test: dynamic output_folder on a renamed local folder — 6 epics / 20 stories rendered correctly
  • Live test: epic-folder layout on a fresh fixture — both epic.md-based and folder-name-derived epics render with correct ids, titles, and story epicIds
  • Live test: frontmatter-wins inversion — modifying a story's frontmatter to status: blocked reflects on the dashboard while sprint-status.yaml still says done

DevHDI added 7 commits May 8, 2026 11:32
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.
@DevHDI DevHDI merged commit e320322 into main May 8, 2026
2 checks passed
@DevHDI DevHDI deleted the feature/dynamic-output-dir branch May 8, 2026 19:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant