Skip to content
This repository was archived by the owner on Jul 13, 2026. It is now read-only.

feat: add Codex runtime support - #3

Closed
dickymoore wants to merge 3 commits into
bmad-code-org:mainfrom
dickymoore:dicky/codex-runtime-support
Closed

feat: add Codex runtime support#3
dickymoore wants to merge 3 commits into
bmad-code-org:mainfrom
dickymoore:dicky/codex-runtime-support

Conversation

@dickymoore

@dickymoore dickymoore commented May 1, 2026

Copy link
Copy Markdown
Contributor

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

  • separate top-level runtime/provider detection from child-agent selection
  • stop using AI_AGENT to decide hook/config layout and marker defaults
  • preserve legacy fallback behavior for missing/unversioned agent config instead of silently disabling fallback
  • make Codex stop-hook results distinguish configured-on-disk vs trusted-and-verified state
  • pass PROJECT_ROOT through installed stop-hook commands so marker lookup does not depend on the caller's working directory
  • make legacy AI_COMMAND classification consistent with build/monitor behavior
  • document the actual runtime/provider resolution model and its precedence
  • make CLI/troubleshooting examples copy-paste-safe by resolving the installed helper from supported skill roots
  • normalize runtime-layout path assertions so the targeted Python tests stay valid on macOS path layouts as well as Linux
  • keep state/validation guidance aligned with the aiCommand / agentConfig runtime model already supported by the code

Scope 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:

  • cleanup-only stop-hook internals
  • cosmetic formatting-only fixes
  • any broader architectural redesign of .agents neutrality beyond documenting the current runtime model accurately

Tests

  • 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 -q
  • current result on the pushed branch: 126 passed

Companion Work

Remaining Follow-ups

  1. Cleanup-only stop-hook/internal refactors after runtime behavior lands.
  2. Any optional broader redesign of shared .agents runtime semantics, if we decide to go beyond documenting the current behavior.

@coderabbitai

coderabbitai Bot commented May 1, 2026

Copy link
Copy Markdown

Note

Reviews paused

It 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 reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

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

Changes

Runtime-aware retrospectives, marker handling, and stop-hook support

Layer / File(s) Summary
Data / Shape
source/src/story_automator/core/agent_config.py, source/src/story_automator/commands/state.py
Agent-config parsing now accepts a top-level retro entry, normalizes per-task maps, and defaults defaultPrimary to "auto" and defaultFallback to "false"; state doc generation preserves legacy top-level agentConfig.retro into perTask.retro when present.
Core Implementation
source/src/story_automator/core/runtime_layout.py, source/src/story_automator/core/runtime_policy.py
New runtime-layout utilities for provider detection, candidate skills-root discovery, portable path resolution, and active-marker resolution; runtime_policy delegates path/asset resolution to layout helpers and adds workflow/portable-path rules and SKILL.md acceptance for required workflows.
Provider Logic
source/src/story_automator/core/stop_hooks.py
New stop-hook subsystem with ensure_stop_hook dispatcher, Claude JSON hook handling, Codex hooks + TOML feature-toggle logic, atomic-write helpers, structured HookConfigError, and normalization/idempotency behaviors.
CLI / Wiring
source/src/story_automator/commands/basic.py, source/src/story_automator/commands/orchestrator.py, source/src/story_automator/commands/orchestrator_epic_agents.py, source/src/story_automator/commands/tmux.py
cmd_ensure_stop_hook now derives provider via runtime_provider() and returns structured JSON on errors; cmd_stop_hook and orchestrator helper use active_marker_path(); orchestrator helper adds marker path and retro-agent actions; new retro_agent_action reads state/frontmatter and resolves retro agent; tmux commands resolve "auto" via runtime_provider.
Workflow / Steps
payload/.../steps-c/step-01-init.md, step-01b-continue.md, step-02b-preflight-finalize.md, step-03b-execute-finish.md, step-04-wrapup.md
Removed hardcoded markerFile frontmatter; marker create/remove and gitignore handling now use orchestrator-helper marker path/create/remove and ensure-marker-gitignore; retrospectives spawn using the helper-resolved retro agent.
Installer & Scripts
install.sh, scripts/smoke-test.sh
Installer and smoke test generalized to install/verify under multiple supported skill roots (.agents/skills, .claude/skills, .codex/skills), loop per-root, and parameterize test helpers; installer selects complete roots and reports diagnostics when none are complete.
Documentation & Payload
README.md, docs/*.md, payload/.claude/skills/.../data/*, workflow.md
Docs and payload prompts/workflow references updated to use <installed-skill-root>/... paths, describe supported skill roots and runtime/provider env-vars, and state retrospectives inherit configured primary agent by default.
Tests
source/tests/test_runtime_layout.py, source/tests/test_stop_hooks.py, source/tests/test_state_policy_metadata.py
Added tests covering runtime-layout/provider/marker resolution, stop-hook install/idempotency/TOML formats and error paths, and retro-agent resolution/legacy compatibility/YAML-comment handling.

Sequence Diagram

sequenceDiagram
    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
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related issues

Possibly related PRs

Suggested reviewers

  • bma-d

Poem

🐇 I nibble code and plant a root,
Retros leap where helpers boot.
Markers move to runtime ground,
Hooks install and calm the sound.
Hooray — the orchestrator's new route!

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

@dickymoore
dickymoore force-pushed the dicky/codex-runtime-support branch from 7707edc to d07c0ff Compare May 1, 2026 17:35
@dickymoore dickymoore changed the title Add Codex runtime layout support Add Codex runtime support May 2, 2026
@dickymoore
dickymoore marked this pull request as ready for review May 3, 2026 08:53
@dickymoore
dickymoore requested a review from bma-d as a code owner May 3, 2026 08:53
@dickymoore
dickymoore requested a review from bmadcode May 3, 2026 08:54
@augmentcode

augmentcode Bot commented May 3, 2026

Copy link
Copy Markdown
🤖 Augment PR Summary

Summary: 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:

  • Introduced a deterministic runtime layout helper (runtime_layout.py) to infer provider, resolve active skill roots, and compute project-scoped marker paths.
  • Made marker/state-file resolution runtime-aware (no longer hard-coded to .claude), and added orchestrator-helper marker path to expose the resolved marker path + gitignore entry.
  • Refactored stop-hook installation into a provider-aware implementation (stop_hooks.py) supporting Claude (.claude/settings.json) and Codex (.codex/hooks.json + .codex/config.toml).
  • Added a retro-agent helper action and updated retrospective spawning to use the configured retro agent rather than forcing Claude.
  • Updated runtime policy asset/path resolution to support “portable” skill paths across .claude/.agents/.codex roots.
  • Extended docs and payload step instructions to use the new runtime-aware helper commands.
  • Added unit tests covering runtime layout detection, marker path behavior, Codex hook config mutation, and retro agent resolution.

Technical Notes: Provider selection can be forced via env vars (e.g., AI_AGENT), Codex hook enablement is ensured via TOML edits, and marker location is decoupled from provider to follow the active installed skill root where possible.

🤖 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. 2 suggestions posted.

Fix All in Augment

Comment augment review to trigger a new review at any time.

Comment thread source/src/story_automator/core/runtime_layout.py

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

Actionable comments posted: 3

🧹 Nitpick comments (1)
source/src/story_automator/commands/orchestrator_epic_agents.py (1)

233-331: 💤 Low value

Consider documenting the expected YAML structure.

This custom indentation-based YAML parser handles the agentConfig frontmatter 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

📥 Commits

Reviewing files that changed from the base of the PR and between 9e7dff8 and 0e8e613.

📒 Files selected for processing (30)
  • README.md
  • docs/agents-and-monitoring.md
  • docs/story-execution.md
  • payload/.claude/skills/bmad-story-automator/data/agent-config-prompts.md
  • payload/.claude/skills/bmad-story-automator/data/marker-file-format.md
  • payload/.claude/skills/bmad-story-automator/data/orchestrator-rules-appendix.md
  • payload/.claude/skills/bmad-story-automator/data/retrospective-automation.md
  • payload/.claude/skills/bmad-story-automator/data/scripts-reference.md
  • payload/.claude/skills/bmad-story-automator/data/stop-hook-config.md
  • payload/.claude/skills/bmad-story-automator/data/stop-hook-recovery.md
  • payload/.claude/skills/bmad-story-automator/data/stop-hook-troubleshooting.md
  • payload/.claude/skills/bmad-story-automator/data/tmux-long-command-testing.md
  • payload/.claude/skills/bmad-story-automator/steps-c/step-01-init.md
  • payload/.claude/skills/bmad-story-automator/steps-c/step-01b-continue.md
  • payload/.claude/skills/bmad-story-automator/steps-c/step-02a-preflight-config.md
  • payload/.claude/skills/bmad-story-automator/steps-c/step-02b-preflight-finalize.md
  • payload/.claude/skills/bmad-story-automator/steps-c/step-03b-execute-finish.md
  • payload/.claude/skills/bmad-story-automator/steps-c/step-04-wrapup.md
  • payload/.claude/skills/bmad-story-automator/workflow.md
  • source/src/story_automator/commands/basic.py
  • source/src/story_automator/commands/orchestrator.py
  • source/src/story_automator/commands/orchestrator_epic_agents.py
  • source/src/story_automator/commands/state.py
  • source/src/story_automator/core/agent_config.py
  • source/src/story_automator/core/runtime_layout.py
  • source/src/story_automator/core/runtime_policy.py
  • source/src/story_automator/core/stop_hooks.py
  • source/tests/test_runtime_layout.py
  • source/tests/test_state_policy_metadata.py
  • source/tests/test_stop_hooks.py
💤 Files with no reviewable changes (1)
  • payload/.claude/skills/bmad-story-automator/steps-c/step-02a-preflight-config.md

Comment thread payload/.claude/skills/bmad-story-automator/data/stop-hook-troubleshooting.md Outdated
Comment thread tests/test_stop_hooks.py
Comment thread tests/test_stop_hooks.py

@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 (2)
docs/installation-and-layout.md (1)

22-27: ⚡ Quick win

Clarify 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 value

Optional: preserve a space before the trailing comment.

When rewriting an inline features = { ... } # comment, the regex consumes the whitespace between } and #, and the formatter then concatenates comment directly, 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

📥 Commits

Reviewing files that changed from the base of the PR and between 0e8e613 and ccedfad.

📒 Files selected for processing (14)
  • README.md
  • docs/agents-and-monitoring.md
  • docs/cli-reference.md
  • docs/how-it-works.md
  • docs/installation-and-layout.md
  • docs/troubleshooting.md
  • payload/.claude/skills/bmad-story-automator/data/stop-hook-troubleshooting.md
  • source/src/story_automator/commands/basic.py
  • source/src/story_automator/commands/orchestrator_epic_agents.py
  • source/src/story_automator/core/runtime_layout.py
  • source/src/story_automator/core/stop_hooks.py
  • source/tests/test_runtime_layout.py
  • source/tests/test_state_policy_metadata.py
  • source/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

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

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

📥 Commits

Reviewing files that changed from the base of the PR and between ccedfad and 01f412d.

📒 Files selected for processing (8)
  • README.md
  • docs/agents-and-monitoring.md
  • docs/cli-reference.md
  • docs/how-it-works.md
  • docs/installation-and-layout.md
  • docs/troubleshooting.md
  • source/src/story_automator/core/stop_hooks.py
  • source/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

Comment thread skills/bmad-story-automator/src/story_automator/core/stop_hooks.py
Comment thread skills/bmad-story-automator/src/story_automator/core/stop_hooks.py

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

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-state still requires aiCommand, contradicting the new deprecated/empty default.

The state template now defaults aiCommand to "" (deprecated, replaced by agentConfig), and cmd_build_state_doc no longer enforces a non-empty value. However, cmd_validate_state still calls required("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 aiCommand is optional when agentConfig is 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 win

Resolve the default agent after root is known.

agent_type() now returns a concrete provider, so these if agent == "auto" branches only run for an explicit --agent auto. In the default path, provider inference still happens before get_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 with runtime_provider(root) once root is 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 win

Use 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:

  1. Using consistent curly-brace syntax: {installed-skill-root}
  2. Adding a note explaining the expected values for Claude vs Codex, or
  3. 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 value

Defensive check on agent_config.get('defaultFallback') returning None.

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 sets defaultFallback: null in the input config JSON, this renders as defaultFallback: null in the generated YAML, which agent_config.py's effective resolution logic may not treat as "disabled" the same way false is. Consider coercing None to False here 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

📥 Commits

Reviewing files that changed from the base of the PR and between 01f412d and fdfc310.

📒 Files selected for processing (27)
  • README.md
  • docs/cli-reference.md
  • docs/how-it-works.md
  • docs/installation-and-layout.md
  • docs/troubleshooting.md
  • install.sh
  • payload/.claude/skills/bmad-story-automator-review/instructions.xml
  • payload/.claude/skills/bmad-story-automator/data/agent-fallback-troubleshooting.md
  • payload/.claude/skills/bmad-story-automator/data/agent-fallback.md
  • payload/.claude/skills/bmad-story-automator/data/code-review-loop.md
  • payload/.claude/skills/bmad-story-automator/data/orchestration-policy.json
  • payload/.claude/skills/bmad-story-automator/data/preflight-prompts.md
  • payload/.claude/skills/bmad-story-automator/data/retrospective-automation.md
  • payload/.claude/skills/bmad-story-automator/data/retrospective-prompts.md
  • payload/.claude/skills/bmad-story-automator/data/stop-hook-config.md
  • payload/.claude/skills/bmad-story-automator/data/stop-hook-troubleshooting.md
  • payload/.claude/skills/bmad-story-automator/data/tmux-commands.md
  • payload/.claude/skills/bmad-story-automator/data/workflow-commands.md
  • payload/.claude/skills/bmad-story-automator/templates/state-document.md
  • scripts/smoke-test.sh
  • source/src/story_automator/commands/orchestrator_epic_agents.py
  • source/src/story_automator/commands/state.py
  • source/src/story_automator/commands/tmux.py
  • source/src/story_automator/core/agent_config.py
  • source/src/story_automator/core/runtime_layout.py
  • source/src/story_automator/core/tmux_runtime.py
  • source/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

Comment thread payload/.claude/skills/bmad-story-automator/data/tmux-commands.md Outdated
Comment thread source/src/story_automator/commands/orchestrator_epic_agents.py Outdated
Comment thread source/src/story_automator/core/agent_config.py Outdated
@dickymoore

Copy link
Copy Markdown
Contributor Author

Review follow-up summary:

  • Fixed runtime marker fallback, explicit agent normalization, installed-root docs, null fallback handling, and tmux root-aware agent selection in 6ebbd06 and 5580dc2.
  • Confirmed existing fixes for non-dict perTask, portable path escaping, and agentConfig-only state validation are already on the branch.
  • Deferred cleanup/cosmetic stop-hook items are listed in the PR description under Proposed Follow-ups item 8; the bespoke frontmatter parser follow-up remains item 7.

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

Actionable comments posted: 2

🧹 Nitpick comments (4)
docs/installation-and-layout.md (3)

22-33: ⚡ Quick win

Clarify when to use .agents/skills vs provider-specific roots.

The documentation lists three supported roots but doesn't explain when operators should use the generic .agents/skills versus provider-specific roots like .codex/skills. Consider adding a brief note about:

  • Is .agents/skills a 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 win

Consider 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.md files
  • Installed skill root: The qualifying root where bmad-story-automator gets installed

This 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 value

Consider 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_bundle will fail with an opaque FileNotFoundError if the payload is absent

shutil.copytree raises FileNotFoundError directly if payload/.claude/skills/bmad-story-automator or bmad-story-automator-review doesn'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

📥 Commits

Reviewing files that changed from the base of the PR and between fdfc310 and df3131c.

📒 Files selected for processing (14)
  • README.md
  • docs/installation-and-layout.md
  • install.sh
  • payload/.claude/skills/bmad-story-automator/data/orchestration-policy.json
  • payload/.claude/skills/bmad-story-automator/data/tmux-commands.md
  • scripts/smoke-test.sh
  • source/src/story_automator/commands/orchestrator_epic_agents.py
  • source/src/story_automator/commands/state.py
  • source/src/story_automator/commands/tmux.py
  • source/src/story_automator/core/agent_config.py
  • source/src/story_automator/core/runtime_layout.py
  • source/src/story_automator/core/runtime_policy.py
  • source/tests/test_runtime_layout.py
  • source/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

Comment thread skills/bmad-story-automator/src/story_automator/commands/state.py Outdated
Comment thread skills/bmad-story-automator/src/story_automator/commands/tmux.py Outdated
@dickymoore dickymoore changed the title Add Codex runtime support feat: add Codex runtime support May 8, 2026
@dickymoore
dickymoore force-pushed the dicky/codex-runtime-support branch from 0217624 to cf96221 Compare May 8, 2026 17:29

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

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.

Comment thread skills/bmad-story-automator/src/story_automator/core/runtime_layout.py Outdated
Comment thread skills/bmad-story-automator/src/story_automator/core/stop_hooks.py
Comment thread skills/bmad-story-automator/src/story_automator/core/agent_config.py Outdated
Comment thread docs/installation-and-layout.md Outdated
Comment thread docs/installation-and-layout.md Outdated
Comment thread tests/test_runtime_layout.py Outdated
Comment thread docs/cli-reference.md Outdated
Comment thread docs/troubleshooting.md Outdated
Comment thread skills/bmad-story-automator/src/story_automator/commands/basic.py Outdated
Comment thread skills/bmad-story-automator/src/story_automator/commands/tmux.py
@dickymoore

Copy link
Copy Markdown
Contributor Author

Merge-blocker rework is now pushed on dicky/codex-runtime-support at b3a4c9e.

Addressed in this pass:

  • runtime/provider layout no longer reads AI_AGENT; child-agent selection is now separate from top-level hook/config behavior
  • legacy missing/unversioned fallback config preserves the prior Codex fallback instead of silently disabling fallback
  • Codex stop-hook setup now reports configured vs pending-trust vs verified instead of always implying the safeguard is active
  • installed stop-hook commands export PROJECT_ROOT so marker resolution does not depend on the caller's working directory
  • legacy AI_COMMAND classification is now consistent with build/monitor behavior
  • contract-critical docs and validation guidance were updated to match the corrected runtime model

Validation run for this pass:

  • 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 -q
  • result: 126 passed

Intentionally deferred to tightly-scoped follow-up PRs:

  1. CLI/troubleshooting doc copy-paste ergonomics
  2. remaining state/validation doc alignment
  3. test robustness cleanup
  4. low-risk stop-hook/internal cleanup

@dickymoore

Copy link
Copy Markdown
Contributor Author

Cross-PR summary for the current Automator/Codex work:

  1. bmad-code-org/bmad-automator#3
    feat: add Codex runtime support
    Main runtime PR. This is the behavior change set in Automator itself: Codex runtime support, separation of runtime/provider detection from child-agent selection, truthful Codex stop-hook verification state, legacy fallback preservation, deterministic project-root marker lookup, legacy AI_COMMAND consistency, macOS-safe runtime-layout test normalization, and the accompanying runtime/state/operator doc alignment.

  2. bmad-code-org/BMAD-METHOD#2345
    feat(installer): add BMad Automator module
    Installer foundation PR. This registers Automator as an installable BMAD module, adds the installer/module metadata, handles external source-root module resolution, and adds the installer/docs/test coverage needed for Automator to exist in the BMAD installer flow.

  3. bma-d/BMAD-METHOD#3
    Support bmad-automator runtime copy in BMAD installer
    Stacked installer follow-up. This addresses the current packaging gap where the installed bmad-story-automator skill needs its Python runtime directories copied into place. Scope is intentionally narrow to the bma source-root install path and does not broaden installer semantics.

  4. bmad-code-org/bmad-plugins-marketplace#15
    Register BMad Automator in official registry
    Marketplace/discoverability PR. This adds the official registry entry so Automator is discoverable through normal marketplace-backed installer flows, with the module marked experimental and the current runtime/platform expectations documented.

Short version: bmad-automator#3 is the runtime behavior PR, BMAD-METHOD#2345 + bma-d/BMAD-METHOD#3 are the installer path PRs, and bmad-plugins-marketplace#15 is the registry/discoverability PR.

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

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.

@bma-d

bma-d commented May 18, 2026

Copy link
Copy Markdown
Collaborator

Effectively merged in the commits

a05f635
386421e
76447ba

and made available via

npx --yes bmad-method@latest install --modules automator --next automator --tools codex --yes

@bma-d bma-d closed this May 18, 2026
mira5557373 added a commit to mira5557373/bmad-automator that referenced this pull request Jun 16, 2026
…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>
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants