feat(fleet): opus-fable adoption — doctrine + discipline hooks (v0.18.0 A)

Fleet behaves more like Fable 5 on existing model tiers, behind
ROBOCO_FABLE_MODE_ENABLED (config default off; armed :-true on the NAS compose,
absent from the registry compose).

- Doctrine: vendored agents/prompts/doctrine/fable.md composed into every
  agent's system prompt via fable_doctrine_layer() after base.md.
- Hooks (Claude Code): 4 non-overlapping hooks (stop-gate/bash-discipline/
  honesty-nudge/precompact) appended per-agent via _fable_hook_groups(). The
  make-quality + lint-suppression duplicates are deliberately NOT added (already
  gate-enforced); session-start skipped.
- Hooks (grok): conservative V1 — only the non-denying honesty-nudge, since a
  grok hook deny cancels the whole run.
- Flag on the feature-flags card; hook scripts shipped into the agent image.

Flag-off spawn path proven byte-identical (worktree diff, sha256 match); full
suite green (2074 unit + e2e-smoke + hook harness), mypy/xenon/ruff clean.
Fixed a real stdin bug in the vendored stop-gate hook (heredoc + pipe both
claimed stdin). Distilled from rennf93/opus-fable-playbook (MIT).
This commit is contained in:
Renn F
2026-07-04 06:44:40 +02:00
parent 30289333da
commit 7716830322
24 changed files with 881 additions and 5 deletions
@@ -5,6 +5,7 @@ from __future__ import annotations
import json
import tomllib
from typing import TYPE_CHECKING
from unittest.mock import patch
from roboco.llm.providers import grok_cli_config as gc
@@ -175,3 +176,29 @@ def test_write_grok_hooks_noops_when_script_absent(tmp_path: Path) -> None:
is False
)
assert not hooks_dir.exists()
def test_write_grok_fable_hooks_writes_honesty_nudge_when_enabled(
tmp_path: Path,
) -> None:
hooks_dir = tmp_path / "hooks"
with (
patch("roboco.config.settings.fable_mode_enabled", True),
patch(
"roboco.llm.providers.grok_cli_config.FABLE_HONESTY_NUDGE_HOOK",
"/app/scripts/fable-honesty-nudge-hook.sh",
),
patch("pathlib.Path.is_file", return_value=True),
):
result = gc.write_grok_fable_hooks(hooks_dir=hooks_dir)
assert result is True
written = json.loads((hooks_dir / "roboco-fable-honesty-nudge.json").read_text())
assert written["hooks"]["PostToolUse"][0]["matcher"] == "Bash"
def test_write_grok_fable_hooks_noop_when_disabled(tmp_path: Path) -> None:
hooks_dir = tmp_path / "hooks"
with patch("roboco.config.settings.fable_mode_enabled", False):
result = gc.write_grok_fable_hooks(hooks_dir=hooks_dir)
assert result is False
assert not hooks_dir.exists()