atdd 0.2.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- atdd/__init__.py +6 -0
- atdd/__main__.py +4 -0
- atdd/cli.py +404 -0
- atdd/coach/__init__.py +0 -0
- atdd/coach/commands/__init__.py +0 -0
- atdd/coach/commands/add_persistence_metadata.py +215 -0
- atdd/coach/commands/analyze_migrations.py +188 -0
- atdd/coach/commands/consumers.py +720 -0
- atdd/coach/commands/infer_governance_status.py +149 -0
- atdd/coach/commands/initializer.py +177 -0
- atdd/coach/commands/interface.py +1078 -0
- atdd/coach/commands/inventory.py +565 -0
- atdd/coach/commands/migration.py +240 -0
- atdd/coach/commands/registry.py +1560 -0
- atdd/coach/commands/session.py +430 -0
- atdd/coach/commands/sync.py +405 -0
- atdd/coach/commands/test_interface.py +399 -0
- atdd/coach/commands/test_runner.py +141 -0
- atdd/coach/commands/tests/__init__.py +1 -0
- atdd/coach/commands/tests/test_telemetry_array_validation.py +235 -0
- atdd/coach/commands/traceability.py +4264 -0
- atdd/coach/conventions/session.convention.yaml +754 -0
- atdd/coach/overlays/__init__.py +2 -0
- atdd/coach/overlays/claude.md +2 -0
- atdd/coach/schemas/config.schema.json +34 -0
- atdd/coach/schemas/manifest.schema.json +101 -0
- atdd/coach/templates/ATDD.md +282 -0
- atdd/coach/templates/SESSION-TEMPLATE.md +327 -0
- atdd/coach/utils/__init__.py +0 -0
- atdd/coach/utils/graph/__init__.py +0 -0
- atdd/coach/utils/graph/urn.py +875 -0
- atdd/coach/validators/__init__.py +0 -0
- atdd/coach/validators/shared_fixtures.py +365 -0
- atdd/coach/validators/test_enrich_wagon_registry.py +167 -0
- atdd/coach/validators/test_registry.py +575 -0
- atdd/coach/validators/test_session_validation.py +1183 -0
- atdd/coach/validators/test_traceability.py +448 -0
- atdd/coach/validators/test_update_feature_paths.py +108 -0
- atdd/coach/validators/test_validate_contract_consumers.py +297 -0
- atdd/coder/__init__.py +1 -0
- atdd/coder/conventions/adapter.recipe.yaml +88 -0
- atdd/coder/conventions/backend.convention.yaml +460 -0
- atdd/coder/conventions/boundaries.convention.yaml +666 -0
- atdd/coder/conventions/commons.convention.yaml +460 -0
- atdd/coder/conventions/complexity.recipe.yaml +109 -0
- atdd/coder/conventions/component-naming.convention.yaml +178 -0
- atdd/coder/conventions/design.convention.yaml +327 -0
- atdd/coder/conventions/design.recipe.yaml +273 -0
- atdd/coder/conventions/dto.convention.yaml +660 -0
- atdd/coder/conventions/frontend.convention.yaml +542 -0
- atdd/coder/conventions/green.convention.yaml +1012 -0
- atdd/coder/conventions/presentation.convention.yaml +587 -0
- atdd/coder/conventions/refactor.convention.yaml +535 -0
- atdd/coder/conventions/technology.convention.yaml +206 -0
- atdd/coder/conventions/tests/__init__.py +0 -0
- atdd/coder/conventions/tests/test_adapter_recipe.py +302 -0
- atdd/coder/conventions/tests/test_complexity_recipe.py +289 -0
- atdd/coder/conventions/tests/test_component_taxonomy.py +278 -0
- atdd/coder/conventions/tests/test_component_urn_naming.py +165 -0
- atdd/coder/conventions/tests/test_thinness_recipe.py +286 -0
- atdd/coder/conventions/thinness.recipe.yaml +82 -0
- atdd/coder/conventions/train.convention.yaml +325 -0
- atdd/coder/conventions/verification.protocol.yaml +53 -0
- atdd/coder/schemas/design_system.schema.json +361 -0
- atdd/coder/validators/__init__.py +0 -0
- atdd/coder/validators/test_commons_structure.py +485 -0
- atdd/coder/validators/test_complexity.py +416 -0
- atdd/coder/validators/test_cross_language_consistency.py +431 -0
- atdd/coder/validators/test_design_system_compliance.py +413 -0
- atdd/coder/validators/test_dto_testing_patterns.py +268 -0
- atdd/coder/validators/test_green_cross_stack_layers.py +168 -0
- atdd/coder/validators/test_green_layer_dependencies.py +148 -0
- atdd/coder/validators/test_green_python_layer_structure.py +103 -0
- atdd/coder/validators/test_green_supabase_layer_structure.py +103 -0
- atdd/coder/validators/test_import_boundaries.py +396 -0
- atdd/coder/validators/test_init_file_urns.py +593 -0
- atdd/coder/validators/test_preact_layer_boundaries.py +221 -0
- atdd/coder/validators/test_presentation_convention.py +260 -0
- atdd/coder/validators/test_python_architecture.py +674 -0
- atdd/coder/validators/test_quality_metrics.py +420 -0
- atdd/coder/validators/test_station_master_pattern.py +244 -0
- atdd/coder/validators/test_train_infrastructure.py +454 -0
- atdd/coder/validators/test_train_urns.py +293 -0
- atdd/coder/validators/test_typescript_architecture.py +616 -0
- atdd/coder/validators/test_usecase_structure.py +421 -0
- atdd/coder/validators/test_wagon_boundaries.py +586 -0
- atdd/conftest.py +126 -0
- atdd/planner/__init__.py +1 -0
- atdd/planner/conventions/acceptance.convention.yaml +538 -0
- atdd/planner/conventions/appendix.convention.yaml +187 -0
- atdd/planner/conventions/artifact-naming.convention.yaml +852 -0
- atdd/planner/conventions/component.convention.yaml +670 -0
- atdd/planner/conventions/criteria.convention.yaml +141 -0
- atdd/planner/conventions/feature.convention.yaml +371 -0
- atdd/planner/conventions/interface.convention.yaml +382 -0
- atdd/planner/conventions/steps.convention.yaml +141 -0
- atdd/planner/conventions/train.convention.yaml +552 -0
- atdd/planner/conventions/wagon.convention.yaml +275 -0
- atdd/planner/conventions/wmbt.convention.yaml +258 -0
- atdd/planner/schemas/acceptance.schema.json +336 -0
- atdd/planner/schemas/appendix.schema.json +78 -0
- atdd/planner/schemas/component.schema.json +114 -0
- atdd/planner/schemas/feature.schema.json +197 -0
- atdd/planner/schemas/train.schema.json +192 -0
- atdd/planner/schemas/wagon.schema.json +281 -0
- atdd/planner/schemas/wmbt.schema.json +59 -0
- atdd/planner/validators/__init__.py +0 -0
- atdd/planner/validators/conftest.py +5 -0
- atdd/planner/validators/test_draft_wagon_registry.py +374 -0
- atdd/planner/validators/test_plan_cross_refs.py +240 -0
- atdd/planner/validators/test_plan_uniqueness.py +224 -0
- atdd/planner/validators/test_plan_urn_resolution.py +268 -0
- atdd/planner/validators/test_plan_wagons.py +174 -0
- atdd/planner/validators/test_train_validation.py +514 -0
- atdd/planner/validators/test_wagon_urn_chain.py +648 -0
- atdd/planner/validators/test_wmbt_consistency.py +327 -0
- atdd/planner/validators/test_wmbt_vocabulary.py +632 -0
- atdd/tester/__init__.py +1 -0
- atdd/tester/conventions/artifact.convention.yaml +257 -0
- atdd/tester/conventions/contract.convention.yaml +1009 -0
- atdd/tester/conventions/filename.convention.yaml +555 -0
- atdd/tester/conventions/migration.convention.yaml +509 -0
- atdd/tester/conventions/red.convention.yaml +797 -0
- atdd/tester/conventions/routing.convention.yaml +51 -0
- atdd/tester/conventions/telemetry.convention.yaml +458 -0
- atdd/tester/schemas/a11y.tmpl.json +17 -0
- atdd/tester/schemas/artifact.schema.json +189 -0
- atdd/tester/schemas/contract.schema.json +591 -0
- atdd/tester/schemas/contract.tmpl.json +95 -0
- atdd/tester/schemas/db.tmpl.json +20 -0
- atdd/tester/schemas/e2e.tmpl.json +17 -0
- atdd/tester/schemas/edge_function.tmpl.json +17 -0
- atdd/tester/schemas/event.tmpl.json +17 -0
- atdd/tester/schemas/http.tmpl.json +19 -0
- atdd/tester/schemas/job.tmpl.json +18 -0
- atdd/tester/schemas/load.tmpl.json +21 -0
- atdd/tester/schemas/metric.tmpl.json +19 -0
- atdd/tester/schemas/pack.schema.json +139 -0
- atdd/tester/schemas/realtime.tmpl.json +20 -0
- atdd/tester/schemas/rls.tmpl.json +18 -0
- atdd/tester/schemas/script.tmpl.json +16 -0
- atdd/tester/schemas/sec.tmpl.json +18 -0
- atdd/tester/schemas/storage.tmpl.json +18 -0
- atdd/tester/schemas/telemetry.schema.json +128 -0
- atdd/tester/schemas/telemetry_tracking_manifest.schema.json +143 -0
- atdd/tester/schemas/test_filename.schema.json +194 -0
- atdd/tester/schemas/test_intent.schema.json +179 -0
- atdd/tester/schemas/unit.tmpl.json +18 -0
- atdd/tester/schemas/visual.tmpl.json +18 -0
- atdd/tester/schemas/ws.tmpl.json +17 -0
- atdd/tester/utils/__init__.py +0 -0
- atdd/tester/utils/filename.py +300 -0
- atdd/tester/validators/__init__.py +0 -0
- atdd/tester/validators/cleanup_duplicate_headers.py +116 -0
- atdd/tester/validators/cleanup_duplicate_headers_v2.py +135 -0
- atdd/tester/validators/conftest.py +5 -0
- atdd/tester/validators/coverage_gap_report.py +321 -0
- atdd/tester/validators/fix_dual_ac_references.py +179 -0
- atdd/tester/validators/remove_duplicate_lines.py +93 -0
- atdd/tester/validators/test_acceptance_urn_filename_mapping.py +359 -0
- atdd/tester/validators/test_acceptance_urn_separator.py +166 -0
- atdd/tester/validators/test_artifact_naming_category.py +307 -0
- atdd/tester/validators/test_contract_schema_compliance.py +706 -0
- atdd/tester/validators/test_contracts_structure.py +200 -0
- atdd/tester/validators/test_coverage_adequacy.py +797 -0
- atdd/tester/validators/test_dual_ac_reference.py +225 -0
- atdd/tester/validators/test_fixture_validity.py +372 -0
- atdd/tester/validators/test_isolation.py +487 -0
- atdd/tester/validators/test_migration_coverage.py +204 -0
- atdd/tester/validators/test_migration_criteria.py +276 -0
- atdd/tester/validators/test_migration_generation.py +116 -0
- atdd/tester/validators/test_python_test_naming.py +410 -0
- atdd/tester/validators/test_red_layer_validation.py +95 -0
- atdd/tester/validators/test_red_python_layer_structure.py +87 -0
- atdd/tester/validators/test_red_supabase_layer_structure.py +90 -0
- atdd/tester/validators/test_telemetry_structure.py +634 -0
- atdd/tester/validators/test_typescript_test_naming.py +301 -0
- atdd/tester/validators/test_typescript_test_structure.py +84 -0
- atdd-0.2.1.dist-info/METADATA +221 -0
- atdd-0.2.1.dist-info/RECORD +184 -0
- atdd-0.2.1.dist-info/WHEEL +5 -0
- atdd-0.2.1.dist-info/entry_points.txt +2 -0
- atdd-0.2.1.dist-info/licenses/LICENSE +674 -0
- atdd-0.2.1.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
"""
|
|
2
|
+
SPEC-CODER-CONV-0013, SPEC-CODER-CONV-0014: Component URN naming migration tests for coder
|
|
3
|
+
|
|
4
|
+
Tests for migrating component URN pattern in coder conventions.
|
|
5
|
+
"""
|
|
6
|
+
import pytest
|
|
7
|
+
import yaml
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
|
|
10
|
+
REPO_ROOT = Path(__file__).resolve().parents[5]
|
|
11
|
+
COMPONENT_NAMING_PATH = REPO_ROOT / "atdd/coder/conventions/component-naming.convention.yaml"
|
|
12
|
+
GREEN_CONV_PATH = REPO_ROOT / "atdd/coder/conventions/green.convention.yaml"
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def test_coder_convention_uses_colon_hierarchy():
|
|
16
|
+
"""
|
|
17
|
+
SPEC-CODER-CONV-0013: Update coder component-naming.convention.yaml with new URN pattern
|
|
18
|
+
|
|
19
|
+
Verify that the URN pattern uses colons for hierarchy.
|
|
20
|
+
"""
|
|
21
|
+
with open(COMPONENT_NAMING_PATH) as f:
|
|
22
|
+
convention = yaml.safe_load(f)
|
|
23
|
+
|
|
24
|
+
pattern = convention.get("urn_naming", {}).get("pattern", "")
|
|
25
|
+
|
|
26
|
+
# Should use colon after wagon and feature
|
|
27
|
+
assert "component:{wagon}:{feature}" in pattern, \
|
|
28
|
+
f"Pattern should use colons for hierarchy: {pattern}"
|
|
29
|
+
|
|
30
|
+
# Should NOT use dot between wagon and feature
|
|
31
|
+
assert "component:{wagon}.{feature}" not in pattern, \
|
|
32
|
+
f"Pattern should not use dots between wagon and feature: {pattern}"
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def test_coder_artifact_examples_use_new_format():
|
|
36
|
+
"""
|
|
37
|
+
SPEC-CODER-CONV-0013: All artifact derivation examples updated with new format
|
|
38
|
+
|
|
39
|
+
Verify all artifact_derivation examples use colon-based hierarchy.
|
|
40
|
+
"""
|
|
41
|
+
with open(COMPONENT_NAMING_PATH) as f:
|
|
42
|
+
convention = yaml.safe_load(f)
|
|
43
|
+
|
|
44
|
+
artifact_derivation = convention.get("artifact_derivation", {})
|
|
45
|
+
# Navigate to the nested structure: rules > capability_suffix > by_artifact_type
|
|
46
|
+
rules = artifact_derivation.get("rules", {})
|
|
47
|
+
capability_suffix = rules.get("capability_suffix", {})
|
|
48
|
+
by_artifact_type = capability_suffix.get("by_artifact_type", {})
|
|
49
|
+
|
|
50
|
+
# Collect all example URNs from all artifact types
|
|
51
|
+
all_urns = []
|
|
52
|
+
for artifact_type, config in by_artifact_type.items():
|
|
53
|
+
examples = config.get("examples", [])
|
|
54
|
+
for example in examples:
|
|
55
|
+
urn = example.get("urn", "")
|
|
56
|
+
if urn:
|
|
57
|
+
all_urns.append((artifact_type, urn))
|
|
58
|
+
|
|
59
|
+
assert len(all_urns) > 0, "Should have artifact derivation examples"
|
|
60
|
+
|
|
61
|
+
for artifact_type, urn in all_urns:
|
|
62
|
+
# Check for colon format (look for pattern: component:word:word)
|
|
63
|
+
# After "component:" there should be wagon, then colon, then feature
|
|
64
|
+
parts = urn.split(":")
|
|
65
|
+
assert len(parts) >= 3, \
|
|
66
|
+
f"URN should have at least 3 colon-separated parts: {urn}"
|
|
67
|
+
|
|
68
|
+
# First part should be "component"
|
|
69
|
+
assert parts[0] == "component", f"URN should start with 'component:': {urn}"
|
|
70
|
+
|
|
71
|
+
# Should have wagon and feature separated by colon
|
|
72
|
+
# Format: component:wagon:feature.rest
|
|
73
|
+
wagon_feature = f"{parts[1]}:{parts[2].split('.')[0]}"
|
|
74
|
+
assert ":" in wagon_feature, \
|
|
75
|
+
f"{artifact_type} URN should use colon between wagon and feature: {urn}"
|
|
76
|
+
|
|
77
|
+
# Should NOT contain old format component:wagon.feature
|
|
78
|
+
assert not urn.startswith(f"component:{parts[1]}."), \
|
|
79
|
+
f"{artifact_type} URN should not use old dot format after wagon: {urn}"
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def test_coder_complete_example_updated():
|
|
83
|
+
"""
|
|
84
|
+
SPEC-CODER-CONV-0013: Complete example section updated with all four component URNs
|
|
85
|
+
|
|
86
|
+
Verify the complete_example section has all URNs in new format.
|
|
87
|
+
"""
|
|
88
|
+
with open(COMPONENT_NAMING_PATH) as f:
|
|
89
|
+
convention = yaml.safe_load(f)
|
|
90
|
+
|
|
91
|
+
complete_example = convention.get("complete_example", {})
|
|
92
|
+
components = complete_example.get("components", [])
|
|
93
|
+
|
|
94
|
+
assert len(components) >= 4, "Should have at least 4 component examples"
|
|
95
|
+
|
|
96
|
+
for component in components:
|
|
97
|
+
urn = component.get("urn", "")
|
|
98
|
+
name = component.get("name", "")
|
|
99
|
+
|
|
100
|
+
# All URNs should use colon format
|
|
101
|
+
parts = urn.split(":")
|
|
102
|
+
assert len(parts) >= 3, \
|
|
103
|
+
f"{name} URN should have at least 3 colon-separated parts: {urn}"
|
|
104
|
+
|
|
105
|
+
# Should be component:wagon:feature.component
|
|
106
|
+
wagon = parts[1]
|
|
107
|
+
feature_and_rest = parts[2]
|
|
108
|
+
|
|
109
|
+
# Verify no dot immediately after component: (old format)
|
|
110
|
+
assert not urn.startswith(f"component:{wagon}."), \
|
|
111
|
+
f"{name} URN should not use old format component:wagon.feature: {urn}"
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def test_green_convention_examples_updated():
|
|
115
|
+
"""
|
|
116
|
+
SPEC-CODER-CONV-0014: Update coder green.convention.yaml component URN examples
|
|
117
|
+
|
|
118
|
+
Verify green convention examples use new colon hierarchy.
|
|
119
|
+
"""
|
|
120
|
+
with open(GREEN_CONV_PATH) as f:
|
|
121
|
+
convention = yaml.safe_load(f)
|
|
122
|
+
|
|
123
|
+
# urn_naming is nested under green_phase
|
|
124
|
+
green_phase = convention.get("green_phase", {})
|
|
125
|
+
examples = green_phase.get("urn_naming", {}).get("examples", [])
|
|
126
|
+
|
|
127
|
+
assert len(examples) > 0, "Should have example URNs in green convention"
|
|
128
|
+
|
|
129
|
+
for example in examples:
|
|
130
|
+
urn = example.get("urn", "")
|
|
131
|
+
wagon = example.get("wagon", "")
|
|
132
|
+
feature = example.get("feature", "")
|
|
133
|
+
|
|
134
|
+
# Should use new format: component:wagon:feature
|
|
135
|
+
expected_prefix = f"component:{wagon}:{feature}"
|
|
136
|
+
assert urn.startswith(expected_prefix), \
|
|
137
|
+
f"URN should use colon hierarchy:\n URN: {urn}\n Expected prefix: {expected_prefix}"
|
|
138
|
+
|
|
139
|
+
# Should NOT use old format: component:wagon.feature
|
|
140
|
+
old_format = f"component:{wagon}.{feature}"
|
|
141
|
+
assert not urn.startswith(old_format), \
|
|
142
|
+
f"URN should not use old dot format: {urn}"
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
def test_green_convention_pattern_correct():
|
|
146
|
+
"""
|
|
147
|
+
SPEC-CODER-CONV-0014: Green convention pattern field updated correctly
|
|
148
|
+
|
|
149
|
+
Verify the pattern field in green.convention.yaml uses new format.
|
|
150
|
+
"""
|
|
151
|
+
with open(GREEN_CONV_PATH) as f:
|
|
152
|
+
convention = yaml.safe_load(f)
|
|
153
|
+
|
|
154
|
+
# urn_naming is nested under green_phase
|
|
155
|
+
green_phase = convention.get("green_phase", {})
|
|
156
|
+
pattern = green_phase.get("urn_naming", {}).get("pattern", "")
|
|
157
|
+
description = green_phase.get("urn_naming", {}).get("description", "")
|
|
158
|
+
|
|
159
|
+
# Pattern should use colons for hierarchy
|
|
160
|
+
assert "component:{wagon}:{feature}" in pattern, \
|
|
161
|
+
f"Pattern should use colons: {pattern}"
|
|
162
|
+
|
|
163
|
+
# Description should mention hierarchy via colons
|
|
164
|
+
assert "hierarchy" in description.lower() or "colon" in description.lower(), \
|
|
165
|
+
f"Description should mention hierarchy or colons: {description}"
|
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Tests for thinness recipe.
|
|
3
|
+
|
|
4
|
+
Tests SPEC-CODER-UTL-0153 to 042, 053, 056, 057, 058
|
|
5
|
+
ATDD: These tests define expected behavior of thinness recipe BEFORE implementation.
|
|
6
|
+
"""
|
|
7
|
+
import pytest
|
|
8
|
+
import yaml
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
from typing import Dict, Any
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
# Recipe loader utilities
|
|
14
|
+
def load_recipe(recipe_name: str) -> Dict[str, Any]:
|
|
15
|
+
"""
|
|
16
|
+
Load recipe YAML file.
|
|
17
|
+
|
|
18
|
+
SPEC-CODER-UTL-0153: Load thinness recipe
|
|
19
|
+
"""
|
|
20
|
+
recipe_path = Path(__file__).resolve().parents[1] / f"{recipe_name}.recipe.yaml"
|
|
21
|
+
|
|
22
|
+
if not recipe_path.exists():
|
|
23
|
+
raise FileNotFoundError(f"Recipe not found: {recipe_path}")
|
|
24
|
+
|
|
25
|
+
with open(recipe_path, 'r') as f:
|
|
26
|
+
return yaml.safe_load(f)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def check_recipe_applies(recipe_name: str, smells: Dict[str, Any]) -> bool:
|
|
30
|
+
"""
|
|
31
|
+
Check if recipe applies based on smell detection results.
|
|
32
|
+
|
|
33
|
+
SPEC-CODER-UTL-0154: Detect when thinness recipe applies
|
|
34
|
+
"""
|
|
35
|
+
recipe = load_recipe(recipe_name)
|
|
36
|
+
|
|
37
|
+
if recipe_name == "thinness":
|
|
38
|
+
# thinness applies when thinness check fails
|
|
39
|
+
return smells.get("thinness", {}).get("passed", True) is False
|
|
40
|
+
|
|
41
|
+
return False
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def execute_recipe_step(recipe_name: str, step: int, context: Dict[str, Any]) -> Dict[str, Any]:
|
|
45
|
+
"""
|
|
46
|
+
Execute a recipe step.
|
|
47
|
+
|
|
48
|
+
SPEC-CODER-UTL-0155, 041, 042: Execute steps 1, 2, 3
|
|
49
|
+
"""
|
|
50
|
+
recipe = load_recipe(recipe_name)
|
|
51
|
+
steps = recipe.get("steps", [])
|
|
52
|
+
|
|
53
|
+
if step < 1 or step > len(steps):
|
|
54
|
+
return {"success": False, "error": f"Invalid step: {step}"}
|
|
55
|
+
|
|
56
|
+
step_def = steps[step - 1]
|
|
57
|
+
|
|
58
|
+
# Return step guidance
|
|
59
|
+
return {
|
|
60
|
+
"success": True,
|
|
61
|
+
"step": step,
|
|
62
|
+
"what": step_def.get("what", ""),
|
|
63
|
+
"where": step_def.get("where", ""),
|
|
64
|
+
"template": step_def.get("template", ""),
|
|
65
|
+
"next_action": "verify_tests" if step == 1 else ("continue" if step < len(steps) else "verify_final")
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def select_recipe(smells: Dict[str, Any]) -> Dict[str, Any]:
|
|
70
|
+
"""
|
|
71
|
+
Select recipe based on smell detection.
|
|
72
|
+
|
|
73
|
+
SPEC-CODER-UTL-0168: Map handler smell to thinness recipe
|
|
74
|
+
"""
|
|
75
|
+
# Priority 1: Handler smell → thinness
|
|
76
|
+
if smells.get("thinness", {}).get("passed", True) is False:
|
|
77
|
+
return {"recipe": "thinness", "priority": 1}
|
|
78
|
+
|
|
79
|
+
# Priority 2: Complexity smell → specification
|
|
80
|
+
if smells.get("complexity", {}).get("passed", True) is False:
|
|
81
|
+
return {"recipe": "specification", "priority": 2}
|
|
82
|
+
|
|
83
|
+
# Priority 3: Missing adapter → adapter
|
|
84
|
+
if smells.get("missing_adapter", False):
|
|
85
|
+
return {"recipe": "adapter", "priority": 3}
|
|
86
|
+
|
|
87
|
+
return {"recipe": None, "priority": 0}
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def verify_recipe_step(step_result: Dict[str, Any], test_status: str) -> bool:
|
|
91
|
+
"""
|
|
92
|
+
Verify recipe step maintains GREEN tests.
|
|
93
|
+
|
|
94
|
+
SPEC-CODER-UTL-0171: Verify recipe step maintains GREEN tests
|
|
95
|
+
SPEC-CODER-UTL-0173: Rollback on step failure
|
|
96
|
+
"""
|
|
97
|
+
if test_status == "GREEN":
|
|
98
|
+
return True
|
|
99
|
+
|
|
100
|
+
# RED tests trigger rollback
|
|
101
|
+
if test_status == "RED":
|
|
102
|
+
# This would call rollback_refactor_step() in real usage
|
|
103
|
+
return False
|
|
104
|
+
|
|
105
|
+
return False
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def verify_recipe_final(recipe_name: str, results: Dict[str, Any]) -> Dict[str, Any]:
|
|
109
|
+
"""
|
|
110
|
+
Verify final state after all recipe steps.
|
|
111
|
+
|
|
112
|
+
SPEC-CODER-UTL-0172: Verify final state after all recipe steps
|
|
113
|
+
"""
|
|
114
|
+
verification = {"success": True, "checks": []}
|
|
115
|
+
|
|
116
|
+
if recipe_name == "thinness":
|
|
117
|
+
# Final verification: thinness.check() should pass
|
|
118
|
+
verification["checks"].append({
|
|
119
|
+
"check": "thinness.check()",
|
|
120
|
+
"expected": "passed=true"
|
|
121
|
+
})
|
|
122
|
+
|
|
123
|
+
return verification
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
# TESTS
|
|
127
|
+
|
|
128
|
+
class TestThinHandlerRecipeLoading:
|
|
129
|
+
"""Test SPEC-CODER-UTL-0153: Load thinness recipe"""
|
|
130
|
+
|
|
131
|
+
def test_load_recipe(self):
|
|
132
|
+
"""Should load thinness recipe YAML"""
|
|
133
|
+
recipe = load_recipe("thinness")
|
|
134
|
+
|
|
135
|
+
assert recipe is not None
|
|
136
|
+
assert recipe["recipe"] == "thinness"
|
|
137
|
+
assert recipe["pattern"] == "Thin Handler + Use Case"
|
|
138
|
+
assert "steps" in recipe
|
|
139
|
+
assert len(recipe["steps"]) == 3
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
class TestThinHandlerRecipeApplies:
|
|
143
|
+
"""Test SPEC-CODER-UTL-0154: Detect when thinness recipe applies"""
|
|
144
|
+
|
|
145
|
+
def test_recipe_applies(self):
|
|
146
|
+
"""Should return true when thinness check fails"""
|
|
147
|
+
smells = {
|
|
148
|
+
"thinness": {
|
|
149
|
+
"passed": False,
|
|
150
|
+
"smells": [
|
|
151
|
+
{"line": 10, "reason": "Business logic in handler"}
|
|
152
|
+
]
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
result = check_recipe_applies("thinness", smells)
|
|
157
|
+
|
|
158
|
+
assert result is True
|
|
159
|
+
|
|
160
|
+
def test_recipe_not_applies(self):
|
|
161
|
+
"""Should return false when thinness check passes"""
|
|
162
|
+
smells = {
|
|
163
|
+
"thinness": {
|
|
164
|
+
"passed": True,
|
|
165
|
+
"smells": []
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
result = check_recipe_applies("thinness", smells)
|
|
170
|
+
|
|
171
|
+
assert result is False
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
class TestThinHandlerRecipeSteps:
|
|
175
|
+
"""Test SPEC-CODER-UTL-0155, 041, 042: Execute recipe steps"""
|
|
176
|
+
|
|
177
|
+
def test_execute_step_1(self):
|
|
178
|
+
"""Should execute step 1: create use case"""
|
|
179
|
+
context = {"file_path": "presentation/handlers/order.py"}
|
|
180
|
+
|
|
181
|
+
result = execute_recipe_step("thinness", 1, context)
|
|
182
|
+
|
|
183
|
+
assert result["success"] is True
|
|
184
|
+
assert "use case" in result["what"].lower()
|
|
185
|
+
assert "application/" in result["where"]
|
|
186
|
+
assert result["next_action"] == "verify_tests"
|
|
187
|
+
|
|
188
|
+
def test_execute_step_2(self):
|
|
189
|
+
"""Should execute step 2: define port"""
|
|
190
|
+
context = {"usecase_created": True}
|
|
191
|
+
|
|
192
|
+
result = execute_recipe_step("thinness", 2, context)
|
|
193
|
+
|
|
194
|
+
assert result["success"] is True
|
|
195
|
+
assert "port" in result["what"].lower()
|
|
196
|
+
assert "application/ports/" in result["where"]
|
|
197
|
+
assert result["next_action"] == "continue"
|
|
198
|
+
|
|
199
|
+
def test_execute_step_3(self):
|
|
200
|
+
"""Should execute step 3: thin the handler"""
|
|
201
|
+
context = {"usecase_created": True, "port_defined": True}
|
|
202
|
+
|
|
203
|
+
result = execute_recipe_step("thinness", 3, context)
|
|
204
|
+
|
|
205
|
+
assert result["success"] is True
|
|
206
|
+
assert "thin" in result["what"].lower() or "handler" in result["what"].lower()
|
|
207
|
+
assert result["next_action"] == "verify_final"
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
class TestThinHandlerRecipeSelection:
|
|
211
|
+
"""Test SPEC-CODER-UTL-0168: Map handler smell to thinness recipe"""
|
|
212
|
+
|
|
213
|
+
def test_select_from_smell(self):
|
|
214
|
+
"""Should select thinness recipe when handler smell detected"""
|
|
215
|
+
smells = {
|
|
216
|
+
"thinness": {
|
|
217
|
+
"passed": False,
|
|
218
|
+
"smells": [{"line": 10, "reason": "Business logic"}]
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
result = select_recipe(smells)
|
|
223
|
+
|
|
224
|
+
assert result["recipe"] == "thinness"
|
|
225
|
+
assert result["priority"] == 1
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
class TestThinHandlerRecipeVerification:
|
|
229
|
+
"""Test SPEC-CODER-UTL-0171, 057, 058: Recipe verification"""
|
|
230
|
+
|
|
231
|
+
def test_verify_step_green(self):
|
|
232
|
+
"""Should return true when tests are GREEN"""
|
|
233
|
+
step_result = {"success": True, "step": 1}
|
|
234
|
+
|
|
235
|
+
result = verify_recipe_step(step_result, "GREEN")
|
|
236
|
+
|
|
237
|
+
assert result is True
|
|
238
|
+
|
|
239
|
+
def test_rollback_on_failure(self):
|
|
240
|
+
"""Should return false and trigger rollback when tests are RED"""
|
|
241
|
+
step_result = {"success": True, "step": 1}
|
|
242
|
+
|
|
243
|
+
result = verify_recipe_step(step_result, "RED")
|
|
244
|
+
|
|
245
|
+
assert result is False
|
|
246
|
+
|
|
247
|
+
def test_verify_final(self):
|
|
248
|
+
"""Should verify final state with thinness check"""
|
|
249
|
+
results = {"all_steps_completed": True}
|
|
250
|
+
|
|
251
|
+
verification = verify_recipe_final("thinness", results)
|
|
252
|
+
|
|
253
|
+
assert verification["success"] is True
|
|
254
|
+
assert len(verification["checks"]) > 0
|
|
255
|
+
assert any("thinness" in check["check"] for check in verification["checks"])
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
class TestThinHandlerRecipeIntegration:
|
|
259
|
+
"""Integration tests for full recipe workflow"""
|
|
260
|
+
|
|
261
|
+
def test_full_recipe_workflow(self):
|
|
262
|
+
"""Should execute full recipe from detection to verification"""
|
|
263
|
+
# 1. Detect smell
|
|
264
|
+
smells = {"thinness": {"passed": False, "smells": [{"line": 10, "reason": "Business logic"}]}}
|
|
265
|
+
|
|
266
|
+
# 2. Select recipe
|
|
267
|
+
selected = select_recipe(smells)
|
|
268
|
+
assert selected["recipe"] == "thinness"
|
|
269
|
+
|
|
270
|
+
# 3. Load recipe
|
|
271
|
+
recipe = load_recipe("thinness")
|
|
272
|
+
assert recipe is not None
|
|
273
|
+
|
|
274
|
+
# 4. Execute steps
|
|
275
|
+
context = {}
|
|
276
|
+
for step in range(1, 4):
|
|
277
|
+
result = execute_recipe_step("thinness", step, context)
|
|
278
|
+
assert result["success"] is True
|
|
279
|
+
|
|
280
|
+
# Verify step
|
|
281
|
+
verified = verify_recipe_step(result, "GREEN")
|
|
282
|
+
assert verified is True
|
|
283
|
+
|
|
284
|
+
# 5. Final verification
|
|
285
|
+
verification = verify_recipe_final("thinness", {"all_steps": True})
|
|
286
|
+
assert verification["success"] is True
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
recipe: thinness
|
|
2
|
+
pattern: "Thin Handler + Use Case"
|
|
3
|
+
category: presentation_application
|
|
4
|
+
source: "Clean Architecture (Robert C. Martin)"
|
|
5
|
+
utils: "thinness.py"
|
|
6
|
+
|
|
7
|
+
applies_when:
|
|
8
|
+
- "Handler contains business logic"
|
|
9
|
+
|
|
10
|
+
steps:
|
|
11
|
+
- step: 1
|
|
12
|
+
what: "Create use case in application layer"
|
|
13
|
+
where: "application/{feature}_usecase.py"
|
|
14
|
+
template: |
|
|
15
|
+
class {Action}UseCase:
|
|
16
|
+
"""Execute {action} business logic."""
|
|
17
|
+
|
|
18
|
+
def __init__(self, repo: {Resource}Repository):
|
|
19
|
+
self.repo = repo
|
|
20
|
+
|
|
21
|
+
def execute(self, input: {Action}Input) -> {Resource}:
|
|
22
|
+
# Business logic here
|
|
23
|
+
result = {Resource}.create(input)
|
|
24
|
+
self.repo.save(result)
|
|
25
|
+
return result
|
|
26
|
+
|
|
27
|
+
verify:
|
|
28
|
+
- run: pytest -xvs tests/application/test_{feature}_usecase.py
|
|
29
|
+
expect: GREEN
|
|
30
|
+
|
|
31
|
+
- step: 2
|
|
32
|
+
what: "Define port interface (if not exists)"
|
|
33
|
+
where: "application/ports/{resource}_repository.py"
|
|
34
|
+
template: |
|
|
35
|
+
class {Resource}Repository:
|
|
36
|
+
"""Port interface for {resource} persistence."""
|
|
37
|
+
|
|
38
|
+
def save(self, entity: {Resource}) -> None:
|
|
39
|
+
raise NotImplementedError
|
|
40
|
+
|
|
41
|
+
def find_by_id(self, id: {Resource}Id) -> {Resource}:
|
|
42
|
+
raise NotImplementedError
|
|
43
|
+
|
|
44
|
+
- step: 3
|
|
45
|
+
what: "Thin the handler (parse → call → format)"
|
|
46
|
+
where: "presentation/handlers/{feature}_handler.py"
|
|
47
|
+
before: |
|
|
48
|
+
# Fat handler with business logic
|
|
49
|
+
async def handle_create_order(req: Request) -> Response:
|
|
50
|
+
order_data = req.body
|
|
51
|
+
# Validation logic
|
|
52
|
+
if not order_data.get('customer_id'):
|
|
53
|
+
return Response(400, "Missing customer")
|
|
54
|
+
# Business logic (BAD - should be in use case)
|
|
55
|
+
total = sum(item['price'] * item['qty'] for item in order_data['items'])
|
|
56
|
+
discount = total * 0.1 if total > 100 else 0
|
|
57
|
+
final_total = total - discount
|
|
58
|
+
# Persistence (BAD - should be behind port)
|
|
59
|
+
db.execute("INSERT INTO orders ...")
|
|
60
|
+
return Response(201, {"id": order_id})
|
|
61
|
+
|
|
62
|
+
after: |
|
|
63
|
+
# Thin handler
|
|
64
|
+
async def handle_create_order(req: Request) -> Response:
|
|
65
|
+
# Parse
|
|
66
|
+
input = CreateOrderDTO.parse(req.body)
|
|
67
|
+
|
|
68
|
+
# Call use case
|
|
69
|
+
order = create_order_usecase.execute(input)
|
|
70
|
+
|
|
71
|
+
# Format
|
|
72
|
+
return Response(201, CreateOrderDTO.from_domain(order))
|
|
73
|
+
|
|
74
|
+
verify:
|
|
75
|
+
- run: pytest -xvs tests/presentation/test_{feature}_handler.py
|
|
76
|
+
expect: GREEN
|
|
77
|
+
|
|
78
|
+
final_verify:
|
|
79
|
+
- "Handler has NO business logic (only parse/call/format)"
|
|
80
|
+
- "Use case orchestrates domain + ports"
|
|
81
|
+
- "All tests GREEN"
|
|
82
|
+
- "Coverage maintained or improved"
|