Skip to content

[6.11.0] sprint_plan.py: hardcoded positional identifier scheme — generate writes an empty development_status instead of failing #2827

Description

@millsks

Component: bmad-sprint-planningscripts/sprint_plan.py
Version observed: BMad Method v6.11.0
Severity: data loss (generate, fix); unusable (status, validate)

Summary

The skill assumes positional identifiers — Epic 1, Story 1.2, epic-1, 1-2-slug — in four
hardcoded regexes. A project that uses non-positional identifiers cannot use any subcommand, and
two of them rewrite the tracking file rather than failing.

Non-positional keys are a reasonable choice: they mean adding an epic never renumbers another. The
skill has no way to be told about them.

The four patterns

All in sprint_plan.py. None is read from config, environment, or the customization file:

EPIC_KEY_RE  = re.compile(r"^epic-(\d+)$")                        # :55
RETRO_KEY_RE = re.compile(r"^epic-(\d+)-retrospective$")           # :56
STORY_KEY_RE = re.compile(r"^(\d+)-(\d+)([a-z]?)-.+")              # :57
EPIC_RE      = re.compile(r"^#{1,3}\s*Epic\s+(\d+)\s*:?\s*(.*?)\s*#*\s*$", re.IGNORECASE)  # :44

Impact, in severity order

  1. generate is destructive on a non-matching epics file. It resolves zero epics and zero
    stories and writes an empty development_status, replacing a populated file with nothing. It
    does not detect that it parsed nothing from a non-empty input.
  2. fix rebuilds into the positional scheme, discarding the project's identifiers — which are
    also the keys shared with the epics file, the test-design handoff's story bindings, and every
    story filename on disk.
  3. status reads the same keys, so its counts are silently meaningless rather than absent.
  4. validate is safe but always negative. It reports every key as unrecognized, so it cannot
    distinguish "this project uses a different scheme" from "this file is corrupt".

Reproduction

A project whose epics file uses headings like ## CPM-EP-EVIDENCE: An evidence log that cannot lie
and ### CPM-EVIDENCE-S01: Five outcome states with one precedence order, and whose tracking file
uses matching lowercased keys.

$ python sprint_plan.py validate --status-file _bmad-output/implementation-artifacts/sprint-status.yaml
{"ok": true, "action": "validate", "valid": false, "problems": [
  "unrecognized key 'epic-platform' (expected epic-N, N-M-slug, or epic-N-retrospective)",
  "unrecognized key 'cpm-evidence-s01-five-outcome-states-precedence-order' (...)",
  ... 62 keys, all of them ...
]}

Running the script's own patterns against that epics file:

epic headings matched: 0
story headings matched: 0
total headings in file: 81

So generate would resolve nothing and emit an empty development_status.

Why it cannot be worked around

customize.toml exposes exactly four keys — activation_steps_prepend, activation_steps_append,
persistent_facts, on_complete. None reaches parsing or validation.

Editing the script works until the next update overwrites it: the customize.toml beside it carries
DO NOT EDIT -- overwritten on every update.

Suggested fixes, cheapest first

  1. Make generate refuse when it parses zero epics from a non-empty epics file. One guard, and
    it closes the data-loss case regardless of everything else. Parsing nothing out of 81 headings is
    a parse failure, not an empty backlog.
  2. Have validate distinguish "no key matches any known pattern" — likely a different
    identifier scheme — from "some keys are malformed" — likely corruption. The current message reads
    as the latter for what is usually the former.
  3. Expose the patterns in customize.toml, e.g. a [workflow.identifiers] block carrying the
    four regexes, so a project declares its scheme once and every subcommand honours it.

Secondary: the inline dependency assumes uv

sprint_plan.py declares its dependency as PEP 723 inline metadata:

# /// script
# requires-python = ">=3.10"
# dependencies = ["ruamel.yaml>=0.18"]
# ///

Only uv run provisions that automatically. A project standardized on another package manager gets
ModuleNotFoundError: No module named 'ruamel' from the documented invocation, with nothing
indicating the dependency is the problem rather than the script. A line in the skill's instructions
about non-uv environments — install ruamel.yaml into the interpreter you invoke it with — would
save the rediscovery.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions