diff --git a/CHANGELOG.md b/CHANGELOG.md index a8e914c..02e9f32 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,9 +4,11 @@ summary: Chronological history of repository and skill changes. # Changelog -## 2026-07-20 — Composed ticket and epic execution +## 2026-07-20 — Portable ticket and epic execution +- feat: make ticket and epic execution runtime agnostic - feat: compose epic execution through implement-ticket + (7c4e500a35d48b5dba311094b4d34d8ca97f25a1) ## 2026-07-19 — Epic workflow and review contract cleanup diff --git a/README.md b/README.md index 349e177..fd4c9bb 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ A personal monorepo for agent skills and supporting scripts. evaluation fixtures shared by repository-owned review skills - `justfile` — common tasks for testing, validation, and formatting -Current skills: +Current reusable agent skills: - `skills/implement-ticket` — implement exactly one standalone ticket or named epic child through isolated execution, repository-owned review, PR gates, and @@ -29,6 +29,18 @@ Current skills: - `skills/review-code-simplicity` — reduce local cognitive load through behavior-preserving reuse, DRY, control-flow, and test simplification +The composed implementation dependency chain is: + +```text +implement-epic +└── implement-ticket + └── review-code-change +``` + +Compatible runtimes may provide named subagents or equivalent isolated +implementation and review contexts. OpenAI-facing files under `agents/` are +optional discovery metadata, not part of the skills' portable contracts. + ## Quick Start Run the core checks: @@ -52,7 +64,7 @@ Validate a review packet and result together: python3 review-suite/scripts/validate.py pair packet.json result.json ``` -Run deterministic local evals (no Codex required): +Run deterministic local evals without an agent runtime: ```bash just eval-prepare-changesets diff --git a/skills/implement-epic/SKILL.md b/skills/implement-epic/SKILL.md index 47eed53..41de666 100644 --- a/skills/implement-epic/SKILL.md +++ b/skills/implement-epic/SKILL.md @@ -21,6 +21,33 @@ inline a copy of the ticket workflow, or weaken any gate. cleanup, and terminal evidence. Do not invoke individual review lenses or `review-code-change` directly from this skill. +## Require compatible runtime capabilities + +A compatible agentic runtime must be able to: + +- load `implement-epic` and repository-owned `implement-ticket` by stable skill + name or an equivalent repository-owned dependency mechanism; +- read repository instructions plus structured GitHub or Linear relationships; +- retain task state while repeating the dependency-driven graph loop; +- invoke one mutating ticket execution in an exclusively owned branch/worktree + context and wait for its terminal result; +- inspect and verify returned candidate, PR, tracker, and base evidence; +- poll or wait for asynchronous ticket, CI, review, and graph-transition gates; + and +- preserve a fresh read-only reviewer context through the ticket result without + taking ownership of local review. + +The portable dependency chain is `implement-epic` → `implement-ticket` → +`review-code-change`. Verify `implement-ticket` directly and require its result +to prove that its own review dependency and applicable capabilities were +available. Do not make this skill invoke `review-code-change` itself. + +Stop before child mutation with an explicit limitation when an applicable +capability or dependency is unavailable. Product-specific discovery metadata +such as `agents/openai.yaml` may exist, but it does not constrain the operating +contract or require a particular agent product. Terms such as worker and +subagent describe possible isolated execution roles, not required product APIs. + ## Load graph and closeout references - Read [the GitHub graph adapter](references/github.md) whenever GitHub owns diff --git a/skills/implement-epic/evals/cases.json b/skills/implement-epic/evals/cases.json index 3b6e2da..75e8f9d 100644 --- a/skills/implement-epic/evals/cases.json +++ b/skills/implement-epic/evals/cases.json @@ -39,6 +39,7 @@ "id": "exclusive-delegated-worktree", "request": "Delegate ready child G-161 from epic G-160.", "graph": "G-161 is the only ready child.", + "runtime_profile": "Repository-owned skills and named subagents are available.", "delegation": "Worker returns a candidate from a named exclusive branch/worktree plus terminal evidence." }, { @@ -74,5 +75,31 @@ "request": "Implement ready children G-221 and G-222 in parallel.", "graph": "Both are graph-independent, but both edit the same shared contract and have ordered rollout semantics.", "authority": "parallel execution requested" + }, + { + "id": "equivalent-isolated-context-profile", + "request": "Implement epic G-230 through authorized child merges.", + "graph": "G-231 is the only open ready child.", + "runtime_profile": "The runtime calls isolated units jobs rather than subagents, but provides one exclusive mutating job, a fresh read-only audit job inside implement-ticket, retained loop state, and asynchronous waiting.", + "ticket_results": ["G-231 merged with internally consistent evidence"] + }, + { + "id": "missing-review-dependency-through-ticket", + "request": "Implement epic G-240.", + "graph": "G-241 is the only open ready child.", + "runtime_profile": "implement-ticket is available, but repository-owned review-code-change cannot be loaded.", + "ticket_results": ["G-241 blocked before mutation because review-code-change is unavailable"] + }, + { + "id": "missing-isolation-capability", + "request": "Implement epic G-250.", + "graph": "G-251 is the only open ready child.", + "runtime_profile": "The runtime cannot create or verify an exclusively owned mutating ticket context." + }, + { + "id": "missing-asynchronous-wait", + "request": "Implement epic G-260 through child merges.", + "graph": "G-261 is open and ready, with asynchronous CI and review gates required by its ticket workflow.", + "runtime_profile": "The runtime cannot poll, wait, or resume after asynchronous child gates." } ] diff --git a/skills/implement-epic/evals/results.json b/skills/implement-epic/evals/results.json index 641af0e..5ea00ca 100644 --- a/skills/implement-epic/evals/results.json +++ b/skills/implement-epic/evals/results.json @@ -63,5 +63,25 @@ "case_id": "parallel-nonoverlap-required", "workflow_state": "serial_execution_required", "required_actions": ["reject parallel mutation", "sequence shared-contract work", "recompute readiness after each result"] + }, + { + "case_id": "equivalent-isolated-context-profile", + "workflow_state": "epic_children_merged", + "required_actions": ["accept equivalent isolated context terminology", "verify G-231 merged evidence", "refresh the complete graph", "preserve review ownership inside implement-ticket"] + }, + { + "case_id": "missing-review-dependency-through-ticket", + "workflow_state": "blocked", + "required_actions": ["preserve the G-241 blocked result", "name review-code-change as unavailable", "do not invoke review directly", "do not count G-241 complete"] + }, + { + "case_id": "missing-isolation-capability", + "workflow_state": "blocked", + "required_actions": ["name exclusive mutating context isolation as unavailable", "perform no child mutation", "do not weaken ownership checks"] + }, + { + "case_id": "missing-asynchronous-wait", + "workflow_state": "blocked", + "required_actions": ["name asynchronous wait as unavailable", "perform no child mutation", "do not pretend ticket or remote gates passed"] } ] diff --git a/skills/implement-epic/scripts/tests/test_orchestration_contract.py b/skills/implement-epic/scripts/tests/test_orchestration_contract.py index 7475a8a..666011d 100644 --- a/skills/implement-epic/scripts/tests/test_orchestration_contract.py +++ b/skills/implement-epic/scripts/tests/test_orchestration_contract.py @@ -25,6 +25,10 @@ def setUpClass(cls): cls.linear = read(SKILL_ROOT / "references" / "linear.md") cls.closeout = read(SKILL_ROOT / "references" / "closeout.md") cls.contract = compact(cls.skill + cls.github + cls.linear + cls.closeout) + cls.eval_contract = compact( + read(SKILL_ROOT / "evals" / "cases.json") + + read(SKILL_ROOT / "evals" / "results.json") + ) cls.cases = { item["id"]: item for item in json.loads(read(SKILL_ROOT / "evals" / "cases.json")) @@ -43,6 +47,28 @@ def test_canonical_name_and_metadata(self): self.assertIn("$implement-epic", metadata) self.assertNotIn("$implement-epic-sequence", metadata) + def test_product_neutral_runtime_contract(self): + self.assertNotIn("Codex", self.contract) + self.assertNotIn("OpenAI", self.contract) + self.assertNotIn("Codex", self.eval_contract) + self.assertNotIn("OpenAI", self.eval_contract) + self.assertIn("compatible agentic runtime", self.contract) + self.assertIn( + "`implement-epic` and repository-owned `implement-ticket` by stable skill name", + self.contract, + ) + self.assertIn( + "`implement-epic` → `implement-ticket` → `review-code-change`", + self.contract, + ) + self.assertIn("retain task state", self.contract) + self.assertIn("poll or wait for asynchronous", self.contract) + self.assertIn( + "worker and subagent describe possible isolated execution roles", + self.contract, + ) + self.assertIn("does not constrain the operating contract", self.contract) + def test_implement_ticket_is_required_and_owns_one_ticket(self): self.assertIn("verify that `implement-ticket` is available", self.contract) self.assertIn( @@ -138,6 +164,10 @@ def test_forward_cases_cover_composed_contract(self): "umbrella-closeout-separately", "unexpected-requires-epic-child-result", "parallel-nonoverlap-required", + "equivalent-isolated-context-profile", + "missing-review-dependency-through-ticket", + "missing-isolation-capability", + "missing-asynchronous-wait", } self.assertEqual(required, set(self.cases)) self.assertEqual(required, set(self.results)) @@ -158,6 +188,16 @@ def test_forward_results_preserve_critical_boundaries(self): "blocked", self.results["unexpected-requires-epic-child-result"]["workflow_state"], ) + self.assertEqual( + "epic_children_merged", + self.results["equivalent-isolated-context-profile"]["workflow_state"], + ) + for case_id in ( + "missing-review-dependency-through-ticket", + "missing-isolation-capability", + "missing-asynchronous-wait", + ): + self.assertEqual("blocked", self.results[case_id]["workflow_state"]) if __name__ == "__main__": diff --git a/skills/implement-ticket/SKILL.md b/skills/implement-ticket/SKILL.md index 7fda69f..03da8f9 100644 --- a/skills/implement-ticket/SKILL.md +++ b/skills/implement-ticket/SKILL.md @@ -34,7 +34,8 @@ same-numbered issue from the PR host for the real tracker ticket. A compatible agentic runtime must be able to: -- load this skill and repository-owned skill dependencies; +- load `implement-ticket` and repository-owned `review-code-change` by stable + skill name or an equivalent repository-owned dependency mechanism; - read repository instructions, tracker state, and structured relationships; - inspect and create isolated branch/worktree state; - edit files, run commands, commit, push, and manage PRs when authorized; @@ -46,7 +47,8 @@ A compatible agentic runtime must be able to: Stop with an explicit missing-capability result when an applicable capability is unavailable. Product-specific discovery metadata such as `agents/openai.yaml` may exist, but it does not constrain the operating contract or require a -particular agent product. +particular agent product. Terms such as worker and subagent describe possible +isolated execution roles, not required product APIs. ## Resolve the operating contract @@ -93,10 +95,11 @@ manual transition. State that consequence before publication or merge. Do not use automatic closing syntax when its effect conflicts with the resolved completion policy. -For a merge-inclusive run, verify before implementation that -`review-code-change` is available and readable. It is the only local -adversarial-review dependency. Return `blocked` when it is unavailable; do not -substitute a third-party skill, generic self-review, or unreviewed merge path. +Before implementation can produce a reviewed PR, verify that +`review-code-change` is available and readable by stable name or an equivalent +repository-owned dependency mechanism. It is the only local adversarial-review +dependency. Return `blocked` before mutation when it is unavailable; do not +substitute a third-party skill, generic self-review, or unreviewed path. ## Establish source-of-truth precedence diff --git a/skills/implement-ticket/evals/cases.json b/skills/implement-ticket/evals/cases.json index 0e45e4e..3dbfa04 100644 --- a/skills/implement-ticket/evals/cases.json +++ b/skills/implement-ticket/evals/cases.json @@ -4,7 +4,8 @@ "request": "Implement GitHub issue G-21.", "tracker_state": "Open standalone PR-sized ticket with complete requirements, no blockers, and no existing implementation.", "authority": "ready_pr_only", - "capabilities": ["review-code-change", "isolated_worktree", "github_pr"] + "runtime_profile": "Repository-owned skills and named subagents are available.", + "capabilities": ["stable_skill_loading", "structured_tracker_relationships", "isolated_worktree", "command_execution", "github_pr", "named_read_only_reviewer", "asynchronous_wait", "thread_aware_feedback"] }, { "id": "authorized-merge-cleanup", @@ -93,5 +94,31 @@ "tracker_state": "G-35 has children and requires whole-epic delivery.", "incoming_handoff": "implement-ticket:requires-epic:github:G-35", "capabilities": [] + }, + { + "id": "equivalent-isolated-context-profile", + "request": "Implement GitHub issue G-38 to a ready PR.", + "tracker_state": "G-38 is a ready standalone ticket with no competing implementation.", + "runtime_profile": "The runtime calls isolated units jobs rather than subagents, but provides an exclusive mutating job and a fresh read-only audit job with equivalent permissions and integrity checks.", + "capabilities": ["stable_skill_loading", "structured_tracker_relationships", "isolated_worktree", "command_execution", "github_pr", "equivalent_read_only_reviewer", "asynchronous_wait", "thread_aware_feedback"] + }, + { + "id": "missing-review-code-change", + "request": "Implement GitHub issue G-39.", + "tracker_state": "G-39 is otherwise ready and independently shippable.", + "runtime_profile": "The runtime cannot load repository-owned review-code-change by stable name or an equivalent dependency mechanism." + }, + { + "id": "missing-isolation-capability", + "request": "Implement GitHub issue G-40.", + "tracker_state": "G-40 is otherwise ready and independently shippable.", + "runtime_profile": "The runtime cannot create or verify an exclusively owned implementation branch/worktree context." + }, + { + "id": "missing-asynchronous-wait", + "request": "Implement and merge GitHub issue G-41.", + "tracker_state": "G-41 is otherwise ready and requires asynchronous CI and review gates.", + "authority": "merge_after_gates", + "runtime_profile": "The runtime cannot poll, wait, or resume after asynchronous gates." } ] diff --git a/skills/implement-ticket/evals/results.json b/skills/implement-ticket/evals/results.json index e460cc7..a4eea6e 100644 --- a/skills/implement-ticket/evals/results.json +++ b/skills/implement-ticket/evals/results.json @@ -78,5 +78,25 @@ "case_id": "repeated-epic-handoff", "terminal_state": "blocked", "required_actions": ["report routing cycle detected", "perform no mutation", "do not emit another requires_epic"] + }, + { + "case_id": "equivalent-isolated-context-profile", + "terminal_state": "ready_pr", + "required_actions": ["accept equivalent isolated context terminology", "verify exclusive mutating ownership", "invoke fresh read-only review", "publish one PR"] + }, + { + "case_id": "missing-review-code-change", + "terminal_state": "blocked", + "required_actions": ["name review-code-change as unavailable", "perform no implementation mutation", "do not substitute generic or third-party review"] + }, + { + "case_id": "missing-isolation-capability", + "terminal_state": "blocked", + "required_actions": ["name exclusive implementation isolation as unavailable", "perform no implementation mutation", "preserve existing worktrees"] + }, + { + "case_id": "missing-asynchronous-wait", + "terminal_state": "blocked", + "required_actions": ["name asynchronous wait as unavailable", "perform no implementation mutation", "do not pretend CI or review gates passed"] } ] diff --git a/skills/implement-ticket/scripts/tests/test_implement_ticket_contract.py b/skills/implement-ticket/scripts/tests/test_implement_ticket_contract.py index 1a1cb9e..31df47c 100644 --- a/skills/implement-ticket/scripts/tests/test_implement_ticket_contract.py +++ b/skills/implement-ticket/scripts/tests/test_implement_ticket_contract.py @@ -30,6 +30,10 @@ def setUpClass(cls): cls.linear_compact = compact(cls.linear) cls.gates_compact = compact(cls.gates) cls.result_compact = compact(cls.result) + cls.eval_contract = compact( + read(SKILL_ROOT / "evals" / "cases.json") + + read(SKILL_ROOT / "evals" / "results.json") + ) cls.all_contract = compact( cls.skill + cls.github + cls.linear + cls.gates + cls.result ) @@ -45,8 +49,23 @@ def setUpClass(cls): def test_frontmatter_and_product_neutral_contract(self): self.assertTrue(self.skill.startswith("---\nname: implement-ticket\n")) self.assertNotIn("Codex", self.all_contract) + self.assertNotIn("OpenAI", self.all_contract) + self.assertNotIn("Codex", self.eval_contract) + self.assertNotIn("OpenAI", self.eval_contract) self.assertNotIn("code-review-pro", self.all_contract) self.assertIn("compatible agentic runtime", self.skill) + self.assertIn( + "`implement-ticket` and repository-owned `review-code-change` by stable skill name", + self.skill_compact, + ) + self.assertIn( + "worker and subagent describe possible isolated execution roles", + self.skill_compact, + ) + self.assertIn( + "does not constrain the operating contract", + self.skill_compact, + ) def test_scope_is_exactly_one_ticket(self): self.assertIn("one ticket per branch, worktree, and PR", self.skill_compact) @@ -106,6 +125,7 @@ def test_authority_does_not_expand(self): def test_review_dependency_and_integrity_are_preserved(self): self.assertIn("only local adversarial-review dependency", self.all_contract) + self.assertIn("Return `blocked` before mutation", self.skill_compact) self.assertIn("Do not substitute another skill", self.gates_compact) self.assertIn( "fresh or minimally inherited read-only context", self.gates_compact @@ -162,6 +182,10 @@ def test_forward_cases_cover_required_boundaries(self): "explicit-child-not-redirected", "missing-implement-epic", "repeated-epic-handoff", + "equivalent-isolated-context-profile", + "missing-review-code-change", + "missing-isolation-capability", + "missing-asynchronous-wait", } self.assertEqual(required, set(self.cases)) self.assertEqual(required, set(self.results)) @@ -184,6 +208,16 @@ def test_forward_results_enforce_routing_and_authority(self): self.assertEqual( "blocked", self.results["repeated-epic-handoff"]["terminal_state"] ) + self.assertEqual( + "ready_pr", + self.results["equivalent-isolated-context-profile"]["terminal_state"], + ) + for case_id in ( + "missing-review-code-change", + "missing-isolation-capability", + "missing-asynchronous-wait", + ): + self.assertEqual("blocked", self.results[case_id]["terminal_state"]) self.assertIn( "do not merge", self.results["clean-local-review-remote-pending"]["required_actions"],