sourcecode 1.64.0__tar.gz → 1.65.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.
Files changed (122) hide show
  1. {sourcecode-1.64.0 → sourcecode-1.65.0}/CHANGELOG.md +54 -0
  2. {sourcecode-1.64.0 → sourcecode-1.65.0}/PKG-INFO +33 -3
  3. {sourcecode-1.64.0 → sourcecode-1.65.0}/README.md +32 -2
  4. {sourcecode-1.64.0 → sourcecode-1.65.0}/pyproject.toml +1 -1
  5. {sourcecode-1.64.0 → sourcecode-1.65.0}/src/sourcecode/__init__.py +1 -1
  6. {sourcecode-1.64.0 → sourcecode-1.65.0}/src/sourcecode/hibernate_strat.py +102 -2
  7. {sourcecode-1.64.0 → sourcecode-1.65.0}/src/sourcecode/integration_detector.py +31 -0
  8. {sourcecode-1.64.0 → sourcecode-1.65.0}/src/sourcecode/migrate_check.py +266 -68
  9. {sourcecode-1.64.0 → sourcecode-1.65.0}/src/sourcecode/path_filters.py +33 -0
  10. {sourcecode-1.64.0 → sourcecode-1.65.0}/.github/workflows/build-windows.yml +0 -0
  11. {sourcecode-1.64.0 → sourcecode-1.65.0}/.gitignore +0 -0
  12. {sourcecode-1.64.0 → sourcecode-1.65.0}/.ruff.toml +0 -0
  13. {sourcecode-1.64.0 → sourcecode-1.65.0}/CONTRIBUTING.md +0 -0
  14. {sourcecode-1.64.0 → sourcecode-1.65.0}/LICENSE +0 -0
  15. {sourcecode-1.64.0 → sourcecode-1.65.0}/SECURITY.md +0 -0
  16. {sourcecode-1.64.0 → sourcecode-1.65.0}/raw +0 -0
  17. {sourcecode-1.64.0 → sourcecode-1.65.0}/src/sourcecode/adaptive_scanner.py +0 -0
  18. {sourcecode-1.64.0 → sourcecode-1.65.0}/src/sourcecode/architecture_analyzer.py +0 -0
  19. {sourcecode-1.64.0 → sourcecode-1.65.0}/src/sourcecode/architecture_summary.py +0 -0
  20. {sourcecode-1.64.0 → sourcecode-1.65.0}/src/sourcecode/ast_extractor.py +0 -0
  21. {sourcecode-1.64.0 → sourcecode-1.65.0}/src/sourcecode/cache.py +0 -0
  22. {sourcecode-1.64.0 → sourcecode-1.65.0}/src/sourcecode/canonical_ir.py +0 -0
  23. {sourcecode-1.64.0 → sourcecode-1.65.0}/src/sourcecode/cir_graphs.py +0 -0
  24. {sourcecode-1.64.0 → sourcecode-1.65.0}/src/sourcecode/classifier.py +0 -0
  25. {sourcecode-1.64.0 → sourcecode-1.65.0}/src/sourcecode/cli.py +0 -0
  26. {sourcecode-1.64.0 → sourcecode-1.65.0}/src/sourcecode/code_notes_analyzer.py +0 -0
  27. {sourcecode-1.64.0 → sourcecode-1.65.0}/src/sourcecode/confidence_analyzer.py +0 -0
  28. {sourcecode-1.64.0 → sourcecode-1.65.0}/src/sourcecode/context_scorer.py +0 -0
  29. {sourcecode-1.64.0 → sourcecode-1.65.0}/src/sourcecode/context_summarizer.py +0 -0
  30. {sourcecode-1.64.0 → sourcecode-1.65.0}/src/sourcecode/contract_model.py +0 -0
  31. {sourcecode-1.64.0 → sourcecode-1.65.0}/src/sourcecode/contract_pipeline.py +0 -0
  32. {sourcecode-1.64.0 → sourcecode-1.65.0}/src/sourcecode/coverage_parser.py +0 -0
  33. {sourcecode-1.64.0 → sourcecode-1.65.0}/src/sourcecode/dependency_analyzer.py +0 -0
  34. {sourcecode-1.64.0 → sourcecode-1.65.0}/src/sourcecode/detectors/__init__.py +0 -0
  35. {sourcecode-1.64.0 → sourcecode-1.65.0}/src/sourcecode/detectors/base.py +0 -0
  36. {sourcecode-1.64.0 → sourcecode-1.65.0}/src/sourcecode/detectors/csproj_parser.py +0 -0
  37. {sourcecode-1.64.0 → sourcecode-1.65.0}/src/sourcecode/detectors/dart.py +0 -0
  38. {sourcecode-1.64.0 → sourcecode-1.65.0}/src/sourcecode/detectors/dotnet.py +0 -0
  39. {sourcecode-1.64.0 → sourcecode-1.65.0}/src/sourcecode/detectors/elixir.py +0 -0
  40. {sourcecode-1.64.0 → sourcecode-1.65.0}/src/sourcecode/detectors/go.py +0 -0
  41. {sourcecode-1.64.0 → sourcecode-1.65.0}/src/sourcecode/detectors/heuristic.py +0 -0
  42. {sourcecode-1.64.0 → sourcecode-1.65.0}/src/sourcecode/detectors/hybrid.py +0 -0
  43. {sourcecode-1.64.0 → sourcecode-1.65.0}/src/sourcecode/detectors/java.py +0 -0
  44. {sourcecode-1.64.0 → sourcecode-1.65.0}/src/sourcecode/detectors/jvm_ext.py +0 -0
  45. {sourcecode-1.64.0 → sourcecode-1.65.0}/src/sourcecode/detectors/nodejs.py +0 -0
  46. {sourcecode-1.64.0 → sourcecode-1.65.0}/src/sourcecode/detectors/parsers.py +0 -0
  47. {sourcecode-1.64.0 → sourcecode-1.65.0}/src/sourcecode/detectors/php.py +0 -0
  48. {sourcecode-1.64.0 → sourcecode-1.65.0}/src/sourcecode/detectors/project.py +0 -0
  49. {sourcecode-1.64.0 → sourcecode-1.65.0}/src/sourcecode/detectors/python.py +0 -0
  50. {sourcecode-1.64.0 → sourcecode-1.65.0}/src/sourcecode/detectors/ruby.py +0 -0
  51. {sourcecode-1.64.0 → sourcecode-1.65.0}/src/sourcecode/detectors/rust.py +0 -0
  52. {sourcecode-1.64.0 → sourcecode-1.65.0}/src/sourcecode/detectors/systems.py +0 -0
  53. {sourcecode-1.64.0 → sourcecode-1.65.0}/src/sourcecode/detectors/terraform.py +0 -0
  54. {sourcecode-1.64.0 → sourcecode-1.65.0}/src/sourcecode/detectors/tooling.py +0 -0
  55. {sourcecode-1.64.0 → sourcecode-1.65.0}/src/sourcecode/doc_analyzer.py +0 -0
  56. {sourcecode-1.64.0 → sourcecode-1.65.0}/src/sourcecode/entrypoint_classifier.py +0 -0
  57. {sourcecode-1.64.0 → sourcecode-1.65.0}/src/sourcecode/env_analyzer.py +0 -0
  58. {sourcecode-1.64.0 → sourcecode-1.65.0}/src/sourcecode/error_schema.py +0 -0
  59. {sourcecode-1.64.0 → sourcecode-1.65.0}/src/sourcecode/explain.py +0 -0
  60. {sourcecode-1.64.0 → sourcecode-1.65.0}/src/sourcecode/file_chunker.py +0 -0
  61. {sourcecode-1.64.0 → sourcecode-1.65.0}/src/sourcecode/file_classifier.py +0 -0
  62. {sourcecode-1.64.0 → sourcecode-1.65.0}/src/sourcecode/flow_analyzer.py +0 -0
  63. {sourcecode-1.64.0 → sourcecode-1.65.0}/src/sourcecode/format_contract.py +0 -0
  64. {sourcecode-1.64.0 → sourcecode-1.65.0}/src/sourcecode/fqn_utils.py +0 -0
  65. {sourcecode-1.64.0 → sourcecode-1.65.0}/src/sourcecode/git_analyzer.py +0 -0
  66. {sourcecode-1.64.0 → sourcecode-1.65.0}/src/sourcecode/graph_analyzer.py +0 -0
  67. {sourcecode-1.64.0 → sourcecode-1.65.0}/src/sourcecode/license.py +0 -0
  68. {sourcecode-1.64.0 → sourcecode-1.65.0}/src/sourcecode/mcp/__init__.py +0 -0
  69. {sourcecode-1.64.0 → sourcecode-1.65.0}/src/sourcecode/mcp/onboarding/__init__.py +0 -0
  70. {sourcecode-1.64.0 → sourcecode-1.65.0}/src/sourcecode/mcp/onboarding/applier.py +0 -0
  71. {sourcecode-1.64.0 → sourcecode-1.65.0}/src/sourcecode/mcp/onboarding/backup.py +0 -0
  72. {sourcecode-1.64.0 → sourcecode-1.65.0}/src/sourcecode/mcp/onboarding/detector.py +0 -0
  73. {sourcecode-1.64.0 → sourcecode-1.65.0}/src/sourcecode/mcp/onboarding/planner.py +0 -0
  74. {sourcecode-1.64.0 → sourcecode-1.65.0}/src/sourcecode/mcp/orchestrator.py +0 -0
  75. {sourcecode-1.64.0 → sourcecode-1.65.0}/src/sourcecode/mcp/registry.py +0 -0
  76. {sourcecode-1.64.0 → sourcecode-1.65.0}/src/sourcecode/mcp/runner.py +0 -0
  77. {sourcecode-1.64.0 → sourcecode-1.65.0}/src/sourcecode/mcp/server.py +0 -0
  78. {sourcecode-1.64.0 → sourcecode-1.65.0}/src/sourcecode/mcp_nudge.py +0 -0
  79. {sourcecode-1.64.0 → sourcecode-1.65.0}/src/sourcecode/metrics_analyzer.py +0 -0
  80. {sourcecode-1.64.0 → sourcecode-1.65.0}/src/sourcecode/openapi_surface.py +0 -0
  81. {sourcecode-1.64.0 → sourcecode-1.65.0}/src/sourcecode/output_budget.py +0 -0
  82. {sourcecode-1.64.0 → sourcecode-1.65.0}/src/sourcecode/pr_comment_renderer.py +0 -0
  83. {sourcecode-1.64.0 → sourcecode-1.65.0}/src/sourcecode/pr_impact.py +0 -0
  84. {sourcecode-1.64.0 → sourcecode-1.65.0}/src/sourcecode/prepare_context.py +0 -0
  85. {sourcecode-1.64.0 → sourcecode-1.65.0}/src/sourcecode/progress.py +0 -0
  86. {sourcecode-1.64.0 → sourcecode-1.65.0}/src/sourcecode/ranking_engine.py +0 -0
  87. {sourcecode-1.64.0 → sourcecode-1.65.0}/src/sourcecode/redactor.py +0 -0
  88. {sourcecode-1.64.0 → sourcecode-1.65.0}/src/sourcecode/relevance_scorer.py +0 -0
  89. {sourcecode-1.64.0 → sourcecode-1.65.0}/src/sourcecode/rename_refactor.py +0 -0
  90. {sourcecode-1.64.0 → sourcecode-1.65.0}/src/sourcecode/repo_classifier.py +0 -0
  91. {sourcecode-1.64.0 → sourcecode-1.65.0}/src/sourcecode/repository_ir.py +0 -0
  92. {sourcecode-1.64.0 → sourcecode-1.65.0}/src/sourcecode/ris.py +0 -0
  93. {sourcecode-1.64.0 → sourcecode-1.65.0}/src/sourcecode/runtime_classifier.py +0 -0
  94. {sourcecode-1.64.0 → sourcecode-1.65.0}/src/sourcecode/scanner.py +0 -0
  95. {sourcecode-1.64.0 → sourcecode-1.65.0}/src/sourcecode/schema.py +0 -0
  96. {sourcecode-1.64.0 → sourcecode-1.65.0}/src/sourcecode/security_config.py +0 -0
  97. {sourcecode-1.64.0 → sourcecode-1.65.0}/src/sourcecode/semantic_analyzer.py +0 -0
  98. {sourcecode-1.64.0 → sourcecode-1.65.0}/src/sourcecode/serializer.py +0 -0
  99. {sourcecode-1.64.0 → sourcecode-1.65.0}/src/sourcecode/spring_event_topology.py +0 -0
  100. {sourcecode-1.64.0 → sourcecode-1.65.0}/src/sourcecode/spring_findings.py +0 -0
  101. {sourcecode-1.64.0 → sourcecode-1.65.0}/src/sourcecode/spring_impact.py +0 -0
  102. {sourcecode-1.64.0 → sourcecode-1.65.0}/src/sourcecode/spring_model.py +0 -0
  103. {sourcecode-1.64.0 → sourcecode-1.65.0}/src/sourcecode/spring_security_audit.py +0 -0
  104. {sourcecode-1.64.0 → sourcecode-1.65.0}/src/sourcecode/spring_semantic.py +0 -0
  105. {sourcecode-1.64.0 → sourcecode-1.65.0}/src/sourcecode/spring_tx_analyzer.py +0 -0
  106. {sourcecode-1.64.0 → sourcecode-1.65.0}/src/sourcecode/summarizer.py +0 -0
  107. {sourcecode-1.64.0 → sourcecode-1.65.0}/src/sourcecode/telemetry/__init__.py +0 -0
  108. {sourcecode-1.64.0 → sourcecode-1.65.0}/src/sourcecode/telemetry/config.py +0 -0
  109. {sourcecode-1.64.0 → sourcecode-1.65.0}/src/sourcecode/telemetry/consent.py +0 -0
  110. {sourcecode-1.64.0 → sourcecode-1.65.0}/src/sourcecode/telemetry/events.py +0 -0
  111. {sourcecode-1.64.0 → sourcecode-1.65.0}/src/sourcecode/telemetry/filters.py +0 -0
  112. {sourcecode-1.64.0 → sourcecode-1.65.0}/src/sourcecode/telemetry/transport.py +0 -0
  113. {sourcecode-1.64.0 → sourcecode-1.65.0}/src/sourcecode/tree_utils.py +0 -0
  114. {sourcecode-1.64.0 → sourcecode-1.65.0}/src/sourcecode/validation_surface.py +0 -0
  115. {sourcecode-1.64.0 → sourcecode-1.65.0}/src/sourcecode/version_check.py +0 -0
  116. {sourcecode-1.64.0 → sourcecode-1.65.0}/src/sourcecode/workspace.py +0 -0
  117. {sourcecode-1.64.0 → sourcecode-1.65.0}/supabase/functions/README.md +0 -0
  118. {sourcecode-1.64.0 → sourcecode-1.65.0}/supabase/functions/get-license/index.ts +0 -0
  119. {sourcecode-1.64.0 → sourcecode-1.65.0}/supabase/functions/lemonsqueezy-webhook/index.ts +0 -0
  120. {sourcecode-1.64.0 → sourcecode-1.65.0}/supabase/functions/telemetry/index.ts +0 -0
  121. {sourcecode-1.64.0 → sourcecode-1.65.0}/supabase/sql/license_event_ordering.sql +0 -0
  122. {sourcecode-1.64.0 → sourcecode-1.65.0}/supabase/sql/telemetry_events.sql +0 -0
@@ -1,5 +1,59 @@
1
1
  # Changelog
2
2
 
3
+ ## [1.65.0] — 2026-06-30
4
+
5
+ ### Fixed — non-Spring / already-modernized repos no longer fabricate blockers (`migrate-check`)
6
+
7
+ A Pro audit of a real enterprise repo (**Keycloak**, Quarkus 3.33 / Jakarta EE /
8
+ Hibernate ORM 6.2.13, Jakarta migration **complete**, 7,885 Java files / 186
9
+ modules) exposed a cluster of false positives that produced `readiness 0/100`, a
10
+ phantom `hibernate_rewrite` headline, and a `boot3` dimension marked applicable
11
+ with **zero** Spring. A migration verdict shown to a CTO must never contradict its
12
+ own evidence. Keycloak is now a golden regression fixture
13
+ (`tests/test_migrate_check_keycloak_regression.py`).
14
+
15
+ - **Hibernate version is resolved before any 5→6 verdict (BUG #1).** The effective
16
+ Hibernate version is parsed from build descriptors, following Maven
17
+ `${properties}` and BOM-managed `<hibernate*.version>` values plus explicit
18
+ `hibernate-core`/`hibernate-orm` coordinates. Major ≥ 6 → the 5→6 axis is **N/A**
19
+ (migration already done), never a blocker. Unresolved version → low-confidence
20
+ hypothesis, **no** `headline_blocker`. New `hibernate.effective_version`,
21
+ `version_major`, `version_confidence`, `migration_applicable`.
22
+ - **Boot3 dimension auto-disables without Spring (BUG #2).** `boot3.applicable` is
23
+ now a function of real Spring usage (build coordinate or `org.springframework`
24
+ import). Quarkus / Micronaut / Jakarta-pure repos report `boot3` as N/A instead
25
+ of a contradictory `applicable: true`.
26
+ - **Jakarta scorer ignores permanent `javax.*` (BUG #3).** The JDK/JSR allowlist
27
+ (`javax.xml.*`, `javax.crypto.*`, `javax.naming.*`, `javax.sql.*`,
28
+ `javax.management.*`, `javax.security.auth.*`, …) is honored end-to-end, so the
29
+ Keycloak's 1,065 permanent `javax.*` imports stop counting as jakarta debt.
30
+ - **Readiness headline can no longer be a misleading 0 (BUG #4).** Framework
31
+ blockers (jakarta / Boot3 / Security) deduct uncapped; orthogonal JDK debt is
32
+ advisory-capped and best-practice hygiene is excluded — so an already-modernized
33
+ repo reads high (Keycloak 85/100), never 0. The headline cannot contradict its
34
+ own note.
35
+ - **Effort excludes N/A axes (BUG #5).** `estimated_effort_days` is computed over
36
+ product (`main`) findings only; the phantom Hibernate 5→6 effort is gone.
37
+ - **Best-practice hygiene no longer sinks a dimension (BUG #6).**
38
+ `java.util.Date`/`Calendar` (MIG-016, `java_8_best_practice`) is reported as a
39
+ separate `hygiene_findings` metric and excluded from JDK-modernization scoring.
40
+ - **MIG-006 explanation matches the actual symbol (BUG #7).** The explanation now
41
+ names the detected `javax.annotation.*` symbol instead of a fixed
42
+ `@PostConstruct/@PreDestroy/@Resource` blurb. `javax.annotation.Generated` (JSR-250,
43
+ emitted into autogenerated SCIM/JAXB sources) is downgraded to **low** and bucketed
44
+ as `generated`.
45
+ - **Test/fixture findings segregated from `blocking_count` (BUG #8).** Findings in
46
+ test harness modules (`testsuite/`, `test-framework/`, `integration-arquillian/`,
47
+ `*-test*`) and under `src/test/` are tagged `code_context: test`; autogenerated
48
+ sources `generated`. Only `main` counts toward `blocking_count`, readiness, and
49
+ effort. The other buckets surface in `non_blocking{}`. Keycloak's 10 test `web.xml`
50
+ + 14 test-framework `sun.*` no longer inflate the verdict (`blocking_count` 30 → 6).
51
+ - **`export --integrations` is no longer Spring-centric (BUG #9).** Detection now
52
+ covers plain-Java/Jakarta HTTP clients (JDK `HttpClient`, Apache, OkHttp), JNDI
53
+ LDAP, and JavaMail/SMTP in addition to Spring templates, and emits an explicit
54
+ `confidence` (`observed` / `not_analyzed`) + `coverage_note` rather than an
55
+ authoritative `0`. Keycloak: 0 → 144 integrations (80 HTTP / 33 LDAP / 31 SMTP).
56
+
3
57
  ## [1.64.0] — 2026-06-30
4
58
 
5
59
  ### Fixed — zero false-positive blockers (`migrate-check` / `endpoints`)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sourcecode
3
- Version: 1.64.0
3
+ Version: 1.65.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 (`RestTemplate`, `WebClient`, `@FeignClient`, `LdapTemplate`, `JmsTemplate`, ActiveMQ) with `file:line` evidence and a literal `target` URL/name when present. |
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`) + `coverage_note`: a count of `0` means "no client construct found", **not** "no integrations" (runtime/DI-wired 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.
@@ -610,7 +610,7 @@ Detects migration blockers across Java source files, Spring XML config files, an
610
610
  | `MIG-002` | high | `javax.servlet` import — Servlet API changed |
611
611
  | `MIG-003` | high | `javax.validation` import — Bean Validation changed |
612
612
  | `MIG-004` | high | `javax.transaction` import — TX API changed |
613
- | `MIG-006` | medium | `javax.annotation` import — CDI annotations changed |
613
+ | `MIG-006` | medium¹ | `javax.annotation` import — CDI annotations changed (¹`javax.annotation.Generated` in autogenerated code → **low**, bucketed `generated`; explanation names the actual symbol) |
614
614
  | `MIG-007` | medium | `javax.inject` import — DI annotations changed |
615
615
  | `MIG-008` | medium | `javax.ws.rs` import — JAX-RS changed |
616
616
  | `MIG-009` | medium | `javax.jms` import — JMS API changed |
@@ -722,9 +722,39 @@ reported as **N/A** (`score: null`), never as `0`. The top-level
722
722
  states that `readiness_score` is a derived aggregate over applicable dimensions
723
723
  only — for decisions, read the per-dimension breakdown + `blocking_count`.
724
724
 
725
+ #### Reliability guarantees (no version, no verdict)
726
+
727
+ A migration verdict shown to a decision-maker must never contradict its own
728
+ evidence. `migrate-check` enforces:
729
+
730
+ - **Version-gated framework axes.** The Hibernate 5→6 axis applies only when the
731
+ effective Hibernate version (resolved from `pom.xml`/Gradle, following Maven
732
+ `${properties}` and BOM-managed `<hibernate*.version>`) is `< 6`. A resolved
733
+ Hibernate **6+** repo is reported `migration_applicable: false` (N/A) — no
734
+ `hibernate_rewrite` headline. An **unresolved** version degrades to a
735
+ low-confidence hypothesis with no headline blocker. See
736
+ `hibernate.effective_version` / `version_major` / `version_confidence`.
737
+ - **Boot3 auto-disables without Spring.** `boot3.applicable` tracks real Spring
738
+ usage; Quarkus / Micronaut / Helidon / Jakarta-pure repos report `boot3` as N/A
739
+ (`spring_present: false`), not a contradictory `applicable: true`.
740
+ - **Permanent `javax.*` are never jakarta debt.** JDK/JSR namespaces
741
+ (`javax.xml.*`, `javax.crypto.*`, `javax.naming.*`, `javax.sql.*`,
742
+ `javax.management.*`, `javax.security.auth.*`, …) are allowlisted across every
743
+ jakarta scorer.
744
+ - **Three buckets: blocker ≠ hygiene ≠ test.** Only product (`main`) code counts
745
+ toward `blocking_count`, readiness, and effort. Test harnesses
746
+ (`testsuite/`, `test-framework/`, `integration-arquillian/`, `*-test*`,
747
+ `src/test/`) and autogenerated sources are tagged `code_context` and surfaced in
748
+ `non_blocking{}`. Best-practice hygiene (`java.util.Date`) is reported as
749
+ `hygiene_findings` and never sinks a dimension. Each finding carries
750
+ `code_context: main | test | generated`.
751
+
725
752
  ```bash
726
753
  # inspect only the Hibernate rewrite targets
727
754
  sourcecode migrate-check . --format json | jq '.hibernate.rewrite_targets[]'
755
+
756
+ # product blockers only (excludes test/fixtures + hygiene)
757
+ sourcecode migrate-check . --format json | jq '.blocking_count, .non_blocking, .hygiene_findings'
728
758
  ```
729
759
 
730
760
  ### `rename-class` — Java class rename
@@ -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 (`RestTemplate`, `WebClient`, `@FeignClient`, `LdapTemplate`, `JmsTemplate`, ActiveMQ) with `file:line` evidence and a literal `target` URL/name when present. |
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`) + `coverage_note`: a count of `0` means "no client construct found", **not** "no integrations" (runtime/DI-wired 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.
@@ -572,7 +572,7 @@ Detects migration blockers across Java source files, Spring XML config files, an
572
572
  | `MIG-002` | high | `javax.servlet` import — Servlet API changed |
573
573
  | `MIG-003` | high | `javax.validation` import — Bean Validation changed |
574
574
  | `MIG-004` | high | `javax.transaction` import — TX API changed |
575
- | `MIG-006` | medium | `javax.annotation` import — CDI annotations changed |
575
+ | `MIG-006` | medium¹ | `javax.annotation` import — CDI annotations changed (¹`javax.annotation.Generated` in autogenerated code → **low**, bucketed `generated`; explanation names the actual symbol) |
576
576
  | `MIG-007` | medium | `javax.inject` import — DI annotations changed |
577
577
  | `MIG-008` | medium | `javax.ws.rs` import — JAX-RS changed |
578
578
  | `MIG-009` | medium | `javax.jms` import — JMS API changed |
@@ -684,9 +684,39 @@ reported as **N/A** (`score: null`), never as `0`. The top-level
684
684
  states that `readiness_score` is a derived aggregate over applicable dimensions
685
685
  only — for decisions, read the per-dimension breakdown + `blocking_count`.
686
686
 
687
+ #### Reliability guarantees (no version, no verdict)
688
+
689
+ A migration verdict shown to a decision-maker must never contradict its own
690
+ evidence. `migrate-check` enforces:
691
+
692
+ - **Version-gated framework axes.** The Hibernate 5→6 axis applies only when the
693
+ effective Hibernate version (resolved from `pom.xml`/Gradle, following Maven
694
+ `${properties}` and BOM-managed `<hibernate*.version>`) is `< 6`. A resolved
695
+ Hibernate **6+** repo is reported `migration_applicable: false` (N/A) — no
696
+ `hibernate_rewrite` headline. An **unresolved** version degrades to a
697
+ low-confidence hypothesis with no headline blocker. See
698
+ `hibernate.effective_version` / `version_major` / `version_confidence`.
699
+ - **Boot3 auto-disables without Spring.** `boot3.applicable` tracks real Spring
700
+ usage; Quarkus / Micronaut / Helidon / Jakarta-pure repos report `boot3` as N/A
701
+ (`spring_present: false`), not a contradictory `applicable: true`.
702
+ - **Permanent `javax.*` are never jakarta debt.** JDK/JSR namespaces
703
+ (`javax.xml.*`, `javax.crypto.*`, `javax.naming.*`, `javax.sql.*`,
704
+ `javax.management.*`, `javax.security.auth.*`, …) are allowlisted across every
705
+ jakarta scorer.
706
+ - **Three buckets: blocker ≠ hygiene ≠ test.** Only product (`main`) code counts
707
+ toward `blocking_count`, readiness, and effort. Test harnesses
708
+ (`testsuite/`, `test-framework/`, `integration-arquillian/`, `*-test*`,
709
+ `src/test/`) and autogenerated sources are tagged `code_context` and surfaced in
710
+ `non_blocking{}`. Best-practice hygiene (`java.util.Date`) is reported as
711
+ `hygiene_findings` and never sinks a dimension. Each finding carries
712
+ `code_context: main | test | generated`.
713
+
687
714
  ```bash
688
715
  # inspect only the Hibernate rewrite targets
689
716
  sourcecode migrate-check . --format json | jq '.hibernate.rewrite_targets[]'
717
+
718
+ # product blockers only (excludes test/fixtures + hygiene)
719
+ sourcecode migrate-check . --format json | jq '.blocking_count, .non_blocking, .hygiene_findings'
690
720
  ```
691
721
 
692
722
  ### `rename-class` — Java class rename
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "sourcecode"
7
- version = "1.64.0"
7
+ version = "1.65.0"
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"
@@ -1,3 +1,3 @@
1
1
  """sourcecode — Deterministic codebase context maps for AI coding agents."""
2
2
 
3
- __version__ = "1.64.0"
3
+ __version__ = "1.65.0"
@@ -253,6 +253,75 @@ def _scan_build_dependency(root: Path) -> tuple[bool, list[str]]:
253
253
  samples.append(snippet)
254
254
  return found, samples
255
255
 
256
+
257
+ # ── BUG #1: effective Hibernate version resolution ─────────────────────────
258
+ # A 5→6 rewrite verdict is only valid when the project is actually on Hibernate 5.
259
+ # Resolve the effective major version from build descriptors, following Maven
260
+ # ${properties} (incl. BOM-managed values declared as <hibernate*.version>) and
261
+ # explicit hibernate-core / hibernate-orm coordinates. When the resolved major is
262
+ # ≥ 6 the 5→6 axis does NOT apply (the migration is already done). When it cannot
263
+ # be resolved the axis degrades to a low-confidence hypothesis (no headline).
264
+ _HIB_VER = r"(\d+\.\d+(?:\.\d+)?(?:[.\-][\w]+)*)" # 6.2.13.Final, 5.6.15, 6.2
265
+ _HIB_PROP_RE = re.compile(
266
+ r"<((?:[\w.\-]*hibernate[\w.\-]*?)\.?version)>\s*" + _HIB_VER + r"\s*</\1>",
267
+ re.IGNORECASE,
268
+ )
269
+ _HIB_COORD_VERSION_RE = re.compile(
270
+ r"<artifactId>\s*hibernate-(?:core|orm)\s*</artifactId>\s*"
271
+ r"(?:<[^>]+>[^<]*</[^>]+>\s*)*?<version>\s*" + _HIB_VER,
272
+ re.IGNORECASE | re.DOTALL,
273
+ )
274
+ _HIB_GRADLE_VERSION_RE = re.compile(
275
+ r"org\.hibernate(?:\.orm)?:hibernate-(?:core|orm):" + _HIB_VER,
276
+ re.IGNORECASE,
277
+ )
278
+
279
+
280
+ def _ver_key(full: str) -> tuple[int, int]:
281
+ parts = full.split(".")
282
+ try:
283
+ major = int(parts[0])
284
+ except (ValueError, IndexError):
285
+ return (0, 0)
286
+ try:
287
+ minor = int(parts[1]) if len(parts) > 1 else 0
288
+ except ValueError:
289
+ minor = 0
290
+ return (major, minor)
291
+
292
+
293
+ def _resolve_hibernate_version(root: Path) -> tuple[Optional[str], Optional[int], str]:
294
+ """Return (full_version_string, major, confidence) for the effective Hibernate.
295
+
296
+ confidence is "high" when a version was resolved from a build descriptor,
297
+ "none" when none could be determined (degrade to hypothesis). The newest
298
+ declared Hibernate wins in a multi-module build.
299
+ """
300
+ import os
301
+ best_full: Optional[str] = None
302
+ for dirpath, dirnames, filenames in os.walk(root):
303
+ dirnames[:] = [d for d in dirnames if d not in _SKIP_BUILD_SCAN_DIRS]
304
+ for fname in filenames:
305
+ if fname not in _BUILD_FILE_NAMES:
306
+ continue
307
+ try:
308
+ text = (Path(dirpath) / fname).read_text(encoding="utf-8", errors="replace")
309
+ except OSError:
310
+ continue
311
+ fulls: list[str] = []
312
+ for m in _HIB_PROP_RE.finditer(text):
313
+ fulls.append(m.group(2))
314
+ for m in _HIB_COORD_VERSION_RE.finditer(text):
315
+ fulls.append(m.group(1))
316
+ for m in _HIB_GRADLE_VERSION_RE.finditer(text):
317
+ fulls.append(m.group(1))
318
+ for full in fulls:
319
+ if best_full is None or _ver_key(full) > _ver_key(best_full):
320
+ best_full = full
321
+ if best_full is None:
322
+ return None, None, "none"
323
+ return best_full, _ver_key(best_full)[0], "high"
324
+
256
325
  # Escalation markers — dynamic / reflection-based persistence construction.
257
326
  _ABSTRACTION_CLASS_RE = re.compile(
258
327
  r"\b(DynamicEntityDao|DynamicEntityDaoImpl|BasicPersistenceModule|"
@@ -406,12 +475,23 @@ class HibernateStratification:
406
475
  # when backed by a build dependency or a parsed org.hibernate import, "none"
407
476
  # when detection was vetoed for lack of evidence.
408
477
  evidence: dict = field(default_factory=dict)
478
+ # BUG #1: effective Hibernate version + whether the 5→6 axis applies at all.
479
+ # When the resolved major is ≥ 6 the migration is already done → not applicable
480
+ # (never a blocker). When unresolved, the axis is a low-confidence hypothesis.
481
+ effective_version: Optional[str] = None
482
+ version_major: Optional[int] = None
483
+ version_confidence: str = "none"
484
+ migration_applicable: bool = True
409
485
 
410
486
  def to_dict(self) -> dict:
411
487
  return {
412
488
  "schema_version": HIBERNATE_SCHEMA_VERSION,
413
489
  "detected": self.detected,
414
490
  "evidence": self.evidence,
491
+ "effective_version": self.effective_version,
492
+ "version_major": self.version_major,
493
+ "version_confidence": self.version_confidence,
494
+ "migration_applicable": self.migration_applicable,
415
495
  "classification": self.classification,
416
496
  "classification_label": self.classification_label,
417
497
  "stratified": True,
@@ -763,6 +843,7 @@ def analyze_hibernate(file_paths: list[str], root: Path) -> HibernateStratificat
763
843
  return strat
764
844
 
765
845
  dep_present, dep_samples = _scan_build_dependency(root)
846
+ eff_version, version_major, version_conf = _resolve_hibernate_version(root)
766
847
 
767
848
  has_evidence = dep_present or any_hibernate_import or any_jpa_import
768
849
  evidence: dict = {
@@ -771,6 +852,9 @@ def analyze_hibernate(file_paths: list[str], root: Path) -> HibernateStratificat
771
852
  "hibernate_import_present": any_hibernate_import,
772
853
  "hibernate_import_sample": hibernate_import_sample,
773
854
  "jpa_import_present": any_jpa_import,
855
+ "effective_version": eff_version,
856
+ "version_major": version_major,
857
+ "version_confidence": version_conf,
774
858
  "confidence": "high" if has_evidence else "none",
775
859
  }
776
860
 
@@ -959,6 +1043,12 @@ def analyze_hibernate(file_paths: list[str], root: Path) -> HibernateStratificat
959
1043
  strat = HibernateStratification()
960
1044
  strat.findings = findings
961
1045
  strat.evidence = evidence
1046
+ strat.effective_version = eff_version
1047
+ strat.version_major = version_major
1048
+ strat.version_confidence = version_conf
1049
+ # BUG #1: the 5→6 axis only applies when NOT already on Hibernate 6+. A
1050
+ # resolved major ≥ 6 means the migration is complete — never a blocker.
1051
+ strat.migration_applicable = not (version_major is not None and version_major >= 6)
962
1052
  strat.rewrite_targets = sorted(
963
1053
  targets, key=lambda t: (t.source_file, t.line_start, t.layer, t.id)
964
1054
  )
@@ -1126,13 +1216,23 @@ def analyze_hibernate(file_paths: list[str], root: Path) -> HibernateStratificat
1126
1216
  stops.append("SQL/HQL shape not statically inferable (string concatenation)")
1127
1217
  strat.stop_conditions_triggered = stops
1128
1218
 
1129
- if stops:
1219
+ if not strat.migration_applicable:
1220
+ # Already on Hibernate 6+ — the 5→6 stratification below is retained as an
1221
+ # informational exposure map, but it is NOT a migration and NOT a blocker.
1222
+ strat.classification = CLASS_NONE
1223
+ strat.classification_label = (
1224
+ f"ALREADY ON HIBERNATE {strat.version_major} "
1225
+ f"({strat.effective_version}) — NO 5→6 MIGRATION PENDING"
1226
+ )
1227
+ strat.stop_conditions_triggered = []
1228
+ elif stops:
1130
1229
  strat.classification = CLASS_REWRITE
1131
1230
  elif layer_files[LAYER_CRITERIA] or concat_query_global or jpa_deprecated_seen:
1132
1231
  strat.classification = CLASS_UPGRADE_CARE
1133
1232
  else:
1134
1233
  strat.classification = CLASS_UPGRADE
1135
- strat.classification_label = _CLASS_LABELS[strat.classification]
1234
+ if strat.migration_applicable:
1235
+ strat.classification_label = _CLASS_LABELS[strat.classification]
1136
1236
 
1137
1237
  # ── Risk separation: observable vs inferred ─────────────────────────────
1138
1238
  observable: list[str] = []
@@ -35,10 +35,28 @@ _ATTR_NAME_RE = re.compile(r'(?:name|value)\s*=\s*"([^"]*)"')
35
35
  _FIRST_LITERAL_RE = re.compile(r'^\s*"([^"]*)"')
36
36
 
37
37
  # token -> (kind, client). Matched as whole-word usage outside imports/comments.
38
+ # Covers Spring AND plain-Java/Jakarta stacks (Quarkus, Micronaut, Keycloak SPI):
39
+ # the detector must not be Spring-centric, or a non-Spring repo with real LDAP /
40
+ # SMTP / HTTP integrations falsely reports "0 integrations".
38
41
  _TOKEN_CLIENTS: "tuple[tuple[str, str, str], ...]" = (
42
+ # Spring HTTP clients
39
43
  ("RestTemplate", "http", "resttemplate"),
40
44
  ("WebClient", "http", "webclient"),
45
+ # Plain-Java / third-party HTTP clients
46
+ ("HttpClient", "http", "jdk-httpclient"), # java.net.http.HttpClient
47
+ ("CloseableHttpClient", "http", "apache-httpclient"),
48
+ ("HttpClients", "http", "apache-httpclient"),
49
+ ("OkHttpClient", "http", "okhttp"),
50
+ # LDAP / directory (Spring + JNDI)
41
51
  ("LdapTemplate", "ldap", "ldaptemplate"),
52
+ ("InitialLdapContext", "ldap", "jndi-ldap"),
53
+ ("InitialDirContext", "ldap", "jndi-ldap"),
54
+ ("LdapContext", "ldap", "jndi-ldap"),
55
+ # Mail / SMTP (JavaMail / Jakarta Mail)
56
+ ("JavaMailSender", "smtp", "spring-mail"),
57
+ ("MimeMessage", "smtp", "javamail"),
58
+ ("Transport", "smtp", "javamail"),
59
+ # JMS / messaging
42
60
  ("JmsTemplate", "jms", "jmstemplate"),
43
61
  ("ActiveMQConnectionFactory", "jms", "activemq"),
44
62
  )
@@ -156,8 +174,21 @@ def detect_integrations(file_paths: "list[str]", root: Path) -> dict:
156
174
  for r in records:
157
175
  by_kind[r["kind"]] = by_kind.get(r["kind"], 0) + 1
158
176
 
177
+ # BUG #9: honest confidence. A zero count means "no detectable client
178
+ # construct was found", NOT "this system has no integrations" — runtime-wired
179
+ # clients (DI, config-driven endpoints, JCA connectors) are invisible to static
180
+ # text matching. Report that explicitly instead of an authoritative "0".
181
+ confidence = "observed" if records else "not_analyzed"
159
182
  return {
160
183
  "integrations": records,
161
184
  "by_kind": {k: by_kind[k] for k in sorted(by_kind)},
162
185
  "count": len(records),
186
+ "confidence": confidence,
187
+ "coverage_note": (
188
+ "Detects HTTP (RestTemplate/WebClient/JDK/Apache/OkHttp), LDAP (Spring "
189
+ "+ JNDI), SMTP (JavaMail), and JMS client constructs by source-text "
190
+ "matching. A count of 0 means no such construct was found, not that the "
191
+ "system has no outbound integrations — runtime/DI-wired clients are not "
192
+ "statically visible."
193
+ ),
163
194
  }