fix(verify): baseline-untracked subtraction + skill's baseline key in the shared gate#105
Conversation
…baseline key Two independent verify-gate bugs found by the PR #86 final review, both living in the same shared gate block. #88: has_changes_since counted every untracked file, so an intent-gap halt's saved patch — untracked residue the resets deliberately protect — let a from-scratch re-drive that produced nothing but a spec status flip pass the proof-of-work gate on the file's mere presence. The gate now passes task.baseline_untracked and subtracts it. A None snapshot still counts every untracked file: the opposite of attempt_dirty's ignore-all, because a proof-of-work gate fails open toward "work happened" while a rollback gate fails open toward "nothing to remove". Docstring pins the asymmetry. #89: the baseline-match gate read the spec's baseline_commit and skipped itself when the key was absent, but bmad-dev-auto stamps baseline_revision — so the check never fired in production and a spec claiming a foreign baseline sailed through. It now reads either key, the idiom devcontract already used. conftest's write_spec stamps baseline_revision like the real skill, which makes the reader load-bearing across all its call sites: the two pre-existing lying-baseline tests fail without the fix, where before they passed vacuously. Closes #88, Closes #89
|
Warning Review limit reached
Next review available in: 44 minutes Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (1)
WalkthroughModified ChangesBaseline verification gate fixes
Estimated code review effort: 3 (Moderate) | ~20 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
🧹 Nitpick comments (1)
src/bmad_loop/verify.py (1)
1116-1122: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueError message references
baseline_commiteven when value came frombaseline_revision.Line 1126 hardcodes
spec baseline_commitin the retry message, but the value may have been read frombaseline_revision. This is a minor cosmetic inconsistency that could confuse debugging when the gate fires for a generic-skill session.♻️ Proposed fix
- return VerifyOutcome.retry( - f"spec baseline_commit {claimed_baseline[:12]} does not match " - f"orchestrator-recorded baseline {task.baseline_commit[:12]}" - ) + return VerifyOutcome.retry( + f"spec baseline {claimed_baseline[:12]} does not match " + f"orchestrator-recorded baseline {task.baseline_commit[:12]}" - ) + )🤖 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/bmad_loop/verify.py` around lines 1116 - 1122, The retry/error message in the baseline gate is hardcoded to reference baseline_commit even when the value was sourced from baseline_revision. Update the message in verify.py near claimed_baseline handling so it reflects the actual frontmatter key used, matching the same fallback logic as devcontract.synthesize_result. Keep the wording consistent with whichever key was read, or make the message generic enough to cover both cases.
🤖 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 `@src/bmad_loop/verify.py`:
- Around line 1116-1122: The retry/error message in the baseline gate is
hardcoded to reference baseline_commit even when the value was sourced from
baseline_revision. Update the message in verify.py near claimed_baseline
handling so it reflects the actual frontmatter key used, matching the same
fallback logic as devcontract.synthesize_result. Keep the wording consistent
with whichever key was read, or make the message generic enough to cover both
cases.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 98a99acd-f58c-4102-8f84-53929e183e56
📒 Files selected for processing (4)
CHANGELOG.mdsrc/bmad_loop/verify.pytests/conftest.pytests/test_verify.py
|
Addressed the CodeRabbit nitpick in 86972e9: the baseline-mismatch retry message now says |
Closes #88, Closes #89
Both bugs were surfaced by the PR #86 final review (the intent-gap/patch-restore audit), where each was adversarially verified against
mainand filed rather than folded into that PR — they are pre-existing, not regressions from #86. They land together because both fixes are in the same ~15-line block:_verify_shared_gates, the workflow-tag / expected-status / baseline-match / proof-of-work gates shared verbatim byverify_dev,verify_dev_bundleandverify_dev_stories.#88 — the proof-of-work gate now subtracts the baseline snapshot
has_changes_sincecounted every untracked file and never subtractedbaseline_untracked(contrastattempt_dirtydirectly below it, which does). After any intent-gap halt the saved patch is untracked residue underimplementation_artifacts, which_protected_relpathsshields from every reset. #86's T4 fix excludes the latched patch by path, but a from-scratch re-arm (restore_patch=None— the common "the attempted reading was wrong" case) has no latch and never learns the path. A re-driven session that produced zero code changes but flipped the spec status passed the gate on the patch file's mere presence, andfinalize_commit'sadd -Athen swept it into the story commit.has_changes_sincegains a keyword-onlybaseline_untracked, subtracted when non-None; the sole production caller passestask.baseline_untracked. Both snapshot writers (re-arm inruns.py, dispatch-time re-capture inengine.py) run while the patch is on disk, so it lands in the snapshot and stops counting.Nonedeliberately keeps counting all untracked files — the opposite ofattempt_dirty'sNone= ignore-all. Pinned in the docstring, because it reads like an inconsistency and isn't: the two gates fail open in opposite directions. A proof-of-work gate must fail open toward "work happened" (a pre-snapshot run must not have its gate silently weakened into never seeing new files); a rollback gate must fail open toward "nothing to remove" (never delete a file it cannot prove this attempt created).#89 — the baseline-match gate reads the key the skill actually writes
The gate read
fm.get("baseline_commit", "")and skips the whole check when the key is absent. The genericbmad-dev-autoskill's step-03 stampsbaseline_revision;baseline_commitexists only in theresult.jsondevcontractsynthesizes, which the gate does not consult (it re-reads frontmatter directly). Net: in production the baseline-match gate never fired for generic-skill sessions, and a spec claiming a stale or foreign baseline sailed through.The gate now reads both keys via the exact idiom already in
devcontract.synthesize_result. It composes with #86's review-F2 re-stamp (re-arm rewritesbaseline_revision), so patch-restore re-drives stay consistent by construction.Fixture honesty. The bug was masked because
conftest.write_specemittedbaseline_commit:— fabricating precisely the key the real skill never writes, which made the gate look alive under test.write_specnow stampsbaseline_revisionlike the skill, and_spec_baselinereads either key. This is the load-bearing part: with the fixture honest but the reader unfixed, the two pre-existing teststest_verify_dev_lying_baselineandtest_verify_dev_stories_lying_baselinefail — they had been passing vacuously. No other suite breakage surfaced, so no spec stamps both keys.Tests
has_changes_since: snapshot subtraction,Nonecounts everything, post-snapshot file counts, tracked edit counts regardless of snapshot.retry("no changes…")with no latch in play;baseline_untracked=None→ still passes (the fallback pinned); real work still passes with the snapshot in place.baseline_revisionfires the gate (zero coverage before — this was the masked path); a matching one passes.Mutation-checked: reverting either source fix fails exactly the corresponding new tests (plus the two now-honest pre-existing ones).
Verification
.venv/bin/python -m pytest— 1930 passed, 1 skipped. Fulltrunk check(no filter) clean.Follow-up ordering note
Per the bundling plan, #91(d) must land after this: it rewrites the exact
has_changes_sincecall this PR modifies (renamingproof_exclude→extra_excludeand deriving the restore-patch exclusion inside the gate). Thebaseline_untracked=task.baseline_untrackedargument added here has to survive that refactor verbatim.Summary by CodeRabbit
Bug Fixes
Documentation