sourcecode 2.5.5__tar.gz → 2.5.7__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.7}/CHANGELOG.md +138 -0
- {sourcecode-2.5.5 → sourcecode-2.5.7}/PKG-INFO +1 -1
- {sourcecode-2.5.5 → sourcecode-2.5.7}/pyproject.toml +1 -1
- {sourcecode-2.5.5 → sourcecode-2.5.7}/src/sourcecode/__init__.py +1 -1
- {sourcecode-2.5.5 → sourcecode-2.5.7}/src/sourcecode/architecture_analyzer.py +46 -8
- {sourcecode-2.5.5 → sourcecode-2.5.7}/src/sourcecode/architecture_summary.py +55 -6
- {sourcecode-2.5.5 → sourcecode-2.5.7}/src/sourcecode/confidence_analyzer.py +39 -0
- {sourcecode-2.5.5 → sourcecode-2.5.7}/src/sourcecode/reconciliation.py +305 -3
- {sourcecode-2.5.5 → sourcecode-2.5.7}/src/sourcecode/summarizer.py +3 -1
- {sourcecode-2.5.5 → sourcecode-2.5.7}/.github/workflows/build-windows.yml +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.7}/.gitignore +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.7}/.ruff.toml +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.7}/CONTRIBUTING.md +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.7}/LICENSE +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.7}/README.md +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.7}/SECURITY.md +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.7}/raw +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.7}/scripts/compare_integration_engines.py +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.7}/scripts/customer_smoke_test.sh +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.7}/scripts/generate_jdk_exports.py +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.7}/src/sourcecode/adaptive_scanner.py +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.7}/src/sourcecode/archetype.py +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.7}/src/sourcecode/ast_extractor.py +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.7}/src/sourcecode/cache.py +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.7}/src/sourcecode/call_surface.py +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.7}/src/sourcecode/caller_metrics.py +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.7}/src/sourcecode/canonical_ir.py +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.7}/src/sourcecode/cir_graphs.py +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.7}/src/sourcecode/classifier.py +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.7}/src/sourcecode/cli.py +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.7}/src/sourcecode/code_notes_analyzer.py +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.7}/src/sourcecode/context_cache.py +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.7}/src/sourcecode/context_graph.py +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.7}/src/sourcecode/context_scorer.py +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.7}/src/sourcecode/context_summarizer.py +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.7}/src/sourcecode/contract_model.py +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.7}/src/sourcecode/contract_pipeline.py +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.7}/src/sourcecode/coverage_parser.py +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.7}/src/sourcecode/dependency_analyzer.py +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.7}/src/sourcecode/detectors/__init__.py +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.7}/src/sourcecode/detectors/base.py +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.7}/src/sourcecode/detectors/csproj_parser.py +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.7}/src/sourcecode/detectors/dart.py +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.7}/src/sourcecode/detectors/dotnet.py +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.7}/src/sourcecode/detectors/elixir.py +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.7}/src/sourcecode/detectors/go.py +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.7}/src/sourcecode/detectors/heuristic.py +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.7}/src/sourcecode/detectors/hybrid.py +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.7}/src/sourcecode/detectors/java.py +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.7}/src/sourcecode/detectors/jvm_ext.py +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.7}/src/sourcecode/detectors/nodejs.py +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.7}/src/sourcecode/detectors/parsers.py +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.7}/src/sourcecode/detectors/php.py +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.7}/src/sourcecode/detectors/project.py +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.7}/src/sourcecode/detectors/python.py +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.7}/src/sourcecode/detectors/ruby.py +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.7}/src/sourcecode/detectors/rust.py +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.7}/src/sourcecode/detectors/systems.py +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.7}/src/sourcecode/detectors/terraform.py +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.7}/src/sourcecode/detectors/tooling.py +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.7}/src/sourcecode/doc_analyzer.py +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.7}/src/sourcecode/dynamic_argument_surface.py +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.7}/src/sourcecode/endpoint_literals.py +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.7}/src/sourcecode/endpoint_metrics.py +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.7}/src/sourcecode/entrypoint_classifier.py +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.7}/src/sourcecode/env_analyzer.py +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.7}/src/sourcecode/error_schema.py +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.7}/src/sourcecode/evidence_provider.py +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.7}/src/sourcecode/explain.py +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.7}/src/sourcecode/file_chunker.py +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.7}/src/sourcecode/file_classifier.py +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.7}/src/sourcecode/format_contract.py +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.7}/src/sourcecode/fqn_utils.py +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.7}/src/sourcecode/git_analyzer.py +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.7}/src/sourcecode/graph_analyzer.py +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.7}/src/sourcecode/graph_evidence.py +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.7}/src/sourcecode/hibernate_strat.py +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.7}/src/sourcecode/jdk_exports.py +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.7}/src/sourcecode/license.py +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.7}/src/sourcecode/mcp/__init__.py +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.7}/src/sourcecode/mcp/onboarding/__init__.py +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.7}/src/sourcecode/mcp/onboarding/applier.py +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.7}/src/sourcecode/mcp/onboarding/backup.py +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.7}/src/sourcecode/mcp/onboarding/detector.py +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.7}/src/sourcecode/mcp/onboarding/planner.py +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.7}/src/sourcecode/mcp/orchestrator.py +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.7}/src/sourcecode/mcp/registry.py +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.7}/src/sourcecode/mcp/runner.py +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.7}/src/sourcecode/mcp/server.py +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.7}/src/sourcecode/mcp_nudge.py +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.7}/src/sourcecode/metrics_analyzer.py +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.7}/src/sourcecode/migrate_check.py +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.7}/src/sourcecode/openapi_surface.py +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.7}/src/sourcecode/output_budget.py +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.7}/src/sourcecode/path_filters.py +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.7}/src/sourcecode/pr_comment_renderer.py +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.7}/src/sourcecode/pr_impact.py +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.7}/src/sourcecode/prepare_context.py +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.7}/src/sourcecode/progress.py +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.7}/src/sourcecode/ranking_engine.py +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.7}/src/sourcecode/redactor.py +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.7}/src/sourcecode/relevance_scorer.py +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.7}/src/sourcecode/rename_refactor.py +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.7}/src/sourcecode/repo_classifier.py +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.7}/src/sourcecode/repository_ir.py +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.7}/src/sourcecode/retrieval/__init__.py +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.7}/src/sourcecode/retrieval/context.py +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.7}/src/sourcecode/retrieval/errors.py +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.7}/src/sourcecode/retrieval/executor.py +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.7}/src/sourcecode/retrieval/planner.py +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.7}/src/sourcecode/retrieval/query.py +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.7}/src/sourcecode/retrieval/request.py +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.7}/src/sourcecode/retrieval/resolution.py +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.7}/src/sourcecode/retrieval/result.py +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.7}/src/sourcecode/retrieval/retriever.py +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.7}/src/sourcecode/retrieval/runtime.py +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.7}/src/sourcecode/retrieval/steps.py +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.7}/src/sourcecode/retrieval/steps_endpoint.py +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.7}/src/sourcecode/retrieval/steps_graph.py +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.7}/src/sourcecode/retrieval/steps_impact.py +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.7}/src/sourcecode/retrieval/steps_intf.py +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.7}/src/sourcecode/retrieval/steps_struct.py +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.7}/src/sourcecode/retrieval/steps_txsec.py +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.7}/src/sourcecode/ris.py +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.7}/src/sourcecode/runtime_classifier.py +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.7}/src/sourcecode/scanner.py +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.7}/src/sourcecode/schema.py +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.7}/src/sourcecode/security_config.py +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.7}/src/sourcecode/security_posture.py +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.7}/src/sourcecode/semantic_analyzer.py +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.7}/src/sourcecode/semantic_impact_engine.py +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.7}/src/sourcecode/semantic_integration_engine.py +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.7}/src/sourcecode/semantic_services.py +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.7}/src/sourcecode/serializer.py +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.7}/src/sourcecode/spring_event_topology.py +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.7}/src/sourcecode/spring_findings.py +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.7}/src/sourcecode/spring_impact.py +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.7}/src/sourcecode/spring_model.py +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.7}/src/sourcecode/spring_security_audit.py +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.7}/src/sourcecode/spring_semantic.py +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.7}/src/sourcecode/spring_tx_analyzer.py +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.7}/src/sourcecode/telemetry/__init__.py +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.7}/src/sourcecode/telemetry/config.py +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.7}/src/sourcecode/telemetry/consent.py +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.7}/src/sourcecode/telemetry/events.py +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.7}/src/sourcecode/telemetry/filters.py +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.7}/src/sourcecode/telemetry/transport.py +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.7}/src/sourcecode/tree_utils.py +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.7}/src/sourcecode/type_usage_surface.py +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.7}/src/sourcecode/validation_inference.py +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.7}/src/sourcecode/validation_surface.py +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.7}/src/sourcecode/version_check.py +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.7}/src/sourcecode/workspace.py +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.7}/supabase/functions/README.md +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.7}/supabase/functions/get-license/index.ts +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.7}/supabase/functions/lemonsqueezy-webhook/index.ts +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.7}/supabase/functions/telemetry/index.ts +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.7}/supabase/sql/license_event_ordering.sql +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.7}/supabase/sql/licensing_schema.sql +0 -0
- {sourcecode-2.5.5 → sourcecode-2.5.7}/supabase/sql/telemetry_events.sql +0 -0
|
@@ -1,5 +1,143 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [2.5.7] — 2026-07-17
|
|
4
|
+
|
|
5
|
+
**A framework claim must be attested by its own runtime.** The `examples` repo
|
|
6
|
+
read *"C#/.NET rest api using JAX-RS."* — a JVM-only framework attributed to a
|
|
7
|
+
.NET headline, self-contradictory inside a single clause. This release closes the
|
|
8
|
+
last of the narrative cluster, and settles how reconciliation findings reach the
|
|
9
|
+
surfaces that report them.
|
|
10
|
+
|
|
11
|
+
### Fixed
|
|
12
|
+
|
|
13
|
+
- **A polyglot repository was narrated as one program (P2-F)**
|
|
14
|
+
(`reconciliation.py`, `architecture_summary.py`): the sentence was assembled
|
|
15
|
+
from two unrelated sources —
|
|
16
|
+
|
|
17
|
+
| part | comes from | on `examples` |
|
|
18
|
+
|---|---|---|
|
|
19
|
+
| `C#/.NET` | the **primary** stack | dotnet, root `.`, declares **zero** frameworks |
|
|
20
|
+
| `using JAX-RS` | **every** stack | java, workspace `microservices-orders`, the sole owner of JAX-RS |
|
|
21
|
+
|
|
22
|
+
New Evidence Reconciliation rule **`framework_stack_attestation`**: a framework
|
|
23
|
+
named in a single-program headline whose owning stacks are all of another
|
|
24
|
+
runtime family contradicts the claim. Ownership is already structural — a
|
|
25
|
+
`FrameworkDetection` is discovered inside the `StackDetection` whose manifest
|
|
26
|
+
declares it — so the rule reasons from evidence and never from what a framework
|
|
27
|
+
is called. Grouped by runtime family, so a Kotlin framework beside Java is one
|
|
28
|
+
program; `fullstack`/`monorepo`/`microservices` are exempt, their headline is
|
|
29
|
+
meant to span runtimes.
|
|
30
|
+
|
|
31
|
+
The type label had to go with it. `project_type` is decided by frameworks
|
|
32
|
+
pooled across every stack, while a separate vote picks the primary, so
|
|
33
|
+
*"rest api"* was java's evidence too — dropping only the `using` clause would
|
|
34
|
+
have left *"C#/.NET rest api."*, the same claim one clause shorter. The
|
|
35
|
+
headline now degrades to what is actually known:
|
|
36
|
+
|
|
37
|
+
> C#/.NET project. Polyglot repository: the rest api evidence (JAX-RS) is
|
|
38
|
+
> declared by the Java stack, not by C#/.NET — no single stack owns this
|
|
39
|
+
> repository's architecture. See `stacks`.
|
|
40
|
+
|
|
41
|
+
Every fact survives, correctly attributed.
|
|
42
|
+
|
|
43
|
+
- **The structured fields kept asserting what the sentence had retracted
|
|
44
|
+
(P2-F-RESIDUE(a))** (`confidence_analyzer.py`, `reconciliation.py`):
|
|
45
|
+
`project_type` and `primary_stack` are read on their own by agents that never
|
|
46
|
+
see the prose, and on `examples` they still said `"api"` + `"dotnet"` silently.
|
|
47
|
+
`ConfidenceAnalyzer` now consumes the same rule, emitting an
|
|
48
|
+
`analysis_gaps` entry (`area="framework_stack_attestation"`) plus an anomaly
|
|
49
|
+
beside its existing "ambiguous primary" siblings.
|
|
50
|
+
|
|
51
|
+
`project_type` deliberately keeps its value: the classifier's vote is a
|
|
52
|
+
separate decision, and minting a *"polyglot"* type here would silently rewrite
|
|
53
|
+
what every consumer branches on. Flagging the claim is honest; changing the
|
|
54
|
+
field is a different change.
|
|
55
|
+
|
|
56
|
+
### Internal
|
|
57
|
+
|
|
58
|
+
- `_FRAMEWORK_DRIVEN_TYPES` moved out of `architecture_summary` into
|
|
59
|
+
`reconciliation.type_claim_unattested()`. Two surfaces must judge *"did the
|
|
60
|
+
frameworks decide this type"* identically — a duplicated table is how the
|
|
61
|
+
headline kept lying in the first place.
|
|
62
|
+
- `ReconciliationFinding.detail` is now **sink-neutral**. It used to narrate
|
|
63
|
+
itself (*"framework(s) named in the headline … the sentence attributes them
|
|
64
|
+
to"*), which the rule that strips the claim promptly made false. Details are
|
|
65
|
+
read by prose and by structured gaps alike; none may assume prose.
|
|
66
|
+
|
|
67
|
+
Validation across 12 field-test repos through the real CLI: `examples` emits the
|
|
68
|
+
degraded headline, the gap and the anomaly (*"JAX-RS declared by another runtime,
|
|
69
|
+
not by the dotnet primary stack"*); petclinic, keycloak, eureka, jobrunr, ofbiz,
|
|
70
|
+
jenkins, broadleaf, alfresco, openmrs, neo4j and spaghetti-api are **silent —
|
|
71
|
+
every headline byte-identical**, no gap, no anomaly. Every control's primary
|
|
72
|
+
stack declares its own frameworks, which is exactly why the rule stays quiet.
|
|
73
|
+
|
|
74
|
+
Measured along the way, and worth knowing: on a polyglot repo the **primary stack
|
|
75
|
+
is a coin flip** — `max(stacks, …)` breaks an exact tie between java and dotnet
|
|
76
|
+
(both manifest-detected, both high-confidence) by list order, and the narrative
|
|
77
|
+
then welded the loser's evidence onto the winner's label.
|
|
78
|
+
|
|
79
|
+
14 new tests; suite 3462 passed, 5 skipped.
|
|
80
|
+
|
|
81
|
+
## [2.5.6] — 2026-07-17
|
|
82
|
+
|
|
83
|
+
**A layer must be attested by code.** The architecture fallback chain reported
|
|
84
|
+
directories as architecture; measured on the field-test repos, every fabricated
|
|
85
|
+
claim rested on files that are not code.
|
|
86
|
+
|
|
87
|
+
### Fixed
|
|
88
|
+
|
|
89
|
+
- **Build and asset directories were reported as modules (P1-D-REMAINING)**
|
|
90
|
+
(`reconciliation.py`, `architecture_analyzer.py`, `summarizer.py`):
|
|
91
|
+
|
|
92
|
+
| repo | claimed | what it rested on |
|
|
93
|
+
|---|---|---|
|
|
94
|
+
| ofbiz | *"modular: docker, framework, runtime, gradle"* | shell scripts, a wrapper jar, a `.gitignore` |
|
|
95
|
+
| neo4j | *"modular: packaging, annotations, community"* | `packaging/` is a build directory |
|
|
96
|
+
| eureka | *"modular: …, images, …"* | five PNGs |
|
|
97
|
+
| openmrs | *"modular: …, webapp, monitoring"* | GIFs and yaml |
|
|
98
|
+
| petclinic | *"Layered Architecture with orchestration, data layers"* | ONE directory entry and a `settings.gradle` — with 47 Java files visible and ignored |
|
|
99
|
+
| jobrunr | *"Fullstack with frontend, backend layers"* | a JVM library; the "frontend" is `dashboard/ui/model/`, **16 Java files** |
|
|
100
|
+
|
|
101
|
+
Two new Evidence Reconciliation rules, mirror images of each other and both
|
|
102
|
+
applied to the duplicated layer tables (architecture prose + headline):
|
|
103
|
+
|
|
104
|
+
- **`layer_code_attestation`** — a claimed MODULE or logical code layer that
|
|
105
|
+
holds no code of the repo's runtime is not one. Stricter sibling of
|
|
106
|
+
`layer_language_coherence`, which deliberately ignores runtime-less layers
|
|
107
|
+
so that a real Spring-MVC `templates/` view survives: a view may be
|
|
108
|
+
templates, a module may not.
|
|
109
|
+
- **`composition_runtime_split`** — a pattern that IS the split must show it.
|
|
110
|
+
`fullstack` names a frontend TIER and a backend TIER; if every layer runs on
|
|
111
|
+
one runtime, the layer names are a directory-name coincidence. Not applied
|
|
112
|
+
to `monorepo`/`microservices` — an all-TypeScript monorepo, or same-runtime
|
|
113
|
+
services, are perfectly real.
|
|
114
|
+
|
|
115
|
+
petclinic's stem-based layering is Python-shaped (`cli.py`, `*_analyzer.py`,
|
|
116
|
+
`config.py`) and matches almost nothing in Java, which is why two stray hits
|
|
117
|
+
could mint a whole "Layered Architecture".
|
|
118
|
+
|
|
119
|
+
- **Module names that never name a module** (`architecture_analyzer.py`): the
|
|
120
|
+
reverse-DNS package roots every JVM repo starts its namespace with
|
|
121
|
+
(`org`/`com`/`io`/`net` — jobrunr reported a module called *"org"*) and
|
|
122
|
+
multi-release source sets (`src/main/java17` minted *"java17"*; now as
|
|
123
|
+
transparent as `java/`). Same class as the `core` already excluded.
|
|
124
|
+
|
|
125
|
+
Validation across 10 field-test repos through the real CLI: ofbiz →
|
|
126
|
+
*"framework, applications"* (its two real modules); neo4j → *"annotations,
|
|
127
|
+
community"*; eureka → its eight real `eureka-*` modules; openmrs → *"web,
|
|
128
|
+
test-suite, api, liquibase"*; jobrunr → *"modular: jobrunr, language-support,
|
|
129
|
+
framework-support"* (its real top-level directories); **petclinic emits no
|
|
130
|
+
architecture claim at all** — the fabricated line is gone while every true
|
|
131
|
+
statement survives (*"Java server-side mvc web app using Spring Boot,
|
|
132
|
+
Thymeleaf"*, frameworks, bootstrap annotation). Broadleaf, alfresco, keycloak
|
|
133
|
+
and jenkins: byte-identical prose.
|
|
134
|
+
|
|
135
|
+
Investigated and refuted along the way: these claims are NOT built on a
|
|
136
|
+
truncated file list — the analyzer sees 100% of petclinic's Java and 99.4% of
|
|
137
|
+
ofbiz's.
|
|
138
|
+
|
|
139
|
+
11 new tests; suite 3448 passed.
|
|
140
|
+
|
|
3
141
|
## [2.5.5] — 2026-07-16
|
|
4
142
|
|
|
5
143
|
**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")
|
|
@@ -205,6 +205,22 @@ class ArchitectureSummarizer:
|
|
|
205
205
|
for f in s.frameworks[:2]
|
|
206
206
|
))[:3]
|
|
207
207
|
|
|
208
|
+
# F-1 reconciliation: the headline describes ONE program, but fw_names is
|
|
209
|
+
# gathered from every stack in the repo. On a polyglot repository that
|
|
210
|
+
# welds a foreign runtime's framework onto the primary's label. Drop what
|
|
211
|
+
# the claimed stack does not attest, and never let the TYPE label — which
|
|
212
|
+
# those same frameworks drove — survive its own evidence (P2-F).
|
|
213
|
+
from sourcecode.reconciliation import type_claim_unattested, unattested_frameworks
|
|
214
|
+
|
|
215
|
+
foreign = unattested_frameworks(primary.stack, sm.project_type, sm.stacks)
|
|
216
|
+
attested = [name for name in fw_names if name not in foreign]
|
|
217
|
+
dropped = [name for name in fw_names if name in foreign]
|
|
218
|
+
if dropped and not attested and type_claim_unattested(
|
|
219
|
+
claimed_stack=primary.stack, project_type=sm.project_type, stacks=sm.stacks
|
|
220
|
+
):
|
|
221
|
+
return self._describe_foreign_evidence(sm, stack_label, runtime, dropped)
|
|
222
|
+
fw_names = attested
|
|
223
|
+
|
|
208
224
|
fw_str = f" using {', '.join(fw_names)}" if fw_names else ""
|
|
209
225
|
if runtime:
|
|
210
226
|
# BUG #4: never assert "rest api" in the headline unless the endpoints
|
|
@@ -237,6 +253,29 @@ class ArchitectureSummarizer:
|
|
|
237
253
|
return f"{stack_label} {runtime.lower()}{fw_str}."
|
|
238
254
|
return f"{stack_label} project{fw_str}."
|
|
239
255
|
|
|
256
|
+
def _describe_foreign_evidence(
|
|
257
|
+
self, sm: SourceMap, stack_label: str, runtime: str, dropped: list[str]
|
|
258
|
+
) -> str:
|
|
259
|
+
"""Headline for a claim whose defining evidence belongs to another stack.
|
|
260
|
+
|
|
261
|
+
Says what IS known instead of asserting a program that does not exist:
|
|
262
|
+
the primary stack, the foreign frameworks, and the stacks that actually
|
|
263
|
+
declare them. The reader gets the same facts, correctly attributed.
|
|
264
|
+
"""
|
|
265
|
+
from sourcecode.context_summarizer import _STACK_LABELS
|
|
266
|
+
from sourcecode.reconciliation import framework_owner_stacks
|
|
267
|
+
|
|
268
|
+
owners = framework_owner_stacks(sm.stacks)
|
|
269
|
+
owner_ids = sorted({sid for name in dropped for sid in owners.get(name, ())})
|
|
270
|
+
owner_labels = [_STACK_LABELS.get(sid, sid) for sid in owner_ids] or ["another"]
|
|
271
|
+
plural = "s" if len(owner_labels) > 1 else ""
|
|
272
|
+
return (
|
|
273
|
+
f"{stack_label} project. Polyglot repository: the {runtime.lower()} "
|
|
274
|
+
f"evidence ({', '.join(dropped)}) is declared by the "
|
|
275
|
+
f"{', '.join(owner_labels)} stack{plural}, not by {stack_label} — no "
|
|
276
|
+
"single stack owns this repository's architecture. See `stacks`."
|
|
277
|
+
)
|
|
278
|
+
|
|
240
279
|
def _endpoint_support(self) -> tuple[int, int]:
|
|
241
280
|
"""Return (total, high_confidence) endpoint counts from the canonical
|
|
242
281
|
Java endpoint extractor — the same source the `endpoints` command uses,
|
|
@@ -412,41 +451,51 @@ class ArchitectureSummarizer:
|
|
|
412
451
|
return None
|
|
413
452
|
|
|
414
453
|
def _summarize_dotnet_entry(self, stacks: list[StackDetection]) -> list[str]:
|
|
454
|
+
# P2-F-RESIDUE(c): every line here counts .csproj files — facts about the
|
|
455
|
+
# .NET stack ALONE. Unscoped, they read as claims about the repository:
|
|
456
|
+
# the confluent examples monorepo (20+ subprojects, mostly Java/Python/Go)
|
|
457
|
+
# reported a bare "1 project detected." beside a headline that had just
|
|
458
|
+
# called it polyglot. Naming the stack costs one word and makes each line
|
|
459
|
+
# true on any repo, so no polyglot test is needed to decide the wording.
|
|
460
|
+
from sourcecode.context_summarizer import _STACK_LABELS
|
|
461
|
+
|
|
415
462
|
dotnet_stacks = [s for s in stacks if s.stack == "dotnet"]
|
|
416
463
|
if not dotnet_stacks:
|
|
417
464
|
return []
|
|
465
|
+
label = _STACK_LABELS.get("dotnet", "dotnet") # same table as the headline
|
|
418
466
|
lines: list[str] = []
|
|
419
467
|
signals = [sig for s in dotnet_stacks for sig in s.signals]
|
|
420
468
|
|
|
421
469
|
for sig in signals:
|
|
422
470
|
if "project" in sig and "detected" in sig:
|
|
423
|
-
|
|
471
|
+
count = sig.split(" ", 1)[0]
|
|
472
|
+
lines.append(f"{label} projects: {count}.")
|
|
424
473
|
break
|
|
425
474
|
|
|
426
475
|
for sig in signals:
|
|
427
476
|
if sig.startswith("project types:"):
|
|
428
477
|
types = sig.removeprefix("project types:").strip()
|
|
429
|
-
lines.append(f"
|
|
478
|
+
lines.append(f"{label} project types: {types}.")
|
|
430
479
|
break
|
|
431
480
|
|
|
432
481
|
for sig in signals:
|
|
433
482
|
if sig.startswith("target frameworks:"):
|
|
434
483
|
fws = sig.removeprefix("target frameworks:").strip()
|
|
435
|
-
lines.append(f"
|
|
484
|
+
lines.append(f"{label} target frameworks: {fws}.")
|
|
436
485
|
break
|
|
437
486
|
|
|
438
487
|
for sig in signals:
|
|
439
488
|
if sig.startswith("architecture:"):
|
|
440
489
|
pattern = sig.removeprefix("architecture:").strip()
|
|
441
|
-
lines.append(f"
|
|
490
|
+
lines.append(f"{label} solution pattern: {pattern}.")
|
|
442
491
|
break
|
|
443
492
|
|
|
444
493
|
framework_names = [f.name for s in dotnet_stacks for f in s.frameworks]
|
|
445
494
|
if framework_names:
|
|
446
|
-
lines.append(f"
|
|
495
|
+
lines.append(f"{label} frameworks: {', '.join(framework_names)}.")
|
|
447
496
|
|
|
448
497
|
if not lines:
|
|
449
|
-
lines.append("
|
|
498
|
+
lines.append(f"{label} solution detected.")
|
|
450
499
|
return lines
|
|
451
500
|
|
|
452
501
|
def _summarize_go_entry(self, path: str, content: str) -> list[str]:
|
|
@@ -102,6 +102,45 @@ class ConfidenceAnalyzer:
|
|
|
102
102
|
if heuristic_only and not any(s.detection_method != "heuristic" for s in sm.stacks):
|
|
103
103
|
anomalies.append("All stacks detected via heuristic only — no manifest found")
|
|
104
104
|
|
|
105
|
+
# ── Anomaly: a claim attributed to a stack that does not attest it ────
|
|
106
|
+
# F-1 reconciliation, second sink. The headline degrades itself
|
|
107
|
+
# (architecture_summary), but `project_type` and `primary_stack` are
|
|
108
|
+
# separate structured fields an agent can read on their own — and they
|
|
109
|
+
# keep asserting the claim this rule refutes. Same rule, same judgement,
|
|
110
|
+
# different surface: here it becomes an anomaly + gap instead of prose.
|
|
111
|
+
if primary_stacks:
|
|
112
|
+
from sourcecode.reconciliation import (
|
|
113
|
+
reconcile_headline_evidence,
|
|
114
|
+
type_claim_unattested,
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
_primary = primary_stacks[0]
|
|
118
|
+
for _rf in reconcile_headline_evidence(
|
|
119
|
+
claimed_stack=_primary.stack,
|
|
120
|
+
project_type=sm.project_type,
|
|
121
|
+
stacks=sm.stacks,
|
|
122
|
+
):
|
|
123
|
+
anomalies.append(
|
|
124
|
+
f"{', '.join(_rf.symbols)} declared by another runtime, not by the "
|
|
125
|
+
f"{_primary.stack} primary stack"
|
|
126
|
+
)
|
|
127
|
+
_reason = _rf.detail
|
|
128
|
+
if type_claim_unattested(
|
|
129
|
+
claimed_stack=_primary.stack,
|
|
130
|
+
project_type=sm.project_type,
|
|
131
|
+
stacks=sm.stacks,
|
|
132
|
+
):
|
|
133
|
+
_reason += (
|
|
134
|
+
f' project_type="{sm.project_type}" rests on that same evidence — '
|
|
135
|
+
f"it is not attested for the {_primary.stack} primary stack, and "
|
|
136
|
+
"the repository is polyglot rather than one program."
|
|
137
|
+
)
|
|
138
|
+
gaps.append(AnalysisGap(
|
|
139
|
+
area=_rf.rule,
|
|
140
|
+
reason=_reason,
|
|
141
|
+
impact="medium",
|
|
142
|
+
))
|
|
143
|
+
|
|
105
144
|
# ── Anomaly: entry points all low-confidence ──────────────────────────
|
|
106
145
|
if normalized_entry_points and all(ep.confidence == "low" for ep in normalized_entry_points):
|
|
107
146
|
anomalies.append("All entry points are low-confidence (heuristic/code_signal only)")
|