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.
Files changed (160) hide show
  1. {sourcecode-2.5.5 → sourcecode-2.5.7}/CHANGELOG.md +138 -0
  2. {sourcecode-2.5.5 → sourcecode-2.5.7}/PKG-INFO +1 -1
  3. {sourcecode-2.5.5 → sourcecode-2.5.7}/pyproject.toml +1 -1
  4. {sourcecode-2.5.5 → sourcecode-2.5.7}/src/sourcecode/__init__.py +1 -1
  5. {sourcecode-2.5.5 → sourcecode-2.5.7}/src/sourcecode/architecture_analyzer.py +46 -8
  6. {sourcecode-2.5.5 → sourcecode-2.5.7}/src/sourcecode/architecture_summary.py +55 -6
  7. {sourcecode-2.5.5 → sourcecode-2.5.7}/src/sourcecode/confidence_analyzer.py +39 -0
  8. {sourcecode-2.5.5 → sourcecode-2.5.7}/src/sourcecode/reconciliation.py +305 -3
  9. {sourcecode-2.5.5 → sourcecode-2.5.7}/src/sourcecode/summarizer.py +3 -1
  10. {sourcecode-2.5.5 → sourcecode-2.5.7}/.github/workflows/build-windows.yml +0 -0
  11. {sourcecode-2.5.5 → sourcecode-2.5.7}/.gitignore +0 -0
  12. {sourcecode-2.5.5 → sourcecode-2.5.7}/.ruff.toml +0 -0
  13. {sourcecode-2.5.5 → sourcecode-2.5.7}/CONTRIBUTING.md +0 -0
  14. {sourcecode-2.5.5 → sourcecode-2.5.7}/LICENSE +0 -0
  15. {sourcecode-2.5.5 → sourcecode-2.5.7}/README.md +0 -0
  16. {sourcecode-2.5.5 → sourcecode-2.5.7}/SECURITY.md +0 -0
  17. {sourcecode-2.5.5 → sourcecode-2.5.7}/raw +0 -0
  18. {sourcecode-2.5.5 → sourcecode-2.5.7}/scripts/compare_integration_engines.py +0 -0
  19. {sourcecode-2.5.5 → sourcecode-2.5.7}/scripts/customer_smoke_test.sh +0 -0
  20. {sourcecode-2.5.5 → sourcecode-2.5.7}/scripts/generate_jdk_exports.py +0 -0
  21. {sourcecode-2.5.5 → sourcecode-2.5.7}/src/sourcecode/adaptive_scanner.py +0 -0
  22. {sourcecode-2.5.5 → sourcecode-2.5.7}/src/sourcecode/archetype.py +0 -0
  23. {sourcecode-2.5.5 → sourcecode-2.5.7}/src/sourcecode/ast_extractor.py +0 -0
  24. {sourcecode-2.5.5 → sourcecode-2.5.7}/src/sourcecode/cache.py +0 -0
  25. {sourcecode-2.5.5 → sourcecode-2.5.7}/src/sourcecode/call_surface.py +0 -0
  26. {sourcecode-2.5.5 → sourcecode-2.5.7}/src/sourcecode/caller_metrics.py +0 -0
  27. {sourcecode-2.5.5 → sourcecode-2.5.7}/src/sourcecode/canonical_ir.py +0 -0
  28. {sourcecode-2.5.5 → sourcecode-2.5.7}/src/sourcecode/cir_graphs.py +0 -0
  29. {sourcecode-2.5.5 → sourcecode-2.5.7}/src/sourcecode/classifier.py +0 -0
  30. {sourcecode-2.5.5 → sourcecode-2.5.7}/src/sourcecode/cli.py +0 -0
  31. {sourcecode-2.5.5 → sourcecode-2.5.7}/src/sourcecode/code_notes_analyzer.py +0 -0
  32. {sourcecode-2.5.5 → sourcecode-2.5.7}/src/sourcecode/context_cache.py +0 -0
  33. {sourcecode-2.5.5 → sourcecode-2.5.7}/src/sourcecode/context_graph.py +0 -0
  34. {sourcecode-2.5.5 → sourcecode-2.5.7}/src/sourcecode/context_scorer.py +0 -0
  35. {sourcecode-2.5.5 → sourcecode-2.5.7}/src/sourcecode/context_summarizer.py +0 -0
  36. {sourcecode-2.5.5 → sourcecode-2.5.7}/src/sourcecode/contract_model.py +0 -0
  37. {sourcecode-2.5.5 → sourcecode-2.5.7}/src/sourcecode/contract_pipeline.py +0 -0
  38. {sourcecode-2.5.5 → sourcecode-2.5.7}/src/sourcecode/coverage_parser.py +0 -0
  39. {sourcecode-2.5.5 → sourcecode-2.5.7}/src/sourcecode/dependency_analyzer.py +0 -0
  40. {sourcecode-2.5.5 → sourcecode-2.5.7}/src/sourcecode/detectors/__init__.py +0 -0
  41. {sourcecode-2.5.5 → sourcecode-2.5.7}/src/sourcecode/detectors/base.py +0 -0
  42. {sourcecode-2.5.5 → sourcecode-2.5.7}/src/sourcecode/detectors/csproj_parser.py +0 -0
  43. {sourcecode-2.5.5 → sourcecode-2.5.7}/src/sourcecode/detectors/dart.py +0 -0
  44. {sourcecode-2.5.5 → sourcecode-2.5.7}/src/sourcecode/detectors/dotnet.py +0 -0
  45. {sourcecode-2.5.5 → sourcecode-2.5.7}/src/sourcecode/detectors/elixir.py +0 -0
  46. {sourcecode-2.5.5 → sourcecode-2.5.7}/src/sourcecode/detectors/go.py +0 -0
  47. {sourcecode-2.5.5 → sourcecode-2.5.7}/src/sourcecode/detectors/heuristic.py +0 -0
  48. {sourcecode-2.5.5 → sourcecode-2.5.7}/src/sourcecode/detectors/hybrid.py +0 -0
  49. {sourcecode-2.5.5 → sourcecode-2.5.7}/src/sourcecode/detectors/java.py +0 -0
  50. {sourcecode-2.5.5 → sourcecode-2.5.7}/src/sourcecode/detectors/jvm_ext.py +0 -0
  51. {sourcecode-2.5.5 → sourcecode-2.5.7}/src/sourcecode/detectors/nodejs.py +0 -0
  52. {sourcecode-2.5.5 → sourcecode-2.5.7}/src/sourcecode/detectors/parsers.py +0 -0
  53. {sourcecode-2.5.5 → sourcecode-2.5.7}/src/sourcecode/detectors/php.py +0 -0
  54. {sourcecode-2.5.5 → sourcecode-2.5.7}/src/sourcecode/detectors/project.py +0 -0
  55. {sourcecode-2.5.5 → sourcecode-2.5.7}/src/sourcecode/detectors/python.py +0 -0
  56. {sourcecode-2.5.5 → sourcecode-2.5.7}/src/sourcecode/detectors/ruby.py +0 -0
  57. {sourcecode-2.5.5 → sourcecode-2.5.7}/src/sourcecode/detectors/rust.py +0 -0
  58. {sourcecode-2.5.5 → sourcecode-2.5.7}/src/sourcecode/detectors/systems.py +0 -0
  59. {sourcecode-2.5.5 → sourcecode-2.5.7}/src/sourcecode/detectors/terraform.py +0 -0
  60. {sourcecode-2.5.5 → sourcecode-2.5.7}/src/sourcecode/detectors/tooling.py +0 -0
  61. {sourcecode-2.5.5 → sourcecode-2.5.7}/src/sourcecode/doc_analyzer.py +0 -0
  62. {sourcecode-2.5.5 → sourcecode-2.5.7}/src/sourcecode/dynamic_argument_surface.py +0 -0
  63. {sourcecode-2.5.5 → sourcecode-2.5.7}/src/sourcecode/endpoint_literals.py +0 -0
  64. {sourcecode-2.5.5 → sourcecode-2.5.7}/src/sourcecode/endpoint_metrics.py +0 -0
  65. {sourcecode-2.5.5 → sourcecode-2.5.7}/src/sourcecode/entrypoint_classifier.py +0 -0
  66. {sourcecode-2.5.5 → sourcecode-2.5.7}/src/sourcecode/env_analyzer.py +0 -0
  67. {sourcecode-2.5.5 → sourcecode-2.5.7}/src/sourcecode/error_schema.py +0 -0
  68. {sourcecode-2.5.5 → sourcecode-2.5.7}/src/sourcecode/evidence_provider.py +0 -0
  69. {sourcecode-2.5.5 → sourcecode-2.5.7}/src/sourcecode/explain.py +0 -0
  70. {sourcecode-2.5.5 → sourcecode-2.5.7}/src/sourcecode/file_chunker.py +0 -0
  71. {sourcecode-2.5.5 → sourcecode-2.5.7}/src/sourcecode/file_classifier.py +0 -0
  72. {sourcecode-2.5.5 → sourcecode-2.5.7}/src/sourcecode/format_contract.py +0 -0
  73. {sourcecode-2.5.5 → sourcecode-2.5.7}/src/sourcecode/fqn_utils.py +0 -0
  74. {sourcecode-2.5.5 → sourcecode-2.5.7}/src/sourcecode/git_analyzer.py +0 -0
  75. {sourcecode-2.5.5 → sourcecode-2.5.7}/src/sourcecode/graph_analyzer.py +0 -0
  76. {sourcecode-2.5.5 → sourcecode-2.5.7}/src/sourcecode/graph_evidence.py +0 -0
  77. {sourcecode-2.5.5 → sourcecode-2.5.7}/src/sourcecode/hibernate_strat.py +0 -0
  78. {sourcecode-2.5.5 → sourcecode-2.5.7}/src/sourcecode/jdk_exports.py +0 -0
  79. {sourcecode-2.5.5 → sourcecode-2.5.7}/src/sourcecode/license.py +0 -0
  80. {sourcecode-2.5.5 → sourcecode-2.5.7}/src/sourcecode/mcp/__init__.py +0 -0
  81. {sourcecode-2.5.5 → sourcecode-2.5.7}/src/sourcecode/mcp/onboarding/__init__.py +0 -0
  82. {sourcecode-2.5.5 → sourcecode-2.5.7}/src/sourcecode/mcp/onboarding/applier.py +0 -0
  83. {sourcecode-2.5.5 → sourcecode-2.5.7}/src/sourcecode/mcp/onboarding/backup.py +0 -0
  84. {sourcecode-2.5.5 → sourcecode-2.5.7}/src/sourcecode/mcp/onboarding/detector.py +0 -0
  85. {sourcecode-2.5.5 → sourcecode-2.5.7}/src/sourcecode/mcp/onboarding/planner.py +0 -0
  86. {sourcecode-2.5.5 → sourcecode-2.5.7}/src/sourcecode/mcp/orchestrator.py +0 -0
  87. {sourcecode-2.5.5 → sourcecode-2.5.7}/src/sourcecode/mcp/registry.py +0 -0
  88. {sourcecode-2.5.5 → sourcecode-2.5.7}/src/sourcecode/mcp/runner.py +0 -0
  89. {sourcecode-2.5.5 → sourcecode-2.5.7}/src/sourcecode/mcp/server.py +0 -0
  90. {sourcecode-2.5.5 → sourcecode-2.5.7}/src/sourcecode/mcp_nudge.py +0 -0
  91. {sourcecode-2.5.5 → sourcecode-2.5.7}/src/sourcecode/metrics_analyzer.py +0 -0
  92. {sourcecode-2.5.5 → sourcecode-2.5.7}/src/sourcecode/migrate_check.py +0 -0
  93. {sourcecode-2.5.5 → sourcecode-2.5.7}/src/sourcecode/openapi_surface.py +0 -0
  94. {sourcecode-2.5.5 → sourcecode-2.5.7}/src/sourcecode/output_budget.py +0 -0
  95. {sourcecode-2.5.5 → sourcecode-2.5.7}/src/sourcecode/path_filters.py +0 -0
  96. {sourcecode-2.5.5 → sourcecode-2.5.7}/src/sourcecode/pr_comment_renderer.py +0 -0
  97. {sourcecode-2.5.5 → sourcecode-2.5.7}/src/sourcecode/pr_impact.py +0 -0
  98. {sourcecode-2.5.5 → sourcecode-2.5.7}/src/sourcecode/prepare_context.py +0 -0
  99. {sourcecode-2.5.5 → sourcecode-2.5.7}/src/sourcecode/progress.py +0 -0
  100. {sourcecode-2.5.5 → sourcecode-2.5.7}/src/sourcecode/ranking_engine.py +0 -0
  101. {sourcecode-2.5.5 → sourcecode-2.5.7}/src/sourcecode/redactor.py +0 -0
  102. {sourcecode-2.5.5 → sourcecode-2.5.7}/src/sourcecode/relevance_scorer.py +0 -0
  103. {sourcecode-2.5.5 → sourcecode-2.5.7}/src/sourcecode/rename_refactor.py +0 -0
  104. {sourcecode-2.5.5 → sourcecode-2.5.7}/src/sourcecode/repo_classifier.py +0 -0
  105. {sourcecode-2.5.5 → sourcecode-2.5.7}/src/sourcecode/repository_ir.py +0 -0
  106. {sourcecode-2.5.5 → sourcecode-2.5.7}/src/sourcecode/retrieval/__init__.py +0 -0
  107. {sourcecode-2.5.5 → sourcecode-2.5.7}/src/sourcecode/retrieval/context.py +0 -0
  108. {sourcecode-2.5.5 → sourcecode-2.5.7}/src/sourcecode/retrieval/errors.py +0 -0
  109. {sourcecode-2.5.5 → sourcecode-2.5.7}/src/sourcecode/retrieval/executor.py +0 -0
  110. {sourcecode-2.5.5 → sourcecode-2.5.7}/src/sourcecode/retrieval/planner.py +0 -0
  111. {sourcecode-2.5.5 → sourcecode-2.5.7}/src/sourcecode/retrieval/query.py +0 -0
  112. {sourcecode-2.5.5 → sourcecode-2.5.7}/src/sourcecode/retrieval/request.py +0 -0
  113. {sourcecode-2.5.5 → sourcecode-2.5.7}/src/sourcecode/retrieval/resolution.py +0 -0
  114. {sourcecode-2.5.5 → sourcecode-2.5.7}/src/sourcecode/retrieval/result.py +0 -0
  115. {sourcecode-2.5.5 → sourcecode-2.5.7}/src/sourcecode/retrieval/retriever.py +0 -0
  116. {sourcecode-2.5.5 → sourcecode-2.5.7}/src/sourcecode/retrieval/runtime.py +0 -0
  117. {sourcecode-2.5.5 → sourcecode-2.5.7}/src/sourcecode/retrieval/steps.py +0 -0
  118. {sourcecode-2.5.5 → sourcecode-2.5.7}/src/sourcecode/retrieval/steps_endpoint.py +0 -0
  119. {sourcecode-2.5.5 → sourcecode-2.5.7}/src/sourcecode/retrieval/steps_graph.py +0 -0
  120. {sourcecode-2.5.5 → sourcecode-2.5.7}/src/sourcecode/retrieval/steps_impact.py +0 -0
  121. {sourcecode-2.5.5 → sourcecode-2.5.7}/src/sourcecode/retrieval/steps_intf.py +0 -0
  122. {sourcecode-2.5.5 → sourcecode-2.5.7}/src/sourcecode/retrieval/steps_struct.py +0 -0
  123. {sourcecode-2.5.5 → sourcecode-2.5.7}/src/sourcecode/retrieval/steps_txsec.py +0 -0
  124. {sourcecode-2.5.5 → sourcecode-2.5.7}/src/sourcecode/ris.py +0 -0
  125. {sourcecode-2.5.5 → sourcecode-2.5.7}/src/sourcecode/runtime_classifier.py +0 -0
  126. {sourcecode-2.5.5 → sourcecode-2.5.7}/src/sourcecode/scanner.py +0 -0
  127. {sourcecode-2.5.5 → sourcecode-2.5.7}/src/sourcecode/schema.py +0 -0
  128. {sourcecode-2.5.5 → sourcecode-2.5.7}/src/sourcecode/security_config.py +0 -0
  129. {sourcecode-2.5.5 → sourcecode-2.5.7}/src/sourcecode/security_posture.py +0 -0
  130. {sourcecode-2.5.5 → sourcecode-2.5.7}/src/sourcecode/semantic_analyzer.py +0 -0
  131. {sourcecode-2.5.5 → sourcecode-2.5.7}/src/sourcecode/semantic_impact_engine.py +0 -0
  132. {sourcecode-2.5.5 → sourcecode-2.5.7}/src/sourcecode/semantic_integration_engine.py +0 -0
  133. {sourcecode-2.5.5 → sourcecode-2.5.7}/src/sourcecode/semantic_services.py +0 -0
  134. {sourcecode-2.5.5 → sourcecode-2.5.7}/src/sourcecode/serializer.py +0 -0
  135. {sourcecode-2.5.5 → sourcecode-2.5.7}/src/sourcecode/spring_event_topology.py +0 -0
  136. {sourcecode-2.5.5 → sourcecode-2.5.7}/src/sourcecode/spring_findings.py +0 -0
  137. {sourcecode-2.5.5 → sourcecode-2.5.7}/src/sourcecode/spring_impact.py +0 -0
  138. {sourcecode-2.5.5 → sourcecode-2.5.7}/src/sourcecode/spring_model.py +0 -0
  139. {sourcecode-2.5.5 → sourcecode-2.5.7}/src/sourcecode/spring_security_audit.py +0 -0
  140. {sourcecode-2.5.5 → sourcecode-2.5.7}/src/sourcecode/spring_semantic.py +0 -0
  141. {sourcecode-2.5.5 → sourcecode-2.5.7}/src/sourcecode/spring_tx_analyzer.py +0 -0
  142. {sourcecode-2.5.5 → sourcecode-2.5.7}/src/sourcecode/telemetry/__init__.py +0 -0
  143. {sourcecode-2.5.5 → sourcecode-2.5.7}/src/sourcecode/telemetry/config.py +0 -0
  144. {sourcecode-2.5.5 → sourcecode-2.5.7}/src/sourcecode/telemetry/consent.py +0 -0
  145. {sourcecode-2.5.5 → sourcecode-2.5.7}/src/sourcecode/telemetry/events.py +0 -0
  146. {sourcecode-2.5.5 → sourcecode-2.5.7}/src/sourcecode/telemetry/filters.py +0 -0
  147. {sourcecode-2.5.5 → sourcecode-2.5.7}/src/sourcecode/telemetry/transport.py +0 -0
  148. {sourcecode-2.5.5 → sourcecode-2.5.7}/src/sourcecode/tree_utils.py +0 -0
  149. {sourcecode-2.5.5 → sourcecode-2.5.7}/src/sourcecode/type_usage_surface.py +0 -0
  150. {sourcecode-2.5.5 → sourcecode-2.5.7}/src/sourcecode/validation_inference.py +0 -0
  151. {sourcecode-2.5.5 → sourcecode-2.5.7}/src/sourcecode/validation_surface.py +0 -0
  152. {sourcecode-2.5.5 → sourcecode-2.5.7}/src/sourcecode/version_check.py +0 -0
  153. {sourcecode-2.5.5 → sourcecode-2.5.7}/src/sourcecode/workspace.py +0 -0
  154. {sourcecode-2.5.5 → sourcecode-2.5.7}/supabase/functions/README.md +0 -0
  155. {sourcecode-2.5.5 → sourcecode-2.5.7}/supabase/functions/get-license/index.ts +0 -0
  156. {sourcecode-2.5.5 → sourcecode-2.5.7}/supabase/functions/lemonsqueezy-webhook/index.ts +0 -0
  157. {sourcecode-2.5.5 → sourcecode-2.5.7}/supabase/functions/telemetry/index.ts +0 -0
  158. {sourcecode-2.5.5 → sourcecode-2.5.7}/supabase/sql/license_event_ordering.sql +0 -0
  159. {sourcecode-2.5.5 → sourcecode-2.5.7}/supabase/sql/licensing_schema.sql +0 -0
  160. {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
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sourcecode
3
- Version: 2.5.5
3
+ Version: 2.5.7
4
4
  Summary: Persistent structural context and ultra-fast repeated analysis for AI coding agents
5
5
  License-File: LICENSE
6
6
  Keywords: agents,ai,codebase,context,developer-tools,llm
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "sourcecode"
7
- version = "2.5.5"
7
+ version = "2.5.7"
8
8
  description = "Persistent structural context and ultra-fast repeated analysis for AI coding agents"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.9"
@@ -4,4 +4,4 @@ ASK Engine is the product. ``ask`` is the canonical CLI command; ``sourcecode``
4
4
  the legacy compatibility alias and the Python/PyPI package name. See
5
5
  docs/PRODUCT_IDENTITY.md (normative)."""
6
6
 
7
- __version__ = "2.5.5"
7
+ __version__ = "2.5.7"
@@ -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 incoherent_layers
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
- _GENERIC_NAMES = {"utils", "helpers", "common", "shared", "misc", "core", "root", ""}
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 _bad in incoherent_layers(
675
- pattern_name,
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 (part not in _SRC_TRANSPARENT
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
- lines.append(sig[0].upper() + sig[1:] + ".")
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"Stack: {types}.")
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"Target: {fws}.")
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"Patrón detectado: {pattern}.")
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"Frameworks: {', '.join(framework_names)}.")
495
+ lines.append(f"{label} frameworks: {', '.join(framework_names)}.")
447
496
 
448
497
  if not lines:
449
- lines.append("Solución .NET detectada.")
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)")