"""Project-owned definition-of-done checks (task 03). What counts as a check is project knowledge: core ships a default definition (core/checks), a same-named file in local/ replaces it wholesale, the claude adapter classifies agent commands against the resolved file, and the Focus panel renders one row per entry from the same definition served over /api/state. These tests pin the resolution order, the two parsers' agreement, the generic pass/fail judgment that replaced per-tool output parsing, and the absence of the origin project's stack anywhere else in core. python3 -m unittest discover -s tests -v """ from __future__ import annotations import re import sys import tempfile import unittest from pathlib import Path REPO = Path(__file__).resolve().parents[1] sys.path.insert(0, str(REPO / "manager" / "core")) sys.path.insert(0, str(REPO / "manager" / "core" / "adapters" / "claude")) import config # noqa: E402 import emit # noqa: E402 CORE = REPO / "manager" / "core" class ShippedDefault(unittest.TestCase): """A project defining nothing sees today's rows — as the default.""" def test_default_rows_and_patterns(self): original = config.LOCAL try: with tempfile.TemporaryDirectory() as tmp: config.LOCAL = Path(tmp) # no local/checks checks = config.checks() finally: config.LOCAL = original self.assertEqual([c["label"] for c in checks], ["pytest", "lint-imports", "frontend"]) by_label = {c["label"]: c["pattern"] for c in checks} self.assertTrue(re.search(by_label["pytest"], "python3 -m pytest -q")) self.assertTrue(re.search(by_label["lint-imports"], "lint-imports")) for cmd in ("npm run test", "vue-tsc --noEmit", "npx vitest run"): self.assertTrue(re.search(by_label["frontend"], cmd), cmd) self.assertFalse(re.search(by_label["pytest"], "python3 -m unittest")) class LocalOverride(unittest.TestCase): """local/checks beats core/checks wholesale, like prompts.""" def _with_local(self, text): original = config.LOCAL try: with tempfile.TemporaryDirectory() as tmp: config.LOCAL = Path(tmp) (Path(tmp) / "checks").write_text(text, encoding="utf-8") return config.checks() finally: config.LOCAL = original def test_local_definition_wins(self): checks = self._with_local("# ours\nsmoke: \\bmake smoke\\b\n" "types: \\bmypy\\b\n") self.assertEqual([c["label"] for c in checks], ["smoke", "types"]) def test_replacement_is_wholesale_even_when_empty(self): self.assertEqual(self._with_local("# nothing to check\n"), []) def test_malformed_lines_are_skipped(self): checks = self._with_local("no separator here\n" ": pattern without label\n" "label without pattern:\n" "bad-regex: [unclosed\n" "good: \\bok\\b\n") self.assertEqual([c["label"] for c in checks], ["good"]) def test_bench_defines_its_own(self): """Bench's real local/checks: the stdlib suite, so the self-hosted Focus view shows a check that can actually run here.""" checks = config.checks() self.assertEqual([c["label"] for c in checks], ["unittest"]) self.assertTrue(re.search(checks[0]["pattern"], "python3 -m unittest")) class AdapterReadsTheSameFile(unittest.TestCase): """emit.py resolves and parses the identical definition, so a label edit flows to classification and rendering alike.""" def test_parsers_agree_on_the_shipped_default(self): original = emit.MANAGER try: emit.MANAGER = Path(tempfile.mkdtemp()) # no local, no core (emit.MANAGER / "core").mkdir() (emit.MANAGER / "core" / "checks").write_text( (CORE / "checks").read_text(encoding="utf-8"), encoding="utf-8") adapter_view = [(label, pattern.pattern) for label, pattern in emit.check_defs()] finally: emit.MANAGER = original core_original = config.LOCAL try: with tempfile.TemporaryDirectory() as tmp: config.LOCAL = Path(tmp) board_view = [(c["label"], c["pattern"]) for c in config.checks()] finally: config.LOCAL = core_original self.assertEqual(adapter_view, board_view) def test_local_wins_in_the_adapter_too(self): original = emit.MANAGER try: with tempfile.TemporaryDirectory() as tmp: emit.MANAGER = Path(tmp) (Path(tmp) / "core").mkdir() (Path(tmp) / "core" / "checks").write_text( "core-only: \\bx\\b\n", encoding="utf-8") (Path(tmp) / "local").mkdir() (Path(tmp) / "local" / "checks").write_text( "ours: \\bmake check\\b\n", encoding="utf-8") defs = emit.check_defs() finally: emit.MANAGER = original self.assertEqual([label for label, _ in defs], ["ours"]) def test_missing_files_mean_no_checks_not_a_crash(self): original = emit.MANAGER try: with tempfile.TemporaryDirectory() as tmp: emit.MANAGER = Path(tmp) self.assertEqual(emit.check_defs(), []) finally: emit.MANAGER = original class Classification(unittest.TestCase): """Bash commands classify against the resolved definitions; the label carries into the summary; judgment is generic, not per-tool.""" def setUp(self): self._original = emit.MANAGER self._tmp = tempfile.TemporaryDirectory() emit.MANAGER = Path(self._tmp.name) (emit.MANAGER / "core").mkdir() (emit.MANAGER / "core" / "checks").write_text( "suite: \\bunittest\\b\nlint: \\bmake lint\\b\n", encoding="utf-8") def tearDown(self): emit.MANAGER = self._original self._tmp.cleanup() def _bash(self, cmd, out="", hook="PostToolUse"): return emit.classify(hook, "Bash", {"command": cmd}, {"stdout": out, "stderr": ""}) def test_matching_command_becomes_a_check_with_its_label(self): ev = self._bash("python3 -m unittest discover -s tests", "Ran 7 tests in 0.1s\n\nOK") self.assertEqual(ev["kind"], "check") self.assertTrue(ev["ok"]) self.assertTrue(ev["summary"].startswith("suite — ")) def test_label_edits_flow_to_the_event(self): (emit.MANAGER / "core" / "checks").write_text( "renamed: \\bunittest\\b\n", encoding="utf-8") ev = self._bash("python3 -m unittest", "OK") self.assertTrue(ev["summary"].startswith("renamed — ")) def test_counted_failures_fail(self): ev = self._bash("python3 -m unittest", "2 failed, 5 passed in 1.2s") self.assertEqual(ev["kind"], "check") self.assertFalse(ev["ok"]) self.assertIn("2 failed", ev["summary"]) self.assertIn("5 passed", ev["summary"]) def test_unjudgeable_output_stays_neutral(self): ev = self._bash("make lint", "some chatter") self.assertEqual(ev["kind"], "check") self.assertIsNone(ev["ok"]) def test_unmatched_commands_stay_commands(self): self.assertEqual(self._bash("ls -la")["kind"], "command") def test_git_classification_survives(self): ev = self._bash('git commit -m "a message"') self.assertEqual(ev["kind"], "git") def test_running_events_keep_the_check_kind(self): ev = self._bash("python3 -m unittest", hook="PreToolUse") self.assertEqual(ev["kind"], "check") self.assertTrue(ev["running"]) class GenericJudgment(unittest.TestCase): """No tool names: counts, broken totals and OK/FAILED verdict lines.""" def test_ladder(self): cases = [ ("3 passed in 0.5s", True), ("1 failed, 2 passed", False), ("2 errors", False), ("Ran 7 tests in 0.1s\n\nOK", True), ("Ran 7 tests in 0.1s\n\nFAILED (failures=1)", False), ("0 broken contracts", True), ("4 broken contracts", False), ("nothing recognizable", None), ("", None), ] for out, expected in cases: ok, _ = emit.judge(out) self.assertEqual(ok, expected, f"judge({out!r})") class ServedToTheBrowser(unittest.TestCase): """The state API carries the definition; Focus renders from it with no fixed rows and no duplicated patterns.""" @classmethod def setUpClass(cls): cls.html = (CORE / "board.html").read_text(encoding="utf-8") def test_state_payload_includes_checks(self): import httpd payload = httpd.state_payload() self.assertIn("checks", payload) self.assertEqual(payload["checks"], config.checks()) def test_focus_renders_from_served_definition(self): self.assertIn("S.state?.checks", self.html) self.assertIn("new RegExp(c.pattern)", self.html) self.assertIn("no checks defined", self.html) def test_no_fixed_rows_or_duplicated_patterns(self): for fossil in ("lint-imports", "vitest", "vue-tsc", "pytest"): self.assertNotIn(fossil, self.html, f"board.html still hardcodes {fossil}") class NoFossilsInCore(unittest.TestCase): """Nothing in manager/core/ names the origin project's stack outside the shipped default checks definition.""" def test_core_is_clean(self): allowed = CORE / "checks" pattern = re.compile(r"pytest|lint-imports|vue-tsc|vitest") for path in sorted(CORE.rglob("*")): if not path.is_file() or path == allowed: continue try: text = path.read_text(encoding="utf-8") except UnicodeDecodeError: continue self.assertIsNone(pattern.search(text), f"{path.relative_to(REPO)} names a stack " "that belongs in the checks definition") if __name__ == "__main__": unittest.main()