sourcecode 1.35.33__tar.gz → 1.35.34__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 (109) hide show
  1. {sourcecode-1.35.33 → sourcecode-1.35.34}/PKG-INFO +148 -8
  2. {sourcecode-1.35.33 → sourcecode-1.35.34}/README.md +147 -7
  3. {sourcecode-1.35.33 → sourcecode-1.35.34}/pyproject.toml +1 -1
  4. {sourcecode-1.35.33 → sourcecode-1.35.34}/src/sourcecode/__init__.py +1 -1
  5. {sourcecode-1.35.33 → sourcecode-1.35.34}/src/sourcecode/cli.py +72 -13
  6. {sourcecode-1.35.33 → sourcecode-1.35.34}/src/sourcecode/detectors/java.py +30 -1
  7. {sourcecode-1.35.33 → sourcecode-1.35.34}/src/sourcecode/prepare_context.py +1 -1
  8. {sourcecode-1.35.33 → sourcecode-1.35.34}/src/sourcecode/rename_refactor.py +15 -2
  9. {sourcecode-1.35.33 → sourcecode-1.35.34}/src/sourcecode/repository_ir.py +32 -7
  10. {sourcecode-1.35.33 → sourcecode-1.35.34}/.github/workflows/build-windows.yml +0 -0
  11. {sourcecode-1.35.33 → sourcecode-1.35.34}/.gitignore +0 -0
  12. {sourcecode-1.35.33 → sourcecode-1.35.34}/.ruff.toml +0 -0
  13. {sourcecode-1.35.33 → sourcecode-1.35.34}/CHANGELOG.md +0 -0
  14. {sourcecode-1.35.33 → sourcecode-1.35.34}/CONTRIBUTING.md +0 -0
  15. {sourcecode-1.35.33 → sourcecode-1.35.34}/LICENSE +0 -0
  16. {sourcecode-1.35.33 → sourcecode-1.35.34}/SECURITY.md +0 -0
  17. {sourcecode-1.35.33 → sourcecode-1.35.34}/raw +0 -0
  18. {sourcecode-1.35.33 → sourcecode-1.35.34}/src/sourcecode/adaptive_scanner.py +0 -0
  19. {sourcecode-1.35.33 → sourcecode-1.35.34}/src/sourcecode/architecture_analyzer.py +0 -0
  20. {sourcecode-1.35.33 → sourcecode-1.35.34}/src/sourcecode/architecture_summary.py +0 -0
  21. {sourcecode-1.35.33 → sourcecode-1.35.34}/src/sourcecode/ast_extractor.py +0 -0
  22. {sourcecode-1.35.33 → sourcecode-1.35.34}/src/sourcecode/cache.py +0 -0
  23. {sourcecode-1.35.33 → sourcecode-1.35.34}/src/sourcecode/canonical_ir.py +0 -0
  24. {sourcecode-1.35.33 → sourcecode-1.35.34}/src/sourcecode/cir_graphs.py +0 -0
  25. {sourcecode-1.35.33 → sourcecode-1.35.34}/src/sourcecode/classifier.py +0 -0
  26. {sourcecode-1.35.33 → sourcecode-1.35.34}/src/sourcecode/code_notes_analyzer.py +0 -0
  27. {sourcecode-1.35.33 → sourcecode-1.35.34}/src/sourcecode/confidence_analyzer.py +0 -0
  28. {sourcecode-1.35.33 → sourcecode-1.35.34}/src/sourcecode/context_scorer.py +0 -0
  29. {sourcecode-1.35.33 → sourcecode-1.35.34}/src/sourcecode/context_summarizer.py +0 -0
  30. {sourcecode-1.35.33 → sourcecode-1.35.34}/src/sourcecode/contract_model.py +0 -0
  31. {sourcecode-1.35.33 → sourcecode-1.35.34}/src/sourcecode/contract_pipeline.py +0 -0
  32. {sourcecode-1.35.33 → sourcecode-1.35.34}/src/sourcecode/coverage_parser.py +0 -0
  33. {sourcecode-1.35.33 → sourcecode-1.35.34}/src/sourcecode/dependency_analyzer.py +0 -0
  34. {sourcecode-1.35.33 → sourcecode-1.35.34}/src/sourcecode/detectors/__init__.py +0 -0
  35. {sourcecode-1.35.33 → sourcecode-1.35.34}/src/sourcecode/detectors/base.py +0 -0
  36. {sourcecode-1.35.33 → sourcecode-1.35.34}/src/sourcecode/detectors/csproj_parser.py +0 -0
  37. {sourcecode-1.35.33 → sourcecode-1.35.34}/src/sourcecode/detectors/dart.py +0 -0
  38. {sourcecode-1.35.33 → sourcecode-1.35.34}/src/sourcecode/detectors/dotnet.py +0 -0
  39. {sourcecode-1.35.33 → sourcecode-1.35.34}/src/sourcecode/detectors/elixir.py +0 -0
  40. {sourcecode-1.35.33 → sourcecode-1.35.34}/src/sourcecode/detectors/go.py +0 -0
  41. {sourcecode-1.35.33 → sourcecode-1.35.34}/src/sourcecode/detectors/heuristic.py +0 -0
  42. {sourcecode-1.35.33 → sourcecode-1.35.34}/src/sourcecode/detectors/hybrid.py +0 -0
  43. {sourcecode-1.35.33 → sourcecode-1.35.34}/src/sourcecode/detectors/jvm_ext.py +0 -0
  44. {sourcecode-1.35.33 → sourcecode-1.35.34}/src/sourcecode/detectors/nodejs.py +0 -0
  45. {sourcecode-1.35.33 → sourcecode-1.35.34}/src/sourcecode/detectors/parsers.py +0 -0
  46. {sourcecode-1.35.33 → sourcecode-1.35.34}/src/sourcecode/detectors/php.py +0 -0
  47. {sourcecode-1.35.33 → sourcecode-1.35.34}/src/sourcecode/detectors/project.py +0 -0
  48. {sourcecode-1.35.33 → sourcecode-1.35.34}/src/sourcecode/detectors/python.py +0 -0
  49. {sourcecode-1.35.33 → sourcecode-1.35.34}/src/sourcecode/detectors/ruby.py +0 -0
  50. {sourcecode-1.35.33 → sourcecode-1.35.34}/src/sourcecode/detectors/rust.py +0 -0
  51. {sourcecode-1.35.33 → sourcecode-1.35.34}/src/sourcecode/detectors/systems.py +0 -0
  52. {sourcecode-1.35.33 → sourcecode-1.35.34}/src/sourcecode/detectors/terraform.py +0 -0
  53. {sourcecode-1.35.33 → sourcecode-1.35.34}/src/sourcecode/detectors/tooling.py +0 -0
  54. {sourcecode-1.35.33 → sourcecode-1.35.34}/src/sourcecode/doc_analyzer.py +0 -0
  55. {sourcecode-1.35.33 → sourcecode-1.35.34}/src/sourcecode/entrypoint_classifier.py +0 -0
  56. {sourcecode-1.35.33 → sourcecode-1.35.34}/src/sourcecode/env_analyzer.py +0 -0
  57. {sourcecode-1.35.33 → sourcecode-1.35.34}/src/sourcecode/error_schema.py +0 -0
  58. {sourcecode-1.35.33 → sourcecode-1.35.34}/src/sourcecode/explain.py +0 -0
  59. {sourcecode-1.35.33 → sourcecode-1.35.34}/src/sourcecode/file_chunker.py +0 -0
  60. {sourcecode-1.35.33 → sourcecode-1.35.34}/src/sourcecode/file_classifier.py +0 -0
  61. {sourcecode-1.35.33 → sourcecode-1.35.34}/src/sourcecode/flow_analyzer.py +0 -0
  62. {sourcecode-1.35.33 → sourcecode-1.35.34}/src/sourcecode/fqn_utils.py +0 -0
  63. {sourcecode-1.35.33 → sourcecode-1.35.34}/src/sourcecode/git_analyzer.py +0 -0
  64. {sourcecode-1.35.33 → sourcecode-1.35.34}/src/sourcecode/graph_analyzer.py +0 -0
  65. {sourcecode-1.35.33 → sourcecode-1.35.34}/src/sourcecode/license.py +0 -0
  66. {sourcecode-1.35.33 → sourcecode-1.35.34}/src/sourcecode/mcp/__init__.py +0 -0
  67. {sourcecode-1.35.33 → sourcecode-1.35.34}/src/sourcecode/mcp/onboarding/__init__.py +0 -0
  68. {sourcecode-1.35.33 → sourcecode-1.35.34}/src/sourcecode/mcp/onboarding/applier.py +0 -0
  69. {sourcecode-1.35.33 → sourcecode-1.35.34}/src/sourcecode/mcp/onboarding/backup.py +0 -0
  70. {sourcecode-1.35.33 → sourcecode-1.35.34}/src/sourcecode/mcp/onboarding/detector.py +0 -0
  71. {sourcecode-1.35.33 → sourcecode-1.35.34}/src/sourcecode/mcp/onboarding/planner.py +0 -0
  72. {sourcecode-1.35.33 → sourcecode-1.35.34}/src/sourcecode/mcp/orchestrator.py +0 -0
  73. {sourcecode-1.35.33 → sourcecode-1.35.34}/src/sourcecode/mcp/registry.py +0 -0
  74. {sourcecode-1.35.33 → sourcecode-1.35.34}/src/sourcecode/mcp/runner.py +0 -0
  75. {sourcecode-1.35.33 → sourcecode-1.35.34}/src/sourcecode/mcp/server.py +0 -0
  76. {sourcecode-1.35.33 → sourcecode-1.35.34}/src/sourcecode/mcp_nudge.py +0 -0
  77. {sourcecode-1.35.33 → sourcecode-1.35.34}/src/sourcecode/metrics_analyzer.py +0 -0
  78. {sourcecode-1.35.33 → sourcecode-1.35.34}/src/sourcecode/migrate_check.py +0 -0
  79. {sourcecode-1.35.33 → sourcecode-1.35.34}/src/sourcecode/output_budget.py +0 -0
  80. {sourcecode-1.35.33 → sourcecode-1.35.34}/src/sourcecode/path_filters.py +0 -0
  81. {sourcecode-1.35.33 → sourcecode-1.35.34}/src/sourcecode/pr_comment_renderer.py +0 -0
  82. {sourcecode-1.35.33 → sourcecode-1.35.34}/src/sourcecode/pr_impact.py +0 -0
  83. {sourcecode-1.35.33 → sourcecode-1.35.34}/src/sourcecode/progress.py +0 -0
  84. {sourcecode-1.35.33 → sourcecode-1.35.34}/src/sourcecode/ranking_engine.py +0 -0
  85. {sourcecode-1.35.33 → sourcecode-1.35.34}/src/sourcecode/redactor.py +0 -0
  86. {sourcecode-1.35.33 → sourcecode-1.35.34}/src/sourcecode/relevance_scorer.py +0 -0
  87. {sourcecode-1.35.33 → sourcecode-1.35.34}/src/sourcecode/repo_classifier.py +0 -0
  88. {sourcecode-1.35.33 → sourcecode-1.35.34}/src/sourcecode/ris.py +0 -0
  89. {sourcecode-1.35.33 → sourcecode-1.35.34}/src/sourcecode/runtime_classifier.py +0 -0
  90. {sourcecode-1.35.33 → sourcecode-1.35.34}/src/sourcecode/scanner.py +0 -0
  91. {sourcecode-1.35.33 → sourcecode-1.35.34}/src/sourcecode/schema.py +0 -0
  92. {sourcecode-1.35.33 → sourcecode-1.35.34}/src/sourcecode/semantic_analyzer.py +0 -0
  93. {sourcecode-1.35.33 → sourcecode-1.35.34}/src/sourcecode/serializer.py +0 -0
  94. {sourcecode-1.35.33 → sourcecode-1.35.34}/src/sourcecode/spring_event_topology.py +0 -0
  95. {sourcecode-1.35.33 → sourcecode-1.35.34}/src/sourcecode/spring_findings.py +0 -0
  96. {sourcecode-1.35.33 → sourcecode-1.35.34}/src/sourcecode/spring_impact.py +0 -0
  97. {sourcecode-1.35.33 → sourcecode-1.35.34}/src/sourcecode/spring_model.py +0 -0
  98. {sourcecode-1.35.33 → sourcecode-1.35.34}/src/sourcecode/spring_security_audit.py +0 -0
  99. {sourcecode-1.35.33 → sourcecode-1.35.34}/src/sourcecode/spring_semantic.py +0 -0
  100. {sourcecode-1.35.33 → sourcecode-1.35.34}/src/sourcecode/spring_tx_analyzer.py +0 -0
  101. {sourcecode-1.35.33 → sourcecode-1.35.34}/src/sourcecode/summarizer.py +0 -0
  102. {sourcecode-1.35.33 → sourcecode-1.35.34}/src/sourcecode/telemetry/__init__.py +0 -0
  103. {sourcecode-1.35.33 → sourcecode-1.35.34}/src/sourcecode/telemetry/config.py +0 -0
  104. {sourcecode-1.35.33 → sourcecode-1.35.34}/src/sourcecode/telemetry/consent.py +0 -0
  105. {sourcecode-1.35.33 → sourcecode-1.35.34}/src/sourcecode/telemetry/events.py +0 -0
  106. {sourcecode-1.35.33 → sourcecode-1.35.34}/src/sourcecode/telemetry/filters.py +0 -0
  107. {sourcecode-1.35.33 → sourcecode-1.35.34}/src/sourcecode/telemetry/transport.py +0 -0
  108. {sourcecode-1.35.33 → sourcecode-1.35.34}/src/sourcecode/tree_utils.py +0 -0
  109. {sourcecode-1.35.33 → sourcecode-1.35.34}/src/sourcecode/workspace.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sourcecode
3
- Version: 1.35.33
3
+ Version: 1.35.34
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
@@ -114,9 +114,7 @@ pipx install sourcecode
114
114
 
115
115
  ```bash
116
116
  sourcecode version
117
- # sourcecode 1.35.33
118
-
119
- **v1.35.28** — 7 bug fixes: `rename-class` cross-package disambiguation (BUG-4), `rename-class` collision detection (BUG-2), `find_java_files` false positive on `com/test/` package paths (BUG-1), `cold-start --compact` correct key names (BUG-6), `@EnableMethodSecurity` no longer suppresses SEC-001 (BUG-3), `explain` @Entity stereotype detection (BUG-5), XML+annotation mixed security retagging (BUG-7).
117
+ # sourcecode 1.35.34
120
118
  ```
121
119
 
122
120
  ---
@@ -136,6 +134,9 @@ sourcecode --agent
136
134
  # Blast radius: what breaks if this class changes?
137
135
  sourcecode impact OrderService /path/to/repo
138
136
 
137
+ # Spring Boot 2→3 migration readiness: javax→jakarta blockers, removed APIs
138
+ sourcecode migrate-check /path/to/repo
139
+
139
140
  # Spring semantic audit: TX anomalies + security surface (free)
140
141
  sourcecode spring-audit /path/to/repo
141
142
 
@@ -180,6 +181,9 @@ sourcecode cache warm
180
181
 
181
182
  # Clear cache
182
183
  sourcecode cache clear
184
+
185
+ # Check RIS freshness relative to current git HEAD
186
+ sourcecode cache freshness
183
187
  ```
184
188
 
185
189
  **`--no-cache`** bypasses both layers and forces a fresh scan. Use in CI or when you need to verify a fresh result.
@@ -337,9 +341,14 @@ Extracts all Spring MVC (`@GetMapping`, `@PostMapping`, `@RequestMapping`, etc.)
337
341
 
338
342
  ```bash
339
343
  sourcecode spring-audit /path/to/repo
340
- sourcecode spring-audit /path/to/repo --scope tx # TX anomalies only
344
+ sourcecode spring-audit /path/to/repo --scope tx # TX anomalies only
341
345
  sourcecode spring-audit /path/to/repo --scope security # security surface only
342
346
  sourcecode spring-audit /path/to/repo --min-severity high
347
+
348
+ # CI/CD gate: exit 1 on any finding
349
+ sourcecode spring-audit . --ci
350
+ sourcecode spring-audit . --ci --min-severity high # exit 1 only on high/critical
351
+ sourcecode spring-audit . --ci --format github-comment # Markdown output + exit 1
343
352
  ```
344
353
 
345
354
  Detects structural Spring anomalies that survive code review and tests, but cause production failures:
@@ -406,20 +415,59 @@ sourcecode cold-start /path/to/repo --compact # ~10K token subset
406
415
 
407
416
  Returns the Repository Intelligence Snapshot (RIS) instantly — zero re-analysis. The RIS is built by a prior warm cache pass and includes stacks, entry points, endpoint surface, and Spring semantic signals. Status field: `cold_start_ready` | `cold_start_stale` | `no_ris`.
408
417
 
409
- Use `--compact` to get a ~10K token subset safe for direct LLM injection. Full snapshot can exceed 100K tokens on medium repos — use `--output FILE` for local search tooling.
418
+ Use `--compact` to get a ~10K token subset safe for direct LLM injection. Full snapshot ranges from ~100K–200K tokens on medium repos — use `--output FILE` for local search tooling.
410
419
 
411
420
  ### `repo-ir` — symbol-level IR
412
421
 
413
422
  ```bash
414
- sourcecode repo-ir /path/to/repo --summary-only # ~20K tokens
415
- sourcecode repo-ir /path/to/repo --since HEAD~1 # symbol-level diff
423
+ sourcecode repo-ir /path/to/repo --summary-only # ~20K tokens
424
+ sourcecode repo-ir /path/to/repo --since HEAD~1 # symbol-level diff
416
425
  sourcecode repo-ir /path/to/repo --files src/.../OrderService.java
426
+ sourcecode repo-ir /path/to/repo --max-nodes 200 --max-edges 500 # limit graph size
427
+ sourcecode repo-ir /path/to/repo --output ir.json.gz --gzip # compressed output (~70-80% smaller)
428
+ sourcecode repo-ir /path/to/repo --include-tests # include test files
417
429
  ```
418
430
 
419
431
  Builds a deterministic symbol graph: classes, methods, import/injection edges, Spring roles, subsystems.
420
432
 
433
+ **Size control flags:**
434
+
435
+ | Flag | Description |
436
+ |------|-------------|
437
+ | `--summary-only` | Omit full graph nodes/edges; keep analysis summary, impact, and change_set (<300KB typical) |
438
+ | `--max-nodes N` | Keep top N nodes by impact score |
439
+ | `--max-edges N` | Keep top N edges (priority: edges between kept nodes) |
440
+ | `--gzip` | Compress output with gzip. Requires `--output`. ~70–80% smaller. |
441
+ | `--force` | Bypass the 50K-token size guard and emit output anyway |
442
+ | `--include-tests` | Include test source files (excluded by default) |
443
+
421
444
  **Size warning:** Without `--summary-only`, output can exceed 1MB for mid-size repos. Always use `--summary-only` unless you need the full graph for downstream tooling.
422
445
 
446
+ ### `explain` — architectural summary for a class
447
+
448
+ ```bash
449
+ sourcecode explain UserService
450
+ sourcecode explain OrderController /path/to/repo
451
+ sourcecode explain UserService --format json
452
+ ```
453
+
454
+ Human-readable architectural summary derived entirely from static analysis: Spring stereotype, public methods, incoming callers, outgoing dependencies, events published/consumed, `@Transactional` boundaries, security constraints, and related REST endpoints. JAVA/SPRING ONLY.
455
+
456
+ ### `pr-impact` — PR blast-radius report
457
+
458
+ ```bash
459
+ sourcecode pr-impact --files changed_files.txt
460
+ sourcecode pr-impact /path/to/repo --files diff.txt --format json
461
+ ```
462
+
463
+ Takes a file listing changed Java files (one path per line) and produces a consolidated report: modified classes, affected REST endpoints reachable through the call chain, direct callers of each changed class, event publishers/consumers triggered, `@Transactional` methods in changed classes, and a consolidated risk level (`CRITICAL` / `HIGH` / `MEDIUM` / `LOW`). JAVA/SPRING ONLY.
464
+
465
+ ```bash
466
+ # Typical CI usage: pipe git diff to a file, then run
467
+ git diff --name-only main | grep '\.java$' > changed.txt
468
+ sourcecode pr-impact . --files changed.txt --format json
469
+ ```
470
+
423
471
  ### `onboard` — codebase orientation
424
472
 
425
473
  ```bash
@@ -453,6 +501,98 @@ sourcecode modernize /path/to/repo
453
501
 
454
502
  High-coupling nodes (high fan-in = risky to change), dead zone candidates (isolated symbols), subsystem tangles.
455
503
 
504
+ ### `migrate-check` — Spring Boot 2→3 migration readiness
505
+
506
+ ```bash
507
+ sourcecode migrate-check /path/to/repo
508
+ sourcecode migrate-check . --min-severity high
509
+ sourcecode migrate-check . --format text
510
+ sourcecode migrate-check . --output migration.json
511
+ ```
512
+
513
+ Detects migration blockers across Java source files, Spring XML config files, and Maven/Gradle build files. 27 rules organized by target:
514
+
515
+ **Jakarta namespace (MIG-001..009) — javax→jakarta**
516
+
517
+ | Rule | Severity | Pattern |
518
+ |------|----------|---------|
519
+ | `MIG-001` | critical | `javax.persistence` import — JPA will not compile |
520
+ | `MIG-002` | high | `javax.servlet` import — Servlet API changed |
521
+ | `MIG-003` | high | `javax.validation` import — Bean Validation changed |
522
+ | `MIG-004` | high | `javax.transaction` import — TX API changed |
523
+ | `MIG-006` | medium | `javax.annotation` import — CDI annotations changed |
524
+ | `MIG-007` | medium | `javax.inject` import — DI annotations changed |
525
+ | `MIG-008` | medium | `javax.ws.rs` import — JAX-RS changed |
526
+ | `MIG-009` | medium | `javax.jms` import — JMS API changed |
527
+
528
+ **Spring Security 6 (MIG-005, MIG-019, MIG-020)**
529
+
530
+ | Rule | Severity | Pattern |
531
+ |------|----------|---------|
532
+ | `MIG-005` | high | `extends WebSecurityConfigurerAdapter` — removed in Spring Security 6 |
533
+ | `MIG-019` | high | SpringFox / `@EnableSwagger2` — incompatible with Spring Boot 3 |
534
+ | `MIG-020` | high | `antMatchers()` / `authorizeRequests()` — replaced in Spring Security 6 |
535
+
536
+ **Java version compatibility (MIG-010..025)**
537
+
538
+ | Rule | Severity | Pattern |
539
+ |------|----------|---------|
540
+ | `MIG-010` | critical | `SecurityManager` / `AccessController` — removed in Java 17 (JEP 411) |
541
+ | `MIG-011` | high | `sun.*` / `com.sun.net.*` internal API imports — strong encapsulation since Java 9 |
542
+ | `MIG-012` | high | Nashorn `ScriptEngine` — removed in Java 15 |
543
+ | `MIG-013` | high | `sun.misc.Unsafe` — requires `--add-opens` on Java 9+ |
544
+ | `MIG-014` | medium | `setAccessible(true)` — may throw `InaccessibleObjectException` on Java 17+ |
545
+ | `MIG-015` | medium | `finalize()` override — deprecated for removal since Java 18 |
546
+ | `MIG-016` | low | `java.util.Date` / `Calendar` / `SimpleDateFormat` — use `java.time` |
547
+ | `MIG-021` | high | `javax.xml.bind` (JAXB) — removed from JDK in Java 11 |
548
+ | `MIG-022` | high | `javax.xml.ws` (JAX-WS) — removed from JDK in Java 11 |
549
+ | `MIG-023` | critical | `org.omg.*` / CORBA APIs — removed from JDK in Java 11 |
550
+ | `MIG-024` | medium | `Thread.stop()` / `Thread.suspend()` / `Thread.resume()` — deprecated for removal |
551
+ | `MIG-025` | medium | `ReflectionFactory` / `MethodHandles.privateLookupIn` — JPMS deep-reflection risk |
552
+
553
+ **Spring XML config (MIG-030..032)**
554
+
555
+ | Rule | Severity | Pattern |
556
+ |------|----------|---------|
557
+ | `MIG-030` | high | `javax.*` class reference in Spring XML bean definitions |
558
+ | `MIG-031` | high | `<http auto-config>` or versioned spring-security ≤5 schema in XML |
559
+ | `MIG-032` | high | `web.xml` with Servlet ≤4 namespace — must migrate to `jakarta.ee` |
560
+
561
+ **Build file dependencies (MIG-040..043)**
562
+
563
+ | Rule | Severity | Pattern |
564
+ |------|----------|---------|
565
+ | `MIG-040` | high | `io.springfox` dependency — incompatible with Spring Boot 3 |
566
+ | `MIG-041` | high | Hibernate 5.x explicitly pinned — Spring Boot 3 requires Hibernate 6 |
567
+ | `MIG-042` | medium | ByteBuddy < 1.12.x — may not support Java 17+ strong encapsulation |
568
+ | `MIG-043` | high | EhCache 2.x (`net.sf.ehcache`) — incompatible with Spring Boot 3 |
569
+
570
+ Each finding includes `severity`, `title`, `source_file`, `first_line`, `explanation`, `fix_hint`, `migration_target`, and `openrewrite_recipe` (when an automated recipe exists).
571
+
572
+ ### `rename-class` — Java class rename
573
+
574
+ ```bash
575
+ sourcecode rename-class . --from ServiceA --to ServiceB
576
+ sourcecode rename-class /path/to/repo --from OrderManager --to OrderService
577
+ sourcecode rename-class . --from OldName --to NewName --dry-run
578
+ sourcecode rename-class . --from OldName --to NewName --no-tests # src/main only
579
+ ```
580
+
581
+ Renames a Java class safely throughout the repository: declaration, constructor, all import statements, type references (fields, params, return types), `extends`/`implements`, generics, casts, and Spring `@Qualifier` names. Renames the physical `.java` file. Emits a structured change audit trail (`file`, `before_lines`, `after_lines`, `intent`, `diff`).
582
+
583
+ Use `--dry-run` to preview changes without writing to disk.
584
+
585
+ ### `chunk-file` — split large Java files for agent consumption
586
+
587
+ ```bash
588
+ sourcecode chunk-file BigService.java
589
+ sourcecode chunk-file BigService.java --max-lines 300
590
+ sourcecode chunk-file BigService.java --chunk 5 # read chunk 5 only
591
+ sourcecode chunk-file BigService.java --metadata-only # boundaries only, no content
592
+ ```
593
+
594
+ Splits a large Java file at method/class boundaries so AI agents can read files with 10K–25K+ lines in context-sized pieces. Each chunk includes `chunk_id`, `start_line`, `end_line`, `chunk_type`, symbol name, a `context_header` (package + class + imports summary), and `content`. A `size_warning` flag marks methods that exceed `--max-lines` and cannot be split further.
595
+
456
596
  ### `prepare-context` — task-specific context
457
597
 
458
598
  Low-level access to all tasks with full options:
@@ -76,9 +76,7 @@ pipx install sourcecode
76
76
 
77
77
  ```bash
78
78
  sourcecode version
79
- # sourcecode 1.35.33
80
-
81
- **v1.35.28** — 7 bug fixes: `rename-class` cross-package disambiguation (BUG-4), `rename-class` collision detection (BUG-2), `find_java_files` false positive on `com/test/` package paths (BUG-1), `cold-start --compact` correct key names (BUG-6), `@EnableMethodSecurity` no longer suppresses SEC-001 (BUG-3), `explain` @Entity stereotype detection (BUG-5), XML+annotation mixed security retagging (BUG-7).
79
+ # sourcecode 1.35.34
82
80
  ```
83
81
 
84
82
  ---
@@ -98,6 +96,9 @@ sourcecode --agent
98
96
  # Blast radius: what breaks if this class changes?
99
97
  sourcecode impact OrderService /path/to/repo
100
98
 
99
+ # Spring Boot 2→3 migration readiness: javax→jakarta blockers, removed APIs
100
+ sourcecode migrate-check /path/to/repo
101
+
101
102
  # Spring semantic audit: TX anomalies + security surface (free)
102
103
  sourcecode spring-audit /path/to/repo
103
104
 
@@ -142,6 +143,9 @@ sourcecode cache warm
142
143
 
143
144
  # Clear cache
144
145
  sourcecode cache clear
146
+
147
+ # Check RIS freshness relative to current git HEAD
148
+ sourcecode cache freshness
145
149
  ```
146
150
 
147
151
  **`--no-cache`** bypasses both layers and forces a fresh scan. Use in CI or when you need to verify a fresh result.
@@ -299,9 +303,14 @@ Extracts all Spring MVC (`@GetMapping`, `@PostMapping`, `@RequestMapping`, etc.)
299
303
 
300
304
  ```bash
301
305
  sourcecode spring-audit /path/to/repo
302
- sourcecode spring-audit /path/to/repo --scope tx # TX anomalies only
306
+ sourcecode spring-audit /path/to/repo --scope tx # TX anomalies only
303
307
  sourcecode spring-audit /path/to/repo --scope security # security surface only
304
308
  sourcecode spring-audit /path/to/repo --min-severity high
309
+
310
+ # CI/CD gate: exit 1 on any finding
311
+ sourcecode spring-audit . --ci
312
+ sourcecode spring-audit . --ci --min-severity high # exit 1 only on high/critical
313
+ sourcecode spring-audit . --ci --format github-comment # Markdown output + exit 1
305
314
  ```
306
315
 
307
316
  Detects structural Spring anomalies that survive code review and tests, but cause production failures:
@@ -368,20 +377,59 @@ sourcecode cold-start /path/to/repo --compact # ~10K token subset
368
377
 
369
378
  Returns the Repository Intelligence Snapshot (RIS) instantly — zero re-analysis. The RIS is built by a prior warm cache pass and includes stacks, entry points, endpoint surface, and Spring semantic signals. Status field: `cold_start_ready` | `cold_start_stale` | `no_ris`.
370
379
 
371
- Use `--compact` to get a ~10K token subset safe for direct LLM injection. Full snapshot can exceed 100K tokens on medium repos — use `--output FILE` for local search tooling.
380
+ Use `--compact` to get a ~10K token subset safe for direct LLM injection. Full snapshot ranges from ~100K–200K tokens on medium repos — use `--output FILE` for local search tooling.
372
381
 
373
382
  ### `repo-ir` — symbol-level IR
374
383
 
375
384
  ```bash
376
- sourcecode repo-ir /path/to/repo --summary-only # ~20K tokens
377
- sourcecode repo-ir /path/to/repo --since HEAD~1 # symbol-level diff
385
+ sourcecode repo-ir /path/to/repo --summary-only # ~20K tokens
386
+ sourcecode repo-ir /path/to/repo --since HEAD~1 # symbol-level diff
378
387
  sourcecode repo-ir /path/to/repo --files src/.../OrderService.java
388
+ sourcecode repo-ir /path/to/repo --max-nodes 200 --max-edges 500 # limit graph size
389
+ sourcecode repo-ir /path/to/repo --output ir.json.gz --gzip # compressed output (~70-80% smaller)
390
+ sourcecode repo-ir /path/to/repo --include-tests # include test files
379
391
  ```
380
392
 
381
393
  Builds a deterministic symbol graph: classes, methods, import/injection edges, Spring roles, subsystems.
382
394
 
395
+ **Size control flags:**
396
+
397
+ | Flag | Description |
398
+ |------|-------------|
399
+ | `--summary-only` | Omit full graph nodes/edges; keep analysis summary, impact, and change_set (<300KB typical) |
400
+ | `--max-nodes N` | Keep top N nodes by impact score |
401
+ | `--max-edges N` | Keep top N edges (priority: edges between kept nodes) |
402
+ | `--gzip` | Compress output with gzip. Requires `--output`. ~70–80% smaller. |
403
+ | `--force` | Bypass the 50K-token size guard and emit output anyway |
404
+ | `--include-tests` | Include test source files (excluded by default) |
405
+
383
406
  **Size warning:** Without `--summary-only`, output can exceed 1MB for mid-size repos. Always use `--summary-only` unless you need the full graph for downstream tooling.
384
407
 
408
+ ### `explain` — architectural summary for a class
409
+
410
+ ```bash
411
+ sourcecode explain UserService
412
+ sourcecode explain OrderController /path/to/repo
413
+ sourcecode explain UserService --format json
414
+ ```
415
+
416
+ Human-readable architectural summary derived entirely from static analysis: Spring stereotype, public methods, incoming callers, outgoing dependencies, events published/consumed, `@Transactional` boundaries, security constraints, and related REST endpoints. JAVA/SPRING ONLY.
417
+
418
+ ### `pr-impact` — PR blast-radius report
419
+
420
+ ```bash
421
+ sourcecode pr-impact --files changed_files.txt
422
+ sourcecode pr-impact /path/to/repo --files diff.txt --format json
423
+ ```
424
+
425
+ Takes a file listing changed Java files (one path per line) and produces a consolidated report: modified classes, affected REST endpoints reachable through the call chain, direct callers of each changed class, event publishers/consumers triggered, `@Transactional` methods in changed classes, and a consolidated risk level (`CRITICAL` / `HIGH` / `MEDIUM` / `LOW`). JAVA/SPRING ONLY.
426
+
427
+ ```bash
428
+ # Typical CI usage: pipe git diff to a file, then run
429
+ git diff --name-only main | grep '\.java$' > changed.txt
430
+ sourcecode pr-impact . --files changed.txt --format json
431
+ ```
432
+
385
433
  ### `onboard` — codebase orientation
386
434
 
387
435
  ```bash
@@ -415,6 +463,98 @@ sourcecode modernize /path/to/repo
415
463
 
416
464
  High-coupling nodes (high fan-in = risky to change), dead zone candidates (isolated symbols), subsystem tangles.
417
465
 
466
+ ### `migrate-check` — Spring Boot 2→3 migration readiness
467
+
468
+ ```bash
469
+ sourcecode migrate-check /path/to/repo
470
+ sourcecode migrate-check . --min-severity high
471
+ sourcecode migrate-check . --format text
472
+ sourcecode migrate-check . --output migration.json
473
+ ```
474
+
475
+ Detects migration blockers across Java source files, Spring XML config files, and Maven/Gradle build files. 27 rules organized by target:
476
+
477
+ **Jakarta namespace (MIG-001..009) — javax→jakarta**
478
+
479
+ | Rule | Severity | Pattern |
480
+ |------|----------|---------|
481
+ | `MIG-001` | critical | `javax.persistence` import — JPA will not compile |
482
+ | `MIG-002` | high | `javax.servlet` import — Servlet API changed |
483
+ | `MIG-003` | high | `javax.validation` import — Bean Validation changed |
484
+ | `MIG-004` | high | `javax.transaction` import — TX API changed |
485
+ | `MIG-006` | medium | `javax.annotation` import — CDI annotations changed |
486
+ | `MIG-007` | medium | `javax.inject` import — DI annotations changed |
487
+ | `MIG-008` | medium | `javax.ws.rs` import — JAX-RS changed |
488
+ | `MIG-009` | medium | `javax.jms` import — JMS API changed |
489
+
490
+ **Spring Security 6 (MIG-005, MIG-019, MIG-020)**
491
+
492
+ | Rule | Severity | Pattern |
493
+ |------|----------|---------|
494
+ | `MIG-005` | high | `extends WebSecurityConfigurerAdapter` — removed in Spring Security 6 |
495
+ | `MIG-019` | high | SpringFox / `@EnableSwagger2` — incompatible with Spring Boot 3 |
496
+ | `MIG-020` | high | `antMatchers()` / `authorizeRequests()` — replaced in Spring Security 6 |
497
+
498
+ **Java version compatibility (MIG-010..025)**
499
+
500
+ | Rule | Severity | Pattern |
501
+ |------|----------|---------|
502
+ | `MIG-010` | critical | `SecurityManager` / `AccessController` — removed in Java 17 (JEP 411) |
503
+ | `MIG-011` | high | `sun.*` / `com.sun.net.*` internal API imports — strong encapsulation since Java 9 |
504
+ | `MIG-012` | high | Nashorn `ScriptEngine` — removed in Java 15 |
505
+ | `MIG-013` | high | `sun.misc.Unsafe` — requires `--add-opens` on Java 9+ |
506
+ | `MIG-014` | medium | `setAccessible(true)` — may throw `InaccessibleObjectException` on Java 17+ |
507
+ | `MIG-015` | medium | `finalize()` override — deprecated for removal since Java 18 |
508
+ | `MIG-016` | low | `java.util.Date` / `Calendar` / `SimpleDateFormat` — use `java.time` |
509
+ | `MIG-021` | high | `javax.xml.bind` (JAXB) — removed from JDK in Java 11 |
510
+ | `MIG-022` | high | `javax.xml.ws` (JAX-WS) — removed from JDK in Java 11 |
511
+ | `MIG-023` | critical | `org.omg.*` / CORBA APIs — removed from JDK in Java 11 |
512
+ | `MIG-024` | medium | `Thread.stop()` / `Thread.suspend()` / `Thread.resume()` — deprecated for removal |
513
+ | `MIG-025` | medium | `ReflectionFactory` / `MethodHandles.privateLookupIn` — JPMS deep-reflection risk |
514
+
515
+ **Spring XML config (MIG-030..032)**
516
+
517
+ | Rule | Severity | Pattern |
518
+ |------|----------|---------|
519
+ | `MIG-030` | high | `javax.*` class reference in Spring XML bean definitions |
520
+ | `MIG-031` | high | `<http auto-config>` or versioned spring-security ≤5 schema in XML |
521
+ | `MIG-032` | high | `web.xml` with Servlet ≤4 namespace — must migrate to `jakarta.ee` |
522
+
523
+ **Build file dependencies (MIG-040..043)**
524
+
525
+ | Rule | Severity | Pattern |
526
+ |------|----------|---------|
527
+ | `MIG-040` | high | `io.springfox` dependency — incompatible with Spring Boot 3 |
528
+ | `MIG-041` | high | Hibernate 5.x explicitly pinned — Spring Boot 3 requires Hibernate 6 |
529
+ | `MIG-042` | medium | ByteBuddy < 1.12.x — may not support Java 17+ strong encapsulation |
530
+ | `MIG-043` | high | EhCache 2.x (`net.sf.ehcache`) — incompatible with Spring Boot 3 |
531
+
532
+ Each finding includes `severity`, `title`, `source_file`, `first_line`, `explanation`, `fix_hint`, `migration_target`, and `openrewrite_recipe` (when an automated recipe exists).
533
+
534
+ ### `rename-class` — Java class rename
535
+
536
+ ```bash
537
+ sourcecode rename-class . --from ServiceA --to ServiceB
538
+ sourcecode rename-class /path/to/repo --from OrderManager --to OrderService
539
+ sourcecode rename-class . --from OldName --to NewName --dry-run
540
+ sourcecode rename-class . --from OldName --to NewName --no-tests # src/main only
541
+ ```
542
+
543
+ Renames a Java class safely throughout the repository: declaration, constructor, all import statements, type references (fields, params, return types), `extends`/`implements`, generics, casts, and Spring `@Qualifier` names. Renames the physical `.java` file. Emits a structured change audit trail (`file`, `before_lines`, `after_lines`, `intent`, `diff`).
544
+
545
+ Use `--dry-run` to preview changes without writing to disk.
546
+
547
+ ### `chunk-file` — split large Java files for agent consumption
548
+
549
+ ```bash
550
+ sourcecode chunk-file BigService.java
551
+ sourcecode chunk-file BigService.java --max-lines 300
552
+ sourcecode chunk-file BigService.java --chunk 5 # read chunk 5 only
553
+ sourcecode chunk-file BigService.java --metadata-only # boundaries only, no content
554
+ ```
555
+
556
+ Splits a large Java file at method/class boundaries so AI agents can read files with 10K–25K+ lines in context-sized pieces. Each chunk includes `chunk_id`, `start_line`, `end_line`, `chunk_type`, symbol name, a `context_header` (package + class + imports summary), and `content`. A `size_warning` flag marks methods that exceed `--max-lines` and cannot be split further.
557
+
418
558
  ### `prepare-context` — task-specific context
419
559
 
420
560
  Low-level access to all tasks with full options:
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "sourcecode"
7
- version = "1.35.33"
7
+ version = "1.35.34"
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.35.33"
3
+ __version__ = "1.35.34"
@@ -3466,21 +3466,34 @@ def repo_ir_cmd(
3466
3466
  _ir_tokens_est = _ir_size // 4
3467
3467
  # P1-C: abort when estimated tokens > 50K unless --force or --output is given.
3468
3468
  if _ir_tokens_est > 50_000 and not force:
3469
+ if summary_only:
3470
+ _hint = (
3471
+ "Use --max-nodes N --max-edges N to cap graph size, "
3472
+ "--output FILE to save to disk, or --force to bypass this guard."
3473
+ )
3474
+ else:
3475
+ _hint = (
3476
+ "Use --summary-only (~5K tokens), --max-nodes N --max-edges N, "
3477
+ "--output FILE to save to disk, or --force to bypass this guard."
3478
+ )
3469
3479
  _emit_error_json(
3470
3480
  "OUTPUT_TOO_LARGE",
3471
3481
  f"Estimated output is ~{_ir_tokens_est // 1000}K tokens — too large for most LLM context windows.",
3472
- hint=(
3473
- "Use --summary-only (~5K tokens), --max-nodes N --max-edges N, "
3474
- "--output FILE to save to disk, or --force to bypass this guard."
3475
- ),
3482
+ hint=_hint,
3476
3483
  expected="Output under 50K estimated tokens.",
3477
3484
  )
3478
3485
  raise typer.Exit(1)
3479
3486
  if _ir_tokens_est > 10_000:
3480
- sys.stderr.write(
3481
- f"[repo-ir] ~{_ir_tokens_est // 1000}K tokens — "
3482
- "use --summary-only or --output FILE for smaller output.\n"
3483
- )
3487
+ if summary_only:
3488
+ sys.stderr.write(
3489
+ f"[repo-ir] ~{_ir_tokens_est // 1000}K tokens "
3490
+ "use --max-nodes N --max-edges N or --output FILE for smaller output.\n"
3491
+ )
3492
+ else:
3493
+ sys.stderr.write(
3494
+ f"[repo-ir] ~{_ir_tokens_est // 1000}K tokens — "
3495
+ "use --summary-only or --output FILE for smaller output.\n"
3496
+ )
3484
3497
  sys.stderr.flush()
3485
3498
  try:
3486
3499
  sys.stdout.buffer.write(output.encode("utf-8"))
@@ -3590,6 +3603,15 @@ def impact_cmd(
3590
3603
  sys.stderr.flush()
3591
3604
  target, path = str(path), _target_as_path
3592
3605
 
3606
+ if not target.strip():
3607
+ _emit_error_json(
3608
+ INVALID_INPUT_CODE,
3609
+ "Class name must not be empty.",
3610
+ hint="Pass a class name or FQN. Example: sourcecode impact OrderService .",
3611
+ expected="A non-empty class name or FQN.",
3612
+ )
3613
+ raise typer.Exit(1)
3614
+
3593
3615
  root = path.resolve()
3594
3616
  if not root.is_dir():
3595
3617
  _emit_error_json(
@@ -3718,6 +3740,15 @@ def endpoints_cmd(
3718
3740
  sourcecode endpoints . --controller LiquidacionJornada
3719
3741
  sourcecode endpoints . --limit 10
3720
3742
  """
3743
+ if format not in ("json", "yaml"):
3744
+ _emit_error_json(
3745
+ INVALID_INPUT_CODE,
3746
+ f"Invalid format '{format}'.",
3747
+ hint="format must be: json or yaml.",
3748
+ expected="json | yaml",
3749
+ )
3750
+ raise typer.Exit(code=1)
3751
+
3721
3752
  target = path.resolve()
3722
3753
  if not target.exists() or not target.is_dir():
3723
3754
  _emit_error_json(
@@ -4258,6 +4289,15 @@ def impact_chain_cmd(
4258
4289
  from sourcecode.spring_impact import run_impact_chain
4259
4290
  from sourcecode.spring_findings import SpringAuditResult
4260
4291
 
4292
+ if not symbol.strip():
4293
+ _emit_error_json(
4294
+ INVALID_INPUT_CODE,
4295
+ "Symbol name must not be empty.",
4296
+ hint="Pass a class name or FQN. Example: sourcecode impact-chain OrderService .",
4297
+ expected="A non-empty class name or FQN.",
4298
+ )
4299
+ raise typer.Exit(code=1)
4300
+
4261
4301
  _VALID_TYPES = ("impact", "events")
4262
4302
  if query_type not in _VALID_TYPES:
4263
4303
  _emit_error_json(
@@ -4427,10 +4467,10 @@ def pr_impact_cmd(
4427
4467
  )
4428
4468
  raise typer.Exit(code=1)
4429
4469
 
4430
- if not files.exists():
4470
+ if not files.exists() or files.is_dir():
4431
4471
  _emit_error_json(
4432
4472
  INVALID_INPUT_CODE,
4433
- f"--files '{files}' does not exist. Expected a text file listing changed file paths (one per line), not a directory or class name.",
4473
+ f"--files '{files}' does not exist or is a directory. Expected a text file listing changed file paths (one per line).",
4434
4474
  path=str(files),
4435
4475
  hint=(
4436
4476
  "Create a file with one changed Java file path per line, then pass it with --files. "
@@ -4568,6 +4608,15 @@ def explain_cmd(
4568
4608
  from sourcecode.spring_model import SpringSemanticModel
4569
4609
  from sourcecode.explain import explain_class
4570
4610
 
4611
+ if not class_name.strip():
4612
+ _emit_error_json(
4613
+ INVALID_INPUT_CODE,
4614
+ "Class name must not be empty.",
4615
+ hint="Pass a class name. Example: sourcecode explain UserService .",
4616
+ expected="A non-empty class name.",
4617
+ )
4618
+ raise typer.Exit(code=1)
4619
+
4571
4620
  target = path.resolve()
4572
4621
  if not target.exists() or not target.is_dir():
4573
4622
  _emit_error_json(
@@ -5122,7 +5171,7 @@ def rename_class_cmd(
5122
5171
  help="Output format: json (default) or yaml.",
5123
5172
  ),
5124
5173
  ) -> None:
5125
- """Rename a Java class throughout the repository (BLOCKER-A fix).
5174
+ """Rename a Java class throughout the repository.
5126
5175
 
5127
5176
  \b
5128
5177
  Renames a Java class safely:
@@ -5133,7 +5182,7 @@ def rename_class_cmd(
5133
5182
  - Updates extends / implements
5134
5183
  - Updates generics, casts, Spring @Qualifier names
5135
5184
  - Renames the physical .java file
5136
- - Emits a structured change audit trail (BLOCKER-C)
5185
+ - Emits a structured change audit trail
5137
5186
 
5138
5187
  \b
5139
5188
  Examples:
@@ -5244,7 +5293,7 @@ def chunk_file_cmd(
5244
5293
  help="Copy output to clipboard after a successful run.",
5245
5294
  ),
5246
5295
  ) -> None:
5247
- """Split a large Java file into semantic chunks for AI agent consumption (BLOCKER-B fix).
5296
+ """Split a large Java file into semantic chunks for AI agent consumption.
5248
5297
 
5249
5298
  \b
5250
5299
  Splits a Java file at method/class boundaries so AI agents can read
@@ -5278,6 +5327,16 @@ def chunk_file_cmd(
5278
5327
  )
5279
5328
  raise typer.Exit(1)
5280
5329
 
5330
+ if abs_file.suffix != ".java":
5331
+ _emit_error_json(
5332
+ INVALID_INPUT_CODE,
5333
+ f"'{abs_file.name}' is not a Java file. chunk-file only supports .java files.",
5334
+ path=str(abs_file),
5335
+ hint="Pass a .java source file.",
5336
+ expected="A .java file path.",
5337
+ )
5338
+ raise typer.Exit(1)
5339
+
5281
5340
  result = chunk_java_file(abs_file, max_lines=max_lines, include_content=not metadata_only)
5282
5341
 
5283
5342
  if chunk_id is not None:
@@ -111,6 +111,8 @@ class JavaDetector(AbstractDetector):
111
111
  manifests.append("pom.xml")
112
112
  pom_path = context.root / "pom.xml"
113
113
  frameworks.extend(self._frameworks_from_pom(pom_path))
114
+ for child_pom in self._get_child_pom_paths(pom_path):
115
+ frameworks.extend(self._frameworks_from_pom(child_pom))
114
116
  meta = self._parse_pom_metadata(pom_path)
115
117
  if meta.get("language_version"):
116
118
  language_version = meta["language_version"]
@@ -232,6 +234,26 @@ class JavaDetector(AbstractDetector):
232
234
  _SKIP = frozenset({"test", "it", "integration"})
233
235
  return [p for p in profiles if p.lower() not in _SKIP]
234
236
 
237
+ def _get_child_pom_paths(self, root_pom: Path) -> list[Path]:
238
+ """Return pom.xml paths for direct Maven child modules declared in <modules>."""
239
+ try:
240
+ tree = ElementTree.parse(root_pom)
241
+ except (OSError, ElementTree.ParseError):
242
+ return []
243
+ root_elem = tree.getroot()
244
+ ns_match = _NS_TAG_RE.match(root_elem.tag)
245
+ ns = ns_match.group(0) if ns_match else ""
246
+ modules_elem = root_elem.find(f"{ns}modules")
247
+ if modules_elem is None:
248
+ return []
249
+ result = []
250
+ for mod in modules_elem.findall(f"{ns}module"):
251
+ if mod.text:
252
+ child_pom = root_pom.parent / mod.text.strip() / "pom.xml"
253
+ if child_pom.exists():
254
+ result.append(child_pom)
255
+ return result
256
+
235
257
  def _frameworks_from_pom(self, path: Path) -> list[FrameworkDetection]:
236
258
  try:
237
259
  tree = ElementTree.parse(path)
@@ -289,7 +311,14 @@ class JavaDetector(AbstractDetector):
289
311
  frameworks.append(FrameworkDetection(name="Micronaut", source=source))
290
312
  if "io.vertx" in text or "vertx" in text:
291
313
  frameworks.append(FrameworkDetection(name="Vert.x", source=source))
292
- if "jakarta.ee" in text or "javax.ws.rs" in text:
314
+ if (
315
+ "jakarta.ee" in text
316
+ or "javax.ws.rs-api" in text # JAX-RS 2.x spec; excludes jsr311-api (1.x API-only jar)
317
+ or "jakarta.ws.rs" in text # Jakarta namespace (EE 9+)
318
+ or "javaee-api" in text # full Java EE platform
319
+ or "jakartaee-api" in text # full Jakarta EE platform
320
+ or "resteasy" in text # RESTEasy (JBoss JAX-RS impl)
321
+ ):
293
322
  frameworks.append(FrameworkDetection(name="Jakarta EE", source=source))
294
323
  if "mybatis" in text:
295
324
  frameworks.append(FrameworkDetection(name="MyBatis", source=source))
@@ -2464,7 +2464,7 @@ class TaskContextBuilder:
2464
2464
  # Java-aware algorithm (Fix #2): find Service/RestController/Repository/Mapper
2465
2465
  # files with no matching test pair in src/test/**
2466
2466
  _JAVA_TARGET_SUFFIXES = (
2467
- "Service.java", "RestController.java",
2467
+ "Service.java", "Controller.java", "RestController.java",
2468
2468
  "Repository.java", "Mapper.java",
2469
2469
  )
2470
2470
  # Build set of test stems (FooTest → Foo, FooIT → Foo, etc.)
@@ -152,6 +152,9 @@ def _find_class_file(
152
152
  return None
153
153
 
154
154
 
155
+ _PKG_IMPORT_RE = re.compile(r'^\s*(?:package|import)\s')
156
+
157
+
155
158
  def _apply_rename(source: str, old_name: str, new_name: str) -> str:
156
159
  """Apply word-boundary replacement for class name (PascalCase and camelCase forms)."""
157
160
  result = re.sub(r'\b' + re.escape(old_name) + r'\b', new_name, source)
@@ -159,7 +162,12 @@ def _apply_rename(source: str, old_name: str, new_name: str) -> str:
159
162
  old_camel = _to_camel(old_name)
160
163
  new_camel = _to_camel(new_name)
161
164
  if old_camel != old_name and old_camel in result:
162
- result = re.sub(r'\b' + re.escape(old_camel) + r'\b', new_camel, result)
165
+ camel_re = re.compile(r'\b' + re.escape(old_camel) + r'\b')
166
+ lines = result.splitlines(keepends=True)
167
+ result = ''.join(
168
+ line if _PKG_IMPORT_RE.match(line) else camel_re.sub(new_camel, line)
169
+ for line in lines
170
+ )
163
171
 
164
172
  return result
165
173
 
@@ -193,7 +201,12 @@ def _apply_rename_refs_only(source: str, old_name: str, new_name: str) -> str:
193
201
  old_camel = _to_camel(old_name)
194
202
  new_camel = _to_camel(new_name)
195
203
  if old_camel != old_name and old_camel in result:
196
- result = re.sub(r'\b' + re.escape(old_camel) + r'\b', new_camel, result)
204
+ camel_re = re.compile(r'\b' + re.escape(old_camel) + r'\b')
205
+ lines = result.splitlines(keepends=True)
206
+ result = ''.join(
207
+ line if _PKG_IMPORT_RE.match(line) else camel_re.sub(new_camel, line)
208
+ for line in lines
209
+ )
197
210
 
198
211
  return result
199
212
 
@@ -23,6 +23,7 @@ from pathlib import Path
23
23
  from typing import Any, Optional
24
24
 
25
25
  from sourcecode.fqn_utils import normalize_owner_fqn as _normalize_owner_fqn
26
+ from sourcecode.path_filters import is_test_path as _is_test_path
26
27
 
27
28
  # ---------------------------------------------------------------------------
28
29
  # Data classes — Phases 1–4
@@ -3582,13 +3583,37 @@ def find_java_files(root: Path, *, max_files: int = 8000, limitations: list[str]
3582
3583
  except ValueError:
3583
3584
  continue
3584
3585
  parts = rel.split("/")
3585
- # Skip test dirs
3586
- if (
3587
- "/src/test/" in rel or rel.startswith("src/test/")
3588
- or "/src/tests/" in rel or rel.startswith("src/tests/")
3589
- or rel.startswith("test/") or rel.startswith("tests/")
3590
- ):
3591
- continue
3586
+ # Skip test dirs — use centralised is_test_path (consistent with
3587
+ # extract_java_endpoints), guarded against false positives where a Java
3588
+ # *package* is named "test" inside a production src/main/ source root.
3589
+ if _is_test_path(rel):
3590
+ _skip = True
3591
+ # Prepend "/" so the check works whether or not rel has a leading slash.
3592
+ _rel_sl = "/" + rel
3593
+ if "/src/main/" in _rel_sl:
3594
+ # is_test_path may fire on a package segment (e.g. com.example.test)
3595
+ # rather than a true test module directory. Only skip when the path
3596
+ # prefix BEFORE src/main/ is itself a test path (meaning the whole
3597
+ # module is a test module, not just a package named "test").
3598
+ _prefix = _rel_sl.split("/src/main/")[0]
3599
+ _prefix_parts = [p for p in _prefix.split("/") if p]
3600
+ # A module is a test module if is_test_path says so OR if any
3601
+ # module directory component starts with "test" (e.g. "test-framework",
3602
+ # "test-providers", "testsuite") OR contains "test"/"tests" as a
3603
+ # hyphen/underscore-separated word (e.g. "jobrunr-micronaut-tests").
3604
+ _prefix_is_test = (
3605
+ _is_test_path(_prefix + "/x.java")
3606
+ or any(p.lower().startswith("test") for p in _prefix_parts)
3607
+ or any(
3608
+ w in {"test", "tests", "spec", "specs"}
3609
+ for p in _prefix_parts
3610
+ for w in p.lower().replace("-", " ").replace("_", " ").split()
3611
+ )
3612
+ )
3613
+ if not _prefix_is_test:
3614
+ _skip = False
3615
+ if _skip:
3616
+ continue
3592
3617
  # Skip vendor/generated/build dirs
3593
3618
  if any(part in _VENDOR_DIRS for part in parts[:-1]):
3594
3619
  continue
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes