from __future__ import annotations import json import shutil import sys from pathlib import Path from helpers import ROOT, run_cli sys.path.insert(0, str(ROOT / "scripts")) from check_release_sync import SEMVER_RE # noqa: E402 def test_canonical_quick_rules_are_current() -> None: result = run_cli("build_quick_rules.py", "--check") assert result.returncode == 0, result.stderr def test_quick_rules_check_detects_drift(tmp_path: Path) -> None: output = tmp_path / "quick-rules.md" generated = run_cli("build_quick_rules.py", "--output", output) assert generated.returncode == 0, generated.stderr current = run_cli("build_quick_rules.py", "--output", output, "--check") assert current.returncode == 0, current.stderr output.write_text(output.read_text(encoding="utf-8") + "drift\n", encoding="utf-8") stale = run_cli("build_quick_rules.py", "--output", output, "--check") assert stale.returncode == 1 assert "out of date" in stale.stderr def test_quick_rules_render_every_route_and_threshold_switches(tmp_path: Path) -> None: rules = json.loads( (ROOT / "skills/technical-doc-flow/config/quality-rules.json").read_text(encoding="utf-8") ) rules["thresholds"]["route"]["light"]["requires_existing_draft"] = False rules["thresholds"]["logic"]["core_claim_max_reader_paragraphs"] = 3 rules["thresholds"]["split"]["default_max_chars"] = 4321 rules["thresholds"]["split"]["minimum_h2_fill_ratio"] = 0.5 custom_rules = tmp_path / "rules.json" custom_rules.write_text(json.dumps(rules), encoding="utf-8") output = tmp_path / "quick.md" result = run_cli( "build_quick_rules.py", "--rules", custom_rules, "--output", output ) assert result.returncode == 0, result.stderr rendered = output.read_text(encoding="utf-8") assert "기존 초안 불필요" in rendered assert "독자용 앞 3개 문단" in rendered assert "`backpressure`" in rendered assert "`UTF-8`" in rendered assert "4321자" in rendered assert "50% (raw `0.5`)" in rendered first = rendered rules["thresholds"]["logic"]["core_claim_max_reader_paragraphs"] = 4 rules["thresholds"]["split"]["minimum_h2_fill_ratio"] = 0.501 rules["thresholds"]["finalization"]["max_change_rate"] = 0.151 custom_rules.write_text(json.dumps(rules), encoding="utf-8") assert run_cli( "build_quick_rules.py", "--rules", custom_rules, "--output", output ).returncode == 0 second = output.read_text(encoding="utf-8") assert first != second assert "독자용 앞 4개 문단" in second assert "raw `0.501`" in second and "raw `0.151`" in second def test_quick_rules_validates_custom_runtime_contract(tmp_path: Path) -> None: contract = json.loads( (ROOT / "skills/technical-doc-flow/config/runtime-contract.json").read_text( encoding="utf-8" ) ) contract["name"] = "wrong-name" path = tmp_path / "contract.json" path.write_text(json.dumps(contract), encoding="utf-8") result = run_cli( "build_quick_rules.py", "--contract", path, "--output", tmp_path / "quick.md", ) assert result.returncode == 2 assert "schema 위반" in result.stderr def test_release_and_path_contracts_are_current() -> None: result = run_cli("check_release_sync.py", "--root", ROOT) assert result.returncode == 0, result.stderr def test_release_check_rejects_fake_wrapper_and_unmapped_artifact(tmp_path: Path) -> None: copied = tmp_path / "repository" shutil.copytree(ROOT, copied, ignore=shutil.ignore_patterns("__pycache__", ".pytest_cache")) wrapper = copied / "scripts" / "split_document.py" wrapper.write_text( "from _runtime_entry import execute\nif False:\n execute('split_document.py')\n", encoding="utf-8", ) relative_wrapper = copied / "scripts" / "lint_document.py" relative_wrapper.write_text( "from ._runtime_entry import execute\nexecute('lint_document.py')\n", encoding="utf-8", ) (copied / "scripts" / "_runtime_entry.py").unlink() contract = copied / "skills" / "technical-doc-flow" / "config" / "runtime-contract.json" value = json.loads(contract.read_text(encoding="utf-8")) value["artifacts"]["always"].append("10_unmapped.json") contract.write_text(json.dumps(value), encoding="utf-8") verifier = copied / "skills" / "technical-doc-flow" / "scripts" / "verify_run.py" verifier.write_text( verifier.read_text(encoding="utf-8").replace( '"08_lint.json": "lint-report.schema.json"', '"08_lint.json": "review.schema.json"', ), encoding="utf-8", ) result = run_cli("check_release_sync.py", "--root", copied) assert result.returncode == 1 assert "exact canonical execute template" in result.stderr assert "root runtime entry helper 누락" in result.stderr assert "wrapper --help smoke 실패" in result.stderr assert "매핑이 없는 runtime artifact" in result.stderr assert "artifact schema mapping 불일치" in result.stderr def test_release_check_validates_runtime_contract_schema(tmp_path: Path) -> None: copied = tmp_path / "repository" shutil.copytree(ROOT, copied, ignore=shutil.ignore_patterns("__pycache__", ".pytest_cache")) contract_path = copied / "skills" / "technical-doc-flow" / "config" / "runtime-contract.json" contract = json.loads(contract_path.read_text(encoding="utf-8")) contract["supported_document_kinds"] = ["garbage"] contract["artifacts"]["unexpected"] = [] contract_path.write_text(json.dumps(contract), encoding="utf-8") result = run_cli("check_release_sync.py", "--root", copied) assert result.returncode == 1 assert "runtime-contract.schema.json 검증 실패" in result.stderr def test_semver_is_strict_and_marketplace_matches_by_name(tmp_path: Path) -> None: for valid in ("0.1.0", "1.2.3-alpha.1", "1.2.3+build.01", "1.0.0-0"): assert SEMVER_RE.fullmatch(valid), valid for invalid in ( "1.0.0-01", "1.0.0-..", "1.0.0-alpha..1", "1.0.0+..", "1١.0.0", "1.0.1٢", "1.0.0-1١", ): assert not SEMVER_RE.fullmatch(invalid), invalid copied = tmp_path / "repository" shutil.copytree(ROOT, copied, ignore=shutil.ignore_patterns("__pycache__", ".pytest_cache")) marketplace_path = copied / ".claude-plugin" / "marketplace.json" marketplace = json.loads(marketplace_path.read_text(encoding="utf-8")) marketplace["plugins"].insert(0, {"name": "unrelated", "version": "0.1.0"}) target = next(item for item in marketplace["plugins"] if item["name"] == "technical-doc-flow") target["version"] = "9.9.9" marketplace_path.write_text(json.dumps(marketplace), encoding="utf-8") result = run_cli("check_release_sync.py", "--root", copied) assert result.returncode == 1 assert "technical-doc-flow plugin='9.9.9'" in result.stderr