Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,8 @@ breaking changes may land in a minor release.

### Changed

- bmad-loop now warns when the legacy `review.on_status_contradiction = "retry"` mode is configured.

- **`bmad-loop diagnose --json` reports `schema_version: 4`.** Journal `path` values
become `path_present`; stale-restore and merge filename lists become counts.

Expand Down
2 changes: 1 addition & 1 deletion src/bmad_loop/data/settings/core.toml
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ kind = "select"
options_ref = "REVIEW_ON_STATUS_CONTRADICTION_MODES"
default_ref = "ReviewPolicy.on_status_contradiction"
label = "review revokes sprint sign-off"
description = "escalate: pause naming both sides when a review writes sprint-status back off done (default) · retry: legacy — burn review cycles, then defer + roll back"
description = "escalate: pause naming both sides when a review writes sprint-status back off done (default) · retry: legacy — burn review cycles, then defer + roll back; slated for removal in 0.12.0 (#813)"

[[section]]
name = "stories"
Expand Down
11 changes: 10 additions & 1 deletion src/bmad_loop/policy.py
Original file line number Diff line number Diff line change
Expand Up @@ -243,7 +243,8 @@ class ReviewPolicy:
# disagreement, so a human resolves it instead of the budget burning
# down onto a rollback.
# "retry" — legacy behavior: treat it as an ordinary verify failure, burn
# review cycles to limits.max_review_cycles, then defer.
# review cycles to limits.max_review_cycles, then defer. Slated for
# removal in 0.12.0 (#813); loading it now emits a DeprecationWarning.
# Keys on sprint-status only: the spec's own frontmatter status legitimately
# cycles (in-review/in-progress) while a review patches, and `status: blocked`
# remains the sanctioned way for a review to hand a story back to a human.
Expand Down Expand Up @@ -984,6 +985,14 @@ def loads(text: str, plugin_schemas: dict[str, Any] | None = None) -> Policy:
f"{sorted(REVIEW_ON_STATUS_CONTRADICTION_MODES)}:"
f" got {review.on_status_contradiction!r}"
)
if review.on_status_contradiction == "retry":
warnings.warn(
'review.on_status_contradiction = "retry" is legacy (superseded by '
'"escalate" per #334) and will be removed in 0.12.0 (#813). Switch to '
'"escalate" (the default).',
DeprecationWarning,
stacklevel=3,
)
stories = StoriesPolicy(
source=_typed_str(stories_d, "stories", "source", StoriesPolicy.source).strip(),
spec_folder=_typed_str(
Expand Down
13 changes: 13 additions & 0 deletions tests/test_policy.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import json
import re
import sys
import warnings

import pytest

Expand Down Expand Up @@ -71,6 +72,18 @@ def test_review_on_status_contradiction_invalid():
policy.loads('[review]\non_status_contradiction = "defer"\n')


def test_review_on_status_contradiction_retry_warns_escalate_does_not():
# "retry" is legacy (superseded by "escalate" per #334) and slated for
# removal in 0.12.0 (#813); loading it must warn. The "escalate" default
# must stay silent.
with pytest.warns(DeprecationWarning, match="retry"):
policy.loads('[review]\non_status_contradiction = "retry"\n')
with warnings.catch_warnings():
warnings.simplefilter("error")
policy.loads('[review]\non_status_contradiction = "escalate"\n')
policy.loads("")


def test_stories_defaults():
pol = policy.loads("")
assert pol.stories.source == "sprint-status"
Expand Down