Skip to content

feat(relay): shared event-write primitive + bmad-loop relay <Event>#238

Draft
pbean wants to merge 1 commit into
mainfrom
feat/generic-relay
Draft

feat(relay): shared event-write primitive + bmad-loop relay <Event>#238
pbean wants to merge 1 commit into
mainfrom
feat/generic-relay

Conversation

@pbean

@pbean pbean commented Jul 21, 2026

Copy link
Copy Markdown
Collaborator

What

Factor the relay event-write logic — already duplicated between data/bmad_loop_hook.py and PR #226's hermes_hooks.relay_event (identical env-gating, casing-tolerant field extraction, atomic events/<ts>-<task>-<event>.json write) — into one core primitive that owns the event schema, and add a general bmad-loop relay <Event> command over it.

This is the general, in-tree counterpart to bmad_loop_hook.py for global/user-scoped-hook adapters — NOT Hermes-specific. An out-of-tree adapter whose CLI registers a single user-scoped hook (rather than copying the per-project script) points that hook at bmad-loop relay Stop and needs zero per-adapter relay code.

Changes

  • New src/bmad_loop/events.pywrite_relay_event(event_name, payload, environ) -> Path | None (+ build_event). Owns the canonical schema (ts, event, task_id, session_id, transcript_path, cwd) and the env-gated (BMAD_LOOP_RUN_DIR + BMAD_LOOP_TASK_ID), atomic write. No-ops outside an active bmad-loop session.
  • bmad-loop relay <Event> in cli.py — reads the hook payload from stdin and defers to the primitive. event choices come from CANONICAL_EVENTS (never a hardcoded set).
  • data/bmad_loop_hook.py — behavior unchanged. It keeps its own inlined copy on purpose: it is installed into target projects as a lone, stdlib-only file and run as a bare subprocess where the bmad_loop package need not be importable, so it cannot import the primitive. Added a docstring note documenting the deliberate duplication and the sync guard.

Tests (tests/test_relay_event.py)

  • primitive: active-env write, no-env / partial-env no-op, camelCase + workspacePaths fallbacks, unusable-workspacePaths degrades to None
  • CLI stdin path, no-env no-op, garbage-stdin tolerance, non-canonical event rejected (parse error), every canonical event accepted
  • schema parity: the standalone script (run as a real subprocess) and the primitive emit byte-identical events across every payload dialect — pins the inlined copy to the primitive so they cannot drift
  • existing tests/test_hook_script.py behavior unchanged (incl. test_installed_copy_matches_source)

Verification

  • Full pytest green in both TMUX states (TMUX-unset and inside a real tmux session): 2717 passed, 7 skipped. The one failure (test_tui_app.py::test_validate_findings_multiline_message_keeps_column_alignment) is pre-existing and unrelated — an environment-sensitive TUI rendering-width test that fails identically on untouched main.
  • Full trunk check — no issues.

Context

Session 2 of the PR #226 review plan (make the coding-CLI adapter axis extensible out-of-tree). Independent of the other sessions. The out-of-tree Hermes package (a later session) is where hermes_hooks.relay_event gets deleted in favor of pointing its hook at this command.

The event-write body was duplicated between data/bmad_loop_hook.py and
PR #226's hermes_hooks.relay_event (identical env-gating, casing-tolerant
field extraction, atomic events/<ts>-<task>-<event>.json write). Factor the
schema + write into one core primitive, bmad_loop.events.write_relay_event,
and add a general `bmad-loop relay <Event>` command over it that reads the
hook payload from stdin.

This is the general, in-tree counterpart to bmad_loop_hook.py for
global/user-scoped-hook adapters: such an adapter (e.g. an out-of-tree Hermes
package) points its native hook at `bmad-loop relay Stop` and needs zero
per-adapter relay code. Not Hermes-specific.

data/bmad_loop_hook.py keeps its own inlined copy on purpose — it is installed
into target projects as a lone, stdlib-only file and run as a bare subprocess
where the bmad_loop package need not be importable, so it cannot import the
primitive. Its behavior is unchanged; a parity test in tests/test_relay_event.py
pins the inlined copy and the primitive to the same schema across every payload
dialect so they cannot drift.

Event choices come from CANONICAL_EVENTS (never a hardcoded set). Tests cover
the active-env write, the no-env no-op, the CLI stdin path, event-name gating,
and standalone/primitive schema parity.
@coderabbitai

coderabbitai Bot commented Jul 21, 2026

Copy link
Copy Markdown

Important

Review skipped

Draft detected.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 9bcb717e-c331-4513-8e94-b20c4bab156f

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/generic-relay

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant