sourcecode 1.41.0__tar.gz → 1.44.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 (121) hide show
  1. {sourcecode-1.41.0 → sourcecode-1.44.0}/.gitignore +4 -1
  2. {sourcecode-1.41.0 → sourcecode-1.44.0}/CHANGELOG.md +72 -0
  3. {sourcecode-1.41.0 → sourcecode-1.44.0}/PKG-INFO +3 -3
  4. {sourcecode-1.41.0 → sourcecode-1.44.0}/README.md +2 -2
  5. {sourcecode-1.41.0 → sourcecode-1.44.0}/pyproject.toml +1 -1
  6. {sourcecode-1.41.0 → sourcecode-1.44.0}/src/sourcecode/__init__.py +1 -1
  7. {sourcecode-1.41.0 → sourcecode-1.44.0}/src/sourcecode/repository_ir.py +101 -3
  8. {sourcecode-1.41.0 → sourcecode-1.44.0}/src/sourcecode/spring_impact.py +66 -0
  9. sourcecode-1.41.0/supabase/.temp/cli-latest +0 -1
  10. {sourcecode-1.41.0 → sourcecode-1.44.0}/.github/workflows/build-windows.yml +0 -0
  11. {sourcecode-1.41.0 → sourcecode-1.44.0}/.ruff.toml +0 -0
  12. {sourcecode-1.41.0 → sourcecode-1.44.0}/CONTRIBUTING.md +0 -0
  13. {sourcecode-1.41.0 → sourcecode-1.44.0}/LICENSE +0 -0
  14. {sourcecode-1.41.0 → sourcecode-1.44.0}/SECURITY.md +0 -0
  15. {sourcecode-1.41.0 → sourcecode-1.44.0}/raw +0 -0
  16. {sourcecode-1.41.0 → sourcecode-1.44.0}/src/sourcecode/adaptive_scanner.py +0 -0
  17. {sourcecode-1.41.0 → sourcecode-1.44.0}/src/sourcecode/architecture_analyzer.py +0 -0
  18. {sourcecode-1.41.0 → sourcecode-1.44.0}/src/sourcecode/architecture_summary.py +0 -0
  19. {sourcecode-1.41.0 → sourcecode-1.44.0}/src/sourcecode/ast_extractor.py +0 -0
  20. {sourcecode-1.41.0 → sourcecode-1.44.0}/src/sourcecode/cache.py +0 -0
  21. {sourcecode-1.41.0 → sourcecode-1.44.0}/src/sourcecode/canonical_ir.py +0 -0
  22. {sourcecode-1.41.0 → sourcecode-1.44.0}/src/sourcecode/cir_graphs.py +0 -0
  23. {sourcecode-1.41.0 → sourcecode-1.44.0}/src/sourcecode/classifier.py +0 -0
  24. {sourcecode-1.41.0 → sourcecode-1.44.0}/src/sourcecode/cli.py +0 -0
  25. {sourcecode-1.41.0 → sourcecode-1.44.0}/src/sourcecode/code_notes_analyzer.py +0 -0
  26. {sourcecode-1.41.0 → sourcecode-1.44.0}/src/sourcecode/confidence_analyzer.py +0 -0
  27. {sourcecode-1.41.0 → sourcecode-1.44.0}/src/sourcecode/context_scorer.py +0 -0
  28. {sourcecode-1.41.0 → sourcecode-1.44.0}/src/sourcecode/context_summarizer.py +0 -0
  29. {sourcecode-1.41.0 → sourcecode-1.44.0}/src/sourcecode/contract_model.py +0 -0
  30. {sourcecode-1.41.0 → sourcecode-1.44.0}/src/sourcecode/contract_pipeline.py +0 -0
  31. {sourcecode-1.41.0 → sourcecode-1.44.0}/src/sourcecode/coverage_parser.py +0 -0
  32. {sourcecode-1.41.0 → sourcecode-1.44.0}/src/sourcecode/dependency_analyzer.py +0 -0
  33. {sourcecode-1.41.0 → sourcecode-1.44.0}/src/sourcecode/detectors/__init__.py +0 -0
  34. {sourcecode-1.41.0 → sourcecode-1.44.0}/src/sourcecode/detectors/base.py +0 -0
  35. {sourcecode-1.41.0 → sourcecode-1.44.0}/src/sourcecode/detectors/csproj_parser.py +0 -0
  36. {sourcecode-1.41.0 → sourcecode-1.44.0}/src/sourcecode/detectors/dart.py +0 -0
  37. {sourcecode-1.41.0 → sourcecode-1.44.0}/src/sourcecode/detectors/dotnet.py +0 -0
  38. {sourcecode-1.41.0 → sourcecode-1.44.0}/src/sourcecode/detectors/elixir.py +0 -0
  39. {sourcecode-1.41.0 → sourcecode-1.44.0}/src/sourcecode/detectors/go.py +0 -0
  40. {sourcecode-1.41.0 → sourcecode-1.44.0}/src/sourcecode/detectors/heuristic.py +0 -0
  41. {sourcecode-1.41.0 → sourcecode-1.44.0}/src/sourcecode/detectors/hybrid.py +0 -0
  42. {sourcecode-1.41.0 → sourcecode-1.44.0}/src/sourcecode/detectors/java.py +0 -0
  43. {sourcecode-1.41.0 → sourcecode-1.44.0}/src/sourcecode/detectors/jvm_ext.py +0 -0
  44. {sourcecode-1.41.0 → sourcecode-1.44.0}/src/sourcecode/detectors/nodejs.py +0 -0
  45. {sourcecode-1.41.0 → sourcecode-1.44.0}/src/sourcecode/detectors/parsers.py +0 -0
  46. {sourcecode-1.41.0 → sourcecode-1.44.0}/src/sourcecode/detectors/php.py +0 -0
  47. {sourcecode-1.41.0 → sourcecode-1.44.0}/src/sourcecode/detectors/project.py +0 -0
  48. {sourcecode-1.41.0 → sourcecode-1.44.0}/src/sourcecode/detectors/python.py +0 -0
  49. {sourcecode-1.41.0 → sourcecode-1.44.0}/src/sourcecode/detectors/ruby.py +0 -0
  50. {sourcecode-1.41.0 → sourcecode-1.44.0}/src/sourcecode/detectors/rust.py +0 -0
  51. {sourcecode-1.41.0 → sourcecode-1.44.0}/src/sourcecode/detectors/systems.py +0 -0
  52. {sourcecode-1.41.0 → sourcecode-1.44.0}/src/sourcecode/detectors/terraform.py +0 -0
  53. {sourcecode-1.41.0 → sourcecode-1.44.0}/src/sourcecode/detectors/tooling.py +0 -0
  54. {sourcecode-1.41.0 → sourcecode-1.44.0}/src/sourcecode/doc_analyzer.py +0 -0
  55. {sourcecode-1.41.0 → sourcecode-1.44.0}/src/sourcecode/entrypoint_classifier.py +0 -0
  56. {sourcecode-1.41.0 → sourcecode-1.44.0}/src/sourcecode/env_analyzer.py +0 -0
  57. {sourcecode-1.41.0 → sourcecode-1.44.0}/src/sourcecode/error_schema.py +0 -0
  58. {sourcecode-1.41.0 → sourcecode-1.44.0}/src/sourcecode/explain.py +0 -0
  59. {sourcecode-1.41.0 → sourcecode-1.44.0}/src/sourcecode/file_chunker.py +0 -0
  60. {sourcecode-1.41.0 → sourcecode-1.44.0}/src/sourcecode/file_classifier.py +0 -0
  61. {sourcecode-1.41.0 → sourcecode-1.44.0}/src/sourcecode/flow_analyzer.py +0 -0
  62. {sourcecode-1.41.0 → sourcecode-1.44.0}/src/sourcecode/format_contract.py +0 -0
  63. {sourcecode-1.41.0 → sourcecode-1.44.0}/src/sourcecode/fqn_utils.py +0 -0
  64. {sourcecode-1.41.0 → sourcecode-1.44.0}/src/sourcecode/git_analyzer.py +0 -0
  65. {sourcecode-1.41.0 → sourcecode-1.44.0}/src/sourcecode/graph_analyzer.py +0 -0
  66. {sourcecode-1.41.0 → sourcecode-1.44.0}/src/sourcecode/license.py +0 -0
  67. {sourcecode-1.41.0 → sourcecode-1.44.0}/src/sourcecode/mcp/__init__.py +0 -0
  68. {sourcecode-1.41.0 → sourcecode-1.44.0}/src/sourcecode/mcp/onboarding/__init__.py +0 -0
  69. {sourcecode-1.41.0 → sourcecode-1.44.0}/src/sourcecode/mcp/onboarding/applier.py +0 -0
  70. {sourcecode-1.41.0 → sourcecode-1.44.0}/src/sourcecode/mcp/onboarding/backup.py +0 -0
  71. {sourcecode-1.41.0 → sourcecode-1.44.0}/src/sourcecode/mcp/onboarding/detector.py +0 -0
  72. {sourcecode-1.41.0 → sourcecode-1.44.0}/src/sourcecode/mcp/onboarding/planner.py +0 -0
  73. {sourcecode-1.41.0 → sourcecode-1.44.0}/src/sourcecode/mcp/orchestrator.py +0 -0
  74. {sourcecode-1.41.0 → sourcecode-1.44.0}/src/sourcecode/mcp/registry.py +0 -0
  75. {sourcecode-1.41.0 → sourcecode-1.44.0}/src/sourcecode/mcp/runner.py +0 -0
  76. {sourcecode-1.41.0 → sourcecode-1.44.0}/src/sourcecode/mcp/server.py +0 -0
  77. {sourcecode-1.41.0 → sourcecode-1.44.0}/src/sourcecode/mcp_nudge.py +0 -0
  78. {sourcecode-1.41.0 → sourcecode-1.44.0}/src/sourcecode/metrics_analyzer.py +0 -0
  79. {sourcecode-1.41.0 → sourcecode-1.44.0}/src/sourcecode/migrate_check.py +0 -0
  80. {sourcecode-1.41.0 → sourcecode-1.44.0}/src/sourcecode/openapi_surface.py +0 -0
  81. {sourcecode-1.41.0 → sourcecode-1.44.0}/src/sourcecode/output_budget.py +0 -0
  82. {sourcecode-1.41.0 → sourcecode-1.44.0}/src/sourcecode/path_filters.py +0 -0
  83. {sourcecode-1.41.0 → sourcecode-1.44.0}/src/sourcecode/pr_comment_renderer.py +0 -0
  84. {sourcecode-1.41.0 → sourcecode-1.44.0}/src/sourcecode/pr_impact.py +0 -0
  85. {sourcecode-1.41.0 → sourcecode-1.44.0}/src/sourcecode/prepare_context.py +0 -0
  86. {sourcecode-1.41.0 → sourcecode-1.44.0}/src/sourcecode/progress.py +0 -0
  87. {sourcecode-1.41.0 → sourcecode-1.44.0}/src/sourcecode/ranking_engine.py +0 -0
  88. {sourcecode-1.41.0 → sourcecode-1.44.0}/src/sourcecode/redactor.py +0 -0
  89. {sourcecode-1.41.0 → sourcecode-1.44.0}/src/sourcecode/relevance_scorer.py +0 -0
  90. {sourcecode-1.41.0 → sourcecode-1.44.0}/src/sourcecode/rename_refactor.py +0 -0
  91. {sourcecode-1.41.0 → sourcecode-1.44.0}/src/sourcecode/repo_classifier.py +0 -0
  92. {sourcecode-1.41.0 → sourcecode-1.44.0}/src/sourcecode/ris.py +0 -0
  93. {sourcecode-1.41.0 → sourcecode-1.44.0}/src/sourcecode/runtime_classifier.py +0 -0
  94. {sourcecode-1.41.0 → sourcecode-1.44.0}/src/sourcecode/scanner.py +0 -0
  95. {sourcecode-1.41.0 → sourcecode-1.44.0}/src/sourcecode/schema.py +0 -0
  96. {sourcecode-1.41.0 → sourcecode-1.44.0}/src/sourcecode/security_config.py +0 -0
  97. {sourcecode-1.41.0 → sourcecode-1.44.0}/src/sourcecode/semantic_analyzer.py +0 -0
  98. {sourcecode-1.41.0 → sourcecode-1.44.0}/src/sourcecode/serializer.py +0 -0
  99. {sourcecode-1.41.0 → sourcecode-1.44.0}/src/sourcecode/spring_event_topology.py +0 -0
  100. {sourcecode-1.41.0 → sourcecode-1.44.0}/src/sourcecode/spring_findings.py +0 -0
  101. {sourcecode-1.41.0 → sourcecode-1.44.0}/src/sourcecode/spring_model.py +0 -0
  102. {sourcecode-1.41.0 → sourcecode-1.44.0}/src/sourcecode/spring_security_audit.py +0 -0
  103. {sourcecode-1.41.0 → sourcecode-1.44.0}/src/sourcecode/spring_semantic.py +0 -0
  104. {sourcecode-1.41.0 → sourcecode-1.44.0}/src/sourcecode/spring_tx_analyzer.py +0 -0
  105. {sourcecode-1.41.0 → sourcecode-1.44.0}/src/sourcecode/summarizer.py +0 -0
  106. {sourcecode-1.41.0 → sourcecode-1.44.0}/src/sourcecode/telemetry/__init__.py +0 -0
  107. {sourcecode-1.41.0 → sourcecode-1.44.0}/src/sourcecode/telemetry/config.py +0 -0
  108. {sourcecode-1.41.0 → sourcecode-1.44.0}/src/sourcecode/telemetry/consent.py +0 -0
  109. {sourcecode-1.41.0 → sourcecode-1.44.0}/src/sourcecode/telemetry/events.py +0 -0
  110. {sourcecode-1.41.0 → sourcecode-1.44.0}/src/sourcecode/telemetry/filters.py +0 -0
  111. {sourcecode-1.41.0 → sourcecode-1.44.0}/src/sourcecode/telemetry/transport.py +0 -0
  112. {sourcecode-1.41.0 → sourcecode-1.44.0}/src/sourcecode/tree_utils.py +0 -0
  113. {sourcecode-1.41.0 → sourcecode-1.44.0}/src/sourcecode/validation_surface.py +0 -0
  114. {sourcecode-1.41.0 → sourcecode-1.44.0}/src/sourcecode/version_check.py +0 -0
  115. {sourcecode-1.41.0 → sourcecode-1.44.0}/src/sourcecode/workspace.py +0 -0
  116. {sourcecode-1.41.0 → sourcecode-1.44.0}/supabase/functions/README.md +0 -0
  117. {sourcecode-1.41.0 → sourcecode-1.44.0}/supabase/functions/get-license/index.ts +0 -0
  118. {sourcecode-1.41.0 → sourcecode-1.44.0}/supabase/functions/lemonsqueezy-webhook/index.ts +0 -0
  119. {sourcecode-1.41.0 → sourcecode-1.44.0}/supabase/functions/telemetry/index.ts +0 -0
  120. {sourcecode-1.41.0 → sourcecode-1.44.0}/supabase/sql/license_event_ordering.sql +0 -0
  121. {sourcecode-1.41.0 → sourcecode-1.44.0}/supabase/sql/telemetry_events.sql +0 -0
@@ -33,4 +33,7 @@ src/*.egg-info/
33
33
  .DS_Store
34
34
  Thumbs.db
35
35
 
36
- .planning
36
+ .planning
37
+
38
+ # Supabase local temp
39
+ supabase/.temp/
@@ -1,5 +1,77 @@
1
1
  # Changelog
2
2
 
3
+ ## [1.44.0] — 2026-06-16
4
+
5
+ ### Fixed
6
+ - **CH-004c — annotation-free structural classes dropped their inheritance edges.**
7
+ `build_repo_ir`'s fast pre-scan skips files with no recognized annotation marker,
8
+ emitting only minimal class-name symbols (for same-package resolution) and **no
9
+ relations**. A class with no annotation and no injected field of its own that
10
+ participates purely structurally — `class X extends Base implements I {}` — therefore
11
+ lost its `extends`/`implements` edges, so `implementation_graph` could not link
12
+ sub→supertype and impact analysis could not traverse the hierarchy. The pre-scan now
13
+ also keeps a file when a type declaration carries an `extends`/`implements` clause
14
+ (`_INHERIT_PRESCAN_RE`), routing it through full extraction so its inheritance edges
15
+ are built and resolved in pass 2 with the same-package map. Annotation-free leaf
16
+ classes with no inheritance still skip, preserving the pre-scan optimization. Closes
17
+ the last open layer of CH-004 (a/b shipped in 1.43.0).
18
+
19
+ ## [1.43.0] — 2026-06-16
20
+
21
+ ### Fixed
22
+ - **CH-004a — impact graph dropped field-injection-only classes.** `build_repo_ir`'s fast
23
+ pre-scan skips files with no recognized annotation marker; the marker set included
24
+ `@Inject` but not `@Autowired`, `@Resource`, `@Qualifier`, or `@Value`. A class wired
25
+ purely by field injection with no class-level stereotype (e.g. an abstract base controller
26
+ that holds the services its concrete subclasses inherit) was skipped entirely, so its
27
+ `injects` edges never existed and `impact-chain` could not traverse through it. Added the
28
+ field/setter-injection annotations (`@Autowired`, `@Resource`, `@Qualifier`, `@Value`,
29
+ `@PersistenceContext`, `@PersistenceUnit`) to the pre-scan marker set.
30
+ - **CH-004b — same-package supertypes were not FQN-resolved.** The `extends`/`implements`
31
+ edge builder resolved supertype names only via `import_map`, so a same-package
32
+ `extends Base` (which needs no Java import) produced a **bare-name** edge target. The
33
+ `implementation_graph` could then not link sub→supertype, making same-package class
34
+ hierarchies invisible to impact analysis. Supertypes are now resolved via
35
+ `_resolve_dep_type` (import + same-package + wildcard), matching `injects`/constructor edges.
36
+
37
+ Both fixes were surfaced by a field test on BroadleafCommerce (2985-file monolith); they
38
+ under-reported blast radius on any large repo, not just that one. See
39
+ `docs/eval/2026-06-16-broadleaf-checkout-impact-fieldtest.md`.
40
+
41
+ ## [1.42.0] — 2026-06-16
42
+
43
+ ### Added
44
+ - **Fase 22 — type-usage edges in `impact-chain` (CH-003).** Value/DTO/response
45
+ types previously had an invisible blast radius: the impact graph modelled call and
46
+ DI/injection edges but not how a type is wired *by type*. Two new edges close this:
47
+ - **`returns`** — `method → returnTypeFQN` (method-level). For a `@ResponseBody`
48
+ handler returning a domain type this is the only link from the type back to its
49
+ endpoint, so `_collect_endpoints` now surfaces that route precisely.
50
+ - **`instantiates`** — `class → T` for `new T(...)`, giving build-only value types
51
+ (commands, receipts) a visible blast radius. Controller-like classes are excluded
52
+ (already covered precisely by `returns`) to avoid broadening a DTO's impact to
53
+ every route on the controller.
54
+
55
+ ### Fixed
56
+ - **Inline-annotation method parsing.** `_METHOD_DECL_RE` could not match a
57
+ modifier-position annotation (`public @ResponseBody Vets foo()`); the whole
58
+ declaration failed and the method — its endpoint *and* return type — was silently
59
+ dropped. Inline annotations are now consumed and folded into the method's annotation
60
+ set. Recovers, e.g., the `GET /vets` handler in spring-petclinic `VetController`.
61
+
62
+ ### Changed
63
+ - **Safety guard for type-usage blind spots (CH-003 Part 1).** A fully empty blast
64
+ radius on a positively-identified plain value type (node present, `symbol_kind` in
65
+ class/enum/record, no stereotype annotation, role `other`, not a controller) is no
66
+ longer reported at `confidence: high` — it drops to `low` with a warning that an
67
+ empty result is not proof the type is unused. Spine symbols and incomplete-IR cases
68
+ keep prior behaviour. The guard now stays dormant when real type-usage edges exist.
69
+
70
+ ### Field test (spring-petclinic #2333)
71
+ - `impact-chain Vets`: was `confidence: high` with 0 callers / 0 endpoints (a dangerous
72
+ false zero) → now `high`, caller `showResourcesVetList`, endpoint `GET /vets`.
73
+ - `impact-chain VetController`: was 1 of 2 endpoints → both (`GET /vets` + `/vets.html`).
74
+
3
75
  ## [1.41.0] — 2026-06-16
4
76
 
5
77
  ### Added
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sourcecode
3
- Version: 1.41.0
3
+ Version: 1.44.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
@@ -40,7 +40,7 @@ Description-Content-Type: text/markdown
40
40
 
41
41
  **Persistent structural context and ultra-fast repeated analysis for AI coding agents.**
42
42
 
43
- ![Version](https://img.shields.io/badge/version-1.39.0-blue)
43
+ ![Version](https://img.shields.io/badge/version-1.44.0-blue)
44
44
  ![Python](https://img.shields.io/badge/python-3.9%2B-green)
45
45
 
46
46
  ---
@@ -114,7 +114,7 @@ pipx install sourcecode
114
114
 
115
115
  ```bash
116
116
  sourcecode version
117
- # sourcecode 1.39.0
117
+ # sourcecode 1.44.0
118
118
  ```
119
119
 
120
120
  ---
@@ -2,7 +2,7 @@
2
2
 
3
3
  **Persistent structural context and ultra-fast repeated analysis for AI coding agents.**
4
4
 
5
- ![Version](https://img.shields.io/badge/version-1.39.0-blue)
5
+ ![Version](https://img.shields.io/badge/version-1.44.0-blue)
6
6
  ![Python](https://img.shields.io/badge/python-3.9%2B-green)
7
7
 
8
8
  ---
@@ -76,7 +76,7 @@ pipx install sourcecode
76
76
 
77
77
  ```bash
78
78
  sourcecode version
79
- # sourcecode 1.39.0
79
+ # sourcecode 1.44.0
80
80
  ```
81
81
 
82
82
  ---
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "sourcecode"
7
- version = "1.41.0"
7
+ version = "1.44.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.41.0"
3
+ __version__ = "1.44.0"
@@ -131,9 +131,22 @@ _CLASS_DECL_RE = re.compile(
131
131
  r'\s*\{',
132
132
  )
133
133
 
134
+ # CH-004c: detect a type declaration that participates structurally via inheritance
135
+ # (`class X extends Base` / `interface I extends A` / `class C implements I`). Used by
136
+ # the fast pre-scan to NOT skip annotation-free files that still own extends/implements
137
+ # edges the impact graph needs. `[^{;]*?` keeps the match within a single declaration
138
+ # head (stops at the body `{` or a `;`), matching the precision of _CLASS_DECL_RE.
139
+ _INHERIT_PRESCAN_RE = re.compile(
140
+ r'\b(?:class|interface)\s+[A-Z]\w*[^{;]*?\b(?:extends|implements)\b'
141
+ )
142
+
134
143
  _METHOD_DECL_RE = re.compile(
135
144
  r'^(?P<modifiers>(?:(?:public|private|protected|static|final|synchronized'
136
145
  r'|abstract|default|native|strictfp|override)\s+)*)'
146
+ # Inline (modifier-position) annotations, e.g. `public @ResponseBody Vets foo()`
147
+ # or `@Valid`, `@Nonnull`. Without this the whole declaration fails to match and
148
+ # the method (its endpoint + return-type edge) is silently dropped (CH-003/Fase 22).
149
+ r'(?P<inline_anns>(?:@[\w.]+(?:\s*\([^)]*\))?\s+)*)'
137
150
  r'(?:<[\w,\s?]+>\s+)?'
138
151
  r'(?P<return_type>(?:void|boolean|byte|char|short|int|long|float|double|String|[\w.<>\[\]?,]+)\s+)'
139
152
  r'(?P<name>[a-z_]\w*)\s*\(',
@@ -830,6 +843,11 @@ def _extract_symbols(
830
843
  if mname not in _JAVA_KEYWORDS:
831
844
  fqn = f"{class_fqn}#{mname}"
832
845
  modifiers = _parse_modifier_str(mth_m.group("modifiers") or "")
846
+ # Fold modifier-position inline annotations into pending_anns so
847
+ # symbol_kind / endpoint detection see them (e.g. @ResponseBody).
848
+ for _inl in re.findall(r'@[\w.]+', mth_m.group("inline_anns") or ""):
849
+ if _inl not in pending_anns:
850
+ pending_anns.append(_inl)
833
851
  used = _resolve_types_from_text(stripped, import_map)
834
852
  conf = "high" if ("public" in modifiers or pending_anns) else "medium"
835
853
 
@@ -1256,7 +1274,11 @@ def _build_relations(
1256
1274
  # commas so each base produces its own edge and the reverse graph sees
1257
1275
  # every supertype (not a single mangled token).
1258
1276
  for base in _split_supertype_list(extends_str):
1259
- to = import_map.get(base, base)
1277
+ # CH-004: resolve same-package / wildcard-imported supertypes to their
1278
+ # FQN (not just import_map), else a same-package `extends Base` stays a
1279
+ # bare name and the implementation_graph cannot link sub→supertype.
1280
+ _sbase = re.sub(r'<.*', '', base).strip()
1281
+ to = _resolve_dep_type(_sbase) or import_map.get(_sbase, base)
1260
1282
  edges.append(RelationEdge(
1261
1283
  from_symbol=class_fqn,
1262
1284
  to_symbol=to,
@@ -1267,7 +1289,8 @@ def _build_relations(
1267
1289
 
1268
1290
  if implements_str:
1269
1291
  for base in _split_supertype_list(implements_str):
1270
- to = import_map.get(base, base)
1292
+ _sbase = re.sub(r'<.*', '', base).strip()
1293
+ to = _resolve_dep_type(_sbase) or import_map.get(_sbase, base)
1271
1294
  edges.append(RelationEdge(
1272
1295
  from_symbol=class_fqn,
1273
1296
  to_symbol=to,
@@ -1330,6 +1353,32 @@ def _build_relations(
1330
1353
  evidence={"type": "annotation", "value": ann},
1331
1354
  ))
1332
1355
 
1356
+ # ── Type-usage: return-type edges (CH-003 / Fase 22) ──────────────────────
1357
+ # A method's declared return type is a real dependency edge the call/DI graph
1358
+ # misses: for a value/DTO/response type returned (esp. @ResponseBody) by a
1359
+ # controller handler, this is the ONLY link from the type back to its endpoint.
1360
+ # method → returnTypeFQN, type="returns". Resolution reuses _resolve_dep_type,
1361
+ # so only in-repo / imported types produce edges (primitives & void are skipped).
1362
+ _PRIMITIVE_RETURNS: frozenset[str] = frozenset({
1363
+ "void", "boolean", "byte", "char", "short", "int", "long", "float",
1364
+ "double", "String", "Object",
1365
+ })
1366
+ for sym in symbols:
1367
+ if sym.type != "method" or not sym.return_type:
1368
+ continue
1369
+ _ret_base = re.sub(r'<.*', '', sym.return_type).replace("[]", "").strip()
1370
+ if not _ret_base or _ret_base in _PRIMITIVE_RETURNS or not _ret_base[0].isupper():
1371
+ continue
1372
+ _ret_fqn = _resolve_dep_type(_ret_base)
1373
+ if _ret_fqn and _ret_fqn != _enclosing_class(sym.symbol):
1374
+ edges.append(RelationEdge(
1375
+ from_symbol=sym.symbol,
1376
+ to_symbol=_ret_fqn,
1377
+ type="returns",
1378
+ confidence="high",
1379
+ evidence={"type": "return_type", "value": sym.return_type},
1380
+ ))
1381
+
1333
1382
  _class_syms = [s for s in symbols if s.type in ("class", "interface") and "#" not in s.symbol]
1334
1383
 
1335
1384
  # Strip comments before event scanning to prevent Javadoc examples from
@@ -1425,6 +1474,46 @@ def _build_relations(
1425
1474
  evidence={"type": "signature", "value": f"{ev_label}<{event_simple}>"},
1426
1475
  ))
1427
1476
 
1477
+ # ── Type-usage: instantiation edges (CH-003 / Fase 22) ────────────────────
1478
+ # `new T(...)` couples the instantiating class to T — a type-usage edge the
1479
+ # call/DI graph misses. Attributed at class level (one primary type per file,
1480
+ # mirroring the publishes_event scan). Controllers are EXCLUDED: their value-type
1481
+ # coupling is already covered precisely by `returns` edges, and a class-level
1482
+ # edge from a controller would broaden a DTO's impact to every route on that
1483
+ # controller (false breadth). Method-level attribution is a future refinement.
1484
+ # Controller-like = class-level @RestController/@Controller OR a class that owns
1485
+ # any endpoint-handler method (mappings are often only method-level).
1486
+ _classes_with_endpoints = {
1487
+ _enclosing_class(s.symbol) for s in symbols if s.symbol_kind == "endpoint"
1488
+ }
1489
+ _instantiating_controllers = {
1490
+ s.symbol for s in symbols
1491
+ if s.type in ("class", "interface")
1492
+ and (
1493
+ any(a in ("@RestController", "@Controller") for a in s.annotations)
1494
+ or s.symbol in _classes_with_endpoints
1495
+ )
1496
+ }
1497
+ _non_controller_classes = [
1498
+ s for s in _class_syms if s.symbol not in _instantiating_controllers
1499
+ ]
1500
+ if _non_controller_classes:
1501
+ _inst_targets: set[str] = set()
1502
+ for _m in re.finditer(r'\bnew\s+([A-Z]\w*)\s*[(<]', _source_no_comments):
1503
+ _t_fqn = _resolve_dep_type(_m.group(1))
1504
+ if _t_fqn:
1505
+ _inst_targets.add(_t_fqn)
1506
+ for cls_sym in _non_controller_classes:
1507
+ for _tgt in sorted(_inst_targets):
1508
+ if _tgt != cls_sym.symbol:
1509
+ edges.append(RelationEdge(
1510
+ from_symbol=cls_sym.symbol,
1511
+ to_symbol=_tgt,
1512
+ type="instantiates",
1513
+ confidence="medium",
1514
+ evidence={"type": "method_call", "value": f"new {_tgt.split('.')[-1]}(...)"},
1515
+ ))
1516
+
1428
1517
  seen: set[tuple[str, str, str]] = set()
1429
1518
  unique: list[RelationEdge] = []
1430
1519
  for e in edges:
@@ -3052,6 +3141,14 @@ def build_repo_ir(
3052
3141
  '@RequiredArgsConstructor', '@AllArgsConstructor',
3053
3142
  '@Inject', '@ApplicationScoped', '@RequestScoped', '@Singleton',
3054
3143
  '@EnableMethodSecurity', '@EnableGlobalMethodSecurity',
3144
+ # Field/setter injection markers (CH-004). A class wired purely by field
3145
+ # injection — no class-level stereotype — is still a node in the DI graph:
3146
+ # e.g. an abstract base controller that holds @Autowired/@Resource services
3147
+ # its concrete subclasses inherit. Omitting these pre-scan-skips such classes,
3148
+ # dropping their injects edges, so impact-chain cannot reach callers through
3149
+ # them (Broadleaf: AbstractCheckoutController → checkout endpoints went missing).
3150
+ '@Autowired', '@Resource', '@Qualifier', '@Value',
3151
+ '@PersistenceContext', '@PersistenceUnit',
3055
3152
  # JPA / persistence (needed for stereotype detection in all commands)
3056
3153
  '@Entity', '@MappedSuperclass', '@Embeddable',
3057
3154
  # AOP / messaging / event sourcing
@@ -3098,7 +3195,8 @@ def build_repo_ir(
3098
3195
  _meta_chars_read += len(source)
3099
3196
  # Fast pre-scan: if file has no relevant annotations skip full extraction.
3100
3197
  # Still register package/class name for same-package resolution.
3101
- if not any(marker in source for marker in _effective_markers):
3198
+ if not any(marker in source for marker in _effective_markers) \
3199
+ and not _INHERIT_PRESCAN_RE.search(source):
3102
3200
  pkg_m = _PKG_RE.search(source)
3103
3201
  _pkg = pkg_m.group(1) if pkg_m else ""
3104
3202
  # Minimal class-name symbols for same-package map (no methods/fields)
@@ -129,6 +129,50 @@ class ImpactChainResult:
129
129
  return d
130
130
 
131
131
 
132
+ # ---------------------------------------------------------------------------
133
+ # CH-003 — value/DTO type blind-spot detection
134
+ # ---------------------------------------------------------------------------
135
+ # The impact graph models call + DI/injection edges but not *type-usage* edges
136
+ # (constructor instantiation `new T()`, field/local-variable type, and method
137
+ # return type incl. @ResponseBody). For a service/repository the call+DI edges
138
+ # cover the real blast radius; for a value/DTO/response type they cover nothing,
139
+ # so its impact is invisible — and an all-zero result reported at confidence=high
140
+ # reads as "globally dead" (a dangerous false negative). Until type-usage edges
141
+ # are modelled (Fase 22 / CH-002), positively identify plain value types and
142
+ # downgrade confidence + warn instead of asserting an empty high-confidence result.
143
+ _STEREOTYPE_ANNOTATIONS = frozenset({
144
+ "@Service", "@Repository", "@Controller", "@RestController",
145
+ "@Component", "@Configuration", "@ControllerAdvice",
146
+ "@RestControllerAdvice", "@Bean",
147
+ })
148
+ _VALUE_TYPE_KINDS = frozenset({"class", "enum", "record"})
149
+
150
+
151
+ def _is_unmodeled_value_type(cir, class_fqn: str, model) -> bool:
152
+ """True iff class_fqn is positively a plain value/DTO type whose blast radius
153
+ flows only through type-usage edges the impact graph does not model.
154
+
155
+ Conservative: returns False whenever the type cannot be positively confirmed
156
+ (node metadata absent, stereotype annotation present, recognized Spring role,
157
+ or controller) so spine symbols and incomplete-IR cases keep legacy behaviour.
158
+ """
159
+ graph = (getattr(cir, "_raw_ir", None) or {}).get("graph") or {}
160
+ node = next((n for n in (graph.get("nodes") or []) if n.get("fqn") == class_fqn), None)
161
+ if node is None:
162
+ return False # cannot confirm — stay conservative (preserves IC-V3)
163
+ if (node.get("symbol_kind") or node.get("type")) not in _VALUE_TYPE_KINDS:
164
+ return False # interface / annotation / bean-method etc.
165
+ anns = node.get("annotations") or []
166
+ if any(a.split("(", 1)[0] in _STEREOTYPE_ANNOTATIONS for a in anns):
167
+ return False # Spring stereotype bean — spine participant
168
+ if (node.get("role") or "other") != "other":
169
+ return False # recognized Spring role (repository/service/controller/mapper)
170
+ controllers = getattr(getattr(model, "endpoint_index", None), "controller_fqns", frozenset())
171
+ if class_fqn in controllers:
172
+ return False
173
+ return True
174
+
175
+
132
176
  # ---------------------------------------------------------------------------
133
177
  # Symbol resolution
134
178
  # ---------------------------------------------------------------------------
@@ -706,9 +750,31 @@ class ImpactOrchestrator:
706
750
  impact_findings_raw,
707
751
  )
708
752
 
753
+ # CH-003: empty blast radius on a positively-identified value/DTO type is a
754
+ # type-usage blind spot, not proof of dead code — warn + drop confidence.
755
+ empty_blast = (
756
+ not direct_callers and not indirect_callers
757
+ and not endpoints_affected and not subtype_classes_added
758
+ )
759
+ value_type_blind_spot = (
760
+ empty_blast
761
+ and "#" not in resolved_symbol
762
+ and resolution != "not_found"
763
+ and _is_unmodeled_value_type(cir, resolved_symbol, model)
764
+ )
765
+ if value_type_blind_spot:
766
+ warnings.append(
767
+ "Type-usage edges not modeled (CH-003): this type's blast radius flows "
768
+ "through instantiation (new T()), field/local types, and method return "
769
+ "types (incl. @ResponseBody) — edges impact-chain does not yet track. "
770
+ "An empty result is NOT proof the type is unused."
771
+ )
772
+
709
773
  confidence: str
710
774
  if resolution == "not_found":
711
775
  confidence = "low"
776
+ elif value_type_blind_spot:
777
+ confidence = "low"
712
778
  elif resolution == "partial" or warnings:
713
779
  confidence = "medium"
714
780
  else:
@@ -1 +0,0 @@
1
- v2.106.0
File without changes
File without changes
File without changes
File without changes
File without changes