Skip to content

Release 0.7.8 — reconcile stale spec frontmatter status (no more false defers)#25

Merged
pbean merged 5 commits into
mainfrom
release/0.7.8
Jun 30, 2026
Merged

Release 0.7.8 — reconcile stale spec frontmatter status (no more false defers)#25
pbean merged 5 commits into
mainfrom
release/0.7.8

Conversation

@pbean

@pbean pbean commented Jun 29, 2026

Copy link
Copy Markdown
Collaborator

Summary

Fixes completed bmad-dev-auto work being falsely deferred when the skill leaves the spec
frontmatter status behind its prose.

The decoupled bmad-dev-auto skill can finalize a run in its ## Auto Run Result prose
(Status: done) yet leave the YAML frontmatter at the template default draft. Every orchestrator
gate reads frontmatter, so _post_dev_state_sync no-op'd (sprint/ledger never advanced) and the
story or sweep bundle deferred — and with rollback_on_failure on, the tested work was reverted and
lost. (Observed in the wild: a fully-implemented, 1442-test-green deferred-work bundle deferred with
spec status is 'draft', expected 'done'.)

Fix (Approach: reconcile, not escalate)

engine._reconcile_generic_terminal_status runs in the shared Engine._dev_phase immediately
before _post_dev_state_sync, so it covers both the story (sprint) and sweep-bundle (ledger)
paths. When the terminal ## Auto Run Result Status is done and the frontmatter is at a
non-terminal status (devcontract.RECONCILABLE_FROM: ""/draft/ready-for-dev/in-progress),
it advances the frontmatter to the success status via devcontract.reset_spec_status and journals
spec-status-reconciled.

This does not violate the "never trust prose for a gate" doctrine. Reconcile repairs a
bookkeeping field only; every deterministic gate still runs afterward against real on-disk/git state:

  • has_changes_since(baseline) — real worktree diff
  • dw-id cross-check (bundles)
  • the verify-command suite
  • the on-disk ledger check (review-bundle)

So a spec that lied (no diff / wrong dw-ids / broken build) still fails its gate. blocked is never
reconciled (guard: prose must be done); an already-terminal or unknown frontmatter status is never
overridden (idempotent, never-regress).

The companion upstream skill fix is bmad-code-org/BMAD-METHOD#2536 (make the Finalize step write
frontmatter status explicitly); this orchestrator change is the durable backstop regardless of
skill version.

Tests

  • test_devcontract.pyRECONCILABLE_FROM guard + reset-from-each-reconcilable-value
  • test_engine.py — advance / idempotent-when-already-done / skip-blocked-prose / does-not-bypass-the-no-change-gate (story path)
  • test_sweep.py — the exact DW-bundle regression: stale-frontmatter bundle now closes the ledger

Full suite 1194 green, trunk check clean. Version bumped 0.7.7 → 0.7.8 (sync_version), CHANGELOG updated.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Bug Fixes
    • Fixed completed work sometimes staying deferred when spec frontmatter status was stale, by reconciling frontmatter status from the terminal auto-run result.
    • Correctly handles empty/null frontmatter status, preserves inline comments/formatting on updates, and avoids reconciliation for blocked, already-final, unknown, missing, or out-of-tree specs.
    • Verification now ignores orchestrator-generated artifact directories when checking for meaningful changes.
  • Chores
    • Bumped release/version metadata to 0.7.8 (including marketplace/module packaging).
  • Tests
    • Expanded coverage for the reconciliation behavior, YAML edge cases, out-of-tree skips, and updated verification gating.

….7.8

bmad-dev-auto can finalize a run in its `## Auto Run Result` prose
(Status: done) but leave the spec frontmatter at the template default
`draft`. Every gate reads frontmatter, so the sprint/ledger sync no-op'd
and the story or sweep bundle falsely deferred — losing tested work on
rollback. The orchestrator now reconciles the frontmatter to the success
status from the terminal prose in _dev_phase before the gates run
(_reconcile_generic_terminal_status, journaled spec-status-reconciled),
covering both the story (sprint) and bundle (ledger) paths. Only a `done`
outcome from a non-terminal status is reconciled, and every deterministic
gate (worktree diff, dw-ids, verify commands, ledger) still runs — so prose
never passes a gate.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jun 29, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: e86be356-b171-4360-9afd-1bbe0022a854

📥 Commits

Reviewing files that changed from the base of the PR and between 34e6927 and d0e4d57.

📒 Files selected for processing (1)
  • tests/test_verify.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • tests/test_verify.py

Walkthrough

Adds generic-dev reconciliation for stale frontmatter when prose reports Status: done, expands fixture and verification helpers for prose-finalized specs, adds regression coverage, and bumps package, module, plugin, and changelog versions to 0.7.8.

Changes

Frontmatter reconciliation feature

Layer / File(s) Summary
Reconciliation contract and status reset
src/automator/devcontract.py
Adds the reconcilable frontmatter allowlist, accepts blank status: values, and inserts a missing status: line when resetting spec status.
Artifact path scoping
src/automator/engine.py, src/automator/verify.py
Excludes repo-root . and out-of-tree paths from protected and artifact-relative checks, and adds helpers for artifact exclude prefixes and spec-root validation.
Engine reconciliation before gates
src/automator/engine.py
_dev_phase calls _reconcile_generic_terminal_status before downstream sync and verification; the helper repairs reconcilable frontmatter from terminal prose, updates result_json, and journals spec-status-reconciled.
Prose-finalized spec fixtures
tests/conftest.py
write_spec, dev_effect, and bundle_dev_effect can generate specs with prose terminal status while controlling frontmatter status separately.
Reconciliation tests
tests/test_devcontract.py, tests/test_engine.py, tests/test_sweep.py, tests/test_verify.py
Adds coverage for allowlist contents, status rewriting, blocked and idempotent cases, no-change gating, bundle ledger closure, and artifact-aware change detection.

Version bump to 0.7.8

Layer / File(s) Summary
Release version updates
.claude-plugin/marketplace.json, module.yaml, src/automator/data/skills/bmad-auto-setup/assets/module.yaml, pyproject.toml, src/automator/__init__.py, CHANGELOG.md
Updates version declarations to 0.7.8 and adds the changelog note for the reconciliation fix.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

  • bmad-code-org/bmad-auto#13 — Also changes orchestrator-owned artifact handling and dirty-check logic in src/automator/engine.py and related verification paths.

Poem

🐇 I nibbled a draft, then read the sign,
“Status: done!” — the stars aligned.
The frontmatter hopped to keep things true,
While gates and ledgers carried through.
🥕✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 73.47% 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
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly matches the main change: releasing 0.7.8 to reconcile stale spec frontmatter status and prevent false defers.
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
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch release/0.7.8

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.

@augmentcode

augmentcode Bot commented Jun 29, 2026

Copy link
Copy Markdown
🤖 Augment PR Summary

Summary: This PR releases 0.7.8 and fixes a real-world false-deferral in the generic bmad-dev-auto flow where the session can finalize in prose (## Auto Run ResultStatus: done) but leave spec frontmatter status non-terminal.

Changes:

  • Adds an allowlist (devcontract.RECONCILABLE_FROM) of non-terminal statuses that are safe to reconcile from.
  • Extends devcontract.reset_spec_status() to handle empty/null status: values, preserve inline comments, and insert a missing status: line when absent.
  • Introduces Engine._reconcile_generic_terminal_status() to repair frontmatter status before sprint/ledger sync and verification gates run, journaling the action.
  • Adds safety checks to avoid mutating out-of-tree spec_file paths (verify.spec_within_roots).
  • Updates proof-of-work gating to ignore orchestrator-owned artifact folders via verify.has_changes_since(..., exclude=...) and verify.artifact_relpaths().
  • Hardens artifact exclusion logic to drop "." (repo root) so misconfiguration can’t disable dirty/change detection.
  • Adds regression tests covering reconciliation behavior, YAML edge cases, out-of-tree protection, and the updated no-change gate behavior.
  • Bumps version metadata and updates CHANGELOG.md.

Technical Notes: Reconciliation is explicitly bookkeeping-only (allowlisted, never overrides blocked/terminal/unknown states) and all deterministic gates (diff, dw-id checks, verify commands, ledger checks) still run afterward.

🤖 Was this summary useful? React with 👍 or 👎

@augmentcode augmentcode 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.

Review completed. 1 suggestion posted.

Fix All in Augment

Comment augment review to trigger a new review at any time.

# allowlist: anything else (already-terminal done/in-review, blocked, or an
# unknown custom token) is left untouched, so reconciliation can never override a
# status the skill set on purpose.
RECONCILABLE_FROM = frozenset({"", "draft", "ready-for-dev", "in-progress"})

@augmentcode augmentcode Bot Jun 29, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

RECONCILABLE_FROM includes the empty string (""), but reset_spec_status()’s _FM_STATUS_RE requires a non-empty [A-Za-z-]+ value, so a spec with a missing/empty status: line won’t actually be reconciled (silent no-op). If "" is intended to cover that case, reconciliation can’t currently perform the rewrite.

Severity: low

Fix This in Augment

🤖 Was this useful? React with 👍 or 👎, or 🚀 if it prevented an incident/outage.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Confirmed and fixed in 61154ed. Rather than drop "" from the allowlist, we made it actionable:

  • _FM_STATUS_RE's value token was relaxed [A-Za-z-]+[A-Za-z-]*, so a present-but-empty status: (e.g. status: / status: "") is now matched and filled.
  • reset_spec_status now inserts a status: line before the closing fence when the frontmatter has none (line-ending-aware), so a missing status is reconciled too.

Added test_reset_status_fills_empty_value and test_reset_status_inserts_missing_line to cover both. Thanks for spotting the silent no-op.

@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

🤖 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/automator/engine.py`:
- Around line 1041-1046: The reconciliation in the engine is making
orchestrator-owned spec rewrites count as proof-of-work, so the verify/no-change
gate can pass on bookkeeping-only edits. Update the change-detection path used
around the _reconcile_generic_terminal_status flow to ignore protected artifact
locations like _bmad-output, reusing the same protected-path logic as
_protected_relpaths() and rollback so reconciliation-only edits never register
as real work. Ensure the no-change check and related verification gates exclude
these orchestrator-managed paths before deciding whether the session has
meaningful changes.
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: b60b8082-a6b0-4a44-a31a-005fa2f6ef70

📥 Commits

Reviewing files that changed from the base of the PR and between ec715d4 and 71de70b.

⛔ Files ignored due to path filters (1)
  • uv.lock is excluded by !**/*.lock
📒 Files selected for processing (12)
  • .claude-plugin/marketplace.json
  • CHANGELOG.md
  • module.yaml
  • pyproject.toml
  • src/automator/__init__.py
  • src/automator/data/skills/bmad-auto-setup/assets/module.yaml
  • src/automator/devcontract.py
  • src/automator/engine.py
  • tests/conftest.py
  • tests/test_devcontract.py
  • tests/test_engine.py
  • tests/test_sweep.py

Comment thread src/automator/engine.py
Address PR #25 automated-reviewer findings.

CodeRabbit (major): _reconcile_generic_terminal_status rewrites the spec
under _bmad-output before the verify gate. `bmad-auto init` does not
gitignore _bmad-output, so the skill's spec authorship + the reconcile
rewrite registered as a git change — and since reconcile flips the
frontmatter to the success status, the status backstop no longer caught
empty sessions, letting no-code work pass `has_changes_since`.

- has_changes_since gains an `exclude` param (mirrors attempt_dirty);
  new verify.artifact_relpaths() returns the orchestrator-owned artifact
  folders (same set as Engine._protected_relpaths). verify_dev and
  verify_dev_bundle pass it so only real source edits count as proof.
- Rewrite test_generic_reconcile_does_not_bypass_no_change_gate to drop
  the _bmad-output gitignore hack (and the false #2522 premise); the gate
  now defers spec-only work on a real tracked tree.

Augment (low): "" in RECONCILABLE_FROM was a no-op — reset_spec_status
could not rewrite a blank/missing status. Make it actionable instead:
relax _FM_STATUS_RE to fill an empty value, and insert a status: line
when the frontmatter has none. Adds devcontract coverage.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@pbean

pbean commented Jun 29, 2026

Copy link
Copy Markdown
Collaborator Author

augment review

@augmentcode augmentcode 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.

Review completed. 2 suggestions posted.

Fix All in Augment

Comment augment review to trigger a new review at any time.

Comment thread src/automator/engine.py Outdated
if not spec_path.is_file():
return
success_status = "in-review" if self._dev_review_enabled() else "done"
fm_status = verify.status_of(verify.read_frontmatter(spec_path))

@augmentcode augmentcode Bot Jun 29, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

verify.status_of() will return the string "none" when frontmatter contains a bare status: with no value (YAML null), so this reconciliation won’t run even though RECONCILABLE_FROM is trying to cover blank statuses. Consider whether you want to treat YAML-null as equivalent to empty/missing here to avoid silent non-reconcile on that input.

Severity: medium

Fix This in Augment

🤖 Was this useful? React with 👍 or 👎, or 🚀 if it prevented an incident/outage.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Confirmed and fixed in 5de2aa1. You're right — a bare status: / status: null parses to YAML null, and verify.status_of stringifies that to "none" (via str(None)), which isn't in RECONCILABLE_FROM, so the most common blank form was silently skipped.

_reconcile_generic_terminal_status now reads the frontmatter once and normalizes a YAML-null/missing status to "" so it matches the allowlist member (kept status_of untouched — it's a shared gate chokepoint and "none" vs "" is immaterial to the other gates). A literal status: none string stays "none" and is still left alone.

Fixing this also surfaced a corruption bug in the round-1 fill code: a bare status: (no trailing space) filled to status:done — invalid YAML, the key drops on re-parse. reset_spec_status now guarantees key: value spacing. Added an engine test for the null-status reconcile and a devcontract test asserting the filled line re-parses cleanly. Thanks — good catch (medium was right; it had teeth).

Comment thread src/automator/verify.py Outdated
paths.planning_artifacts,
):
try:
out.append(folder.relative_to(paths.project).as_posix())

@augmentcode augmentcode Bot Jun 29, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

If any artifact folder is configured to equal paths.project, relative_to(...).as_posix() will yield ".", and passing that into the exclude list can effectively exclude the whole repo from the proof-of-work diff. Consider filtering out "."/empty prefixes in artifact_relpaths() so a misconfiguration can’t disable change detection.

Severity: medium

Fix This in Augment

🤖 Was this useful? React with 👍 or 👎, or 🚀 if it prevented an incident/outage.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Good defensive catch — fixed in 5de2aa1. If an artifact folder is configured to equal paths.project, relative_to(...).as_posix() yields ".", and :(exclude). would exclude the whole tree, disabling the proof-of-work gate.

artifact_relpaths now drops empty/"." results. I applied the same filter to the pre-existing Engine._protected_relpaths (it shares the identical pattern and the same latent gap) so the two stay in sync. Added verify tests covering the "." guard and the artifact-only-edit exclusion.

…nst "."

Address PR #25 round-2 reviewer findings (Augment, both medium).

YAML-null status never reconciled: a bare `status:` / `status: null` reads
as the string "none" through verify.status_of (str(None)), which dodges the
RECONCILABLE_FROM allowlist, so the most common "blank" template form was
never reconciled. _reconcile_generic_terminal_status now normalizes a
YAML-null/missing status to "" (a literal `status: none` string stays "none"
and is still left alone).

Routing null/bare into reset_spec_status surfaced a corruption bug: filling a
bare `status:` (no trailing space) produced `status:done` — invalid YAML, the
key is lost on re-parse. _repl now guarantees `key: value` spacing.

Whole-tree exclude guard: artifact_relpaths (and, for parity,
Engine._protected_relpaths) dropped a "." result that arises when an artifact
folder is configured == the project/repo root, which as an exclude/keep prefix
would disable change detection / the dirty check.

Adds coverage: bare-null reconcile (engine), bare `status:` validity
(devcontract), artifact_relpaths "." guard + artifact-only-edit exclusion
(verify).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@pbean

pbean commented Jun 29, 2026

Copy link
Copy Markdown
Collaborator Author

augment review

@augmentcode augmentcode 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.

Review completed. 2 suggestions posted.

Fix All in Augment

Comment augment review to trigger a new review at any time.

Comment thread src/automator/devcontract.py Outdated
pre = m.group("pre")
if not pre.endswith((" ", "\t")):
pre += " "
return f"{pre}{m.group('q')}{new_status}{m.group('q')}{m.group('rest')}"

@augmentcode augmentcode Bot Jun 29, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

When the original frontmatter line is comment-only (e.g. status: # intentionally blank), this rewrite will produce status: done# ..., which changes the inline comment into part of the scalar and can break/alter YAML parsing. Consider ensuring there’s always whitespace between the inserted value and rest when rest begins with #.

Severity: medium

Fix This in Augment

🤖 Was this useful? React with 👍 or 👎, or 🚀 if it prevented an incident/outage.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Confirmed and fixed in 34e6927. A blank value with a trailing inline comment (status: # tbd, which YAML parses as null, so the round-2 normalization routes it into the fill path) abutted the value to the comment — status: done# tbd — and since a # with no preceding space isn't a YAML comment, the value became the whole done# tbd scalar.

reset_spec_status._repl now re-inserts a separating space when rest begins with #. Verified across the cases:

status: # intentionally blank -> status: done # intentionally blank   reparsed=done
status:   # tbd               -> status:   done # tbd                 reparsed=done
status: 'x' # keep                -> status: 'done' # keep                reparsed=done

Added a devcontract test asserting the comment survives and the frontmatter re-parses to done. Thanks!

Comment thread src/automator/engine.py
spec_file = (result_json or {}).get("spec_file")
if not spec_file:
return
spec_path = verify.resolve_spec_path(str(spec_file), self.workspace.paths)

@augmentcode augmentcode Bot Jun 29, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

spec_path can be an absolute (or otherwise out-of-repo) path via resolve_spec_path(), and this reconciliation then writes to it via reset_spec_status(). Consider guarding that the resolved spec path is within the project/artifact roots before mutating, to avoid unintended edits if a session reports a surprising spec_file.

Severity: medium

Fix This in Augment

🤖 Was this useful? React with 👍 or 👎, or 🚀 if it prevented an incident/outage.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Good defensive catch — fixed in 34e6927. resolve_spec_path returns an absolute spec_file as-is with no containment check, and reconcile is the one path that mutates a file keyed off the session-supplied path.

Added verify.spec_within_roots(spec_path, paths) (mirrors the is_relative_to idiom already at verify.py:265) and gated the reconcile write on it: the spec must equal or sit under the project root or an artifact root (output_folder / implementation_artifacts / planning_artifacts — so a legitimately out-of-project artifact dir is still allowed). A spec outside those roots is left untouched and journaled as spec-reconcile-skipped-out-of-tree. The normal generic flow is unaffected — find_result_artifact globs implementation_artifacts, so spec_file is always an absolute path under that root. Added verify + engine tests for the skip path.

…f-tree spec

Address PR #25 round-3 reviewer findings (Augment, both medium).

Blank status + inline comment: filling a blank `status: # tbd` (parsed as
YAML-null) abutted the value to the comment -> `status: done# tbd`, where the
`#` is no longer a comment and the value becomes the whole `done# tbd` scalar.
reset_spec_status._repl now re-inserts a separating space when `rest` begins
with `#`, so the result stays valid YAML and the comment survives.

Out-of-tree spec write: _reconcile_generic_terminal_status mutates a path taken
from the session's result_json. resolve_spec_path returns an absolute path
as-is with no containment check. New verify.spec_within_roots gates the
reconcile write to the project/artifact roots (out-of-project artifact dirs
still allowed); a surprising spec_file is left untouched and journaled as
spec-reconcile-skipped-out-of-tree.

Adds coverage: comment-preserving fill (devcontract), spec_within_roots
(verify), out-of-tree reconcile skip (engine).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

@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.

🧹 Nitpick comments (2)
tests/test_verify.py (2)

749-757: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add the out-of-project artifact-root case.

spec_within_roots() is supposed to allow specs under artifact dirs even when those dirs live outside project, but this test only covers roots nested inside project. A future simplification to project-only containment would still pass here.

Suggested test tweak
 def test_spec_within_roots(project, tmp_path):
     """Specs under the project / artifact roots are writable; an out-of-tree
     absolute path is refused (guards the reconcile mutation)."""
     assert verify.spec_within_roots(project.implementation_artifacts / "spec-x.md", project)
     assert verify.spec_within_roots(project.project / "anywhere.md", project)
     assert verify.spec_within_roots(project.output_folder, project)  # the root itself
+    import dataclasses
+
+    external_impl = tmp_path / "external-artifacts"
+    external_impl.mkdir()
+    external_paths = dataclasses.replace(project, implementation_artifacts=external_impl)
+    assert verify.spec_within_roots(external_impl / "spec-x.md", external_paths)
     outside = tmp_path / "outside" / "spec.md"
🤖 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 `@tests/test_verify.py` around lines 749 - 757, The current test only covers
roots nested inside project, so it misses the artifact-root scenario that should
still be allowed. Update test_spec_within_roots in tests/test_verify.py to add a
case using an artifact root path outside project and assert
verify.spec_within_roots accepts a spec under that root; keep the existing
checks for project-contained paths and out-of-tree absolute paths.

728-746: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Exercise a root-level _bmad-output edit in this regression.

This only proves exclusion for implementation_artifacts. If artifact_relpaths() accidentally stops returning paths.output_folder, this test still passes while bundle/ledger files directly under _bmad-output start tripping the no-changes gate.

Suggested test tweak
 def test_has_changes_since_excludes_artifact_only_edit(project):
     """A change confined to the artifact folders is not proof of dev work."""
     baseline = verify.rev_parse_head(project.project)
+    (project.output_folder / "ledger.json").write_text("bookkeeping\n")
     (project.implementation_artifacts / "spec-x.md").write_text("bookkeeping\n")
     assert verify.has_changes_since(project.project, baseline) is True  # unscoped
🤖 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 `@tests/test_verify.py` around lines 728 - 746, Extend the has_changes_since
regression in test_has_changes_since_excludes_artifact_only_edit to also cover a
direct root-level _bmad-output edit, not just implementation_artifacts. Use
artifact_relpaths(project) and the existing verify.has_changes_since /
verify.rev_parse_head flow, but add a write under project.paths.output_folder
(or equivalent root output folder symbol) so the test proves artifact_relpaths()
still excludes paths.output_folder and bundle/ledger files there do not count as
changes.
🤖 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.

Nitpick comments:
In `@tests/test_verify.py`:
- Around line 749-757: The current test only covers roots nested inside project,
so it misses the artifact-root scenario that should still be allowed. Update
test_spec_within_roots in tests/test_verify.py to add a case using an artifact
root path outside project and assert verify.spec_within_roots accepts a spec
under that root; keep the existing checks for project-contained paths and
out-of-tree absolute paths.
- Around line 728-746: Extend the has_changes_since regression in
test_has_changes_since_excludes_artifact_only_edit to also cover a direct
root-level _bmad-output edit, not just implementation_artifacts. Use
artifact_relpaths(project) and the existing verify.has_changes_since /
verify.rev_parse_head flow, but add a write under project.paths.output_folder
(or equivalent root output folder symbol) so the test proves artifact_relpaths()
still excludes paths.output_folder and bundle/ledger files there do not count as
changes.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: bff08987-b190-42a4-817c-9876712c93b3

📥 Commits

Reviewing files that changed from the base of the PR and between 61154ed and 34e6927.

📒 Files selected for processing (6)
  • src/automator/devcontract.py
  • src/automator/engine.py
  • src/automator/verify.py
  • tests/test_devcontract.py
  • tests/test_engine.py
  • tests/test_verify.py
🚧 Files skipped from review as they are similar to previous changes (3)
  • src/automator/devcontract.py
  • src/automator/verify.py
  • src/automator/engine.py

@pbean

pbean commented Jun 29, 2026

Copy link
Copy Markdown
Collaborator Author

augment review

@augmentcode augmentcode 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.

Review completed. No suggestions at this time.

Comment augment review to trigger a new review at any time.

…utput exclude

Harden two regressions per CodeRabbit nitpicks on PR #25:
- test_spec_within_roots: assert an artifact root configured OUTSIDE project
  is still accepted, guarding against a project-only containment regression.
- test_has_changes_since_excludes_artifact_only_edit: also write directly under
  _bmad-output, proving artifact_relpaths covers output_folder (not just the
  impl subdir) so bundle/ledger edits never trip the no-change gate.

Promote dataclasses to a module-level import.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@pbean

pbean commented Jun 30, 2026

Copy link
Copy Markdown
Collaborator Author

@coderabbitai both nitpicks addressed in d0e4d57 (test-only).

test_spec_within_roots — added the out-of-project artifact-root case: dataclasses.replace(project, implementation_artifacts=<dir outside project>), then asserting a spec under that external root is still accepted. Guards against a future simplification to project-only containment, which spec_within_roots deliberately does not do.

test_has_changes_since_excludes_artifact_only_edit — now also writes directly under _bmad-output (output_folder / "ledger.json") alongside the existing impl-artifact edit. The exclude=artifact_relpaths(...) assertion now proves the output_folder prefix is in the exclude set, not just the impl subdir — so root-level bundle/ledger edits can't start tripping the no-change gate unnoticed.

Full suite green (1204 passed), trunk clean. Thanks for the catches.

@coderabbitai

coderabbitai Bot commented Jun 30, 2026

Copy link
Copy Markdown

Tip

For best results, initiate chat on the files or code changes.

@pbean Thanks — that addresses both nits cleanly.

The added coverage around out-of-project artifact roots in test_spec_within_roots and the root-level _bmad-output/ledger.json exclusion check in test_has_changes_since_excludes_artifact_only_edit closes the gaps I was worried about. Appreciate the follow-up and the extra regression protection.

🐇✨


If you found this review helpful, would you consider giving us a shout-out on X?

Thank you for using CodeRabbit!

@pbean pbean merged commit ef7f77b into main Jun 30, 2026
7 checks passed
@pbean pbean deleted the release/0.7.8 branch June 30, 2026 00:49
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