sourcecode 2.5.5__tar.gz → 2.5.6__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-2.5.5 → sourcecode-2.5.6}/CHANGELOG.md +60 -0
- {sourcecode-2.5.5 → sourcecode-2.5.6}/PKG-INFO +1 -1
- {sourcecode-2.5.5 → sourcecode-2.5.6}/pyproject.toml +1 -1
- {sourcecode-2.5.5 → sourcecode-2.5.6}/src/sourcecode/__init__.py +1 -1
- {sourcecode-2.5.5 → sourcecode-2.5.6}/src/sourcecode/architecture_analyzer.py +46 -8
- {sourcecode-2.5.5 → sourcecode-2.5.6}/src/sourcecode/reconciliation.py +114 -3
- {sourcecode-2.5.5 → sourcecode-2.5.6}/src/sourcecode/summarizer.py +3 -1
- {sourcecode-2.5.5 → sourcecode-2.5.6}/.github/workflows/build-windows.yml +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.6}/.gitignore +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.6}/.ruff.toml +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.6}/CONTRIBUTING.md +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.6}/LICENSE +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.6}/README.md +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.6}/SECURITY.md +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.6}/raw +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.6}/scripts/compare_integration_engines.py +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.6}/scripts/customer_smoke_test.sh +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.6}/scripts/generate_jdk_exports.py +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.6}/src/sourcecode/adaptive_scanner.py +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.6}/src/sourcecode/archetype.py +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.6}/src/sourcecode/architecture_summary.py +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.6}/src/sourcecode/ast_extractor.py +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.6}/src/sourcecode/cache.py +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.6}/src/sourcecode/call_surface.py +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.6}/src/sourcecode/caller_metrics.py +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.6}/src/sourcecode/canonical_ir.py +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.6}/src/sourcecode/cir_graphs.py +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.6}/src/sourcecode/classifier.py +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.6}/src/sourcecode/cli.py +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.6}/src/sourcecode/code_notes_analyzer.py +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.6}/src/sourcecode/confidence_analyzer.py +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.6}/src/sourcecode/context_cache.py +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.6}/src/sourcecode/context_graph.py +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.6}/src/sourcecode/context_scorer.py +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.6}/src/sourcecode/context_summarizer.py +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.6}/src/sourcecode/contract_model.py +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.6}/src/sourcecode/contract_pipeline.py +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.6}/src/sourcecode/coverage_parser.py +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.6}/src/sourcecode/dependency_analyzer.py +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.6}/src/sourcecode/detectors/__init__.py +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.6}/src/sourcecode/detectors/base.py +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.6}/src/sourcecode/detectors/csproj_parser.py +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.6}/src/sourcecode/detectors/dart.py +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.6}/src/sourcecode/detectors/dotnet.py +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.6}/src/sourcecode/detectors/elixir.py +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.6}/src/sourcecode/detectors/go.py +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.6}/src/sourcecode/detectors/heuristic.py +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.6}/src/sourcecode/detectors/hybrid.py +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.6}/src/sourcecode/detectors/java.py +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.6}/src/sourcecode/detectors/jvm_ext.py +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.6}/src/sourcecode/detectors/nodejs.py +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.6}/src/sourcecode/detectors/parsers.py +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.6}/src/sourcecode/detectors/php.py +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.6}/src/sourcecode/detectors/project.py +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.6}/src/sourcecode/detectors/python.py +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.6}/src/sourcecode/detectors/ruby.py +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.6}/src/sourcecode/detectors/rust.py +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.6}/src/sourcecode/detectors/systems.py +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.6}/src/sourcecode/detectors/terraform.py +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.6}/src/sourcecode/detectors/tooling.py +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.6}/src/sourcecode/doc_analyzer.py +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.6}/src/sourcecode/dynamic_argument_surface.py +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.6}/src/sourcecode/endpoint_literals.py +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.6}/src/sourcecode/endpoint_metrics.py +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.6}/src/sourcecode/entrypoint_classifier.py +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.6}/src/sourcecode/env_analyzer.py +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.6}/src/sourcecode/error_schema.py +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.6}/src/sourcecode/evidence_provider.py +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.6}/src/sourcecode/explain.py +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.6}/src/sourcecode/file_chunker.py +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.6}/src/sourcecode/file_classifier.py +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.6}/src/sourcecode/format_contract.py +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.6}/src/sourcecode/fqn_utils.py +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.6}/src/sourcecode/git_analyzer.py +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.6}/src/sourcecode/graph_analyzer.py +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.6}/src/sourcecode/graph_evidence.py +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.6}/src/sourcecode/hibernate_strat.py +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.6}/src/sourcecode/jdk_exports.py +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.6}/src/sourcecode/license.py +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.6}/src/sourcecode/mcp/__init__.py +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.6}/src/sourcecode/mcp/onboarding/__init__.py +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.6}/src/sourcecode/mcp/onboarding/applier.py +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.6}/src/sourcecode/mcp/onboarding/backup.py +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.6}/src/sourcecode/mcp/onboarding/detector.py +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.6}/src/sourcecode/mcp/onboarding/planner.py +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.6}/src/sourcecode/mcp/orchestrator.py +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.6}/src/sourcecode/mcp/registry.py +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.6}/src/sourcecode/mcp/runner.py +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.6}/src/sourcecode/mcp/server.py +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.6}/src/sourcecode/mcp_nudge.py +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.6}/src/sourcecode/metrics_analyzer.py +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.6}/src/sourcecode/migrate_check.py +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.6}/src/sourcecode/openapi_surface.py +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.6}/src/sourcecode/output_budget.py +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.6}/src/sourcecode/path_filters.py +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.6}/src/sourcecode/pr_comment_renderer.py +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.6}/src/sourcecode/pr_impact.py +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.6}/src/sourcecode/prepare_context.py +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.6}/src/sourcecode/progress.py +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.6}/src/sourcecode/ranking_engine.py +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.6}/src/sourcecode/redactor.py +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.6}/src/sourcecode/relevance_scorer.py +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.6}/src/sourcecode/rename_refactor.py +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.6}/src/sourcecode/repo_classifier.py +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.6}/src/sourcecode/repository_ir.py +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.6}/src/sourcecode/retrieval/__init__.py +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.6}/src/sourcecode/retrieval/context.py +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.6}/src/sourcecode/retrieval/errors.py +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.6}/src/sourcecode/retrieval/executor.py +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.6}/src/sourcecode/retrieval/planner.py +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.6}/src/sourcecode/retrieval/query.py +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.6}/src/sourcecode/retrieval/request.py +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.6}/src/sourcecode/retrieval/resolution.py +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.6}/src/sourcecode/retrieval/result.py +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.6}/src/sourcecode/retrieval/retriever.py +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.6}/src/sourcecode/retrieval/runtime.py +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.6}/src/sourcecode/retrieval/steps.py +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.6}/src/sourcecode/retrieval/steps_endpoint.py +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.6}/src/sourcecode/retrieval/steps_graph.py +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.6}/src/sourcecode/retrieval/steps_impact.py +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.6}/src/sourcecode/retrieval/steps_intf.py +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.6}/src/sourcecode/retrieval/steps_struct.py +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.6}/src/sourcecode/retrieval/steps_txsec.py +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.6}/src/sourcecode/ris.py +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.6}/src/sourcecode/runtime_classifier.py +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.6}/src/sourcecode/scanner.py +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.6}/src/sourcecode/schema.py +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.6}/src/sourcecode/security_config.py +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.6}/src/sourcecode/security_posture.py +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.6}/src/sourcecode/semantic_analyzer.py +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.6}/src/sourcecode/semantic_impact_engine.py +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.6}/src/sourcecode/semantic_integration_engine.py +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.6}/src/sourcecode/semantic_services.py +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.6}/src/sourcecode/serializer.py +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.6}/src/sourcecode/spring_event_topology.py +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.6}/src/sourcecode/spring_findings.py +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.6}/src/sourcecode/spring_impact.py +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.6}/src/sourcecode/spring_model.py +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.6}/src/sourcecode/spring_security_audit.py +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.6}/src/sourcecode/spring_semantic.py +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.6}/src/sourcecode/spring_tx_analyzer.py +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.6}/src/sourcecode/telemetry/__init__.py +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.6}/src/sourcecode/telemetry/config.py +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.6}/src/sourcecode/telemetry/consent.py +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.6}/src/sourcecode/telemetry/events.py +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.6}/src/sourcecode/telemetry/filters.py +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.6}/src/sourcecode/telemetry/transport.py +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.6}/src/sourcecode/tree_utils.py +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.6}/src/sourcecode/type_usage_surface.py +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.6}/src/sourcecode/validation_inference.py +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.6}/src/sourcecode/validation_surface.py +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.6}/src/sourcecode/version_check.py +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.6}/src/sourcecode/workspace.py +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.6}/supabase/functions/README.md +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.6}/supabase/functions/get-license/index.ts +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.6}/supabase/functions/lemonsqueezy-webhook/index.ts +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.6}/supabase/functions/telemetry/index.ts +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.6}/supabase/sql/license_event_ordering.sql +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.6}/supabase/sql/licensing_schema.sql +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.6}/supabase/sql/telemetry_events.sql +0 -0
|
@@ -1,5 +1,65 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [2.5.6] — 2026-07-17
|
|
4
|
+
|
|
5
|
+
**A layer must be attested by code.** The architecture fallback chain reported
|
|
6
|
+
directories as architecture; measured on the field-test repos, every fabricated
|
|
7
|
+
claim rested on files that are not code.
|
|
8
|
+
|
|
9
|
+
### Fixed
|
|
10
|
+
|
|
11
|
+
- **Build and asset directories were reported as modules (P1-D-REMAINING)**
|
|
12
|
+
(`reconciliation.py`, `architecture_analyzer.py`, `summarizer.py`):
|
|
13
|
+
|
|
14
|
+
| repo | claimed | what it rested on |
|
|
15
|
+
|---|---|---|
|
|
16
|
+
| ofbiz | *"modular: docker, framework, runtime, gradle"* | shell scripts, a wrapper jar, a `.gitignore` |
|
|
17
|
+
| neo4j | *"modular: packaging, annotations, community"* | `packaging/` is a build directory |
|
|
18
|
+
| eureka | *"modular: …, images, …"* | five PNGs |
|
|
19
|
+
| openmrs | *"modular: …, webapp, monitoring"* | GIFs and yaml |
|
|
20
|
+
| petclinic | *"Layered Architecture with orchestration, data layers"* | ONE directory entry and a `settings.gradle` — with 47 Java files visible and ignored |
|
|
21
|
+
| jobrunr | *"Fullstack with frontend, backend layers"* | a JVM library; the "frontend" is `dashboard/ui/model/`, **16 Java files** |
|
|
22
|
+
|
|
23
|
+
Two new Evidence Reconciliation rules, mirror images of each other and both
|
|
24
|
+
applied to the duplicated layer tables (architecture prose + headline):
|
|
25
|
+
|
|
26
|
+
- **`layer_code_attestation`** — a claimed MODULE or logical code layer that
|
|
27
|
+
holds no code of the repo's runtime is not one. Stricter sibling of
|
|
28
|
+
`layer_language_coherence`, which deliberately ignores runtime-less layers
|
|
29
|
+
so that a real Spring-MVC `templates/` view survives: a view may be
|
|
30
|
+
templates, a module may not.
|
|
31
|
+
- **`composition_runtime_split`** — a pattern that IS the split must show it.
|
|
32
|
+
`fullstack` names a frontend TIER and a backend TIER; if every layer runs on
|
|
33
|
+
one runtime, the layer names are a directory-name coincidence. Not applied
|
|
34
|
+
to `monorepo`/`microservices` — an all-TypeScript monorepo, or same-runtime
|
|
35
|
+
services, are perfectly real.
|
|
36
|
+
|
|
37
|
+
petclinic's stem-based layering is Python-shaped (`cli.py`, `*_analyzer.py`,
|
|
38
|
+
`config.py`) and matches almost nothing in Java, which is why two stray hits
|
|
39
|
+
could mint a whole "Layered Architecture".
|
|
40
|
+
|
|
41
|
+
- **Module names that never name a module** (`architecture_analyzer.py`): the
|
|
42
|
+
reverse-DNS package roots every JVM repo starts its namespace with
|
|
43
|
+
(`org`/`com`/`io`/`net` — jobrunr reported a module called *"org"*) and
|
|
44
|
+
multi-release source sets (`src/main/java17` minted *"java17"*; now as
|
|
45
|
+
transparent as `java/`). Same class as the `core` already excluded.
|
|
46
|
+
|
|
47
|
+
Validation across 10 field-test repos through the real CLI: ofbiz →
|
|
48
|
+
*"framework, applications"* (its two real modules); neo4j → *"annotations,
|
|
49
|
+
community"*; eureka → its eight real `eureka-*` modules; openmrs → *"web,
|
|
50
|
+
test-suite, api, liquibase"*; jobrunr → *"modular: jobrunr, language-support,
|
|
51
|
+
framework-support"* (its real top-level directories); **petclinic emits no
|
|
52
|
+
architecture claim at all** — the fabricated line is gone while every true
|
|
53
|
+
statement survives (*"Java server-side mvc web app using Spring Boot,
|
|
54
|
+
Thymeleaf"*, frameworks, bootstrap annotation). Broadleaf, alfresco, keycloak
|
|
55
|
+
and jenkins: byte-identical prose.
|
|
56
|
+
|
|
57
|
+
Investigated and refuted along the way: these claims are NOT built on a
|
|
58
|
+
truncated file list — the analyzer sees 100% of petclinic's Java and 99.4% of
|
|
59
|
+
ofbiz's.
|
|
60
|
+
|
|
61
|
+
11 new tests; suite 3448 passed.
|
|
62
|
+
|
|
3
63
|
## [2.5.5] — 2026-07-16
|
|
4
64
|
|
|
5
65
|
**An architecture claim must belong to one program.** Third Evidence
|
|
@@ -4,7 +4,11 @@ import re
|
|
|
4
4
|
from pathlib import Path
|
|
5
5
|
from typing import Literal, Optional
|
|
6
6
|
|
|
7
|
-
from sourcecode.reconciliation import
|
|
7
|
+
from sourcecode.reconciliation import (
|
|
8
|
+
incoherent_layers,
|
|
9
|
+
pattern_contradicted,
|
|
10
|
+
unattested_layers,
|
|
11
|
+
)
|
|
8
12
|
|
|
9
13
|
from sourcecode.schema import (
|
|
10
14
|
ArchitectureAnalysis,
|
|
@@ -41,11 +45,27 @@ _SRC_TRANSPARENT = {
|
|
|
41
45
|
"src", "lib", "app", "pkg",
|
|
42
46
|
"main", "java", "kotlin", "scala", "groovy",
|
|
43
47
|
}
|
|
48
|
+
|
|
49
|
+
# `src/main/java17/` is a MULTI-RELEASE source set, as transparent as `java/`
|
|
50
|
+
# — JobRunr's minted a module called "java17".
|
|
51
|
+
_VERSIONED_SRC_RE = re.compile(r"^(?:java|kotlin|scala)\d+$")
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def _is_transparent_segment(name: str) -> bool:
|
|
55
|
+
"""A path segment that carries no architectural meaning of its own."""
|
|
56
|
+
low = name.lower()
|
|
57
|
+
return low in _SRC_TRANSPARENT or bool(_VERSIONED_SRC_RE.match(low))
|
|
44
58
|
_CODE_EXTENSIONS = {
|
|
45
59
|
".py", ".js", ".ts", ".tsx", ".jsx", ".mjs", ".cjs",
|
|
46
60
|
".go", ".java", ".kt", ".rs", ".rb",
|
|
47
61
|
}
|
|
48
|
-
|
|
62
|
+
# Names that never identify a module: generic buckets, and the reverse-DNS
|
|
63
|
+
# package roots every JVM repo starts its namespace with (JobRunr reported a
|
|
64
|
+
# module called "org").
|
|
65
|
+
_GENERIC_NAMES = {
|
|
66
|
+
"utils", "helpers", "common", "shared", "misc", "core", "root", "",
|
|
67
|
+
"org", "com", "io", "net",
|
|
68
|
+
}
|
|
49
69
|
|
|
50
70
|
_TEST_DIRS: frozenset[str] = frozenset({
|
|
51
71
|
"tests", "test", "spec", "specs", "__tests__", "e2e",
|
|
@@ -671,14 +691,18 @@ class ArchitectureAnalyzer:
|
|
|
671
691
|
# reconciliation rule drops layers owned by a different runtime;
|
|
672
692
|
# template/asset-only layers are untouched, so a real Spring-MVC
|
|
673
693
|
# `templates/` view survives.
|
|
674
|
-
for
|
|
675
|
-
|
|
676
|
-
{k: [source_paths[i] for i in v] for k, v in layer_files.items()},
|
|
677
|
-
source_paths,
|
|
678
|
-
):
|
|
694
|
+
_layer_paths = {k: [source_paths[i] for i in v] for k, v in layer_files.items()}
|
|
695
|
+
for _bad in incoherent_layers(pattern_name, _layer_paths, source_paths):
|
|
679
696
|
matched.pop(_bad, None)
|
|
680
697
|
layer_files.pop(_bad, None)
|
|
681
698
|
|
|
699
|
+
# A claim that IS a runtime split must show one: JobRunr, a JVM
|
|
700
|
+
# library, read "Fullstack with frontend, backend layers" off a
|
|
701
|
+
# `dashboard/ui/model/` package of 16 Java files. Nothing to prune —
|
|
702
|
+
# the pattern itself is what the evidence contradicts.
|
|
703
|
+
if pattern_contradicted(pattern_name, _layer_paths, source_paths):
|
|
704
|
+
continue
|
|
705
|
+
|
|
682
706
|
# A pattern with unmet required keys cannot qualify (e.g. mvc needs a view).
|
|
683
707
|
required = _PATTERN_REQUIRED_KEYS.get(pattern_name)
|
|
684
708
|
if required and not required.issubset(matched.keys()):
|
|
@@ -842,6 +866,14 @@ class ArchitectureAnalyzer:
|
|
|
842
866
|
k: v for k, v in layer_files.items()
|
|
843
867
|
if v and (not total or len(v) / total >= _LAYER_MIN_SHARE)
|
|
844
868
|
}
|
|
869
|
+
# P1-D residue: these stem patterns are Python-shaped (cli.py,
|
|
870
|
+
# *_analyzer.py, config.py). On a Java repo almost nothing matches, so
|
|
871
|
+
# the few stray hits are not a layering — petclinic's "Layered
|
|
872
|
+
# Architecture with orchestration, data layers" rested on ONE directory
|
|
873
|
+
# entry plus a settings.gradle, with 47 Java files visible and ignored.
|
|
874
|
+
# A logical code layer that holds no code of this repo is not one.
|
|
875
|
+
for _bad in unattested_layers(non_empty, paths):
|
|
876
|
+
non_empty.pop(_bad, None)
|
|
845
877
|
if len(non_empty) >= 2:
|
|
846
878
|
return "layered", [
|
|
847
879
|
ArchitectureLayer(name=k, pattern="layered", files=v, confidence="low")
|
|
@@ -861,13 +893,19 @@ class ArchitectureAnalyzer:
|
|
|
861
893
|
for p in paths:
|
|
862
894
|
parts = p.replace("\\", "/").split("/")
|
|
863
895
|
for part in parts[:-1]:
|
|
864
|
-
if (
|
|
896
|
+
if (not _is_transparent_segment(part)
|
|
865
897
|
and part.lower() not in _NON_SOURCE_DIRS
|
|
866
898
|
and part.lower() not in _GENERIC_NAMES):
|
|
867
899
|
module_files.setdefault(part, []).append(p)
|
|
868
900
|
break
|
|
869
901
|
|
|
870
902
|
meaningful = {k: v for k, v in module_files.items() if len(v) >= 3}
|
|
903
|
+
# P1-D residue: a MODULE of this repo must hold this repo's code. File
|
|
904
|
+
# count alone let build and asset directories be reported as modules —
|
|
905
|
+
# ofbiz "docker, framework, runtime, gradle", neo4j "packaging",
|
|
906
|
+
# eureka "images" (five PNGs). The real modules survive untouched.
|
|
907
|
+
for _bad in unattested_layers(meaningful, paths):
|
|
908
|
+
meaningful.pop(_bad, None)
|
|
871
909
|
if len(meaningful) >= 2:
|
|
872
910
|
return "modular", [
|
|
873
911
|
ArchitectureLayer(name=k, pattern="modular", files=v, confidence="low")
|
|
@@ -223,6 +223,15 @@ _CROSS_RUNTIME_PATTERNS: frozenset[str] = frozenset({
|
|
|
223
223
|
"fullstack", "microservices", "monorepo",
|
|
224
224
|
})
|
|
225
225
|
|
|
226
|
+
# Patterns whose meaning IS the runtime split — a "fullstack" repo names a
|
|
227
|
+
# frontend TIER and a backend TIER. If every layer runs on the same runtime
|
|
228
|
+
# there is no split, and the claim is a directory-name coincidence: JobRunr, a
|
|
229
|
+
# JVM library, read "Fullstack with frontend, backend layers" off a
|
|
230
|
+
# `dashboard/ui/model/` package holding 16 JAVA files. (Not monorepo or
|
|
231
|
+
# microservices: an all-TypeScript monorepo, or same-runtime services, are
|
|
232
|
+
# perfectly real.)
|
|
233
|
+
_SPLIT_REQUIRED_PATTERNS: frozenset[str] = frozenset({"fullstack"})
|
|
234
|
+
|
|
226
235
|
|
|
227
236
|
def runtime_census(layer_files: dict[str, list[str]]) -> dict[str, dict[str, int]]:
|
|
228
237
|
"""{layer -> {runtime_family -> file count}} — the language evidence view.
|
|
@@ -288,6 +297,91 @@ def _rule_layer_language_coherence(
|
|
|
288
297
|
]
|
|
289
298
|
|
|
290
299
|
|
|
300
|
+
def _rule_layer_code_attestation(
|
|
301
|
+
census: dict[str, dict[str, int]],
|
|
302
|
+
repo_runtimes: dict[str, int],
|
|
303
|
+
) -> list[ReconciliationFinding]:
|
|
304
|
+
"""A claimed code module holds no code of the repo's runtime.
|
|
305
|
+
|
|
306
|
+
Stricter sibling of `layer_language_coherence`, for claims whose layers are
|
|
307
|
+
modules or logical code layers — their whole role is to hold code, so a
|
|
308
|
+
directory with none is not one of them. (`layer_language_coherence` cannot
|
|
309
|
+
serve here: it deliberately ignores layers with no runtime files at all, so
|
|
310
|
+
that a real Spring-MVC `templates/` view survives. A view may be templates;
|
|
311
|
+
a MODULE may not.)
|
|
312
|
+
|
|
313
|
+
Measured on the field-test repos: ofbiz's "modular architecture" listed
|
|
314
|
+
`docker`, `gradle`, `runtime` and `themes` (shell scripts, wrapper jars, a
|
|
315
|
+
.gitignore, images — zero Java); neo4j listed `packaging`; eureka listed
|
|
316
|
+
`images` (five PNGs); petclinic's whole "Layered Architecture with
|
|
317
|
+
orchestration, data layers" rested on one directory entry and a
|
|
318
|
+
settings.gradle.
|
|
319
|
+
"""
|
|
320
|
+
if not repo_runtimes:
|
|
321
|
+
return []
|
|
322
|
+
dominant = max(sorted(repo_runtimes), key=lambda f: repo_runtimes[f])
|
|
323
|
+
unattested = sorted(
|
|
324
|
+
layer for layer, counts in census.items() if not counts.get(dominant)
|
|
325
|
+
)
|
|
326
|
+
if not unattested:
|
|
327
|
+
return []
|
|
328
|
+
return [
|
|
329
|
+
ReconciliationFinding(
|
|
330
|
+
rule="layer_code_attestation",
|
|
331
|
+
symbols=tuple(unattested),
|
|
332
|
+
detail=(
|
|
333
|
+
f"{len(unattested)} claimed layer(s) hold no {dominant} code — "
|
|
334
|
+
"a directory without the repo's code is not a module of it."
|
|
335
|
+
),
|
|
336
|
+
)
|
|
337
|
+
]
|
|
338
|
+
|
|
339
|
+
|
|
340
|
+
def _rule_composition_runtime_split(
|
|
341
|
+
pattern: str,
|
|
342
|
+
census: dict[str, dict[str, int]],
|
|
343
|
+
) -> list[ReconciliationFinding]:
|
|
344
|
+
"""A pattern that names a runtime split shows none.
|
|
345
|
+
|
|
346
|
+
The mirror image of `layer_language_coherence`: a claim about one program
|
|
347
|
+
must NOT span runtimes; a claim that IS the split must show it. Silent
|
|
348
|
+
unless the pattern's layers carry runtime evidence at all.
|
|
349
|
+
"""
|
|
350
|
+
if pattern not in _SPLIT_REQUIRED_PATTERNS or len(census) < 2:
|
|
351
|
+
return []
|
|
352
|
+
families = {fam for counts in census.values() for fam in counts}
|
|
353
|
+
if len(families) != 1:
|
|
354
|
+
return [] # a real split, or no runtime evidence to judge
|
|
355
|
+
only = next(iter(families))
|
|
356
|
+
return [
|
|
357
|
+
ReconciliationFinding(
|
|
358
|
+
rule="composition_runtime_split",
|
|
359
|
+
symbols=tuple(sorted(census)),
|
|
360
|
+
detail=(
|
|
361
|
+
f"the '{pattern}' claim names a runtime split but every layer is "
|
|
362
|
+
f"{only} — the layer names are a coincidence, not two tiers."
|
|
363
|
+
),
|
|
364
|
+
)
|
|
365
|
+
]
|
|
366
|
+
|
|
367
|
+
|
|
368
|
+
def unattested_layers(
|
|
369
|
+
layer_files: dict[str, list[str]], repo_files: "list[str]"
|
|
370
|
+
) -> frozenset[str]:
|
|
371
|
+
"""Layers holding no code of the repo's runtime (convenience view).
|
|
372
|
+
|
|
373
|
+
For consumers claiming code modules / logical code layers. Never raises.
|
|
374
|
+
"""
|
|
375
|
+
try:
|
|
376
|
+
repo_runtimes = runtime_census({"_repo": list(repo_files)})["_repo"]
|
|
377
|
+
out: set[str] = set()
|
|
378
|
+
for f in _rule_layer_code_attestation(runtime_census(layer_files), repo_runtimes):
|
|
379
|
+
out.update(f.symbols)
|
|
380
|
+
return frozenset(out)
|
|
381
|
+
except Exception:
|
|
382
|
+
return frozenset()
|
|
383
|
+
|
|
384
|
+
|
|
291
385
|
def reconcile_architecture_evidence(
|
|
292
386
|
*,
|
|
293
387
|
pattern: str,
|
|
@@ -304,17 +398,34 @@ def reconcile_architecture_evidence(
|
|
|
304
398
|
"""
|
|
305
399
|
findings: list[ReconciliationFinding] = []
|
|
306
400
|
try:
|
|
401
|
+
census = runtime_census(layer_files)
|
|
307
402
|
repo_runtimes = runtime_census({"_repo": list(repo_files)})["_repo"]
|
|
308
403
|
findings.extend(
|
|
309
|
-
_rule_layer_language_coherence(
|
|
310
|
-
pattern, runtime_census(layer_files), repo_runtimes
|
|
311
|
-
)
|
|
404
|
+
_rule_layer_language_coherence(pattern, census, repo_runtimes)
|
|
312
405
|
)
|
|
406
|
+
findings.extend(_rule_composition_runtime_split(pattern, census))
|
|
313
407
|
except Exception:
|
|
314
408
|
pass
|
|
315
409
|
return findings
|
|
316
410
|
|
|
317
411
|
|
|
412
|
+
def pattern_contradicted(
|
|
413
|
+
pattern: str, layer_files: dict[str, list[str]], repo_files: "list[str]"
|
|
414
|
+
) -> bool:
|
|
415
|
+
"""True when a rule rejects the PATTERN itself, not just some of its layers.
|
|
416
|
+
|
|
417
|
+
`incoherent_layers` prunes layers and lets the pattern re-qualify on what
|
|
418
|
+
is left; a split-less "fullstack" has nothing to re-qualify with — the
|
|
419
|
+
claim as a whole is the thing contradicted.
|
|
420
|
+
"""
|
|
421
|
+
return any(
|
|
422
|
+
f.rule == "composition_runtime_split"
|
|
423
|
+
for f in reconcile_architecture_evidence(
|
|
424
|
+
pattern=pattern, layer_files=layer_files, repo_files=repo_files
|
|
425
|
+
)
|
|
426
|
+
)
|
|
427
|
+
|
|
428
|
+
|
|
318
429
|
def incoherent_layers(
|
|
319
430
|
pattern: str, layer_files: dict[str, list[str]], repo_files: "list[str]"
|
|
320
431
|
) -> frozenset[str]:
|
|
@@ -9,7 +9,7 @@ from pathlib import Path
|
|
|
9
9
|
from typing import Any
|
|
10
10
|
|
|
11
11
|
from sourcecode.architecture_analyzer import _LAYER_MIN_SHARE, _PATTERN_REQUIRED_KEYS
|
|
12
|
-
from sourcecode.reconciliation import incoherent_layers
|
|
12
|
+
from sourcecode.reconciliation import incoherent_layers, pattern_contradicted
|
|
13
13
|
from sourcecode.detectors.parsers import load_json_file, load_toml_file
|
|
14
14
|
from sourcecode.tree_utils import safe_read_text
|
|
15
15
|
from sourcecode.entrypoint_classifier import is_production_entry_point
|
|
@@ -439,6 +439,8 @@ class ProjectSummarizer:
|
|
|
439
439
|
# two duplicated tables leaves the other one lying.
|
|
440
440
|
for _bad in incoherent_layers(pattern_name, layer_files, file_paths):
|
|
441
441
|
layer_files.pop(_bad, None)
|
|
442
|
+
if pattern_contradicted(pattern_name, layer_files, file_paths):
|
|
443
|
+
continue
|
|
442
444
|
# A pattern must show its defining trait here too, or the headline
|
|
443
445
|
# contradicts the architecture prose that already enforces this.
|
|
444
446
|
required = _PATTERN_REQUIRED_KEYS.get(pattern_name)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|