from __future__ import annotations import argparse import json import sys import tomllib from pathlib import Path import pytest import custodian_cli from custodian_cli import cmd_fix_consistency from statehub_register import ( RegisterInference, detect_project_flavor_signal, project_registration_plan, refuse_project_flavor_scaffold, run_register, _find_or_create_topic, _invoke_llm, _normalise_inference, _parse_json_object, write_registration_files, ) def test_parse_json_object_accepts_fenced_json(): parsed = _parse_json_object( """Here is the answer: ```json {"repo_slug": "demo-repo", "in_scope": ["tests"]} ``` """ ) assert parsed == {"repo_slug": "demo-repo", "in_scope": ["tests"]} def test_normalise_inference_slugifies_and_normalises_prefix(): inference = _normalise_inference( { "project_description": "Provides demo automation.", "domain_slug": "Custodian", "repo_slug": "Demo Repo!", "workplan_prefix": "demo", "in_scope": ["one", "two"], "out_of_scope": ["three"], } ) assert inference.domain_slug == "custodian" assert inference.repo_slug == "demo-repo" assert inference.workplan_prefix == "DEMO-WP" assert inference.in_scope == ["one", "two"] assert inference.out_of_scope == ["three"] def test_invoke_llm_uses_mock_adapter(monkeypatch): payload = {"project_description": "Mocked repo.", "repo_slug": "mocked-repo"} monkeypatch.setenv("STATEHUB_REGISTER_MOCK_LLM_RESPONSE", json.dumps(payload)) args = argparse.Namespace( llm_provider="mock", llm_model=None, llm_api_key=None, llm_timeout=5, ) assert json.loads(_invoke_llm("infer this repo", args)) == payload def test_write_registration_files_primes_codex_repo(tmp_path: Path): inference = RegisterInference( in_scope=["Run the demo service."], out_of_scope=["Operate unrelated services."], current_state="Status: active; implementation is small.", ) written = write_registration_files( project_path=tmp_path, project_name="demo-service", project_description="Provides a demo service.", domain="custodian", topic_id="cee7bedf-2b48-46ef-8601-006474f2ad7a", topic_slug="custodian", repo_slug="demo-service", wp_prefix="DEMO-WP", intent_markdown="# INTENT\n\nDemo service intent.\n", inference=inference, ) assert {path.name for path in written} == { "INTENT.md", "SCOPE.md", "AGENTS.md", ".custodian-brief.md", "DEMO-WP-0001-statehub-bootstrap.md", ".gitignore", } assert "!.claude/rules/" in (tmp_path / ".gitignore").read_text() assert (tmp_path / "INTENT.md").read_text() == "# INTENT\n\nDemo service intent.\n" assert "**Repo slug:** demo-service" in (tmp_path / "AGENTS.md").read_text() assert "Run the demo service." in (tmp_path / "SCOPE.md").read_text() workplan = (tmp_path / "workplans" / "DEMO-WP-0001-statehub-bootstrap.md").read_text() assert "id: DEMO-WP-0001" in workplan assert "id: DEMO-WP-0001-T01" in workplan assert "statehub fix-consistency" in workplan def test_detect_project_flavor_from_classification(tmp_path: Path): (tmp_path / ".repo-classification.yaml").write_text( "repo_classification:\n category: project\n domain: infotech\n", encoding="utf-8", ) assert detect_project_flavor_signal(tmp_path) == ( ".repo-classification.yaml category=project" ) def test_detect_project_flavor_from_goal_md(tmp_path: Path): (tmp_path / "GOAL.md").write_text( "---\nrepo_flavor: project\n---\n\n# Project goal\n", encoding="utf-8", ) assert detect_project_flavor_signal(tmp_path) == "GOAL.md repo_flavor=project" def test_detect_project_flavor_from_prj_directory_name(tmp_path: Path): repo = tmp_path / "prj-example" repo.mkdir() assert detect_project_flavor_signal(repo) == "slug prefix prj- (prj-example)" def test_detect_project_flavor_from_repo_slug_argument(tmp_path: Path): repo = tmp_path / "example" repo.mkdir() assert detect_project_flavor_signal(repo) is None assert detect_project_flavor_signal(repo, "prj-example") == ( "slug prefix prj- (prj-example)" ) def test_classification_project_takes_precedence_over_slug(tmp_path: Path): repo = tmp_path / "prj-example" repo.mkdir() (repo / ".repo-classification.yaml").write_text( "repo_classification:\n category: project\n domain: infotech\n", encoding="utf-8", ) assert detect_project_flavor_signal(repo) == ( ".repo-classification.yaml category=project" ) def test_durable_repo_is_not_project_flavor(tmp_path: Path): (tmp_path / ".repo-classification.yaml").write_text( "repo_classification:\n category: tooling\n domain: infotech\n", encoding="utf-8", ) assert detect_project_flavor_signal(tmp_path, "demo-service") is None def test_refuse_project_flavor_scaffold_points_at_rmgr(tmp_path: Path): repo = tmp_path / "prj-example" repo.mkdir() with pytest.raises(SystemExit, match="rmgr scaffold") as exc: refuse_project_flavor_scaffold(repo) message = str(exc.value) assert "refuses to scaffold a project-flavor repository" in message assert "never PRJ-WP-" in message assert str(repo) in message def test_run_register_refuses_prj_repo_without_writing_or_calling_api( tmp_path: Path, monkeypatch: pytest.MonkeyPatch ): repo = tmp_path / "prj-example" repo.mkdir() (repo / ".repo-classification.yaml").write_text( "repo_classification:\n category: project\n domain: infotech\n", encoding="utf-8", ) called: list[str] = [] monkeypatch.setattr( "statehub_register._check_api", lambda *_args, **_kwargs: called.append("api"), ) monkeypatch.setattr( "statehub_register.write_registration_files", lambda **_kwargs: called.append("write") or [], ) args = argparse.Namespace(path=str(repo), repo_slug=None) with pytest.raises(SystemExit, match="rmgr scaffold"): run_register(args) assert called == [] assert not (repo / "INTENT.md").exists() assert not (repo / "workplans").exists() def test_project_registration_plan_durable_vs_refuse_vs_delegate(tmp_path: Path): durable = tmp_path / "demo-service" durable.mkdir() assert project_registration_plan(durable) == ("durable", None) fresh = tmp_path / "prj-example" fresh.mkdir() assert project_registration_plan(fresh)[0] == "refuse" assert project_registration_plan(fresh, wp_prefix="EXCO-WP") == ( "delegate", "EXCO-WP", ) (fresh / "GOAL.md").write_text( "---\nrepo_flavor: project\n---\n# Goal\n", encoding="utf-8" ) assert project_registration_plan(fresh) == ("register-only", None) assert project_registration_plan(fresh, wp_prefix="EXCO-WP") == ( "register-only", None, ) def _stub_register_io(monkeypatch: pytest.MonkeyPatch): written: list[dict] = [] registered: list[tuple] = [] monkeypatch.setattr("statehub_register._check_api", lambda *_a, **_k: None) monkeypatch.setattr( "statehub_register._api_get", lambda path, *_a, **_k: ( [{"slug": "infotech", "id": "dom-1"}] if str(path).startswith("/domains") else [{"id": "topic-1", "slug": "infotech", "domain_slug": "infotech"}] ), ) monkeypatch.setattr( "statehub_register.write_registration_files", lambda **kwargs: written.append(kwargs) or [], ) monkeypatch.setattr( "statehub_register._register_or_update_repo", lambda **kwargs: registered.append(("repo", kwargs)) or {"id": "repo-1"}, ) monkeypatch.setattr( "statehub_register._register_host_path", lambda *a, **k: registered.append(("path", a, k)), ) monkeypatch.setattr("statehub_register._record_progress", lambda *_a, **_k: None) return written, registered def _register_args(path: Path, **overrides): values = { "path": str(path), "repo_slug": None, "wp_prefix": None, "domain": "infotech", "topic": None, "description": "Test project.", "intent": None, "api_base": "http://unused", "no_llm": True, "force": False, "llm_provider": "mock", "llm_model": None, "llm_api_key": None, "llm_timeout": 5, } values.update(overrides) return argparse.Namespace(**values) def test_run_register_delegates_project_scaffold_and_skips_templating( tmp_path: Path, monkeypatch: pytest.MonkeyPatch ): repo = tmp_path / "prj-example" repo.mkdir() written, registered = _stub_register_io(monkeypatch) delegated: list[dict] = [] def fake_delegate(project_path, **kwargs): delegated.append(kwargs) (project_path / "GOAL.md").write_text( "---\nrepo_flavor: project\n---\n# Goal\n", encoding="utf-8" ) return {"status": "applied"} monkeypatch.setattr("statehub_register.delegate_project_scaffold", fake_delegate) run_register(_register_args(repo, wp_prefix="EXCO-WP")) assert delegated == [ { "slug": "prj-example", "wp_prefix": "EXCO-WP", "domain": "infotech", "force": False, } ] assert written == [] assert [kind for kind, *_ in registered] == ["repo", "path"] assert not (repo / "INTENT.md").exists() assert (repo / "GOAL.md").is_file() def test_run_register_existing_project_skips_scaffold_and_templating( tmp_path: Path, monkeypatch: pytest.MonkeyPatch ): repo = tmp_path / "prj-example" repo.mkdir() (repo / "GOAL.md").write_text( "---\nrepo_flavor: project\n---\n# Goal\n", encoding="utf-8" ) written, registered = _stub_register_io(monkeypatch) monkeypatch.setattr( "statehub_register.delegate_project_scaffold", lambda *_a, **_k: (_ for _ in ()).throw(AssertionError("should not scaffold")), ) run_register(_register_args(repo)) assert written == [] assert [kind for kind, *_ in registered] == ["repo", "path"] assert not (repo / "INTENT.md").exists() def test_run_register_honors_explicit_topic_slug( tmp_path: Path, monkeypatch: pytest.MonkeyPatch ): repo = tmp_path / "demo-service" repo.mkdir() written, registered = _stub_register_io(monkeypatch) monkeypatch.setattr( "statehub_register._api_get", lambda path, *_a, **_k: ( [{"slug": "infotech", "id": "dom-1"}] if str(path).startswith("/domains") else [ {"id": "topic-1", "slug": "custodian", "domain_slug": "infotech"}, {"id": "topic-2", "slug": "activity-core", "domain_slug": "infotech"}, ] ), ) run_register( _register_args( repo, topic="activity-core", intent="Register the demo service with the requested topic.", ) ) assert written[0]["topic_id"] == "topic-2" repo_registration = next(item for kind, item in registered if kind == "repo") assert repo_registration["topic_id"] == "topic-2" def test_topic_selection_refuses_ambiguous_domain( monkeypatch: pytest.MonkeyPatch, ): monkeypatch.setattr( "statehub_register._api_get", lambda *_a, **_k: [ {"id": "topic-1", "slug": "custodian", "domain_slug": "infotech"}, {"id": "topic-2", "slug": "activity-core", "domain_slug": "infotech"}, ], ) with pytest.raises(SystemExit, match=r"Pass --topic "): _find_or_create_topic( "infotech", "demo", "demo", RegisterInference(), "http://unused", ) def test_topic_selection_uses_exact_inferred_topic( monkeypatch: pytest.MonkeyPatch, ): topics = [ {"id": "topic-1", "slug": "custodian", "domain_slug": "infotech"}, {"id": "topic-2", "slug": "activity-core", "domain_slug": "infotech"}, ] monkeypatch.setattr("statehub_register._api_get", lambda *_a, **_k: topics) selected = _find_or_create_topic( "infotech", "activity-core", "activity-core", RegisterInference(topic_slug="activity-core"), "http://unused", ) assert selected == topics[1] def test_topic_selection_refuses_cross_domain_topic( monkeypatch: pytest.MonkeyPatch, ): monkeypatch.setattr( "statehub_register._api_get", lambda *_a, **_k: [ {"id": "topic-1", "slug": "activity-core", "domain_slug": "agents"}, ], ) with pytest.raises(SystemExit, match=r"belongs to domain\(s\) agents"): _find_or_create_topic( "infotech", "demo", "demo", RegisterInference(), "http://unused", requested_topic_slug="activity-core", ) def test_topic_selection_refuses_missing_explicit_topic( monkeypatch: pytest.MonkeyPatch, ): monkeypatch.setattr( "statehub_register._api_get", lambda *_a, **_k: [ {"id": "topic-1", "slug": "custodian", "domain_slug": "infotech"}, ], ) with pytest.raises(SystemExit, match=r"Available: custodian"): _find_or_create_topic( "infotech", "demo", "demo", RegisterInference(), "http://unused", requested_topic_slug="activity-core", ) def test_write_registration_files_is_idempotent_without_force(tmp_path: Path): inference = RegisterInference() kwargs = { "project_path": tmp_path, "project_name": "demo", "project_description": "Provides a demo.", "domain": "custodian", "topic_id": "topic", "topic_slug": "custodian", "repo_slug": "demo", "wp_prefix": "DEMO-WP", "intent_markdown": "# INTENT\n\nDemo.\n", "inference": inference, } assert write_registration_files(**kwargs) assert write_registration_files(**kwargs) == [] def _fix_args(**overrides): values = { "repo": None, "all": False, "path": None, "repo_path": None, "remote": False, "max_seconds": None, "no_writeback": False, "archive_closed": False, "archive_workplan": None, "archive_date": None, "api_base": "http://statehub.test", "as_json": False, "strict_warnings": False, } values.update(overrides) return argparse.Namespace(**values) def _install_fake_checker(monkeypatch, tmp_path: Path) -> Path: checker = tmp_path / "scripts" / "consistency_check.py" checker.parent.mkdir() checker.write_text("#!/usr/bin/env python3\n", encoding="utf-8") monkeypatch.setattr(custodian_cli, "STATE_HUB_DIR", tmp_path) return checker def test_fix_consistency_defaults_to_here_and_normalises_warning_exit(monkeypatch, tmp_path: Path): checker = _install_fake_checker(monkeypatch, tmp_path) repo = tmp_path / "repo" repo.mkdir() calls = [] def fake_run(cmd): calls.append(cmd) return argparse.Namespace(returncode=2) monkeypatch.setattr(custodian_cli.subprocess, "run", fake_run) with pytest.raises(SystemExit) as exc: cmd_fix_consistency(_fix_args(path=str(repo))) assert exc.value.code == 0 assert calls == [[ sys.executable, str(checker), "--here", str(repo.resolve()), "--fix", "--api-base", "http://statehub.test", ]] def test_fix_consistency_strict_warnings_preserves_exit_two(monkeypatch, tmp_path: Path): _install_fake_checker(monkeypatch, tmp_path) repo = tmp_path / "repo" repo.mkdir() monkeypatch.setattr( custodian_cli.subprocess, "run", lambda _cmd: argparse.Namespace(returncode=2), ) with pytest.raises(SystemExit) as exc: cmd_fix_consistency(_fix_args(path=str(repo), strict_warnings=True)) assert exc.value.code == 2 def test_fix_consistency_repo_remote_passes_pull_before_fix_options(monkeypatch, tmp_path: Path): checker = _install_fake_checker(monkeypatch, tmp_path) repo = tmp_path / "repo" repo.mkdir() calls = [] def fake_run(cmd): calls.append(cmd) return argparse.Namespace(returncode=0) monkeypatch.setattr(custodian_cli.subprocess, "run", fake_run) with pytest.raises(SystemExit) as exc: cmd_fix_consistency( _fix_args( repo="demo-service", repo_path=str(repo), remote=True, no_writeback=True, as_json=True, max_seconds=12, ) ) assert exc.value.code == 0 assert calls == [[ sys.executable, str(checker), "--repo", "demo-service", "--repo-path", str(repo.resolve()), "--fix", "--remote", "--no-writeback", "--api-base", "http://statehub.test", "--json", "--max-seconds", "12", ]] def test_fix_consistency_remote_requires_explicit_repo_or_all(monkeypatch, tmp_path: Path): _install_fake_checker(monkeypatch, tmp_path) calls = [] monkeypatch.setattr(custodian_cli.subprocess, "run", lambda cmd: calls.append(cmd)) with pytest.raises(SystemExit) as exc: cmd_fix_consistency(_fix_args(remote=True, path=str(tmp_path))) assert exc.value.code == 1 assert calls == [] def test_quality_debt_cli_support_files_ship_in_the_wheel(): config = tomllib.loads( (Path(__file__).resolve().parents[1] / "pyproject.toml").read_text( encoding="utf-8" ) ) force_include = config["tool"]["hatch"]["build"]["targets"]["wheel"][ "force-include" ] assert force_include["scripts/quality_debt.py"] == "scripts/quality_debt.py" assert ( force_include["scripts/quality_assessment.py"] == "scripts/quality_assessment.py" )