Skip to content

Commit c063618

Browse files
Trecekclaude
andauthored
Implementation Plan: Discipline Delivery Channel Matrix Contract (#4119)
## Summary Create `tests/contracts/test_discipline_delivery_matrix.py` — a parametrized 8-case contract test suite that encodes the session-type × backend discipline-delivery channel matrix. The test exercises `build_interactive_cmd`, `build_skill_session_cmd`, `build_food_truck_cmd`, and `_read_full_sous_chef` composition paths to assert that the PRIMARY delivery channel is populated for each (SessionType × backend) combination, making future regressions CI-detectable. Also update `tests/contracts/AGENTS.md` with the new file entry. Closes #4044 ## Implementation Plan Plan file: `/home/talon/projects/autoskillit-runs/impl-20260626-224909-325309/.autoskillit/temp/make-plan/t5_p6_a1_wp1_discipline_delivery_matrix_plan_2026-06-26_230000.md` 🤖 Generated with [Claude Code](https://claude.com/claude-code) via AutoSkillit <!-- autoskillit:pipeline-signature steps=prepare_pr,run_arch_lenses,compose_pr,annotate_pr_diff,review_pr --> ## Token Usage Summary | Step | Model | count | uncached | output | cache_read | peak_ctx | turns | cache_write | time | |------|-------|-------|----------|--------|------------|----------|-------|-------------|------| | plan* | opus[1m] | 1 | 2.5k | 25.1k | 2.3M | 130.1k | 57 | 135.7k | 16m 5s | | verify* | sonnet | 1 | 1.5k | 5.1k | 152.4k | 47.8k | 15 | 50.8k | 6m 53s | | implement* | MiniMax-M3 | 1 | 69.2k | 10.8k | 1.2M | 0 | 50 | 0 | 4m 36s | | audit_impl* | sonnet | 1 | 52 | 12.6k | 199.8k | 42.0k | 16 | 37.8k | 3m 38s | | prepare_pr* | MiniMax-M3 | 1 | 41.7k | 3.1k | 192.0k | 0 | 13 | 0 | 47s | | compose_pr* | MiniMax-M3 | 1 | 36.4k | 1.7k | 140.9k | 0 | 12 | 0 | 32s | | **Total** | | | 151.3k | 58.4k | 4.2M | 130.1k | | 224.4k | 32m 32s | \* *Step used a non-Anthropic provider; caching behavior may differ.* ## Token Efficiency | Step | LoC Changed | cache_read/LoC | cache_write/LoC | output/LoC | |------|-------------|----------------|-----------------|------------| | plan | 0 | — | — | — | | verify | 0 | — | — | — | | implement | 178 | 6686.2 | 0.0 | 60.7 | | audit_impl | 0 | — | — | — | | prepare_pr | 0 | — | — | — | | compose_pr | 0 | — | — | — | | **Total** | **178** | 23701.3 | 1260.6 | 327.9 | ## Model Usage Breakdown | Model | steps | uncached | output | cache_read | cache_write | time | |-------|-------|----------|--------|------------|-------------|------| | opus[1m] | 1 | 2.5k | 25.1k | 2.3M | 135.7k | 16m 5s | | sonnet | 2 | 1.6k | 17.7k | 352.2k | 88.6k | 10m 31s | | MiniMax-M3 | 3 | 147.2k | 15.6k | 1.5M | 0 | 5m 55s | --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 3b37a1d commit c063618

2 files changed

Lines changed: 159 additions & 0 deletions

File tree

tests/contracts/AGENTS.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ Protocol satisfaction, package gateway, and skill contract compliance tests.
2626
| `test_config_field_coverage.py` | REQ-CONFIG-001: every sub-config dataclass field must be referenced in from_dynaconf |
2727
| `test_core_public_api_surface.py` | Validates that every symbol in autoskillit.core.__all__ is importable via the public gateway |
2828
| `test_diagnose_ci_steps.py` | Contract tests for diagnose-ci SKILL.md step numbering and cross-reference integrity |
29+
| `test_discipline_delivery_matrix.py` | Contract: session-type x backend discipline delivery channel matrix — interactive primary channel, headless prompt + completion marker, skill prompt, and sous-chef composition |
2930
| `test_docstring_skill_prefix.py` | Contract: source files must not use /autoskillit: prefix for skills_extended skills |
3031
| `test_environment_setup_design_contracts.py` | Contract tests verifying the environment-setup skill design doc completeness |
3132
| `test_ephemeral_skill_namespace.py` | Contract: ephemeral SKILL.md bodies use the correct namespace for the session delivery channel |
Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
"""Discipline delivery channel matrix contract tests.
2+
3+
Session-type x backend parametrized tests asserting that each combination's
4+
PRIMARY delivery channel is populated via the correct builder.
5+
"""
6+
7+
from __future__ import annotations
8+
9+
from pathlib import Path
10+
11+
import pytest
12+
13+
from autoskillit.core import (
14+
SESSION_TYPE_ENV_VAR,
15+
SESSION_TYPE_FLEET,
16+
SESSION_TYPE_ORCHESTRATOR,
17+
SESSION_TYPE_SKILL,
18+
DirectInstall,
19+
)
20+
from autoskillit.execution.backends.claude import ClaudeCodeBackend
21+
from autoskillit.execution.backends.codex import CodexBackend # noqa: F401
22+
23+
pytestmark = [pytest.mark.layer("contracts"), pytest.mark.small]
24+
25+
26+
def _assert_interactive_primary_channel(backend, spec) -> None:
27+
"""Assert the interactive primary delivery channel is populated."""
28+
if isinstance(backend, ClaudeCodeBackend):
29+
assert "--append-system-prompt" in spec.cmd
30+
else:
31+
assert any("developer_instructions=" in arg for arg in spec.cmd)
32+
33+
34+
class TestFleetInteractive:
35+
@pytest.mark.parametrize(
36+
"backend",
37+
[ClaudeCodeBackend(), CodexBackend()],
38+
ids=["claude-code", "codex"],
39+
)
40+
def test_primary_channel_populated(self, backend) -> None:
41+
spec = backend.build_interactive_cmd(
42+
system_prompt="Fleet discipline prompt",
43+
env_extras={SESSION_TYPE_ENV_VAR: SESSION_TYPE_FLEET},
44+
)
45+
_assert_interactive_primary_channel(backend, spec)
46+
47+
@pytest.mark.parametrize(
48+
"backend",
49+
[ClaudeCodeBackend(), CodexBackend()],
50+
ids=["claude-code", "codex"],
51+
)
52+
def test_session_type_fleet_in_env(self, backend) -> None:
53+
spec = backend.build_interactive_cmd(
54+
system_prompt="Fleet discipline prompt",
55+
env_extras={SESSION_TYPE_ENV_VAR: SESSION_TYPE_FLEET},
56+
)
57+
assert spec.env.get(SESSION_TYPE_ENV_VAR) == SESSION_TYPE_FLEET
58+
59+
60+
class TestOrchestratorInteractive:
61+
@pytest.mark.parametrize(
62+
"backend",
63+
[ClaudeCodeBackend(), CodexBackend()],
64+
ids=["claude-code", "codex"],
65+
)
66+
def test_primary_channel_populated(self, backend) -> None:
67+
spec = backend.build_interactive_cmd(
68+
system_prompt="Orchestrator discipline prompt",
69+
)
70+
_assert_interactive_primary_channel(backend, spec)
71+
72+
@pytest.mark.parametrize(
73+
"backend",
74+
[ClaudeCodeBackend(), CodexBackend()],
75+
ids=["claude-code", "codex"],
76+
)
77+
def test_no_session_type_assertion(self, backend) -> None:
78+
spec = backend.build_interactive_cmd(
79+
system_prompt="Orchestrator discipline prompt",
80+
)
81+
assert not spec.env.get(SESSION_TYPE_ENV_VAR, "")
82+
83+
84+
class TestOrchestratorHeadless:
85+
@pytest.mark.parametrize(
86+
"backend",
87+
[ClaudeCodeBackend(), CodexBackend()],
88+
ids=["claude-code", "codex"],
89+
)
90+
def test_orchestrator_prompt_non_empty(self, backend) -> None:
91+
spec = backend.build_food_truck_cmd(
92+
orchestrator_prompt="Run the pipeline",
93+
plugin_source=DirectInstall(plugin_dir=Path("/tmp")),
94+
cwd="/tmp",
95+
completion_marker="%%DONE%%",
96+
)
97+
assert any("Run the pipeline" in arg for arg in spec.cmd)
98+
assert any("%%DONE%%" in arg for arg in spec.cmd)
99+
100+
@pytest.mark.parametrize(
101+
"backend",
102+
[ClaudeCodeBackend(), CodexBackend()],
103+
ids=["claude-code", "codex"],
104+
)
105+
def test_session_type_orchestrator_in_env(self, backend) -> None:
106+
spec = backend.build_food_truck_cmd(
107+
orchestrator_prompt="Run the pipeline",
108+
plugin_source=DirectInstall(plugin_dir=Path("/tmp")),
109+
cwd="/tmp",
110+
completion_marker="%%DONE%%",
111+
)
112+
assert spec.env.get(SESSION_TYPE_ENV_VAR) == SESSION_TYPE_ORCHESTRATOR
113+
114+
115+
class TestSkillSession:
116+
@pytest.mark.parametrize(
117+
"backend",
118+
[ClaudeCodeBackend(), CodexBackend()],
119+
ids=["claude-code", "codex"],
120+
)
121+
def test_positional_prompt_non_empty(self, backend) -> None:
122+
spec = backend.build_skill_session_cmd("/investigate foo", "/tmp")
123+
assert any("investigate" in arg for arg in spec.cmd)
124+
125+
@pytest.mark.parametrize(
126+
"backend",
127+
[ClaudeCodeBackend(), CodexBackend()],
128+
ids=["claude-code", "codex"],
129+
)
130+
def test_session_type_skill_in_env(self, backend) -> None:
131+
spec = backend.build_skill_session_cmd("/investigate foo", "/tmp")
132+
assert spec.env.get(SESSION_TYPE_ENV_VAR) == SESSION_TYPE_SKILL
133+
134+
135+
class TestSousChefDelivery:
136+
def test_sous_chef_in_orchestrator_prompt(self) -> None:
137+
from autoskillit.cli._prompts import _build_orchestrator_prompt, _read_full_sous_chef
138+
139+
sous_chef = _read_full_sous_chef()
140+
assert sous_chef, "_read_full_sous_chef must return non-empty content"
141+
prompt = _build_orchestrator_prompt("test-recipe", "mcp__autoskillit__")
142+
assert sous_chef[:80] in prompt
143+
144+
def test_sous_chef_in_open_kitchen_prompt(self) -> None:
145+
from autoskillit.cli._prompts import _build_open_kitchen_prompt, _read_full_sous_chef
146+
147+
sous_chef = _read_full_sous_chef()
148+
assert sous_chef, "_read_full_sous_chef must return non-empty content"
149+
prompt = _build_open_kitchen_prompt("mcp__autoskillit__")
150+
assert sous_chef[:80] in prompt
151+
152+
def test_sous_chef_not_in_fleet_dispatch_prompt(self) -> None:
153+
from autoskillit.cli._prompts import _build_fleet_dispatch_prompt, _read_full_sous_chef
154+
155+
sous_chef = _read_full_sous_chef()
156+
assert sous_chef, "_read_full_sous_chef must return non-empty content"
157+
prompt = _build_fleet_dispatch_prompt("mcp__autoskillit__")
158+
assert sous_chef[:80] not in prompt

0 commit comments

Comments
 (0)