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,593 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Test and auto-fix URN headers in initialization/barrel files.
|
|
3
|
+
|
|
4
|
+
Validates and fixes:
|
|
5
|
+
- Python __init__.py files: URN comment + package docstring
|
|
6
|
+
- Dart index.dart files: URN comment + export documentation
|
|
7
|
+
- TypeScript index.ts files: URN comment + module documentation
|
|
8
|
+
|
|
9
|
+
Convention:
|
|
10
|
+
- All init/barrel files must have URN header
|
|
11
|
+
- URN format: urn:jel:{wagon}:{component}:{layer}:{sublayer}...
|
|
12
|
+
- URN derived from file path structure
|
|
13
|
+
|
|
14
|
+
Auto-fix Strategy:
|
|
15
|
+
- Generate URN from file path
|
|
16
|
+
- Add appropriate language-specific comment
|
|
17
|
+
- Add package/module docstring
|
|
18
|
+
- Preserve existing code (imports/exports)
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
import pytest
|
|
22
|
+
import re
|
|
23
|
+
from pathlib import Path
|
|
24
|
+
from typing import List, Tuple, Optional
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
# Path constants
|
|
28
|
+
REPO_ROOT = Path(__file__).resolve().parents[3]
|
|
29
|
+
PYTHON_DIR = REPO_ROOT / "python"
|
|
30
|
+
DART_DIR = REPO_ROOT / "lib"
|
|
31
|
+
TS_DIR = REPO_ROOT / "typescript"
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def find_python_init_files() -> List[Path]:
|
|
35
|
+
"""Find all Python __init__.py files."""
|
|
36
|
+
if not PYTHON_DIR.exists():
|
|
37
|
+
return []
|
|
38
|
+
|
|
39
|
+
return list(PYTHON_DIR.rglob("__init__.py"))
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def find_dart_index_files() -> List[Path]:
|
|
43
|
+
"""Find all Dart index.dart barrel files."""
|
|
44
|
+
if not DART_DIR.exists():
|
|
45
|
+
return []
|
|
46
|
+
|
|
47
|
+
return list(DART_DIR.rglob("index.dart"))
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def find_ts_index_files() -> List[Path]:
|
|
51
|
+
"""Find all TypeScript index.ts barrel files."""
|
|
52
|
+
if not TS_DIR.exists():
|
|
53
|
+
return []
|
|
54
|
+
|
|
55
|
+
index_files = []
|
|
56
|
+
index_files.extend(TS_DIR.rglob("index.ts"))
|
|
57
|
+
index_files.extend(TS_DIR.rglob("index.tsx"))
|
|
58
|
+
return index_files
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def generate_urn_from_path(file_path: Path, language: str) -> str:
|
|
62
|
+
"""
|
|
63
|
+
Generate URN from file path.
|
|
64
|
+
|
|
65
|
+
Examples:
|
|
66
|
+
- python/pace_dilemmas/pair_fragments/src/domain/services/__init__.py
|
|
67
|
+
→ urn:jel:pace-dilemmas:pair-fragments:domain:services
|
|
68
|
+
|
|
69
|
+
- lib/maintain_ux/provide_foundations/index.dart
|
|
70
|
+
→ urn:jel:maintain-ux:provide-foundations
|
|
71
|
+
|
|
72
|
+
- typescript/play_match/initialize_session/src/domain/index.ts
|
|
73
|
+
→ urn:jel:play-match:initialize-session:domain
|
|
74
|
+
"""
|
|
75
|
+
parts = file_path.parts
|
|
76
|
+
|
|
77
|
+
# Find language root index
|
|
78
|
+
try:
|
|
79
|
+
if language == "python":
|
|
80
|
+
lang_idx = parts.index("python")
|
|
81
|
+
elif language == "dart":
|
|
82
|
+
lang_idx = parts.index("lib")
|
|
83
|
+
elif language == "typescript":
|
|
84
|
+
lang_idx = parts.index("typescript")
|
|
85
|
+
else:
|
|
86
|
+
return ""
|
|
87
|
+
except ValueError:
|
|
88
|
+
return ""
|
|
89
|
+
|
|
90
|
+
# Extract path components after language root
|
|
91
|
+
path_components = parts[lang_idx + 1:]
|
|
92
|
+
|
|
93
|
+
# Remove filename and 'src' directories
|
|
94
|
+
filtered_components = []
|
|
95
|
+
for comp in path_components:
|
|
96
|
+
if comp in ["__init__.py", "index.dart", "index.ts", "index.tsx"]:
|
|
97
|
+
continue
|
|
98
|
+
if comp == "src":
|
|
99
|
+
continue
|
|
100
|
+
# Convert underscores to hyphens for kebab-case
|
|
101
|
+
comp_kebab = comp.replace("_", "-")
|
|
102
|
+
filtered_components.append(comp_kebab)
|
|
103
|
+
|
|
104
|
+
# Build URN
|
|
105
|
+
if not filtered_components:
|
|
106
|
+
return ""
|
|
107
|
+
|
|
108
|
+
urn = "urn:jel:" + ":".join(filtered_components)
|
|
109
|
+
return urn
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def extract_urn_from_file(file_path: Path, language: str) -> Optional[str]:
|
|
113
|
+
"""Extract URN from file header."""
|
|
114
|
+
try:
|
|
115
|
+
with open(file_path, 'r', encoding='utf-8') as f:
|
|
116
|
+
lines = f.readlines()
|
|
117
|
+
except Exception:
|
|
118
|
+
return None
|
|
119
|
+
|
|
120
|
+
comment_prefix = "#" if language == "python" else "//"
|
|
121
|
+
|
|
122
|
+
for line in lines[:10]: # Check first 10 lines
|
|
123
|
+
stripped = line.strip()
|
|
124
|
+
if stripped.startswith(comment_prefix):
|
|
125
|
+
# Match: # urn:jel:... or // urn:jel:...
|
|
126
|
+
match = re.match(rf'{re.escape(comment_prefix)}\s*urn:jel:(.+)', stripped)
|
|
127
|
+
if match:
|
|
128
|
+
return f"urn:jel:{match.group(1).strip()}"
|
|
129
|
+
|
|
130
|
+
return None
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def get_package_description(file_path: Path, urn: str) -> str:
|
|
134
|
+
"""Generate appropriate package description from URN."""
|
|
135
|
+
# Extract last component as package name
|
|
136
|
+
components = urn.split(":")
|
|
137
|
+
if len(components) < 3:
|
|
138
|
+
return "Package exports."
|
|
139
|
+
|
|
140
|
+
last_component = components[-1]
|
|
141
|
+
package_name = last_component.replace("-", "_")
|
|
142
|
+
|
|
143
|
+
# Check if this is a layer name
|
|
144
|
+
layer_names = {
|
|
145
|
+
"domain": "Domain layer",
|
|
146
|
+
"application": "Application layer",
|
|
147
|
+
"presentation": "Presentation layer",
|
|
148
|
+
"integration": "Integration layer",
|
|
149
|
+
"entities": "Entity definitions",
|
|
150
|
+
"services": "Domain services",
|
|
151
|
+
"use-cases": "Use case implementations",
|
|
152
|
+
"ports": "Port interfaces",
|
|
153
|
+
"controllers": "Controller implementations",
|
|
154
|
+
"repositories": "Repository implementations",
|
|
155
|
+
"adapters": "Adapter implementations",
|
|
156
|
+
"mappers": "Mapper implementations",
|
|
157
|
+
"engines": "Engine implementations",
|
|
158
|
+
"queries": "Query implementations",
|
|
159
|
+
"validators": "Validator implementations",
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
if last_component in layer_names:
|
|
163
|
+
return f"{layer_names[last_component]}."
|
|
164
|
+
|
|
165
|
+
# Get parent component for context
|
|
166
|
+
if len(components) >= 4:
|
|
167
|
+
parent = components[-2]
|
|
168
|
+
return f"{last_component.replace('-', ' ').title()} for {parent.replace('-', ' ')} component."
|
|
169
|
+
|
|
170
|
+
return f"{package_name.replace('_', ' ').title()} package."
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
def fix_python_init_file(file_path: Path) -> bool:
|
|
174
|
+
"""
|
|
175
|
+
Add URN header and docstring to Python __init__.py file.
|
|
176
|
+
|
|
177
|
+
Returns:
|
|
178
|
+
True if file was modified, False otherwise
|
|
179
|
+
"""
|
|
180
|
+
# Generate expected URN
|
|
181
|
+
expected_urn = generate_urn_from_path(file_path, "python")
|
|
182
|
+
if not expected_urn:
|
|
183
|
+
return False
|
|
184
|
+
|
|
185
|
+
# Check current URN
|
|
186
|
+
current_urn = extract_urn_from_file(file_path, "python")
|
|
187
|
+
|
|
188
|
+
# Read current content
|
|
189
|
+
try:
|
|
190
|
+
with open(file_path, 'r', encoding='utf-8') as f:
|
|
191
|
+
current_content = f.read()
|
|
192
|
+
except Exception:
|
|
193
|
+
return False
|
|
194
|
+
|
|
195
|
+
# Check if already has correct URN and docstring
|
|
196
|
+
has_urn = current_urn == expected_urn
|
|
197
|
+
has_docstring = '"""' in current_content or "'''" in current_content
|
|
198
|
+
|
|
199
|
+
if has_urn and has_docstring:
|
|
200
|
+
return False # Already correct
|
|
201
|
+
|
|
202
|
+
# Generate package description
|
|
203
|
+
description = get_package_description(file_path, expected_urn)
|
|
204
|
+
|
|
205
|
+
# Build new header
|
|
206
|
+
header_parts = []
|
|
207
|
+
|
|
208
|
+
# Add URN comment
|
|
209
|
+
if not has_urn:
|
|
210
|
+
header_parts.append(f"# {expected_urn}")
|
|
211
|
+
|
|
212
|
+
# Add docstring
|
|
213
|
+
if not has_docstring:
|
|
214
|
+
header_parts.append(f'"""{description}"""')
|
|
215
|
+
|
|
216
|
+
# Combine header with existing content
|
|
217
|
+
if header_parts:
|
|
218
|
+
# Remove old URN if exists
|
|
219
|
+
lines = current_content.split('\n')
|
|
220
|
+
cleaned_lines = []
|
|
221
|
+
for line in lines:
|
|
222
|
+
# Skip old URN comments
|
|
223
|
+
if line.strip().startswith("# urn:jel:"):
|
|
224
|
+
continue
|
|
225
|
+
cleaned_lines.append(line)
|
|
226
|
+
|
|
227
|
+
cleaned_content = '\n'.join(cleaned_lines).lstrip('\n')
|
|
228
|
+
|
|
229
|
+
new_content = '\n'.join(header_parts) + '\n'
|
|
230
|
+
if cleaned_content:
|
|
231
|
+
new_content += '\n' + cleaned_content
|
|
232
|
+
|
|
233
|
+
# Write updated content
|
|
234
|
+
try:
|
|
235
|
+
with open(file_path, 'w', encoding='utf-8') as f:
|
|
236
|
+
f.write(new_content)
|
|
237
|
+
return True
|
|
238
|
+
except Exception:
|
|
239
|
+
return False
|
|
240
|
+
|
|
241
|
+
return False
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
def fix_dart_index_file(file_path: Path) -> bool:
|
|
245
|
+
"""
|
|
246
|
+
Add URN header and documentation to Dart index.dart file.
|
|
247
|
+
|
|
248
|
+
Returns:
|
|
249
|
+
True if file was modified, False otherwise
|
|
250
|
+
"""
|
|
251
|
+
# Generate expected URN
|
|
252
|
+
expected_urn = generate_urn_from_path(file_path, "dart")
|
|
253
|
+
if not expected_urn:
|
|
254
|
+
return False
|
|
255
|
+
|
|
256
|
+
# Check current URN
|
|
257
|
+
current_urn = extract_urn_from_file(file_path, "dart")
|
|
258
|
+
|
|
259
|
+
# Read current content
|
|
260
|
+
try:
|
|
261
|
+
with open(file_path, 'r', encoding='utf-8') as f:
|
|
262
|
+
current_content = f.read()
|
|
263
|
+
except Exception:
|
|
264
|
+
return False
|
|
265
|
+
|
|
266
|
+
# Check if already has correct URN
|
|
267
|
+
if current_urn == expected_urn:
|
|
268
|
+
return False # Already correct
|
|
269
|
+
|
|
270
|
+
# Generate module description
|
|
271
|
+
description = get_package_description(file_path, expected_urn)
|
|
272
|
+
|
|
273
|
+
# Build new header
|
|
274
|
+
header = f"// {expected_urn}\n/// {description}\n"
|
|
275
|
+
|
|
276
|
+
# Remove old URN if exists
|
|
277
|
+
lines = current_content.split('\n')
|
|
278
|
+
cleaned_lines = []
|
|
279
|
+
for line in lines:
|
|
280
|
+
# Skip old URN comments
|
|
281
|
+
if line.strip().startswith("// urn:jel:"):
|
|
282
|
+
continue
|
|
283
|
+
# Skip old documentation comments at the start
|
|
284
|
+
if not cleaned_lines and line.strip().startswith("///"):
|
|
285
|
+
continue
|
|
286
|
+
cleaned_lines.append(line)
|
|
287
|
+
|
|
288
|
+
cleaned_content = '\n'.join(cleaned_lines).lstrip('\n')
|
|
289
|
+
|
|
290
|
+
new_content = header
|
|
291
|
+
if cleaned_content:
|
|
292
|
+
new_content += '\n' + cleaned_content
|
|
293
|
+
|
|
294
|
+
# Write updated content
|
|
295
|
+
try:
|
|
296
|
+
with open(file_path, 'w', encoding='utf-8') as f:
|
|
297
|
+
f.write(new_content)
|
|
298
|
+
return True
|
|
299
|
+
except Exception:
|
|
300
|
+
return False
|
|
301
|
+
|
|
302
|
+
|
|
303
|
+
def fix_ts_index_file(file_path: Path) -> bool:
|
|
304
|
+
"""
|
|
305
|
+
Add URN header and documentation to TypeScript index.ts file.
|
|
306
|
+
|
|
307
|
+
Returns:
|
|
308
|
+
True if file was modified, False otherwise
|
|
309
|
+
"""
|
|
310
|
+
# Generate expected URN
|
|
311
|
+
expected_urn = generate_urn_from_path(file_path, "typescript")
|
|
312
|
+
if not expected_urn:
|
|
313
|
+
return False
|
|
314
|
+
|
|
315
|
+
# Check current URN
|
|
316
|
+
current_urn = extract_urn_from_file(file_path, "typescript")
|
|
317
|
+
|
|
318
|
+
# Read current content
|
|
319
|
+
try:
|
|
320
|
+
with open(file_path, 'r', encoding='utf-8') as f:
|
|
321
|
+
current_content = f.read()
|
|
322
|
+
except Exception:
|
|
323
|
+
return False
|
|
324
|
+
|
|
325
|
+
# Check if already has correct URN
|
|
326
|
+
if current_urn == expected_urn:
|
|
327
|
+
return False # Already correct
|
|
328
|
+
|
|
329
|
+
# Generate module description
|
|
330
|
+
description = get_package_description(file_path, expected_urn)
|
|
331
|
+
|
|
332
|
+
# Build new header
|
|
333
|
+
header = f"// {expected_urn}\n/** {description} */\n"
|
|
334
|
+
|
|
335
|
+
# Remove old URN if exists
|
|
336
|
+
lines = current_content.split('\n')
|
|
337
|
+
cleaned_lines = []
|
|
338
|
+
for line in lines:
|
|
339
|
+
# Skip old URN comments
|
|
340
|
+
if line.strip().startswith("// urn:jel:"):
|
|
341
|
+
continue
|
|
342
|
+
cleaned_lines.append(line)
|
|
343
|
+
|
|
344
|
+
cleaned_content = '\n'.join(cleaned_lines).lstrip('\n')
|
|
345
|
+
|
|
346
|
+
new_content = header
|
|
347
|
+
if cleaned_content:
|
|
348
|
+
new_content += '\n' + cleaned_content
|
|
349
|
+
|
|
350
|
+
# Write updated content
|
|
351
|
+
try:
|
|
352
|
+
with open(file_path, 'w', encoding='utf-8') as f:
|
|
353
|
+
f.write(new_content)
|
|
354
|
+
return True
|
|
355
|
+
except Exception:
|
|
356
|
+
return False
|
|
357
|
+
|
|
358
|
+
|
|
359
|
+
@pytest.mark.coder
|
|
360
|
+
def test_python_init_files_have_urns():
|
|
361
|
+
"""
|
|
362
|
+
SPEC-CODER-URN-0001: Python __init__.py files have URN headers.
|
|
363
|
+
|
|
364
|
+
All __init__.py files must have:
|
|
365
|
+
- URN comment header (# urn:jel:...)
|
|
366
|
+
- Package docstring
|
|
367
|
+
|
|
368
|
+
Auto-fix: Adds missing URN and docstring
|
|
369
|
+
|
|
370
|
+
Given: All Python __init__.py files
|
|
371
|
+
When: Checking for URN headers
|
|
372
|
+
Then: All files have correct URN and docstring
|
|
373
|
+
"""
|
|
374
|
+
init_files = find_python_init_files()
|
|
375
|
+
|
|
376
|
+
if not init_files:
|
|
377
|
+
pytest.skip("No Python __init__.py files found")
|
|
378
|
+
|
|
379
|
+
missing_urns = []
|
|
380
|
+
fixed_files = []
|
|
381
|
+
|
|
382
|
+
for init_file in init_files:
|
|
383
|
+
expected_urn = generate_urn_from_path(init_file, "python")
|
|
384
|
+
if not expected_urn:
|
|
385
|
+
continue
|
|
386
|
+
|
|
387
|
+
current_urn = extract_urn_from_file(init_file, "python")
|
|
388
|
+
|
|
389
|
+
# Try to read content for docstring check
|
|
390
|
+
try:
|
|
391
|
+
with open(init_file, 'r', encoding='utf-8') as f:
|
|
392
|
+
content = f.read()
|
|
393
|
+
has_docstring = '"""' in content or "'''" in content
|
|
394
|
+
except Exception:
|
|
395
|
+
has_docstring = False
|
|
396
|
+
|
|
397
|
+
if current_urn != expected_urn or not has_docstring:
|
|
398
|
+
# Auto-fix
|
|
399
|
+
if fix_python_init_file(init_file):
|
|
400
|
+
fixed_files.append(init_file)
|
|
401
|
+
else:
|
|
402
|
+
missing_urns.append((init_file, expected_urn, current_urn))
|
|
403
|
+
|
|
404
|
+
# Report results
|
|
405
|
+
if fixed_files:
|
|
406
|
+
rel_paths = [f.relative_to(REPO_ROOT) for f in fixed_files]
|
|
407
|
+
print(f"\n✅ Auto-fixed {len(fixed_files)} Python __init__.py files:")
|
|
408
|
+
for path in rel_paths[:10]:
|
|
409
|
+
print(f" {path}")
|
|
410
|
+
if len(rel_paths) > 10:
|
|
411
|
+
print(f" ... and {len(rel_paths) - 10} more")
|
|
412
|
+
|
|
413
|
+
if missing_urns:
|
|
414
|
+
pytest.fail(
|
|
415
|
+
f"\n\nFound {len(missing_urns)} Python __init__.py files that could not be fixed:\n\n" +
|
|
416
|
+
"\n".join(
|
|
417
|
+
f" {file.relative_to(REPO_ROOT)}\n"
|
|
418
|
+
f" Expected: {expected}\n"
|
|
419
|
+
f" Current: {current or 'None'}"
|
|
420
|
+
for file, expected, current in missing_urns[:10]
|
|
421
|
+
) +
|
|
422
|
+
(f"\n\n... and {len(missing_urns) - 10} more" if len(missing_urns) > 10 else "")
|
|
423
|
+
)
|
|
424
|
+
|
|
425
|
+
|
|
426
|
+
@pytest.mark.coder
|
|
427
|
+
def test_dart_index_files_have_urns():
|
|
428
|
+
"""
|
|
429
|
+
SPEC-CODER-URN-0002: Dart index.dart files have URN headers.
|
|
430
|
+
|
|
431
|
+
All index.dart barrel files must have:
|
|
432
|
+
- URN comment header (// urn:jel:...)
|
|
433
|
+
- Module documentation (///)
|
|
434
|
+
|
|
435
|
+
Auto-fix: Adds missing URN and documentation
|
|
436
|
+
|
|
437
|
+
Given: All Dart index.dart files
|
|
438
|
+
When: Checking for URN headers
|
|
439
|
+
Then: All files have correct URN and documentation
|
|
440
|
+
"""
|
|
441
|
+
index_files = find_dart_index_files()
|
|
442
|
+
|
|
443
|
+
if not index_files:
|
|
444
|
+
pytest.skip("No Dart index.dart files found")
|
|
445
|
+
|
|
446
|
+
missing_urns = []
|
|
447
|
+
fixed_files = []
|
|
448
|
+
|
|
449
|
+
for index_file in index_files:
|
|
450
|
+
expected_urn = generate_urn_from_path(index_file, "dart")
|
|
451
|
+
if not expected_urn:
|
|
452
|
+
continue
|
|
453
|
+
|
|
454
|
+
current_urn = extract_urn_from_file(index_file, "dart")
|
|
455
|
+
|
|
456
|
+
if current_urn != expected_urn:
|
|
457
|
+
# Auto-fix
|
|
458
|
+
if fix_dart_index_file(index_file):
|
|
459
|
+
fixed_files.append(index_file)
|
|
460
|
+
else:
|
|
461
|
+
missing_urns.append((index_file, expected_urn, current_urn))
|
|
462
|
+
|
|
463
|
+
# Report results
|
|
464
|
+
if fixed_files:
|
|
465
|
+
rel_paths = [f.relative_to(REPO_ROOT) for f in fixed_files]
|
|
466
|
+
print(f"\n✅ Auto-fixed {len(fixed_files)} Dart index.dart files:")
|
|
467
|
+
for path in rel_paths[:10]:
|
|
468
|
+
print(f" {path}")
|
|
469
|
+
if len(rel_paths) > 10:
|
|
470
|
+
print(f" ... and {len(rel_paths) - 10} more")
|
|
471
|
+
|
|
472
|
+
if missing_urns:
|
|
473
|
+
pytest.fail(
|
|
474
|
+
f"\n\nFound {len(missing_urns)} Dart index.dart files that could not be fixed:\n\n" +
|
|
475
|
+
"\n".join(
|
|
476
|
+
f" {file.relative_to(REPO_ROOT)}\n"
|
|
477
|
+
f" Expected: {expected}\n"
|
|
478
|
+
f" Current: {current or 'None'}"
|
|
479
|
+
for file, expected, current in missing_urns[:10]
|
|
480
|
+
) +
|
|
481
|
+
(f"\n\n... and {len(missing_urns) - 10} more" if len(missing_urns) > 10 else "")
|
|
482
|
+
)
|
|
483
|
+
|
|
484
|
+
|
|
485
|
+
@pytest.mark.coder
|
|
486
|
+
def test_typescript_index_files_have_urns():
|
|
487
|
+
"""
|
|
488
|
+
SPEC-CODER-URN-0003: TypeScript index.ts files have URN headers.
|
|
489
|
+
|
|
490
|
+
All index.ts/tsx barrel files must have:
|
|
491
|
+
- URN comment header (// urn:jel:...)
|
|
492
|
+
- Module documentation (/** ... */)
|
|
493
|
+
|
|
494
|
+
Auto-fix: Adds missing URN and documentation
|
|
495
|
+
|
|
496
|
+
Given: All TypeScript index.ts/tsx files
|
|
497
|
+
When: Checking for URN headers
|
|
498
|
+
Then: All files have correct URN and documentation
|
|
499
|
+
"""
|
|
500
|
+
index_files = find_ts_index_files()
|
|
501
|
+
|
|
502
|
+
if not index_files:
|
|
503
|
+
pytest.skip("No TypeScript index.ts/tsx files found")
|
|
504
|
+
|
|
505
|
+
missing_urns = []
|
|
506
|
+
fixed_files = []
|
|
507
|
+
|
|
508
|
+
for index_file in index_files:
|
|
509
|
+
expected_urn = generate_urn_from_path(index_file, "typescript")
|
|
510
|
+
if not expected_urn:
|
|
511
|
+
continue
|
|
512
|
+
|
|
513
|
+
current_urn = extract_urn_from_file(index_file, "typescript")
|
|
514
|
+
|
|
515
|
+
if current_urn != expected_urn:
|
|
516
|
+
# Auto-fix
|
|
517
|
+
if fix_ts_index_file(index_file):
|
|
518
|
+
fixed_files.append(index_file)
|
|
519
|
+
else:
|
|
520
|
+
missing_urns.append((index_file, expected_urn, current_urn))
|
|
521
|
+
|
|
522
|
+
# Report results
|
|
523
|
+
if fixed_files:
|
|
524
|
+
rel_paths = [f.relative_to(REPO_ROOT) for f in fixed_files]
|
|
525
|
+
print(f"\n✅ Auto-fixed {len(fixed_files)} TypeScript index files:")
|
|
526
|
+
for path in rel_paths[:10]:
|
|
527
|
+
print(f" {path}")
|
|
528
|
+
if len(rel_paths) > 10:
|
|
529
|
+
print(f" ... and {len(rel_paths) - 10} more")
|
|
530
|
+
|
|
531
|
+
if missing_urns:
|
|
532
|
+
pytest.fail(
|
|
533
|
+
f"\n\nFound {len(missing_urns)} TypeScript index files that could not be fixed:\n\n" +
|
|
534
|
+
"\n".join(
|
|
535
|
+
f" {file.relative_to(REPO_ROOT)}\n"
|
|
536
|
+
f" Expected: {expected}\n"
|
|
537
|
+
f" Current: {current or 'None'}"
|
|
538
|
+
for file, expected, current in missing_urns[:10]
|
|
539
|
+
) +
|
|
540
|
+
(f"\n\n... and {len(missing_urns) - 10} more" if len(missing_urns) > 10 else "")
|
|
541
|
+
)
|
|
542
|
+
|
|
543
|
+
|
|
544
|
+
@pytest.mark.coder
|
|
545
|
+
def test_urn_generation_logic():
|
|
546
|
+
"""
|
|
547
|
+
SPEC-CODER-URN-0004: URN generation logic is correct.
|
|
548
|
+
|
|
549
|
+
Validate URN generation from various file paths.
|
|
550
|
+
|
|
551
|
+
Given: Sample file paths
|
|
552
|
+
When: Generating URNs
|
|
553
|
+
Then: URNs match expected format
|
|
554
|
+
"""
|
|
555
|
+
test_cases = [
|
|
556
|
+
# (file_path, language, expected_urn)
|
|
557
|
+
("python/pace_dilemmas/pair_fragments/src/domain/services/__init__.py",
|
|
558
|
+
"python",
|
|
559
|
+
"urn:jel:pace-dilemmas:pair-fragments:domain:services"),
|
|
560
|
+
|
|
561
|
+
("python/pace_dilemmas/pair_fragments/src/domain/__init__.py",
|
|
562
|
+
"python",
|
|
563
|
+
"urn:jel:pace-dilemmas:pair-fragments:domain"),
|
|
564
|
+
|
|
565
|
+
("lib/maintain_ux/provide_foundations/index.dart",
|
|
566
|
+
"dart",
|
|
567
|
+
"urn:jel:maintain-ux:provide-foundations"),
|
|
568
|
+
|
|
569
|
+
("typescript/play_match/initialize_session/src/domain/index.ts",
|
|
570
|
+
"typescript",
|
|
571
|
+
"urn:jel:play-match:initialize-session:domain"),
|
|
572
|
+
]
|
|
573
|
+
|
|
574
|
+
failures = []
|
|
575
|
+
|
|
576
|
+
for path_str, language, expected in test_cases:
|
|
577
|
+
# Create a Path object from the string
|
|
578
|
+
test_path = REPO_ROOT / path_str
|
|
579
|
+
|
|
580
|
+
actual = generate_urn_from_path(test_path, language)
|
|
581
|
+
|
|
582
|
+
if actual != expected:
|
|
583
|
+
failures.append(
|
|
584
|
+
f"Path: {path_str}\n"
|
|
585
|
+
f" Expected: {expected}\n"
|
|
586
|
+
f" Actual: {actual}"
|
|
587
|
+
)
|
|
588
|
+
|
|
589
|
+
if failures:
|
|
590
|
+
pytest.fail(
|
|
591
|
+
f"\n\nURN generation logic failures:\n\n" +
|
|
592
|
+
"\n\n".join(failures)
|
|
593
|
+
)
|