This repository was archived by the owner on Jul 13, 2026. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 16
Add opt-in TEA workflow support to story automator #24
Closed
dickymoore
wants to merge
19
commits into
bmad-code-org:main
from
dickymoore:feature/tea-story-automator
+2,165
−58
Closed
Changes from all commits
Commits
Show all changes
19 commits
Select commit
Hold shift + click to select a range
2240827
Add TEA-capable policy and state support
dickymoore 04813c5
Add runtime TEA policy selection
dickymoore a21e5f6
Preserve standard-mode state and preflight UX
dickymoore de7d69b
Add TEA workflow detection
dickymoore 7656470
Fix TEA review flow regressions
dickymoore cc08957
Accept canonical TEA skill names
dickymoore bbad902
Add minimal TEA adapter fallback
dickymoore e3859e9
Tighten TEA fallback and NFR gating
dickymoore 8026802
Isolate standard mode from TEA overrides
dickymoore 4c8b7b4
Keep standard mode isolated from TEA detection
dickymoore 909b572
conflict resolution and PR comment resolution
dickymoore 7036650
Tighten TEA PR comment follow-ups
dickymoore 0532fa3
Merge upstream main into TEA workflow branch
dickymoore 162a450
fix: avoid asset resolution for agent task sequencing
dickymoore c1ff3e9
fix: tighten PR review follow-ups
dickymoore a0a5f62
fix: address latest PR review comments
dickymoore 05f3ff0
fix: tighten explicit policy detection
dickymoore 4118637
fix: harden PR review follow-ups
dickymoore e4fd975
test: stabilize unreadable override regression
dickymoore File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
8 changes: 8 additions & 0 deletions
8
skills/bmad-story-automator/data/tea-story-automator/parse/tea_step.json
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,8 @@ | ||
| { | ||
| "requiredKeys": ["status", "summary", "next_action"], | ||
| "schema": { | ||
| "status": "SUCCESS|FAILURE|AMBIGUOUS", | ||
| "summary": "brief description", | ||
| "next_action": "proceed|retry|escalate" | ||
| } | ||
| } |
7 changes: 7 additions & 0 deletions
7
skills/bmad-story-automator/data/tea-story-automator/prompts/tea_step.md
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| Run the `{{label}}` TEA workflow for story `{{story_id}}`. | ||
|
|
||
| {{skill_line}}{{workflow_line}}{{instructions_line}}{{checklist_line}}{{template_line}}Use the story context already prepared by story automator. | ||
|
|
||
| Return a concise structured result that matches the configured parse schema. | ||
|
|
||
| {{extra_instruction}} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -62,6 +62,7 @@ def cmd_orchestrator_helper(args: list[str]) -> int: | |
| "state-latest-incomplete": _state_latest_incomplete, | ||
| "state-summary": _state_summary, | ||
| "state-update": _state_update, | ||
| "state-progress": _state_progress, | ||
| "escalate": _escalate, | ||
| "commit-ready": _commit_ready, | ||
| "normalize-key": _normalize_key, | ||
|
|
@@ -100,6 +101,7 @@ def _usage(code: int) -> int: | |
| print(" state-latest-incomplete <folder>", file=target) | ||
| print(" state-summary <file>", file=target) | ||
| print(" state-update <file> --set k=v", file=target) | ||
| print(" state-progress <file> --story ID --set step=value", file=target) | ||
| print(" escalate <trigger> <context>", file=target) | ||
| print(" commit-ready <story_id>", file=target) | ||
| print(" normalize-key <input> [--to id|key|prefix|json]", file=target) | ||
|
|
@@ -110,7 +112,7 @@ def _usage(code: int) -> int: | |
| print(" get-epic-stories <epic> [--state-file path]", file=target) | ||
| print(" check-blocking <story_id>", file=target) | ||
| print(" agents-build --state-file path --complexity-file path --output path --config-json '{}'", file=target) | ||
| print(" agents-resolve (--state-file path | --agents-file path) --story ID --task create|dev|auto|review", file=target) | ||
| print(" agents-resolve (--state-file path | --agents-file path) --story ID --task STEP_NAME", file=target) | ||
| print(" retro-agent --state-file path", file=target) | ||
| return code | ||
|
|
||
|
|
@@ -475,6 +477,125 @@ def _verify_step(args: list[str]) -> int: | |
| return exit_code | ||
|
|
||
|
|
||
| def _normalize_progress_key(value: str) -> str: | ||
| key = str(value or "").strip().lower().replace("_", "-") | ||
| aliases = { | ||
| "create": "create-story", | ||
| "dev": "dev-story", | ||
| "auto": "automate", | ||
| "review": "code-review", | ||
| "test-automate": "test-automate", | ||
| "test-review": "test-review", | ||
| "git_commit": "git-commit", | ||
| "git-commit": "git-commit", | ||
| "status": "status", | ||
| "story": "story", | ||
| "create-story": "create-story", | ||
| "dev-story": "dev-story", | ||
| "automate": "automate", | ||
| "code-review": "code-review", | ||
| "atdd": "atdd", | ||
| "nfr": "nfr", | ||
| "trace": "trace", | ||
| } | ||
| return aliases.get(key, key) | ||
|
|
||
|
|
||
| def _parse_markdown_cells(line: str) -> list[str]: | ||
| parts = [part.strip() for part in line.split("|")] | ||
| return [part for part in parts[1:-1]] | ||
|
|
||
|
|
||
| def _render_markdown_row(cells: list[str]) -> str: | ||
| return "| " + " | ".join(cells) + " |" | ||
|
|
||
|
|
||
| def _state_progress(args: list[str]) -> int: | ||
| if not args: | ||
| print_json({"ok": False, "error": "file_not_found"}) | ||
| return 1 | ||
| state_file = args[0] | ||
| try: | ||
| if not file_exists(state_file): | ||
| print_json({"ok": False, "error": "file_not_found"}) | ||
| return 1 | ||
| except OSError: | ||
| print_json({"ok": False, "error": "state_file_unreadable"}) | ||
| return 1 | ||
| story_id = "" | ||
| updates: dict[str, str] = {} | ||
| idx = 1 | ||
| while idx < len(args): | ||
| if args[idx] == "--story" and idx + 1 < len(args): | ||
| story_id = args[idx + 1] | ||
| idx += 2 | ||
| continue | ||
| if args[idx] == "--set" and idx + 1 < len(args): | ||
| raw_update = args[idx + 1] | ||
| if "=" not in raw_update: | ||
| print_json({"ok": False, "error": "invalid_set_argument", "argument": raw_update}) | ||
| return 1 | ||
| key, value = raw_update.split("=", 1) | ||
| updates[_normalize_progress_key(key)] = value | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. skills/bmad-story-automator/src/story_automator/commands/orchestrator.py:539 — Severity: medium 🤖 Was this useful? React with 👍 or 👎, or 🚀 if it prevented an incident/outage. |
||
| idx += 2 | ||
| continue | ||
|
coderabbitai[bot] marked this conversation as resolved.
|
||
| idx += 1 | ||
| if not story_id or not updates: | ||
| print_json({"ok": False, "error": "missing_story_or_updates"}) | ||
| return 1 | ||
|
|
||
| try: | ||
| lines = read_text(state_file).splitlines() | ||
| except OSError: | ||
| print_json({"ok": False, "error": "state_file_unreadable"}) | ||
| return 1 | ||
| header_idx = -1 | ||
| story_idx = -1 | ||
| headers: list[str] = [] | ||
| story_cells: list[str] = [] | ||
| for i, line in enumerate(lines): | ||
| if line.startswith("| Story "): | ||
| header_idx = i | ||
| headers = [_normalize_progress_key(cell) for cell in _parse_markdown_cells(line)] | ||
| continue | ||
| if header_idx >= 0 and line.startswith(f"| {story_id} |"): | ||
| story_idx = i | ||
| story_cells = _parse_markdown_cells(line) | ||
| break | ||
| if header_idx < 0 or not headers: | ||
| print_json({"ok": False, "error": "progress_table_not_found"}) | ||
| return 1 | ||
| if story_idx < 0 or not story_cells: | ||
| print_json({"ok": False, "error": "story_row_not_found"}) | ||
| return 1 | ||
| if len(story_cells) != len(headers): | ||
| print_json({"ok": False, "error": "progress_row_misaligned"}) | ||
| return 1 | ||
|
dickymoore marked this conversation as resolved.
|
||
|
|
||
| header_map = {name: pos for pos, name in enumerate(headers)} | ||
| applied: list[str] = [] | ||
| for key, value in updates.items(): | ||
| if key == "story": | ||
| print_json({"ok": False, "error": "story_column_immutable"}) | ||
| return 1 | ||
| pos = header_map.get(key) | ||
| if pos is None: | ||
| continue | ||
| story_cells[pos] = value | ||
| applied.append(key) | ||
| if not applied: | ||
| print_json({"ok": False, "error": "progress_columns_not_found"}) | ||
| return 1 | ||
| lines[story_idx] = _render_markdown_row(story_cells) | ||
| try: | ||
| Path(state_file).write_text("\n".join(lines) + "\n", encoding="utf-8") | ||
| except OSError: | ||
| print_json({"ok": False, "error": "state_file_unwritable"}) | ||
| return 1 | ||
| print_json({"ok": True, "story": story_id, "updated": applied}) | ||
| return 0 | ||
|
|
||
|
|
||
| def _parse_context_int(context: str, key: str) -> int: | ||
| match = re.search(rf"{re.escape(key)}=(\d+)", context) | ||
| return int(match.group(1)) if match else 0 | ||
|
|
||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[P2]
orchestrator-helpernow owns markdown progress-table parsing and mutation._state_progressfinds the table byline.startswith("| Story "), splits cells withline.split("|"), finds the story row by markdown prefix, rewrites the row, and writes the state file. That duplicates state document schema ownership outside the state-doc layer. Please keep this CLI as a thin entry point and move progress-table parse/update/render behind the canonical state/state-document layer.