Files
rune/scripts/gen_mode_matrix.py
Nick df088fbe30 feat: THE CONVERGENCE LANDING — six waves, one tree
- GO CORE TURN ENGINE (src/go/rune-core): per-channel single-flight,
  channel-keyed context, K25 tail+snapshot store (D40 seam), live
  Ollama with the thinking split, PTY parity proven with Python
  stopped + the owner's 27B live with thinking surfaced
- FULL-WIDTH SHARED BOX (D36): spans both panes, focus-signage
  placeholder, drafts survive focus round-trips, PTY-proven 110+140
- MODEL SPLIT (D37): pane-aware /model, per-pane record fields,
  status line; letter-stealing round 2 fixed (j/k type)
- APPROVAL CARD (T5): gold y/n through /v1/approvals/resolve, stale
  revalidation, chained asks, PTY-proven both legs
- PROVIDERS P1: 18 providers both languages, three-artifact drift
  gate, provider step in first-run (real names, no 'local' hardcode)
- SCROLLBACK: /history depth persisted, ctrl+up/down + pgup/pgdn,
  dim ↑N indicator, deeper-page fetch, PTY-proven on real history
- RETIREMENT rows 4+10 advanced: tool-executor dispatch wired
  (49/49+48/48), Go mode-gate door built, work-manager CLI parity
2026-10-19 00:02:18 +01:00

142 lines
5.5 KiB
Python

#!/usr/bin/env python3
"""gen_mode_matrix.py — ONE mode matrix, three doors (wave-tools-live T1;
local-tool-runtime joined 2026-10-19, retirement campaign tools row).
The frozen CHAT/TASK/PLAN matrix lives ONCE in
schemas/mode/v1/mode_matrix.json. The Go authorities (toolexec mode
enforcement + local-tool-runtime dispatch gate) each embed a copy
because go:embed cannot reach above their module; this script generates
those copies BYTE-IDENTICALLY and ``--check`` makes drift structurally
impossible (the boundary-codegen single-source law, mirrored):
- a hand-edited embedded copy trips --check RED (byte-diff vs regen),
- a canonical change without regeneration trips --check RED,
- a structurally invalid matrix (modes/classes/executor-tool names)
refuses to generate at all.
Python reads the canonical artifact directly (rune.mode_gate) — no
generation for the Python side.
"""
from __future__ import annotations
import argparse
import json
import sys
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
CANONICAL = ROOT / "schemas" / "mode" / "v1" / "mode_matrix.json"
EMBEDDED = [
ROOT / "src" / "go" / "tool-executor" / "internal" / "toolexec" / "mode_matrix.json",
ROOT / "src" / "go" / "local-tool-runtime" / "mode_matrix.json",
]
HARD_MODES = {"CHAT", "TASK", "PLAN"}
GO_CLASS_VOCABULARY = {
"read", "research.queue", "research.run", "plan.draft", "plan.revise",
"viability.check", "task.create", "task.start", "build", "edit", "shell",
}
def _validate(data: dict) -> list[str]:
errors: list[str] = []
if data.get("schema_version") != 1:
errors.append("schema_version must be 1")
modes = data.get("modes")
classes = data.get("tool_classes")
if not isinstance(modes, dict) or set(modes) != HARD_MODES:
errors.append(f"modes must be exactly {sorted(HARD_MODES)}")
return errors
if not isinstance(classes, dict) or set(classes) != GO_CLASS_VOCABULARY:
errors.append("tool_classes must be exactly the frozen Go vocabulary "
f"{sorted(GO_CLASS_VOCABULARY)}")
return errors
for mode, spec in modes.items():
if not isinstance(spec, dict):
errors.append(f"mode {mode} must be an object")
continue
for field in ("model", "context", "rules"):
if not isinstance(spec.get(field), str) or not spec.get(field):
errors.append(f"mode {mode} must hard-set {field}")
named = spec.get("classes")
if not isinstance(named, list) or not named:
errors.append(f"mode {mode} names no classes")
continue
for name in named:
if name not in classes:
errors.append(f"mode {mode} names unknown class {name!r}")
# Executor-tool names must exist in the Python registry the chat door
# dispatches to (two languages, one vocabulary half).
sys.path.insert(0, str(ROOT / "src"))
try:
from rune.tool_executor import TOOLS # noqa: E402
registry = {tool["name"] for tool in TOOLS}
except Exception as exc: # pragma: no cover - registry drift is a gate failure
errors.append(f"tool_executor registry unreadable: {exc}")
return errors
for class_name, spec in classes.items():
if not isinstance(spec, dict):
errors.append(f"class {class_name} must be an object")
continue
tools = spec.get("executor_tools")
if not isinstance(tools, list):
errors.append(f"class {class_name} executor_tools must be a list")
continue
for name in tools:
if name not in registry:
errors.append(f"class {class_name} maps unknown executor tool {name!r}")
status = spec.get("status", "")
if status == "not_implemented" and tools:
errors.append(f"class {class_name} is not_implemented but maps executor tools")
return errors
def main() -> int:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--check", action="store_true",
help="verify the embedded copy is byte-identical; exit 1 on drift")
args = parser.parse_args()
try:
raw = CANONICAL.read_bytes()
data = json.loads(raw)
except (OSError, ValueError) as exc:
print(f"mode matrix canonical artifact unreadable: {exc}", file=sys.stderr)
return 1
errors = _validate(data)
if errors:
for error in errors:
print("mode matrix INVALID:", error, file=sys.stderr)
return 1
if args.check:
failed = False
for embedded in EMBEDDED:
try:
current = embedded.read_bytes()
except OSError:
print(f"CHECK FAILED: embedded copy missing ({embedded}) — run scripts/gen_mode_matrix.py",
file=sys.stderr)
failed = True
continue
if current != raw:
print(f"CHECK FAILED: embedded copy drifted from the canonical artifact ({embedded}) — "
"run scripts/gen_mode_matrix.py", file=sys.stderr)
failed = True
if failed:
return 1
print("mode matrix: embedded copies byte-identical to canonical")
return 0
for embedded in EMBEDDED:
embedded.parent.mkdir(parents=True, exist_ok=True)
embedded.write_bytes(raw)
print(f"mode matrix: embedded copy written ({len(raw)} bytes) -> {embedded}")
return 0
if __name__ == "__main__":
sys.exit(main())