Skip to content

Commit 9faa94d

Browse files
Trecekautoskillitclaude
authored
Implementation Plan: T5-P5-A12-WP1 Make diagram staleness enforcement automated (#4103)
## Summary Create `tests/arch/test_recipe_diagram_freshness.py` — a parametrized arch test that runs `check_diagram_staleness` against every bundled recipe YAML. Recipes with pre-rendered diagrams must not be stale (PASS); recipes without diagrams are marked `xfail(strict=True)` so CI blocks if a diagram is added without removing the xfail marker (XPASS → FAIL). Three meta-tests enforce minimum recipe count (>= 10), xfail cap shrink enforcement (<= 10), and collection sanity. Update `tests/arch/AGENTS.md` file table for the pre-commit hook invariant. ## Implementation Plan Plan file: `/home/talon/projects/autoskillit-runs/impl-20260612-235015-900129/.autoskillit/temp/make-plan/t5-p5-a12-wp1-diagram-freshness_plan_2026-06-12_235500.md` Closes #4031 🤖 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 | 59 | 10.9k | 778.3k | 74.3k | 29 | 78.0k | 10m 14s | | verify* | sonnet | 1 | 4.0k | 6.8k | 457.2k | 49.6k | 30 | 28.6k | 3m 38s | | implement* | MiniMax-M3 | 1 | 1.1M | 5.0k | 0 | 0 | 50 | 0 | 4m 18s | | fix* | sonnet | 1 | 190 | 11.7k | 1.1M | 58.9k | 64 | 38.5k | 5m 56s | | audit_impl* | sonnet | 1 | 44 | 7.7k | 166.4k | 42.0k | 12 | 30.0k | 3m 46s | | prepare_pr* | MiniMax-M3 | 1 | 186.4k | 2.2k | 0 | 0 | 14 | 0 | 54s | | compose_pr* | MiniMax-M3 | 1 | 175.5k | 999 | 0 | 0 | 12 | 0 | 35s | | **Total** | | | 1.4M | 45.3k | 2.5M | 74.3k | | 175.1k | 29m 24s | \* *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 | 77 | 0.0 | 0.0 | 65.6 | | fix | 8 | 134024.2 | 4811.9 | 1456.6 | | audit_impl | 0 | — | — | — | | prepare_pr | 0 | — | — | — | | compose_pr | 0 | — | — | — | | **Total** | **85** | 29107.4 | 2059.7 | 532.5 | ## Model Usage Breakdown | Model | steps | uncached | output | cache_read | cache_write | time | |-------|-------|----------|--------|------------|-------------|------| | opus[1m] | 1 | 59 | 10.9k | 778.3k | 78.0k | 10m 14s | | sonnet | 3 | 4.3k | 26.1k | 1.7M | 97.1k | 13m 22s | | MiniMax-M3 | 3 | 1.4M | 8.2k | 0 | 0 | 5m 46s | --------- Co-authored-by: autoskillit <autoskillit@anthropic.com> Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 750eec8 commit 9faa94d

6 files changed

Lines changed: 85 additions & 4 deletions

File tree

src/autoskillit/recipes/diagrams/implementation-groups.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
<!-- autoskillit-recipe-hash: sha256:0000000000000000000000000000000000000000000000000000000000000000 -->
1+
<!-- autoskillit-recipe-hash: sha256:c8e501dca905a687bfceb05c903a86d7f4bdb9d0057fe39dc510a303d595e1bf -->
22
<!-- autoskillit-diagram-format: v7 -->
33
## implementation-groups
44
Group-based implementation with per-group plan/implement/test cycles and PR gates.

src/autoskillit/recipes/diagrams/implementation.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
<!-- autoskillit-recipe-hash: sha256:169e575e265babf6ca6e9eebb9b33d0614759e29b68b5f613d179d82461ec2ea -->
1+
<!-- autoskillit-recipe-hash: sha256:6387790f3513145bccf0f7c64f4d1ac8e3b807f61bd3ff217da33b1bdefef946 -->
22
<!-- autoskillit-diagram-format: v7 -->
33
# implementation
44

src/autoskillit/recipes/diagrams/merge-prs.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
<!-- autoskillit-recipe-hash: sha256:0000000000000000000000000000000000000000000000000000000000000000 -->
1+
<!-- autoskillit-recipe-hash: sha256:a5e0288f51345023d8161c7568652dc056eb49b396ab23f7803ff26d62e98ed4 -->
22
<!-- autoskillit-diagram-format: v7 -->
33
## merge-prs
44
Merge multiple PRs into an integration branch with conflict resolution and CI gates.

src/autoskillit/recipes/diagrams/remediation.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
<!-- autoskillit-recipe-hash: sha256:0000000000000000000000000000000000000000000000000000000000000000 -->
1+
<!-- autoskillit-recipe-hash: sha256:3a11f66644f46f34ca24a05b186d0d2c7421a6c331a9440d764d0450f9a8ac27 -->
22
<!-- autoskillit-diagram-format: v7 -->
33
## remediation
44
Investigate, rectify, implement, and merge a bug fix with CI and PR gates.

tests/arch/AGENTS.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ AST enforcement, sub-package layer contracts, and architectural invariant tests.
7777
| `test_pyright_suppression_allowlist.py` | REQ-PYRIGHT-001: pyright/type-ignore suppression allowlist + count budget |
7878
| `test_python_no_hardcoded_temp.py` | Architectural invariant: no literal `.autoskillit/temp` outside the whitelist |
7979
| `test_quota_capability_isolation.py` | AST guard: quota modules must not reference BackendCapabilities fields |
80+
| `test_recipe_diagram_freshness.py` | Parametrized diagram freshness enforcement: bundled recipes must have non-stale diagrams; missing diagrams are xfail(strict=True) with shrink-enforcement meta-test |
8081
| `test_recipe_rule_registration.py` | REQ-RECIPE-001: every recipe/rules_*.py file must be imported by recipe/__init__.py |
8182
| `test_rule_severity_consistency.py` | AST guards: rule functions must use make_finding()/make_block_finding(); RuleFinding severity must match @semantic_rule decorator severity; _KNOWN_NON_CONFORMING_RULES entries must carry tracking comments |
8283
| `test_regex_guards.py` | Arch guard: keyword regexes in cmd-scanning rules must use path-safe lookbehind guards |
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
"""Parametrized diagram freshness enforcement: bundled recipes must have non-stale
2+
diagrams; missing diagrams are xfail(strict=True) with shrink-enforcement meta-test.
3+
"""
4+
5+
from __future__ import annotations
6+
7+
from pathlib import Path
8+
from typing import Any
9+
10+
import pytest
11+
12+
from autoskillit.recipe.diagrams import check_diagram_staleness
13+
from autoskillit.recipe.io import builtin_recipes_dir
14+
15+
pytestmark = [pytest.mark.layer("arch"), pytest.mark.small]
16+
17+
_RECIPES_DIR = builtin_recipes_dir()
18+
_BUNDLED_RECIPE_YAML_PATHS = sorted(_RECIPES_DIR.glob("*.yaml"))
19+
20+
MINIMUM_RECIPE_COUNT = 10
21+
CURRENT_XFAIL_CAP = 10
22+
23+
_RECIPES_WITH_DIAGRAMS = [
24+
p for p in _BUNDLED_RECIPE_YAML_PATHS if (_RECIPES_DIR / "diagrams" / f"{p.stem}.md").exists()
25+
]
26+
_WITHOUT_SET = set(_BUNDLED_RECIPE_YAML_PATHS) - set(_RECIPES_WITH_DIAGRAMS)
27+
_RECIPES_WITHOUT_DIAGRAMS = sorted(_WITHOUT_SET)
28+
29+
30+
def _make_params() -> list[Any]:
31+
params: list[Any] = []
32+
for p in _BUNDLED_RECIPE_YAML_PATHS:
33+
if p in _WITHOUT_SET:
34+
params.append(
35+
pytest.param(
36+
p,
37+
id=p.stem,
38+
marks=pytest.mark.xfail(
39+
strict=True,
40+
reason=f"diagram not yet created for {p.stem}",
41+
),
42+
)
43+
)
44+
else:
45+
params.append(pytest.param(p, id=p.stem))
46+
return params
47+
48+
49+
@pytest.mark.parametrize("recipe_path", _make_params())
50+
def test_bundled_recipe_diagram_not_stale(recipe_path: Path) -> None:
51+
is_stale = check_diagram_staleness(recipe_path.stem, _RECIPES_DIR, recipe_path)
52+
assert not is_stale, (
53+
f"Diagram for '{recipe_path.stem}' is stale or missing. "
54+
f"Run '/render-recipe {recipe_path.stem}' to regenerate."
55+
)
56+
57+
58+
def test_minimum_recipe_count() -> None:
59+
assert len(_BUNDLED_RECIPE_YAML_PATHS) >= MINIMUM_RECIPE_COUNT, (
60+
f"Expected at least {MINIMUM_RECIPE_COUNT} bundled recipe YAMLs, "
61+
f"found {len(_BUNDLED_RECIPE_YAML_PATHS)}. "
62+
"Is builtin_recipes_dir() resolving correctly?"
63+
)
64+
65+
66+
def test_xfail_diagram_count_is_shrinking() -> None:
67+
assert len(_RECIPES_WITHOUT_DIAGRAMS) <= CURRENT_XFAIL_CAP, (
68+
f"Expected at most {CURRENT_XFAIL_CAP} recipes without diagrams, "
69+
f"found {len(_RECIPES_WITHOUT_DIAGRAMS)}: "
70+
f"{sorted(p.stem for p in _RECIPES_WITHOUT_DIAGRAMS)}. "
71+
"A new recipe was added without a diagram — either create the diagram "
72+
"or raise CURRENT_XFAIL_CAP (with justification)."
73+
)
74+
75+
76+
def test_check_diagram_staleness_missing_is_stale(tmp_path: Path) -> None:
77+
(tmp_path / "diagrams").mkdir()
78+
recipe_yaml = tmp_path / "fake_recipe.yaml"
79+
recipe_yaml.write_text("steps: []\n")
80+
assert check_diagram_staleness("fake_recipe", tmp_path, recipe_yaml)

0 commit comments

Comments
 (0)