"""command-pedagogy가 실제 파이프라인 문서와 프롬프트에 연결되어 있는지 본다.""" from __future__ import annotations import json import os import unittest ROOT = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) PIPELINE = os.path.join(ROOT, ".agents", "skills", "running-tech-log-pipeline") def read(*parts: str) -> str: with open(os.path.join(PIPELINE, *parts), encoding="utf-8") as fh: return fh.read() class CommandPedagogyPipelineContractTest(unittest.TestCase): def test_pipeline_skill_routes_command_work_to_separate_agents(self): text = read("SKILL.md") for token in ( "check-command-pedagogy.py", "command-pedagogy-planner", "command-pedagogy-editor", "command-pedagogy-reviewer", "qualityReviews.commandPedagogy", "qualityReviews.technicalEvidence", ): self.assertIn(token, text) self.assertIn("shell/CLI block이 없으면", text) self.assertIn("finding이 없으면 planner/editor", text) def test_stage_contract_freezes_bounded_command_repair(self): text = read("references", "stage-contracts.md") for token in ( "initialAnalysis", "finalAnalysis", "CommandPatchSet", "apply-command-pedagogy-patch.py", "majorFindings", "command-pedagogy-reviewer", "fact-reviewer", ): self.assertIn(token, text) self.assertIn("command block 밖의 산문", text) def test_subagent_prompts_include_the_command_lane_and_final_reviews(self): text = read("references", "subagent-prompts.md") for call in ( 'Agent(subagent_type="command-pedagogy-planner"', 'Agent(subagent_type="command-pedagogy-editor"', 'Agent(subagent_type="command-pedagogy-reviewer"', 'Agent(subagent_type="fact-reviewer"', ): self.assertIn(call, text) self.assertIn("CommandPatchSet", text) self.assertIn("initial command analysis", text) self.assertIn("final command analysis", text) self.assertIn("validate-command-pedagogy-artifact.py", text) self.assertIn("sha256", text.lower()) class FirstClassCommandContractTest(unittest.TestCase): def test_command_plan_and_patch_set_have_repository_schemas(self): schema_dir = os.path.join(PIPELINE, "schemas") expectations = { "command-plan.schema.json": {"section_id", "source_sha256", "mode", "command_groups"}, "command-patch-set.schema.json": {"source_sha256", "patches"}, } for name, required in expectations.items(): path = os.path.join(schema_dir, name) self.assertTrue(os.path.isfile(path), path) with open(path, encoding="utf-8") as fh: schema = json.load(fh) self.assertEqual("https://json-schema.org/draft/2020-12/schema", schema["$schema"]) self.assertTrue(required.issubset(set(schema["required"]))) def test_claude_command_agents_are_thin_adapters_to_canonical_contracts(self): pairs = { "command-pedagogy-planner.md": "contracts/command-pedagogy-planner.md", "command-pedagogy-editor.md": "contracts/command-pedagogy-editor.md", "command-pedagogy-reviewer.md": "contracts/command-pedagogy-reviewer.md", } for adapter_name, canonical_rel in pairs.items(): canonical = os.path.join(PIPELINE, canonical_rel) adapter = os.path.join(ROOT, ".claude", "agents", adapter_name) self.assertTrue(os.path.isfile(canonical), canonical) with open(adapter, encoding="utf-8") as fh: adapter_text = fh.read() self.assertIn( f".agents/skills/running-tech-log-pipeline/{canonical_rel}", adapter_text, ) substantive = [line for line in adapter_text.splitlines() if line.strip()] self.assertLessEqual(len(substantive), 14, adapter_name) def test_command_authoring_policy_declares_modes_without_blanket_bans(self): policy = os.path.join(PIPELINE, "policies", "command-authoring.yaml") self.assertTrue(os.path.isfile(policy), policy) with open(policy, encoding="utf-8") as fh: content = fh.read() for mode in ("tutorial", "operator", "diagnostic", "automation", "reference"): self.assertIn(f" {mode}:", content) self.assertIn("blanket_ban: false", content) self.assertIn("validation_cleanup_same_chain", content) def test_pipeline_structural_gate_requires_command_pedagogy_assets(self): verifier = os.path.join(ROOT, "scripts", "verify-pipeline.py") versions = os.path.join(ROOT, "scripts", "skill-versions.py") with open(verifier, encoding="utf-8") as fh: verifier_text = fh.read() with open(versions, encoding="utf-8") as fh: versions_text = fh.read() required = ( ".agents/skills/writing-practitioner-guides/references/command-pedagogy.md", ".agents/skills/running-tech-log-pipeline/policies/command-authoring.yaml", ".agents/skills/running-tech-log-pipeline/schemas/command-plan.schema.json", ".agents/skills/running-tech-log-pipeline/schemas/command-patch-set.schema.json", ".agents/skills/running-tech-log-pipeline/contracts/command-pedagogy-planner.md", ".agents/skills/running-tech-log-pipeline/contracts/command-pedagogy-editor.md", ".agents/skills/running-tech-log-pipeline/contracts/command-pedagogy-reviewer.md", ".claude/agents/command-pedagogy-planner.md", ".claude/agents/command-pedagogy-editor.md", ".claude/agents/command-pedagogy-reviewer.md", "scripts/command_pedagogy.py", "scripts/check-command-pedagogy.py", "scripts/apply-command-pedagogy-patch.py", "scripts/validate-command-pedagogy-artifact.py", ) for rel in required: self.assertIn(f'"{rel}"', verifier_text, rel) for rel in ( "scripts/command_pedagogy.py", "scripts/check-command-pedagogy.py", "scripts/apply-command-pedagogy-patch.py", "scripts/validate-command-pedagogy-artifact.py", ): self.assertIn(f'"{rel}"', versions_text, rel) if __name__ == "__main__": unittest.main()