Merge feature/operator-workbench into unify/skills-20261111

This commit is contained in:
Kevin Veen-Birkenbach committed 2026-11-11 22:51:51 +00:00
commit 6b75be39d5
12 files changed
+771 -9

No files matched your search

+3 -2
View File
@@ -47,11 +47,12 @@ class HubCase(unittest.TestCase):
self.projects = base / "projects"
self.tree_file = base / "tree" / "branch-tree.json"
self.timers = base / "timers"
self.kept = base / "kept"
self.cleanup = base / "state" / "cleanup"
self.offices = base / "state" / "offices"
self.approvers = base / "config" / "approvers.json"
self.replay = base / "replay"
for path in (self.pages, self.state, self.outside, self.projects, self.tree_file.parent, self.timers):
for path in (self.pages, self.state, self.outside, self.projects, self.tree_file.parent, self.timers, self.kept):
path.mkdir()
silence = mock.patch.object(server.Handler, "log_message")
silence.start()
@@ -70,7 +71,7 @@ class HubCase(unittest.TestCase):
transcripts=self.projects,
scripts=self.scripts,
templates=HUB / "templates",
places=server.Places(tree_file=self.tree_file, timers=self.timers, vendor=HUB / "vendor", cleanup=self.cleanup, offices=self.offices),
places=server.Places(tree_file=self.tree_file, timers=self.timers, vendor=HUB / "vendor", cleanup=self.cleanup, offices=self.offices, kept=self.kept),
model=server.Model(url="", model="", key=""),
engines=self.engines,
approvers=self.approvers,
+94
View File
@@ -0,0 +1,94 @@
import json
import os
import subprocess
import sys
import tempfile
import unittest
from pathlib import Path
GATES = Path(__file__).resolve().parents[1] / "skills" / "kvb-check-gate" / "scripts" / "gates.py"
def git(repo: Path, *args: str) -> str:
return subprocess.run(["git", *args], cwd=repo, capture_output=True, text=True, check=True).stdout.strip()
class TestGates(unittest.TestCase):
def setUp(self):
scratch = tempfile.TemporaryDirectory()
self.addCleanup(scratch.cleanup)
self.base = Path(scratch.name)
self.repo = self.base / "repo"
self.repo.mkdir()
git(self.repo, "init", "-q", "-b", "main")
git(self.repo, "config", "user.email", "t@example.org")
git(self.repo, "config", "user.name", "t")
self.commit("a")
self.env = {**os.environ, "GATES_DB": str(self.base / "gates.db")}
def commit(self, name: str) -> None:
(self.repo / name).write_text(name, encoding="utf-8")
git(self.repo, "add", name)
git(self.repo, "commit", "-q", "-m", name)
def gates(self, *args: str) -> subprocess.CompletedProcess:
return subprocess.run(
[sys.executable, str(GATES), *args], cwd=self.repo, env=self.env, capture_output=True, text=True, check=False
)
def resolve(self, branch: str, action: str = "merge") -> str:
return self.gates("resolve", "--repo", str(self.repo), "--branch", branch, "--action", action).stdout.strip()
def test_a_branch_takes_its_own_rule_else_the_strictest_of_all_parents_else_none(self):
repo = str(self.repo)
self.assertEqual(self.resolve("fix"), "none\t-")
for parent, level in (("iam", "test"), ("production", "quality-high")):
self.gates("rule", "set", "--repo", repo, "--branch", parent, "--action", "merge", "--level", level)
self.gates("parents", "add", "--repo", repo, "--branch", "fix", "--parent", parent)
self.assertEqual(self.resolve("fix"), "quality-high\tproduction")
self.gates("rule", "set", "--repo", repo, "--branch", "fix", "--action", "merge", "--level", "quick")
self.assertEqual(self.resolve("fix"), "quick\tfix")
self.assertEqual(self.resolve("fix", "push"), "none\t-")
def test_derive_gives_each_branch_its_nearest_ancestor_tips_and_the_default_branch_none(self):
git(self.repo, "checkout", "-q", "-b", "feature")
self.commit("b")
git(self.repo, "checkout", "-q", "-b", "fix")
self.commit("c")
derived = self.gates("parents", "derive", "--repo", str(self.repo)).stdout.splitlines()
self.assertEqual(sorted(derived), ["feature\tmain\tderived", "fix\tfeature\tderived"])
shown = json.loads(self.gates("show", "--json", "--repo", str(self.repo)).stdout)
branches = next(iter(shown.values()))
self.assertEqual(branches["main"]["parents"], [])
self.assertEqual(branches["fix"]["parents"], [{"parent": "feature", "source": "derived"}])
def test_the_tree_covers_unstaged_and_untracked_files_but_not_ignored_ones(self):
tree = lambda: self.gates("tree", "--worktree", str(self.repo)).stdout.strip()
head = git(self.repo, "rev-parse", "HEAD^{tree}")
self.assertEqual(tree(), head)
(self.repo / ".gitignore").write_text("ignored\n", encoding="utf-8")
git(self.repo, "add", ".gitignore")
git(self.repo, "commit", "-q", "-m", "ignore")
(self.repo / "ignored").write_text("x", encoding="utf-8")
self.assertEqual(tree(), git(self.repo, "rev-parse", "HEAD^{tree}"))
(self.repo / "a").write_text("changed", encoding="utf-8")
(self.repo / "new").write_text("new", encoding="utf-8")
dirty = tree()
self.assertEqual(git(self.repo, "status", "--porcelain"), "M a\n?? new")
git(self.repo, "add", "-A")
self.assertEqual(dirty, git(self.repo, "write-tree"))
def test_a_receipt_meets_its_own_level_and_every_lower_one(self):
repo, tree = str(self.repo), git(self.repo, "rev-parse", "HEAD^{tree}")
check = lambda level: self.gates("receipt", "check", "--repo", repo, "--tree", tree, "--level", level).returncode
self.assertEqual(check("quick"), 1)
self.gates("receipt", "add", "--repo", repo, "--tree", tree, "--level", "test", "--log", "/tmp/x.log")
self.assertEqual((check("quick"), check("test"), check("quality-high")), (0, 0, 1))
def test_a_branch_cannot_be_its_own_parent(self):
result = self.gates("parents", "add", "--repo", str(self.repo), "--branch", "fix", "--parent", "fix")
self.assertEqual(result.returncode, 2)
if __name__ == "__main__":
unittest.main()
+1 -1
View File
@@ -175,7 +175,7 @@ class TestHubSpeech(HubCase):
args = {
"--bind": "127.0.0.1", "--listen-port": "0", "--public-port": "1", "--council": str(self.council), "--pages": str(self.pages), "--state": str(self.state),
"--host-state": str(HOST_STATE), "--skills": str(self.outside), "--transcripts": str(self.projects), "--templates": str(HUB / "templates"),
"--tree-file": str(self.tree_file), "--timers": str(self.timers), "--vendor": str(HUB / "vendor"), "--cleanup": str(self.cleanup), "--offices": str(self.offices),
"--tree-file": str(self.tree_file), "--timers": str(self.timers), "--kept": str(self.kept), "--vendor": str(HUB / "vendor"), "--cleanup": str(self.cleanup), "--offices": str(self.offices),
"--llm-key-file": os.devnull, "--llm-url": "", "--llm-model": "",
"--piper": str(self.engines.piper), "--voices": str(self.engines.voices), "--operator-voice": "en_US-test-high:9", "--whisper": str(self.engines.whisper),
"--whisper-model": str(self.engines.whisper_model), "--scratch": str(self.engines.scratch), "--approvers": str(self.approvers),
+16
View File
@@ -166,6 +166,22 @@ class TestRasci(unittest.TestCase):
rows = json.loads((self.root / "rasci.json").read_text(encoding="utf-8"))
return {task: row["after"] for task, row in rows.items() if "after" in row}
def test_unpin_drops_the_pin_a_change_stamps_only_its_row_and_the_rank_follows_after(self):
rows = lambda: json.loads((self.root / "rasci.json").read_text(encoding="utf-8"))
self._out("set", "work/a", "--prio", "1", "--worker", "One")
self._out("set", "work/b", "--prio", "2", "--worker", "One")
self.assertEqual(self._out("agents")[0].split("\t")[3], "work/a, work/b")
self._out("set", "work/a", "--after", "work/b")
self.assertEqual(self._out("agents")[0].split("\t")[3], "work/b, work/a")
stamped = rows()["work/a"]["updated"]
self._out("set", "work/b", "--unpin")
self.assertNotIn("prio", rows()["work/b"])
self.assertEqual(rows()["work/a"]["updated"], stamped)
self.assertIn("updated", rows()["work/b"])
refused = self._run("set", "work/b", "--unpin", "--prio", "3")
self.assertEqual(refused.returncode, 2)
self.assertIn("--unpin", refused.stderr)
def test_after_stores_and_drops_a_dependency_and_refuses_unknown_tasks_and_cycles(self):
for task in ("work/a", "work/b", "work/c"):
self._out("set", task)