Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
6b3ccda
fix(install): resolve bmad-build-auto rename, refuse the shim (#405)
Jul 30, 2026
2b1a150
fix(engine): drive the resolved dev primitive in all session prompts …
Jul 30, 2026
9754f84
fix(cli): warn when a dry-run's preview is not actually runnable (#405)
Jul 30, 2026
10ce26c
fix(engine): harvest spec-frontmatter deferrals into the ledger (#405)
Jul 30, 2026
dec2cab
chore(release): 0.9.1 — Compatibility hotfix for the BMad Method's `b…
Jul 30, 2026
8eb05b2
fix(engine): stop the review prompt from double-filing harvested defe…
Jul 31, 2026
6b4f4f9
fix(engine): state the real defer-rollback semantics of the harvest (…
Jul 31, 2026
9ca3ee1
fix(worktree): seed the _bmad config surface into isolation worktrees…
Jul 31, 2026
377fcac
feat(validate): warn when the renderer stub has no central _bmad/conf…
Jul 31, 2026
20d3dc0
fix(engine): revert a harvested deferral when its attempt rolls back …
Jul 31, 2026
54a5d9b
docs(changelog): cover the review-round fixes in 0.9.1 (#405)
Jul 31, 2026
ed28777
fix(validate): block a run whose renderer stub cannot compose a promp…
Jul 31, 2026
f325920
fix(worktree): pause the run when the _bmad/scripts seed comes up sho…
Jul 31, 2026
2c553df
fix(engine): make the harvest revert survive a crash replay (#405)
Jul 31, 2026
0b61083
docs(changelog): reclassify the renderer checks and the two crash leg…
Jul 31, 2026
25997c4
fix(test): compare the ledger rel in posix form, like the code under …
Jul 31, 2026
15f0a80
docs(worktree): state the real reason the seed report is a return, no…
Jul 31, 2026
19d21d5
fix(worktree): gate the _bmad/scripts seed escalation on a renderer s…
Jul 31, 2026
e370528
fix(engine): classify the crash-replay ledger by filesystem, not by g…
Jul 31, 2026
0321876
docs(changelog): qualify the two 0.9.1 claims round R9 falsified (#405)
Jul 31, 2026
2e3c560
fix(renderer): close the required-file surface, both layers (#405)
Jul 31, 2026
87d71af
docs(changelog): amend the two renderer bullets for round R10 (#405)
Jul 31, 2026
1e564e3
fix(preflight): scope the skills gates to the dev-primitive roles (#405)
Jul 31, 2026
897988f
fix(worktree): pause when the skills seed comes up short (#405)
Jul 31, 2026
7344440
fix(worktree): scope the skills seed gate to the dispatched primitive…
Jul 31, 2026
b96b9dc
fix(worktree): merge skill seeds per file, gate on required files (#405)
Jul 31, 2026
64c4f2c
fix(engine): persist the pre-harvest ledger snapshot (#405)
Jul 31, 2026
8478394
fix(worktree): gate the skills seed on walk parity with the copier (#…
Aug 1, 2026
29ea28f
fix(sweep): close a bundle's ledger ids only once its attempt is acce…
Aug 1, 2026
a8ab809
fix(engine): persist the ledger disarm on the PROCEED path (#405)
Aug 1, 2026
d54d595
fix(sweep): re-open a bundle's ledger ids when a review-leg defer dis…
Aug 1, 2026
07918cd
fix(sweep): correct claims and portability found by adversarial revie…
Aug 1, 2026
3de6270
fix(install): gate a renderer stub on its own declared render sources…
Aug 1, 2026
1e5c030
fix(install): mirror the renderer's is-a-file guard on render sources…
Aug 1, 2026
0870941
fix(install): make provisioning total on a filesystem it cannot read …
Aug 1, 2026
d1e60af
fix(install): report a seed the worktree never got, and stop pollutin…
Aug 1, 2026
dac373b
fix(validate): refuse worktree isolation under a repo_root override, …
Aug 1, 2026
e3dba25
fix(core): three small contracts — location: n/a, per-role workflow m…
Aug 1, 2026
2edd753
fix(diagnose): alias one spec under one name, and strip the location …
Aug 1, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .claude-plugin/marketplace.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
"name": "bmad-loop",
"source": "./src/bmad_loop/data/skills",
"description": "Automation-mode skills driven by the bmad-loop orchestrator: interactive escalation resolution (bmad-loop-resolve) and deferred-work sweep triage (bmad-loop-sweep) — the inner dev primitive (which self-reviews and commits) is the upstream bmad-dev-auto skill",
"version": "0.9.0",
"version": "0.9.1",
"author": {
"name": "pinkyd"
},
Expand Down
320 changes: 320 additions & 0 deletions CHANGELOG.md

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -443,7 +443,7 @@ Merge-back is always **serialized** — `max_parallel` is a validated knob clamp
<img src="docs/images/settings-scm.png" alt="The settings editor with the [scm] section expanded: isolation, branch_per, merge_strategy, the seed-adapter-configs switch, and the extra-worktree-seed-files field." width="880">
</p>

For a monorepo or any layout where the git root differs from the project dir, set an optional `repo_root` key in `_bmad/bmm/config.yaml` — it decouples where git/code work happens from where run state lives (defaults to the project dir).
For a monorepo or any layout where the git root differs from the project dir, set an optional `repo_root` key in `_bmad/bmm/config.yaml` — it decouples where git/code work happens from where run state lives (defaults to the project dir). It is **not compatible with `isolation = "worktree"`**: provisioning seeds a worktree from `repo_root` while the preflight probes `project`, so `validate` reports the pair and `run`/`sweep`/`resume` refuse to start. Use one or the other on 0.9.x — plumbing both through provisioning is tracked as #414.

### Plugins

Expand Down
2 changes: 1 addition & 1 deletion docs/FEATURES.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ See [README.md](../README.md) for the narrative overview and [setup-guide.md](se
- Failed-unit forensics: a deferred/escalated unit's worktree + branch stay mounted (`keep_failed`, default on) and its full diff is preserved to `run_dir/failed/<unit>/changes.patch`; `failed_diff_max_mb` caps per-file untracked-file size (oversized skipped with a marker), `failed_diff_unlimited` lifts the cap.
- Config seeding: a worktree checks out _tracked_ files only, so a project's gitignored MCP/CLI configs (`.mcp.json`, `.claude/settings.json`, `.codex/config.toml`, `.gemini/settings.json`) would be missing — an isolated session couldn't reach its MCP server. With `seed_adapter_defaults` (default on) each loaded adapter's own `seed_files` are copied in from the main repo before the session launches; `worktree_seed` adds extra paths. Copy-when-absent, seeded before the hook-merge (a seeded `settings.json` keeps its content and just gains the Stop hook), and shielded from the unit's `git add -A`.
- Run state never moves into a worktree — `.bmad-loop/` always lives in the main repo; spec paths are persisted relative to the worktree so a kept-failed run stays portable.
- Merge-back is serialized; `max_parallel` is a validated knob clamped to `1` until parallel fan-out is built. The `repo_root` key in `_bmad/bmm/config.yaml` (defaults to the project dir) decouples where git/code work happens from where run state lives (monorepos).
- Merge-back is serialized; `max_parallel` is a validated knob clamped to `1` until parallel fan-out is built. The `repo_root` key in `_bmad/bmm/config.yaml` (defaults to the project dir) decouples where git/code work happens from where run state lives (monorepos) — but it is **mutually exclusive with `isolation = "worktree"`**, which seeds from `repo_root` while the preflight probes `project`; `validate` reports the pair and the run/sweep/resume preflight refuses it (#414).
- `commit_message_template` (`{story_key}` / `{run_id}` substituted) customizes story/bundle commit messages.

### Plugins (extensibility)
Expand Down
2 changes: 1 addition & 1 deletion module.yaml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
code: bmad-loop
name: BMAD Loop Skills
description: "Automation-mode skills driven by the bmad-loop orchestrator: interactive escalation resolution (bmad-loop-resolve) and deferred-work sweep triage (bmad-loop-sweep) — the inner dev primitive (which self-reviews and commits) is the upstream bmad-dev-auto skill"
module_version: 0.9.0
module_version: 0.9.1
default_selected: false
module_greeting: >
BMAD Loop installed — both the automation skills and the
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ build-backend = "hatchling.build"

[project]
name = "bmad-loop"
version = "0.9.0"
version = "0.9.1"
description = "Deterministic ralph-loop orchestrator for the BMAD implementation phase"
readme = "README.md"
license = "MIT"
Expand Down
2 changes: 1 addition & 1 deletion src/bmad_loop/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,4 @@
spec files, and the per-run directory under .bmad-loop/runs/.
"""

__version__ = "0.9.0"
__version__ = "0.9.1"
53 changes: 52 additions & 1 deletion src/bmad_loop/bmadconfig.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,50 @@ def rebase(p: Path) -> Path:
)


def worktree_isolation_conflict(paths: ProjectPaths, isolation: str) -> str | None:
"""The refusal message for ``isolation = "worktree"`` under a `repo_root`
override, or None when the combination is supported (#414).

Worktree provisioning reads ``repo_root`` for every surface it seeds *off disk*
— the upstream skill trees, `_bmad/` and the `_bmad/custom/` overrides inside
it, and each `seed_files`/`seed_globs` entry — and bakes the absolute hook-relay
path from it into the worktree's hook config, while `init`, `validate` and the
run preflight write and probe those same surfaces under ``project``. (The relay
itself is pointed at, never copied. The `MODULE_SKILLS` this wheel bundles are
seeded from package data and are unaffected by either root; nothing is seeded
from ``project``, which `provision_worktree` is never even passed.)
`load_paths` *requires* `project/_bmad/bmm/config.yaml`, so `_bmad/` is under
`project` by definition and `repo_root/_bmad/` generally does not exist. When
the two diverge the preflight therefore approves a surface the isolated run
never receives, and the seed-completeness gates go inert rather than fire: an
isolated session dispatches into a worktree with no dev primitive and no
renderer, and stops with no result and nothing journaled naming the cause.

Refusing is the whole fix here — the alternative (plumb `project` through
provisioning for the non-git reads) is a behaviour change this patch release
does not make, so the message names only remediations that exist today. Both
are named because either alone is sufficient and which one is right is the
operator's call: the override buys a decoupled git root, the isolation mode
buys per-unit worktrees, and 0.9.x cannot give both.

Sole producer of the text, shared by `cmd_validate`, the run/sweep preflight,
the dry-run honesty banner and the TUI's pre-launch guard, so the four cannot
drift. Compares resolved paths: `load_paths` resolves both sides, but a
hand-built :class:`ProjectPaths` (tests) need not have."""
if isolation != "worktree":
return None
if paths.repo_root.resolve() == paths.project.resolve():
return None
return (
'isolation = "worktree" is not supported when repo_root differs from the project '
f"directory: worktree provisioning seeds from repo_root ({paths.repo_root}) while "
f"init, validate and the run preflight read the project ({paths.project}), so an "
"isolated session would get none of the skills the preflight just approved. "
"Remove the `repo_root` key from _bmad/bmm/config.yaml, or set "
'`isolation = "none"` under [scm] in .bmad-loop/policy.toml.'
)


def _resolve(raw: str, project: Path) -> Path:
return Path(raw.replace("{project-root}", str(project))).resolve()

Expand All @@ -73,7 +117,14 @@ def load_paths(project: Path) -> ProjectPaths:
if not config_path.is_file():
raise BmadConfigError(f"BMAD config not found: {config_path} (is BMAD installed here?)")
try:
doc = yaml.safe_load(config_path.read_text(encoding="utf-8")) or {}
# UnicodeDecodeError is a ValueError, not an OSError, so an undecodable file
# would otherwise escape every caller's `except BmadConfigError` and crash
# them. Same reasoning as `policy.load`.
raw = config_path.read_text(encoding="utf-8")
except UnicodeDecodeError as e:
raise BmadConfigError(f"{config_path} is not valid UTF-8: {e}") from e
try:
doc = yaml.safe_load(raw) or {}
except yaml.YAMLError as e:
raise BmadConfigError(f"invalid YAML in {config_path}: {e}") from e

Expand Down
7 changes: 7 additions & 0 deletions src/bmad_loop/checks.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
"bmad-config",
"policy",
"policy.model-qualified",
"policy.isolation-repo-root",
"adapter.profile",
"adapter.binary",
"adapter.hookless",
Expand All @@ -48,6 +49,7 @@
"queue.stories-manifest",
"git.worktree-clean",
"git.probe",
"git.render-tracked",
"hooks.config-parse",
"hooks.registered",
"mux.backend",
Expand All @@ -59,6 +61,11 @@
"skills.base",
"skills.base-missing",
"skills.base-incomplete",
"skills.base-shim",
"skills.dev-renderer",
"skills.dev-renderer-config",
"skills.dev-renderer-sources",
"skills.customize-legacy",
"skills.stories-dispatch",
"skills.stories-dispatch-missing",
"skills.stories-dispatch-stale",
Expand Down
Loading