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.
@@ -58,6 +58,69 @@
58
58
  },
59
59
  "required": ["last_version"],
60
60
  "additionalProperties": false
61
+ },
62
+ "coverage": {
63
+ "type": "object",
64
+ "description": "Hierarchy coverage validation settings (ATDD Hierarchy Coverage Spec v0.1)",
65
+ "properties": {
66
+ "exceptions": {
67
+ "type": "object",
68
+ "description": "Allow-lists for coverage exceptions",
69
+ "properties": {
70
+ "wagons_not_in_train": {
71
+ "type": "array",
72
+ "items": {"type": "string"},
73
+ "description": "Wagon slugs allowed without train coverage"
74
+ },
75
+ "features_orphaned": {
76
+ "type": "array",
77
+ "items": {"type": "string"},
78
+ "description": "Feature URNs allowed without wagon manifest reference"
79
+ },
80
+ "wmbts_without_acceptance": {
81
+ "type": "array",
82
+ "items": {"type": "string"},
83
+ "description": "WMBT URNs allowed without acceptance criteria"
84
+ },
85
+ "acceptance_without_tests": {
86
+ "type": "array",
87
+ "items": {"type": "string"},
88
+ "description": "Acceptance URNs allowed without test coverage"
89
+ },
90
+ "contracts_unreferenced": {
91
+ "type": "array",
92
+ "items": {"type": "string"},
93
+ "description": "Contract URNs allowed without produce/consume"
94
+ },
95
+ "telemetry_unreferenced": {
96
+ "type": "array",
97
+ "items": {"type": "string"},
98
+ "description": "Telemetry URNs allowed without references"
99
+ },
100
+ "features_without_implementation": {
101
+ "type": "array",
102
+ "items": {"type": "string"},
103
+ "description": "Feature URNs allowed without code implementation"
104
+ }
105
+ },
106
+ "additionalProperties": false
107
+ },
108
+ "thresholds": {
109
+ "type": "object",
110
+ "description": "Coverage threshold settings",
111
+ "properties": {
112
+ "min_acceptance_coverage": {
113
+ "type": "number",
114
+ "minimum": 0,
115
+ "maximum": 100,
116
+ "default": 80,
117
+ "description": "Minimum percentage of acceptances that must have tests"
118
+ }
119
+ },
120
+ "additionalProperties": false
121
+ }
122
+ },
123
+ "additionalProperties": false
61
124
  }
62
125
  },
63
126
  "required": ["version", "release"],
@@ -0,0 +1,97 @@
1
+ """
2
+ ATDD Hierarchy Coverage Spec v0.1 Rollout Phase Controller.
3
+
4
+ Manages the phased rollout of coverage validation rules:
5
+ - Phase 1 (WARNINGS_ONLY): All validators emit warnings only
6
+ - Phase 2 (PLANNER_TESTER_ENFORCEMENT): Sections 2 + 3 validators strict
7
+ - Phase 3 (FULL_ENFORCEMENT): All validators (including Section 4) strict
8
+
9
+ Usage in validators:
10
+ from atdd.coach.utils.coverage_phase import CoveragePhase, should_enforce
11
+
12
+ if should_enforce(CoveragePhase.PLANNER_TESTER_ENFORCEMENT):
13
+ assert condition, "Error message"
14
+ else:
15
+ if not condition:
16
+ emit_coverage_warning("COVERAGE-PLAN-2.1", "Warning message", CoveragePhase.PLANNER_TESTER_ENFORCEMENT)
17
+ """
18
+
19
+ from enum import IntEnum
20
+ from typing import Optional
21
+ import warnings
22
+
23
+
24
+ class CoveragePhase(IntEnum):
25
+ """
26
+ Rollout phases for ATDD Hierarchy Coverage Spec v0.1.
27
+
28
+ Phases are ordered by strictness level:
29
+ - WARNINGS_ONLY (1): All new validators emit warnings, no assertions
30
+ - PLANNER_TESTER_ENFORCEMENT (2): Planner (Section 2) + Tester (Section 3) strict
31
+ - FULL_ENFORCEMENT (3): All validators including Coder (Section 4) strict
32
+ """
33
+ WARNINGS_ONLY = 1
34
+ PLANNER_TESTER_ENFORCEMENT = 2
35
+ FULL_ENFORCEMENT = 3
36
+
37
+
38
+ # Current rollout phase - update this to advance through phases
39
+ CURRENT_PHASE = CoveragePhase.WARNINGS_ONLY
40
+
41
+
42
+ def should_enforce(validator_phase: CoveragePhase) -> bool:
43
+ """
44
+ Check if a validator should enforce strict mode.
45
+
46
+ Args:
47
+ validator_phase: The phase at which this validator becomes strict
48
+
49
+ Returns:
50
+ True if current phase >= validator_phase (should enforce)
51
+ False if current phase < validator_phase (should warn only)
52
+
53
+ Example:
54
+ # This validator becomes strict in Phase 2
55
+ if should_enforce(CoveragePhase.PLANNER_TESTER_ENFORCEMENT):
56
+ assert wagon_in_train, "Wagon must be in at least one train"
57
+ else:
58
+ if not wagon_in_train:
59
+ emit_coverage_warning("COVERAGE-PLAN-2.1", "Wagon not in any train", CoveragePhase.PLANNER_TESTER_ENFORCEMENT)
60
+ """
61
+ return CURRENT_PHASE >= validator_phase
62
+
63
+
64
+ def get_current_phase() -> CoveragePhase:
65
+ """Get the current rollout phase."""
66
+ return CURRENT_PHASE
67
+
68
+
69
+ def get_phase_name(phase: Optional[CoveragePhase] = None) -> str:
70
+ """Get human-readable name for a phase."""
71
+ phase = phase or CURRENT_PHASE
72
+ return {
73
+ CoveragePhase.WARNINGS_ONLY: "Phase 1: Warnings Only",
74
+ CoveragePhase.PLANNER_TESTER_ENFORCEMENT: "Phase 2: Planner+Tester Enforcement",
75
+ CoveragePhase.FULL_ENFORCEMENT: "Phase 3: Full Enforcement",
76
+ }.get(phase, "Unknown Phase")
77
+
78
+
79
+ def emit_coverage_warning(
80
+ spec_id: str,
81
+ message: str,
82
+ validator_phase: CoveragePhase = CoveragePhase.PLANNER_TESTER_ENFORCEMENT
83
+ ) -> None:
84
+ """
85
+ Emit a coverage validation warning with phase context.
86
+
87
+ Args:
88
+ spec_id: The SPEC ID (e.g., "COVERAGE-PLAN-2.1")
89
+ message: The warning message
90
+ validator_phase: Phase when this becomes an error
91
+ """
92
+ phase_name = get_phase_name(validator_phase)
93
+ warnings.warn(
94
+ f"[{spec_id}] {message} (will become error in {phase_name})",
95
+ category=UserWarning,
96
+ stacklevel=3
97
+ )
@@ -445,3 +445,157 @@ def pytest_html_results_summary(prefix, summary, postfix):
445
445
  'against platform schemas and conventions.</p>'
446
446
  '</div>'
447
447
  ])
448
+
449
+
450
+ # ============================================================================
451
+ # COVERAGE VALIDATION FIXTURES (ATDD Hierarchy Coverage Spec v0.1)
452
+ # ============================================================================
453
+
454
+
455
+ @pytest.fixture(scope="module")
456
+ def coverage_exceptions(atdd_config: Dict[str, Any]) -> Dict[str, List[str]]:
457
+ """
458
+ Load coverage exception allow-lists from .atdd/config.yaml.
459
+
460
+ Returns:
461
+ Dict mapping exception type to list of allowed URNs/slugs
462
+ """
463
+ return atdd_config.get("coverage", {}).get("exceptions", {})
464
+
465
+
466
+ @pytest.fixture(scope="module")
467
+ def coverage_thresholds(atdd_config: Dict[str, Any]) -> Dict[str, Any]:
468
+ """
469
+ Load coverage threshold settings from .atdd/config.yaml.
470
+
471
+ Returns:
472
+ Dict with threshold settings (min_acceptance_coverage, etc.)
473
+ """
474
+ defaults = {"min_acceptance_coverage": 80}
475
+ thresholds = atdd_config.get("coverage", {}).get("thresholds", {})
476
+ return {**defaults, **thresholds}
477
+
478
+
479
+ @pytest.fixture(scope="module")
480
+ def feature_files() -> List[Tuple[Path, Dict[str, Any]]]:
481
+ """
482
+ Discover all feature files in plan/*/features/.
483
+
484
+ Returns:
485
+ List of (path, feature_data) tuples
486
+ """
487
+ import re
488
+ features = []
489
+ if not PLAN_DIR.exists():
490
+ return features
491
+
492
+ for wagon_dir in PLAN_DIR.iterdir():
493
+ if wagon_dir.is_dir() and not wagon_dir.name.startswith("_"):
494
+ features_dir = wagon_dir / "features"
495
+ if features_dir.exists():
496
+ for feature_file in features_dir.glob("*.yaml"):
497
+ try:
498
+ with open(feature_file) as f:
499
+ data = yaml.safe_load(f)
500
+ if data:
501
+ features.append((feature_file, data))
502
+ except Exception:
503
+ pass
504
+ return features
505
+
506
+
507
+ @pytest.fixture(scope="module")
508
+ def wmbt_files() -> List[Tuple[Path, Dict[str, Any]]]:
509
+ """
510
+ Discover all WMBT files in plan/*/.
511
+
512
+ WMBT files match pattern: [DLPCEMYRK]NNN.yaml (e.g., D001.yaml, L010.yaml)
513
+
514
+ Returns:
515
+ List of (path, wmbt_data) tuples
516
+ """
517
+ import re
518
+ wmbts = []
519
+ if not PLAN_DIR.exists():
520
+ return wmbts
521
+
522
+ wmbt_pattern = re.compile(r"^[DLPCEMYRK]\d{3}\.yaml$")
523
+
524
+ for wagon_dir in PLAN_DIR.iterdir():
525
+ if wagon_dir.is_dir() and not wagon_dir.name.startswith("_"):
526
+ for wmbt_file in wagon_dir.glob("*.yaml"):
527
+ if wmbt_pattern.match(wmbt_file.name):
528
+ try:
529
+ with open(wmbt_file) as f:
530
+ data = yaml.safe_load(f)
531
+ if data:
532
+ wmbts.append((wmbt_file, data))
533
+ except Exception:
534
+ pass
535
+ return wmbts
536
+
537
+
538
+ @pytest.fixture(scope="module")
539
+ def acceptance_urns_by_wagon(wmbt_files: List[Tuple[Path, Dict[str, Any]]]) -> Dict[str, List[str]]:
540
+ """
541
+ Extract all acceptance URNs grouped by wagon slug.
542
+
543
+ Returns:
544
+ Dict mapping wagon slug to list of acceptance URNs
545
+ """
546
+ by_wagon: Dict[str, List[str]] = {}
547
+
548
+ for path, wmbt_data in wmbt_files:
549
+ # Derive wagon slug from directory name (snake_case -> kebab-case)
550
+ wagon_slug = path.parent.name.replace("_", "-")
551
+
552
+ if wagon_slug not in by_wagon:
553
+ by_wagon[wagon_slug] = []
554
+
555
+ for acc in wmbt_data.get("acceptances", []):
556
+ if isinstance(acc, dict) and "identity" in acc:
557
+ urn = acc["identity"].get("urn", "")
558
+ if urn:
559
+ by_wagon[wagon_slug].append(urn)
560
+ elif isinstance(acc, str):
561
+ by_wagon[wagon_slug].append(acc)
562
+
563
+ return by_wagon
564
+
565
+
566
+ @pytest.fixture(scope="module")
567
+ def all_acceptance_urns(acceptance_urns_by_wagon: Dict[str, List[str]]) -> List[str]:
568
+ """
569
+ Get flat list of all acceptance URNs across all wagons.
570
+
571
+ Returns:
572
+ List of all acceptance URNs
573
+ """
574
+ all_urns = []
575
+ for urns in acceptance_urns_by_wagon.values():
576
+ all_urns.extend(urns)
577
+ return all_urns
578
+
579
+
580
+ @pytest.fixture(scope="module")
581
+ def wagon_to_train_mapping(train_files: List[Tuple[Path, Dict]]) -> Dict[str, List[str]]:
582
+ """
583
+ Build mapping of wagon slugs to train IDs that reference them.
584
+
585
+ Returns:
586
+ Dict mapping wagon slug to list of train IDs
587
+ """
588
+ mapping: Dict[str, List[str]] = {}
589
+
590
+ for train_path, train_data in train_files:
591
+ train_id = train_data.get("train_id", train_path.stem)
592
+ participants = train_data.get("participants", [])
593
+
594
+ for participant in participants:
595
+ if isinstance(participant, str) and participant.startswith("wagon:"):
596
+ wagon_slug = participant.replace("wagon:", "")
597
+ if wagon_slug not in mapping:
598
+ mapping[wagon_slug] = []
599
+ mapping[wagon_slug].append(train_id)
600
+
601
+ return mapping
@@ -0,0 +1,85 @@
1
+ version: "1.0"
2
+ name: "Coder Coverage Convention"
3
+ description: "Section 4 of ATDD Hierarchy Coverage Spec v0.1 - Ensures code implementation covers all features"
4
+
5
+ rules:
6
+ - id: "COVERAGE-CODE-4.1"
7
+ name: "Feature - Implementation Coverage"
8
+ description: "Every feature must have corresponding code implementation"
9
+ requirement: "Each feature's components must map to code paths"
10
+ exceptions: "features_without_implementation allow-list"
11
+ validator: "test_all_features_have_implementations"
12
+
13
+ - id: "COVERAGE-CODE-4.2"
14
+ name: "Implementation - Tests Coverage"
15
+ description: "Every implementation must have at least one linked test"
16
+ requirement: "Each implementation must have at least one linked test"
17
+ validator: "test_all_implementations_have_tests"
18
+
19
+ coverage_graph:
20
+ description: "The coder coverage graph ensures features are implemented and tested"
21
+ levels:
22
+ - name: "Feature"
23
+ implemented_by: "Code files in python/, supabase/, web/"
24
+ validators: ["COVERAGE-CODE-4.1"]
25
+
26
+ - name: "Implementation"
27
+ tested_by: "Test files referencing feature/component"
28
+ validators: ["COVERAGE-CODE-4.2"]
29
+
30
+ exception_handling:
31
+ description: "How exceptions are handled in coder coverage validation"
32
+ allow_lists:
33
+ features_without_implementation:
34
+ description: "Feature URNs that are allowed without code implementation"
35
+ use_case: "Planned features, design-only features, or features implemented externally"
36
+ config_path: "coverage.exceptions.features_without_implementation"
37
+
38
+ implementation_patterns:
39
+ description: "How features map to code implementations"
40
+
41
+ python:
42
+ location: "python/{wagon}/"
43
+ patterns:
44
+ - "use_case_{feature_slug}.py"
45
+ - "service_{feature_slug}.py"
46
+ - "{feature_slug}_handler.py"
47
+ example: "python/maintain_ux/use_case_provide_foundations.py"
48
+
49
+ typescript:
50
+ location: "supabase/functions/{wagon}/{feature}/"
51
+ patterns:
52
+ - "index.ts"
53
+ - "handler.ts"
54
+ - "{feature_slug}.ts"
55
+ example: "supabase/functions/maintain-ux/provide-foundations/index.ts"
56
+
57
+ web:
58
+ location: "web/src/features/{wagon}/{feature}/"
59
+ patterns:
60
+ - "index.tsx"
61
+ - "{feature_slug}.tsx"
62
+ - "components/"
63
+ example: "web/src/features/maintain-ux/provide-foundations/index.tsx"
64
+
65
+ test_patterns:
66
+ description: "How implementations map to tests"
67
+
68
+ python:
69
+ location: "python/{wagon}/"
70
+ filename: "test_*.py"
71
+ reference_method: "Import or reference to implementation module"
72
+
73
+ typescript:
74
+ location: "supabase/functions/{wagon}/{feature}/test/"
75
+ filename: "*.test.ts"
76
+ reference_method: "Import or reference to implementation"
77
+
78
+ web:
79
+ location: "web/tests/{wagon}/{feature}/"
80
+ filename: "*.test.tsx"
81
+ reference_method: "Import or reference to component"
82
+
83
+ rollout:
84
+ phase: "FULL_ENFORCEMENT"
85
+ description: "Section 4 validators become strict in Phase 3 (after planner+tester)"
@@ -0,0 +1,5 @@
1
+ """
2
+ Shared fixtures for coder tests.
3
+ """
4
+ # Import all shared fixtures from coach via absolute import
5
+ from atdd.coach.validators.shared_fixtures import *