Skip to content

feat: unify bmad-build onto the shared snapshot renderer with a customizable defer sink - #2657

Merged
alexeyv merged 1 commit into
mainfrom
feat/unify-build-renderer
Aug 1, 2026
Merged

feat: unify bmad-build onto the shared snapshot renderer with a customizable defer sink#2657
alexeyv merged 1 commit into
mainfrom
feat/unify-build-renderer

Conversation

@alexeyv

@alexeyv alexeyv commented Jul 31, 2026

Copy link
Copy Markdown
Collaborator

What

bmad-build now renders through the shared snapshot renderer (_bmad/scripts/render_skill.py) — its skill-local render.py is retired — and deferred-work recording in both build skills becomes a [workflow] defer customization key.

Why

Two renderers meant double maintenance, and the strictly more robust one (immutable content-addressed generations, atomic publish, strict validation) was only used by bmad-build-auto. The deferral sink was hard-wired: bmad-build to a deferred-work.md ledger via renderer-derived variables, build-auto to spec frontmatter. The defer key keeps each default but lets installs reroute deferrals (another file, SQLite, Jira) without editing step files.

How

  • Swap bmad-build's SKILL.md to the shared invocation; inline artifact-path joins; convert 22 cross-references to [[bmad-snapshot:]] tokens
  • Add defer to both skills' customize.toml — call sites keep producing entries, the key owns where they are recorded
  • Retire test/test-build-renderer.js; the shared suite is parameterized per skill (29 tests), the contract test pins the relocated default, Suite 49 covers the installed bmad-build surface

Testing

Full npm run quality green at every commit; adversarial/edge-case/verification-gap/intent-alignment review each commit with all patches applied; key assertions mutation-tested.

🤖 Generated with Claude Code

@alexeyv
alexeyv marked this pull request as draft July 31, 2026 17:13
@greptile-apps

greptile-apps Bot commented Jul 31, 2026

Copy link
Copy Markdown

Greptile Summary

This PR unifies bmad-build onto the shared immutable snapshot renderer (render_skill.py), retiring its dedicated render.py, and promotes deferred-work recording in both build skills to a [workflow] defer customization key. The defer key gives each install a single, documented interception point for routing deferrals to alternative sinks (different file, SQLite, Jira) without editing step files.

  • Renderer consolidation: bmad-build/render.py (~420 lines) is deleted; SKILL.md switches to invoking the shared render_skill.py with explicit --project-root and --skill arguments; all 22 cross-references in the step files are converted to [[bmad-snapshot:]] tokens.
  • Defer key: Added to both skills' customize.toml with defaults (single-line for bmad-build, multi-line for bmad-build-auto); each step file's call site replaces the inlined recording instruction with {workflow.defer}; the shared renderer enforces that the key is non-empty.
  • Test changes: test-build-renderer.js is retired; test-build-auto-renderer.js is parameterized to cover both skills (29 tests); test-build-auto-contract.js is added to pin the orchestrator contract around the deferred: frontmatter list.

Confidence Score: 4/5

Safe to merge. The renderer consolidation is well-contained: the shared renderer already served bmad-build-auto, the new defer key has enforced non-empty semantics validated by tests, and the retired render.py is fully replaced with matching behavior.

The change is large in line count but low in behavioral risk — the shared renderer was already proven for bmad-build-auto, and the new defer key adds a customization seam rather than changing default behavior. The test suite covers defer override routing, empty-defer HALT, SKILL.md command dispatch, immutability, and the orchestrator contract. The two observations are purely about test ergonomics and do not affect the correctness of the production skill or its defaults.

Files Needing Attention: test/test-build-auto-renderer.js could benefit from a failure accumulation structure matching test-build-auto-contract.js; test/test-build-auto-contract.js may need additional locale checks if other language builds of build-auto.md exist.

Important Files Changed

Filename Overview
src/bmm-skills/4-implementation/bmad-build/render.py Deleted: ~420-line skill-local renderer replaced by the shared render_skill.py
src/bmm-skills/4-implementation/bmad-build/SKILL.md Invocation updated to use shared renderer with explicit --project-root and --skill flags; on-success prose clarified
src/bmm-skills/4-implementation/bmad-build/customize.toml Adds single-line defer key with ledger default; updates open_spec comment to remove empty-string disable path; adds {skill-root} note for review layers
src/bmm-skills/4-implementation/bmad-build-auto/customize.toml Adds multi-line defer key with spec frontmatter default; adds non-empty validation note and cross-skill placeholder warnings
src/bmm-skills/4-implementation/bmad-build-auto/step-04-review.md Defer action bullet slimmed to field spec only; recording instruction moved to a dedicated ### Defer recording section that splices {workflow.defer} at column 0
src/bmm-skills/4-implementation/bmad-build/step-04-review.md defer action bullet now delegates to {workflow.defer} token placed inside the list item; {deferred_work_file} defined in prose as compile-time fallback reference
test/test-build-auto-renderer.js Parameterized to cover both bmad-build and bmad-build-auto; adds 10+ new tests for defer override routing, empty-defer HALT, and SKILL.md command dispatch; test() helper catches failures to stderr without accumulating them for an end-of-run summary
test/test-build-auto-contract.js New contract test suite pinning the orchestrator-facing deferred: frontmatter contract; validates YAML shape round-trips, forbids deferred-work.md references, checks Chinese i18n doc
test/test-build-renderer.js Deleted: retired skill-local renderer test replaced by the parameterized shared suite in test-build-auto-renderer.js
package.json Removes test-build-renderer.js from the test:renderer script

Sequence Diagram

sequenceDiagram
    participant A as Agent
    participant R as render_skill.py
    participant C as customize.toml
    participant S as Step file (rendered)
    participant Sink as Deferral Sink

    A->>R: uv run render_skill.py --project-root --skill
    R->>C: read workflow.defer default / override
    R->>S: "splice {workflow.defer} token into recording instruction"
    R-->>A: stdout: read and follow /snapshot/workflow.md

    Note over A,S: Runtime execution
    A->>S: read step-0N.md (call site)
    S-->>A: "produce entry {summary, evidence, ...}"
    A->>S: follow recording instruction (from defer token)
    alt default sink (bmad-build)
        A->>Sink: append entry to deferred-work.md
    else default sink (bmad-build-auto)
        A->>Sink: update deferred: list in spec frontmatter
    else custom override
        A->>Sink: write to alternate sink (Jira / SQLite / other file)
    end
Loading

Comments Outside Diff (2)

  1. test/test-build-auto-renderer.js, line 24-33 (link)

    P2 Test failure details lost in renderer suite

    The test() helper here logs failures to console.error and relies on the final passed/total count as the only summary. Unlike test-build-auto-contract.js, it has no failures array, so a developer looking at CI output has to scroll through all output to find the first failing assertion rather than seeing a re-listed failure block. This is minor in a 29-test suite today but will become friction as the suite grows with more bmad-build-specific cases.

    Prompt To Fix With AI
    This is a comment left during a code review.
    Path: test/test-build-auto-renderer.js
    Line: 24-33
    
    Comment:
    **Test failure details lost in renderer suite**
    
    The `test()` helper here logs failures to `console.error` and relies on the final `passed/total` count as the only summary. Unlike `test-build-auto-contract.js`, it has no `failures` array, so a developer looking at CI output has to scroll through all output to find the first failing assertion rather than seeing a re-listed failure block. This is minor in a 29-test suite today but will become friction as the suite grows with more `bmad-build`-specific cases.
    
    ---
    
    For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

    Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

  2. test/test-build-auto-contract.js, line 165-169 (link)

    P2 i18n coverage limited to Chinese only

    The contract test pins the English and Chinese reference docs against the new "default sink" qualifier language. If other localized build-auto.md files exist (e.g. docs/fr/reference/build-auto.md, docs/vi-vn/reference/build-auto.md), they are not validated here, so a future i18n update could silently ship stale orchestrator guidance. Worth adding at least a filesystem glob check that either finds no other locale files or validates each one it finds.

    Prompt To Fix With AI
    This is a comment left during a code review.
    Path: test/test-build-auto-contract.js
    Line: 165-169
    
    Comment:
    **i18n coverage limited to Chinese only**
    
    The contract test pins the English and Chinese reference docs against the new "default sink" qualifier language. If other localized `build-auto.md` files exist (e.g. `docs/fr/reference/build-auto.md`, `docs/vi-vn/reference/build-auto.md`), they are not validated here, so a future i18n update could silently ship stale orchestrator guidance. Worth adding at least a filesystem glob check that either finds no other locale files or validates each one it finds.
    
    ---
    
    For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

    Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Prompt To Fix All With AI
### Issue 1
test/test-build-auto-renderer.js:24-33
**Test failure details lost in renderer suite**

The `test()` helper here logs failures to `console.error` and relies on the final `passed/total` count as the only summary. Unlike `test-build-auto-contract.js`, it has no `failures` array, so a developer looking at CI output has to scroll through all output to find the first failing assertion rather than seeing a re-listed failure block. This is minor in a 29-test suite today but will become friction as the suite grows with more `bmad-build`-specific cases.

### Issue 2
test/test-build-auto-contract.js:165-169
**i18n coverage limited to Chinese only**

The contract test pins the English and Chinese reference docs against the new "default sink" qualifier language. If other localized `build-auto.md` files exist (e.g. `docs/fr/reference/build-auto.md`, `docs/vi-vn/reference/build-auto.md`), they are not validated here, so a future i18n update could silently ship stale orchestrator guidance. Worth adding at least a filesystem glob check that either finds no other locale files or validates each one it finds.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Reviews (1): Last reviewed commit: "chore(build): align install tests and do..." | Re-trigger Greptile

@coderabbitai

coderabbitai Bot commented Jul 31, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The build skills now use shared snapshot rendering and configurable workflow.defer handling. Workflow references use absolute snapshot targets, deferred findings support alternate destinations with spec fallback, and tests cover both skills.

Changes

Build renderer and deferral flow

Layer / File(s) Summary
Shared renderer migration
src/bmm-skills/ship/bmad-build/SKILL.md, src/bmm-skills/ship/bmad-build/workflow.md, test/test-build-auto-renderer.js, test/test-installation-components.js, package.json
bmad-build uses the shared render_skill.py command. Renderer tests and installation checks now cover both build skills, dispatch, snapshot publication, and failure handling.
Configurable defer contract
src/bmm-skills/ship/bmad-build-auto/customize.toml, src/bmm-skills/ship/bmad-build/customize.toml, src/bmm-skills/ship/bmad-build-auto/step-04-review.md, docs/how-to/quick-fixes.md, docs/reference/build-auto.md, test/test-build-auto-contract.js
workflow.defer records structured findings through configurable destinations. Defaults preserve deferred spec entries and support YAML-safe serialization and fallback behavior.
Workflow path and ledger integration
src/bmm-skills/ship/bmad-build/step-*.md, src/bmm-skills/ship/bmad-build/step-oneshot.md, src/bmm-skills/ship/bmad-build/sync-sprint-status.md, src/bmm-skills/ship/bmad-build/workflow.md
Build workflow steps use snapshot references, implementation-artifact paths, and workflow.defer. Sprint-status processing reads from implementation_artifacts.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Sequence Diagram(s)

sequenceDiagram
  participant BuildCommand
  participant render_skill.py
  participant RenderedWorkflow
  BuildCommand->>render_skill.py: Render selected skill
  render_skill.py-->>BuildCommand: Return absolute workflow path
  BuildCommand->>RenderedWorkflow: Execute workflow
Loading
sequenceDiagram
  participant ReviewStep
  participant workflow.defer
  participant DeferredSink
  participant Spec
  ReviewStep->>workflow.defer: Send finding fields
  workflow.defer->>DeferredSink: Write configured destination
  DeferredSink-->>workflow.defer: Return success or failure
  workflow.defer->>Spec: Record fallback entry when needed
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the shared renderer migration and customizable defer sink, which are the main changes.
Description check ✅ Passed The description directly explains the renderer unification, configurable defer sink, test changes, and validation performed.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/unify-build-renderer

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (2)
test/test-build-auto-contract.js (1)

55-74: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Duplicated workflowTable and shippedAutoDefer helpers across both test suites. Both files carry byte-identical copies and both comments ask a maintainer to keep the copies in step by hand. The root cause is the absence of a shared test helper module.

  • test/test-build-auto-contract.js#L55-L74: move workflowTable and shippedAutoDefer into a new shared module under test/ and import them here.
  • test/test-build-auto-renderer.js#L178-L199: delete the local copies and import the same shared helpers, then drop the "keep the two in step" comments.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@test/test-build-auto-contract.js` around lines 55 - 74, Move the duplicated
workflowTable and shippedAutoDefer helpers into a shared module under test/. In
test/test-build-auto-contract.js lines 55-74, remove the local definitions and
import the shared helpers; do the same in test/test-build-auto-renderer.js lines
178-199, also removing the comments about keeping copies synchronized.
test/test-build-auto-renderer.js (1)

169-176: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Scope shippedDefer to the [workflow] table.

shippedAutoDefer slices the [workflow] table before matching, and the comment on Line 178 states why: only a key inside that table resolves {workflow.defer}. shippedDefer matches ^defer = "(.+)"$ against the whole file. If a defer key is later added under another table, this helper can return a value that cannot render, and the test would still pass.

Reuse the existing workflowTable helper for both.

♻️ Proposed change
 function shippedDefer() {
-  const shipped = fs.readFileSync(path.join(SKILLS_SRC, 'bmad-build', 'customize.toml'), 'utf8');
-  const match = shipped.match(/^defer = "(.+)"$/m);
-  assert(match, 'customize.toml ships no single-line defer default');
+  const label = 'bmad-build customize.toml';
+  const table = workflowTable(fs.readFileSync(path.join(SKILLS_SRC, 'bmad-build', 'customize.toml'), 'utf8'), label);
+  const match = table.match(/^defer = "(.+)"$/m);
+  assert(match, `${label} ships no single-line defer default in [workflow]`);
   const value = match[1];
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@test/test-build-auto-renderer.js` around lines 169 - 176, Update shippedDefer
to pass the customize.toml contents through the existing workflowTable helper
before matching the defer entry, matching the scoping already used by
shippedAutoDefer. Keep the existing assertions and placeholder validation
unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/bmm-skills/4-implementation/bmad-build/step-04-review.md`:
- Line 53: Remove all leading indentation before the {workflow.defer} token so
it starts at column 0 in both
src/bmm-skills/4-implementation/bmad-build/step-04-review.md:53-53 and
src/bmm-skills/4-implementation/bmad-build/step-oneshot.md:39-39; apply the same
formatting change at both defer sites.

---

Nitpick comments:
In `@test/test-build-auto-contract.js`:
- Around line 55-74: Move the duplicated workflowTable and shippedAutoDefer
helpers into a shared module under test/. In test/test-build-auto-contract.js
lines 55-74, remove the local definitions and import the shared helpers; do the
same in test/test-build-auto-renderer.js lines 178-199, also removing the
comments about keeping copies synchronized.

In `@test/test-build-auto-renderer.js`:
- Around line 169-176: Update shippedDefer to pass the customize.toml contents
through the existing workflowTable helper before matching the defer entry,
matching the scoping already used by shippedAutoDefer. Keep the existing
assertions and placeholder validation unchanged.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 73b72196-c981-4923-a005-5d0135e35cc4

📥 Commits

Reviewing files that changed from the base of the PR and between 1164911 and e8eceff.

📒 Files selected for processing (20)
  • docs/how-to/quick-fixes.md
  • docs/reference/build-auto.md
  • package.json
  • src/bmm-skills/4-implementation/bmad-build-auto/customize.toml
  • src/bmm-skills/4-implementation/bmad-build-auto/step-04-review.md
  • src/bmm-skills/4-implementation/bmad-build/SKILL.md
  • src/bmm-skills/4-implementation/bmad-build/customize.toml
  • src/bmm-skills/4-implementation/bmad-build/render.py
  • src/bmm-skills/4-implementation/bmad-build/step-01-clarify-and-route.md
  • src/bmm-skills/4-implementation/bmad-build/step-02-plan.md
  • src/bmm-skills/4-implementation/bmad-build/step-03-implement.md
  • src/bmm-skills/4-implementation/bmad-build/step-04-review.md
  • src/bmm-skills/4-implementation/bmad-build/step-05-present.md
  • src/bmm-skills/4-implementation/bmad-build/step-oneshot.md
  • src/bmm-skills/4-implementation/bmad-build/sync-sprint-status.md
  • src/bmm-skills/4-implementation/bmad-build/workflow.md
  • test/test-build-auto-contract.js
  • test/test-build-auto-renderer.js
  • test/test-build-renderer.js
  • test/test-installation-components.js
💤 Files with no reviewable changes (2)
  • src/bmm-skills/4-implementation/bmad-build/render.py
  • test/test-build-renderer.js

Comment thread src/bmm-skills/4-implementation/bmad-build/step-04-review.md Outdated
@alexeyv
alexeyv marked this pull request as ready for review August 1, 2026 03:05

@greptile-apps greptile-apps Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

alexeyv has reached the 50-credit limit for trial accounts. To continue receiving code reviews, upgrade your plan.

@github-actions

github-actions Bot commented Aug 1, 2026

Copy link
Copy Markdown
Contributor

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Aug 1, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@alexeyv
alexeyv force-pushed the feat/unify-build-renderer branch from b5fcfe2 to e1c1079 Compare August 1, 2026 03:06

@greptile-apps greptile-apps Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

alexeyv has reached the 50-credit limit for trial accounts. To continue receiving code reviews, upgrade your plan.

@alexeyv
alexeyv force-pushed the feat/unify-build-renderer branch from e1c1079 to 8fc755d Compare August 1, 2026 03:30

@greptile-apps greptile-apps Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

alexeyv has reached the 50-credit limit for trial accounts. To continue receiving code reviews, upgrade your plan.

@alexeyv
alexeyv marked this pull request as draft August 1, 2026 04:59
@alexeyv
alexeyv force-pushed the feat/unify-build-renderer branch 2 times, most recently from beec7be to 7045e11 Compare August 1, 2026 10:48
@alexeyv
alexeyv marked this pull request as ready for review August 1, 2026 10:50

@greptile-apps greptile-apps Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

alexeyv has reached the 50-credit limit for trial accounts. To continue receiving code reviews, upgrade your plan.

@github-actions

github-actions Bot commented Aug 1, 2026

Copy link
Copy Markdown
Contributor

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Aug 1, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@alexeyv
alexeyv force-pushed the feat/unify-build-renderer branch from 7045e11 to 5f46e9f Compare August 1, 2026 10:58

@greptile-apps greptile-apps Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

alexeyv has reached the 50-credit limit for trial accounts. To continue receiving code reviews, upgrade your plan.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
src/bmm-skills/ship/bmad-build-auto/customize.toml (1)

115-183: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Document that {skill-root} resolves to the rendered snapshot directory.

bmad-build/customize.toml adds a note (lines 145-148 in that file) explaining that {skill-root} inside a review-layer instruction resolves to the rendered snapshot directory, and that overrides must point only at .md files shipped inside the skill directory. This file's review layers use the same {skill-root}/review-prompts/*.md pattern but carry no equivalent note.

Add the same clarification here so an override author does not point {skill-root} at a file the snapshot renderer never publishes.

📝 Proposed addition
 # Review layers for the review step. `instruction` is the layer's whole
 # execution recipe — subagents by default, but an override may run anything
 # (e.g. an external reviewer via bash). {diff_output} is substituted at run
 # time. `when` (optional) gates a layer; empty `instruction` disables it.
+#
+# Inside a review-layer instruction, {skill-root} resolves to this skill's
+# rendered snapshot directory. Point it only at .md files shipped inside the
+# skill directory, since those are the files published into the snapshot.

 [[workflow.review_layers]]
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/bmm-skills/ship/bmad-build-auto/customize.toml` around lines 115 - 183,
Add a clarification comment adjacent to the review-layer instructions in
workflow.review_layers explaining that {skill-root} resolves to the rendered
snapshot directory and override paths must target only .md files shipped within
the skill directory. Keep the existing review-layer behavior and instruction
strings unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@test/test-build-auto-renderer.js`:
- Around line 490-530: Update the snapshot assertions around renderRoot, the
markdown reference matching, and artifacts to use platform-native path
construction and matching instead of assuming leading `/` or literal `/`
separators. Normalize or construct expected paths consistently with path.sep so
Windows drive-prefixed and backslash paths are accepted while preserving the
existing containment, existence, and artifact checks.

---

Nitpick comments:
In `@src/bmm-skills/ship/bmad-build-auto/customize.toml`:
- Around line 115-183: Add a clarification comment adjacent to the review-layer
instructions in workflow.review_layers explaining that {skill-root} resolves to
the rendered snapshot directory and override paths must target only .md files
shipped within the skill directory. Keep the existing review-layer behavior and
instruction strings unchanged.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 3b2f4b9a-5795-4968-86e1-06ae7120eaae

📥 Commits

Reviewing files that changed from the base of the PR and between b5fcfe2 and 7045e11.

📒 Files selected for processing (19)
  • docs/how-to/quick-fixes.md
  • docs/reference/build-auto.md
  • package.json
  • src/bmm-skills/ship/bmad-build-auto/customize.toml
  • src/bmm-skills/ship/bmad-build-auto/step-04-review.md
  • src/bmm-skills/ship/bmad-build/SKILL.md
  • src/bmm-skills/ship/bmad-build/customize.toml
  • src/bmm-skills/ship/bmad-build/step-01-clarify-and-route.md
  • src/bmm-skills/ship/bmad-build/step-02-plan.md
  • src/bmm-skills/ship/bmad-build/step-03-implement.md
  • src/bmm-skills/ship/bmad-build/step-04-review.md
  • src/bmm-skills/ship/bmad-build/step-05-present.md
  • src/bmm-skills/ship/bmad-build/step-oneshot.md
  • src/bmm-skills/ship/bmad-build/sync-sprint-status.md
  • src/bmm-skills/ship/bmad-build/workflow.md
  • test/test-build-auto-contract.js
  • test/test-build-auto-renderer.js
  • test/test-build-renderer.js
  • test/test-installation-components.js
💤 Files with no reviewable changes (1)
  • test/test-build-renderer.js
🚧 Files skipped from review as they are similar to previous changes (4)
  • package.json
  • docs/how-to/quick-fixes.md
  • docs/reference/build-auto.md
  • test/test-build-auto-contract.js

Comment on lines +490 to +530
const renderRoot = path.join(fs.realpathSync(build.project), '_bmad', 'render');
const referenced = new Set();
for (const match of markdown.matchAll(/`(\/[^`]+\.md)`/g)) {
const target = match[1];
if (!target.startsWith(`${renderRoot}${path.sep}`)) continue;
assert(target.startsWith(`${dir}${path.sep}`), `cross-generation reference: ${target}`);
assert(fs.existsSync(target), `snapshot reference does not resolve: ${target}`);
referenced.add(path.relative(dir, target));
}
// Every published step must be reachable, which also keeps the loop above non-vacuous.
for (const name of Object.keys(bytesByName(dir))) {
if (!/^(?:step-|sync-sprint-status)/.test(name)) continue;
assert(referenced.has(name), `published step is unreachable from the snapshot: ${name}`);
}

const prompt = path.join(dir, 'review-prompts', 'edge-case-hunter.md');
assert(fs.existsSync(prompt), 'review prompt was not published into the snapshot');
assert(markdown.includes(prompt), 'snapshot reviewer path missing');

const review = fs.readFileSync(path.join(dir, 'step-04-review.md'), 'utf8');
for (const heading of [
'#### Blind Hunter (`blind-hunter`)',
'#### Edge Case Hunter (`edge-case-hunter`)',
'#### Verification Gap Reviewer (`verification-gap`)',
]) {
assert(review.includes(heading), `default review layer missing: ${heading}`);
}
assert(review.includes('{diff_output}'), 'runtime placeholder was removed from review layers');

const oneshot = fs.readFileSync(path.join(dir, 'step-oneshot.md'), 'utf8');
assert(oneshot.includes('#### Blind Hunter (`blind-hunter`)'), 'oneshot review layer block missing');

// The spec editor handoff must reach both terminal routes (#2652).
const present = fs.readFileSync(path.join(dir, 'step-05-present.md'), 'utf8');
assert(present.includes('code -r'), 'open_spec default missing from step-05-present.md');
assert(oneshot.includes('code -r'), 'open_spec default missing from step-oneshot.md');
assert(/^Offer to push\b/m.test(present), 'standalone "Offer to push" line was lost');

const artifacts = `${fs.realpathSync(build.project)}/implementation`;
assert(markdown.includes(`${artifacts}/sprint-status.yaml`), 'sprint-status path was not baked absolute');
assert(markdown.includes(`${artifacts}/deferred-work.md`), 'deferred-work path was not baked absolute');

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Use platform-native paths in the bmad-build snapshot assertions.

The reference regex only matches paths that start with /. Windows snapshot paths use a drive prefix and backslashes. The artifact assertions also force / after the project root. These assertions fail on Windows even when the renderer is correct.

Proposed fix
-    for (const match of markdown.matchAll(/`(\/[^`]+\.md)`/g)) {
+    for (const match of markdown.matchAll(/`([^`]+\.md)`/g)) {
       const target = match[1];
-      if (!target.startsWith(`${renderRoot}${path.sep}`)) continue;
+      if (!path.isAbsolute(target) || !target.startsWith(`${renderRoot}${path.sep}`)) continue;
       assert(target.startsWith(`${dir}${path.sep}`), `cross-generation reference: ${target}`);
       assert(fs.existsSync(target), `snapshot reference does not resolve: ${target}`);
       referenced.add(path.relative(dir, target));
     }
@@
-    const artifacts = `${fs.realpathSync(build.project)}/implementation`;
-    assert(markdown.includes(`${artifacts}/sprint-status.yaml`), 'sprint-status path was not baked absolute');
-    assert(markdown.includes(`${artifacts}/deferred-work.md`), 'deferred-work path was not baked absolute');
+    const artifacts = path.join(fs.realpathSync(build.project), 'implementation');
+    assert(markdown.includes(path.join(artifacts, 'sprint-status.yaml')), 'sprint-status path was not baked absolute');
+    assert(markdown.includes(path.join(artifacts, 'deferred-work.md')), 'deferred-work path was not baked absolute');
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const renderRoot = path.join(fs.realpathSync(build.project), '_bmad', 'render');
const referenced = new Set();
for (const match of markdown.matchAll(/`(\/[^`]+\.md)`/g)) {
const target = match[1];
if (!target.startsWith(`${renderRoot}${path.sep}`)) continue;
assert(target.startsWith(`${dir}${path.sep}`), `cross-generation reference: ${target}`);
assert(fs.existsSync(target), `snapshot reference does not resolve: ${target}`);
referenced.add(path.relative(dir, target));
}
// Every published step must be reachable, which also keeps the loop above non-vacuous.
for (const name of Object.keys(bytesByName(dir))) {
if (!/^(?:step-|sync-sprint-status)/.test(name)) continue;
assert(referenced.has(name), `published step is unreachable from the snapshot: ${name}`);
}
const prompt = path.join(dir, 'review-prompts', 'edge-case-hunter.md');
assert(fs.existsSync(prompt), 'review prompt was not published into the snapshot');
assert(markdown.includes(prompt), 'snapshot reviewer path missing');
const review = fs.readFileSync(path.join(dir, 'step-04-review.md'), 'utf8');
for (const heading of [
'#### Blind Hunter (`blind-hunter`)',
'#### Edge Case Hunter (`edge-case-hunter`)',
'#### Verification Gap Reviewer (`verification-gap`)',
]) {
assert(review.includes(heading), `default review layer missing: ${heading}`);
}
assert(review.includes('{diff_output}'), 'runtime placeholder was removed from review layers');
const oneshot = fs.readFileSync(path.join(dir, 'step-oneshot.md'), 'utf8');
assert(oneshot.includes('#### Blind Hunter (`blind-hunter`)'), 'oneshot review layer block missing');
// The spec editor handoff must reach both terminal routes (#2652).
const present = fs.readFileSync(path.join(dir, 'step-05-present.md'), 'utf8');
assert(present.includes('code -r'), 'open_spec default missing from step-05-present.md');
assert(oneshot.includes('code -r'), 'open_spec default missing from step-oneshot.md');
assert(/^Offer to push\b/m.test(present), 'standalone "Offer to push" line was lost');
const artifacts = `${fs.realpathSync(build.project)}/implementation`;
assert(markdown.includes(`${artifacts}/sprint-status.yaml`), 'sprint-status path was not baked absolute');
assert(markdown.includes(`${artifacts}/deferred-work.md`), 'deferred-work path was not baked absolute');
const renderRoot = path.join(fs.realpathSync(build.project), '_bmad', 'render');
const referenced = new Set();
for (const match of markdown.matchAll(/`([^`]+\.md)`/g)) {
const target = match[1];
if (!path.isAbsolute(target) || !target.startsWith(`${renderRoot}${path.sep}`)) continue;
assert(target.startsWith(`${dir}${path.sep}`), `cross-generation reference: ${target}`);
assert(fs.existsSync(target), `snapshot reference does not resolve: ${target}`);
referenced.add(path.relative(dir, target));
}
// Every published step must be reachable, which also keeps the loop above non-vacuous.
for (const name of Object.keys(bytesByName(dir))) {
if (!/^(?:step-|sync-sprint-status)/.test(name)) continue;
assert(referenced.has(name), `published step is unreachable from the snapshot: ${name}`);
}
const prompt = path.join(dir, 'review-prompts', 'edge-case-hunter.md');
assert(fs.existsSync(prompt), 'review prompt was not published into the snapshot');
assert(markdown.includes(prompt), 'snapshot reviewer path missing');
const review = fs.readFileSync(path.join(dir, 'step-04-review.md'), 'utf8');
for (const heading of [
'#### Blind Hunter (`blind-hunter`)',
'#### Edge Case Hunter (`edge-case-hunter`)',
'#### Verification Gap Reviewer (`verification-gap`)',
]) {
assert(review.includes(heading), `default review layer missing: ${heading}`);
}
assert(review.includes('{diff_output}'), 'runtime placeholder was removed from review layers');
const oneshot = fs.readFileSync(path.join(dir, 'step-oneshot.md'), 'utf8');
assert(oneshot.includes('#### Blind Hunter (`blind-hunter`)'), 'oneshot review layer block missing');
// The spec editor handoff must reach both terminal routes (`#2652`).
const present = fs.readFileSync(path.join(dir, 'step-05-present.md'), 'utf8');
assert(present.includes('code -r'), 'open_spec default missing from step-05-present.md');
assert(oneshot.includes('code -r'), 'open_spec default missing from step-oneshot.md');
assert(/^Offer to push\b/m.test(present), 'standalone "Offer to push" line was lost');
const artifacts = path.join(fs.realpathSync(build.project), 'implementation');
assert(markdown.includes(path.join(artifacts, 'sprint-status.yaml')), 'sprint-status path was not baked absolute');
assert(markdown.includes(path.join(artifacts, 'deferred-work.md')), 'deferred-work path was not baked absolute');
🧰 Tools
🪛 ast-grep (0.45.0)

[warning] 508-508: Filesystem path is not a string literal; a request-/variable-derived path can enable path traversal. Validate and normalize the path before use.
Context: fs.readFileSync(path.join(dir, 'step-04-review.md'), 'utf8')
Note: [CWE-22] Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal').

(detect-non-literal-fs-filename)


[warning] 518-518: Filesystem path is not a string literal; a request-/variable-derived path can enable path traversal. Validate and normalize the path before use.
Context: fs.readFileSync(path.join(dir, 'step-oneshot.md'), 'utf8')
Note: [CWE-22] Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal').

(detect-non-literal-fs-filename)


[warning] 522-522: Filesystem path is not a string literal; a request-/variable-derived path can enable path traversal. Validate and normalize the path before use.
Context: fs.readFileSync(path.join(dir, 'step-05-present.md'), 'utf8')
Note: [CWE-22] Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal').

(detect-non-literal-fs-filename)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@test/test-build-auto-renderer.js` around lines 490 - 530, Update the snapshot
assertions around renderRoot, the markdown reference matching, and artifacts to
use platform-native path construction and matching instead of assuming leading
`/` or literal `/` separators. Normalize or construct expected paths
consistently with path.sep so Windows drive-prefixed and backslash paths are
accepted while preserving the existing containment, existence, and artifact
checks.

@greptile-apps greptile-apps Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

alexeyv has reached the 50-credit limit for trial accounts. To continue receiving code reviews, upgrade your plan.

@greptile-apps greptile-apps Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

alexeyv has reached the 50-credit limit for trial accounts. To continue receiving code reviews, upgrade your plan.

@greptile-apps greptile-apps Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

alexeyv has reached the 50-credit limit for trial accounts. To continue receiving code reviews, upgrade your plan.

@greptile-apps greptile-apps Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

alexeyv has reached the 50-credit limit for trial accounts. To continue receiving code reviews, upgrade your plan.

@greptile-apps greptile-apps Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

alexeyv has reached the 50-credit limit for trial accounts. To continue receiving code reviews, upgrade your plan.

@alexeyv
alexeyv force-pushed the feat/unify-build-renderer branch from c7bad19 to b05a563 Compare August 1, 2026 17:42

@greptile-apps greptile-apps Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

alexeyv has reached the 50-credit limit for trial accounts. To continue receiving code reviews, upgrade your plan.

@alexeyv
alexeyv merged commit 6245e34 into main Aug 1, 2026
5 checks passed
@alexeyv
alexeyv deleted the feat/unify-build-renderer branch August 1, 2026 18:19
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