Skip to content

feat(adapters): add Mistral Vibe profile + vibe-hooks-toml dialect - #413

Draft
ayhid wants to merge 1 commit into
bmad-code-org:mainfrom
ayhid:feat/mistral-vibe-profile
Draft

feat(adapters): add Mistral Vibe profile + vibe-hooks-toml dialect#413
ayhid wants to merge 1 commit into
bmad-code-org:mainfrom
ayhid:feat/mistral-vibe-profile

Conversation

@ayhid

@ayhid ayhid commented Jul 31, 2026

Copy link
Copy Markdown

Drafted per the Discord thread with @jeremy — flagging early rather than dropping it unannounced, and happy to close it if the pre-1.0 stance on new CLIs means "not yet".

What

Adds mistral-vibe (alias vibe), driving Mistral's vibe CLI through the existing generic tmux + hook-signal transport.

It is not the usual zero-Python profile add. Vibe is the first CLI whose hook config is not JSON: .vibe/hooks.toml is a flat hooks = [...] array of tables whose entries name their own event in a type field rather than being keyed by it. So this also adds a sixth dialect, vibe-hooks-toml.

Why it touches install.py

Hook-config I/O was JSON-shaped at four read sites and three write sites. Those now go through a dialect-aware load_hook_config / dump_hook_config seam; every JSON dialect emits byte-identical output, so the existing merge tests characterize the refactor.

The TOML writer is hand-rolled (~25 lines) rather than a new dependency — tomlkit ships only with the [tui] extra, and vibe's HookConfig schema is closed and entirely scalar. It raises ProfileError on a non-scalar rather than emitting something vibe would misparse. Known trade-off, documented in the docstring: re-emitting drops comments in the user's hooks.toml, which only happens when merge_hooks reports changed.

Profile facts, pinned against vibe 2.23.2

  • post_agent is the only turn-end event — no SessionStart / SessionEnd / PreCompact analogue — and it fires per response turn, so the profile ships stop_without_result_nudges = 5, the same reason copilot's agentStop needed it.
  • --trust is load-bearing. Vibe reads project hooks only in a trusted directory; without it no Stop ever arrives and every session reads as a timeout. Trust is per-invocation rather than an exact-path store, so isolation = "worktree" keeps working — vibe does not inherit antigravity's isolation = "none" restriction.
  • Vibe has no --model flag; the model comes from VIBE_ACTIVE_MODEL or vibe's own config, so [adapter] model must stay empty. generic.py only appends model_flag when a model is set, so this is a documented limitation, not a crash.
  • Payload keys are snake_case (session_id, transcript_path, cwd), so bmad_loop_hook.py is unchanged.

Testing

  • Vibe's own pydantic hook loader parses the emitted file with zero issues, including a command carrying quotes and backslashes and a pre-existing user hook alongside ours.
  • initvalidate → re-init on a scratch project: registers, detects, idempotent, and a hand-added decoy user hook survives untouched.
  • New tests: merge_hooks vibe cases (shape, idempotency, user-hook preservation, the [BUG] bmad-loop validate and probe fail to recognize registered hooks for antigravity adapter #159 marker guarantee, malformed-shape refusal), a tomllib round-trip over the emitter, install_into end-to-end. Added mistral-vibe to the existing dialect parametrizations in test_probe.py and the profile assertions in test_profile.py.
  • Ablation-checked both "is refused" tests — deleting each guard makes its test fail.
  • Full suite passes (3 pre-existing macOS-only failures around non-UTF-8 filenames, unrelated).

Known gaps

  • usage_parser = "none" is pending, not final. The live --probe is blocked on vibe's one-time interactive onboarding wizard, so the hook payload and the token schema in ~/.vibe/logs/session/*/messages.jsonl are inferred from reading vibe's source rather than observed. I'll re-probe after vibe --setup and follow up; if messages.jsonl carries usage, the parser is a separate small PR.
  • Subagent turn-ends. Vibe's subagents inherit the parent's hook config and fire post_agent with a non-empty transcript_path, so copilot's subagent_stop_without_transcript mechanism won't filter them (they carry a non-null parent_session_id instead). I've left this to artifact-gating plus the raised nudge floor rather than adding a profile knob unilaterally — happy to add one if you'd rather, but it seemed like your call.

Marked draft since the two above are open and the pre-1.0 stance on new CLIs is yours to set.

Drives Mistral's `vibe` CLI (>= 2.23) through the generic tmux +
hook-signal transport, as `mistral-vibe` (alias `vibe`).

Vibe is the first CLI whose hook config is not JSON: `.vibe/hooks.toml`
is a flat `hooks = [...]` array of tables whose entries name their own
event in a `type` field rather than being keyed by it. Hook-config I/O
therefore moves behind a dialect-aware `load_hook_config` /
`dump_hook_config` seam; every JSON dialect emits byte-identical output
as before. The TOML writer is hand-rolled — tomlkit ships only with the
[tui] extra — and refuses a non-scalar rather than emitting something
vibe would misparse.

Profile facts pinned against 2.23.2:

- `post_agent` is the only turn-end event (no SessionStart / SessionEnd
  / PreCompact analogue) and fires per response turn, so the profile
  ships stop_without_result_nudges = 5, as copilot's agentStop needed.
- `--trust` is mandatory: vibe reads project hooks only in a trusted
  directory, and without it no Stop ever arrives and every session
  reads as a timeout. Trust is per-invocation rather than an exact-path
  store, so isolation = "worktree" keeps working (unlike antigravity).
- Vibe exposes no --model flag; the model comes from VIBE_ACTIVE_MODEL
  or vibe's own config, so `[adapter] model` must stay empty.
- usage_parser = "none" pending a probe of
  ~/.vibe/logs/session/*/messages.jsonl.
@coderabbitai

coderabbitai Bot commented Jul 31, 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 Plus

Run ID: 562752ac-d98b-409e-b044-2af18cc1bea4

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

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