diff --git a/agent-ops/skills/common/prepare-epic-work-items/scripts/run_epic_cycle.py b/agent-ops/skills/common/prepare-epic-work-items/scripts/run_epic_cycle.py index c01b0ff7..cfb4dd01 100755 --- a/agent-ops/skills/common/prepare-epic-work-items/scripts/run_epic_cycle.py +++ b/agent-ops/skills/common/prepare-epic-work-items/scripts/run_epic_cycle.py @@ -343,7 +343,7 @@ def validate_pairs( pairs.append((plan, review, header)) union.update(target_ids) contents = plan.read_text(encoding="utf-8") + "\n" + review.read_text(encoding="utf-8") - if "[TODO" in contents or "" in contents or "" in contents: + if "[TODO" in contents or "" in contents: raise CycleError(f"unresolved template token: {plan.parent}") if dispatcher.is_file(): run( diff --git a/agent-ops/skills/common/prepare-epic-work-items/tests/test_run_epic_cycle.py b/agent-ops/skills/common/prepare-epic-work-items/tests/test_run_epic_cycle.py index cd1261b5..8650d300 100644 --- a/agent-ops/skills/common/prepare-epic-work-items/tests/test_run_epic_cycle.py +++ b/agent-ops/skills/common/prepare-epic-work-items/tests/test_run_epic_cycle.py @@ -260,6 +260,32 @@ class EpicCycleContractTest(unittest.TestCase): self.assertEqual([pair[0].parent.name for pair in pairs], ["02_second"]) self.assertEqual(union, {"second-task"}) + def test_validate_pair_allows_documented_milestone_slug_notation(self) -> None: + with tempfile.TemporaryDirectory() as raw: + workspace = Path(raw) + command(workspace, "git", "init") + task = workspace / "agent-task" / "m-sample" / "01_work" + task.mkdir(parents=True) + header = ( + "\n" + ) + (task / "PLAN-local-G01.md").write_text( + header + "# Plan\n", + encoding="utf-8", + ) + (task / "CODE_REVIEW-local-G01.md").write_text( + header + + "# Review\n\n" + + "For task groups matching `m-`, preserve metadata.\n", + encoding="utf-8", + ) + + pairs, union = MODULE.validate_pairs(workspace, "m-sample", {"inside"}) + + self.assertEqual(len(pairs), 1) + self.assertEqual(union, {"inside"}) + def test_validate_pair_rejects_foreign_pair_outside_selected_batch(self) -> None: with tempfile.TemporaryDirectory() as raw: workspace = Path(raw)