from __future__ import annotations import contextlib import hashlib import io import json import shutil import sys import tempfile import unittest from unittest import mock from collections import Counter from pathlib import Path REPO_ROOT = Path(__file__).resolve().parents[2] ADAPTER_DIR = REPO_ROOT / "harness/adapters" if str(ADAPTER_DIR) not in sys.path: sys.path.insert(0, str(ADAPTER_DIR)) import generate as adapters # noqa: E402 import generate_workflows as compatibility # noqa: E402 import fs_transaction # noqa: E402 class GenerateAdaptersTest(unittest.TestCase): maxDiff = None def setUp(self) -> None: self.tempdir = tempfile.TemporaryDirectory() self.root = Path(self.tempdir.name) shutil.copytree(REPO_ROOT / "harness/source", self.root / "harness/source") (self.root / "harness/adapters").mkdir(parents=True) shutil.copyfile( REPO_ROOT / adapters.PLATFORM_METADATA_REL, self.root / adapters.PLATFORM_METADATA_REL, ) entrypoint = self.root / "harness/runtime/branch_from_project.py" entrypoint.parent.mkdir(parents=True) shutil.copyfile(REPO_ROOT / "harness/runtime/branch_from_project.py", entrypoint) def tearDown(self) -> None: self.tempdir.cleanup() def _write_all(self) -> adapters.GenerationResult: result = adapters.generate(self.root, check=False) self.assertEqual(len(result.written), 113) return result def _main(self, *args: str) -> int: with contextlib.redirect_stdout(io.StringIO()), contextlib.redirect_stderr(io.StringIO()): return adapters.main([*args, "--root", str(self.root)]) def test_manifest_covers_all_113_targets_exactly_once(self) -> None: catalog = adapters.load_catalog(REPO_ROOT) paths = [target.path for _, target in catalog.targets] self.assertEqual(len(paths), 113) self.assertEqual(len(set(paths)), 113) self.assertEqual( Counter(target.kind for _, target in catalog.targets), { "agent-skill": 16, "agent-workflow": 16, "claude-command": 24, "plugin-agent-md": 11, "claude-agent-md": 13, "codex-agent-md": 11, "codex-agent-toml": 11, "antigravity-agent-json": 11, }, ) self.assertEqual(set(paths), adapters._target_inventory(catalog)) def test_neutral_sources_match_pre_migration_semantic_goldens(self) -> None: fixture = json.loads( (REPO_ROOT / "harness/tests/fixtures/legacy-semantic-sha256.json").read_text( encoding="utf-8" ) )["sources"] catalog = adapters.load_catalog(REPO_ROOT) self.assertEqual(len(fixture), 37) self.assertEqual( set(fixture), {f"{source.kind}:{source.identifier}" for source in catalog.sources}, ) for source in catalog.sources: golden = fixture[f"{source.kind}:{source.identifier}"] self.assertTrue((REPO_ROOT / golden["origin_target"]).is_file()) self.assertEqual( hashlib.sha256(source.body.encode("utf-8")).hexdigest(), golden["body_sha256"], ) def test_adapter_snapshots_and_current_targets_are_fresh(self) -> None: snapshots = json.loads( (REPO_ROOT / "harness/tests/fixtures/adapter-snapshots.json").read_text( encoding="utf-8" ) )["targets"] catalog = adapters.load_catalog(REPO_ROOT) self.assertEqual(len(snapshots), 113) for source, target in catalog.targets: rendered = adapters.render_target(catalog, source, target) digest = hashlib.sha256(rendered.encode("utf-8")).hexdigest() self.assertEqual(digest, snapshots[target.path], target.path) self.assertEqual(rendered, (REPO_ROOT / target.path).read_text(encoding="utf-8")) self.assertTrue(adapters.generate(REPO_ROOT, check=True).ok) def test_markers_cover_markdown_toml_and_json_targets(self) -> None: catalog = adapters.load_catalog(REPO_ROOT) for source, target in catalog.targets: rendered = adapters.render_target(catalog, source, target) expected_fragment = f"{source.metadata_path} sha256:{source.digest}; DO NOT EDIT" self.assertIn(expected_fragment, rendered, target.path) if target.kind == "antigravity-agent-json": self.assertIn("_generated", json.loads(rendered)) def test_write_then_check_and_compatibility_wrapper(self) -> None: self._write_all() self.assertTrue(adapters.generate(self.root, check=True).ok) self.assertEqual(compatibility.generate(self.root, check=True), []) def test_direct_edit_drift_is_nonzero_and_not_overwritten_by_check(self) -> None: self._write_all() target = self.root / ".claude/commands/branch.md" target.write_text("manual drift\n", encoding="utf-8") self.assertEqual(self._main("--check"), 1) self.assertEqual(target.read_text(encoding="utf-8"), "manual drift\n") self.assertIn(".claude/commands/branch.md", adapters.generate(self.root).stale) def test_missing_target_is_nonzero_and_write_recreates_it(self) -> None: self._write_all() target = self.root / ".claude/commands/branch.md" target.unlink() self.assertEqual(self._main("--check"), 1) self.assertEqual(adapters.generate(self.root).missing, (".claude/commands/branch.md",)) self.assertEqual(self._main("--write"), 0) self.assertTrue(target.is_file()) def test_extra_target_is_nonzero_and_write_refuses_to_delete_it(self) -> None: self._write_all() extra = self.root / ".claude/commands/unmapped.md" extra.write_text("unmapped\n", encoding="utf-8") self.assertEqual(self._main("--check"), 1) self.assertEqual(self._main("--write"), 1) self.assertTrue(extra.is_file()) def test_missing_and_extra_sources_are_nonzero(self) -> None: missing = self.root / "harness/source/skills/branch.md" missing.unlink() self.assertEqual(self._main("--check"), 2) shutil.copyfile(REPO_ROOT / "harness/source/skills/branch.md", missing) (self.root / "harness/source/skills/unmapped.md").write_text("extra\n", encoding="utf-8") self.assertEqual(self._main("--check"), 2) def test_duplicate_source_and_target_mappings_are_nonzero(self) -> None: manifest_path = self.root / adapters.MANIFEST_REL manifest = json.loads(manifest_path.read_text(encoding="utf-8")) manifest["sources"].append(dict(manifest["sources"][0])) manifest_path.write_text(json.dumps(manifest), encoding="utf-8") self.assertEqual(self._main("--check"), 2) shutil.copyfile(REPO_ROOT / adapters.MANIFEST_REL, manifest_path) first = json.loads((self.root / "harness/source/workflows/blogify.json").read_text()) second_path = self.root / "harness/source/workflows/branch.json" second = json.loads(second_path.read_text()) second["targets"].append(dict(first["targets"][0])) second_path.write_text(json.dumps(second), encoding="utf-8") self.assertEqual(self._main("--check"), 2) def test_heading_locale_is_machine_metadata_not_adapter_frontmatter(self) -> None: catalog = adapters.load_catalog(REPO_ROOT) self.assertTrue(all(source.heading_locale in {"ko-KR", "en", "mixed"} for source in catalog.sources)) for source, target in catalog.targets: rendered = adapters.render_target(catalog, source, target) self.assertNotIn("heading_locale", rendered, target.path) def test_every_workflow_has_nested_profile_and_risk_contract(self) -> None: catalog = adapters.load_catalog(REPO_ROOT) workflows = {source.identifier: source for source in catalog.sources if source.kind == "workflow"} self.assertEqual(len(workflows), 24) for source in workflows.values(): contract = source.execution_contract self.assertIsInstance(contract, dict, source.identifier) self.assertIn(contract["profile"], adapters.EXECUTION_PROFILES) self.assertIn(contract["default_risk"], adapters.EXECUTION_RISKS) self.assertIsInstance(contract["design_bearing"], bool) for target in source.targets: rendered = adapters.render_target(catalog, source, target) self.assertIn("execution_contract:", rendered, target.path) self.assertIn("harness/runtime/workflow_dispatch.py", rendered, target.path) self.assertIn("--phase baseline", rendered, target.path) self.assertIn("--phase final", rendered, target.path) self.assertEqual(workflows["branch-from-project"].execution_contract, { "kind": "deterministic", "profile": "capture", "default_risk": "low", "design_bearing": False, "entrypoint": "harness/runtime/branch_from_project.py", "dry_run_first": True, "result_schema": "branch-from-project-result/v1", }) expected_profiles = { "branch-from-project": "capture", "branch": "capture", "daily": "capture", "branch-spec": "design", "project-spec": "design", "depth": "audit", "coverage": "audit", "sync": "audit", "lint": "audit", "interviewize": "publish", "blogify": "publish", } self.assertEqual( {name: workflows[name].execution_contract["profile"] for name in expected_profiles}, expected_profiles, ) def test_document_writers_and_reporters_keep_runtime_connections_in_every_adapter(self) -> None: catalog = adapters.load_catalog(REPO_ROOT) sources = {(source.kind, source.identifier): source for source in catalog.sources} writer_required = ( "harness/runtime/document_commit.py", "document-commit/v1", "document-commit-result/v1", "--dry-run", "plan_sha256", "--expected-plan-sha256", "--apply", ) writer_forbidden = ( "자동 rollback 미구현", "**C4. Parent hub Cluster 갱신**", "**M5. Parent hub Cluster 점검**", "### Step 6: Parent hub Cluster 갱신", ) for identifier in ("wiki-doc-author", "wiki-source-summarizer"): source = sources[("agent", identifier)] self.assertNotIn("replace_file_content", source.body, identifier) documents = [source.body, *(adapters.render_target(catalog, source, target) for target in source.targets)] for document in documents: for token in writer_required: self.assertIn(token, document, f"{identifier}: {token}") for token in writer_forbidden: self.assertNotIn(token, document, f"{identifier}: {token}") proof_required = ( "proof-request/v1", "harness/runtime/proof_runner.py", "proof-runner-result/v1", "proof-manifest/v1", "manifest_path", "manifest_sha256", "proof_count", "pass_count", "fail_count", "1~3", ) reporter_agents = ( "branch-depth-auditor", "coverage-auditor", "extraction-broker", "project-readiness-auditor", "wiki-adversarial-reviewer", "wiki-consistency-auditor", "wiki-decision-researcher", "wiki-diagram-reviewer", "wiki-link-verifier", "wiki-research-lane", "wiki-source-summarizer", ) reporter_workflows = ( "branch-spec", "coverage", "depth", "ingest", "invest-research", "lint", "migrate-claims", "project-spec", "sync", ) for kind, identifiers in (("agent", reporter_agents), ("workflow", reporter_workflows)): for identifier in identifiers: source = sources[(kind, identifier)] documents = [source.body, *(adapters.render_target(catalog, source, target) for target in source.targets)] for document in documents: self.assertNotIn("harness/runtime/proof_manifest.py", document, f"{kind}:{identifier}") for token in proof_required: self.assertIn(token, document, f"{kind}:{identifier}: {token}") global_workflow = (REPO_ROOT / ".agents/plugins/wiki-superpowers/skills/wiki-workflow/SKILL.md").read_text(encoding="utf-8") for token in proof_required: self.assertIn(token, global_workflow, token) self.assertNotIn("harness/runtime/proof_manifest.py", global_workflow) self.assertNotIn("중립 adapter 전면 migration은 후속 TODO", global_workflow) def test_deterministic_workflow_without_entrypoint_fails_closed(self) -> None: metadata = self.root / "harness/source/workflows/branch-from-project.json" document = json.loads(metadata.read_text(encoding="utf-8")) del document["execution_contract"]["entrypoint"] metadata.write_text(json.dumps(document), encoding="utf-8") self.assertEqual(self._main("--check"), 2) def test_write_uses_one_global_replace_many_call(self) -> None: self._write_all() source = self.root / "harness/source/skills/branch.md" source.write_text(source.read_text(encoding="utf-8") + "\n", encoding="utf-8") with mock.patch.object(adapters, "replace_many") as replace: result = adapters.generate(self.root, check=False) replace.assert_called_once() self.assertEqual(len(replace.call_args.args[0]), 113) self.assertEqual(len(result.written), 113) def test_global_write_rolls_back_every_target_after_mid_commit_failure(self) -> None: self._write_all() source = self.root / "harness/source/skills/branch.md" source.write_text(source.read_text(encoding="utf-8") + "\n", encoding="utf-8") catalog = adapters.load_catalog(self.root) targets = [self.root / target.path for _, target in catalog.targets] before = {target: target.read_bytes() for target in targets} original_replace = fs_transaction.os.replace calls = 0 def fail_second_replace(source_path: object, target_path: object) -> None: nonlocal calls calls += 1 if calls == 2: raise OSError("injected adapter commit failure") original_replace(source_path, target_path) with mock.patch.object(fs_transaction.os, "replace", side_effect=fail_second_replace): with self.assertRaises(fs_transaction.TransactionError): adapters.generate(self.root, check=False) self.assertEqual({target: target.read_bytes() for target in targets}, before) def test_global_write_cleans_staged_bytes_when_staging_fails(self) -> None: self._write_all() source = self.root / "harness/source/skills/branch.md" source.write_text(source.read_text(encoding="utf-8") + "\n", encoding="utf-8") original_temporary = fs_transaction._temporary_bytes staged: list[Path] = [] calls = 0 def fail_second_stage(target: Path, data: bytes, mode: int | None = None) -> Path: nonlocal calls calls += 1 if calls == 2: raise OSError("injected adapter staging failure") temporary = original_temporary(target, data, mode) staged.append(temporary) return temporary with mock.patch.object(fs_transaction, "_temporary_bytes", side_effect=fail_second_stage): with self.assertRaises(OSError): adapters.generate(self.root, check=False) self.assertTrue(staged) self.assertTrue(all(not temporary.exists() for temporary in staged)) if __name__ == "__main__": unittest.main()