feat: add Codex runtime support - #3
Conversation
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
WalkthroughThis PR generalizes runtime and installation paths beyond Claude to support multiple providers (Claude/Codex/.agents), makes retrospectives use the configured retro agent by default, implements provider-aware stop-hook installers (Claude JSON and Codex TOML), adds runtime-layout and marker resolution utilities, extends orchestrator helper actions, updates workflow steps and docs, and adds tests for layout, stop-hooks, and retro-agent resolution. ChangesRuntime-aware retrospectives, marker handling, and stop-hook support
Sequence DiagramsequenceDiagram
participant User as User/Orchestrator
participant Script as Workflow Script
participant Helper as orchestrator-helper
participant Layout as runtime_layout
participant StopHooks as stop_hooks
participant Provider as Claude/Codex
User->>Script: start workflow
Script->>Helper: orchestrator-helper marker path
Helper->>Layout: active_marker_path()
Layout-->>Helper: {file, entry}
Helper-->>Script: JSON {file,entry}
Script->>Script: ensure-marker-gitignore (entry)
Script->>Helper: orchestrator-helper retro-agent --state-file
Helper->>Helper: load agentConfig from state/frontmatter
Helper->>Layout: runtime_provider()
Helper-->>Script: {primary,fallback} for retro
Script->>Script: tmux-wrapper build-cmd/spawn using resolved agent
Script->>Helper: orchestrator-helper marker create
Helper->>Layout: active_marker_path()
Helper->>Provider: write marker at resolved location
alt Install / Ensure Stop Hook
Script->>Helper: orchestrator-helper ensure-stop-hook
Helper->>StopHooks: ensure_stop_hook(provider=...)
StopHooks->>Provider: update provider-specific config (JSON/TOML)
StopHooks-->>Helper: {changed,reason,...}
end
Script->>Helper: orchestrator-helper marker remove
Helper->>Layout: active_marker_path()
Helper->>Provider: remove marker
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related issues
Possibly related PRs
Suggested reviewers
Poem
✨ Finishing Touches🧪 Generate unit tests (beta)
|
7707edc to
d07c0ff
Compare
🤖 Augment PR SummarySummary: This PR adds first-class Codex runtime layout support to Story Automator so installers/consumers no longer need to patch paths and hook behavior post-install. Changes:
Technical Notes: Provider selection can be forced via env vars (e.g., 🤖 Was this summary useful? React with 👍 or 👎 |
There was a problem hiding this comment.
Actionable comments posted: 3
🧹 Nitpick comments (1)
source/src/story_automator/commands/orchestrator_epic_agents.py (1)
233-331: 💤 Low valueConsider documenting the expected YAML structure.
This custom indentation-based YAML parser handles the
agentConfigfrontmatter section with specific indent levels (2/4/6/8 spaces). The implementation appears correct for the expected structure, but the tight coupling to specific indentation could be fragile if the frontmatter format evolves.The final
parse_agent_config(json.dumps(config))call ensures consistent post-processing, which is good.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@source/src/story_automator/commands/orchestrator_epic_agents.py` around lines 233 - 331, The _load_agent_config_from_state function uses a custom indentation-based parser for the agentConfig frontmatter and is tightly coupled to exact indent levels (2/4/6/8) and keys like "agentConfig:", "perTask:", "complexityOverrides:", and "retro:"—document the expected frontmatter schema and indentation rules immediately above the _load_agent_config_from_state definition (or as its docstring) describing required top-level marker "agentConfig", the allowed child sections ("perTask", "complexityOverrides", "retro"), the precise indent levels for section headers (2 spaces) and nested keys (4/6/8 spaces), and example snippets for each structure so future authors know how to format input and what the parser expects; also mention that parse_agent_config(json.dumps(config)) is applied to the constructed dict for post-processing so callers can rely on that normalization.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In
`@payload/.claude/skills/bmad-story-automator/data/stop-hook-troubleshooting.md`:
- Around line 113-114: The troubleshooting hint in stop-hook-troubleshooting.md
hardcodes the path ".claude/skills/bmad-story-automator/scripts/" which misleads
users on .agents/.codex runtimes; update the table entry to mention and check
all possible runtime skill trees (e.g., .claude, .agents, .codex) or provide a
single command that checks each location (for example suggest running ls -la
against .claude/skills/... OR .agents/skills/... OR .codex/skills/...) and/or
recommend using a glob like */skills/bmad-story-automator/scripts/ so the
diagnostic works for Codex and Agents layouts; change the exact hardcoded path
string in the table row to the more general variants so the troubleshooting flow
no longer assumes only the .claude layout.
In `@source/tests/test_stop_hooks.py`:
- Line 224: The S108 linter flag on the assertion accessing
config["projects"]["/tmp/example"] is a false positive because "/tmp/example" is
a TOML table key in the test fixture, so suppress the warning by adding a
per-line noqa: S108 comment to the assertion containing
config["projects"]["/tmp/example"] (the self.assertEqual(... trust_level ...)
line) so the linter ignores it without changing test semantics.
- Around line 139-147: The test asserts changed=False even though normalization
mutates hooks.json (timeout/statusMessage), which is confusing; update the
code/tests to clarify the semantic contract: either add a concise inline comment
in source/tests/test_stop_hooks.py next to the _run_ensure_stop_hook("codex")
assertion explaining that changed==True denotes a newly registered hook
requiring restart while normalization-only writes still return changed==False,
or update the production cmd_ensure_stop_hook function/method
docstring/return-schema to explicitly state that normalization writes do not set
changed=True; reference the symbols _run_ensure_stop_hook and
cmd_ensure_stop_hook (and the hooks.json normalization behavior) when adding the
comment or docstring so readers can locate the behavior.
---
Nitpick comments:
In `@source/src/story_automator/commands/orchestrator_epic_agents.py`:
- Around line 233-331: The _load_agent_config_from_state function uses a custom
indentation-based parser for the agentConfig frontmatter and is tightly coupled
to exact indent levels (2/4/6/8) and keys like "agentConfig:", "perTask:",
"complexityOverrides:", and "retro:"—document the expected frontmatter schema
and indentation rules immediately above the _load_agent_config_from_state
definition (or as its docstring) describing required top-level marker
"agentConfig", the allowed child sections ("perTask", "complexityOverrides",
"retro"), the precise indent levels for section headers (2 spaces) and nested
keys (4/6/8 spaces), and example snippets for each structure so future authors
know how to format input and what the parser expects; also mention that
parse_agent_config(json.dumps(config)) is applied to the constructed dict for
post-processing so callers can rely on that normalization.
🪄 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: 0d781ed9-54d7-48a6-8796-b9dadd96fcf8
📒 Files selected for processing (30)
README.mddocs/agents-and-monitoring.mddocs/story-execution.mdpayload/.claude/skills/bmad-story-automator/data/agent-config-prompts.mdpayload/.claude/skills/bmad-story-automator/data/marker-file-format.mdpayload/.claude/skills/bmad-story-automator/data/orchestrator-rules-appendix.mdpayload/.claude/skills/bmad-story-automator/data/retrospective-automation.mdpayload/.claude/skills/bmad-story-automator/data/scripts-reference.mdpayload/.claude/skills/bmad-story-automator/data/stop-hook-config.mdpayload/.claude/skills/bmad-story-automator/data/stop-hook-recovery.mdpayload/.claude/skills/bmad-story-automator/data/stop-hook-troubleshooting.mdpayload/.claude/skills/bmad-story-automator/data/tmux-long-command-testing.mdpayload/.claude/skills/bmad-story-automator/steps-c/step-01-init.mdpayload/.claude/skills/bmad-story-automator/steps-c/step-01b-continue.mdpayload/.claude/skills/bmad-story-automator/steps-c/step-02a-preflight-config.mdpayload/.claude/skills/bmad-story-automator/steps-c/step-02b-preflight-finalize.mdpayload/.claude/skills/bmad-story-automator/steps-c/step-03b-execute-finish.mdpayload/.claude/skills/bmad-story-automator/steps-c/step-04-wrapup.mdpayload/.claude/skills/bmad-story-automator/workflow.mdsource/src/story_automator/commands/basic.pysource/src/story_automator/commands/orchestrator.pysource/src/story_automator/commands/orchestrator_epic_agents.pysource/src/story_automator/commands/state.pysource/src/story_automator/core/agent_config.pysource/src/story_automator/core/runtime_layout.pysource/src/story_automator/core/runtime_policy.pysource/src/story_automator/core/stop_hooks.pysource/tests/test_runtime_layout.pysource/tests/test_state_policy_metadata.pysource/tests/test_stop_hooks.py
💤 Files with no reviewable changes (1)
- payload/.claude/skills/bmad-story-automator/steps-c/step-02a-preflight-config.md
There was a problem hiding this comment.
🧹 Nitpick comments (2)
docs/installation-and-layout.md (1)
22-27: ⚡ Quick winClarify Codex path split between skill root and hook/config root.
This doc correctly uses
.agents/skills/...for installed skills, but it should explicitly note that Codex stop-hook/config files are under.codex/...to avoid operator confusion when wiring runtime behavior.Proposed doc tweak
The installer writes into the target project's native runtime skill root: - Claude Code: `.claude/skills/bmad-story-automator` - Claude Code: `.claude/skills/bmad-story-automator-review` - Codex: `.agents/skills/bmad-story-automator` - Codex: `.agents/skills/bmad-story-automator-review` + +Note: for Codex, skill assets live under `.agents/skills/...`, while hook/config files are managed under `.codex/...`.Also applies to: 88-93
🤖 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 `@docs/installation-and-layout.md` around lines 22 - 27, Update the installation-and-layout.md section that lists install paths to explicitly distinguish Codex runtime skill roots from Codex hook/config roots: keep the installed skill paths as `.agents/skills/bmad-story-automator` and `.agents/skills/bmad-story-automator-review` but add a clarifying sentence that Codex stop-hook and configuration files live under `.codex/...` (e.g., `.codex/.../bmad-story-automator`), and mirror the same clarification for the repeated mention at lines ~88-93 so readers understand where to wire runtime behavior versus hook/config files.source/src/story_automator/core/stop_hooks.py (1)
352-365: 💤 Low valueOptional: preserve a space before the trailing comment.
When rewriting an inline
features = { ... } # comment, the regex consumes the whitespace between}and#, and the formatter then concatenatescommentdirectly, producing}# comment. It's still valid TOML and the tests pass, but a single space would keep the file visually identical to common formatting.♻️ Suggested cosmetic tweak
- indent, inner, comment = match.group(1), match.group(2), match.group(3) or "" + indent, inner, comment = match.group(1), match.group(2), match.group(3) or "" + if comment: + comment = f" {comment}" @@ - return f"{indent}features = {{ {', '.join(updated_items)} }}{comment}" + return f"{indent}features = {{ {', '.join(updated_items)} }}{comment}"🤖 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 `@source/src/story_automator/core/stop_hooks.py` around lines 352 - 365, The function _set_inline_features_table_line currently drops the space between the closing brace and a trailing comment because match.group(3) consumes the intervening whitespace; update it so when a comment exists you prepend a single space before appending it. Locate _set_inline_features_table_line, keep the existing item parsing and replacement logic, and change how comment is built/appended (e.g., if match.group(3) is truthy ensure comment starts with a single space before the "#" token) so the returned string uses " ... }}<space># comment" instead of " ... }}# comment".
🤖 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 `@docs/installation-and-layout.md`:
- Around line 22-27: Update the installation-and-layout.md section that lists
install paths to explicitly distinguish Codex runtime skill roots from Codex
hook/config roots: keep the installed skill paths as
`.agents/skills/bmad-story-automator` and
`.agents/skills/bmad-story-automator-review` but add a clarifying sentence that
Codex stop-hook and configuration files live under `.codex/...` (e.g.,
`.codex/.../bmad-story-automator`), and mirror the same clarification for the
repeated mention at lines ~88-93 so readers understand where to wire runtime
behavior versus hook/config files.
In `@source/src/story_automator/core/stop_hooks.py`:
- Around line 352-365: The function _set_inline_features_table_line currently
drops the space between the closing brace and a trailing comment because
match.group(3) consumes the intervening whitespace; update it so when a comment
exists you prepend a single space before appending it. Locate
_set_inline_features_table_line, keep the existing item parsing and replacement
logic, and change how comment is built/appended (e.g., if match.group(3) is
truthy ensure comment starts with a single space before the "#" token) so the
returned string uses " ... }}<space># comment" instead of " ... }}# comment".
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: cdbfc780-04f5-4981-a66b-25afd01ac783
📒 Files selected for processing (14)
README.mddocs/agents-and-monitoring.mddocs/cli-reference.mddocs/how-it-works.mddocs/installation-and-layout.mddocs/troubleshooting.mdpayload/.claude/skills/bmad-story-automator/data/stop-hook-troubleshooting.mdsource/src/story_automator/commands/basic.pysource/src/story_automator/commands/orchestrator_epic_agents.pysource/src/story_automator/core/runtime_layout.pysource/src/story_automator/core/stop_hooks.pysource/tests/test_runtime_layout.pysource/tests/test_state_policy_metadata.pysource/tests/test_stop_hooks.py
✅ Files skipped from review due to trivial changes (5)
- docs/cli-reference.md
- docs/troubleshooting.md
- docs/how-it-works.md
- README.md
- docs/agents-and-monitoring.md
🚧 Files skipped from review as they are similar to previous changes (5)
- source/tests/test_runtime_layout.py
- payload/.claude/skills/bmad-story-automator/data/stop-hook-troubleshooting.md
- source/tests/test_state_policy_metadata.py
- source/src/story_automator/core/runtime_layout.py
- source/src/story_automator/commands/basic.py
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 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 `@source/src/story_automator/core/stop_hooks.py`:
- Around line 352-365: The function _set_inline_features_table_line is losing
the space between the closing brace and a trailing inline comment because
match.group(3) contains the comment token "#..." but not the separator space;
when rebuilding the line ensure you preserve that separator by prefixing a
single space when a trailing comment exists (e.g., compute comment =
match.group(3) or "" and then use comment = (" " + comment) if comment and not
comment.startswith(" ") else comment) before returning the formatted string so
features = {{ ... }} # comment keeps its original space; update only
_set_inline_features_table_line accordingly (no regex change required) and keep
returned format f"{indent}features = {{ {', '.join(updated_items)} }}{comment}".
- Around line 256-259: Remove the unused wrapper function
_ensure_codex_hooks_feature: delete its definition (the function that calls
_prepare_codex_hooks_feature, then _write_prepared_update, and returns
update.result) because ensure_codex_stop_hook already calls
_prepare_codex_hooks_feature and handles writes directly; ensure any imports or
references to _ensure_codex_hooks_feature are also removed so there are no
dangling references, and run tests to verify nothing else depends on
_ensure_codex_hooks_feature.
🪄 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: 585e2367-a0f2-4043-a187-6c01c31867a2
📒 Files selected for processing (8)
README.mddocs/agents-and-monitoring.mddocs/cli-reference.mddocs/how-it-works.mddocs/installation-and-layout.mddocs/troubleshooting.mdsource/src/story_automator/core/stop_hooks.pysource/tests/test_stop_hooks.py
✅ Files skipped from review due to trivial changes (5)
- docs/agents-and-monitoring.md
- docs/cli-reference.md
- README.md
- docs/how-it-works.md
- docs/installation-and-layout.md
🚧 Files skipped from review as they are similar to previous changes (1)
- docs/troubleshooting.md
There was a problem hiding this comment.
Actionable comments posted: 4
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
source/src/story_automator/commands/state.py (1)
240-251:⚠️ Potential issue | 🟠 Major | ⚡ Quick win
validate-statestill requiresaiCommand, contradicting the new deprecated/empty default.The state template now defaults
aiCommandto""(deprecated, replaced byagentConfig), andcmd_build_state_docno longer enforces a non-empty value. However,cmd_validate_statestill callsrequired("aiCommand"), which flags any empty string as"Missing or empty aiCommand". State documents generated under the new agentConfig-only flow will therefore fail validation even though they are correct.Consider relaxing the check so
aiCommandis optional whenagentConfigis present (or drop the requirement entirely since the field is documented as deprecated).🛡️ Possible adjustment
required("status", lambda value: isinstance(value, str) and value in allowed) required("lastUpdated", lambda value: isinstance(value, str) and re.search(r"\d{4}-\d{2}-\d{2}T", value)) - required("aiCommand") + # aiCommand is deprecated in favor of agentConfig; only required if agentConfig is absent. + if not fields.get("agentConfig"): + required("aiCommand")🤖 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 `@source/src/story_automator/commands/state.py` around lines 240 - 251, The validator currently calls required("aiCommand") and flags empty strings even though aiCommand is deprecated; update the validation in the state validation function (the block that calls required(...) and load_policy_for_state) to make aiCommand optional when agentConfig is present: replace the unconditional required("aiCommand") with a conditional check that only requires a non-empty aiCommand if state.get("agentConfig") is falsy (or drop the requirement entirely if you prefer); ensure any existing error messages still use the required(...) helper for consistency when the check is enforced.source/src/story_automator/commands/tmux.py (1)
124-135:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winResolve the default agent after
rootis known.
agent_type()now returns a concrete provider, so theseif agent == "auto"branches only run for an explicit--agent auto. In the default path, provider inference still happens beforeget_project_root()is available, which means invoking the wrapper from outside the repo can still pick the wrong runtime. Start with"auto"/the raw env value and resolve it withruntime_provider(root)oncerootis computed.Also applies to: 174-178
🤖 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 `@source/src/story_automator/commands/tmux.py` around lines 124 - 135, The code currently calls agent_type() and then later may override it with "--agent", causing provider inference to run before get_project_root(); change the flow so agent is initialized to the raw marker (e.g., "auto" or the raw env value) instead of calling agent_type() up front, parse args to possibly set agent string, then compute root = get_project_root() and only then, if agent == "auto" (or equals the raw env placeholder), call runtime_provider(root) to get the concrete provider; apply the same change to the second occurrence around the block referenced at lines 174-178 (replace upfront agent_type() calls with the raw marker and resolve via runtime_provider(root) after root is known).
🧹 Nitpick comments (2)
payload/.claude/skills/bmad-story-automator/data/tmux-commands.md (1)
80-80: ⚡ Quick winUse consistent placeholder syntax and document the values.
The placeholder syntax mixes
{project_root}(curly braces) with<installed-skill-root>(angle brackets), which is inconsistent with the rest of the document that uses curly braces for all placeholders (e.g.,{epic},{story_suffix},{SESSION_NAME}).Additionally, there's no explanation of what value users should substitute for
<installed-skill-root>. Users won't know whether to use.claude/skills,.codex/skills, or something else.Consider:
- Using consistent curly-brace syntax:
{installed-skill-root}- Adding a note explaining the expected values for Claude vs Codex, or
- Providing a shell variable example showing how to determine it dynamically
🤖 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 `@payload/.claude/skills/bmad-story-automator/data/tmux-commands.md` at line 80, Replace the inconsistent placeholder in the tmux command assignment (the script variable name "script") by using {installed-skill-root} instead of <installed-skill-root>, update the line to match the document's curly-brace convention (e.g., "{project_root}/ {installed-skill-root}/bmad-story-automator/..."), and add a short note near that line describing allowable values (for example: use ".claude/skills" for Claude, ".codex/skills" for Codex) plus an optional shell example hint for users to derive it dynamically (e.g., show how to detect the skill root via an environment variable or a simple shell substitution).source/src/story_automator/commands/state.py (1)
80-93: 💤 Low valueDefensive check on
agent_config.get('defaultFallback')returningNone.
agent_config.get('defaultFallback', agent_config.get('fallback', False))only triggers the default when the key is absent. If a user (or earlier normalization) explicitly setsdefaultFallback: nullin the input config JSON, this renders asdefaultFallback: nullin the generated YAML, whichagent_config.py's effective resolution logic may not treat as "disabled" the same wayfalseis. Consider coercingNonetoFalsehere for parity with the documented sentinel set (claude | codex | false).🤖 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 `@source/src/story_automator/commands/state.py` around lines 80 - 93, The current assembly of lines uses agent_config.get('defaultFallback', agent_config.get('fallback', False)) which leaves explicit null (None) values through; update the logic that computes the defaultFallback value (located where agent_config, per_task and lines are handled) to coerce None to False before JSON-encoding — e.g., read fallback = agent_config.get('defaultFallback', agent_config.get('fallback', False)); if fallback is None: fallback = False; then use json.dumps(fallback) in the f-string — this ensures explicit null becomes false to match agent_config.py's expected sentinel handling.
🤖 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 `@payload/.claude/skills/bmad-story-automator/data/tmux-commands.md`:
- Around line 76-77: Revise the unclear line "Script: resolve the installed
helper under the active installed skill root." to explicitly state what
<installed-skill-root> means and give concrete examples per provider (e.g., for
Claude use ".claude/skills", for Codex use ".codex/skills"), and/or explain how
to determine it dynamically (e.g., describe checking the user's config or
environment variable that points to the active skill directory); update the
tmux-commands.md entry so readers know exactly which path to use or how to find
it rather than an ambiguous description.
In `@source/src/story_automator/commands/orchestrator_epic_agents.py`:
- Around line 243-254: The explicit-return paths in _resolve_primary_agent and
_resolve_fallback_agent currently return raw values un-normalized, causing
mismatches against downstream lowercase agent IDs; update both functions so the
non-runtime/false branches return the normalized, stripped, lowercase agent id
(e.g. use value = str(raw or "").strip().lower() and return value) or call the
shared resolver used elsewhere instead of returning str(raw), and ensure
_resolve_fallback_agent still maps boolean/false-like inputs to "false" while
normalizing explicit agent names to lowercase.
In `@source/src/story_automator/core/agent_config.py`:
- Around line 110-121: The resolvers return the raw string for non-auto values
which can preserve casing/whitespace and misroute agent selection; update
_resolve_primary_agent to return the already-normalized local variable (value)
instead of str(raw) and update _resolve_fallback_agent to return a normalized
string (e.g., str(value).strip().lower()) so both functions consistently return
stripped lowercase agent names; reference _resolve_primary_agent,
_resolve_fallback_agent, runtime_provider(), and normalize_fallback_value() when
making the change.
In `@source/src/story_automator/core/runtime_layout.py`:
- Around line 172-186: The current logic uses skills_root returned by
resolve_skills_root(STORY_SKILL_NAME) even when that was the synthetic fallback
(root/.claude/skills); change it to only prefer the skill-root-derived marker
when that skills_root actually exists/contains the story skill or was explicitly
selected: in runtime_layout.py, before using skills_root.relative_to(root) and
returning (skills_root.parent / ACTIVE_MARKER_NAME), add a guard that
skills_root.exists() (or another concrete existence check for the installed
story skill inside skills_root) and that the skill is not just the fallback
path; if the check fails, skip to the provider-based branches
(runtime_provider(root) == "codex" -> root/.agents/.story-automator-active, else
root/.claude/.story-automator-active).
---
Outside diff comments:
In `@source/src/story_automator/commands/state.py`:
- Around line 240-251: The validator currently calls required("aiCommand") and
flags empty strings even though aiCommand is deprecated; update the validation
in the state validation function (the block that calls required(...) and
load_policy_for_state) to make aiCommand optional when agentConfig is present:
replace the unconditional required("aiCommand") with a conditional check that
only requires a non-empty aiCommand if state.get("agentConfig") is falsy (or
drop the requirement entirely if you prefer); ensure any existing error messages
still use the required(...) helper for consistency when the check is enforced.
In `@source/src/story_automator/commands/tmux.py`:
- Around line 124-135: The code currently calls agent_type() and then later may
override it with "--agent", causing provider inference to run before
get_project_root(); change the flow so agent is initialized to the raw marker
(e.g., "auto" or the raw env value) instead of calling agent_type() up front,
parse args to possibly set agent string, then compute root = get_project_root()
and only then, if agent == "auto" (or equals the raw env placeholder), call
runtime_provider(root) to get the concrete provider; apply the same change to
the second occurrence around the block referenced at lines 174-178 (replace
upfront agent_type() calls with the raw marker and resolve via
runtime_provider(root) after root is known).
---
Nitpick comments:
In `@payload/.claude/skills/bmad-story-automator/data/tmux-commands.md`:
- Line 80: Replace the inconsistent placeholder in the tmux command assignment
(the script variable name "script") by using {installed-skill-root} instead of
<installed-skill-root>, update the line to match the document's curly-brace
convention (e.g., "{project_root}/
{installed-skill-root}/bmad-story-automator/..."), and add a short note near
that line describing allowable values (for example: use ".claude/skills" for
Claude, ".codex/skills" for Codex) plus an optional shell example hint for users
to derive it dynamically (e.g., show how to detect the skill root via an
environment variable or a simple shell substitution).
In `@source/src/story_automator/commands/state.py`:
- Around line 80-93: The current assembly of lines uses
agent_config.get('defaultFallback', agent_config.get('fallback', False)) which
leaves explicit null (None) values through; update the logic that computes the
defaultFallback value (located where agent_config, per_task and lines are
handled) to coerce None to False before JSON-encoding — e.g., read fallback =
agent_config.get('defaultFallback', agent_config.get('fallback', False)); if
fallback is None: fallback = False; then use json.dumps(fallback) in the
f-string — this ensures explicit null becomes false to match agent_config.py's
expected sentinel handling.
🪄 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: 40da1dbc-d90a-4eb0-b96c-852fac4e119a
📒 Files selected for processing (27)
README.mddocs/cli-reference.mddocs/how-it-works.mddocs/installation-and-layout.mddocs/troubleshooting.mdinstall.shpayload/.claude/skills/bmad-story-automator-review/instructions.xmlpayload/.claude/skills/bmad-story-automator/data/agent-fallback-troubleshooting.mdpayload/.claude/skills/bmad-story-automator/data/agent-fallback.mdpayload/.claude/skills/bmad-story-automator/data/code-review-loop.mdpayload/.claude/skills/bmad-story-automator/data/orchestration-policy.jsonpayload/.claude/skills/bmad-story-automator/data/preflight-prompts.mdpayload/.claude/skills/bmad-story-automator/data/retrospective-automation.mdpayload/.claude/skills/bmad-story-automator/data/retrospective-prompts.mdpayload/.claude/skills/bmad-story-automator/data/stop-hook-config.mdpayload/.claude/skills/bmad-story-automator/data/stop-hook-troubleshooting.mdpayload/.claude/skills/bmad-story-automator/data/tmux-commands.mdpayload/.claude/skills/bmad-story-automator/data/workflow-commands.mdpayload/.claude/skills/bmad-story-automator/templates/state-document.mdscripts/smoke-test.shsource/src/story_automator/commands/orchestrator_epic_agents.pysource/src/story_automator/commands/state.pysource/src/story_automator/commands/tmux.pysource/src/story_automator/core/agent_config.pysource/src/story_automator/core/runtime_layout.pysource/src/story_automator/core/tmux_runtime.pysource/tests/test_runtime_layout.py
✅ Files skipped from review due to trivial changes (8)
- payload/.claude/skills/bmad-story-automator/data/code-review-loop.md
- docs/cli-reference.md
- payload/.claude/skills/bmad-story-automator/data/agent-fallback-troubleshooting.md
- docs/how-it-works.md
- payload/.claude/skills/bmad-story-automator/data/stop-hook-config.md
- docs/installation-and-layout.md
- payload/.claude/skills/bmad-story-automator/data/stop-hook-troubleshooting.md
- README.md
🚧 Files skipped from review as they are similar to previous changes (3)
- docs/troubleshooting.md
- payload/.claude/skills/bmad-story-automator/data/retrospective-automation.md
- source/tests/test_runtime_layout.py
|
Review follow-up summary:
|
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (4)
docs/installation-and-layout.md (3)
22-33: ⚡ Quick winClarify when to use
.agents/skillsvs provider-specific roots.The documentation lists three supported roots but doesn't explain when operators should use the generic
.agents/skillsversus provider-specific roots like.codex/skills. Consider adding a brief note about:
- Is
.agents/skillsa recommended generic location for multi-provider projects?- When should operators prefer provider-specific roots?
- Does the runtime auto-detect across all three locations?
This would help operators make informed layout decisions during setup.
🤖 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 `@docs/installation-and-layout.md` around lines 22 - 33, Add a short clarifying paragraph to the installation-and-layout doc explaining that .agents/skills is the recommended generic root for multi-provider or provider-agnostic projects, provider-specific roots like .codex/skills or .claude/skills should be used when you need provider-specific config or isolation, and that the runtime auto-detects and merges across all supported roots (".agents/skills", ".claude/skills", ".codex/skills") so the installer will update every qualifying root; also mention that payload/.claude/skills is only an internal packaged path and not part of runtime hierarchy.
22-75: ⚡ Quick winConsider defining skill root terminology upfront.
The document uses several related terms throughout:
- "supported skill root" / "supported dependency skill root" (lines 22, 29, 53)
- "qualifying root" / "qualifying skill root" (lines 31, 75)
- "dependency skill root" (line 14)
- "installed skill root" (line 39)
- "target root" (line 33)
While context makes the meaning clear, explicitly defining the relationship between these terms early in the document would help operators understand the install logic:
- Supported roots: The three recognized locations (
.agents/skills,.claude/skills,.codex/skills)- Qualifying roots: Supported roots that contain all required dependency
SKILL.mdfiles- Installed skill root: The qualifying root where
bmad-story-automatorgets installedThis is a low-priority documentation polish item.
🤖 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 `@docs/installation-and-layout.md` around lines 22 - 75, Add a short "Terminology" section near the top that defines the terms used in the doc: list "Supported roots" and enumerate `.agents/skills`, `.claude/skills`, `.codex/skills`; define "Qualifying roots" as supported roots that contain all required dependency SKILL.md files; define "Installed skill root" / "Target root" as the qualifying root(s) where bmad-story-automator is copied; and clarify "Installed skill payload" as the internal payload path under payload/.claude/skills. Then update usages of the phrases across the file to match these canonical names (e.g., replace mixed uses of "supported dependency skill root", "dependency skill root", "qualifying root", "installed skill root", and "target root" with the chosen terms) so terminology is consistent.
102-103: 💤 Low valueConsider clarifying "still uses" phrasing.
The phrase "Codex still uses the active installed skill root" might be clearer as "Codex uses the active installed skill root for the helper entrypoint, but writes hook/config state..."
The word "still" suggests a contrast with previous behavior, but the contrast here is between skill location and config location, not temporal change.
🤖 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 `@docs/installation-and-layout.md` around lines 102 - 103, Update the sentence to remove ambiguous "still uses" phrasing: replace "Codex still uses the active installed skill root for this helper, but writes hook/config state to `.codex/hooks.json` and `.codex/config.toml`." with wording like "Codex uses the active installed skill root for the helper entrypoint, but writes hook/config state to `.codex/hooks.json` and `.codex/config.toml`." to clarify the contrast is between skill location and config location; target the sentence containing "Codex still uses the active installed skill root" in docs/installation-and-layout.md.source/tests/test_runtime_layout.py (1)
191-197: ⚡ Quick win
_install_bundlewill fail with an opaqueFileNotFoundErrorif the payload is absent
shutil.copytreeraisesFileNotFoundErrordirectly ifpayload/.claude/skills/bmad-story-automatororbmad-story-automator-reviewdoesn't exist. Because every test that exercises provider-detection or marker logic depends on this helper, the failure surfaces as a cryptic OS error with no hint about the missing payload, rather than a skipped or clearly-labelled test failure.A minimal guard keeps the intent clear and prevents confusion in stripped-down checkouts or isolated package installs:
🛠️ Proposed fix
def _install_bundle(self, runtime_dir: str) -> None: source_skill = REPO_ROOT / "payload" / ".claude" / "skills" / "bmad-story-automator" source_review = REPO_ROOT / "payload" / ".claude" / "skills" / "bmad-story-automator-review" + if not source_skill.is_dir() or not source_review.is_dir(): + self.skipTest(f"payload skills not found under {REPO_ROOT / 'payload'}") target_root = self.project_root / runtime_dir / "skills" target_root.mkdir(parents=True, exist_ok=True) shutil.copytree(source_skill, target_root / "bmad-story-automator") shutil.copytree(source_review, target_root / "bmad-story-automator-review")🤖 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 `@source/tests/test_runtime_layout.py` around lines 191 - 197, The helper _install_bundle currently calls shutil.copytree on source_skill and source_review and will raise an opaque FileNotFoundError if the payload is missing; update _install_bundle to check existence of REPO_ROOT / "payload" / ".claude" / "skills" for both source_skill and source_review before calling shutil.copytree and raise a clearer, descriptive error (or call pytest.skip) that mentions the missing payload path and test dependency (reference symbols: _install_bundle, source_skill, source_review, shutil.copytree, REPO_ROOT, project_root) so failures in test_runtime_layout are explicit and informative.
🤖 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 `@source/src/story_automator/commands/state.py`:
- Around line 88-95: The emitted defaultPrimary can become JSON null if the key
exists with null; mirror the defaultFallback guard by computing a
default_primary variable: retrieve agent_config.get('defaultPrimary') into
default_primary, and if it is None set it to agent_config.get('primary', 'auto')
(or 'auto' if that is missing); then use default_primary in the f-string that
emits the "defaultPrimary" line so you never serialize a Python None to "null".
In `@source/src/story_automator/commands/tmux.py`:
- Line 291: cmd_monitor_session currently only treats "auto" specially and
leaves "runtime" as a literal, causing session_status to receive wrong provider
flags; replace the ad-hoc check in cmd_monitor_session (where agent is read and
tested with if agent == "auto") with the same helper used elsewhere: call
_resolve_agent_selection(...) to normalize the agent and derive the
codex/runtime/provider flags (same approach used by _spawn and _build_cmd) and
then pass the normalized values into session_status; ensure the agent variable
used in session_status calls is the resolved value returned by
_resolve_agent_selection so "runtime" is handled correctly.
---
Nitpick comments:
In `@docs/installation-and-layout.md`:
- Around line 22-33: Add a short clarifying paragraph to the
installation-and-layout doc explaining that .agents/skills is the recommended
generic root for multi-provider or provider-agnostic projects, provider-specific
roots like .codex/skills or .claude/skills should be used when you need
provider-specific config or isolation, and that the runtime auto-detects and
merges across all supported roots (".agents/skills", ".claude/skills",
".codex/skills") so the installer will update every qualifying root; also
mention that payload/.claude/skills is only an internal packaged path and not
part of runtime hierarchy.
- Around line 22-75: Add a short "Terminology" section near the top that defines
the terms used in the doc: list "Supported roots" and enumerate
`.agents/skills`, `.claude/skills`, `.codex/skills`; define "Qualifying roots"
as supported roots that contain all required dependency SKILL.md files; define
"Installed skill root" / "Target root" as the qualifying root(s) where
bmad-story-automator is copied; and clarify "Installed skill payload" as the
internal payload path under payload/.claude/skills. Then update usages of the
phrases across the file to match these canonical names (e.g., replace mixed uses
of "supported dependency skill root", "dependency skill root", "qualifying
root", "installed skill root", and "target root" with the chosen terms) so
terminology is consistent.
- Around line 102-103: Update the sentence to remove ambiguous "still uses"
phrasing: replace "Codex still uses the active installed skill root for this
helper, but writes hook/config state to `.codex/hooks.json` and
`.codex/config.toml`." with wording like "Codex uses the active installed skill
root for the helper entrypoint, but writes hook/config state to
`.codex/hooks.json` and `.codex/config.toml`." to clarify the contrast is
between skill location and config location; target the sentence containing
"Codex still uses the active installed skill root" in
docs/installation-and-layout.md.
In `@source/tests/test_runtime_layout.py`:
- Around line 191-197: The helper _install_bundle currently calls
shutil.copytree on source_skill and source_review and will raise an opaque
FileNotFoundError if the payload is missing; update _install_bundle to check
existence of REPO_ROOT / "payload" / ".claude" / "skills" for both source_skill
and source_review before calling shutil.copytree and raise a clearer,
descriptive error (or call pytest.skip) that mentions the missing payload path
and test dependency (reference symbols: _install_bundle, source_skill,
source_review, shutil.copytree, REPO_ROOT, project_root) so failures in
test_runtime_layout are explicit and informative.
🪄 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: b9b8c605-a193-445f-ac4f-87b50d56123c
📒 Files selected for processing (14)
README.mddocs/installation-and-layout.mdinstall.shpayload/.claude/skills/bmad-story-automator/data/orchestration-policy.jsonpayload/.claude/skills/bmad-story-automator/data/tmux-commands.mdscripts/smoke-test.shsource/src/story_automator/commands/orchestrator_epic_agents.pysource/src/story_automator/commands/state.pysource/src/story_automator/commands/tmux.pysource/src/story_automator/core/agent_config.pysource/src/story_automator/core/runtime_layout.pysource/src/story_automator/core/runtime_policy.pysource/tests/test_runtime_layout.pysource/tests/test_state_policy_metadata.py
🚧 Files skipped from review as they are similar to previous changes (9)
- payload/.claude/skills/bmad-story-automator/data/orchestration-policy.json
- payload/.claude/skills/bmad-story-automator/data/tmux-commands.md
- source/src/story_automator/core/agent_config.py
- install.sh
- scripts/smoke-test.sh
- source/src/story_automator/commands/orchestrator_epic_agents.py
- README.md
- source/tests/test_state_policy_metadata.py
- source/src/story_automator/core/runtime_layout.py
0217624 to
cf96221
Compare
bma-d
left a comment
There was a problem hiding this comment.
Review pass complete. P0 clean, P1 not clean. I focused on preserving existing Claude behavior and keeping runtime/provider concerns separated from child-agent selection. Two items could not be placed inline because the affected files/lines are not in this PR diff: docs/state-and-resume.md still hard-codes .claude/.story-automator-active, and steps-v/step-v-01-check.md still treats deprecated aiCommand as the only valid runtime command field even though runtime validation accepts agentConfig. Those should still be fixed before merge.
The main architectural ask: split runtime context from child-agent selection. Provider/layout, skill root, marker root, hook target, and child agent should be separate values; AI_AGENT should not decide hook/config layout.
|
Merge-blocker rework is now pushed on Addressed in this pass:
Validation run for this pass:
Intentionally deferred to tightly-scoped follow-up PRs:
|
|
Cross-PR summary for the current Automator/Codex work:
Short version: |
bma-d
left a comment
There was a problem hiding this comment.
Reviewed current PR head. All review-thread items have been addressed and resolved correctly. Residual note outside the addressed review ledger: local stdlib unittest reproduction still showed one macOS path-normalization assertion failure in tests/test_stop_hooks.py for /var vs /private/var, but that is separate from the reviewed thread items and not blocking this approval.
|
Effectively merged in the commits and made available via |
…t validation Validated the 12-finding ecosystem report against actual code. Most findings target the worktree feature branches (bma-d/m02..m14) or the separate sw-decompose-experiment repo and cannot live on main; this commit lands the subset that genuinely applies to mainline, plus a build regression that the validation surfaced in my own prior change. Build regression (introduced by the earlier R13 wheel fix) — IMPORTANT: - pyproject force-included `../bmad-story-automator-review`, a path that escapes the project root. `python -m build` (sdist→wheel, what `npm run build:python` and CI run) then failed: the sdist cannot contain the sibling dir, so the wheel-from-sdist stage raised "Forced include not found". Removed that single `..` entry. The remaining in-tree force-includes still make the wheel self-contained (policy/data bundled, "bundled policy not found" stays fixed); the review skill was never sufficient for a standalone wheel anyway (build-cmd also needs the consumer's external bmad-create-story skill). Verified: both sdist and wheel now build, and the installed wheel still passes the CI guard. Report finding bmad-code-org#6 — self-contained-runtime guard: - Added tests/test_no_unauthorized_imports.py: an ast-walk asserting the shipped runtime imports only stdlib + first-party. Confirms main is dependency-free and fails CI if a third-party import is ever added silently. Report finding bmad-code-org#12 + new doc gaps — environment variables: - Added docs/environment-variables.md documenting the ~11 env vars main actually reads (incl. previously-undocumented STORY_AUTOMATOR_STATE_FILE and the marker overrides), and marks STATE_*/STORY_AUTOMATOR_CHILD as internal-only. New (validated) correctness/consistency on main: - PROJECT_ROOT: standardize on the `or` fallback so an explicitly-empty value behaves like unset (get_project_root in utils.py; validate_story_creation.py), matching common.project_root/runtime_layout/basic.py. (Did NOT .resolve() the display path — it would diverge from the dirs other consumers compute, e.g. /tmp vs /private/tmp or Windows 8.3 names.) Added a regression test. New (validated) doc/code drift fixes (docs only): - Corrected the child command-file path everywhere: it is <tmpdir>/.sa-<hash>-session-<session>-command.sh (+ -runner.sh), not /tmp/sa-cmd-<session>.sh, and is always written under the default runner runtime (the 500-char gate is legacy-only). (agents-and-monitoring.md, troubleshooting.md, tmux-long-command-debugging.md) - Replaced the never-emitted `codex exec --full-auto` with the accurate base `codex exec` in the descriptive agent tables (5 skill data files). Deferred (validated but out of scope for origin main): - bmad-code-org#1/bmad-code-org#2/bmad-code-org#3/bmad-code-org#7/bmad-code-org#11 live on worktree branches (filelock/psutil deps, emitter cache race, RunLock create_time, JSONL newline, M11 doc gate) — fixable only on bma-d/* branches. bmad-code-org#4 was REFUTED. bmad-code-org#8 is the separate experiment repo. - bmad-code-org#9 (utils/common) is maintainability-only, not a correctness bug (each consumer imports one module; helpers are tuple-compatible) and consolidation risks changing ensure_ascii/unquote semantics — deferred. bmad-code-org#10 (tmux_runtime size) is a high-risk pure refactor — deferred. Full suite: 445 tests; remaining failures are the pre-existing Windows-only environment artifacts. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Summary
Adds Codex runtime support alongside Claude Code in BMad Automator, rebased onto the upstream self-contained
skills/plugin layout.This PR now folds in the correctness, compatibility, platform-testability, and operator-facing doc fixes needed to make the runtime change set robust instead of leaving those concerns split across too many follow-ups.
Main fixes in this PR
AI_AGENTto decide hook/config layout and marker defaultsPROJECT_ROOTthrough installed stop-hook commands so marker lookup does not depend on the caller's working directoryAI_COMMANDclassification consistent with build/monitor behavioraiCommand/agentConfigruntime model already supported by the codeScope Notes
This PR is still intentionally bounded to Codex runtime support plus the fixes needed to make that behavior safe, testable, and reviewable.
What remains intentionally out of scope:
.agentsneutrality beyond documenting the current runtime model accuratelyTests
PYTHONPATH=skills/bmad-story-automator/src python3 -m pytest tests/test_runtime_layout.py tests/test_stop_hooks.py tests/test_state_policy_metadata.py tests/test_success_verifiers.py -q126 passedCompanion Work
Remaining Follow-ups
.agentsruntime semantics, if we decide to go beyond documenting the current behavior.