atdd 0.1.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.
Files changed (183) hide show
  1. atdd/__init__.py +0 -0
  2. atdd/cli.py +404 -0
  3. atdd/coach/__init__.py +0 -0
  4. atdd/coach/commands/__init__.py +0 -0
  5. atdd/coach/commands/add_persistence_metadata.py +215 -0
  6. atdd/coach/commands/analyze_migrations.py +188 -0
  7. atdd/coach/commands/consumers.py +720 -0
  8. atdd/coach/commands/infer_governance_status.py +149 -0
  9. atdd/coach/commands/initializer.py +177 -0
  10. atdd/coach/commands/interface.py +1078 -0
  11. atdd/coach/commands/inventory.py +565 -0
  12. atdd/coach/commands/migration.py +240 -0
  13. atdd/coach/commands/registry.py +1560 -0
  14. atdd/coach/commands/session.py +430 -0
  15. atdd/coach/commands/sync.py +405 -0
  16. atdd/coach/commands/test_interface.py +399 -0
  17. atdd/coach/commands/test_runner.py +141 -0
  18. atdd/coach/commands/tests/__init__.py +1 -0
  19. atdd/coach/commands/tests/test_telemetry_array_validation.py +235 -0
  20. atdd/coach/commands/traceability.py +4264 -0
  21. atdd/coach/conventions/session.convention.yaml +754 -0
  22. atdd/coach/overlays/__init__.py +2 -0
  23. atdd/coach/overlays/claude.md +2 -0
  24. atdd/coach/schemas/config.schema.json +34 -0
  25. atdd/coach/schemas/manifest.schema.json +101 -0
  26. atdd/coach/templates/ATDD.md +282 -0
  27. atdd/coach/templates/SESSION-TEMPLATE.md +327 -0
  28. atdd/coach/utils/__init__.py +0 -0
  29. atdd/coach/utils/graph/__init__.py +0 -0
  30. atdd/coach/utils/graph/urn.py +875 -0
  31. atdd/coach/validators/__init__.py +0 -0
  32. atdd/coach/validators/shared_fixtures.py +365 -0
  33. atdd/coach/validators/test_enrich_wagon_registry.py +167 -0
  34. atdd/coach/validators/test_registry.py +575 -0
  35. atdd/coach/validators/test_session_validation.py +1183 -0
  36. atdd/coach/validators/test_traceability.py +448 -0
  37. atdd/coach/validators/test_update_feature_paths.py +108 -0
  38. atdd/coach/validators/test_validate_contract_consumers.py +297 -0
  39. atdd/coder/__init__.py +1 -0
  40. atdd/coder/conventions/adapter.recipe.yaml +88 -0
  41. atdd/coder/conventions/backend.convention.yaml +460 -0
  42. atdd/coder/conventions/boundaries.convention.yaml +666 -0
  43. atdd/coder/conventions/commons.convention.yaml +460 -0
  44. atdd/coder/conventions/complexity.recipe.yaml +109 -0
  45. atdd/coder/conventions/component-naming.convention.yaml +178 -0
  46. atdd/coder/conventions/design.convention.yaml +327 -0
  47. atdd/coder/conventions/design.recipe.yaml +273 -0
  48. atdd/coder/conventions/dto.convention.yaml +660 -0
  49. atdd/coder/conventions/frontend.convention.yaml +542 -0
  50. atdd/coder/conventions/green.convention.yaml +1012 -0
  51. atdd/coder/conventions/presentation.convention.yaml +587 -0
  52. atdd/coder/conventions/refactor.convention.yaml +535 -0
  53. atdd/coder/conventions/technology.convention.yaml +206 -0
  54. atdd/coder/conventions/tests/__init__.py +0 -0
  55. atdd/coder/conventions/tests/test_adapter_recipe.py +302 -0
  56. atdd/coder/conventions/tests/test_complexity_recipe.py +289 -0
  57. atdd/coder/conventions/tests/test_component_taxonomy.py +278 -0
  58. atdd/coder/conventions/tests/test_component_urn_naming.py +165 -0
  59. atdd/coder/conventions/tests/test_thinness_recipe.py +286 -0
  60. atdd/coder/conventions/thinness.recipe.yaml +82 -0
  61. atdd/coder/conventions/train.convention.yaml +325 -0
  62. atdd/coder/conventions/verification.protocol.yaml +53 -0
  63. atdd/coder/schemas/design_system.schema.json +361 -0
  64. atdd/coder/validators/__init__.py +0 -0
  65. atdd/coder/validators/test_commons_structure.py +485 -0
  66. atdd/coder/validators/test_complexity.py +416 -0
  67. atdd/coder/validators/test_cross_language_consistency.py +431 -0
  68. atdd/coder/validators/test_design_system_compliance.py +413 -0
  69. atdd/coder/validators/test_dto_testing_patterns.py +268 -0
  70. atdd/coder/validators/test_green_cross_stack_layers.py +168 -0
  71. atdd/coder/validators/test_green_layer_dependencies.py +148 -0
  72. atdd/coder/validators/test_green_python_layer_structure.py +103 -0
  73. atdd/coder/validators/test_green_supabase_layer_structure.py +103 -0
  74. atdd/coder/validators/test_import_boundaries.py +396 -0
  75. atdd/coder/validators/test_init_file_urns.py +593 -0
  76. atdd/coder/validators/test_preact_layer_boundaries.py +221 -0
  77. atdd/coder/validators/test_presentation_convention.py +260 -0
  78. atdd/coder/validators/test_python_architecture.py +674 -0
  79. atdd/coder/validators/test_quality_metrics.py +420 -0
  80. atdd/coder/validators/test_station_master_pattern.py +244 -0
  81. atdd/coder/validators/test_train_infrastructure.py +454 -0
  82. atdd/coder/validators/test_train_urns.py +293 -0
  83. atdd/coder/validators/test_typescript_architecture.py +616 -0
  84. atdd/coder/validators/test_usecase_structure.py +421 -0
  85. atdd/coder/validators/test_wagon_boundaries.py +586 -0
  86. atdd/conftest.py +126 -0
  87. atdd/planner/__init__.py +1 -0
  88. atdd/planner/conventions/acceptance.convention.yaml +538 -0
  89. atdd/planner/conventions/appendix.convention.yaml +187 -0
  90. atdd/planner/conventions/artifact-naming.convention.yaml +852 -0
  91. atdd/planner/conventions/component.convention.yaml +670 -0
  92. atdd/planner/conventions/criteria.convention.yaml +141 -0
  93. atdd/planner/conventions/feature.convention.yaml +371 -0
  94. atdd/planner/conventions/interface.convention.yaml +382 -0
  95. atdd/planner/conventions/steps.convention.yaml +141 -0
  96. atdd/planner/conventions/train.convention.yaml +552 -0
  97. atdd/planner/conventions/wagon.convention.yaml +275 -0
  98. atdd/planner/conventions/wmbt.convention.yaml +258 -0
  99. atdd/planner/schemas/acceptance.schema.json +336 -0
  100. atdd/planner/schemas/appendix.schema.json +78 -0
  101. atdd/planner/schemas/component.schema.json +114 -0
  102. atdd/planner/schemas/feature.schema.json +197 -0
  103. atdd/planner/schemas/train.schema.json +192 -0
  104. atdd/planner/schemas/wagon.schema.json +281 -0
  105. atdd/planner/schemas/wmbt.schema.json +59 -0
  106. atdd/planner/validators/__init__.py +0 -0
  107. atdd/planner/validators/conftest.py +5 -0
  108. atdd/planner/validators/test_draft_wagon_registry.py +374 -0
  109. atdd/planner/validators/test_plan_cross_refs.py +240 -0
  110. atdd/planner/validators/test_plan_uniqueness.py +224 -0
  111. atdd/planner/validators/test_plan_urn_resolution.py +268 -0
  112. atdd/planner/validators/test_plan_wagons.py +174 -0
  113. atdd/planner/validators/test_train_validation.py +514 -0
  114. atdd/planner/validators/test_wagon_urn_chain.py +648 -0
  115. atdd/planner/validators/test_wmbt_consistency.py +327 -0
  116. atdd/planner/validators/test_wmbt_vocabulary.py +632 -0
  117. atdd/tester/__init__.py +1 -0
  118. atdd/tester/conventions/artifact.convention.yaml +257 -0
  119. atdd/tester/conventions/contract.convention.yaml +1009 -0
  120. atdd/tester/conventions/filename.convention.yaml +555 -0
  121. atdd/tester/conventions/migration.convention.yaml +509 -0
  122. atdd/tester/conventions/red.convention.yaml +797 -0
  123. atdd/tester/conventions/routing.convention.yaml +51 -0
  124. atdd/tester/conventions/telemetry.convention.yaml +458 -0
  125. atdd/tester/schemas/a11y.tmpl.json +17 -0
  126. atdd/tester/schemas/artifact.schema.json +189 -0
  127. atdd/tester/schemas/contract.schema.json +591 -0
  128. atdd/tester/schemas/contract.tmpl.json +95 -0
  129. atdd/tester/schemas/db.tmpl.json +20 -0
  130. atdd/tester/schemas/e2e.tmpl.json +17 -0
  131. atdd/tester/schemas/edge_function.tmpl.json +17 -0
  132. atdd/tester/schemas/event.tmpl.json +17 -0
  133. atdd/tester/schemas/http.tmpl.json +19 -0
  134. atdd/tester/schemas/job.tmpl.json +18 -0
  135. atdd/tester/schemas/load.tmpl.json +21 -0
  136. atdd/tester/schemas/metric.tmpl.json +19 -0
  137. atdd/tester/schemas/pack.schema.json +139 -0
  138. atdd/tester/schemas/realtime.tmpl.json +20 -0
  139. atdd/tester/schemas/rls.tmpl.json +18 -0
  140. atdd/tester/schemas/script.tmpl.json +16 -0
  141. atdd/tester/schemas/sec.tmpl.json +18 -0
  142. atdd/tester/schemas/storage.tmpl.json +18 -0
  143. atdd/tester/schemas/telemetry.schema.json +128 -0
  144. atdd/tester/schemas/telemetry_tracking_manifest.schema.json +143 -0
  145. atdd/tester/schemas/test_filename.schema.json +194 -0
  146. atdd/tester/schemas/test_intent.schema.json +179 -0
  147. atdd/tester/schemas/unit.tmpl.json +18 -0
  148. atdd/tester/schemas/visual.tmpl.json +18 -0
  149. atdd/tester/schemas/ws.tmpl.json +17 -0
  150. atdd/tester/utils/__init__.py +0 -0
  151. atdd/tester/utils/filename.py +300 -0
  152. atdd/tester/validators/__init__.py +0 -0
  153. atdd/tester/validators/cleanup_duplicate_headers.py +116 -0
  154. atdd/tester/validators/cleanup_duplicate_headers_v2.py +135 -0
  155. atdd/tester/validators/conftest.py +5 -0
  156. atdd/tester/validators/coverage_gap_report.py +321 -0
  157. atdd/tester/validators/fix_dual_ac_references.py +179 -0
  158. atdd/tester/validators/remove_duplicate_lines.py +93 -0
  159. atdd/tester/validators/test_acceptance_urn_filename_mapping.py +359 -0
  160. atdd/tester/validators/test_acceptance_urn_separator.py +166 -0
  161. atdd/tester/validators/test_artifact_naming_category.py +307 -0
  162. atdd/tester/validators/test_contract_schema_compliance.py +706 -0
  163. atdd/tester/validators/test_contracts_structure.py +200 -0
  164. atdd/tester/validators/test_coverage_adequacy.py +797 -0
  165. atdd/tester/validators/test_dual_ac_reference.py +225 -0
  166. atdd/tester/validators/test_fixture_validity.py +372 -0
  167. atdd/tester/validators/test_isolation.py +487 -0
  168. atdd/tester/validators/test_migration_coverage.py +204 -0
  169. atdd/tester/validators/test_migration_criteria.py +276 -0
  170. atdd/tester/validators/test_migration_generation.py +116 -0
  171. atdd/tester/validators/test_python_test_naming.py +410 -0
  172. atdd/tester/validators/test_red_layer_validation.py +95 -0
  173. atdd/tester/validators/test_red_python_layer_structure.py +87 -0
  174. atdd/tester/validators/test_red_supabase_layer_structure.py +90 -0
  175. atdd/tester/validators/test_telemetry_structure.py +634 -0
  176. atdd/tester/validators/test_typescript_test_naming.py +301 -0
  177. atdd/tester/validators/test_typescript_test_structure.py +84 -0
  178. atdd-0.1.0.dist-info/METADATA +191 -0
  179. atdd-0.1.0.dist-info/RECORD +183 -0
  180. atdd-0.1.0.dist-info/WHEEL +5 -0
  181. atdd-0.1.0.dist-info/entry_points.txt +2 -0
  182. atdd-0.1.0.dist-info/licenses/LICENSE +674 -0
  183. atdd-0.1.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,396 @@
1
+ """
2
+ Test import boundaries are enforced across layers.
3
+
4
+ Validates:
5
+ - No circular dependencies between modules
6
+ - Imports follow dependency flow (inward)
7
+ - No imports from test code into production code
8
+
9
+ Inspired by: .claude/utils/coder/import_scan.py
10
+ But: Self-contained, no utility dependencies
11
+ """
12
+
13
+ import pytest
14
+ import re
15
+ from pathlib import Path
16
+ from typing import Dict, List, Set
17
+
18
+
19
+ # Path constants
20
+ REPO_ROOT = Path(__file__).resolve().parents[3]
21
+ PYTHON_DIR = REPO_ROOT / "python"
22
+
23
+
24
+ def extract_all_imports(file_path: Path) -> List[str]:
25
+ """
26
+ Extract all import statements from Python file.
27
+
28
+ Args:
29
+ file_path: Path to Python file
30
+
31
+ Returns:
32
+ List of imported module names
33
+ """
34
+ try:
35
+ with open(file_path, 'r', encoding='utf-8') as f:
36
+ content = f.read()
37
+ except Exception:
38
+ return []
39
+
40
+ imports = []
41
+
42
+ # Match: from X import Y
43
+ from_imports = re.findall(r'from\s+([^\s]+)\s+import', content)
44
+ imports.extend(from_imports)
45
+
46
+ # Match: import X, Y, Z
47
+ direct_imports = re.findall(r'^\s*import\s+([^\s;#]+)', content, re.MULTILINE)
48
+ for imp in direct_imports:
49
+ # Split comma-separated imports
50
+ imports.extend([i.strip() for i in imp.split(',')])
51
+
52
+ return imports
53
+
54
+
55
+ def is_internal_import(import_path: str, base_module: str) -> bool:
56
+ """
57
+ Check if import is internal to the project.
58
+
59
+ Args:
60
+ import_path: Import statement
61
+ base_module: Base module name to check against
62
+
63
+ Returns:
64
+ True if import is internal to project
65
+ """
66
+ # Relative imports
67
+ if import_path.startswith('.'):
68
+ return True
69
+
70
+ # Absolute imports from same base module
71
+ if import_path.startswith(base_module):
72
+ return True
73
+
74
+ # Check if it's from python/ directory structure
75
+ if 'pace_dilemmas' in import_path or 'juggle_domains' in import_path:
76
+ return True
77
+
78
+ return False
79
+
80
+
81
+ def get_module_name(file_path: Path) -> str:
82
+ """
83
+ Get module name from file path.
84
+
85
+ Args:
86
+ file_path: Path to Python file
87
+
88
+ Returns:
89
+ Module name (e.g., "pace_dilemmas.pair_fragments.domain.entities")
90
+ """
91
+ try:
92
+ rel_path = file_path.relative_to(PYTHON_DIR)
93
+ # Remove .py extension
94
+ module_path = str(rel_path).replace('.py', '')
95
+ # Convert path to module name
96
+ module_name = module_path.replace('/', '.')
97
+ # Remove src. prefix if present
98
+ if '.src.' in module_name:
99
+ module_name = module_name.replace('.src.', '.')
100
+ return module_name
101
+ except ValueError:
102
+ return str(file_path.stem)
103
+
104
+
105
+ def find_python_modules() -> List[Path]:
106
+ """
107
+ Find all Python modules (excluding tests).
108
+
109
+ Returns:
110
+ List of Path objects
111
+ """
112
+ if not PYTHON_DIR.exists():
113
+ return []
114
+
115
+ modules = []
116
+ for py_file in PYTHON_DIR.rglob("*.py"):
117
+ # Skip test files
118
+ if '/test/' in str(py_file) or py_file.name.startswith('test_'):
119
+ continue
120
+ # Skip __pycache__
121
+ if '__pycache__' in str(py_file):
122
+ continue
123
+
124
+ modules.append(py_file)
125
+
126
+ return modules
127
+
128
+
129
+ def build_dependency_graph() -> Dict[str, Set[str]]:
130
+ """
131
+ Build dependency graph of all Python modules.
132
+
133
+ Returns:
134
+ Dict mapping module names to their dependencies
135
+ """
136
+ graph = {}
137
+
138
+ for module_file in find_python_modules():
139
+ module_name = get_module_name(module_file)
140
+ imports = extract_all_imports(module_file)
141
+
142
+ # Filter to internal imports only
143
+ internal_imports = {
144
+ imp for imp in imports
145
+ if is_internal_import(imp, 'pace_dilemmas') or is_internal_import(imp, 'juggle_domains')
146
+ }
147
+
148
+ graph[module_name] = internal_imports
149
+
150
+ return graph
151
+
152
+
153
+ def find_circular_dependencies(graph: Dict[str, Set[str]]) -> List[tuple]:
154
+ """
155
+ Find circular dependencies in module graph.
156
+
157
+ Args:
158
+ graph: Dependency graph
159
+
160
+ Returns:
161
+ List of (module_a, module_b) tuples representing circular deps
162
+ """
163
+ circular = []
164
+
165
+ for module, deps in graph.items():
166
+ for dep in deps:
167
+ # Check if dep also imports module (direct circular)
168
+ if dep in graph and module in graph[dep]:
169
+ # Only report each circle once (canonical order)
170
+ if module < dep:
171
+ circular.append((module, dep))
172
+
173
+ return circular
174
+
175
+
176
+ def find_cycles(graph: Dict[str, Set[str]], max_depth: int = 5) -> List[List[str]]:
177
+ """
178
+ Find dependency cycles using DFS.
179
+
180
+ Args:
181
+ graph: Dependency graph
182
+ max_depth: Maximum cycle length to detect
183
+
184
+ Returns:
185
+ List of cycles (each cycle is a list of module names)
186
+ """
187
+ cycles = []
188
+
189
+ def dfs(node, path, visited):
190
+ if node in path:
191
+ # Found a cycle
192
+ cycle_start = path.index(node)
193
+ cycle = path[cycle_start:] + [node]
194
+ if len(cycle) <= max_depth:
195
+ # Normalize cycle (start with smallest element)
196
+ min_idx = cycle.index(min(cycle))
197
+ normalized = cycle[min_idx:] + cycle[:min_idx]
198
+ if normalized not in cycles:
199
+ cycles.append(normalized)
200
+ return
201
+
202
+ if node in visited or node not in graph:
203
+ return
204
+
205
+ visited.add(node)
206
+ path.append(node)
207
+
208
+ for neighbor in graph.get(node, set()):
209
+ dfs(neighbor, path, visited)
210
+
211
+ path.pop()
212
+
213
+ for start_node in graph.keys():
214
+ dfs(start_node, [], set())
215
+
216
+ return cycles
217
+
218
+
219
+ @pytest.mark.coder
220
+ def test_no_circular_module_dependencies():
221
+ """
222
+ SPEC-CODER-IMPORT-0001: No circular dependencies between modules.
223
+
224
+ Circular dependencies cause:
225
+ - Import errors
226
+ - Initialization issues
227
+ - Tight coupling
228
+ - Difficult testing
229
+
230
+ Given: All Python modules in python/
231
+ When: Building dependency graph
232
+ Then: No module imports another that imports it back
233
+ """
234
+ graph = build_dependency_graph()
235
+
236
+ if not graph:
237
+ pytest.skip("No Python modules found to validate")
238
+
239
+ # Find direct circular dependencies (A → B, B → A)
240
+ circular = find_circular_dependencies(graph)
241
+
242
+ if circular:
243
+ pytest.fail(
244
+ f"\\n\\nFound {len(circular)} circular dependencies:\\n\\n" +
245
+ "\\n".join(f" {a} ↔ {b}" for a, b in circular[:10]) +
246
+ (f"\\n ... and {len(circular) - 10} more" if len(circular) > 10 else "")
247
+ )
248
+
249
+
250
+ @pytest.mark.coder
251
+ def test_no_import_cycles():
252
+ """
253
+ SPEC-CODER-IMPORT-0002: No dependency cycles (A → B → C → A).
254
+
255
+ Dependency cycles create:
256
+ - Complex initialization order
257
+ - Difficult refactoring
258
+ - Testing challenges
259
+ - Code smell
260
+
261
+ Given: All Python modules
262
+ When: Analyzing dependency chains
263
+ Then: No module depends on itself through intermediaries
264
+ """
265
+ graph = build_dependency_graph()
266
+
267
+ if not graph:
268
+ pytest.skip("No Python modules found to validate")
269
+
270
+ cycles = find_cycles(graph, max_depth=5)
271
+
272
+ if cycles:
273
+ # Format cycles for display
274
+ formatted_cycles = []
275
+ for cycle in cycles[:5]: # Show first 5
276
+ chain = " → ".join(cycle)
277
+ formatted_cycles.append(f" {chain}")
278
+
279
+ pytest.fail(
280
+ f"\\n\\nFound {len(cycles)} dependency cycles:\\n\\n" +
281
+ "\\n".join(formatted_cycles) +
282
+ (f"\\n ... and {len(cycles) - 5} more" if len(cycles) > 5 else "") +
283
+ f"\\n\\nCycles create tight coupling and make refactoring difficult."
284
+ )
285
+
286
+
287
+ @pytest.mark.coder
288
+ def test_no_test_imports_in_production():
289
+ """
290
+ SPEC-CODER-IMPORT-0003: Production code doesn't import from test code.
291
+
292
+ Test code can import production code, but NOT vice versa.
293
+
294
+ Given: All Python production modules
295
+ When: Checking imports
296
+ Then: No imports from test/ directories
297
+ """
298
+ violations = []
299
+
300
+ for module_file in find_python_modules():
301
+ # Skip if this is already a test file
302
+ if '/test/' in str(module_file):
303
+ continue
304
+
305
+ imports = extract_all_imports(module_file)
306
+
307
+ for imp in imports:
308
+ # Check if importing from test directory
309
+ if '/test/' in imp or imp.startswith('test_') or '.test.' in imp:
310
+ rel_path = module_file.relative_to(REPO_ROOT)
311
+ violations.append(
312
+ f"{rel_path}\\n"
313
+ f" Import: {imp}\\n"
314
+ f" Issue: Production code imports from test code"
315
+ )
316
+
317
+ if violations:
318
+ pytest.fail(
319
+ f"\\n\\nFound {len(violations)} test import violations:\\n\\n" +
320
+ "\\n\\n".join(violations[:10]) +
321
+ (f"\\n\\n... and {len(violations) - 10} more" if len(violations) > 10 else "") +
322
+ f"\\n\\nProduction code should never import test code."
323
+ )
324
+
325
+
326
+ @pytest.mark.coder
327
+ def test_imports_follow_layer_boundaries():
328
+ """
329
+ SPEC-CODER-IMPORT-0004: Imports respect architectural boundaries.
330
+
331
+ Based on file path structure:
332
+ - domain/ can only import from domain/
333
+ - application/ can import from domain/, application/
334
+ - integration/ can import from domain/, integration/
335
+ - presentation/ can import from domain/, application/, presentation/
336
+
337
+ Given: All Python modules with layer structure
338
+ When: Checking imports
339
+ Then: Imports only from allowed layers
340
+ """
341
+ violations = []
342
+
343
+ for module_file in find_python_modules():
344
+ module_path = str(module_file)
345
+
346
+ # Determine this module's layer
347
+ if '/domain/' in module_path:
348
+ current_layer = 'domain'
349
+ allowed_layers = ['domain']
350
+ elif '/application/' in module_path:
351
+ current_layer = 'application'
352
+ allowed_layers = ['domain', 'application']
353
+ elif '/integration/' in module_path or '/infrastructure/' in module_path:
354
+ current_layer = 'integration'
355
+ allowed_layers = ['domain', 'integration', 'infrastructure']
356
+ elif '/presentation/' in module_path:
357
+ current_layer = 'presentation'
358
+ allowed_layers = ['domain', 'application', 'presentation']
359
+ else:
360
+ # Can't determine layer, skip
361
+ continue
362
+
363
+ imports = extract_all_imports(module_file)
364
+
365
+ for imp in imports:
366
+ # Skip external imports
367
+ if not (is_internal_import(imp, 'pace_dilemmas') or is_internal_import(imp, 'juggle_domains')):
368
+ continue
369
+
370
+ # Check if import crosses boundary
371
+ import_layer = None
372
+ if '/domain/' in imp or '.domain.' in imp:
373
+ import_layer = 'domain'
374
+ elif '/application/' in imp or '.application.' in imp:
375
+ import_layer = 'application'
376
+ elif '/integration/' in imp or '/infrastructure/' in imp or '.integration.' in imp:
377
+ import_layer = 'integration'
378
+ elif '/presentation/' in imp or '.presentation.' in imp:
379
+ import_layer = 'presentation'
380
+
381
+ if import_layer and import_layer not in allowed_layers:
382
+ rel_path = module_file.relative_to(REPO_ROOT)
383
+ violations.append(
384
+ f"{rel_path}\\n"
385
+ f" Layer: {current_layer}\\n"
386
+ f" Import: {imp}\\n"
387
+ f" Target layer: {import_layer}\\n"
388
+ f" Violation: {current_layer} cannot import from {import_layer}"
389
+ )
390
+
391
+ if violations:
392
+ pytest.fail(
393
+ f"\\n\\nFound {len(violations)} boundary violations:\\n\\n" +
394
+ "\\n\\n".join(violations[:10]) +
395
+ (f"\\n\\n... and {len(violations) - 10} more" if len(violations) > 10 else "")
396
+ )