sourcecode 1.69.0__tar.gz → 1.71.0__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-1.69.0 → sourcecode-1.71.0}/CHANGELOG.md +134 -2
- {sourcecode-1.69.0 → sourcecode-1.71.0}/PKG-INFO +16 -5
- {sourcecode-1.69.0 → sourcecode-1.71.0}/README.md +15 -4
- {sourcecode-1.69.0 → sourcecode-1.71.0}/pyproject.toml +1 -1
- {sourcecode-1.69.0 → sourcecode-1.71.0}/src/sourcecode/__init__.py +1 -1
- {sourcecode-1.69.0 → sourcecode-1.71.0}/src/sourcecode/architecture_analyzer.py +39 -6
- {sourcecode-1.69.0 → sourcecode-1.71.0}/src/sourcecode/architecture_summary.py +66 -0
- {sourcecode-1.69.0 → sourcecode-1.71.0}/src/sourcecode/detectors/java.py +40 -2
- {sourcecode-1.69.0 → sourcecode-1.71.0}/src/sourcecode/hibernate_strat.py +96 -21
- {sourcecode-1.69.0 → sourcecode-1.71.0}/src/sourcecode/integration_detector.py +88 -0
- {sourcecode-1.69.0 → sourcecode-1.71.0}/src/sourcecode/migrate_check.py +144 -14
- {sourcecode-1.69.0 → sourcecode-1.71.0}/src/sourcecode/repository_ir.py +32 -0
- {sourcecode-1.69.0 → sourcecode-1.71.0}/src/sourcecode/spring_impact.py +30 -3
- {sourcecode-1.69.0 → sourcecode-1.71.0}/.github/workflows/build-windows.yml +0 -0
- {sourcecode-1.69.0 → sourcecode-1.71.0}/.gitignore +0 -0
- {sourcecode-1.69.0 → sourcecode-1.71.0}/.ruff.toml +0 -0
- {sourcecode-1.69.0 → sourcecode-1.71.0}/CONTRIBUTING.md +0 -0
- {sourcecode-1.69.0 → sourcecode-1.71.0}/LICENSE +0 -0
- {sourcecode-1.69.0 → sourcecode-1.71.0}/SECURITY.md +0 -0
- {sourcecode-1.69.0 → sourcecode-1.71.0}/raw +0 -0
- {sourcecode-1.69.0 → sourcecode-1.71.0}/scripts/generate_jdk_exports.py +0 -0
- {sourcecode-1.69.0 → sourcecode-1.71.0}/src/sourcecode/adaptive_scanner.py +0 -0
- {sourcecode-1.69.0 → sourcecode-1.71.0}/src/sourcecode/ast_extractor.py +0 -0
- {sourcecode-1.69.0 → sourcecode-1.71.0}/src/sourcecode/cache.py +0 -0
- {sourcecode-1.69.0 → sourcecode-1.71.0}/src/sourcecode/canonical_ir.py +0 -0
- {sourcecode-1.69.0 → sourcecode-1.71.0}/src/sourcecode/cir_graphs.py +0 -0
- {sourcecode-1.69.0 → sourcecode-1.71.0}/src/sourcecode/classifier.py +0 -0
- {sourcecode-1.69.0 → sourcecode-1.71.0}/src/sourcecode/cli.py +0 -0
- {sourcecode-1.69.0 → sourcecode-1.71.0}/src/sourcecode/code_notes_analyzer.py +0 -0
- {sourcecode-1.69.0 → sourcecode-1.71.0}/src/sourcecode/confidence_analyzer.py +0 -0
- {sourcecode-1.69.0 → sourcecode-1.71.0}/src/sourcecode/context_scorer.py +0 -0
- {sourcecode-1.69.0 → sourcecode-1.71.0}/src/sourcecode/context_summarizer.py +0 -0
- {sourcecode-1.69.0 → sourcecode-1.71.0}/src/sourcecode/contract_model.py +0 -0
- {sourcecode-1.69.0 → sourcecode-1.71.0}/src/sourcecode/contract_pipeline.py +0 -0
- {sourcecode-1.69.0 → sourcecode-1.71.0}/src/sourcecode/coverage_parser.py +0 -0
- {sourcecode-1.69.0 → sourcecode-1.71.0}/src/sourcecode/dependency_analyzer.py +0 -0
- {sourcecode-1.69.0 → sourcecode-1.71.0}/src/sourcecode/detectors/__init__.py +0 -0
- {sourcecode-1.69.0 → sourcecode-1.71.0}/src/sourcecode/detectors/base.py +0 -0
- {sourcecode-1.69.0 → sourcecode-1.71.0}/src/sourcecode/detectors/csproj_parser.py +0 -0
- {sourcecode-1.69.0 → sourcecode-1.71.0}/src/sourcecode/detectors/dart.py +0 -0
- {sourcecode-1.69.0 → sourcecode-1.71.0}/src/sourcecode/detectors/dotnet.py +0 -0
- {sourcecode-1.69.0 → sourcecode-1.71.0}/src/sourcecode/detectors/elixir.py +0 -0
- {sourcecode-1.69.0 → sourcecode-1.71.0}/src/sourcecode/detectors/go.py +0 -0
- {sourcecode-1.69.0 → sourcecode-1.71.0}/src/sourcecode/detectors/heuristic.py +0 -0
- {sourcecode-1.69.0 → sourcecode-1.71.0}/src/sourcecode/detectors/hybrid.py +0 -0
- {sourcecode-1.69.0 → sourcecode-1.71.0}/src/sourcecode/detectors/jvm_ext.py +0 -0
- {sourcecode-1.69.0 → sourcecode-1.71.0}/src/sourcecode/detectors/nodejs.py +0 -0
- {sourcecode-1.69.0 → sourcecode-1.71.0}/src/sourcecode/detectors/parsers.py +0 -0
- {sourcecode-1.69.0 → sourcecode-1.71.0}/src/sourcecode/detectors/php.py +0 -0
- {sourcecode-1.69.0 → sourcecode-1.71.0}/src/sourcecode/detectors/project.py +0 -0
- {sourcecode-1.69.0 → sourcecode-1.71.0}/src/sourcecode/detectors/python.py +0 -0
- {sourcecode-1.69.0 → sourcecode-1.71.0}/src/sourcecode/detectors/ruby.py +0 -0
- {sourcecode-1.69.0 → sourcecode-1.71.0}/src/sourcecode/detectors/rust.py +0 -0
- {sourcecode-1.69.0 → sourcecode-1.71.0}/src/sourcecode/detectors/systems.py +0 -0
- {sourcecode-1.69.0 → sourcecode-1.71.0}/src/sourcecode/detectors/terraform.py +0 -0
- {sourcecode-1.69.0 → sourcecode-1.71.0}/src/sourcecode/detectors/tooling.py +0 -0
- {sourcecode-1.69.0 → sourcecode-1.71.0}/src/sourcecode/doc_analyzer.py +0 -0
- {sourcecode-1.69.0 → sourcecode-1.71.0}/src/sourcecode/entrypoint_classifier.py +0 -0
- {sourcecode-1.69.0 → sourcecode-1.71.0}/src/sourcecode/env_analyzer.py +0 -0
- {sourcecode-1.69.0 → sourcecode-1.71.0}/src/sourcecode/error_schema.py +0 -0
- {sourcecode-1.69.0 → sourcecode-1.71.0}/src/sourcecode/explain.py +0 -0
- {sourcecode-1.69.0 → sourcecode-1.71.0}/src/sourcecode/file_chunker.py +0 -0
- {sourcecode-1.69.0 → sourcecode-1.71.0}/src/sourcecode/file_classifier.py +0 -0
- {sourcecode-1.69.0 → sourcecode-1.71.0}/src/sourcecode/flow_analyzer.py +0 -0
- {sourcecode-1.69.0 → sourcecode-1.71.0}/src/sourcecode/format_contract.py +0 -0
- {sourcecode-1.69.0 → sourcecode-1.71.0}/src/sourcecode/fqn_utils.py +0 -0
- {sourcecode-1.69.0 → sourcecode-1.71.0}/src/sourcecode/git_analyzer.py +0 -0
- {sourcecode-1.69.0 → sourcecode-1.71.0}/src/sourcecode/graph_analyzer.py +0 -0
- {sourcecode-1.69.0 → sourcecode-1.71.0}/src/sourcecode/jdk_exports.py +0 -0
- {sourcecode-1.69.0 → sourcecode-1.71.0}/src/sourcecode/license.py +0 -0
- {sourcecode-1.69.0 → sourcecode-1.71.0}/src/sourcecode/mcp/__init__.py +0 -0
- {sourcecode-1.69.0 → sourcecode-1.71.0}/src/sourcecode/mcp/onboarding/__init__.py +0 -0
- {sourcecode-1.69.0 → sourcecode-1.71.0}/src/sourcecode/mcp/onboarding/applier.py +0 -0
- {sourcecode-1.69.0 → sourcecode-1.71.0}/src/sourcecode/mcp/onboarding/backup.py +0 -0
- {sourcecode-1.69.0 → sourcecode-1.71.0}/src/sourcecode/mcp/onboarding/detector.py +0 -0
- {sourcecode-1.69.0 → sourcecode-1.71.0}/src/sourcecode/mcp/onboarding/planner.py +0 -0
- {sourcecode-1.69.0 → sourcecode-1.71.0}/src/sourcecode/mcp/orchestrator.py +0 -0
- {sourcecode-1.69.0 → sourcecode-1.71.0}/src/sourcecode/mcp/registry.py +0 -0
- {sourcecode-1.69.0 → sourcecode-1.71.0}/src/sourcecode/mcp/runner.py +0 -0
- {sourcecode-1.69.0 → sourcecode-1.71.0}/src/sourcecode/mcp/server.py +0 -0
- {sourcecode-1.69.0 → sourcecode-1.71.0}/src/sourcecode/mcp_nudge.py +0 -0
- {sourcecode-1.69.0 → sourcecode-1.71.0}/src/sourcecode/metrics_analyzer.py +0 -0
- {sourcecode-1.69.0 → sourcecode-1.71.0}/src/sourcecode/openapi_surface.py +0 -0
- {sourcecode-1.69.0 → sourcecode-1.71.0}/src/sourcecode/output_budget.py +0 -0
- {sourcecode-1.69.0 → sourcecode-1.71.0}/src/sourcecode/path_filters.py +0 -0
- {sourcecode-1.69.0 → sourcecode-1.71.0}/src/sourcecode/pr_comment_renderer.py +0 -0
- {sourcecode-1.69.0 → sourcecode-1.71.0}/src/sourcecode/pr_impact.py +0 -0
- {sourcecode-1.69.0 → sourcecode-1.71.0}/src/sourcecode/prepare_context.py +0 -0
- {sourcecode-1.69.0 → sourcecode-1.71.0}/src/sourcecode/progress.py +0 -0
- {sourcecode-1.69.0 → sourcecode-1.71.0}/src/sourcecode/ranking_engine.py +0 -0
- {sourcecode-1.69.0 → sourcecode-1.71.0}/src/sourcecode/redactor.py +0 -0
- {sourcecode-1.69.0 → sourcecode-1.71.0}/src/sourcecode/relevance_scorer.py +0 -0
- {sourcecode-1.69.0 → sourcecode-1.71.0}/src/sourcecode/rename_refactor.py +0 -0
- {sourcecode-1.69.0 → sourcecode-1.71.0}/src/sourcecode/repo_classifier.py +0 -0
- {sourcecode-1.69.0 → sourcecode-1.71.0}/src/sourcecode/ris.py +0 -0
- {sourcecode-1.69.0 → sourcecode-1.71.0}/src/sourcecode/runtime_classifier.py +0 -0
- {sourcecode-1.69.0 → sourcecode-1.71.0}/src/sourcecode/scanner.py +0 -0
- {sourcecode-1.69.0 → sourcecode-1.71.0}/src/sourcecode/schema.py +0 -0
- {sourcecode-1.69.0 → sourcecode-1.71.0}/src/sourcecode/security_config.py +0 -0
- {sourcecode-1.69.0 → sourcecode-1.71.0}/src/sourcecode/semantic_analyzer.py +0 -0
- {sourcecode-1.69.0 → sourcecode-1.71.0}/src/sourcecode/serializer.py +0 -0
- {sourcecode-1.69.0 → sourcecode-1.71.0}/src/sourcecode/spring_event_topology.py +0 -0
- {sourcecode-1.69.0 → sourcecode-1.71.0}/src/sourcecode/spring_findings.py +0 -0
- {sourcecode-1.69.0 → sourcecode-1.71.0}/src/sourcecode/spring_model.py +0 -0
- {sourcecode-1.69.0 → sourcecode-1.71.0}/src/sourcecode/spring_security_audit.py +0 -0
- {sourcecode-1.69.0 → sourcecode-1.71.0}/src/sourcecode/spring_semantic.py +0 -0
- {sourcecode-1.69.0 → sourcecode-1.71.0}/src/sourcecode/spring_tx_analyzer.py +0 -0
- {sourcecode-1.69.0 → sourcecode-1.71.0}/src/sourcecode/summarizer.py +0 -0
- {sourcecode-1.69.0 → sourcecode-1.71.0}/src/sourcecode/telemetry/__init__.py +0 -0
- {sourcecode-1.69.0 → sourcecode-1.71.0}/src/sourcecode/telemetry/config.py +0 -0
- {sourcecode-1.69.0 → sourcecode-1.71.0}/src/sourcecode/telemetry/consent.py +0 -0
- {sourcecode-1.69.0 → sourcecode-1.71.0}/src/sourcecode/telemetry/events.py +0 -0
- {sourcecode-1.69.0 → sourcecode-1.71.0}/src/sourcecode/telemetry/filters.py +0 -0
- {sourcecode-1.69.0 → sourcecode-1.71.0}/src/sourcecode/telemetry/transport.py +0 -0
- {sourcecode-1.69.0 → sourcecode-1.71.0}/src/sourcecode/tree_utils.py +0 -0
- {sourcecode-1.69.0 → sourcecode-1.71.0}/src/sourcecode/validation_surface.py +0 -0
- {sourcecode-1.69.0 → sourcecode-1.71.0}/src/sourcecode/version_check.py +0 -0
- {sourcecode-1.69.0 → sourcecode-1.71.0}/src/sourcecode/workspace.py +0 -0
- {sourcecode-1.69.0 → sourcecode-1.71.0}/supabase/functions/README.md +0 -0
- {sourcecode-1.69.0 → sourcecode-1.71.0}/supabase/functions/get-license/index.ts +0 -0
- {sourcecode-1.69.0 → sourcecode-1.71.0}/supabase/functions/lemonsqueezy-webhook/index.ts +0 -0
- {sourcecode-1.69.0 → sourcecode-1.71.0}/supabase/functions/telemetry/index.ts +0 -0
- {sourcecode-1.69.0 → sourcecode-1.71.0}/supabase/sql/license_event_ordering.sql +0 -0
- {sourcecode-1.69.0 → sourcecode-1.71.0}/supabase/sql/telemetry_events.sql +0 -0
|
@@ -1,5 +1,129 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [1.71.0] — 2026-07-01
|
|
4
|
+
|
|
5
|
+
### Fixed — framework-framing & migration reliability on a large non-Boot Spring repo (Jenkins core field test)
|
|
6
|
+
|
|
7
|
+
A migration/architecture audit of **jenkins-ci/jenkins** (`e7eef8d2cf`, branch
|
|
8
|
+
`master`; ~1,900 production Java classes, Maven multi-module, uses
|
|
9
|
+
`spring-security-web` but **no Spring MVC, no Spring Boot, no DI container** —
|
|
10
|
+
Jenkins runs on its own Stapler web framework and ACL/SecurityRealm security SPI)
|
|
11
|
+
surfaced five reproducible defects. In every case the engine already computed the
|
|
12
|
+
*correct* internal signal (downgraded confidence, `spring_present`, `not_detected`,
|
|
13
|
+
a prose `coverage_note`) but that signal did not propagate to the high-level field
|
|
14
|
+
an automated consumer reads first. Each fix targets the root cause and adds
|
|
15
|
+
regression coverage. All findings were verified against direct `grep` over the same
|
|
16
|
+
repo.
|
|
17
|
+
|
|
18
|
+
- **Framework detection — phantom "Spring MVC / Spring AOP" from pom exclusion blocks (BUG #1).**
|
|
19
|
+
`_frameworks_from_pom` serialized the *entire* pom to text and substring-matched
|
|
20
|
+
framework tokens, so `<exclude>org.springframework:spring-web</exclude>` and
|
|
21
|
+
`<exclude>…:spring-aop</exclude>` inside an enforcer bytecode rule in
|
|
22
|
+
`war/pom.xml` (artifact **exclusions**, not dependencies) read as "uses Spring
|
|
23
|
+
MVC / Spring AOP". Detection is now restricted to real dependency / plugin /
|
|
24
|
+
parent **coordinates** (`<exclusion>`/`<exclude>` and comments carry none) — the
|
|
25
|
+
same discipline the Gradle path already applied (JobRunr fix). The Spring Security
|
|
26
|
+
token was also broadened to the true-positive `spring-security-web` /
|
|
27
|
+
`-config` coordinates (Jenkins' real dep), so the honest label survives while the
|
|
28
|
+
phantoms disappear.
|
|
29
|
+
- **`architecture_summary` — categorical "MVC pattern with … view layers" on a repo with zero controllers (BUG #1).**
|
|
30
|
+
The `mvc` pattern is a directory-name heuristic. On a Java/Kotlin repo it is now
|
|
31
|
+
cross-checked against the canonical endpoint extractor in the same run; with no
|
|
32
|
+
HTTP controllers detected it degrades to a directory-derived "Layered code
|
|
33
|
+
organization (… — no HTTP controllers detected)" note, dropping the unverified
|
|
34
|
+
"view" claim rather than asserting MVC.
|
|
35
|
+
- **`migrate-check` — `boot3` dimension applicable without any Spring Boot (BUG #2).**
|
|
36
|
+
The Boot 2→3 axis was gated on `spring_present` (any `org.springframework.*`
|
|
37
|
+
coordinate), so a repo carrying only `spring-security-web` received a
|
|
38
|
+
contaminated `readiness_score` from a migration that does not exist. Applicability
|
|
39
|
+
now requires **positive evidence**: a `spring-boot*` coordinate / Gradle plugin
|
|
40
|
+
(`@SpringBootApplication` always ships one), **or** a concrete `spring_boot_3` /
|
|
41
|
+
`spring_security_6` finding in main code. jakarta findings alone no longer enable
|
|
42
|
+
boot3. A new `spring_boot_present` flag is surfaced; when N/A the dimension is
|
|
43
|
+
excluded from the aggregate with a clear reason.
|
|
44
|
+
- **`migrate-check` — `MIG-002` (javax.servlet) counted `@Deprecated` frozen-legacy shims as high blockers (BUG #3).**
|
|
45
|
+
A namespace-migration finding on a **class-level `@Deprecated`** type (kept for
|
|
46
|
+
binary compat, with a live replacement — Jenkins `ChainedServletFilter` →
|
|
47
|
+
`ChainedServletFilter2`) is now classified `code_context="deprecated_shim"` and
|
|
48
|
+
excluded from `blocking_count`, readiness, and effort, alongside the existing
|
|
49
|
+
test/generated buckets. A `@Deprecated` **method** on a live class stays blocking.
|
|
50
|
+
On Jenkins this dropped `blocking_count` from 6 to 2.
|
|
51
|
+
- **`repo-ir` — `security_model` was a bare `"unknown"` string with no reason (BUG #4).**
|
|
52
|
+
A structured `security_model_detail` companion now mirrors the `not_detected`
|
|
53
|
+
pattern used elsewhere: `status`, `spring_security_filter_chain_detected`,
|
|
54
|
+
`annotation_policies_detected`, `custom_spi_detected`, and a `reason` that
|
|
55
|
+
explicitly notes absence of a *recognized* pattern is not evidence the repo is
|
|
56
|
+
unsecured (Jenkins uses a custom ACL/SecurityRealm SPI).
|
|
57
|
+
- **`export --integrations` — low count carried no structured coverage flag (BUG #5).**
|
|
58
|
+
The honest coverage caveat lived only in the prose `coverage_note`. A structured
|
|
59
|
+
`coverage_confidence` (`low` / `partial` / `high`) plus `coverage_confidence_reason`
|
|
60
|
+
is now emitted: a large repo (≥300 source files) with <10 recognized constructs is
|
|
61
|
+
flagged `low` so a consumer cannot read a low count as low external coupling
|
|
62
|
+
(Jenkins' custom remoting/SCM/Update-Center SPIs are invisible to static matching).
|
|
63
|
+
|
|
64
|
+
New regression coverage across five suites; full suite green (2,900+ tests).
|
|
65
|
+
|
|
66
|
+
## [1.70.0] — 2026-07-01
|
|
67
|
+
|
|
68
|
+
### Fixed — reliability & internal-consistency on a large Spring 5 / Hibernate 5 repo (openmrs-core field test)
|
|
69
|
+
|
|
70
|
+
A full migration audit of **openmrs-core** (`7c0144ad2`, branch `2.9.x`; ~866
|
|
71
|
+
production files, Maven multi-module, Spring Framework 5 + Hibernate 5, classic
|
|
72
|
+
WAR, **no Spring Boot**) surfaced four reproducible defects where a headline value
|
|
73
|
+
was wrong or internally contradictory. Each fix targets the **root cause** and,
|
|
74
|
+
where certainty is not achievable, degrades to an explicit low-confidence / `null`
|
|
75
|
+
signal rather than asserting a concrete-but-false value. New regression coverage
|
|
76
|
+
across four suites.
|
|
77
|
+
|
|
78
|
+
- **`migrate-check` — Hibernate version confused a sibling artifact's version (BUG #1, CRITICAL).**
|
|
79
|
+
Hibernate ORM was resolved by scanning for *any* `<hibernate*version>` property
|
|
80
|
+
and keeping the **newest**, so on openmrs `${hibernateSearchVersion}=6.2.4.Final`
|
|
81
|
+
(Hibernate **Search**, a different artifact) beat `${hibernateVersion}=5.6.15.Final`
|
|
82
|
+
(Hibernate **ORM**). The tool declared the repo "already on Hibernate 6", set
|
|
83
|
+
`migration_applicable:false`, and silently **dropped the 28.9–95.6 person-day**
|
|
84
|
+
rewrite range from `estimated_effort_days` (under-reporting 1.5–2.6×). Resolution
|
|
85
|
+
is now **anchored to the `hibernate-core`/`hibernate-orm` dependency** — the
|
|
86
|
+
`<version>` on that coordinate, resolving a `${property}` to the *specific*
|
|
87
|
+
referenced property, and the Gradle coordinate. The property fallback (BOM-managed
|
|
88
|
+
repos) **excludes sibling artifacts** (Search/Validator/Envers/OGM/…). openmrs now
|
|
89
|
+
resolves `5.6.15.Final` / major 5 / `migration_applicable:true`, and
|
|
90
|
+
`estimated_effort_days` includes the Hibernate rewrite range via a new
|
|
91
|
+
`effort_breakdown` block. The same-name contradiction (`hibernate_readiness:100`
|
|
92
|
+
at root vs `0` nested) is removed: the nested field is renamed
|
|
93
|
+
**`rewrite_zone_readiness`** (raw rewrite-zone score, independent of applicability),
|
|
94
|
+
leaving the document-root `hibernate_readiness` as the single authoritative,
|
|
95
|
+
applicability-gated score.
|
|
96
|
+
|
|
97
|
+
- **`impact-chain` — a class's own methods counted as "direct callers" (BUG #2, HIGH).**
|
|
98
|
+
For a class-level seed, the BFS folds in every method key of the class, so internal
|
|
99
|
+
method→method calls leaked into `direct_callers`. On `ConceptServiceImpl` this
|
|
100
|
+
reported **24 direct callers (22 of them own members)**, inflating `risk_score` to
|
|
101
|
+
`20.0`/`high` — ~12× the real blast radius. Callers whose owning class is a seed
|
|
102
|
+
class are now excluded (they are *members*, not external callers); the count is
|
|
103
|
+
surfaced as `self_referential_excluded` plus an `analysis_warnings` entry.
|
|
104
|
+
`ConceptServiceImpl` now reports **2 external callers**, `risk_score` recomputed to
|
|
105
|
+
`8.5`/`medium`.
|
|
106
|
+
|
|
107
|
+
- **`export --integrations` — a user class classified as a JDK API by bare name (BUG #3, MEDIUM).**
|
|
108
|
+
`org.openmrs.util.HttpClient` (a thin wrapper over `java.net.HttpURLConnection`)
|
|
109
|
+
was labeled `jdk-httpclient` purely because its simple name matches
|
|
110
|
+
`java.net.http.HttpClient`. The client is now resolved by the **fully-qualified
|
|
111
|
+
import** — `jdk-httpclient` only when `java.net.http.HttpClient` is actually
|
|
112
|
+
imported and the file does not declare its own — otherwise it degrades to a
|
|
113
|
+
low-confidence **`custom-http-wrapper`**. Pure type-declaration sites (field,
|
|
114
|
+
parameter, return type such as `setImplementationIdHttpClient(HttpClient c)`) are
|
|
115
|
+
no longer emitted as network-invocation evidence; only constructions / static calls
|
|
116
|
+
count.
|
|
117
|
+
|
|
118
|
+
- **`--compact` — "rest api" headline unsupported by the `endpoints` command (BUG #4, LOW).**
|
|
119
|
+
The `architecture_summary` label is driven by HTTP-framework **presence** (Spring
|
|
120
|
+
MVC on the classpath), so openmrs was headlined "Java rest api" while the
|
|
121
|
+
authoritative `endpoints` command found **only 3 endpoints** (one a test module,
|
|
122
|
+
one medium-confidence `router_dsl`). The summary now consults the same endpoint
|
|
123
|
+
extractor and, below a high-confidence-endpoint threshold, degrades to a qualified,
|
|
124
|
+
consistent phrasing ("HTTP framework present; only N endpoints detected — see
|
|
125
|
+
`endpoints`") instead of overclaiming (Java/Kotlin repos only).
|
|
126
|
+
|
|
3
127
|
## [1.69.0] — 2026-06-30
|
|
4
128
|
|
|
5
129
|
### Fixed — framing & symbol-graph fidelity on a non-framework Java library (JobRunr field test)
|
|
@@ -57,8 +181,16 @@ suites.
|
|
|
57
181
|
emits the misleading "No stereotype detected — may be a plain class or utility" for
|
|
58
182
|
a structurally central non-annotated class: it infers a low-confidence stereotype
|
|
59
183
|
from in-degree, lifecycle methods, and naming (e.g. `BackgroundJobServer` →
|
|
60
|
-
"Likely server/orchestrator … lifecycle methods detected (start/stop)").
|
|
61
|
-
`
|
|
184
|
+
"Likely server/orchestrator … lifecycle methods detected (start/stop)"). Finally
|
|
185
|
+
the `architecture_summary` no longer prints a phantom "MVC pattern with
|
|
186
|
+
controller, model, view layers" for a non-web library: MVC now requires a real
|
|
187
|
+
View layer (it was inferred from a `handlers` dir + a `model` dir with no view),
|
|
188
|
+
a bundled SPA under `src/main/resources/.../frontend` is no longer counted as a
|
|
189
|
+
backend view layer, `testFixtures` source roots are excluded, and View detection
|
|
190
|
+
recognizes template files (`.html`/etc.) that the code-extension filter dropped.
|
|
191
|
+
JobRunr `project_type` "api" → "library", `frameworks` `["Quarkus"]` → `[]`,
|
|
192
|
+
architecture "MVC … controller, model, view" → "Layered … controller, repository,
|
|
193
|
+
infrastructure".
|
|
62
194
|
|
|
63
195
|
## [1.68.0] — 2026-06-30
|
|
64
196
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: sourcecode
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.71.0
|
|
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
|
|
@@ -411,7 +411,7 @@ Emits **structured, tool-agnostic** codebase views as plain JSON/YAML — the ki
|
|
|
411
411
|
|------|--------|
|
|
412
412
|
| `--by-directory` | One group per source directory, each symbol with a `source_file:line` reference. |
|
|
413
413
|
| `--module-graph` | `{nodes, edges, summary}` — directories as modules, inter-module dependencies rolled up from class-level relation edges with hit counts + edge types. |
|
|
414
|
-
| `--integrations` | Outbound integrations across Spring **and** plain-Java/Jakarta stacks: HTTP (`RestTemplate`, `WebClient`, `@FeignClient`, JDK `HttpClient`, Apache, OkHttp), LDAP (`LdapTemplate`, JNDI `InitialDirContext`/`LdapContext`), SMTP (`JavaMailSender`, JavaMail `Transport`/`MimeMessage`), and JMS — with `file:line` evidence and a literal `target` when present. Emits an explicit `confidence` (`observed` / `not_analyzed`)
|
|
414
|
+
| `--integrations` | Outbound integrations across Spring **and** plain-Java/Jakarta stacks: HTTP (`RestTemplate`, `WebClient`, `@FeignClient`, JDK `HttpClient`, Apache, OkHttp), LDAP (`LdapTemplate`, JNDI `InitialDirContext`/`LdapContext`), SMTP (`JavaMailSender`, JavaMail `Transport`/`MimeMessage`), and JMS — with `file:line` evidence and a literal `target` when present. Emits an explicit `confidence` (`observed` / `not_analyzed`), a structured `coverage_confidence` (`low` / `partial` / `high`) with `coverage_confidence_reason`, and a prose `coverage_note`: a count of `0` — or a low count on a large repo (≥300 files, <10 constructs) — is flagged `low`, so a low count is never read as low external coupling (custom protocol/SPI clients are not statically visible). |
|
|
415
415
|
| `--c4` | Unified document: `c4.{context, containers, components, code}` + `api_surface` + a `manifest` with per-directory content hashes for **incremental** consumers (skip directories whose hash is unchanged). `components.module_roots` rolls leaf source dirs up to architectural module roots and classifies each `layered` (DDD: ≥2 of `domain`/`application`/`infrastructure`) vs `flat` (legacy/flat package), with a verifiable `module_count` — so a consumer enumerates real modules instead of inferring boundaries from leaf directories. |
|
|
416
416
|
|
|
417
417
|
The section flags compose (pass several for one multi-section document); `--c4` assembles the full export on its own. URLs assembled at runtime yield `target: null` (honest absence, never a guess); containers are derived from build files (Maven/Gradle) and reported as a limitation when none are found.
|
|
@@ -734,9 +734,20 @@ evidence. `migrate-check` enforces:
|
|
|
734
734
|
`hibernate_rewrite` headline. An **unresolved** version degrades to a
|
|
735
735
|
low-confidence hypothesis with no headline blocker. See
|
|
736
736
|
`hibernate.effective_version` / `version_major` / `version_confidence`.
|
|
737
|
-
- **Boot3
|
|
738
|
-
|
|
739
|
-
|
|
737
|
+
- **Boot3 needs Spring _Boot_, not just Spring.** `boot3.applicable` requires
|
|
738
|
+
positive Spring Boot evidence — a `spring-boot*` coordinate / Gradle plugin (or
|
|
739
|
+
`@SpringBootApplication`), **or** a concrete `spring_boot_3` / `spring_security_6`
|
|
740
|
+
finding. A repo that merely carries a Spring library (e.g. `spring-security-web`
|
|
741
|
+
with no Boot — Jenkins core) reports `boot3` as N/A with a reason and is excluded
|
|
742
|
+
from the aggregate; `spring_present: true` but `spring_boot_present: false`. Quarkus
|
|
743
|
+
/ Micronaut / Helidon / Jakarta-pure repos likewise report `boot3` N/A. jakarta
|
|
744
|
+
findings alone never enable boot3.
|
|
745
|
+
- **Frozen-legacy `@Deprecated` shims aren't blockers.** A `javax→jakarta` finding
|
|
746
|
+
on a **class-level `@Deprecated`** type (kept for binary compat, replaced
|
|
747
|
+
elsewhere) is classified `code_context: "deprecated_shim"` and excluded from
|
|
748
|
+
`blocking_count` / readiness / effort — surfaced under `non_blocking` with a
|
|
749
|
+
"verify before removing" note. A `@Deprecated` **method** on a live class still
|
|
750
|
+
blocks.
|
|
740
751
|
- **Permanent `javax.*` are never jakarta debt.** JDK/JSR namespaces
|
|
741
752
|
(`javax.cache`, `javax.sql`, `javax.xml` JAXP, `javax.crypto`, `javax.naming`,
|
|
742
753
|
`javax.management`, `javax.security.auth`, `javax.annotation.processing`, …) are
|
|
@@ -373,7 +373,7 @@ Emits **structured, tool-agnostic** codebase views as plain JSON/YAML — the ki
|
|
|
373
373
|
|------|--------|
|
|
374
374
|
| `--by-directory` | One group per source directory, each symbol with a `source_file:line` reference. |
|
|
375
375
|
| `--module-graph` | `{nodes, edges, summary}` — directories as modules, inter-module dependencies rolled up from class-level relation edges with hit counts + edge types. |
|
|
376
|
-
| `--integrations` | Outbound integrations across Spring **and** plain-Java/Jakarta stacks: HTTP (`RestTemplate`, `WebClient`, `@FeignClient`, JDK `HttpClient`, Apache, OkHttp), LDAP (`LdapTemplate`, JNDI `InitialDirContext`/`LdapContext`), SMTP (`JavaMailSender`, JavaMail `Transport`/`MimeMessage`), and JMS — with `file:line` evidence and a literal `target` when present. Emits an explicit `confidence` (`observed` / `not_analyzed`)
|
|
376
|
+
| `--integrations` | Outbound integrations across Spring **and** plain-Java/Jakarta stacks: HTTP (`RestTemplate`, `WebClient`, `@FeignClient`, JDK `HttpClient`, Apache, OkHttp), LDAP (`LdapTemplate`, JNDI `InitialDirContext`/`LdapContext`), SMTP (`JavaMailSender`, JavaMail `Transport`/`MimeMessage`), and JMS — with `file:line` evidence and a literal `target` when present. Emits an explicit `confidence` (`observed` / `not_analyzed`), a structured `coverage_confidence` (`low` / `partial` / `high`) with `coverage_confidence_reason`, and a prose `coverage_note`: a count of `0` — or a low count on a large repo (≥300 files, <10 constructs) — is flagged `low`, so a low count is never read as low external coupling (custom protocol/SPI clients are not statically visible). |
|
|
377
377
|
| `--c4` | Unified document: `c4.{context, containers, components, code}` + `api_surface` + a `manifest` with per-directory content hashes for **incremental** consumers (skip directories whose hash is unchanged). `components.module_roots` rolls leaf source dirs up to architectural module roots and classifies each `layered` (DDD: ≥2 of `domain`/`application`/`infrastructure`) vs `flat` (legacy/flat package), with a verifiable `module_count` — so a consumer enumerates real modules instead of inferring boundaries from leaf directories. |
|
|
378
378
|
|
|
379
379
|
The section flags compose (pass several for one multi-section document); `--c4` assembles the full export on its own. URLs assembled at runtime yield `target: null` (honest absence, never a guess); containers are derived from build files (Maven/Gradle) and reported as a limitation when none are found.
|
|
@@ -696,9 +696,20 @@ evidence. `migrate-check` enforces:
|
|
|
696
696
|
`hibernate_rewrite` headline. An **unresolved** version degrades to a
|
|
697
697
|
low-confidence hypothesis with no headline blocker. See
|
|
698
698
|
`hibernate.effective_version` / `version_major` / `version_confidence`.
|
|
699
|
-
- **Boot3
|
|
700
|
-
|
|
701
|
-
|
|
699
|
+
- **Boot3 needs Spring _Boot_, not just Spring.** `boot3.applicable` requires
|
|
700
|
+
positive Spring Boot evidence — a `spring-boot*` coordinate / Gradle plugin (or
|
|
701
|
+
`@SpringBootApplication`), **or** a concrete `spring_boot_3` / `spring_security_6`
|
|
702
|
+
finding. A repo that merely carries a Spring library (e.g. `spring-security-web`
|
|
703
|
+
with no Boot — Jenkins core) reports `boot3` as N/A with a reason and is excluded
|
|
704
|
+
from the aggregate; `spring_present: true` but `spring_boot_present: false`. Quarkus
|
|
705
|
+
/ Micronaut / Helidon / Jakarta-pure repos likewise report `boot3` N/A. jakarta
|
|
706
|
+
findings alone never enable boot3.
|
|
707
|
+
- **Frozen-legacy `@Deprecated` shims aren't blockers.** A `javax→jakarta` finding
|
|
708
|
+
on a **class-level `@Deprecated`** type (kept for binary compat, replaced
|
|
709
|
+
elsewhere) is classified `code_context: "deprecated_shim"` and excluded from
|
|
710
|
+
`blocking_count` / readiness / effort — surfaced under `non_blocking` with a
|
|
711
|
+
"verify before removing" note. A `@Deprecated` **method** on a live class still
|
|
712
|
+
blocks.
|
|
702
713
|
- **Permanent `javax.*` are never jakarta debt.** JDK/JSR namespaces
|
|
703
714
|
(`javax.cache`, `javax.sql`, `javax.xml` JAXP, `javax.crypto`, `javax.naming`,
|
|
704
715
|
`javax.management`, `javax.security.auth`, `javax.annotation.processing`, …) are
|
|
@@ -38,7 +38,15 @@ _CODE_EXTENSIONS = {
|
|
|
38
38
|
}
|
|
39
39
|
_GENERIC_NAMES = {"utils", "helpers", "common", "shared", "misc", "core", "root", ""}
|
|
40
40
|
|
|
41
|
-
_TEST_DIRS: frozenset[str] = frozenset({
|
|
41
|
+
_TEST_DIRS: frozenset[str] = frozenset({
|
|
42
|
+
"tests", "test", "spec", "specs", "__tests__", "e2e",
|
|
43
|
+
# Gradle/Maven test-fixture source roots are test code, not runtime architecture.
|
|
44
|
+
"testfixtures", "testfixture",
|
|
45
|
+
})
|
|
46
|
+
# Always-vendored asset trees — never a backend code layer at any depth.
|
|
47
|
+
_ASSET_DIRS: frozenset[str] = frozenset({
|
|
48
|
+
"node_modules", "bower_components",
|
|
49
|
+
})
|
|
42
50
|
_BENCHMARK_DIRS: frozenset[str] = frozenset({
|
|
43
51
|
"benchmark", "benchmarks", "bench",
|
|
44
52
|
"example", "examples",
|
|
@@ -50,7 +58,7 @@ _BENCHMARK_DIRS: frozenset[str] = frozenset({
|
|
|
50
58
|
_DOCS_DIRS: frozenset[str] = frozenset({"docs", "doc", "documentation", "wiki"})
|
|
51
59
|
_TOOLING_DIRS: frozenset[str] = frozenset({"scripts", "script", "tools", "tool", "ci"})
|
|
52
60
|
# All dirs that are not part of the runtime source architecture
|
|
53
|
-
_NON_SOURCE_DIRS: frozenset[str] = _TEST_DIRS | _BENCHMARK_DIRS | _DOCS_DIRS | _TOOLING_DIRS
|
|
61
|
+
_NON_SOURCE_DIRS: frozenset[str] = _TEST_DIRS | _BENCHMARK_DIRS | _DOCS_DIRS | _TOOLING_DIRS | _ASSET_DIRS
|
|
54
62
|
|
|
55
63
|
# Exact file stems that signal a specific architectural layer
|
|
56
64
|
_LAYER_STEM_EXACT: dict[str, str] = {
|
|
@@ -150,6 +158,15 @@ LAYER_PATTERNS: dict[str, dict[str, list[str]]] = {
|
|
|
150
158
|
},
|
|
151
159
|
}
|
|
152
160
|
|
|
161
|
+
# Layer keys a pattern MUST match to qualify, regardless of score. BUG (JobRunr
|
|
162
|
+
# field test): "mvc" was inferred from a controller-ish dir (`handlers`) plus a
|
|
163
|
+
# `model` dir, with NO view layer — that is not MVC (a library with no templates/
|
|
164
|
+
# pages/components is at most layered). MVC's defining trait is the View, so require
|
|
165
|
+
# it; without it the pattern falls through to layered / a weaker match.
|
|
166
|
+
_PATTERN_REQUIRED_KEYS: dict[str, frozenset[str]] = {
|
|
167
|
+
"mvc": frozenset({"view"}),
|
|
168
|
+
}
|
|
169
|
+
|
|
153
170
|
# Higher value = wins when score ties
|
|
154
171
|
_PATTERN_PRIORITY: dict[str, int] = {
|
|
155
172
|
"cqrs": 8,
|
|
@@ -245,8 +262,12 @@ class ArchitectureAnalyzer:
|
|
|
245
262
|
# Step 2: domain clustering
|
|
246
263
|
domains = self._cluster_domains(filtered)
|
|
247
264
|
|
|
248
|
-
# Step 3: layer detection
|
|
249
|
-
|
|
265
|
+
# Step 3: layer detection. Feed the FULL path list (not the code-extension
|
|
266
|
+
# `filtered` set): a View layer is template files (.html/.jinja/.ejs…) that
|
|
267
|
+
# `filtered` would drop, hiding the very layer that distinguishes MVC.
|
|
268
|
+
# _detect_layers applies its own non-source/asset/test dir filtering, so
|
|
269
|
+
# bundled frontend trees and test fixtures are still excluded.
|
|
270
|
+
pattern, layers = self._detect_layers(sm.file_paths)
|
|
250
271
|
if pattern in (None, "flat", "unknown"):
|
|
251
272
|
if pattern == "flat":
|
|
252
273
|
limitations.append("Layer pattern not detected: project has a flat directory structure")
|
|
@@ -518,10 +539,18 @@ class ArchitectureAnalyzer:
|
|
|
518
539
|
return domains
|
|
519
540
|
|
|
520
541
|
def _detect_layers(self, paths: list[str]) -> tuple[str, list[ArchitectureLayer]]:
|
|
521
|
-
# Exclude non-source paths (tests, benchmarks, docs, tooling
|
|
542
|
+
# Exclude non-source paths (tests, benchmarks, docs, tooling, vendored assets)
|
|
543
|
+
# from layer scoring. Also exclude anything under a `resources/` segment: in
|
|
544
|
+
# Maven/Gradle layouts `src/main/resources/**` is bundled config/assets — e.g.
|
|
545
|
+
# JobRunr ships a React dashboard SPA at
|
|
546
|
+
# `core/src/main/resources/org/jobrunr/dashboard/frontend/src/components`, whose
|
|
547
|
+
# `components` dir would otherwise be miscounted as a backend MVC "view" layer.
|
|
522
548
|
source_paths = [
|
|
523
549
|
p for p in paths
|
|
524
|
-
if not any(
|
|
550
|
+
if not any(
|
|
551
|
+
part.lower() in _NON_SOURCE_DIRS or part.lower() == "resources"
|
|
552
|
+
for part in p.replace("\\", "/").split("/")
|
|
553
|
+
)
|
|
525
554
|
]
|
|
526
555
|
if not source_paths:
|
|
527
556
|
return "unknown", []
|
|
@@ -544,6 +573,10 @@ class ArchitectureAnalyzer:
|
|
|
544
573
|
matched_dirs = [d for d in dir_names if d in keywords]
|
|
545
574
|
if matched_dirs:
|
|
546
575
|
matched[layer_key] = matched_dirs
|
|
576
|
+
# A pattern with unmet required keys cannot qualify (e.g. mvc needs a view).
|
|
577
|
+
required = _PATTERN_REQUIRED_KEYS.get(pattern_name)
|
|
578
|
+
if required and not required.issubset(matched.keys()):
|
|
579
|
+
continue
|
|
547
580
|
score = len(matched)
|
|
548
581
|
priority = _PATTERN_PRIORITY.get(pattern_name, 0)
|
|
549
582
|
if (score, priority) > (best_score, best_priority):
|
|
@@ -23,6 +23,14 @@ _JAVA_EXTENSIONS = {".java", ".kt", ".scala"}
|
|
|
23
23
|
|
|
24
24
|
_CORE_DETECTION_MODULES = {"scanner", "detectors", "classifier", "workspace"}
|
|
25
25
|
|
|
26
|
+
# BUG #4 (v1.70.0): the "REST API" project-type label is driven by HTTP-framework
|
|
27
|
+
# PRESENCE (Spring MVC / JAX-RS on the classpath), not by an actual endpoint count.
|
|
28
|
+
# The `--compact` summary is the first (often only) thing an agent reads, so it must
|
|
29
|
+
# not assert "rest api" when the authoritative `endpoints` command finds almost no
|
|
30
|
+
# high-confidence surface. Below this many high-confidence endpoints we degrade the
|
|
31
|
+
# headline to a qualified, consistent phrasing instead of overclaiming.
|
|
32
|
+
_MIN_REST_ENDPOINTS_FOR_LABEL = 5
|
|
33
|
+
|
|
26
34
|
_OPTIONAL_LABEL_MAP: dict[str, str] = {
|
|
27
35
|
"DependencyAnalyzer": "dependencias",
|
|
28
36
|
"GraphAnalyzer": "grafo de módulos",
|
|
@@ -41,6 +49,7 @@ class ArchitectureSummarizer:
|
|
|
41
49
|
|
|
42
50
|
def __init__(self, root: Path) -> None:
|
|
43
51
|
self.root = root
|
|
52
|
+
self._endpoint_support_cache: tuple[int, int] | None = None
|
|
44
53
|
|
|
45
54
|
def generate(self, sm: SourceMap) -> str | None:
|
|
46
55
|
try:
|
|
@@ -133,6 +142,7 @@ class ArchitectureSummarizer:
|
|
|
133
142
|
# Line 2: architecture pattern + layers
|
|
134
143
|
if arch and arch.pattern not in (None, "unknown", "flat"):
|
|
135
144
|
arch_line = self._describe_arch_pattern(arch)
|
|
145
|
+
arch_line = self._qualify_web_pattern(arch, arch_line, sm)
|
|
136
146
|
if arch_line:
|
|
137
147
|
lines.append(arch_line)
|
|
138
148
|
|
|
@@ -185,9 +195,65 @@ class ArchitectureSummarizer:
|
|
|
185
195
|
|
|
186
196
|
fw_str = f" using {', '.join(fw_names)}" if fw_names else ""
|
|
187
197
|
if runtime:
|
|
198
|
+
# BUG #4: never assert "rest api" in the headline unless the endpoints
|
|
199
|
+
# command actually backs it (Java/Kotlin only — that is where we have an
|
|
200
|
+
# authoritative extractor). Degrade to a qualified, consistent phrasing.
|
|
201
|
+
if sm.project_type == "api" and primary.stack in {"java", "kotlin"}:
|
|
202
|
+
total, high = self._endpoint_support()
|
|
203
|
+
if high < _MIN_REST_ENDPOINTS_FOR_LABEL:
|
|
204
|
+
plural = "s" if total != 1 else ""
|
|
205
|
+
return (
|
|
206
|
+
f"{stack_label} application{fw_str} "
|
|
207
|
+
f"(HTTP framework present; only {total} endpoint{plural} "
|
|
208
|
+
f"detected — see `endpoints`)."
|
|
209
|
+
)
|
|
188
210
|
return f"{stack_label} {runtime.lower()}{fw_str}."
|
|
189
211
|
return f"{stack_label} project{fw_str}."
|
|
190
212
|
|
|
213
|
+
def _endpoint_support(self) -> tuple[int, int]:
|
|
214
|
+
"""Return (total, high_confidence) endpoint counts from the canonical
|
|
215
|
+
Java endpoint extractor — the same source the `endpoints` command uses,
|
|
216
|
+
so the summary cannot diverge from it. Cached; failure degrades to (0, 0)."""
|
|
217
|
+
if self._endpoint_support_cache is not None:
|
|
218
|
+
return self._endpoint_support_cache
|
|
219
|
+
total, high = 0, 0
|
|
220
|
+
try:
|
|
221
|
+
from sourcecode.repository_ir import extract_java_endpoints
|
|
222
|
+
data = extract_java_endpoints(self.root)
|
|
223
|
+
eps = data.get("endpoints", [])
|
|
224
|
+
total = data.get("total", len(eps))
|
|
225
|
+
high = sum(1 for e in eps if (e.get("confidence") or "high") == "high")
|
|
226
|
+
except Exception:
|
|
227
|
+
total, high = 0, 0
|
|
228
|
+
self._endpoint_support_cache = (total, high)
|
|
229
|
+
return self._endpoint_support_cache
|
|
230
|
+
|
|
231
|
+
def _qualify_web_pattern(self, arch: Any, arch_line: str, sm: SourceMap) -> str:
|
|
232
|
+
"""BUG #1 (Jenkins field test): the "mvc" pattern is a directory-name
|
|
233
|
+
heuristic (dirs matching controller/model/view keywords). On a Java/Kotlin
|
|
234
|
+
repo, do not assert "MVC pattern with ... view layers" in prose when the
|
|
235
|
+
canonical endpoint extractor finds no HTTP controllers in the same run —
|
|
236
|
+
Jenkins (Stapler web framework) matches model/view-ish dirs but declares
|
|
237
|
+
zero Spring MVC controllers. Cross-check against `endpoints` and degrade to
|
|
238
|
+
a consistent, non-committal phrasing instead of a categorical MVC claim."""
|
|
239
|
+
if not arch_line or arch.pattern not in ("mvc", "spring_mvc_layered"):
|
|
240
|
+
return arch_line
|
|
241
|
+
primary = next((s for s in sm.stacks if s.primary), sm.stacks[0] if sm.stacks else None)
|
|
242
|
+
if primary is None or primary.stack not in {"java", "kotlin"}:
|
|
243
|
+
return arch_line
|
|
244
|
+
_total, high = self._endpoint_support()
|
|
245
|
+
if high >= _MIN_REST_ENDPOINTS_FOR_LABEL:
|
|
246
|
+
return arch_line
|
|
247
|
+
# No controller backing → the MVC/view claim is unverified. Report the
|
|
248
|
+
# directory-derived layers without the framework label or the "view" claim.
|
|
249
|
+
layer_names = [l.name for l in arch.layers[:4] if l.name != "view"] if arch.layers else []
|
|
250
|
+
if layer_names:
|
|
251
|
+
return (
|
|
252
|
+
f"Layered code organization ({', '.join(layer_names)}; "
|
|
253
|
+
f"directory-based — no HTTP controllers detected)."
|
|
254
|
+
)
|
|
255
|
+
return ""
|
|
256
|
+
|
|
191
257
|
def _describe_arch_pattern(self, arch: Any) -> str:
|
|
192
258
|
pattern_labels = {
|
|
193
259
|
"clean": "Clean Architecture",
|
|
@@ -284,10 +284,43 @@ class JavaDetector(AbstractDetector):
|
|
|
284
284
|
if parent_artifact == "spring-boot-starter-parent":
|
|
285
285
|
sb_version = (parent_elem.findtext(f"{ns}version") or "").strip() or None
|
|
286
286
|
|
|
287
|
-
|
|
287
|
+
# BUG #1 (Jenkins field test): framework tokens must be matched only inside
|
|
288
|
+
# genuine dependency / plugin / parent coordinates, never anywhere in the
|
|
289
|
+
# serialized pom. Jenkins' war/pom.xml lists `<exclude>...:spring-web</exclude>`
|
|
290
|
+
# and `<exclude>...:spring-aop</exclude>` in an enforcer-plugin bytecode rule
|
|
291
|
+
# — those are exclusions of transitive artifacts, not declared dependencies.
|
|
292
|
+
# Serializing the whole tree let them read as "uses Spring MVC / Spring AOP".
|
|
293
|
+
# Restrict the scan to real dependency/plugin/parent artifact coordinates;
|
|
294
|
+
# `<exclusion>` and `<exclude>` elements carry neither.
|
|
295
|
+
text = self._pom_coordinate_text(root_elem, ns)
|
|
288
296
|
frameworks = self._detect_jvm_frameworks(text, "pom.xml", sb_version=sb_version)
|
|
289
297
|
return frameworks
|
|
290
298
|
|
|
299
|
+
def _pom_coordinate_text(self, root_elem: ElementTree.Element, ns: str) -> str:
|
|
300
|
+
"""Collect groupId:artifactId coordinates from real dependency, plugin, and
|
|
301
|
+
parent declarations only. Skips `<exclusion>`/`<exclude>` (transitive-artifact
|
|
302
|
+
removals) and comments, which must never be read as usage signal."""
|
|
303
|
+
coords: list[str] = []
|
|
304
|
+
|
|
305
|
+
def _coord(elem: ElementTree.Element) -> None:
|
|
306
|
+
gid = (elem.findtext(f"{ns}groupId") or "").strip()
|
|
307
|
+
aid = (elem.findtext(f"{ns}artifactId") or "").strip()
|
|
308
|
+
if aid:
|
|
309
|
+
coords.append(f"{gid}:{aid}" if gid else aid)
|
|
310
|
+
|
|
311
|
+
# <dependency> nodes anywhere (dependencies, dependencyManagement, profiles).
|
|
312
|
+
# ElementTree never yields <dependency> under <exclusions> (those are
|
|
313
|
+
# <exclusion>), so exclusion coordinates are structurally excluded.
|
|
314
|
+
for dep in root_elem.iter(f"{ns}dependency"):
|
|
315
|
+
_coord(dep)
|
|
316
|
+
for plugin in root_elem.iter(f"{ns}plugin"):
|
|
317
|
+
_coord(plugin)
|
|
318
|
+
parent_elem = root_elem.find(f"{ns}parent")
|
|
319
|
+
if parent_elem is not None:
|
|
320
|
+
_coord(parent_elem)
|
|
321
|
+
|
|
322
|
+
return "\n".join(coords).lower()
|
|
323
|
+
|
|
291
324
|
def _frameworks_from_gradle(self, path: Path) -> list[FrameworkDetection]:
|
|
292
325
|
original = "\n".join(read_text_lines(path))
|
|
293
326
|
content = original.lower()
|
|
@@ -361,7 +394,12 @@ class JavaDetector(AbstractDetector):
|
|
|
361
394
|
frameworks.append(FrameworkDetection(name="Thymeleaf", source=source))
|
|
362
395
|
if "freemarker" in text:
|
|
363
396
|
frameworks.append(FrameworkDetection(name="FreeMarker", source=source))
|
|
364
|
-
if
|
|
397
|
+
if (
|
|
398
|
+
"spring-boot-starter-security" in text
|
|
399
|
+
or "spring-security-core" in text
|
|
400
|
+
or "spring-security-web" in text
|
|
401
|
+
or "spring-security-config" in text
|
|
402
|
+
):
|
|
365
403
|
frameworks.append(FrameworkDetection(name="Spring Security", source=source))
|
|
366
404
|
if "spring-boot-starter-data-jpa" in text or "spring-data-jpa" in text:
|
|
367
405
|
frameworks.append(FrameworkDetection(name="Spring Data JPA", source=source))
|