Skip to content

fix(sprint-compare): resolve descriptive slug keys via existing resolver#36

Open
Pawel-N-pl wants to merge 3 commits into
mainfrom
fix/sprint-compare-key-format
Open

fix(sprint-compare): resolve descriptive slug keys via existing resolver#36
Pawel-N-pl wants to merge 3 commits into
mainfrom
fix/sprint-compare-key-format

Conversation

@Pawel-N-pl

@Pawel-N-pl Pawel-N-pl commented Jun 6, 2026

Copy link
Copy Markdown

What

sprint-compare flagged already-done stories as incomplete on Resume
(step-01b-continue), firing a spurious Batch/Skip prompt.

Why

cmd_sprint_compare matched dotted IDs (1.1) with a literal ^\s*1.1: regex,
but sprint-status.yaml uses descriptive slug keys (1-1-host-feasibility-probe:,
the BMAD default). No line starts with 1.1:, so every "before currentStory"
story was flagged — a pure false positive (no state corruption).

Fix

Route the check through the existing key resolver in core/sprint.py instead of a
duplicated, weaker regex. New sprint_status_done_in_text reuses
_exact_status_match / _status_rows / _status_key_matches_story — single
source of truth. Completes the downstream-helper migration from #23, which fixed
sprint-status get, sprint_status_epic, check-epic-complete and
get-epic-stories but did not touch cmd_sprint_compare.

Tests

tests/test_sprint_compare.py — dotted/dashed/slug resolution, true negatives,
and an end-to-end cmd_sprint_compare regression (slug-keyed status + dotted
range ⇒ incomplete == []).

Out of scope: an unrelated derive-project-slug degradation is tracked in #35.

Summary by CodeRabbit

  • Improvements

    • More robust detection of story completion in sprint comparisons, improving accuracy when resolving different story identifier formats.
  • Tests

    • Added comprehensive unit and end-to-end tests covering identifier-resolution edge cases and regression scenarios for sprint comparison outputs.

@Pawel-N-pl Pawel-N-pl requested a review from bma-d as a code owner June 6, 2026 22:44
@coderabbitai

coderabbitai Bot commented Jun 6, 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: 2ce3f358-2de2-441a-bac6-a6b84c8dac2e

📥 Commits

Reviewing files that changed from the base of the PR and between e9da7a2 and 00fff64.

📒 Files selected for processing (3)
  • skills/bmad-story-automator/src/story_automator/commands/state.py
  • skills/bmad-story-automator/src/story_automator/core/sprint.py
  • tests/test_sprint_compare.py
🚧 Files skipped from review as they are similar to previous changes (3)
  • skills/bmad-story-automator/src/story_automator/commands/state.py
  • skills/bmad-story-automator/src/story_automator/core/sprint.py
  • tests/test_sprint_compare.py

Walkthrough

This PR extracts sprint-status parsing into sprint_status_done_in_text(content, story_id, project_root) and uses it in cmd_sprint_compare to determine incomplete stories; it factors parsing into _status_from_content and adds unit and end-to-end tests validating identifier resolution and CLI behavior.

Changes

Sprint Status Completion Detection

Layer / File(s) Summary
Status completion helper function
skills/bmad-story-automator/src/story_automator/core/sprint.py
New exported sprint_status_done_in_text determines "done" status from in-memory YAML by exact match then normalized-ID lookup; parsing extracted into _status_from_content.
Command integration with status helper
skills/bmad-story-automator/src/story_automator/commands/state.py
Import and use sprint_status_done_in_text(project_root) in cmd_sprint_compare, replacing the previous regex-based "done" detection and building incomplete via list comprehension.
Unit and integration test coverage
tests/test_sprint_compare.py
Adds fixture YAML and unit tests for dotted/dashed/descriptive slug resolution and negative cases; end-to-end tests write sprint-status.yaml and orchestration.md, run cmd_sprint_compare, and assert checked and incomplete results for done vs. in-progress scenarios.

🎯 3 (Moderate) | ⏱️ ~20 minutes

🐰 A rabbit reads YAML by moonlit code,
No regex leaps where parsers should go,
Helper hops in, tests line up in a row,
CI smiles as the statuses show. 🥕

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 7.14% 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 accurately describes the main fix: routing the sprint-compare check through the existing key resolver to properly handle descriptive slug keys instead of a broken regex.
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.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ 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 fix/sprint-compare-key-format

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 and usage tips.

@augmentcode

augmentcode Bot commented Jun 6, 2026

Copy link
Copy Markdown
🤖 Augment PR Summary

Summary: Fixes sprint-compare falsely reporting earlier stories as incomplete when sprint-status keys are descriptive slugs.
Changes: Replaces the command’s literal dotted-ID regex with a shared resolver.
Adds sprint_status_done_in_text that resolves dotted→dashed→slug keys using existing sprint status parsing helpers.
Updates cmd_sprint_compare to call this helper on already-read sprint-status text.
Adds tests/test_sprint_compare.py covering dotted/dashed/slug resolution plus an end-to-end regression.
Why: Centralizes sprint-status key matching and prevents future drift.

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

return match.group(1).strip() == "done"
norm = normalize_story_key(project_root, story_id)
target = norm.id if norm is not None else story_id
for key, status in _status_rows(content):

@augmentcode augmentcode Bot Jun 6, 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.

sprint_status_done_in_text returns on the first _status_rows() match; if sprint-status.yaml contains both a prefix key (e.g. 1-1:) and a more specific slug key (e.g. 1-1-...:), the answer can depend on file order and diverge from _best_status_match behavior used by sprint_status_get.

Severity: medium

Fix This in Augment

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

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Good catch — fixed in 03332c2. sprint_status_done_in_text now delegates to a shared _status_from_content (also used by sprint_status_get), so resolution goes through _best_status_match ranking: a descriptive slug key wins deterministically over a bare prefix key regardless of file order. Added a regression test asserting that precedence in both orderings.

@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 (1)
skills/bmad-story-automator/src/story_automator/commands/state.py (1)

196-196: 💤 Low value

Consider passing project_root for full epic-based ID support.

The current code works correctly for numeric story IDs (e.g., 1.1, 1-2), which is the typical use case. However, for epic-based story IDs (e.g., epic-foo.1, epic-bar-2-description), the normalization logic in sprint_status_done_in_text may need the project root to resolve keys correctly.

Since get_project_root() is already imported, you could pass it to ensure full compatibility:

♻️ Proposed enhancement
+project_root = get_project_root()
-incomplete = [sid for sid in before if not sprint_status_done_in_text(sprint_text, sid)]
+incomplete = [sid for sid in before if not sprint_status_done_in_text(sprint_text, sid, project_root)]
🤖 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 `@skills/bmad-story-automator/src/story_automator/commands/state.py` at line
196, The list comprehension building incomplete uses
sprint_status_done_in_text(sprint_text, sid) but doesn’t pass project_root,
which can break epic-based IDs; update the call in the incomplete assignment to
pass get_project_root() (or a resolved project_root variable) so
sprint_status_done_in_text(sprint_text, sid, project_root) can normalize
epic-based keys correctly; ensure the function signature of
sprint_status_done_in_text supports the extra param where defined and
import/get_project_root() is used consistently.
🤖 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 `@skills/bmad-story-automator/src/story_automator/commands/state.py`:
- Line 196: The list comprehension building incomplete uses
sprint_status_done_in_text(sprint_text, sid) but doesn’t pass project_root,
which can break epic-based IDs; update the call in the incomplete assignment to
pass get_project_root() (or a resolved project_root variable) so
sprint_status_done_in_text(sprint_text, sid, project_root) can normalize
epic-based keys correctly; ensure the function signature of
sprint_status_done_in_text supports the extra param where defined and
import/get_project_root() is used consistently.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: a65e1b12-db7a-44e1-b6eb-05fd4a0e83ad

📥 Commits

Reviewing files that changed from the base of the PR and between f332173 and e9da7a2.

📒 Files selected for processing (3)
  • skills/bmad-story-automator/src/story_automator/commands/state.py
  • skills/bmad-story-automator/src/story_automator/core/sprint.py
  • tests/test_sprint_compare.py

…olver

Address review feedback on #36:
- Extract `_status_from_content` shared by `sprint_status_get` and
  `sprint_status_done_in_text`, so completion checks use `_best_status_match`
  ranking (descriptive slug wins over a bare prefix key) instead of returning
  on the first row match -- removes order-dependence (augment).
- `cmd_sprint_compare` passes `get_project_root()` so non-numeric epic IDs
  resolve through the full normalizer (coderabbit).
- Add regression test asserting slug-over-prefix precedence regardless of
  file order.
@Pawel-N-pl

Copy link
Copy Markdown
Author

Thanks for the reviews — both points addressed in 03332c2:

  • CodeRabbit (state.py – pass project_root): cmd_sprint_compare now passes get_project_root(), so non-numeric epic IDs resolve through the full normalizer (consistent with fix(story-keys): complete non-numeric epic support #23).
  • Augment (sprint.py – first-match order dependence): replied in-thread — sprint_status_done_in_text now delegates to the shared _status_from_content resolver (same _best_status_match ranking as sprint_status_get), with a regression test locking slug-over-prefix precedence regardless of order.

…onvention

The package is effectively docstring-free (core/ and commands/ carry ~0%);
keep only a terse inline "why" comment on the public text-based variant,
consistent with the existing comment style. No behavior change.
@Pawel-N-pl

Copy link
Copy Markdown
Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jun 7, 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.

@bma-d bma-d left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Thanks for the work here. I'm requesting changes on the sprint-compare blocker below because an explicit sprint-status file can be misread when the process root is absent or points at a different project.

match = re.search(rf"(?m)^\s*{re.escape(story_id)}:\s*(\S+)", sprint_text)
if not match or match.group(1) != "done":
incomplete.append(story_id)
project_root = get_project_root()

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

cmd_sprint_compare accepts explicit --state and --sprint paths, but this now feeds the ambient project root into the text-backed resolver. When that root is absent or points at a different project, a child row like release-3-phase-2-1-title: done can satisfy predecessor release.3, so the predecessor gate reports no incomplete story.

Repro:

tmp=$(mktemp -d)
cat > "$tmp/orchestration.md" <<'EOF'
---
storyRange: ["release.3", "release-3-phase-2.1"]
currentStory: release-3-phase-2.1
---
EOF
cat > "$tmp/sprint-status.yaml" <<'EOF'
development_status:
  release-3-phase-2-1-title: done
EOF
env -u PROJECT_ROOT PYTHONDONTWRITEBYTECODE=1 PYTHONPATH=skills/bmad-story-automator/src python3 -m story_automator sprint-compare --state "$tmp/orchestration.md" --sprint "$tmp/sprint-status.yaml"

Output:

{"ok":true,"incomplete":[],"checked":["release.3"]}

Suggested fix: make the text-backed lookup disambiguate from the supplied sprint content, or derive/reject a project root tied to the explicit sprint path, so unmatched predecessors fail closed.

mira5557373 pushed a commit to mira5557373/bmad-automator that referenced this pull request Jun 23, 2026
…olver

Address review feedback on bmad-code-org#36:
- Extract `_status_from_content` shared by `sprint_status_get` and
  `sprint_status_done_in_text`, so completion checks use `_best_status_match`
  ranking (descriptive slug wins over a bare prefix key) instead of returning
  on the first row match -- removes order-dependence (augment).
- `cmd_sprint_compare` passes `get_project_root()` so non-numeric epic IDs
  resolve through the full normalizer (coderabbit).
- Add regression test asserting slug-over-prefix precedence regardless of
  file order.

(cherry picked from commit 03332c2)
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.

3 participants