sourcecode 2.5.4__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.
Files changed (161) hide show
  1. {sourcecode-2.5.4 → sourcecode-2.5.6}/CHANGELOG.md +116 -0
  2. {sourcecode-2.5.4 → sourcecode-2.5.6}/PKG-INFO +1 -1
  3. {sourcecode-2.5.4 → sourcecode-2.5.6}/pyproject.toml +1 -1
  4. {sourcecode-2.5.4 → sourcecode-2.5.6}/src/sourcecode/__init__.py +1 -1
  5. {sourcecode-2.5.4 → sourcecode-2.5.6}/src/sourcecode/architecture_analyzer.py +75 -4
  6. sourcecode-2.5.6/src/sourcecode/reconciliation.py +457 -0
  7. {sourcecode-2.5.4 → sourcecode-2.5.6}/src/sourcecode/summarizer.py +27 -5
  8. sourcecode-2.5.4/src/sourcecode/reconciliation.py +0 -201
  9. {sourcecode-2.5.4 → sourcecode-2.5.6}/.github/workflows/build-windows.yml +0 -0
  10. {sourcecode-2.5.4 → sourcecode-2.5.6}/.gitignore +0 -0
  11. {sourcecode-2.5.4 → sourcecode-2.5.6}/.ruff.toml +0 -0
  12. {sourcecode-2.5.4 → sourcecode-2.5.6}/CONTRIBUTING.md +0 -0
  13. {sourcecode-2.5.4 → sourcecode-2.5.6}/LICENSE +0 -0
  14. {sourcecode-2.5.4 → sourcecode-2.5.6}/README.md +0 -0
  15. {sourcecode-2.5.4 → sourcecode-2.5.6}/SECURITY.md +0 -0
  16. {sourcecode-2.5.4 → sourcecode-2.5.6}/raw +0 -0
  17. {sourcecode-2.5.4 → sourcecode-2.5.6}/scripts/compare_integration_engines.py +0 -0
  18. {sourcecode-2.5.4 → sourcecode-2.5.6}/scripts/customer_smoke_test.sh +0 -0
  19. {sourcecode-2.5.4 → sourcecode-2.5.6}/scripts/generate_jdk_exports.py +0 -0
  20. {sourcecode-2.5.4 → sourcecode-2.5.6}/src/sourcecode/adaptive_scanner.py +0 -0
  21. {sourcecode-2.5.4 → sourcecode-2.5.6}/src/sourcecode/archetype.py +0 -0
  22. {sourcecode-2.5.4 → sourcecode-2.5.6}/src/sourcecode/architecture_summary.py +0 -0
  23. {sourcecode-2.5.4 → sourcecode-2.5.6}/src/sourcecode/ast_extractor.py +0 -0
  24. {sourcecode-2.5.4 → sourcecode-2.5.6}/src/sourcecode/cache.py +0 -0
  25. {sourcecode-2.5.4 → sourcecode-2.5.6}/src/sourcecode/call_surface.py +0 -0
  26. {sourcecode-2.5.4 → sourcecode-2.5.6}/src/sourcecode/caller_metrics.py +0 -0
  27. {sourcecode-2.5.4 → sourcecode-2.5.6}/src/sourcecode/canonical_ir.py +0 -0
  28. {sourcecode-2.5.4 → sourcecode-2.5.6}/src/sourcecode/cir_graphs.py +0 -0
  29. {sourcecode-2.5.4 → sourcecode-2.5.6}/src/sourcecode/classifier.py +0 -0
  30. {sourcecode-2.5.4 → sourcecode-2.5.6}/src/sourcecode/cli.py +0 -0
  31. {sourcecode-2.5.4 → sourcecode-2.5.6}/src/sourcecode/code_notes_analyzer.py +0 -0
  32. {sourcecode-2.5.4 → sourcecode-2.5.6}/src/sourcecode/confidence_analyzer.py +0 -0
  33. {sourcecode-2.5.4 → sourcecode-2.5.6}/src/sourcecode/context_cache.py +0 -0
  34. {sourcecode-2.5.4 → sourcecode-2.5.6}/src/sourcecode/context_graph.py +0 -0
  35. {sourcecode-2.5.4 → sourcecode-2.5.6}/src/sourcecode/context_scorer.py +0 -0
  36. {sourcecode-2.5.4 → sourcecode-2.5.6}/src/sourcecode/context_summarizer.py +0 -0
  37. {sourcecode-2.5.4 → sourcecode-2.5.6}/src/sourcecode/contract_model.py +0 -0
  38. {sourcecode-2.5.4 → sourcecode-2.5.6}/src/sourcecode/contract_pipeline.py +0 -0
  39. {sourcecode-2.5.4 → sourcecode-2.5.6}/src/sourcecode/coverage_parser.py +0 -0
  40. {sourcecode-2.5.4 → sourcecode-2.5.6}/src/sourcecode/dependency_analyzer.py +0 -0
  41. {sourcecode-2.5.4 → sourcecode-2.5.6}/src/sourcecode/detectors/__init__.py +0 -0
  42. {sourcecode-2.5.4 → sourcecode-2.5.6}/src/sourcecode/detectors/base.py +0 -0
  43. {sourcecode-2.5.4 → sourcecode-2.5.6}/src/sourcecode/detectors/csproj_parser.py +0 -0
  44. {sourcecode-2.5.4 → sourcecode-2.5.6}/src/sourcecode/detectors/dart.py +0 -0
  45. {sourcecode-2.5.4 → sourcecode-2.5.6}/src/sourcecode/detectors/dotnet.py +0 -0
  46. {sourcecode-2.5.4 → sourcecode-2.5.6}/src/sourcecode/detectors/elixir.py +0 -0
  47. {sourcecode-2.5.4 → sourcecode-2.5.6}/src/sourcecode/detectors/go.py +0 -0
  48. {sourcecode-2.5.4 → sourcecode-2.5.6}/src/sourcecode/detectors/heuristic.py +0 -0
  49. {sourcecode-2.5.4 → sourcecode-2.5.6}/src/sourcecode/detectors/hybrid.py +0 -0
  50. {sourcecode-2.5.4 → sourcecode-2.5.6}/src/sourcecode/detectors/java.py +0 -0
  51. {sourcecode-2.5.4 → sourcecode-2.5.6}/src/sourcecode/detectors/jvm_ext.py +0 -0
  52. {sourcecode-2.5.4 → sourcecode-2.5.6}/src/sourcecode/detectors/nodejs.py +0 -0
  53. {sourcecode-2.5.4 → sourcecode-2.5.6}/src/sourcecode/detectors/parsers.py +0 -0
  54. {sourcecode-2.5.4 → sourcecode-2.5.6}/src/sourcecode/detectors/php.py +0 -0
  55. {sourcecode-2.5.4 → sourcecode-2.5.6}/src/sourcecode/detectors/project.py +0 -0
  56. {sourcecode-2.5.4 → sourcecode-2.5.6}/src/sourcecode/detectors/python.py +0 -0
  57. {sourcecode-2.5.4 → sourcecode-2.5.6}/src/sourcecode/detectors/ruby.py +0 -0
  58. {sourcecode-2.5.4 → sourcecode-2.5.6}/src/sourcecode/detectors/rust.py +0 -0
  59. {sourcecode-2.5.4 → sourcecode-2.5.6}/src/sourcecode/detectors/systems.py +0 -0
  60. {sourcecode-2.5.4 → sourcecode-2.5.6}/src/sourcecode/detectors/terraform.py +0 -0
  61. {sourcecode-2.5.4 → sourcecode-2.5.6}/src/sourcecode/detectors/tooling.py +0 -0
  62. {sourcecode-2.5.4 → sourcecode-2.5.6}/src/sourcecode/doc_analyzer.py +0 -0
  63. {sourcecode-2.5.4 → sourcecode-2.5.6}/src/sourcecode/dynamic_argument_surface.py +0 -0
  64. {sourcecode-2.5.4 → sourcecode-2.5.6}/src/sourcecode/endpoint_literals.py +0 -0
  65. {sourcecode-2.5.4 → sourcecode-2.5.6}/src/sourcecode/endpoint_metrics.py +0 -0
  66. {sourcecode-2.5.4 → sourcecode-2.5.6}/src/sourcecode/entrypoint_classifier.py +0 -0
  67. {sourcecode-2.5.4 → sourcecode-2.5.6}/src/sourcecode/env_analyzer.py +0 -0
  68. {sourcecode-2.5.4 → sourcecode-2.5.6}/src/sourcecode/error_schema.py +0 -0
  69. {sourcecode-2.5.4 → sourcecode-2.5.6}/src/sourcecode/evidence_provider.py +0 -0
  70. {sourcecode-2.5.4 → sourcecode-2.5.6}/src/sourcecode/explain.py +0 -0
  71. {sourcecode-2.5.4 → sourcecode-2.5.6}/src/sourcecode/file_chunker.py +0 -0
  72. {sourcecode-2.5.4 → sourcecode-2.5.6}/src/sourcecode/file_classifier.py +0 -0
  73. {sourcecode-2.5.4 → sourcecode-2.5.6}/src/sourcecode/format_contract.py +0 -0
  74. {sourcecode-2.5.4 → sourcecode-2.5.6}/src/sourcecode/fqn_utils.py +0 -0
  75. {sourcecode-2.5.4 → sourcecode-2.5.6}/src/sourcecode/git_analyzer.py +0 -0
  76. {sourcecode-2.5.4 → sourcecode-2.5.6}/src/sourcecode/graph_analyzer.py +0 -0
  77. {sourcecode-2.5.4 → sourcecode-2.5.6}/src/sourcecode/graph_evidence.py +0 -0
  78. {sourcecode-2.5.4 → sourcecode-2.5.6}/src/sourcecode/hibernate_strat.py +0 -0
  79. {sourcecode-2.5.4 → sourcecode-2.5.6}/src/sourcecode/jdk_exports.py +0 -0
  80. {sourcecode-2.5.4 → sourcecode-2.5.6}/src/sourcecode/license.py +0 -0
  81. {sourcecode-2.5.4 → sourcecode-2.5.6}/src/sourcecode/mcp/__init__.py +0 -0
  82. {sourcecode-2.5.4 → sourcecode-2.5.6}/src/sourcecode/mcp/onboarding/__init__.py +0 -0
  83. {sourcecode-2.5.4 → sourcecode-2.5.6}/src/sourcecode/mcp/onboarding/applier.py +0 -0
  84. {sourcecode-2.5.4 → sourcecode-2.5.6}/src/sourcecode/mcp/onboarding/backup.py +0 -0
  85. {sourcecode-2.5.4 → sourcecode-2.5.6}/src/sourcecode/mcp/onboarding/detector.py +0 -0
  86. {sourcecode-2.5.4 → sourcecode-2.5.6}/src/sourcecode/mcp/onboarding/planner.py +0 -0
  87. {sourcecode-2.5.4 → sourcecode-2.5.6}/src/sourcecode/mcp/orchestrator.py +0 -0
  88. {sourcecode-2.5.4 → sourcecode-2.5.6}/src/sourcecode/mcp/registry.py +0 -0
  89. {sourcecode-2.5.4 → sourcecode-2.5.6}/src/sourcecode/mcp/runner.py +0 -0
  90. {sourcecode-2.5.4 → sourcecode-2.5.6}/src/sourcecode/mcp/server.py +0 -0
  91. {sourcecode-2.5.4 → sourcecode-2.5.6}/src/sourcecode/mcp_nudge.py +0 -0
  92. {sourcecode-2.5.4 → sourcecode-2.5.6}/src/sourcecode/metrics_analyzer.py +0 -0
  93. {sourcecode-2.5.4 → sourcecode-2.5.6}/src/sourcecode/migrate_check.py +0 -0
  94. {sourcecode-2.5.4 → sourcecode-2.5.6}/src/sourcecode/openapi_surface.py +0 -0
  95. {sourcecode-2.5.4 → sourcecode-2.5.6}/src/sourcecode/output_budget.py +0 -0
  96. {sourcecode-2.5.4 → sourcecode-2.5.6}/src/sourcecode/path_filters.py +0 -0
  97. {sourcecode-2.5.4 → sourcecode-2.5.6}/src/sourcecode/pr_comment_renderer.py +0 -0
  98. {sourcecode-2.5.4 → sourcecode-2.5.6}/src/sourcecode/pr_impact.py +0 -0
  99. {sourcecode-2.5.4 → sourcecode-2.5.6}/src/sourcecode/prepare_context.py +0 -0
  100. {sourcecode-2.5.4 → sourcecode-2.5.6}/src/sourcecode/progress.py +0 -0
  101. {sourcecode-2.5.4 → sourcecode-2.5.6}/src/sourcecode/ranking_engine.py +0 -0
  102. {sourcecode-2.5.4 → sourcecode-2.5.6}/src/sourcecode/redactor.py +0 -0
  103. {sourcecode-2.5.4 → sourcecode-2.5.6}/src/sourcecode/relevance_scorer.py +0 -0
  104. {sourcecode-2.5.4 → sourcecode-2.5.6}/src/sourcecode/rename_refactor.py +0 -0
  105. {sourcecode-2.5.4 → sourcecode-2.5.6}/src/sourcecode/repo_classifier.py +0 -0
  106. {sourcecode-2.5.4 → sourcecode-2.5.6}/src/sourcecode/repository_ir.py +0 -0
  107. {sourcecode-2.5.4 → sourcecode-2.5.6}/src/sourcecode/retrieval/__init__.py +0 -0
  108. {sourcecode-2.5.4 → sourcecode-2.5.6}/src/sourcecode/retrieval/context.py +0 -0
  109. {sourcecode-2.5.4 → sourcecode-2.5.6}/src/sourcecode/retrieval/errors.py +0 -0
  110. {sourcecode-2.5.4 → sourcecode-2.5.6}/src/sourcecode/retrieval/executor.py +0 -0
  111. {sourcecode-2.5.4 → sourcecode-2.5.6}/src/sourcecode/retrieval/planner.py +0 -0
  112. {sourcecode-2.5.4 → sourcecode-2.5.6}/src/sourcecode/retrieval/query.py +0 -0
  113. {sourcecode-2.5.4 → sourcecode-2.5.6}/src/sourcecode/retrieval/request.py +0 -0
  114. {sourcecode-2.5.4 → sourcecode-2.5.6}/src/sourcecode/retrieval/resolution.py +0 -0
  115. {sourcecode-2.5.4 → sourcecode-2.5.6}/src/sourcecode/retrieval/result.py +0 -0
  116. {sourcecode-2.5.4 → sourcecode-2.5.6}/src/sourcecode/retrieval/retriever.py +0 -0
  117. {sourcecode-2.5.4 → sourcecode-2.5.6}/src/sourcecode/retrieval/runtime.py +0 -0
  118. {sourcecode-2.5.4 → sourcecode-2.5.6}/src/sourcecode/retrieval/steps.py +0 -0
  119. {sourcecode-2.5.4 → sourcecode-2.5.6}/src/sourcecode/retrieval/steps_endpoint.py +0 -0
  120. {sourcecode-2.5.4 → sourcecode-2.5.6}/src/sourcecode/retrieval/steps_graph.py +0 -0
  121. {sourcecode-2.5.4 → sourcecode-2.5.6}/src/sourcecode/retrieval/steps_impact.py +0 -0
  122. {sourcecode-2.5.4 → sourcecode-2.5.6}/src/sourcecode/retrieval/steps_intf.py +0 -0
  123. {sourcecode-2.5.4 → sourcecode-2.5.6}/src/sourcecode/retrieval/steps_struct.py +0 -0
  124. {sourcecode-2.5.4 → sourcecode-2.5.6}/src/sourcecode/retrieval/steps_txsec.py +0 -0
  125. {sourcecode-2.5.4 → sourcecode-2.5.6}/src/sourcecode/ris.py +0 -0
  126. {sourcecode-2.5.4 → sourcecode-2.5.6}/src/sourcecode/runtime_classifier.py +0 -0
  127. {sourcecode-2.5.4 → sourcecode-2.5.6}/src/sourcecode/scanner.py +0 -0
  128. {sourcecode-2.5.4 → sourcecode-2.5.6}/src/sourcecode/schema.py +0 -0
  129. {sourcecode-2.5.4 → sourcecode-2.5.6}/src/sourcecode/security_config.py +0 -0
  130. {sourcecode-2.5.4 → sourcecode-2.5.6}/src/sourcecode/security_posture.py +0 -0
  131. {sourcecode-2.5.4 → sourcecode-2.5.6}/src/sourcecode/semantic_analyzer.py +0 -0
  132. {sourcecode-2.5.4 → sourcecode-2.5.6}/src/sourcecode/semantic_impact_engine.py +0 -0
  133. {sourcecode-2.5.4 → sourcecode-2.5.6}/src/sourcecode/semantic_integration_engine.py +0 -0
  134. {sourcecode-2.5.4 → sourcecode-2.5.6}/src/sourcecode/semantic_services.py +0 -0
  135. {sourcecode-2.5.4 → sourcecode-2.5.6}/src/sourcecode/serializer.py +0 -0
  136. {sourcecode-2.5.4 → sourcecode-2.5.6}/src/sourcecode/spring_event_topology.py +0 -0
  137. {sourcecode-2.5.4 → sourcecode-2.5.6}/src/sourcecode/spring_findings.py +0 -0
  138. {sourcecode-2.5.4 → sourcecode-2.5.6}/src/sourcecode/spring_impact.py +0 -0
  139. {sourcecode-2.5.4 → sourcecode-2.5.6}/src/sourcecode/spring_model.py +0 -0
  140. {sourcecode-2.5.4 → sourcecode-2.5.6}/src/sourcecode/spring_security_audit.py +0 -0
  141. {sourcecode-2.5.4 → sourcecode-2.5.6}/src/sourcecode/spring_semantic.py +0 -0
  142. {sourcecode-2.5.4 → sourcecode-2.5.6}/src/sourcecode/spring_tx_analyzer.py +0 -0
  143. {sourcecode-2.5.4 → sourcecode-2.5.6}/src/sourcecode/telemetry/__init__.py +0 -0
  144. {sourcecode-2.5.4 → sourcecode-2.5.6}/src/sourcecode/telemetry/config.py +0 -0
  145. {sourcecode-2.5.4 → sourcecode-2.5.6}/src/sourcecode/telemetry/consent.py +0 -0
  146. {sourcecode-2.5.4 → sourcecode-2.5.6}/src/sourcecode/telemetry/events.py +0 -0
  147. {sourcecode-2.5.4 → sourcecode-2.5.6}/src/sourcecode/telemetry/filters.py +0 -0
  148. {sourcecode-2.5.4 → sourcecode-2.5.6}/src/sourcecode/telemetry/transport.py +0 -0
  149. {sourcecode-2.5.4 → sourcecode-2.5.6}/src/sourcecode/tree_utils.py +0 -0
  150. {sourcecode-2.5.4 → sourcecode-2.5.6}/src/sourcecode/type_usage_surface.py +0 -0
  151. {sourcecode-2.5.4 → sourcecode-2.5.6}/src/sourcecode/validation_inference.py +0 -0
  152. {sourcecode-2.5.4 → sourcecode-2.5.6}/src/sourcecode/validation_surface.py +0 -0
  153. {sourcecode-2.5.4 → sourcecode-2.5.6}/src/sourcecode/version_check.py +0 -0
  154. {sourcecode-2.5.4 → sourcecode-2.5.6}/src/sourcecode/workspace.py +0 -0
  155. {sourcecode-2.5.4 → sourcecode-2.5.6}/supabase/functions/README.md +0 -0
  156. {sourcecode-2.5.4 → sourcecode-2.5.6}/supabase/functions/get-license/index.ts +0 -0
  157. {sourcecode-2.5.4 → sourcecode-2.5.6}/supabase/functions/lemonsqueezy-webhook/index.ts +0 -0
  158. {sourcecode-2.5.4 → sourcecode-2.5.6}/supabase/functions/telemetry/index.ts +0 -0
  159. {sourcecode-2.5.4 → sourcecode-2.5.6}/supabase/sql/license_event_ordering.sql +0 -0
  160. {sourcecode-2.5.4 → sourcecode-2.5.6}/supabase/sql/licensing_schema.sql +0 -0
  161. {sourcecode-2.5.4 → sourcecode-2.5.6}/supabase/sql/telemetry_events.sql +0 -0
@@ -1,5 +1,121 @@
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
+
63
+ ## [2.5.5] — 2026-07-16
64
+
65
+ **An architecture claim must belong to one program.** Third Evidence
66
+ Reconciliation rule, extending the layer from the impact/extraction surfaces to
67
+ the narrative one.
68
+
69
+ ### Fixed
70
+
71
+ - **A React SPA supplied the "view layer" of a Java MVC claim (SIM-3)**
72
+ (`reconciliation.py`, `architecture_analyzer.py`, `summarizer.py`): keycloak
73
+ read *"MVC pattern with controller, model, view layers"* — the `view` was
74
+ `js/apps/*/src/components` (87 `.tsx`, zero Java), half the `controller` layer
75
+ was the Kubernetes operator's reconcile controllers, and both sat beside a
76
+ 1088-file Java `model`. Directory names are language-blind, so one pattern was
77
+ assembled across two separate programs; P1-D's mass gate cannot catch it,
78
+ because the SPA has genuine mass.
79
+
80
+ New rule **`layer_language_coherence`** — the layer table cross-checked
81
+ against the file-language census. A layer is incoherent iff it holds ZERO
82
+ files of the repo's runtime and at least one file of another: it is a
83
+ different program, not a facet of this architecture. Runtimes are grouped by
84
+ FAMILY (`.java`/`.kt`/`.scala`/`.groovy` = one JVM program), so a Kotlin view
85
+ beside a Java model is never a contradiction. A layer holding no runtime files
86
+ at all (templates, assets) is never flagged — that carve-out is what keeps a
87
+ genuine Spring-MVC `templates/` view intact. Patterns describing repo
88
+ COMPOSITION rather than one program's internals (`fullstack`,
89
+ `microservices`, `monorepo`) are exempt: spanning runtimes is their point.
90
+ The dominant runtime is taken from the whole repo, never from the matched
91
+ layers — which directories match a keyword table is the thing under
92
+ suspicion, so it cannot also be the arbiter. Applied to BOTH duplicated layer
93
+ tables (architecture prose + headline), since fixing one leaves the other
94
+ lying.
95
+
96
+ - **"core" minted an onion/hexagonal domain layer (P1-D residue)**
97
+ (`architecture_analyzer.py`, `summarizer.py`): P1-D removed the keywords that
98
+ do not attest their role (`api`, `store`, `db`) from the layered/fullstack/mvc
99
+ tables but left `core` in onion/hexagonal. `core` names a module's importance,
100
+ never a domain layer, and nearly every large JVM repo has one: keycloak's
101
+ `core/` (623 Java files) plus a `representations/adapters/config` package read
102
+ *"Onion Architecture with domain, adapters layers"*.
103
+
104
+ - **Hexagonal was claimed without ports (P1-D residue)**
105
+ (`architecture_analyzer.py`, `summarizer.py`): a pattern must show its
106
+ defining trait, not just any two of its layers — the mechanism that already
107
+ requires a `view` for MVC. Hexagonal is literally "Ports and Adapters", yet
108
+ `models/` + `adapters/` alone was minting it; onion now requires its domain
109
+ centre. The headline's table enforces required keys too, so it can no longer
110
+ contradict the architecture prose.
111
+
112
+ Validation across 10 field-test repos through the real CLI: keycloak's false
113
+ MVC claim became *"Monorepo workspace with apps, packages layers"* — verified
114
+ true in source (`js/apps/{admin-ui,account-ui}`, `js/libs/*`,
115
+ `pnpm-workspace.yaml`). Broadleaf (layered, all four layers), petclinic,
116
+ jobrunr, neo4j, openmrs, eureka, alfresco, ofbiz and jenkins: byte-identical
117
+ prose, zero collateral. 18 new tests; suite 3440 passed.
118
+
3
119
  ## [2.5.4] — 2026-07-16
4
120
 
5
121
  **Evidence reconciliation + the endpoint axis.** The keycloak real-use simulacro
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sourcecode
3
- Version: 2.5.4
3
+ Version: 2.5.6
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.4"
7
+ version = "2.5.6"
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.4"
7
+ __version__ = "2.5.6"
@@ -4,6 +4,12 @@ import re
4
4
  from pathlib import Path
5
5
  from typing import Literal, Optional
6
6
 
7
+ from sourcecode.reconciliation import (
8
+ incoherent_layers,
9
+ pattern_contradicted,
10
+ unattested_layers,
11
+ )
12
+
7
13
  from sourcecode.schema import (
8
14
  ArchitectureAnalysis,
9
15
  ArchitectureDomain,
@@ -39,11 +45,27 @@ _SRC_TRANSPARENT = {
39
45
  "src", "lib", "app", "pkg",
40
46
  "main", "java", "kotlin", "scala", "groovy",
41
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))
42
58
  _CODE_EXTENSIONS = {
43
59
  ".py", ".js", ".ts", ".tsx", ".jsx", ".mjs", ".cjs",
44
60
  ".go", ".java", ".kt", ".rs", ".rb",
45
61
  }
46
- _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
+ }
47
69
 
48
70
  _TEST_DIRS: frozenset[str] = frozenset({
49
71
  "tests", "test", "spec", "specs", "__tests__", "e2e",
@@ -139,8 +161,14 @@ LAYER_PATTERNS: dict[str, dict[str, list[str]]] = {
139
161
  "application": ["application", "usecases", "usecase"],
140
162
  "infrastructure": ["infrastructure", "infra", "adapters", "persistence"],
141
163
  },
164
+ # P1-D residue (found by measurement, 2026-07-16): "core" belongs with
165
+ # "api"/"store"/"db" — it names a module's importance ("the main module"),
166
+ # never an onion/hexagonal DOMAIN layer, and nearly every large JVM repo has
167
+ # one. With it, keycloak's `core/` (623 Java files) + a
168
+ # `representations/adapters/config` package read "Onion Architecture with
169
+ # domain, adapters layers". Kept are the names that state their role.
142
170
  "onion": {
143
- "domain": ["domain", "core"],
171
+ "domain": ["domain"],
144
172
  "application": ["application", "usecases"],
145
173
  "ports": ["ports", "interfaces"],
146
174
  "adapters": ["adapters", "secondary"],
@@ -173,7 +201,7 @@ LAYER_PATTERNS: dict[str, dict[str, list[str]]] = {
173
201
  "hexagonal": {
174
202
  "port": ["port", "ports", "interface", "interfaces"],
175
203
  "adapter": ["adapter", "adapters"],
176
- "domain": ["domain", "core", "model", "models"],
204
+ "domain": ["domain", "model", "models"], # "core" dropped — see onion
177
205
  },
178
206
  "monorepo": {
179
207
  "apps": ["apps", "applications"],
@@ -196,6 +224,13 @@ LAYER_PATTERNS: dict[str, dict[str, list[str]]] = {
196
224
  # it; without it the pattern falls through to layered / a weaker match.
197
225
  _PATTERN_REQUIRED_KEYS: dict[str, frozenset[str]] = {
198
226
  "mvc": frozenset({"view"}),
227
+ # Same principle as MVC/view — a pattern must show its DEFINING trait, not
228
+ # just any two of its layers. Hexagonal is literally "Ports and Adapters":
229
+ # a `models/` package next to an `adapters/` package is not one, and that
230
+ # pair alone was enough to label keycloak "Hexagonal Architecture". Onion's
231
+ # defining trait is the domain at the centre.
232
+ "hexagonal": frozenset({"port", "adapter"}),
233
+ "onion": frozenset({"domain"}),
199
234
  }
200
235
 
201
236
  # Minimum share of source files a matched layer must hold to count as a LAYER
@@ -646,6 +681,28 @@ class ArchitectureAnalyzer:
646
681
  continue
647
682
  matched[layer_key] = matched_dirs
648
683
  layer_files[layer_key] = files
684
+
685
+ # SIM-3: a layer must belong to the same PROGRAM as the rest of the
686
+ # claim. Directory names are language-blind, so on a polyglot
687
+ # monorepo one pattern was assembled across two subsystems: keycloak
688
+ # read "MVC with controller, model, view layers" where `view` was a
689
+ # React SPA (87 .tsx, zero Java) sitting beside a 1088-file Java
690
+ # `model`. Mass cannot catch this — the SPA has genuine mass. The
691
+ # reconciliation rule drops layers owned by a different runtime;
692
+ # template/asset-only layers are untouched, so a real Spring-MVC
693
+ # `templates/` view survives.
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):
696
+ matched.pop(_bad, None)
697
+ layer_files.pop(_bad, None)
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
+
649
706
  # A pattern with unmet required keys cannot qualify (e.g. mvc needs a view).
650
707
  required = _PATTERN_REQUIRED_KEYS.get(pattern_name)
651
708
  if required and not required.issubset(matched.keys()):
@@ -809,6 +866,14 @@ class ArchitectureAnalyzer:
809
866
  k: v for k, v in layer_files.items()
810
867
  if v and (not total or len(v) / total >= _LAYER_MIN_SHARE)
811
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)
812
877
  if len(non_empty) >= 2:
813
878
  return "layered", [
814
879
  ArchitectureLayer(name=k, pattern="layered", files=v, confidence="low")
@@ -828,13 +893,19 @@ class ArchitectureAnalyzer:
828
893
  for p in paths:
829
894
  parts = p.replace("\\", "/").split("/")
830
895
  for part in parts[:-1]:
831
- if (part not in _SRC_TRANSPARENT
896
+ if (not _is_transparent_segment(part)
832
897
  and part.lower() not in _NON_SOURCE_DIRS
833
898
  and part.lower() not in _GENERIC_NAMES):
834
899
  module_files.setdefault(part, []).append(p)
835
900
  break
836
901
 
837
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)
838
909
  if len(meaningful) >= 2:
839
910
  return "modular", [
840
911
  ArchitectureLayer(name=k, pattern="modular", files=v, confidence="low")