sourcecode 0.27.0__tar.gz → 0.29.0__tar.gz
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.
- {sourcecode-0.27.0 → sourcecode-0.29.0}/PKG-INFO +1 -1
- {sourcecode-0.27.0 → sourcecode-0.29.0}/pyproject.toml +2 -2
- {sourcecode-0.27.0 → sourcecode-0.29.0}/src/sourcecode/__init__.py +1 -1
- {sourcecode-0.27.0 → sourcecode-0.29.0}/src/sourcecode/architecture_analyzer.py +76 -11
- {sourcecode-0.27.0 → sourcecode-0.29.0}/src/sourcecode/cli.py +406 -25
- {sourcecode-0.27.0 → sourcecode-0.29.0}/src/sourcecode/confidence_analyzer.py +54 -2
- {sourcecode-0.27.0 → sourcecode-0.29.0}/src/sourcecode/detectors/heuristic.py +19 -1
- {sourcecode-0.27.0 → sourcecode-0.29.0}/src/sourcecode/detectors/nodejs.py +21 -11
- {sourcecode-0.27.0 → sourcecode-0.29.0}/src/sourcecode/detectors/project.py +7 -0
- {sourcecode-0.27.0 → sourcecode-0.29.0}/src/sourcecode/env_analyzer.py +25 -13
- {sourcecode-0.27.0 → sourcecode-0.29.0}/src/sourcecode/git_analyzer.py +57 -7
- {sourcecode-0.27.0 → sourcecode-0.29.0}/src/sourcecode/schema.py +26 -0
- {sourcecode-0.27.0 → sourcecode-0.29.0}/src/sourcecode/serializer.py +10 -2
- sourcecode-0.29.0/tests/test_cli.py +93 -0
- {sourcecode-0.27.0 → sourcecode-0.29.0}/tests/test_detectors_base.py +10 -9
- {sourcecode-0.27.0 → sourcecode-0.29.0}/tests/test_packaging.py +1 -1
- sourcecode-0.29.0/tests/test_pipeline_integrity.py +441 -0
- sourcecode-0.27.0/tests/test_cli.py +0 -58
- {sourcecode-0.27.0 → sourcecode-0.29.0}/.gitignore +0 -0
- {sourcecode-0.27.0 → sourcecode-0.29.0}/.ruff.toml +0 -0
- {sourcecode-0.27.0 → sourcecode-0.29.0}/CONTRIBUTING.md +0 -0
- {sourcecode-0.27.0 → sourcecode-0.29.0}/LICENSE +0 -0
- {sourcecode-0.27.0 → sourcecode-0.29.0}/README.md +0 -0
- {sourcecode-0.27.0 → sourcecode-0.29.0}/SECURITY.md +0 -0
- {sourcecode-0.27.0 → sourcecode-0.29.0}/docs/privacy.md +0 -0
- {sourcecode-0.27.0 → sourcecode-0.29.0}/docs/schema.md +0 -0
- {sourcecode-0.27.0 → sourcecode-0.29.0}/raw +0 -0
- {sourcecode-0.27.0 → sourcecode-0.29.0}/src/sourcecode/architecture_summary.py +0 -0
- {sourcecode-0.27.0 → sourcecode-0.29.0}/src/sourcecode/classifier.py +0 -0
- {sourcecode-0.27.0 → sourcecode-0.29.0}/src/sourcecode/code_notes_analyzer.py +0 -0
- {sourcecode-0.27.0 → sourcecode-0.29.0}/src/sourcecode/context_summarizer.py +0 -0
- {sourcecode-0.27.0 → sourcecode-0.29.0}/src/sourcecode/coverage_parser.py +0 -0
- {sourcecode-0.27.0 → sourcecode-0.29.0}/src/sourcecode/dependency_analyzer.py +0 -0
- {sourcecode-0.27.0 → sourcecode-0.29.0}/src/sourcecode/detectors/__init__.py +0 -0
- {sourcecode-0.27.0 → sourcecode-0.29.0}/src/sourcecode/detectors/base.py +0 -0
- {sourcecode-0.27.0 → sourcecode-0.29.0}/src/sourcecode/detectors/csproj_parser.py +0 -0
- {sourcecode-0.27.0 → sourcecode-0.29.0}/src/sourcecode/detectors/dart.py +0 -0
- {sourcecode-0.27.0 → sourcecode-0.29.0}/src/sourcecode/detectors/dotnet.py +0 -0
- {sourcecode-0.27.0 → sourcecode-0.29.0}/src/sourcecode/detectors/elixir.py +0 -0
- {sourcecode-0.27.0 → sourcecode-0.29.0}/src/sourcecode/detectors/go.py +0 -0
- {sourcecode-0.27.0 → sourcecode-0.29.0}/src/sourcecode/detectors/hybrid.py +0 -0
- {sourcecode-0.27.0 → sourcecode-0.29.0}/src/sourcecode/detectors/java.py +0 -0
- {sourcecode-0.27.0 → sourcecode-0.29.0}/src/sourcecode/detectors/jvm_ext.py +0 -0
- {sourcecode-0.27.0 → sourcecode-0.29.0}/src/sourcecode/detectors/parsers.py +0 -0
- {sourcecode-0.27.0 → sourcecode-0.29.0}/src/sourcecode/detectors/php.py +0 -0
- {sourcecode-0.27.0 → sourcecode-0.29.0}/src/sourcecode/detectors/python.py +0 -0
- {sourcecode-0.27.0 → sourcecode-0.29.0}/src/sourcecode/detectors/ruby.py +0 -0
- {sourcecode-0.27.0 → sourcecode-0.29.0}/src/sourcecode/detectors/rust.py +0 -0
- {sourcecode-0.27.0 → sourcecode-0.29.0}/src/sourcecode/detectors/systems.py +0 -0
- {sourcecode-0.27.0 → sourcecode-0.29.0}/src/sourcecode/detectors/terraform.py +0 -0
- {sourcecode-0.27.0 → sourcecode-0.29.0}/src/sourcecode/detectors/tooling.py +0 -0
- {sourcecode-0.27.0 → sourcecode-0.29.0}/src/sourcecode/doc_analyzer.py +0 -0
- {sourcecode-0.27.0 → sourcecode-0.29.0}/src/sourcecode/graph_analyzer.py +0 -0
- {sourcecode-0.27.0 → sourcecode-0.29.0}/src/sourcecode/metrics_analyzer.py +0 -0
- {sourcecode-0.27.0 → sourcecode-0.29.0}/src/sourcecode/prepare_context.py +0 -0
- {sourcecode-0.27.0 → sourcecode-0.29.0}/src/sourcecode/redactor.py +0 -0
- {sourcecode-0.27.0 → sourcecode-0.29.0}/src/sourcecode/relevance_scorer.py +0 -0
- {sourcecode-0.27.0 → sourcecode-0.29.0}/src/sourcecode/runtime_classifier.py +0 -0
- {sourcecode-0.27.0 → sourcecode-0.29.0}/src/sourcecode/scanner.py +0 -0
- {sourcecode-0.27.0 → sourcecode-0.29.0}/src/sourcecode/semantic_analyzer.py +0 -0
- {sourcecode-0.27.0 → sourcecode-0.29.0}/src/sourcecode/summarizer.py +0 -0
- {sourcecode-0.27.0 → sourcecode-0.29.0}/src/sourcecode/telemetry/__init__.py +0 -0
- {sourcecode-0.27.0 → sourcecode-0.29.0}/src/sourcecode/telemetry/config.py +0 -0
- {sourcecode-0.27.0 → sourcecode-0.29.0}/src/sourcecode/telemetry/consent.py +0 -0
- {sourcecode-0.27.0 → sourcecode-0.29.0}/src/sourcecode/telemetry/events.py +0 -0
- {sourcecode-0.27.0 → sourcecode-0.29.0}/src/sourcecode/telemetry/filters.py +0 -0
- {sourcecode-0.27.0 → sourcecode-0.29.0}/src/sourcecode/telemetry/transport.py +0 -0
- {sourcecode-0.27.0 → sourcecode-0.29.0}/src/sourcecode/tree_utils.py +0 -0
- {sourcecode-0.27.0 → sourcecode-0.29.0}/src/sourcecode/workspace.py +0 -0
- {sourcecode-0.27.0 → sourcecode-0.29.0}/tests/__init__.py +0 -0
- {sourcecode-0.27.0 → sourcecode-0.29.0}/tests/conftest.py +0 -0
- {sourcecode-0.27.0 → sourcecode-0.29.0}/tests/fixtures/coverage.xml +0 -0
- {sourcecode-0.27.0 → sourcecode-0.29.0}/tests/fixtures/fastapi_app/pyproject.toml +0 -0
- {sourcecode-0.27.0 → sourcecode-0.29.0}/tests/fixtures/fastapi_app/src/main.py +0 -0
- {sourcecode-0.27.0 → sourcecode-0.29.0}/tests/fixtures/go_service/cmd/api/main.go +0 -0
- {sourcecode-0.27.0 → sourcecode-0.29.0}/tests/fixtures/go_service/go.mod +0 -0
- {sourcecode-0.27.0 → sourcecode-0.29.0}/tests/fixtures/jacoco.xml +0 -0
- {sourcecode-0.27.0 → sourcecode-0.29.0}/tests/fixtures/lcov.info +0 -0
- {sourcecode-0.27.0 → sourcecode-0.29.0}/tests/fixtures/nextjs_app/app/page.tsx +0 -0
- {sourcecode-0.27.0 → sourcecode-0.29.0}/tests/fixtures/nextjs_app/package.json +0 -0
- {sourcecode-0.27.0 → sourcecode-0.29.0}/tests/fixtures/nextjs_app/pnpm-lock.yaml +0 -0
- {sourcecode-0.27.0 → sourcecode-0.29.0}/tests/fixtures/pnpm_monorepo/apps/web/app/page.tsx +0 -0
- {sourcecode-0.27.0 → sourcecode-0.29.0}/tests/fixtures/pnpm_monorepo/apps/web/package.json +0 -0
- {sourcecode-0.27.0 → sourcecode-0.29.0}/tests/fixtures/pnpm_monorepo/packages/api/main.py +0 -0
- {sourcecode-0.27.0 → sourcecode-0.29.0}/tests/fixtures/pnpm_monorepo/packages/api/pyproject.toml +0 -0
- {sourcecode-0.27.0 → sourcecode-0.29.0}/tests/fixtures/pnpm_monorepo/pnpm-workspace.yaml +0 -0
- {sourcecode-0.27.0 → sourcecode-0.29.0}/tests/test_architecture_analyzer.py +0 -0
- {sourcecode-0.27.0 → sourcecode-0.29.0}/tests/test_architecture_summary.py +0 -0
- {sourcecode-0.27.0 → sourcecode-0.29.0}/tests/test_classifier.py +0 -0
- {sourcecode-0.27.0 → sourcecode-0.29.0}/tests/test_code_notes_analyzer.py +0 -0
- {sourcecode-0.27.0 → sourcecode-0.29.0}/tests/test_coverage_parser.py +0 -0
- {sourcecode-0.27.0 → sourcecode-0.29.0}/tests/test_cross_consistency.py +0 -0
- {sourcecode-0.27.0 → sourcecode-0.29.0}/tests/test_dependency_analyzer_node_python.py +0 -0
- {sourcecode-0.27.0 → sourcecode-0.29.0}/tests/test_dependency_analyzer_polyglot.py +0 -0
- {sourcecode-0.27.0 → sourcecode-0.29.0}/tests/test_dependency_schema.py +0 -0
- {sourcecode-0.27.0 → sourcecode-0.29.0}/tests/test_detector_dotnet.py +0 -0
- {sourcecode-0.27.0 → sourcecode-0.29.0}/tests/test_detector_go_rust_java.py +0 -0
- {sourcecode-0.27.0 → sourcecode-0.29.0}/tests/test_detector_nodejs.py +0 -0
- {sourcecode-0.27.0 → sourcecode-0.29.0}/tests/test_detector_php_ruby_dart.py +0 -0
- {sourcecode-0.27.0 → sourcecode-0.29.0}/tests/test_detector_python.py +0 -0
- {sourcecode-0.27.0 → sourcecode-0.29.0}/tests/test_detector_universal_managed.py +0 -0
- {sourcecode-0.27.0 → sourcecode-0.29.0}/tests/test_detector_universal_systems.py +0 -0
- {sourcecode-0.27.0 → sourcecode-0.29.0}/tests/test_doc_analyzer_jsdom.py +0 -0
- {sourcecode-0.27.0 → sourcecode-0.29.0}/tests/test_doc_analyzer_python.py +0 -0
- {sourcecode-0.27.0 → sourcecode-0.29.0}/tests/test_graph_analyzer_polyglot.py +0 -0
- {sourcecode-0.27.0 → sourcecode-0.29.0}/tests/test_graph_analyzer_python_node.py +0 -0
- {sourcecode-0.27.0 → sourcecode-0.29.0}/tests/test_graph_schema.py +0 -0
- {sourcecode-0.27.0 → sourcecode-0.29.0}/tests/test_hybrid_inference.py +0 -0
- {sourcecode-0.27.0 → sourcecode-0.29.0}/tests/test_integration.py +0 -0
- {sourcecode-0.27.0 → sourcecode-0.29.0}/tests/test_integration_dependencies.py +0 -0
- {sourcecode-0.27.0 → sourcecode-0.29.0}/tests/test_integration_detection.py +0 -0
- {sourcecode-0.27.0 → sourcecode-0.29.0}/tests/test_integration_docs.py +0 -0
- {sourcecode-0.27.0 → sourcecode-0.29.0}/tests/test_integration_graph_modules.py +0 -0
- {sourcecode-0.27.0 → sourcecode-0.29.0}/tests/test_integration_lqn.py +0 -0
- {sourcecode-0.27.0 → sourcecode-0.29.0}/tests/test_integration_metrics.py +0 -0
- {sourcecode-0.27.0 → sourcecode-0.29.0}/tests/test_integration_multistack.py +0 -0
- {sourcecode-0.27.0 → sourcecode-0.29.0}/tests/test_integration_semantics.py +0 -0
- {sourcecode-0.27.0 → sourcecode-0.29.0}/tests/test_integration_universal.py +0 -0
- {sourcecode-0.27.0 → sourcecode-0.29.0}/tests/test_metrics_analyzer.py +0 -0
- {sourcecode-0.27.0 → sourcecode-0.29.0}/tests/test_phase1_improvements.py +0 -0
- {sourcecode-0.27.0 → sourcecode-0.29.0}/tests/test_real_projects.py +0 -0
- {sourcecode-0.27.0 → sourcecode-0.29.0}/tests/test_redactor.py +0 -0
- {sourcecode-0.27.0 → sourcecode-0.29.0}/tests/test_scanner.py +0 -0
- {sourcecode-0.27.0 → sourcecode-0.29.0}/tests/test_schema.py +0 -0
- {sourcecode-0.27.0 → sourcecode-0.29.0}/tests/test_schema_normalization.py +0 -0
- {sourcecode-0.27.0 → sourcecode-0.29.0}/tests/test_semantic_analyzer_node.py +0 -0
- {sourcecode-0.27.0 → sourcecode-0.29.0}/tests/test_semantic_analyzer_python.py +0 -0
- {sourcecode-0.27.0 → sourcecode-0.29.0}/tests/test_semantic_import_resolution.py +0 -0
- {sourcecode-0.27.0 → sourcecode-0.29.0}/tests/test_semantic_schema.py +0 -0
- {sourcecode-0.27.0 → sourcecode-0.29.0}/tests/test_signal_hierarchy.py +0 -0
- {sourcecode-0.27.0 → sourcecode-0.29.0}/tests/test_summarizer.py +0 -0
- {sourcecode-0.27.0 → sourcecode-0.29.0}/tests/test_telemetry.py +0 -0
- {sourcecode-0.27.0 → sourcecode-0.29.0}/tests/test_workspace_analyzer.py +0 -0
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "sourcecode"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.29.0"
|
|
8
8
|
description = "Deterministic codebase context for AI coding agents"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.9"
|
|
@@ -32,7 +32,7 @@ dependencies = [
|
|
|
32
32
|
]
|
|
33
33
|
|
|
34
34
|
[project.scripts]
|
|
35
|
-
sourcecode = "sourcecode.cli:
|
|
35
|
+
sourcecode = "sourcecode.cli:main_entry"
|
|
36
36
|
|
|
37
37
|
[project.optional-dependencies]
|
|
38
38
|
dev = [
|
|
@@ -12,6 +12,10 @@ from sourcecode.schema import (
|
|
|
12
12
|
SourceMap,
|
|
13
13
|
)
|
|
14
14
|
|
|
15
|
+
_WORKSPACE_CONFIG_FILES: frozenset[str] = frozenset({
|
|
16
|
+
"turbo.json", "nx.json", "pnpm-workspace.yaml", "lerna.json", "rush.json",
|
|
17
|
+
})
|
|
18
|
+
|
|
15
19
|
_TOOLING_PREFIXES = (
|
|
16
20
|
".claude/",
|
|
17
21
|
".vscode/",
|
|
@@ -34,6 +38,18 @@ _CODE_EXTENSIONS = {
|
|
|
34
38
|
_GENERIC_NAMES = {"utils", "helpers", "common", "shared", "misc", "core", "root", ""}
|
|
35
39
|
|
|
36
40
|
_TEST_DIRS: frozenset[str] = frozenset({"tests", "test", "spec", "specs", "__tests__", "e2e"})
|
|
41
|
+
_BENCHMARK_DIRS: frozenset[str] = frozenset({
|
|
42
|
+
"benchmark", "benchmarks", "bench",
|
|
43
|
+
"example", "examples",
|
|
44
|
+
"demo", "demos",
|
|
45
|
+
"playground", "playgrounds",
|
|
46
|
+
"fixture", "fixtures",
|
|
47
|
+
"sandbox",
|
|
48
|
+
})
|
|
49
|
+
_DOCS_DIRS: frozenset[str] = frozenset({"docs", "doc", "documentation", "wiki"})
|
|
50
|
+
_TOOLING_DIRS: frozenset[str] = frozenset({"scripts", "script", "tools", "tool", "ci"})
|
|
51
|
+
# All dirs that are not part of the runtime source architecture
|
|
52
|
+
_NON_SOURCE_DIRS: frozenset[str] = _TEST_DIRS | _BENCHMARK_DIRS | _DOCS_DIRS | _TOOLING_DIRS
|
|
37
53
|
|
|
38
54
|
# Exact file stems that signal a specific architectural layer
|
|
39
55
|
_LAYER_STEM_EXACT: dict[str, str] = {
|
|
@@ -177,15 +193,35 @@ class ArchitectureAnalyzer:
|
|
|
177
193
|
elif pattern == "unknown":
|
|
178
194
|
limitations.append("Patron de capas no reconocido: estructura de directorios sin senales claras")
|
|
179
195
|
|
|
196
|
+
# Step 3b: monorepo override — workspace config is hard evidence
|
|
197
|
+
if self._has_workspace_config(sm.file_paths) and pattern not in (
|
|
198
|
+
"monorepo", "cqrs", "clean", "onion", "hexagonal"
|
|
199
|
+
):
|
|
200
|
+
mono_layers = self._detect_monorepo_packages(filtered)
|
|
201
|
+
if mono_layers or pattern in (None, "unknown", "flat", "modular", "layered"):
|
|
202
|
+
pattern = "monorepo"
|
|
203
|
+
layers = mono_layers
|
|
204
|
+
limitations.append(
|
|
205
|
+
"Workspace config detectado — arquitectura refleja topologia de paquetes"
|
|
206
|
+
)
|
|
207
|
+
|
|
180
208
|
# Step 4: bounded context inference
|
|
181
209
|
bounded_contexts = self._infer_bounded_contexts(domains, graph)
|
|
182
210
|
|
|
183
|
-
# Overall confidence
|
|
211
|
+
# Overall confidence — based on domain quality, not raw count
|
|
184
212
|
confidence: Literal["high", "medium", "low"]
|
|
213
|
+
strong_domains = [d for d in domains if d.confidence in ("high", "medium")]
|
|
214
|
+
all_layers_weak = layers and all(l.confidence == "low" for l in layers)
|
|
185
215
|
if pattern not in (None, "unknown", "flat"):
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
216
|
+
if all_layers_weak:
|
|
217
|
+
# Layers came from file-naming heuristic only, not directory structure
|
|
218
|
+
confidence = "medium"
|
|
219
|
+
limitations.append(
|
|
220
|
+
"Patron inferido de nombres de archivo — sin estructura de directorios confirmatoria"
|
|
221
|
+
)
|
|
222
|
+
else:
|
|
223
|
+
confidence = "high" if len(strong_domains) >= 3 else "medium"
|
|
224
|
+
elif len(strong_domains) >= 1:
|
|
189
225
|
confidence = "medium"
|
|
190
226
|
else:
|
|
191
227
|
confidence = "low"
|
|
@@ -217,6 +253,10 @@ class ArchitectureAnalyzer:
|
|
|
217
253
|
norm = p.replace("\\", "/")
|
|
218
254
|
if self._is_tooling(norm):
|
|
219
255
|
continue
|
|
256
|
+
# Exclude non-source dirs at every path segment (benchmarks, docs, tests, scripts…)
|
|
257
|
+
parts = norm.split("/")
|
|
258
|
+
if any(part.lower() in _NON_SOURCE_DIRS for part in parts[:-1]):
|
|
259
|
+
continue
|
|
220
260
|
ext = Path(norm).suffix.lower()
|
|
221
261
|
if ext not in _CODE_EXTENSIONS:
|
|
222
262
|
continue
|
|
@@ -250,6 +290,8 @@ class ArchitectureAnalyzer:
|
|
|
250
290
|
for name, files in groups.items():
|
|
251
291
|
if len(files) < 2:
|
|
252
292
|
continue
|
|
293
|
+
if name.lower() in _NON_SOURCE_DIRS:
|
|
294
|
+
continue
|
|
253
295
|
role = DOMAIN_ROLES.get(name, "")
|
|
254
296
|
domain_confidence: Literal["high", "medium", "low"]
|
|
255
297
|
if name in DOMAIN_ROLES:
|
|
@@ -262,10 +304,10 @@ class ArchitectureAnalyzer:
|
|
|
262
304
|
return domains
|
|
263
305
|
|
|
264
306
|
def _detect_layers(self, paths: list[str]) -> tuple[str, list[ArchitectureLayer]]:
|
|
265
|
-
# Exclude
|
|
307
|
+
# Exclude non-source paths (tests, benchmarks, docs, tooling) from layer scoring
|
|
266
308
|
source_paths = [
|
|
267
309
|
p for p in paths
|
|
268
|
-
if not any(part.lower() in
|
|
310
|
+
if not any(part.lower() in _NON_SOURCE_DIRS for part in p.replace("\\", "/").split("/"))
|
|
269
311
|
]
|
|
270
312
|
if not source_paths:
|
|
271
313
|
return "unknown", []
|
|
@@ -360,7 +402,7 @@ class ArchitectureAnalyzer:
|
|
|
360
402
|
parts = p.replace("\\", "/").split("/")
|
|
361
403
|
if len(parts) >= 2 and parts[-1].lower() in _ENTRY_FILES:
|
|
362
404
|
top = parts[0]
|
|
363
|
-
if top.lower() not in _SRC_TRANSPARENT and top.lower() not in
|
|
405
|
+
if top.lower() not in _SRC_TRANSPARENT and top.lower() not in _NON_SOURCE_DIRS:
|
|
364
406
|
entry_dirs.setdefault(top, []).append(p)
|
|
365
407
|
if len(entry_dirs) >= 4:
|
|
366
408
|
return "microservices", [
|
|
@@ -394,7 +436,7 @@ class ArchitectureAnalyzer:
|
|
|
394
436
|
non_empty = {k: v for k, v in layer_files.items() if v}
|
|
395
437
|
if len(non_empty) >= 2:
|
|
396
438
|
return "layered", [
|
|
397
|
-
ArchitectureLayer(name=k, pattern="layered", files=v, confidence="
|
|
439
|
+
ArchitectureLayer(name=k, pattern="layered", files=v, confidence="low")
|
|
398
440
|
for k, v in non_empty.items()
|
|
399
441
|
]
|
|
400
442
|
return None
|
|
@@ -412,19 +454,42 @@ class ArchitectureAnalyzer:
|
|
|
412
454
|
parts = p.replace("\\", "/").split("/")
|
|
413
455
|
for part in parts[:-1]:
|
|
414
456
|
if (part not in _SRC_TRANSPARENT
|
|
415
|
-
and part.lower() not in
|
|
457
|
+
and part.lower() not in _NON_SOURCE_DIRS
|
|
416
458
|
and part.lower() not in _GENERIC_NAMES):
|
|
417
459
|
module_files.setdefault(part, []).append(p)
|
|
418
460
|
break
|
|
419
461
|
|
|
420
|
-
meaningful = {k: v for k, v in module_files.items() if len(v) >=
|
|
462
|
+
meaningful = {k: v for k, v in module_files.items() if len(v) >= 3}
|
|
421
463
|
if len(meaningful) >= 2:
|
|
422
464
|
return "modular", [
|
|
423
|
-
ArchitectureLayer(name=k, pattern="modular", files=v, confidence="
|
|
465
|
+
ArchitectureLayer(name=k, pattern="modular", files=v, confidence="low")
|
|
424
466
|
for k, v in meaningful.items()
|
|
425
467
|
]
|
|
426
468
|
return None
|
|
427
469
|
|
|
470
|
+
def _has_workspace_config(self, file_paths: list[str]) -> bool:
|
|
471
|
+
for path in file_paths:
|
|
472
|
+
parts = path.replace("\\", "/").split("/")
|
|
473
|
+
if len(parts) == 1 and parts[0] in _WORKSPACE_CONFIG_FILES:
|
|
474
|
+
return True
|
|
475
|
+
return False
|
|
476
|
+
|
|
477
|
+
def _detect_monorepo_packages(self, paths: list[str]) -> list[ArchitectureLayer]:
|
|
478
|
+
"""Find workspace packages (packages/*, apps/*, libs/*) in a monorepo."""
|
|
479
|
+
_WORKSPACE_ROOTS = {"packages", "apps", "libs", "applications"}
|
|
480
|
+
groups: dict[str, list[str]] = {}
|
|
481
|
+
for p in paths:
|
|
482
|
+
parts = p.replace("\\", "/").split("/")
|
|
483
|
+
if len(parts) >= 2 and parts[0].lower() in _WORKSPACE_ROOTS:
|
|
484
|
+
key = f"{parts[0]}/{parts[1]}"
|
|
485
|
+
groups.setdefault(key, []).append(p)
|
|
486
|
+
result = [
|
|
487
|
+
ArchitectureLayer(name=k, pattern="monorepo", files=v, confidence="medium")
|
|
488
|
+
for k, v in groups.items()
|
|
489
|
+
if len(v) >= 2
|
|
490
|
+
]
|
|
491
|
+
return result[:16]
|
|
492
|
+
|
|
428
493
|
def _infer_bounded_contexts(
|
|
429
494
|
self,
|
|
430
495
|
domains: list[ArchitectureDomain],
|