sourcecode 2.5.8__tar.gz → 2.5.11__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.8 → sourcecode-2.5.11}/CHANGELOG.md +89 -0
- {sourcecode-2.5.8 → sourcecode-2.5.11}/PKG-INFO +1 -1
- {sourcecode-2.5.8 → sourcecode-2.5.11}/pyproject.toml +1 -1
- {sourcecode-2.5.8 → sourcecode-2.5.11}/src/sourcecode/__init__.py +1 -1
- {sourcecode-2.5.8 → sourcecode-2.5.11}/src/sourcecode/architecture_analyzer.py +70 -16
- {sourcecode-2.5.8 → sourcecode-2.5.11}/src/sourcecode/cli.py +34 -2
- {sourcecode-2.5.8 → sourcecode-2.5.11}/src/sourcecode/reconciliation.py +26 -9
- {sourcecode-2.5.8 → sourcecode-2.5.11}/.github/workflows/build-windows.yml +0 -0
- {sourcecode-2.5.8 → sourcecode-2.5.11}/.gitignore +0 -0
- {sourcecode-2.5.8 → sourcecode-2.5.11}/.ruff.toml +0 -0
- {sourcecode-2.5.8 → sourcecode-2.5.11}/CONTRIBUTING.md +0 -0
- {sourcecode-2.5.8 → sourcecode-2.5.11}/LICENSE +0 -0
- {sourcecode-2.5.8 → sourcecode-2.5.11}/README.md +0 -0
- {sourcecode-2.5.8 → sourcecode-2.5.11}/SECURITY.md +0 -0
- {sourcecode-2.5.8 → sourcecode-2.5.11}/raw +0 -0
- {sourcecode-2.5.8 → sourcecode-2.5.11}/scripts/compare_integration_engines.py +0 -0
- {sourcecode-2.5.8 → sourcecode-2.5.11}/scripts/customer_smoke_test.sh +0 -0
- {sourcecode-2.5.8 → sourcecode-2.5.11}/scripts/generate_jdk_exports.py +0 -0
- {sourcecode-2.5.8 → sourcecode-2.5.11}/src/sourcecode/adaptive_scanner.py +0 -0
- {sourcecode-2.5.8 → sourcecode-2.5.11}/src/sourcecode/archetype.py +0 -0
- {sourcecode-2.5.8 → sourcecode-2.5.11}/src/sourcecode/architecture_summary.py +0 -0
- {sourcecode-2.5.8 → sourcecode-2.5.11}/src/sourcecode/ast_extractor.py +0 -0
- {sourcecode-2.5.8 → sourcecode-2.5.11}/src/sourcecode/cache.py +0 -0
- {sourcecode-2.5.8 → sourcecode-2.5.11}/src/sourcecode/call_surface.py +0 -0
- {sourcecode-2.5.8 → sourcecode-2.5.11}/src/sourcecode/caller_metrics.py +0 -0
- {sourcecode-2.5.8 → sourcecode-2.5.11}/src/sourcecode/canonical_ir.py +0 -0
- {sourcecode-2.5.8 → sourcecode-2.5.11}/src/sourcecode/cir_graphs.py +0 -0
- {sourcecode-2.5.8 → sourcecode-2.5.11}/src/sourcecode/classifier.py +0 -0
- {sourcecode-2.5.8 → sourcecode-2.5.11}/src/sourcecode/code_notes_analyzer.py +0 -0
- {sourcecode-2.5.8 → sourcecode-2.5.11}/src/sourcecode/confidence_analyzer.py +0 -0
- {sourcecode-2.5.8 → sourcecode-2.5.11}/src/sourcecode/context_cache.py +0 -0
- {sourcecode-2.5.8 → sourcecode-2.5.11}/src/sourcecode/context_graph.py +0 -0
- {sourcecode-2.5.8 → sourcecode-2.5.11}/src/sourcecode/context_scorer.py +0 -0
- {sourcecode-2.5.8 → sourcecode-2.5.11}/src/sourcecode/context_summarizer.py +0 -0
- {sourcecode-2.5.8 → sourcecode-2.5.11}/src/sourcecode/contract_model.py +0 -0
- {sourcecode-2.5.8 → sourcecode-2.5.11}/src/sourcecode/contract_pipeline.py +0 -0
- {sourcecode-2.5.8 → sourcecode-2.5.11}/src/sourcecode/coverage_parser.py +0 -0
- {sourcecode-2.5.8 → sourcecode-2.5.11}/src/sourcecode/dependency_analyzer.py +0 -0
- {sourcecode-2.5.8 → sourcecode-2.5.11}/src/sourcecode/detectors/__init__.py +0 -0
- {sourcecode-2.5.8 → sourcecode-2.5.11}/src/sourcecode/detectors/base.py +0 -0
- {sourcecode-2.5.8 → sourcecode-2.5.11}/src/sourcecode/detectors/csproj_parser.py +0 -0
- {sourcecode-2.5.8 → sourcecode-2.5.11}/src/sourcecode/detectors/dart.py +0 -0
- {sourcecode-2.5.8 → sourcecode-2.5.11}/src/sourcecode/detectors/dotnet.py +0 -0
- {sourcecode-2.5.8 → sourcecode-2.5.11}/src/sourcecode/detectors/elixir.py +0 -0
- {sourcecode-2.5.8 → sourcecode-2.5.11}/src/sourcecode/detectors/go.py +0 -0
- {sourcecode-2.5.8 → sourcecode-2.5.11}/src/sourcecode/detectors/heuristic.py +0 -0
- {sourcecode-2.5.8 → sourcecode-2.5.11}/src/sourcecode/detectors/hybrid.py +0 -0
- {sourcecode-2.5.8 → sourcecode-2.5.11}/src/sourcecode/detectors/java.py +0 -0
- {sourcecode-2.5.8 → sourcecode-2.5.11}/src/sourcecode/detectors/jvm_ext.py +0 -0
- {sourcecode-2.5.8 → sourcecode-2.5.11}/src/sourcecode/detectors/nodejs.py +0 -0
- {sourcecode-2.5.8 → sourcecode-2.5.11}/src/sourcecode/detectors/parsers.py +0 -0
- {sourcecode-2.5.8 → sourcecode-2.5.11}/src/sourcecode/detectors/php.py +0 -0
- {sourcecode-2.5.8 → sourcecode-2.5.11}/src/sourcecode/detectors/project.py +0 -0
- {sourcecode-2.5.8 → sourcecode-2.5.11}/src/sourcecode/detectors/python.py +0 -0
- {sourcecode-2.5.8 → sourcecode-2.5.11}/src/sourcecode/detectors/ruby.py +0 -0
- {sourcecode-2.5.8 → sourcecode-2.5.11}/src/sourcecode/detectors/rust.py +0 -0
- {sourcecode-2.5.8 → sourcecode-2.5.11}/src/sourcecode/detectors/systems.py +0 -0
- {sourcecode-2.5.8 → sourcecode-2.5.11}/src/sourcecode/detectors/terraform.py +0 -0
- {sourcecode-2.5.8 → sourcecode-2.5.11}/src/sourcecode/detectors/tooling.py +0 -0
- {sourcecode-2.5.8 → sourcecode-2.5.11}/src/sourcecode/doc_analyzer.py +0 -0
- {sourcecode-2.5.8 → sourcecode-2.5.11}/src/sourcecode/dynamic_argument_surface.py +0 -0
- {sourcecode-2.5.8 → sourcecode-2.5.11}/src/sourcecode/endpoint_literals.py +0 -0
- {sourcecode-2.5.8 → sourcecode-2.5.11}/src/sourcecode/endpoint_metrics.py +0 -0
- {sourcecode-2.5.8 → sourcecode-2.5.11}/src/sourcecode/entrypoint_classifier.py +0 -0
- {sourcecode-2.5.8 → sourcecode-2.5.11}/src/sourcecode/env_analyzer.py +0 -0
- {sourcecode-2.5.8 → sourcecode-2.5.11}/src/sourcecode/error_schema.py +0 -0
- {sourcecode-2.5.8 → sourcecode-2.5.11}/src/sourcecode/evidence_provider.py +0 -0
- {sourcecode-2.5.8 → sourcecode-2.5.11}/src/sourcecode/explain.py +0 -0
- {sourcecode-2.5.8 → sourcecode-2.5.11}/src/sourcecode/file_chunker.py +0 -0
- {sourcecode-2.5.8 → sourcecode-2.5.11}/src/sourcecode/file_classifier.py +0 -0
- {sourcecode-2.5.8 → sourcecode-2.5.11}/src/sourcecode/format_contract.py +0 -0
- {sourcecode-2.5.8 → sourcecode-2.5.11}/src/sourcecode/fqn_utils.py +0 -0
- {sourcecode-2.5.8 → sourcecode-2.5.11}/src/sourcecode/git_analyzer.py +0 -0
- {sourcecode-2.5.8 → sourcecode-2.5.11}/src/sourcecode/graph_analyzer.py +0 -0
- {sourcecode-2.5.8 → sourcecode-2.5.11}/src/sourcecode/graph_evidence.py +0 -0
- {sourcecode-2.5.8 → sourcecode-2.5.11}/src/sourcecode/hibernate_strat.py +0 -0
- {sourcecode-2.5.8 → sourcecode-2.5.11}/src/sourcecode/jdk_exports.py +0 -0
- {sourcecode-2.5.8 → sourcecode-2.5.11}/src/sourcecode/license.py +0 -0
- {sourcecode-2.5.8 → sourcecode-2.5.11}/src/sourcecode/mcp/__init__.py +0 -0
- {sourcecode-2.5.8 → sourcecode-2.5.11}/src/sourcecode/mcp/onboarding/__init__.py +0 -0
- {sourcecode-2.5.8 → sourcecode-2.5.11}/src/sourcecode/mcp/onboarding/applier.py +0 -0
- {sourcecode-2.5.8 → sourcecode-2.5.11}/src/sourcecode/mcp/onboarding/backup.py +0 -0
- {sourcecode-2.5.8 → sourcecode-2.5.11}/src/sourcecode/mcp/onboarding/detector.py +0 -0
- {sourcecode-2.5.8 → sourcecode-2.5.11}/src/sourcecode/mcp/onboarding/planner.py +0 -0
- {sourcecode-2.5.8 → sourcecode-2.5.11}/src/sourcecode/mcp/orchestrator.py +0 -0
- {sourcecode-2.5.8 → sourcecode-2.5.11}/src/sourcecode/mcp/registry.py +0 -0
- {sourcecode-2.5.8 → sourcecode-2.5.11}/src/sourcecode/mcp/runner.py +0 -0
- {sourcecode-2.5.8 → sourcecode-2.5.11}/src/sourcecode/mcp/server.py +0 -0
- {sourcecode-2.5.8 → sourcecode-2.5.11}/src/sourcecode/mcp_nudge.py +0 -0
- {sourcecode-2.5.8 → sourcecode-2.5.11}/src/sourcecode/metrics_analyzer.py +0 -0
- {sourcecode-2.5.8 → sourcecode-2.5.11}/src/sourcecode/migrate_check.py +0 -0
- {sourcecode-2.5.8 → sourcecode-2.5.11}/src/sourcecode/openapi_surface.py +0 -0
- {sourcecode-2.5.8 → sourcecode-2.5.11}/src/sourcecode/output_budget.py +0 -0
- {sourcecode-2.5.8 → sourcecode-2.5.11}/src/sourcecode/path_filters.py +0 -0
- {sourcecode-2.5.8 → sourcecode-2.5.11}/src/sourcecode/pr_comment_renderer.py +0 -0
- {sourcecode-2.5.8 → sourcecode-2.5.11}/src/sourcecode/pr_impact.py +0 -0
- {sourcecode-2.5.8 → sourcecode-2.5.11}/src/sourcecode/prepare_context.py +0 -0
- {sourcecode-2.5.8 → sourcecode-2.5.11}/src/sourcecode/progress.py +0 -0
- {sourcecode-2.5.8 → sourcecode-2.5.11}/src/sourcecode/ranking_engine.py +0 -0
- {sourcecode-2.5.8 → sourcecode-2.5.11}/src/sourcecode/redactor.py +0 -0
- {sourcecode-2.5.8 → sourcecode-2.5.11}/src/sourcecode/relevance_scorer.py +0 -0
- {sourcecode-2.5.8 → sourcecode-2.5.11}/src/sourcecode/rename_refactor.py +0 -0
- {sourcecode-2.5.8 → sourcecode-2.5.11}/src/sourcecode/repo_classifier.py +0 -0
- {sourcecode-2.5.8 → sourcecode-2.5.11}/src/sourcecode/repository_ir.py +0 -0
- {sourcecode-2.5.8 → sourcecode-2.5.11}/src/sourcecode/retrieval/__init__.py +0 -0
- {sourcecode-2.5.8 → sourcecode-2.5.11}/src/sourcecode/retrieval/context.py +0 -0
- {sourcecode-2.5.8 → sourcecode-2.5.11}/src/sourcecode/retrieval/errors.py +0 -0
- {sourcecode-2.5.8 → sourcecode-2.5.11}/src/sourcecode/retrieval/executor.py +0 -0
- {sourcecode-2.5.8 → sourcecode-2.5.11}/src/sourcecode/retrieval/planner.py +0 -0
- {sourcecode-2.5.8 → sourcecode-2.5.11}/src/sourcecode/retrieval/query.py +0 -0
- {sourcecode-2.5.8 → sourcecode-2.5.11}/src/sourcecode/retrieval/request.py +0 -0
- {sourcecode-2.5.8 → sourcecode-2.5.11}/src/sourcecode/retrieval/resolution.py +0 -0
- {sourcecode-2.5.8 → sourcecode-2.5.11}/src/sourcecode/retrieval/result.py +0 -0
- {sourcecode-2.5.8 → sourcecode-2.5.11}/src/sourcecode/retrieval/retriever.py +0 -0
- {sourcecode-2.5.8 → sourcecode-2.5.11}/src/sourcecode/retrieval/runtime.py +0 -0
- {sourcecode-2.5.8 → sourcecode-2.5.11}/src/sourcecode/retrieval/steps.py +0 -0
- {sourcecode-2.5.8 → sourcecode-2.5.11}/src/sourcecode/retrieval/steps_endpoint.py +0 -0
- {sourcecode-2.5.8 → sourcecode-2.5.11}/src/sourcecode/retrieval/steps_graph.py +0 -0
- {sourcecode-2.5.8 → sourcecode-2.5.11}/src/sourcecode/retrieval/steps_impact.py +0 -0
- {sourcecode-2.5.8 → sourcecode-2.5.11}/src/sourcecode/retrieval/steps_intf.py +0 -0
- {sourcecode-2.5.8 → sourcecode-2.5.11}/src/sourcecode/retrieval/steps_struct.py +0 -0
- {sourcecode-2.5.8 → sourcecode-2.5.11}/src/sourcecode/retrieval/steps_txsec.py +0 -0
- {sourcecode-2.5.8 → sourcecode-2.5.11}/src/sourcecode/ris.py +0 -0
- {sourcecode-2.5.8 → sourcecode-2.5.11}/src/sourcecode/runtime_classifier.py +0 -0
- {sourcecode-2.5.8 → sourcecode-2.5.11}/src/sourcecode/scanner.py +0 -0
- {sourcecode-2.5.8 → sourcecode-2.5.11}/src/sourcecode/schema.py +0 -0
- {sourcecode-2.5.8 → sourcecode-2.5.11}/src/sourcecode/security_config.py +0 -0
- {sourcecode-2.5.8 → sourcecode-2.5.11}/src/sourcecode/security_posture.py +0 -0
- {sourcecode-2.5.8 → sourcecode-2.5.11}/src/sourcecode/semantic_analyzer.py +0 -0
- {sourcecode-2.5.8 → sourcecode-2.5.11}/src/sourcecode/semantic_impact_engine.py +0 -0
- {sourcecode-2.5.8 → sourcecode-2.5.11}/src/sourcecode/semantic_integration_engine.py +0 -0
- {sourcecode-2.5.8 → sourcecode-2.5.11}/src/sourcecode/semantic_services.py +0 -0
- {sourcecode-2.5.8 → sourcecode-2.5.11}/src/sourcecode/serializer.py +0 -0
- {sourcecode-2.5.8 → sourcecode-2.5.11}/src/sourcecode/spring_event_topology.py +0 -0
- {sourcecode-2.5.8 → sourcecode-2.5.11}/src/sourcecode/spring_findings.py +0 -0
- {sourcecode-2.5.8 → sourcecode-2.5.11}/src/sourcecode/spring_impact.py +0 -0
- {sourcecode-2.5.8 → sourcecode-2.5.11}/src/sourcecode/spring_model.py +0 -0
- {sourcecode-2.5.8 → sourcecode-2.5.11}/src/sourcecode/spring_security_audit.py +0 -0
- {sourcecode-2.5.8 → sourcecode-2.5.11}/src/sourcecode/spring_semantic.py +0 -0
- {sourcecode-2.5.8 → sourcecode-2.5.11}/src/sourcecode/spring_tx_analyzer.py +0 -0
- {sourcecode-2.5.8 → sourcecode-2.5.11}/src/sourcecode/summarizer.py +0 -0
- {sourcecode-2.5.8 → sourcecode-2.5.11}/src/sourcecode/telemetry/__init__.py +0 -0
- {sourcecode-2.5.8 → sourcecode-2.5.11}/src/sourcecode/telemetry/config.py +0 -0
- {sourcecode-2.5.8 → sourcecode-2.5.11}/src/sourcecode/telemetry/consent.py +0 -0
- {sourcecode-2.5.8 → sourcecode-2.5.11}/src/sourcecode/telemetry/events.py +0 -0
- {sourcecode-2.5.8 → sourcecode-2.5.11}/src/sourcecode/telemetry/filters.py +0 -0
- {sourcecode-2.5.8 → sourcecode-2.5.11}/src/sourcecode/telemetry/transport.py +0 -0
- {sourcecode-2.5.8 → sourcecode-2.5.11}/src/sourcecode/tree_utils.py +0 -0
- {sourcecode-2.5.8 → sourcecode-2.5.11}/src/sourcecode/type_usage_surface.py +0 -0
- {sourcecode-2.5.8 → sourcecode-2.5.11}/src/sourcecode/validation_inference.py +0 -0
- {sourcecode-2.5.8 → sourcecode-2.5.11}/src/sourcecode/validation_surface.py +0 -0
- {sourcecode-2.5.8 → sourcecode-2.5.11}/src/sourcecode/version_check.py +0 -0
- {sourcecode-2.5.8 → sourcecode-2.5.11}/src/sourcecode/workspace.py +0 -0
- {sourcecode-2.5.8 → sourcecode-2.5.11}/supabase/functions/README.md +0 -0
- {sourcecode-2.5.8 → sourcecode-2.5.11}/supabase/functions/get-license/index.ts +0 -0
- {sourcecode-2.5.8 → sourcecode-2.5.11}/supabase/functions/lemonsqueezy-webhook/index.ts +0 -0
- {sourcecode-2.5.8 → sourcecode-2.5.11}/supabase/functions/telemetry/index.ts +0 -0
- {sourcecode-2.5.8 → sourcecode-2.5.11}/supabase/sql/license_event_ordering.sql +0 -0
- {sourcecode-2.5.8 → sourcecode-2.5.11}/supabase/sql/licensing_schema.sql +0 -0
- {sourcecode-2.5.8 → sourcecode-2.5.11}/supabase/sql/telemetry_events.sql +0 -0
|
@@ -1,5 +1,94 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [2.5.11] — 2026-07-17
|
|
4
|
+
|
|
5
|
+
**Two ways a tool can lie by omission: analyze the wrong repository without
|
|
6
|
+
saying so, and refute a claim without saying so.** Both closed here. Also lands
|
|
7
|
+
ADR-0005, which ratifies the Evidence Reconciliation Layer as infrastructure and
|
|
8
|
+
records — with measurements — which of its "missing pieces" were never needed.
|
|
9
|
+
|
|
10
|
+
### Fixed
|
|
11
|
+
|
|
12
|
+
- **`ask <path> <subcommand>` silently analyzed the current directory instead
|
|
13
|
+
(P2-G)** (`cli.py`): the argument preprocessor popped the first positional into
|
|
14
|
+
a path that only the top-level `main` reads. Every subcommand declares its own
|
|
15
|
+
`path = Argument(".")`, so a leading path before a subcommand was not
|
|
16
|
+
misparsed — it was **discarded**, and the subcommand fell back to the CWD.
|
|
17
|
+
`ask spaghetti-api endpoints` reported **1077 endpoints from every repo in the
|
|
18
|
+
parent directory** (the real surface was 7), exit 0, no warning — an agent
|
|
19
|
+
consuming that JSON could not tell it was reading someone else's code. Now
|
|
20
|
+
refused: exit 2, empty stdout, naming the correct invocation. Reordering cannot
|
|
21
|
+
be guessed (`impact <symbol> <path>` has no free slot). The false-positive
|
|
22
|
+
guard (an option *value* that happens to match a subcommand name, e.g.
|
|
23
|
+
`--files endpoints`) is covered. The test suite was itself a victim: four tests
|
|
24
|
+
invoked the wrong-order form and passed while analyzing atlas-cli — they
|
|
25
|
+
asserted only "valid JSON", which any repo produces. Corrected to assert an
|
|
26
|
+
artifact only the intended fixture yields.
|
|
27
|
+
|
|
28
|
+
- **Refuted architecture claims were pruned silently (INV-F1-4)**
|
|
29
|
+
(`architecture_analyzer.py`, `reconciliation.py`): three reconciliation rules
|
|
30
|
+
(`layer_language_coherence`, `composition_runtime_split`,
|
|
31
|
+
`layer_code_attestation`) were consumed through name-only projections that
|
|
32
|
+
discarded the finding's explanation. The analyzer dropped a layer or rejected a
|
|
33
|
+
pattern and told the reader nothing — ofbiz showed fewer modules, jobrunr's
|
|
34
|
+
"fullstack" vanished, keycloak's foreign `view` layer disappeared, each without
|
|
35
|
+
a trace. A reader could not distinguish *"this repo has no architecture"* from
|
|
36
|
+
*"we refuted the architecture claim"* — the exact distinction the analyzer's
|
|
37
|
+
degrade-to-N/A discipline exists to preserve. Refutations that shape the
|
|
38
|
+
reported result now surface in `limitations`, prefixed *"Architecture claim
|
|
39
|
+
refuted on evidence: …"*. Scoped to avoid noise: the winning pattern's pruned
|
|
40
|
+
layers, the runtime-split contradiction that removed a contender, and every
|
|
41
|
+
pruning in the fallback paths (which run only when nothing else qualified).
|
|
42
|
+
|
|
43
|
+
### Added
|
|
44
|
+
|
|
45
|
+
- **ADR-0005 — Evidence Reconciliation Layer (F-1)**
|
|
46
|
+
(`docs/architecture/adr/0005-evidence-reconciliation.md`): the layer's contract,
|
|
47
|
+
invariants and limits. Ratifies the contract as **data** (immutable views +
|
|
48
|
+
pure-function rules + `ReconciliationFinding`) and **rejects** the proposed
|
|
49
|
+
provider protocol — an ABC spanning the six consumers would constrain nothing.
|
|
50
|
+
Documents that the "precursors" slated for migration (the Confidence envelope,
|
|
51
|
+
`coverage_confidence`, `zero_result_reason`, readiness N/A) are single-view
|
|
52
|
+
mechanisms that must stay out of F-1, and that pipelines — not commands — are
|
|
53
|
+
the unit of coverage (`onboard`/`review-pr`/`fix-bug` inherit reconciliation
|
|
54
|
+
from `prepare_context`).
|
|
55
|
+
|
|
56
|
+
## [2.5.9] — 2026-07-17
|
|
57
|
+
|
|
58
|
+
**A silently dead fast path.** The backlog carried the `entry_points`
|
|
59
|
+
list-vs-dict drift as a schema tidiness item. It was not: the drift had disabled
|
|
60
|
+
a feature, quietly, on exactly the repositories that feature exists for.
|
|
61
|
+
|
|
62
|
+
### Fixed
|
|
63
|
+
|
|
64
|
+
- **RIS stored `entry_points` in whichever shape it was handed
|
|
65
|
+
(P2-F-RESIDUE(b))** (`ris.py`): `agent_view` emits that key in two shapes under
|
|
66
|
+
one `schema_version` — `{"bootstrap": [path…], "security": […],
|
|
67
|
+
"controllers": {counts}}` when a bootstrap entry point is detected, otherwise
|
|
68
|
+
`[{"path": …, "kind": …}, …]`. RIS copied either one verbatim into
|
|
69
|
+
`structural_map["entrypoints"]`, a key its readers treat as a list of path
|
|
70
|
+
**strings**: they slice it, then guard `isinstance(fp, str)`.
|
|
71
|
+
|
|
72
|
+
Both shapes broke it, measured against the real function:
|
|
73
|
+
|
|
74
|
+
| shape | result |
|
|
75
|
+
|---|---|
|
|
76
|
+
| dict (Spring, …) | `entrypoints[:10]` → `KeyError: slice(None, 10, None)`. `prepare_context._try_ris_fast_path` wraps its whole body in `except Exception: return None`, so **the RIS fast path was silently dead on Java/Spring repos** — falling back to full analysis with no warning. |
|
|
77
|
+
| list of dicts (everything else) | `isinstance(fp, str)` is false for every item → **zero entry-point files recovered**, silently. |
|
|
78
|
+
|
|
79
|
+
The fast path therefore recovered entry points for **no repository at all**,
|
|
80
|
+
and a bare `except` kept it quiet. `_entrypoint_paths()` now normalizes either
|
|
81
|
+
shape — plus plain path lists and junk — to the paths RIS promises. Only
|
|
82
|
+
`bootstrap`/`security` carry paths, so the `controllers` count summary cannot
|
|
83
|
+
leak into the list.
|
|
84
|
+
|
|
85
|
+
The public shape drift is deliberately untouched: unifying it changes a
|
|
86
|
+
documented `schema_version` and belongs in its own release. RIS's contract now
|
|
87
|
+
holds whichever shape it is handed, which is what its readers depend on.
|
|
88
|
+
|
|
89
|
+
3 new tests; suite `3466 passed, 5 skipped, 1 deselected` (the deselection is a
|
|
90
|
+
pre-existing `file_chunker` failure unrelated to this work).
|
|
91
|
+
|
|
3
92
|
## [2.5.8] — 2026-07-17
|
|
4
93
|
|
|
5
94
|
**A count must name what it counted.** 2.5.7 taught the headline to say *"Polyglot
|
|
@@ -5,9 +5,8 @@ from pathlib import Path
|
|
|
5
5
|
from typing import Literal, Optional
|
|
6
6
|
|
|
7
7
|
from sourcecode.reconciliation import (
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
unattested_layers,
|
|
8
|
+
reconcile_architecture_evidence,
|
|
9
|
+
reconcile_code_modules,
|
|
11
10
|
)
|
|
12
11
|
|
|
13
12
|
from sourcecode.schema import (
|
|
@@ -280,6 +279,13 @@ class ArchitectureAnalyzer:
|
|
|
280
279
|
) -> ArchitectureAnalysis:
|
|
281
280
|
limitations: list[str] = []
|
|
282
281
|
evidence: list[dict] = []
|
|
282
|
+
# INV-F1-4: reconciliation rules that refute a layer/pattern must not
|
|
283
|
+
# apply their verdict silently — a pruned layer or a rejected pattern is
|
|
284
|
+
# a positively-detected contradiction the reader is entitled to see, or
|
|
285
|
+
# "no architecture" becomes indistinguishable from "we refuted the
|
|
286
|
+
# architecture claim". `_detect_layers` and its helpers append the
|
|
287
|
+
# findings' sink-neutral detail here; drained into `limitations` below.
|
|
288
|
+
self._refutations: list[str] = []
|
|
283
289
|
|
|
284
290
|
# Step 1: filter paths
|
|
285
291
|
filtered = self._filter_paths(sm.file_paths)
|
|
@@ -482,6 +488,17 @@ class ArchitectureAnalyzer:
|
|
|
482
488
|
"confidence": "low",
|
|
483
489
|
})
|
|
484
490
|
|
|
491
|
+
# INV-F1-4 sink: surface every refutation that shaped the returned
|
|
492
|
+
# result. Prefix frames it as a refutation; the detail (unchanged) states
|
|
493
|
+
# the contradiction, keeping it sink-neutral (INV-F1-5). Dedup: the
|
|
494
|
+
# layered→modular fallthrough can refute the same directory twice.
|
|
495
|
+
_seen: set[str] = set()
|
|
496
|
+
for _detail in self._refutations:
|
|
497
|
+
if _detail in _seen:
|
|
498
|
+
continue
|
|
499
|
+
_seen.add(_detail)
|
|
500
|
+
limitations.append(f"Architecture claim refuted on evidence: {_detail}")
|
|
501
|
+
|
|
485
502
|
return ArchitectureAnalysis(
|
|
486
503
|
requested=True,
|
|
487
504
|
pattern=pattern,
|
|
@@ -665,6 +682,13 @@ class ArchitectureAnalyzer:
|
|
|
665
682
|
best_priority = -1
|
|
666
683
|
best_matched: dict[str, list[str]] = {}
|
|
667
684
|
best_layer_files: dict[str, set[int]] = {}
|
|
685
|
+
# INV-F1-4: refutations that fired while assembling each candidate. Only
|
|
686
|
+
# those that shaped the RETURNED result are surfaced (below): the winning
|
|
687
|
+
# pattern's pruned layers, or — when no classical pattern qualifies — the
|
|
688
|
+
# contradictions that removed the contenders. A refutation of a candidate
|
|
689
|
+
# that lost on score anyway is not what the reader is being misled about.
|
|
690
|
+
cand_refutations: dict[str, list[str]] = {}
|
|
691
|
+
contradiction_details: list[str] = []
|
|
668
692
|
|
|
669
693
|
for pattern_name, layer_keys in LAYER_PATTERNS.items():
|
|
670
694
|
matched: dict[str, list[str]] = {}
|
|
@@ -692,16 +716,29 @@ class ArchitectureAnalyzer:
|
|
|
692
716
|
# template/asset-only layers are untouched, so a real Spring-MVC
|
|
693
717
|
# `templates/` view survives.
|
|
694
718
|
_layer_paths = {k: [source_paths[i] for i in v] for k, v in layer_files.items()}
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
719
|
+
_refs: list[str] = []
|
|
720
|
+
_split_contradicted = False
|
|
721
|
+
for _f in reconcile_architecture_evidence(
|
|
722
|
+
pattern=pattern_name, layer_files=_layer_paths, repo_files=source_paths
|
|
723
|
+
):
|
|
724
|
+
_refs.append(_f.detail)
|
|
725
|
+
if _f.rule == "composition_runtime_split":
|
|
726
|
+
# A claim that IS a runtime split must show one: JobRunr, a JVM
|
|
727
|
+
# library, read "Fullstack with frontend, backend layers" off a
|
|
728
|
+
# `dashboard/ui/model/` package of 16 Java files. Nothing to
|
|
729
|
+
# prune — the pattern itself is what the evidence contradicts.
|
|
730
|
+
_split_contradicted = True
|
|
731
|
+
else:
|
|
732
|
+
# layer_language_coherence: drop the layers owned by a
|
|
733
|
+
# different runtime; template/asset-only layers are untouched,
|
|
734
|
+
# so a real Spring-MVC `templates/` view survives.
|
|
735
|
+
for _bad in _f.symbols:
|
|
736
|
+
matched.pop(_bad, None)
|
|
737
|
+
layer_files.pop(_bad, None)
|
|
738
|
+
if _split_contradicted:
|
|
739
|
+
contradiction_details.extend(_refs)
|
|
704
740
|
continue
|
|
741
|
+
cand_refutations[pattern_name] = _refs
|
|
705
742
|
|
|
706
743
|
# A pattern with unmet required keys cannot qualify (e.g. mvc needs a view).
|
|
707
744
|
required = _PATTERN_REQUIRED_KEYS.get(pattern_name)
|
|
@@ -717,6 +754,10 @@ class ArchitectureAnalyzer:
|
|
|
717
754
|
best_layer_files = layer_files
|
|
718
755
|
|
|
719
756
|
if best_score >= 2:
|
|
757
|
+
# The reported pattern is the one whose refutations misled the reader
|
|
758
|
+
# if left silent (keycloak's dropped `view` layer). Contradictions of
|
|
759
|
+
# OTHER, losing candidates did not shape this result — omit them.
|
|
760
|
+
self._refutations.extend(cand_refutations.get(best_pattern, []))
|
|
720
761
|
layer_confidence: Literal["high", "medium", "low"] = "medium" if best_score >= 3 else "low"
|
|
721
762
|
layers: list[ArchitectureLayer] = []
|
|
722
763
|
for layer_key, matched_dirs in best_matched.items():
|
|
@@ -729,6 +770,11 @@ class ArchitectureAnalyzer:
|
|
|
729
770
|
))
|
|
730
771
|
return best_pattern, layers
|
|
731
772
|
|
|
773
|
+
# No classical pattern qualified. If a contender was removed by a runtime
|
|
774
|
+
# split contradiction (jobrunr's "fullstack"), that is why — and the
|
|
775
|
+
# eventual fallthrough result would otherwise look like a silent absence.
|
|
776
|
+
self._refutations.extend(contradiction_details)
|
|
777
|
+
|
|
732
778
|
# 2. Spring domain-module detection (petclinic-style: deep common prefix + feature dirs)
|
|
733
779
|
spring_result = self._detect_spring_domain_modules(source_paths)
|
|
734
780
|
if spring_result is not None:
|
|
@@ -872,8 +918,13 @@ class ArchitectureAnalyzer:
|
|
|
872
918
|
# Architecture with orchestration, data layers" rested on ONE directory
|
|
873
919
|
# entry plus a settings.gradle, with 47 Java files visible and ignored.
|
|
874
920
|
# A logical code layer that holds no code of this repo is not one.
|
|
875
|
-
|
|
876
|
-
|
|
921
|
+
# INV-F1-4: this path only runs when no classical pattern won, so a
|
|
922
|
+
# pruning here directly shapes the returned result (fewer layers, or a
|
|
923
|
+
# drop below 2 that falls through to flat/unknown) — surface it.
|
|
924
|
+
for _f in reconcile_code_modules(layer_files=non_empty, repo_files=paths):
|
|
925
|
+
for _bad in _f.symbols:
|
|
926
|
+
non_empty.pop(_bad, None)
|
|
927
|
+
self._refutations.append(_f.detail)
|
|
877
928
|
if len(non_empty) >= 2:
|
|
878
929
|
return "layered", [
|
|
879
930
|
ArchitectureLayer(name=k, pattern="layered", files=v, confidence="low")
|
|
@@ -904,8 +955,11 @@ class ArchitectureAnalyzer:
|
|
|
904
955
|
# count alone let build and asset directories be reported as modules —
|
|
905
956
|
# ofbiz "docker, framework, runtime, gradle", neo4j "packaging",
|
|
906
957
|
# eureka "images" (five PNGs). The real modules survive untouched.
|
|
907
|
-
|
|
908
|
-
|
|
958
|
+
# INV-F1-4: same as the layered path — surface each refuted module.
|
|
959
|
+
for _f in reconcile_code_modules(layer_files=meaningful, repo_files=paths):
|
|
960
|
+
for _bad in _f.symbols:
|
|
961
|
+
meaningful.pop(_bad, None)
|
|
962
|
+
self._refutations.append(_f.detail)
|
|
909
963
|
if len(meaningful) >= 2:
|
|
910
964
|
return "modular", [
|
|
911
965
|
ArchitectureLayer(name=k, pattern="modular", files=v, confidence="low")
|
|
@@ -7,7 +7,7 @@ import sys
|
|
|
7
7
|
import threading
|
|
8
8
|
import time
|
|
9
9
|
from pathlib import Path
|
|
10
|
-
from typing import Any, Optional, cast
|
|
10
|
+
from typing import Any, NoReturn, Optional, cast
|
|
11
11
|
|
|
12
12
|
import typer
|
|
13
13
|
|
|
@@ -293,6 +293,30 @@ _OPTIONS_WITH_VALUE: frozenset[str] = frozenset({
|
|
|
293
293
|
})
|
|
294
294
|
|
|
295
295
|
|
|
296
|
+
def _reject_path_before_subcommand(path_token: str, subcommand: str) -> "NoReturn":
|
|
297
|
+
"""Refuse `ask <path> <subcommand>` instead of silently scanning the CWD.
|
|
298
|
+
|
|
299
|
+
`_detected_path` is only read by the root analysis: a subcommand takes its own
|
|
300
|
+
`path` Argument, which defaults to the current directory. So the path token in
|
|
301
|
+
`ask spaghetti-api endpoints` was swallowed here and the subcommand ran against
|
|
302
|
+
the CWD — from a directory of checkouts that is every repository at once.
|
|
303
|
+
Measured: it answered with 1077 endpoints from keycloak, neo4j and eureka
|
|
304
|
+
instead of spaghetti-api's 7, exit code 0, no warning.
|
|
305
|
+
|
|
306
|
+
A wrong answer that looks right is the one failure this tool must not produce,
|
|
307
|
+
and the token order cannot be repaired by guessing: subcommands disagree about
|
|
308
|
+
where a path sits (`impact-chain <symbol> <path>`), so there is no slot to move
|
|
309
|
+
it to. Name the correct command and stop.
|
|
310
|
+
"""
|
|
311
|
+
print(
|
|
312
|
+
f"error: path '{path_token}' comes before the '{subcommand}' subcommand, "
|
|
313
|
+
f"so it would be ignored and the current directory scanned instead.\n"
|
|
314
|
+
f" use: ask {subcommand} {path_token}",
|
|
315
|
+
file=sys.stderr,
|
|
316
|
+
)
|
|
317
|
+
raise SystemExit(2)
|
|
318
|
+
|
|
319
|
+
|
|
296
320
|
def _preprocess_args(args: list[str]) -> list[str]:
|
|
297
321
|
"""Extract a repository path token from an args list and store it in _detected_path.
|
|
298
322
|
|
|
@@ -300,6 +324,8 @@ def _preprocess_args(args: list[str]) -> list[str]:
|
|
|
300
324
|
Correctly skips option values (e.g. ``yaml`` in ``--format yaml``).
|
|
301
325
|
If the first non-flag, non-value positional token is a known subcommand name,
|
|
302
326
|
args are returned unchanged so Click can dispatch the subcommand.
|
|
327
|
+
A path token followed by a subcommand is rejected — see
|
|
328
|
+
``_reject_path_before_subcommand``.
|
|
303
329
|
"""
|
|
304
330
|
result = list(args)
|
|
305
331
|
skip_next = False
|
|
@@ -315,11 +341,17 @@ def _preprocess_args(args: list[str]) -> list[str]:
|
|
|
315
341
|
skip_next = True
|
|
316
342
|
continue
|
|
317
343
|
if arg in _SUBCOMMANDS:
|
|
344
|
+
if _path_index >= 0:
|
|
345
|
+
# A path already claimed a slot, so this subcommand can only be
|
|
346
|
+
# the wrong-order form: the path would be dropped and the CWD
|
|
347
|
+
# scanned in its place.
|
|
348
|
+
_reject_path_before_subcommand(result[_path_index], arg)
|
|
318
349
|
return result # known subcommand — leave for Click to dispatch
|
|
350
|
+
if _path_index >= 0:
|
|
351
|
+
continue # a later positional is the subcommand's own business
|
|
319
352
|
# First genuine positional: treat as repository path
|
|
320
353
|
_set_detected_path(arg)
|
|
321
354
|
_path_index = i
|
|
322
|
-
break
|
|
323
355
|
if _path_index >= 0:
|
|
324
356
|
result.pop(_path_index)
|
|
325
357
|
return result
|
|
@@ -374,21 +374,38 @@ def _rule_composition_runtime_split(
|
|
|
374
374
|
]
|
|
375
375
|
|
|
376
376
|
|
|
377
|
+
def reconcile_code_modules(
|
|
378
|
+
*, layer_files: dict[str, list[str]], repo_files: "list[str]"
|
|
379
|
+
) -> list[ReconciliationFinding]:
|
|
380
|
+
"""Run the code-module attestation rule. Never raises.
|
|
381
|
+
|
|
382
|
+
Returns the findings (name AND detail), so a consumer can both prune the
|
|
383
|
+
refuted layers and SURFACE why — INV-F1-4: a rule that is applied silently
|
|
384
|
+
replaces one dishonesty with another.
|
|
385
|
+
|
|
386
|
+
Args:
|
|
387
|
+
layer_files: {claimed module -> its files}.
|
|
388
|
+
repo_files: every source file, supplying the repo's own runtime.
|
|
389
|
+
"""
|
|
390
|
+
try:
|
|
391
|
+
repo_runtimes = runtime_census({"_repo": list(repo_files)})["_repo"]
|
|
392
|
+
return _rule_layer_code_attestation(runtime_census(layer_files), repo_runtimes)
|
|
393
|
+
except Exception:
|
|
394
|
+
return []
|
|
395
|
+
|
|
396
|
+
|
|
377
397
|
def unattested_layers(
|
|
378
398
|
layer_files: dict[str, list[str]], repo_files: "list[str]"
|
|
379
399
|
) -> frozenset[str]:
|
|
380
400
|
"""Layers holding no code of the repo's runtime (convenience view).
|
|
381
401
|
|
|
382
|
-
|
|
402
|
+
Names only; consumers that must also surface the contradiction use
|
|
403
|
+
`reconcile_code_modules` instead. Never raises.
|
|
383
404
|
"""
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
out
|
|
387
|
-
|
|
388
|
-
out.update(f.symbols)
|
|
389
|
-
return frozenset(out)
|
|
390
|
-
except Exception:
|
|
391
|
-
return frozenset()
|
|
405
|
+
out: set[str] = set()
|
|
406
|
+
for f in reconcile_code_modules(layer_files=layer_files, repo_files=repo_files):
|
|
407
|
+
out.update(f.symbols)
|
|
408
|
+
return frozenset(out)
|
|
392
409
|
|
|
393
410
|
|
|
394
411
|
def reconcile_architecture_evidence(
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|