atdd 0.4.7__py3-none-any.whl → 0.6.0__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.
@@ -0,0 +1,361 @@
1
+ """
2
+ Coder hierarchy coverage validation.
3
+
4
+ ATDD Hierarchy Coverage Spec v0.1 - Section 4: Coder Coverage Rules
5
+
6
+ Validates:
7
+ - Feature <-> Implementation (COVERAGE-CODE-4.1)
8
+ - Implementation <-> Tests (COVERAGE-CODE-4.2)
9
+
10
+ Architecture:
11
+ - Uses shared fixtures from atdd.coach.validators.shared_fixtures
12
+ - Phased rollout via atdd.coach.utils.coverage_phase
13
+ - Exception handling via .atdd/config.yaml coverage.exceptions
14
+ """
15
+
16
+ import pytest
17
+ from pathlib import Path
18
+ from typing import Dict, List, Set, Tuple, Any
19
+
20
+ from atdd.coach.utils.repo import find_repo_root
21
+ from atdd.coach.utils.coverage_phase import (
22
+ CoveragePhase,
23
+ should_enforce,
24
+ emit_coverage_warning
25
+ )
26
+
27
+
28
+ # Path constants
29
+ REPO_ROOT = find_repo_root()
30
+ PLAN_DIR = REPO_ROOT / "plan"
31
+ PYTHON_DIR = REPO_ROOT / "python"
32
+ SUPABASE_DIR = REPO_ROOT / "supabase"
33
+ WEB_DIR = REPO_ROOT / "web"
34
+
35
+
36
+ # ============================================================================
37
+ # HELPER FUNCTIONS
38
+ # ============================================================================
39
+
40
+
41
+ def find_python_implementations(wagon_slug: str, feature_slug: str) -> List[Path]:
42
+ """
43
+ Find Python implementation files for a feature.
44
+
45
+ Searches for:
46
+ - python/{wagon}/use_case_{feature}.py
47
+ - python/{wagon}/service_{feature}.py
48
+ - python/{wagon}/{feature}_handler.py
49
+ - python/{wagon}/{feature}.py
50
+ """
51
+ implementations = []
52
+
53
+ # Convert slugs to filesystem format
54
+ wagon_dir = wagon_slug.replace("-", "_")
55
+ feature_file = feature_slug.replace("-", "_")
56
+
57
+ wagon_path = PYTHON_DIR / wagon_dir
58
+ if not wagon_path.exists():
59
+ return implementations
60
+
61
+ # Check various patterns
62
+ patterns = [
63
+ f"use_case_{feature_file}.py",
64
+ f"service_{feature_file}.py",
65
+ f"{feature_file}_handler.py",
66
+ f"{feature_file}.py",
67
+ ]
68
+
69
+ for pattern in patterns:
70
+ impl_path = wagon_path / pattern
71
+ if impl_path.exists():
72
+ implementations.append(impl_path)
73
+
74
+ # Also search subdirectories
75
+ for subdir in wagon_path.iterdir():
76
+ if subdir.is_dir() and not subdir.name.startswith("_"):
77
+ for pattern in patterns:
78
+ impl_path = subdir / pattern
79
+ if impl_path.exists():
80
+ implementations.append(impl_path)
81
+
82
+ return implementations
83
+
84
+
85
+ def find_typescript_implementations(wagon_slug: str, feature_slug: str) -> List[Path]:
86
+ """
87
+ Find TypeScript implementation files for a feature.
88
+
89
+ Searches for:
90
+ - supabase/functions/{wagon}/{feature}/index.ts
91
+ - supabase/functions/{wagon}/{feature}/handler.ts
92
+ - supabase/functions/{wagon}/{feature}.ts
93
+ """
94
+ implementations = []
95
+
96
+ functions_dir = SUPABASE_DIR / "functions"
97
+ if not functions_dir.exists():
98
+ return implementations
99
+
100
+ # Check various structures
101
+ # Pattern 1: supabase/functions/{wagon}/{feature}/
102
+ feature_dir = functions_dir / wagon_slug / feature_slug
103
+ if feature_dir.exists():
104
+ for pattern in ["index.ts", "handler.ts"]:
105
+ impl_path = feature_dir / pattern
106
+ if impl_path.exists():
107
+ implementations.append(impl_path)
108
+
109
+ # Pattern 2: supabase/functions/{wagon}/{feature}.ts
110
+ wagon_dir = functions_dir / wagon_slug
111
+ if wagon_dir.exists():
112
+ feature_file = wagon_dir / f"{feature_slug}.ts"
113
+ if feature_file.exists():
114
+ implementations.append(feature_file)
115
+
116
+ return implementations
117
+
118
+
119
+ def find_web_implementations(wagon_slug: str, feature_slug: str) -> List[Path]:
120
+ """
121
+ Find web/frontend implementation files for a feature.
122
+
123
+ Searches for:
124
+ - web/src/features/{wagon}/{feature}/
125
+ - web/src/components/{feature}/
126
+ """
127
+ implementations = []
128
+
129
+ # Pattern 1: web/src/features/{wagon}/{feature}/
130
+ features_dir = WEB_DIR / "src" / "features" / wagon_slug / feature_slug
131
+ if features_dir.exists():
132
+ for pattern in ["index.tsx", "index.ts", f"{feature_slug}.tsx"]:
133
+ impl_path = features_dir / pattern
134
+ if impl_path.exists():
135
+ implementations.append(impl_path)
136
+
137
+ # Pattern 2: web/src/components/{feature}/
138
+ components_dir = WEB_DIR / "src" / "components" / feature_slug
139
+ if components_dir.exists():
140
+ implementations.append(components_dir)
141
+
142
+ return implementations
143
+
144
+
145
+ def has_implementation(wagon_slug: str, feature_slug: str) -> bool:
146
+ """
147
+ Check if a feature has any implementation.
148
+ """
149
+ python_impls = find_python_implementations(wagon_slug, feature_slug)
150
+ ts_impls = find_typescript_implementations(wagon_slug, feature_slug)
151
+ web_impls = find_web_implementations(wagon_slug, feature_slug)
152
+
153
+ return len(python_impls) > 0 or len(ts_impls) > 0 or len(web_impls) > 0
154
+
155
+
156
+ def find_tests_for_implementation(impl_path: Path) -> List[Path]:
157
+ """
158
+ Find test files that might test an implementation.
159
+ """
160
+ tests = []
161
+
162
+ if not impl_path.exists():
163
+ return tests
164
+
165
+ # For Python implementations
166
+ if impl_path.suffix == ".py":
167
+ impl_dir = impl_path.parent
168
+ impl_name = impl_path.stem
169
+
170
+ # Look for test_*.py in same directory
171
+ for test_file in impl_dir.glob("test_*.py"):
172
+ if impl_name in test_file.stem:
173
+ tests.append(test_file)
174
+
175
+ # Look for test file with matching name
176
+ test_file = impl_dir / f"test_{impl_name}.py"
177
+ if test_file.exists() and test_file not in tests:
178
+ tests.append(test_file)
179
+
180
+ # For TypeScript implementations
181
+ elif impl_path.suffix == ".ts":
182
+ impl_dir = impl_path.parent
183
+
184
+ # Look for *.test.ts in same directory or test/ subdirectory
185
+ for test_file in impl_dir.glob("*.test.ts"):
186
+ tests.append(test_file)
187
+
188
+ test_dir = impl_dir / "test"
189
+ if test_dir.exists():
190
+ for test_file in test_dir.glob("*.test.ts"):
191
+ tests.append(test_file)
192
+
193
+ return tests
194
+
195
+
196
+ # ============================================================================
197
+ # COVERAGE-CODE-4.1: Feature <-> Implementation Coverage
198
+ # ============================================================================
199
+
200
+
201
+ @pytest.mark.coder
202
+ def test_all_features_have_implementations(feature_files, coverage_exceptions):
203
+ """
204
+ COVERAGE-CODE-4.1: Every feature has implementation code.
205
+
206
+ Given: Feature files in plan/*/features/
207
+ When: Searching for corresponding implementation files
208
+ Then: Every feature has at least one implementation in python/, supabase/, or web/
209
+ """
210
+ allowed_features = set(coverage_exceptions.get("features_without_implementation", []))
211
+ violations = []
212
+
213
+ for path, feature_data in feature_files:
214
+ # Get wagon slug from path
215
+ wagon_dir = path.parent.parent.name # plan/{wagon}/features/{feature}.yaml
216
+ wagon_slug = wagon_dir.replace("_", "-")
217
+
218
+ # Get feature slug
219
+ feature_slug = path.stem.replace("_", "-")
220
+ feature_urn = feature_data.get("urn", f"feature:{wagon_slug}:{feature_slug}")
221
+
222
+ # Skip draft features
223
+ status = feature_data.get("status", "")
224
+ if status == "draft":
225
+ continue
226
+
227
+ # Skip allowed exceptions
228
+ if feature_urn in allowed_features or feature_slug in allowed_features:
229
+ continue
230
+
231
+ # Check for implementations
232
+ if not has_implementation(wagon_slug, feature_slug):
233
+ violations.append(
234
+ f"{feature_urn}: no implementation found in python/, supabase/, or web/"
235
+ )
236
+
237
+ if violations:
238
+ if should_enforce(CoveragePhase.FULL_ENFORCEMENT):
239
+ pytest.fail(
240
+ f"COVERAGE-CODE-4.1: Features without implementations:\n " +
241
+ "\n ".join(violations[:20]) +
242
+ (f"\n ... and {len(violations) - 20} more" if len(violations) > 20 else "") +
243
+ "\n\nImplement feature or add to coverage.exceptions.features_without_implementation"
244
+ )
245
+ else:
246
+ for violation in violations[:10]:
247
+ emit_coverage_warning(
248
+ "COVERAGE-CODE-4.1",
249
+ violation,
250
+ CoveragePhase.FULL_ENFORCEMENT
251
+ )
252
+
253
+
254
+ # ============================================================================
255
+ # COVERAGE-CODE-4.2: Implementation <-> Tests Coverage
256
+ # ============================================================================
257
+
258
+
259
+ @pytest.mark.coder
260
+ def test_all_implementations_have_tests(feature_files):
261
+ """
262
+ COVERAGE-CODE-4.2: Every implementation has at least one test.
263
+
264
+ Given: Feature implementations in python/, supabase/, web/
265
+ When: Searching for corresponding test files
266
+ Then: Every implementation has at least one test file
267
+ """
268
+ violations = []
269
+
270
+ for path, feature_data in feature_files:
271
+ # Get wagon and feature slugs
272
+ wagon_dir = path.parent.parent.name
273
+ wagon_slug = wagon_dir.replace("_", "-")
274
+ feature_slug = path.stem.replace("_", "-")
275
+
276
+ # Skip draft features
277
+ status = feature_data.get("status", "")
278
+ if status == "draft":
279
+ continue
280
+
281
+ # Find all implementations for this feature
282
+ all_impls = (
283
+ find_python_implementations(wagon_slug, feature_slug) +
284
+ find_typescript_implementations(wagon_slug, feature_slug)
285
+ )
286
+
287
+ for impl_path in all_impls:
288
+ tests = find_tests_for_implementation(impl_path)
289
+ if not tests:
290
+ violations.append(
291
+ f"{impl_path.relative_to(REPO_ROOT)}: no tests found"
292
+ )
293
+
294
+ if violations:
295
+ if should_enforce(CoveragePhase.FULL_ENFORCEMENT):
296
+ pytest.fail(
297
+ f"COVERAGE-CODE-4.2: Implementations without tests:\n " +
298
+ "\n ".join(violations[:20]) +
299
+ (f"\n ... and {len(violations) - 20} more" if len(violations) > 20 else "") +
300
+ "\n\nAdd tests for the implementation"
301
+ )
302
+ else:
303
+ for violation in violations[:10]:
304
+ emit_coverage_warning(
305
+ "COVERAGE-CODE-4.2",
306
+ violation,
307
+ CoveragePhase.FULL_ENFORCEMENT
308
+ )
309
+
310
+
311
+ # ============================================================================
312
+ # COVERAGE SUMMARY
313
+ # ============================================================================
314
+
315
+
316
+ @pytest.mark.coder
317
+ def test_coder_coverage_summary(feature_files):
318
+ """
319
+ COVERAGE-CODE-SUMMARY: Report coder coverage statistics.
320
+
321
+ This test always passes but reports coverage metrics for visibility.
322
+ """
323
+ total_features = len(feature_files)
324
+ features_with_impl = 0
325
+ total_implementations = 0
326
+ implementations_with_tests = 0
327
+
328
+ for path, feature_data in feature_files:
329
+ wagon_dir = path.parent.parent.name
330
+ wagon_slug = wagon_dir.replace("_", "-")
331
+ feature_slug = path.stem.replace("_", "-")
332
+
333
+ # Count implementations
334
+ python_impls = find_python_implementations(wagon_slug, feature_slug)
335
+ ts_impls = find_typescript_implementations(wagon_slug, feature_slug)
336
+ web_impls = find_web_implementations(wagon_slug, feature_slug)
337
+
338
+ all_impls = python_impls + ts_impls + web_impls
339
+ if all_impls:
340
+ features_with_impl += 1
341
+ total_implementations += len(all_impls)
342
+
343
+ # Count implementations with tests
344
+ for impl_path in python_impls + ts_impls:
345
+ if find_tests_for_implementation(impl_path):
346
+ implementations_with_tests += 1
347
+
348
+ # Calculate percentages
349
+ feature_impl_pct = (features_with_impl / total_features * 100) if total_features > 0 else 0
350
+ impl_test_pct = (implementations_with_tests / total_implementations * 100) if total_implementations > 0 else 0
351
+
352
+ # Report summary
353
+ summary = (
354
+ f"\n\nCoder Coverage Summary:\n"
355
+ f" Features with implementations: {features_with_impl}/{total_features} ({feature_impl_pct:.1f}%)\n"
356
+ f" Total implementations: {total_implementations}\n"
357
+ f" Implementations with tests: {implementations_with_tests}/{total_implementations} ({impl_test_pct:.1f}%)"
358
+ )
359
+
360
+ # This test always passes - it's informational
361
+ assert True, summary
@@ -0,0 +1,95 @@
1
+ version: "1.0"
2
+ name: "Planner Coverage Convention"
3
+ description: "Section 2 of ATDD Hierarchy Coverage Spec v0.1 - Ensures every planner artifact participates in lifecycle graph"
4
+
5
+ rules:
6
+ - id: "COVERAGE-PLAN-2.1"
7
+ name: "Train - Wagon Coverage"
8
+ description: "Bidirectional coverage between trains and wagons"
9
+ bidirectional:
10
+ - direction: "wagon -> train"
11
+ requirement: "Every wagon must appear in at least one train's participants"
12
+ exceptions: "wagons_not_in_train allow-list or status:draft"
13
+ validator: "test_all_wagons_in_at_least_one_train"
14
+
15
+ - direction: "train -> wagon"
16
+ requirement: "Every wagon: participant must have a wagon manifest"
17
+ validator: "test_all_train_wagon_refs_exist"
18
+
19
+ - id: "COVERAGE-PLAN-2.2"
20
+ name: "Wagon - Feature Coverage"
21
+ description: "Bidirectional coverage between wagons and features"
22
+ bidirectional:
23
+ - direction: "feature -> wagon"
24
+ requirement: "Every feature file must be referenced by its wagon manifest"
25
+ exceptions: "features_orphaned allow-list"
26
+ validator: "test_all_features_in_wagon_manifest"
27
+
28
+ - direction: "wagon -> feature"
29
+ requirement: "Every features[] entry must have corresponding YAML file"
30
+ validator: "test_all_wagon_feature_refs_exist"
31
+
32
+ - id: "COVERAGE-PLAN-2.3"
33
+ name: "WMBT - Feature Coverage"
34
+ description: "Every WMBT must be referenced by at least one feature"
35
+ bidirectional:
36
+ - direction: "wmbt -> feature"
37
+ requirement: "Every WMBT file must appear in at least one feature's wmbts list"
38
+ validator: "test_all_wmbts_in_at_least_one_feature"
39
+
40
+ - id: "COVERAGE-PLAN-2.4"
41
+ name: "WMBT - Acceptance Coverage"
42
+ description: "Every WMBT must have at least one acceptance criterion"
43
+ requirement: "Every WMBT must have at least one acceptance (except status:draft)"
44
+ exceptions: "wmbts_without_acceptance allow-list"
45
+ validator: "test_all_wmbts_have_acceptances"
46
+
47
+ coverage_graph:
48
+ description: "The planner coverage graph ensures traceability from trains down to acceptances"
49
+ levels:
50
+ - name: "Train"
51
+ contains: "Wagons (via participants)"
52
+ validators: ["COVERAGE-PLAN-2.1"]
53
+
54
+ - name: "Wagon"
55
+ contains: "Features (via features[])"
56
+ validators: ["COVERAGE-PLAN-2.1", "COVERAGE-PLAN-2.2"]
57
+
58
+ - name: "Feature"
59
+ contains: "WMBTs (via wmbts[])"
60
+ validators: ["COVERAGE-PLAN-2.2", "COVERAGE-PLAN-2.3"]
61
+
62
+ - name: "WMBT"
63
+ contains: "Acceptances (via acceptances[])"
64
+ validators: ["COVERAGE-PLAN-2.3", "COVERAGE-PLAN-2.4"]
65
+
66
+ - name: "Acceptance"
67
+ contains: "Test criteria"
68
+ validators: ["COVERAGE-PLAN-2.4"]
69
+
70
+ exception_handling:
71
+ description: "How exceptions are handled in coverage validation"
72
+ allow_lists:
73
+ wagons_not_in_train:
74
+ description: "Wagon slugs that are allowed to exist without train coverage"
75
+ use_case: "Utility wagons, shared infrastructure, or wagons under development"
76
+ config_path: "coverage.exceptions.wagons_not_in_train"
77
+
78
+ features_orphaned:
79
+ description: "Feature URNs that are allowed without wagon manifest reference"
80
+ use_case: "Deprecated features being phased out, or experimental features"
81
+ config_path: "coverage.exceptions.features_orphaned"
82
+
83
+ wmbts_without_acceptance:
84
+ description: "WMBT URNs that are allowed without acceptance criteria"
85
+ use_case: "Draft WMBTs or placeholder definitions"
86
+ config_path: "coverage.exceptions.wmbts_without_acceptance"
87
+
88
+ status_exemptions:
89
+ - status: "draft"
90
+ exempted_from: ["COVERAGE-PLAN-2.1", "COVERAGE-PLAN-2.4"]
91
+ reason: "Draft artifacts are work-in-progress and not yet ready for full coverage"
92
+
93
+ rollout:
94
+ phase: "PLANNER_TESTER_ENFORCEMENT"
95
+ description: "Section 2 validators become strict in Phase 2"