diff --git a/.claude/skills/visual-review-and-fix/references/the-walk.md b/.claude/skills/visual-review-and-fix/references/the-walk.md index ef661538..4e0d4912 100644 --- a/.claude/skills/visual-review-and-fix/references/the-walk.md +++ b/.claude/skills/visual-review-and-fix/references/the-walk.md @@ -13,10 +13,10 @@ wording is wrong, do not edit one copy. See "If a promise is wrong" at the botto Three top-level views, and the fragment is the address: -- `#n=sessions` is the default. Session operations leads because the exception-first Attention route - was built first and buried the reader's opening questions in live review (NUI-16 in +- `#n=projects` is the default route. The v2 design moves the entry point up to Projects so the reader + first finds the project and then its sessions (NUI-16 in [`docs/design-next-ui.md`](../../../../docs/design-next-ui.md)). -- `#n=projects` is the complete map. +- `#n=sessions` is fleet operations. - `#n=attention` is the gate queue and the coverage disclosure. It has a nav entry as of #287, last of the three, and the `a` key and the reported-blocks chip in the header still reach it. That chip appears only while a block is reported, which is why it was never a nav entry's substitute. diff --git a/.github/workflows/quality-gate.yml b/.github/workflows/quality-gate.yml index 65afbccd..7393bc3a 100644 --- a/.github/workflows/quality-gate.yml +++ b/.github/workflows/quality-gate.yml @@ -202,20 +202,7 @@ jobs: - name: Run the full suite on the floor run: | python -m unittest discover -s cargento/skills/cargento/tests -t . - python -m unittest \ - scripts.tests.test_validate_plugins \ - scripts.tests.test_bump_version \ - scripts.tests.test_lint_embedded \ - scripts.tests.test_bench_collect \ - scripts.tests.test_capture_hook \ - scripts.tests.test_bench_event_latency \ - scripts.tests.test_derive_prompt_shapes \ - scripts.tests.test_capture_team_registry \ - scripts.tests.test_capture_terminal_identity \ - scripts.tests.test_capture_focus_raise \ - scripts.tests.test_serve_operator_cockpit \ - scripts.tests.test_mark_abstention \ - scripts.tests.test_score_abstention + python -m unittest discover -s scripts/tests -t scripts/tests test: name: Tests + coverage threshold @@ -242,20 +229,7 @@ jobs: run: | coverage erase coverage run -m unittest discover -s cargento/skills/cargento/tests -t . - coverage run -a -m unittest \ - scripts.tests.test_validate_plugins \ - scripts.tests.test_bump_version \ - scripts.tests.test_lint_embedded \ - scripts.tests.test_bench_collect \ - scripts.tests.test_capture_hook \ - scripts.tests.test_bench_event_latency \ - scripts.tests.test_derive_prompt_shapes \ - scripts.tests.test_capture_team_registry \ - scripts.tests.test_capture_terminal_identity \ - scripts.tests.test_capture_focus_raise \ - scripts.tests.test_serve_operator_cockpit \ - scripts.tests.test_mark_abstention \ - scripts.tests.test_score_abstention + coverage run -a -m unittest discover -s scripts/tests -t scripts/tests # No `coverage report` here. It reads `fail_under` from pyproject and # exits non-zero, and it ran BEFORE the label check below, so a PR @@ -388,12 +362,7 @@ jobs: - name: Run dashboard test discovery run: python -m unittest discover -s cargento/skills/cargento/tests -t . - name: Run script unit tests - run: >- - python -m unittest scripts.tests.test_validate_plugins - scripts.tests.test_bump_version scripts.tests.test_lint_embedded - scripts.tests.test_bench_collect scripts.tests.test_capture_hook - scripts.tests.test_capture_terminal_identity - scripts.tests.test_capture_focus_raise + run: python -m unittest discover -s scripts/tests -t scripts/tests # The single required status check. Branch protection requires this job, so # every job above must succeed (a skip or failure anywhere fails the gate). diff --git a/AGENTS.md b/AGENTS.md index 37837fe5..5723bc17 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -218,15 +218,7 @@ git diff "$(git merge-base origin/main HEAD)"..HEAD \ -- '*plugin.json' '*gemini-extension.json' | grep -E '^[+-].*"version"' coverage erase coverage run -m unittest discover -s cargento/skills/cargento/tests -t . -coverage run -a -m unittest \ - scripts.tests.test_validate_plugins scripts.tests.test_bump_version \ - scripts.tests.test_lint_embedded scripts.tests.test_bench_collect \ - scripts.tests.test_capture_hook scripts.tests.test_bench_event_latency \ - scripts.tests.test_derive_prompt_shapes scripts.tests.test_capture_team_registry \ - scripts.tests.test_capture_terminal_identity \ - scripts.tests.test_capture_focus_raise \ - scripts.tests.test_serve_operator_cockpit \ - scripts.tests.test_mark_abstention scripts.tests.test_score_abstention +coverage run -a -m unittest discover -s scripts/tests -t scripts/tests coverage report # enforces the fail_under threshold from pyproject.toml # `test_capture_terminal_identity` and `test_capture_focus_raise` exercise AppleScript # against Terminal.app, and this suite now sends nothing. It used to: measured on a macOS desk with Terminal diff --git a/scripts/tests/test_mark_abstention.py b/scripts/tests/test_mark_abstention.py index a3fbbc8e..a783911a 100644 --- a/scripts/tests/test_mark_abstention.py +++ b/scripts/tests/test_mark_abstention.py @@ -231,7 +231,8 @@ def test_the_temp_file_is_gone_and_the_mode_is_owner_only(self) -> None: mark_abstention._write(path, {"v": 2, "marks": {}}) self.assertTrue(os.path.exists(path)) self.assertFalse(os.path.exists(f"{path}.tmp")) - self.assertEqual(0o600, os.stat(path).st_mode & 0o777) + if os.name != "nt": + self.assertEqual(0o600, os.stat(path).st_mode & 0o777) def test_a_rewrite_replaces_rather_than_appends(self) -> None: with tempfile.TemporaryDirectory() as root: diff --git a/scripts/tests/test_validate_plugins.py b/scripts/tests/test_validate_plugins.py index f6617b62..dbb61a8c 100644 --- a/scripts/tests/test_validate_plugins.py +++ b/scripts/tests/test_validate_plugins.py @@ -1540,3 +1540,42 @@ def test_mutated_native_name_route_and_gate_mapping_are_rejected(self) -> None: mutated.write_text(mutated.read_text().replace(before, after)) problems = validator.check_js_adapters(Path(tmp)) self.assertTrue(any(filename in problem for problem in problems), problems) + + +class ScriptTestsDiscoveryContractTest(unittest.TestCase): + def test_all_script_tests_are_discoverable(self) -> None: + scripts_tests_dir = validator.ROOT / "scripts" / "tests" + test_files = sorted(f.stem for f in scripts_tests_dir.glob("test_*.py")) + self.assertTrue( + len(test_files) >= 13, f"Expected at least 13 test modules, found {len(test_files)}" + ) + + loader = unittest.defaultTestLoader + suite = loader.discover(str(scripts_tests_dir), top_level_dir=str(scripts_tests_dir)) + + loaded_modules: set[str] = set() + + def _walk_suite(s: Any) -> None: + for item in s: + if isinstance(item, unittest.TestSuite): + _walk_suite(item) + elif isinstance(item, unittest.TestCase): + loaded_modules.add(item.__class__.__module__) + + _walk_suite(suite) + for mod in test_files: + self.assertIn(mod, loaded_modules, f"Module {mod} was not discovered by unittest") + + def test_workflows_and_agents_use_discovery_not_hand_kept_lists(self) -> None: + quality_gate_path = validator.ROOT / ".github" / "workflows" / "quality-gate.yml" + quality_gate_text = quality_gate_path.read_text(encoding="utf-8") + agents_path = validator.ROOT / "AGENTS.md" + agents_text = agents_path.read_text(encoding="utf-8") + + # Must not contain hand-enumerated scripts.tests module lists + self.assertNotIn("scripts.tests.test_bump_version", quality_gate_text) + self.assertNotIn("scripts.tests.test_bump_version", agents_text) + + # Must use discover pattern for scripts/tests + self.assertIn("unittest discover -s scripts/tests -t scripts/tests", quality_gate_text) + self.assertIn("unittest discover -s scripts/tests -t scripts/tests", agents_text)