sourcecode 1.46.0__tar.gz → 1.50.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {sourcecode-1.46.0 → sourcecode-1.50.0}/CHANGELOG.md +94 -0
- {sourcecode-1.46.0 → sourcecode-1.50.0}/PKG-INFO +10 -2
- {sourcecode-1.46.0 → sourcecode-1.50.0}/README.md +9 -1
- {sourcecode-1.46.0 → sourcecode-1.50.0}/pyproject.toml +1 -1
- {sourcecode-1.46.0 → sourcecode-1.50.0}/src/sourcecode/__init__.py +1 -1
- {sourcecode-1.46.0 → sourcecode-1.50.0}/src/sourcecode/repository_ir.py +37 -0
- {sourcecode-1.46.0 → sourcecode-1.50.0}/src/sourcecode/spring_impact.py +114 -7
- {sourcecode-1.46.0 → sourcecode-1.50.0}/.github/workflows/build-windows.yml +0 -0
- {sourcecode-1.46.0 → sourcecode-1.50.0}/.gitignore +0 -0
- {sourcecode-1.46.0 → sourcecode-1.50.0}/.ruff.toml +0 -0
- {sourcecode-1.46.0 → sourcecode-1.50.0}/CONTRIBUTING.md +0 -0
- {sourcecode-1.46.0 → sourcecode-1.50.0}/LICENSE +0 -0
- {sourcecode-1.46.0 → sourcecode-1.50.0}/SECURITY.md +0 -0
- {sourcecode-1.46.0 → sourcecode-1.50.0}/raw +0 -0
- {sourcecode-1.46.0 → sourcecode-1.50.0}/src/sourcecode/adaptive_scanner.py +0 -0
- {sourcecode-1.46.0 → sourcecode-1.50.0}/src/sourcecode/architecture_analyzer.py +0 -0
- {sourcecode-1.46.0 → sourcecode-1.50.0}/src/sourcecode/architecture_summary.py +0 -0
- {sourcecode-1.46.0 → sourcecode-1.50.0}/src/sourcecode/ast_extractor.py +0 -0
- {sourcecode-1.46.0 → sourcecode-1.50.0}/src/sourcecode/cache.py +0 -0
- {sourcecode-1.46.0 → sourcecode-1.50.0}/src/sourcecode/canonical_ir.py +0 -0
- {sourcecode-1.46.0 → sourcecode-1.50.0}/src/sourcecode/cir_graphs.py +0 -0
- {sourcecode-1.46.0 → sourcecode-1.50.0}/src/sourcecode/classifier.py +0 -0
- {sourcecode-1.46.0 → sourcecode-1.50.0}/src/sourcecode/cli.py +0 -0
- {sourcecode-1.46.0 → sourcecode-1.50.0}/src/sourcecode/code_notes_analyzer.py +0 -0
- {sourcecode-1.46.0 → sourcecode-1.50.0}/src/sourcecode/confidence_analyzer.py +0 -0
- {sourcecode-1.46.0 → sourcecode-1.50.0}/src/sourcecode/context_scorer.py +0 -0
- {sourcecode-1.46.0 → sourcecode-1.50.0}/src/sourcecode/context_summarizer.py +0 -0
- {sourcecode-1.46.0 → sourcecode-1.50.0}/src/sourcecode/contract_model.py +0 -0
- {sourcecode-1.46.0 → sourcecode-1.50.0}/src/sourcecode/contract_pipeline.py +0 -0
- {sourcecode-1.46.0 → sourcecode-1.50.0}/src/sourcecode/coverage_parser.py +0 -0
- {sourcecode-1.46.0 → sourcecode-1.50.0}/src/sourcecode/dependency_analyzer.py +0 -0
- {sourcecode-1.46.0 → sourcecode-1.50.0}/src/sourcecode/detectors/__init__.py +0 -0
- {sourcecode-1.46.0 → sourcecode-1.50.0}/src/sourcecode/detectors/base.py +0 -0
- {sourcecode-1.46.0 → sourcecode-1.50.0}/src/sourcecode/detectors/csproj_parser.py +0 -0
- {sourcecode-1.46.0 → sourcecode-1.50.0}/src/sourcecode/detectors/dart.py +0 -0
- {sourcecode-1.46.0 → sourcecode-1.50.0}/src/sourcecode/detectors/dotnet.py +0 -0
- {sourcecode-1.46.0 → sourcecode-1.50.0}/src/sourcecode/detectors/elixir.py +0 -0
- {sourcecode-1.46.0 → sourcecode-1.50.0}/src/sourcecode/detectors/go.py +0 -0
- {sourcecode-1.46.0 → sourcecode-1.50.0}/src/sourcecode/detectors/heuristic.py +0 -0
- {sourcecode-1.46.0 → sourcecode-1.50.0}/src/sourcecode/detectors/hybrid.py +0 -0
- {sourcecode-1.46.0 → sourcecode-1.50.0}/src/sourcecode/detectors/java.py +0 -0
- {sourcecode-1.46.0 → sourcecode-1.50.0}/src/sourcecode/detectors/jvm_ext.py +0 -0
- {sourcecode-1.46.0 → sourcecode-1.50.0}/src/sourcecode/detectors/nodejs.py +0 -0
- {sourcecode-1.46.0 → sourcecode-1.50.0}/src/sourcecode/detectors/parsers.py +0 -0
- {sourcecode-1.46.0 → sourcecode-1.50.0}/src/sourcecode/detectors/php.py +0 -0
- {sourcecode-1.46.0 → sourcecode-1.50.0}/src/sourcecode/detectors/project.py +0 -0
- {sourcecode-1.46.0 → sourcecode-1.50.0}/src/sourcecode/detectors/python.py +0 -0
- {sourcecode-1.46.0 → sourcecode-1.50.0}/src/sourcecode/detectors/ruby.py +0 -0
- {sourcecode-1.46.0 → sourcecode-1.50.0}/src/sourcecode/detectors/rust.py +0 -0
- {sourcecode-1.46.0 → sourcecode-1.50.0}/src/sourcecode/detectors/systems.py +0 -0
- {sourcecode-1.46.0 → sourcecode-1.50.0}/src/sourcecode/detectors/terraform.py +0 -0
- {sourcecode-1.46.0 → sourcecode-1.50.0}/src/sourcecode/detectors/tooling.py +0 -0
- {sourcecode-1.46.0 → sourcecode-1.50.0}/src/sourcecode/doc_analyzer.py +0 -0
- {sourcecode-1.46.0 → sourcecode-1.50.0}/src/sourcecode/entrypoint_classifier.py +0 -0
- {sourcecode-1.46.0 → sourcecode-1.50.0}/src/sourcecode/env_analyzer.py +0 -0
- {sourcecode-1.46.0 → sourcecode-1.50.0}/src/sourcecode/error_schema.py +0 -0
- {sourcecode-1.46.0 → sourcecode-1.50.0}/src/sourcecode/explain.py +0 -0
- {sourcecode-1.46.0 → sourcecode-1.50.0}/src/sourcecode/file_chunker.py +0 -0
- {sourcecode-1.46.0 → sourcecode-1.50.0}/src/sourcecode/file_classifier.py +0 -0
- {sourcecode-1.46.0 → sourcecode-1.50.0}/src/sourcecode/flow_analyzer.py +0 -0
- {sourcecode-1.46.0 → sourcecode-1.50.0}/src/sourcecode/format_contract.py +0 -0
- {sourcecode-1.46.0 → sourcecode-1.50.0}/src/sourcecode/fqn_utils.py +0 -0
- {sourcecode-1.46.0 → sourcecode-1.50.0}/src/sourcecode/git_analyzer.py +0 -0
- {sourcecode-1.46.0 → sourcecode-1.50.0}/src/sourcecode/graph_analyzer.py +0 -0
- {sourcecode-1.46.0 → sourcecode-1.50.0}/src/sourcecode/license.py +0 -0
- {sourcecode-1.46.0 → sourcecode-1.50.0}/src/sourcecode/mcp/__init__.py +0 -0
- {sourcecode-1.46.0 → sourcecode-1.50.0}/src/sourcecode/mcp/onboarding/__init__.py +0 -0
- {sourcecode-1.46.0 → sourcecode-1.50.0}/src/sourcecode/mcp/onboarding/applier.py +0 -0
- {sourcecode-1.46.0 → sourcecode-1.50.0}/src/sourcecode/mcp/onboarding/backup.py +0 -0
- {sourcecode-1.46.0 → sourcecode-1.50.0}/src/sourcecode/mcp/onboarding/detector.py +0 -0
- {sourcecode-1.46.0 → sourcecode-1.50.0}/src/sourcecode/mcp/onboarding/planner.py +0 -0
- {sourcecode-1.46.0 → sourcecode-1.50.0}/src/sourcecode/mcp/orchestrator.py +0 -0
- {sourcecode-1.46.0 → sourcecode-1.50.0}/src/sourcecode/mcp/registry.py +0 -0
- {sourcecode-1.46.0 → sourcecode-1.50.0}/src/sourcecode/mcp/runner.py +0 -0
- {sourcecode-1.46.0 → sourcecode-1.50.0}/src/sourcecode/mcp/server.py +0 -0
- {sourcecode-1.46.0 → sourcecode-1.50.0}/src/sourcecode/mcp_nudge.py +0 -0
- {sourcecode-1.46.0 → sourcecode-1.50.0}/src/sourcecode/metrics_analyzer.py +0 -0
- {sourcecode-1.46.0 → sourcecode-1.50.0}/src/sourcecode/migrate_check.py +0 -0
- {sourcecode-1.46.0 → sourcecode-1.50.0}/src/sourcecode/openapi_surface.py +0 -0
- {sourcecode-1.46.0 → sourcecode-1.50.0}/src/sourcecode/output_budget.py +0 -0
- {sourcecode-1.46.0 → sourcecode-1.50.0}/src/sourcecode/path_filters.py +0 -0
- {sourcecode-1.46.0 → sourcecode-1.50.0}/src/sourcecode/pr_comment_renderer.py +0 -0
- {sourcecode-1.46.0 → sourcecode-1.50.0}/src/sourcecode/pr_impact.py +0 -0
- {sourcecode-1.46.0 → sourcecode-1.50.0}/src/sourcecode/prepare_context.py +0 -0
- {sourcecode-1.46.0 → sourcecode-1.50.0}/src/sourcecode/progress.py +0 -0
- {sourcecode-1.46.0 → sourcecode-1.50.0}/src/sourcecode/ranking_engine.py +0 -0
- {sourcecode-1.46.0 → sourcecode-1.50.0}/src/sourcecode/redactor.py +0 -0
- {sourcecode-1.46.0 → sourcecode-1.50.0}/src/sourcecode/relevance_scorer.py +0 -0
- {sourcecode-1.46.0 → sourcecode-1.50.0}/src/sourcecode/rename_refactor.py +0 -0
- {sourcecode-1.46.0 → sourcecode-1.50.0}/src/sourcecode/repo_classifier.py +0 -0
- {sourcecode-1.46.0 → sourcecode-1.50.0}/src/sourcecode/ris.py +0 -0
- {sourcecode-1.46.0 → sourcecode-1.50.0}/src/sourcecode/runtime_classifier.py +0 -0
- {sourcecode-1.46.0 → sourcecode-1.50.0}/src/sourcecode/scanner.py +0 -0
- {sourcecode-1.46.0 → sourcecode-1.50.0}/src/sourcecode/schema.py +0 -0
- {sourcecode-1.46.0 → sourcecode-1.50.0}/src/sourcecode/security_config.py +0 -0
- {sourcecode-1.46.0 → sourcecode-1.50.0}/src/sourcecode/semantic_analyzer.py +0 -0
- {sourcecode-1.46.0 → sourcecode-1.50.0}/src/sourcecode/serializer.py +0 -0
- {sourcecode-1.46.0 → sourcecode-1.50.0}/src/sourcecode/spring_event_topology.py +0 -0
- {sourcecode-1.46.0 → sourcecode-1.50.0}/src/sourcecode/spring_findings.py +0 -0
- {sourcecode-1.46.0 → sourcecode-1.50.0}/src/sourcecode/spring_model.py +0 -0
- {sourcecode-1.46.0 → sourcecode-1.50.0}/src/sourcecode/spring_security_audit.py +0 -0
- {sourcecode-1.46.0 → sourcecode-1.50.0}/src/sourcecode/spring_semantic.py +0 -0
- {sourcecode-1.46.0 → sourcecode-1.50.0}/src/sourcecode/spring_tx_analyzer.py +0 -0
- {sourcecode-1.46.0 → sourcecode-1.50.0}/src/sourcecode/summarizer.py +0 -0
- {sourcecode-1.46.0 → sourcecode-1.50.0}/src/sourcecode/telemetry/__init__.py +0 -0
- {sourcecode-1.46.0 → sourcecode-1.50.0}/src/sourcecode/telemetry/config.py +0 -0
- {sourcecode-1.46.0 → sourcecode-1.50.0}/src/sourcecode/telemetry/consent.py +0 -0
- {sourcecode-1.46.0 → sourcecode-1.50.0}/src/sourcecode/telemetry/events.py +0 -0
- {sourcecode-1.46.0 → sourcecode-1.50.0}/src/sourcecode/telemetry/filters.py +0 -0
- {sourcecode-1.46.0 → sourcecode-1.50.0}/src/sourcecode/telemetry/transport.py +0 -0
- {sourcecode-1.46.0 → sourcecode-1.50.0}/src/sourcecode/tree_utils.py +0 -0
- {sourcecode-1.46.0 → sourcecode-1.50.0}/src/sourcecode/validation_surface.py +0 -0
- {sourcecode-1.46.0 → sourcecode-1.50.0}/src/sourcecode/version_check.py +0 -0
- {sourcecode-1.46.0 → sourcecode-1.50.0}/src/sourcecode/workspace.py +0 -0
- {sourcecode-1.46.0 → sourcecode-1.50.0}/supabase/functions/README.md +0 -0
- {sourcecode-1.46.0 → sourcecode-1.50.0}/supabase/functions/get-license/index.ts +0 -0
- {sourcecode-1.46.0 → sourcecode-1.50.0}/supabase/functions/lemonsqueezy-webhook/index.ts +0 -0
- {sourcecode-1.46.0 → sourcecode-1.50.0}/supabase/functions/telemetry/index.ts +0 -0
- {sourcecode-1.46.0 → sourcecode-1.50.0}/supabase/sql/license_event_ordering.sql +0 -0
- {sourcecode-1.46.0 → sourcecode-1.50.0}/supabase/sql/telemetry_events.sql +0 -0
|
@@ -1,5 +1,99 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [1.50.0] — 2026-06-17
|
|
4
|
+
|
|
5
|
+
### Fixed
|
|
6
|
+
- **F-1 — `impact-chain` confidence no longer capped at `medium` by informational
|
|
7
|
+
warnings.** Confidence was computed as `medium` whenever *any* warning was present, but
|
|
8
|
+
the CH-001a/b interface↔implementation expansion notices ("Interface implementation
|
|
9
|
+
expansion: added N symbols…") are appended on every Spring interface/impl query — the
|
|
10
|
+
overwhelmingly common case — so a clean exact/`class_expanded` resolution could never
|
|
11
|
+
report `high`. The signal was meaningless: `medium` meant "normal", not "degraded".
|
|
12
|
+
|
|
13
|
+
Fix: track a `confidence_reducing` flag set only by genuinely degrading conditions
|
|
14
|
+
(hub-guard capped traversal); informational expansion notices no longer affect
|
|
15
|
+
confidence. `partial` resolution and blind-spot guards (CH-003/CH-005) still degrade as
|
|
16
|
+
before. Verified on shopizer: `impact-chain ProductService` now reports `confidence:high`
|
|
17
|
+
(was `medium`) while still surfacing the expansion notice. 2 regression tests
|
|
18
|
+
(`TestConfidenceNotCappedByInfoWarnings`): expansion warning keeps `high`, hub-guard
|
|
19
|
+
truncation still caps to `medium`.
|
|
20
|
+
|
|
21
|
+
Closes the v1.47.0 field-benchmark backlog (F-3 → CH-006 v1.48.0, F-2 → v1.49.0,
|
|
22
|
+
F-1 → this release).
|
|
23
|
+
|
|
24
|
+
## [1.49.0] — 2026-06-17
|
|
25
|
+
|
|
26
|
+
### Added
|
|
27
|
+
- **F-2 — honest WebFlux / functional-routing signal in `endpoints`.** The endpoint
|
|
28
|
+
surface models annotation-based routing (`@RequestMapping`/`@GetMapping`, JAX-RS) only.
|
|
29
|
+
Routes registered via the functional DSL (`route().GET("/path", handler)` /
|
|
30
|
+
`RouterFunction` / `CustomEndpoint`) were silently invisible — the v1.47.0 field
|
|
31
|
+
benchmark found `endpoints` returned **0** for all of halo (a reactive app with 168
|
|
32
|
+
functional registrations across 51 files), which an agent could misread as "this app
|
|
33
|
+
exposes no endpoints". `endpoints` now detects functional routing and reports a
|
|
34
|
+
`functional_routing` block (`files`, `route_registrations`, `modeled: false`) plus a
|
|
35
|
+
warning; when the annotation surface is empty but functional routes exist, the warning
|
|
36
|
+
explicitly says not to read it as "no endpoints".
|
|
37
|
+
|
|
38
|
+
Deliberately does **not** synthesize endpoint entries: the literal DSL paths are
|
|
39
|
+
relative (real paths depend on `nest()`/group-version prefixes unresolvable statically),
|
|
40
|
+
and emitting partial paths would mislead more than an empty surface (same false-positive
|
|
41
|
+
hazard CH-006 just removed). Full functional-route modeling is a separate effort.
|
|
42
|
+
Validated: halo → 168 registrations surfaced; shopizer (annotation MVC) → no false
|
|
43
|
+
trigger, 286 endpoints unchanged. 3 regression tests
|
|
44
|
+
(`test_functional_routing_surface.py`).
|
|
45
|
+
|
|
46
|
+
## [1.48.0] — 2026-06-17
|
|
47
|
+
|
|
48
|
+
### Fixed
|
|
49
|
+
- **CH-006 — hub-interface caller over-expansion (false-positive callers) in
|
|
50
|
+
`impact-chain`.** `implements` and `extends` are structural type declarations, not
|
|
51
|
+
calls, but they were being traversed in the caller BFS. The reverse edge on an
|
|
52
|
+
interface/base lists its implementors/subclasses, so querying a class that implements a
|
|
53
|
+
high-fanout in-repo interface attributed **every sibling implementor** as a "direct
|
|
54
|
+
caller". Found in the v1.47.0 field benchmark: `impact-chain ThumbnailEndpoint` on halo
|
|
55
|
+
reported 42 direct callers — the other 42 `CustomEndpoint` implementors, none of which
|
|
56
|
+
call it — inflating a leaf endpoint to `risk:high`. The shopizer monolith had the same
|
|
57
|
+
pattern via its shared `Mapper<E,D>` base (45 phantom mapper callers on `ProductService`).
|
|
58
|
+
|
|
59
|
+
Fix: add `implements`/`extends` to the BFS edge-skip set. This is loss-free — the wanted
|
|
60
|
+
interface→implementation expansion (CH-001a/b) flows through `ImplementationGraph`
|
|
61
|
+
indices, not these reverse-graph edges, and real callers travel `injects`/`calls` edges.
|
|
62
|
+
Verified on both repos: halo `ThumbnailEndpoint` 42→0 false callers (`risk:high`→`low`);
|
|
63
|
+
shopizer `ProductService` real callers preserved (32 direct unchanged) while 45 phantom
|
|
64
|
+
`Mapper` callers — confirmed to have zero references to the queried symbol — were dropped.
|
|
65
|
+
2 regression tests (`TestHubInterfaceOverExpansion`): sibling implementors excluded, real
|
|
66
|
+
`injects` caller through a shared interface preserved.
|
|
67
|
+
|
|
68
|
+
Complements CH-005: that guard handles *external* supertypes (under-reporting); CH-006
|
|
69
|
+
handles *in-repo high-fanout* supertypes (over-reporting). False positives are worse than
|
|
70
|
+
an empty result — they actively misdirect a change — so this is the higher-leverage half.
|
|
71
|
+
|
|
72
|
+
## [1.47.0] — 2026-06-17
|
|
73
|
+
|
|
74
|
+
### Added
|
|
75
|
+
- **CH-005 — framework/external-interface DI blind-spot detection in `impact-chain`.**
|
|
76
|
+
When a queried class has an empty blast radius *and* implements/extends an external
|
|
77
|
+
framework supertype (one the in-repo `ImplementationGraph` deliberately drops — e.g.
|
|
78
|
+
Spring Security's `RedirectStrategy`, a servlet `Filter`), `impact-chain` now positively
|
|
79
|
+
detects it instead of silently reporting `0 callers / risk:low` at `confidence=high`.
|
|
80
|
+
Such classes are wired by framework DI/config and invoked polymorphically through the
|
|
81
|
+
external type, so no in-repo edge names their methods — the empty result is an
|
|
82
|
+
unmodeled-edge blind spot, not proof of dead code. The query now:
|
|
83
|
+
- drops `confidence` to `low`,
|
|
84
|
+
- exposes `metadata.blind_spots` (`framework_di`) and `metadata.external_supertypes`,
|
|
85
|
+
- emits a `CH-005` warning pointing the agent to search DI/security/config wiring for
|
|
86
|
+
the supertype to recover the real callers.
|
|
87
|
+
|
|
88
|
+
Inert marker interfaces (`Serializable`, `Cloneable`, `Externalizable`) are excluded —
|
|
89
|
+
they carry no methods, so no polymorphic dispatch and no hidden blast radius.
|
|
90
|
+
|
|
91
|
+
This converts a dangerous false negative ("looks safe to change") into an honest "look
|
|
92
|
+
further" signal. It does **not** recover the real callers (they flow through framework
|
|
93
|
+
wiring the static call-graph never traverses); that is a separate, larger effort.
|
|
94
|
+
Mirrors the existing CH-003 value-type guard. Validated end-to-end on BroadleafCommerce
|
|
95
|
+
(`LocalRedirectStrategy` → flagged; `FieldDaoImpl` with 16 real callers → not flagged).
|
|
96
|
+
|
|
3
97
|
## [1.46.0] — 2026-06-16
|
|
4
98
|
|
|
5
99
|
### Added
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: sourcecode
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.50.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
|
-

|
|
44
44
|

|
|
45
45
|
|
|
46
46
|
---
|
|
@@ -368,6 +368,8 @@ sourcecode endpoints /path/to/repo --output endpoints.json
|
|
|
368
368
|
|
|
369
369
|
Extracts all Spring MVC (`@GetMapping`, `@PostMapping`, `@RequestMapping`, etc.) and JAX-RS (`@GET`, `@POST`, `@Path`) endpoint methods. Returns HTTP method, path, controller class, and handler method.
|
|
370
370
|
|
|
371
|
+
**Functional / WebFlux routing (honest limitation).** Routes registered via the functional DSL — `route().GET("/path", handler)` / `RouterFunction` / `CustomEndpoint`, common in reactive Spring apps — are **not** modeled (their real paths depend on `nest()`/group-version prefixes that can't be resolved statically). Rather than emit partial paths that would mislead, the output reports a `functional_routing` block (`files`, `route_registrations`, `modeled: false`) plus a warning. When the annotation surface is empty but functional routes exist, the warning explicitly tells you not to read it as "no endpoints". Annotation-based (MVC/JAX-RS) repos are unaffected.
|
|
372
|
+
|
|
371
373
|
**Custom security annotations.** Enterprise repos often guard endpoints with a bespoke annotation instead of `@PreAuthorize`/`@Secured`. Drop a `sourcecode.config.json` at the repo root to teach the scanner about it — otherwise those endpoints report `policy: "none_detected"`:
|
|
372
374
|
|
|
373
375
|
```json
|
|
@@ -435,6 +437,12 @@ Unlike `impact` (which traces the caller graph), `impact-chain` builds on the Sp
|
|
|
435
437
|
| `security_surfaces` | Per-endpoint security policy + SEC finding IDs |
|
|
436
438
|
| `impact_findings` | TX-001..005 and SEC-001..003 findings that touch the call chain |
|
|
437
439
|
| `risk_level` | `critical` \| `high` \| `medium` \| `low` |
|
|
440
|
+
| `confidence` | `high` \| `medium` \| `low` — `low` on a detected blind spot, `medium` on partial resolution or capped traversal. Informational interface↔impl expansion notices do **not** lower it, so a clean resolved query stays `high`. |
|
|
441
|
+
| `metadata.blind_spots` | `framework_di` and/or `value_type` when an empty result is unmodeled-edge driven, not real dead code |
|
|
442
|
+
|
|
443
|
+
**Framework/DI blind spot (CH-005).** An empty blast radius is ambiguous: genuinely unused, or invoked through an edge the static graph does not model. When the target class implements/extends an **external** framework type (e.g. Spring Security's `RedirectStrategy`, a servlet `Filter`) it is typically wired by framework DI/config and invoked polymorphically — no in-repo edge names its methods, so `direct_callers` is `0`. Rather than report that as `risk:low` at high confidence (a dangerous false negative that reads as "safe to change"), `impact-chain` detects the external supertype, drops `confidence` to `low`, lists it in `metadata.external_supertypes`, and emits a `CH-005` warning telling you to search the DI/security/config wiring for the supertype. Inert markers (`Serializable`, `Cloneable`) are excluded.
|
|
444
|
+
|
|
445
|
+
**Caller precision (CH-006).** `implements`/`extends` are structural type declarations, not calls — so they are excluded from the caller graph. Querying a class that implements a high-fanout interface (e.g. a 40-implementor `CustomEndpoint` or a shared `Mapper<E,D>` base) does **not** report its sibling implementors as callers; only real `injects`/`calls` edges count. This prevents a leaf class from being inflated to a large false blast radius.
|
|
438
446
|
|
|
439
447
|
**Event topology** — query the publisher/consumer graph for a Spring event class:
|
|
440
448
|
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
**Persistent structural context and ultra-fast repeated analysis for AI coding agents.**
|
|
4
4
|
|
|
5
|
-

|
|
6
6
|

|
|
7
7
|
|
|
8
8
|
---
|
|
@@ -330,6 +330,8 @@ sourcecode endpoints /path/to/repo --output endpoints.json
|
|
|
330
330
|
|
|
331
331
|
Extracts all Spring MVC (`@GetMapping`, `@PostMapping`, `@RequestMapping`, etc.) and JAX-RS (`@GET`, `@POST`, `@Path`) endpoint methods. Returns HTTP method, path, controller class, and handler method.
|
|
332
332
|
|
|
333
|
+
**Functional / WebFlux routing (honest limitation).** Routes registered via the functional DSL — `route().GET("/path", handler)` / `RouterFunction` / `CustomEndpoint`, common in reactive Spring apps — are **not** modeled (their real paths depend on `nest()`/group-version prefixes that can't be resolved statically). Rather than emit partial paths that would mislead, the output reports a `functional_routing` block (`files`, `route_registrations`, `modeled: false`) plus a warning. When the annotation surface is empty but functional routes exist, the warning explicitly tells you not to read it as "no endpoints". Annotation-based (MVC/JAX-RS) repos are unaffected.
|
|
334
|
+
|
|
333
335
|
**Custom security annotations.** Enterprise repos often guard endpoints with a bespoke annotation instead of `@PreAuthorize`/`@Secured`. Drop a `sourcecode.config.json` at the repo root to teach the scanner about it — otherwise those endpoints report `policy: "none_detected"`:
|
|
334
336
|
|
|
335
337
|
```json
|
|
@@ -397,6 +399,12 @@ Unlike `impact` (which traces the caller graph), `impact-chain` builds on the Sp
|
|
|
397
399
|
| `security_surfaces` | Per-endpoint security policy + SEC finding IDs |
|
|
398
400
|
| `impact_findings` | TX-001..005 and SEC-001..003 findings that touch the call chain |
|
|
399
401
|
| `risk_level` | `critical` \| `high` \| `medium` \| `low` |
|
|
402
|
+
| `confidence` | `high` \| `medium` \| `low` — `low` on a detected blind spot, `medium` on partial resolution or capped traversal. Informational interface↔impl expansion notices do **not** lower it, so a clean resolved query stays `high`. |
|
|
403
|
+
| `metadata.blind_spots` | `framework_di` and/or `value_type` when an empty result is unmodeled-edge driven, not real dead code |
|
|
404
|
+
|
|
405
|
+
**Framework/DI blind spot (CH-005).** An empty blast radius is ambiguous: genuinely unused, or invoked through an edge the static graph does not model. When the target class implements/extends an **external** framework type (e.g. Spring Security's `RedirectStrategy`, a servlet `Filter`) it is typically wired by framework DI/config and invoked polymorphically — no in-repo edge names its methods, so `direct_callers` is `0`. Rather than report that as `risk:low` at high confidence (a dangerous false negative that reads as "safe to change"), `impact-chain` detects the external supertype, drops `confidence` to `low`, lists it in `metadata.external_supertypes`, and emits a `CH-005` warning telling you to search the DI/security/config wiring for the supertype. Inert markers (`Serializable`, `Cloneable`) are excluded.
|
|
406
|
+
|
|
407
|
+
**Caller precision (CH-006).** `implements`/`extends` are structural type declarations, not calls — so they are excluded from the caller graph. Querying a class that implements a high-fanout interface (e.g. a 40-implementor `CustomEndpoint` or a shared `Mapper<E,D>` base) does **not** report its sibling implementors as callers; only real `injects`/`calls` edges count. This prevents a leaf class from being inflated to a large false blast radius.
|
|
400
408
|
|
|
401
409
|
**Event topology** — query the publisher/consumer graph for a Spring event class:
|
|
402
410
|
|
|
@@ -3829,6 +3829,18 @@ def extract_java_endpoints(root: Path) -> "dict[str, Any]":
|
|
|
3829
3829
|
all_symbols: list[SymbolRecord] = []
|
|
3830
3830
|
extends_map: dict[str, str] = {}
|
|
3831
3831
|
|
|
3832
|
+
# F-2: detect WebFlux / functional (RouterFunction) routing. Such routes register
|
|
3833
|
+
# via a fluent DSL (route().GET("/path", handler)) instead of @RequestMapping
|
|
3834
|
+
# annotations, so the annotation-based surface built below does not see them. We
|
|
3835
|
+
# deliberately do NOT synthesize endpoint entries: the literal paths here are
|
|
3836
|
+
# relative (the real path includes nest()/group-version prefixes that cannot be
|
|
3837
|
+
# resolved statically), and emitting partial paths would mislead more than an empty
|
|
3838
|
+
# surface. Instead we count them and surface an honest limitation so a zero/partial
|
|
3839
|
+
# annotation surface is never read as "this app exposes no endpoints".
|
|
3840
|
+
_FN_ROUTE_RE = _re.compile(r'\.(?:GET|POST|PUT|DELETE|PATCH|HEAD|OPTIONS)\s*\(\s*"')
|
|
3841
|
+
_fn_route_files: set[str] = set()
|
|
3842
|
+
_fn_route_count = 0
|
|
3843
|
+
|
|
3832
3844
|
for jf in java_files:
|
|
3833
3845
|
try:
|
|
3834
3846
|
source = jf.read_text(encoding="utf-8", errors="replace")
|
|
@@ -3838,6 +3850,11 @@ def extract_java_endpoints(root: Path) -> "dict[str, Any]":
|
|
|
3838
3850
|
rel = str(jf.relative_to(root)).replace("\\", "/")
|
|
3839
3851
|
except ValueError:
|
|
3840
3852
|
rel = str(jf).replace("\\", "/")
|
|
3853
|
+
if "RouterFunction" in source or "RequestPredicates" in source:
|
|
3854
|
+
_fn_hits = len(_FN_ROUTE_RE.findall(source))
|
|
3855
|
+
if _fn_hits:
|
|
3856
|
+
_fn_route_files.add(rel)
|
|
3857
|
+
_fn_route_count += _fn_hits
|
|
3841
3858
|
_, symbols, _ = _extract_symbols(source, rel, extra_capture=_extra_capture)
|
|
3842
3859
|
for sym in symbols:
|
|
3843
3860
|
all_symbols.append(sym)
|
|
@@ -4045,6 +4062,26 @@ def extract_java_endpoints(root: Path) -> "dict[str, Any]":
|
|
|
4045
4062
|
result["spec_sourced_endpoints"] = len(_spec_endpoints)
|
|
4046
4063
|
if _openapi_spec_path:
|
|
4047
4064
|
result["openapi_spec"] = _openapi_spec_path
|
|
4065
|
+
# F-2: surface functional/RouterFunction routing as an honest limitation. Not modeled
|
|
4066
|
+
# in the surface above; counted so an empty/partial annotation surface is not misread.
|
|
4067
|
+
if _fn_route_count:
|
|
4068
|
+
result["functional_routing"] = {
|
|
4069
|
+
"files": len(_fn_route_files),
|
|
4070
|
+
"route_registrations": _fn_route_count,
|
|
4071
|
+
"modeled": False,
|
|
4072
|
+
}
|
|
4073
|
+
_fr_msg = (
|
|
4074
|
+
f"{_fn_route_count} functional route registration(s) across "
|
|
4075
|
+
f"{len(_fn_route_files)} file(s) use WebFlux/RouterFunction routing "
|
|
4076
|
+
f'(route().GET("/path", handler)), which is NOT modeled here — this surface '
|
|
4077
|
+
f"covers annotation-based (@RequestMapping/@GetMapping) endpoints only."
|
|
4078
|
+
)
|
|
4079
|
+
if not endpoints:
|
|
4080
|
+
_fr_msg += (
|
|
4081
|
+
" This surface is EMPTY despite functional routes being present — "
|
|
4082
|
+
"do NOT read it as 'no endpoints'; the app's HTTP surface is unmodeled."
|
|
4083
|
+
)
|
|
4084
|
+
result.setdefault("warnings", []).append(_fr_msg)
|
|
4048
4085
|
return result
|
|
4049
4086
|
|
|
4050
4087
|
|
|
@@ -44,7 +44,18 @@ _SCHEMA_VERSION = "1.0"
|
|
|
44
44
|
# Only appears on class nodes, never method nodes. Including it
|
|
45
45
|
# chains through DTOs and entities that merely reference the service
|
|
46
46
|
# type, inflating caller counts without semantic value.
|
|
47
|
-
|
|
47
|
+
# implements / — CH-006: structural type declarations, NOT calls. The reverse edge
|
|
48
|
+
# extends on an interface/base lists its implementors/subclasses; an
|
|
49
|
+
# implementor does not *call* the interface by virtue of implementing
|
|
50
|
+
# it. Traversing these attributes every SIBLING implementor of a shared
|
|
51
|
+
# interface as a "caller". On a high-fanout in-repo hub interface (e.g.
|
|
52
|
+
# halo's CustomEndpoint, 43 implementors) this turned a leaf endpoint
|
|
53
|
+
# into 42 false direct callers / risk:high. Interface→impl expansion that
|
|
54
|
+
# IS wanted (CH-001a/b) flows through ImplementationGraph indices, not
|
|
55
|
+
# through these reverse-graph edges, so excluding them here is loss-free.
|
|
56
|
+
_SKIP_EDGE_TYPES: frozenset[str] = frozenset(
|
|
57
|
+
{"contained_in", "imports", "implements", "extends"}
|
|
58
|
+
)
|
|
48
59
|
|
|
49
60
|
# Max BFS depth guard — caller growth is bounded per _bfs_callers
|
|
50
61
|
_BFS_DEFAULT_DEPTH = 4
|
|
@@ -148,6 +159,68 @@ _STEREOTYPE_ANNOTATIONS = frozenset({
|
|
|
148
159
|
_VALUE_TYPE_KINDS = frozenset({"class", "enum", "record"})
|
|
149
160
|
|
|
150
161
|
|
|
162
|
+
# ---------------------------------------------------------------------------
|
|
163
|
+
# CH-005 — framework/external-interface DI blind-spot detection
|
|
164
|
+
# ---------------------------------------------------------------------------
|
|
165
|
+
# When a class implements/extends a type that is NOT an in-repo symbol (a
|
|
166
|
+
# framework or library supertype — e.g. Spring Security's RedirectStrategy, a
|
|
167
|
+
# servlet Filter, a JPA base), the class is typically invoked polymorphically
|
|
168
|
+
# *through that external type* and wired by framework DI/config. No in-repo call
|
|
169
|
+
# edge ever names the impl's own method, and ImplementationGraph.build()
|
|
170
|
+
# deliberately drops external supertypes (cir_graphs: `to_fqn not in
|
|
171
|
+
# known_symbols` → skipped), so CH-001b cannot expand to the interface. Result:
|
|
172
|
+
# impact-chain reports 0 callers / risk:low at confidence=high — a dangerous
|
|
173
|
+
# false negative, since the real blast radius flows through framework wiring the
|
|
174
|
+
# static call-graph never traverses. Detect the external supertype positively,
|
|
175
|
+
# warn, and downgrade confidence (parallel to the CH-003 value-type guard).
|
|
176
|
+
#
|
|
177
|
+
# Inert marker interfaces carry no methods → no polymorphic dispatch → no hidden
|
|
178
|
+
# blast radius, so they are excluded to avoid firing on plain Serializable DTOs.
|
|
179
|
+
_INERT_MARKER_SUPERTYPES = frozenset({
|
|
180
|
+
"Serializable", "java.io.Serializable",
|
|
181
|
+
"Cloneable", "java.lang.Cloneable",
|
|
182
|
+
"Externalizable", "java.io.Externalizable",
|
|
183
|
+
})
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
def _external_supertypes(cir, class_fqn: str) -> list[str]:
|
|
187
|
+
"""Return supertypes of class_fqn that are NOT in-repo symbols.
|
|
188
|
+
|
|
189
|
+
Reads raw implements/extends edges from cir.dependencies and keeps only those
|
|
190
|
+
whose target cannot be resolved to a single in-repo class (i.e. framework /
|
|
191
|
+
library types). Mirrors ImplementationGraph's resolution rules (exact FQN
|
|
192
|
+
match, then unambiguous simple-name match) so the internal/external split is
|
|
193
|
+
identical. Inert marker interfaces are dropped. Order-preserving, deduped.
|
|
194
|
+
"""
|
|
195
|
+
deps = getattr(cir, "dependencies", None) or []
|
|
196
|
+
known: set[str] = set(getattr(cir, "symbols", None) or [])
|
|
197
|
+
simple_to_fqn: dict[str, list[str]] = {}
|
|
198
|
+
for sym in known:
|
|
199
|
+
if "#" not in sym and "." in sym:
|
|
200
|
+
simple_to_fqn.setdefault(sym.rsplit(".", 1)[1], []).append(sym)
|
|
201
|
+
|
|
202
|
+
external: list[str] = []
|
|
203
|
+
for edge in deps:
|
|
204
|
+
if edge.get("type") not in ("implements", "extends"):
|
|
205
|
+
continue
|
|
206
|
+
frm = normalize_owner_fqn((edge.get("from") or "").strip())
|
|
207
|
+
if frm != class_fqn:
|
|
208
|
+
continue
|
|
209
|
+
to = (edge.get("to") or "").strip()
|
|
210
|
+
if not to or ">" in to or "<" in to:
|
|
211
|
+
continue
|
|
212
|
+
simple = to.rsplit(".", 1)[1] if "." in to else to
|
|
213
|
+
if simple in _INERT_MARKER_SUPERTYPES or to in _INERT_MARKER_SUPERTYPES:
|
|
214
|
+
continue
|
|
215
|
+
# Internal if it resolves to exactly one in-repo class (exact or simple-name).
|
|
216
|
+
if to in known:
|
|
217
|
+
continue
|
|
218
|
+
if len(simple_to_fqn.get(simple, [])) == 1:
|
|
219
|
+
continue
|
|
220
|
+
external.append(to)
|
|
221
|
+
return list(dict.fromkeys(external))
|
|
222
|
+
|
|
223
|
+
|
|
151
224
|
def _is_unmodeled_value_type(cir, class_fqn: str, model) -> bool:
|
|
152
225
|
"""True iff class_fqn is positively a plain value/DTO type whose blast radius
|
|
153
226
|
flows only through type-usage edges the impact graph does not model.
|
|
@@ -605,6 +678,12 @@ class ImpactOrchestrator:
|
|
|
605
678
|
t0 = time.monotonic()
|
|
606
679
|
depth = max(1, min(depth, _BFS_HARD_LIMIT))
|
|
607
680
|
warnings: list[str] = []
|
|
681
|
+
# F-1: not every warning degrades confidence. The CH-001a/b interface↔impl
|
|
682
|
+
# expansion notices are INFORMATIONAL (they describe normal, correct operation)
|
|
683
|
+
# and previously forced every Spring interface/impl query — the common case — down
|
|
684
|
+
# to confidence=medium permanently. Only genuinely degrading conditions (capped
|
|
685
|
+
# traversal) set this flag; resolution=="partial" is handled separately below.
|
|
686
|
+
confidence_reducing = False
|
|
608
687
|
|
|
609
688
|
# ── 1. Resolve symbol ─────────────────────────────────────────────
|
|
610
689
|
resolution, seed_fqns, sym_warnings = _resolve_symbol(symbol, cir.symbols)
|
|
@@ -702,6 +781,7 @@ class ImpactOrchestrator:
|
|
|
702
781
|
"Hub-class guard active: symbol has > 500 direct callers — "
|
|
703
782
|
"indirect caller traversal capped at depth=1."
|
|
704
783
|
)
|
|
784
|
+
confidence_reducing = True # capped traversal → result is incomplete
|
|
705
785
|
|
|
706
786
|
# ── 3. Endpoints affected ─────────────────────────────────────────
|
|
707
787
|
all_callers = direct_callers + indirect_callers
|
|
@@ -750,16 +830,38 @@ class ImpactOrchestrator:
|
|
|
750
830
|
impact_findings_raw,
|
|
751
831
|
)
|
|
752
832
|
|
|
753
|
-
#
|
|
754
|
-
#
|
|
833
|
+
# Empty blast radius is ambiguous: genuinely-unused code OR an unmodeled-edge
|
|
834
|
+
# blind spot. Two positively-detected blind spots reclassify it from a
|
|
835
|
+
# high-confidence "safe to change" into a low-confidence "look further".
|
|
755
836
|
empty_blast = (
|
|
756
837
|
not direct_callers and not indirect_callers
|
|
757
838
|
and not endpoints_affected and not subtype_classes_added
|
|
758
839
|
)
|
|
840
|
+
class_level_seed = "#" not in resolved_symbol and resolution != "not_found"
|
|
841
|
+
|
|
842
|
+
# CH-005: framework/external-interface DI blind spot. Checked first because
|
|
843
|
+
# its diagnosis (polymorphic invocation via an external supertype + framework
|
|
844
|
+
# wiring) is more specific than the value-type fallback for the same symbol.
|
|
845
|
+
external_supertypes: list[str] = []
|
|
846
|
+
if empty_blast and class_level_seed:
|
|
847
|
+
external_supertypes = _external_supertypes(cir, resolved_symbol)
|
|
848
|
+
framework_di_blind_spot = bool(external_supertypes)
|
|
849
|
+
if framework_di_blind_spot:
|
|
850
|
+
warnings.append(
|
|
851
|
+
"Framework/external-interface DI blind spot (CH-005): this class "
|
|
852
|
+
"implements/extends external type(s) [" + ", ".join(external_supertypes)
|
|
853
|
+
+ "] and is likely invoked polymorphically through them and wired by "
|
|
854
|
+
"framework DI/config. The static call-graph has no in-repo edge naming "
|
|
855
|
+
"this class's methods, so 0 callers is NOT proof it is unused — search "
|
|
856
|
+
"DI/security/config wiring for the supertype to find the real callers."
|
|
857
|
+
)
|
|
858
|
+
|
|
859
|
+
# CH-003: empty blast radius on a positively-identified value/DTO type is a
|
|
860
|
+
# type-usage blind spot, not proof of dead code — warn + drop confidence.
|
|
759
861
|
value_type_blind_spot = (
|
|
760
862
|
empty_blast
|
|
761
|
-
and
|
|
762
|
-
and
|
|
863
|
+
and class_level_seed
|
|
864
|
+
and not framework_di_blind_spot
|
|
763
865
|
and _is_unmodeled_value_type(cir, resolved_symbol, model)
|
|
764
866
|
)
|
|
765
867
|
if value_type_blind_spot:
|
|
@@ -773,9 +875,9 @@ class ImpactOrchestrator:
|
|
|
773
875
|
confidence: str
|
|
774
876
|
if resolution == "not_found":
|
|
775
877
|
confidence = "low"
|
|
776
|
-
elif value_type_blind_spot:
|
|
878
|
+
elif framework_di_blind_spot or value_type_blind_spot:
|
|
777
879
|
confidence = "low"
|
|
778
|
-
elif resolution == "partial" or
|
|
880
|
+
elif resolution == "partial" or confidence_reducing:
|
|
779
881
|
confidence = "medium"
|
|
780
882
|
else:
|
|
781
883
|
confidence = "high"
|
|
@@ -803,6 +905,11 @@ class ImpactOrchestrator:
|
|
|
803
905
|
"risk_score": risk_score,
|
|
804
906
|
"model_build_time_ms": model.build_time_ms,
|
|
805
907
|
"query_time_ms": elapsed_ms,
|
|
908
|
+
"blind_spots": (
|
|
909
|
+
(["framework_di"] if framework_di_blind_spot else [])
|
|
910
|
+
+ (["value_type"] if value_type_blind_spot else [])
|
|
911
|
+
),
|
|
912
|
+
"external_supertypes": external_supertypes,
|
|
806
913
|
},
|
|
807
914
|
)
|
|
808
915
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|