sourcecode 2.5.13__tar.gz → 2.5.16__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-2.5.13 → sourcecode-2.5.16}/CHANGELOG.md +151 -7
- {sourcecode-2.5.13 → sourcecode-2.5.16}/PKG-INFO +1 -1
- {sourcecode-2.5.13 → sourcecode-2.5.16}/pyproject.toml +1 -1
- sourcecode-2.5.16/scripts/perf_harness.py +252 -0
- {sourcecode-2.5.13 → sourcecode-2.5.16}/src/sourcecode/__init__.py +1 -1
- {sourcecode-2.5.13 → sourcecode-2.5.16}/src/sourcecode/ast_extractor.py +12 -11
- {sourcecode-2.5.13 → sourcecode-2.5.16}/src/sourcecode/canonical_ir.py +8 -0
- {sourcecode-2.5.13 → sourcecode-2.5.16}/src/sourcecode/classifier.py +80 -52
- {sourcecode-2.5.13 → sourcecode-2.5.16}/src/sourcecode/cli.py +18 -0
- {sourcecode-2.5.13 → sourcecode-2.5.16}/src/sourcecode/context_graph.py +35 -0
- {sourcecode-2.5.13 → sourcecode-2.5.16}/src/sourcecode/detectors/java.py +35 -1
- {sourcecode-2.5.13 → sourcecode-2.5.16}/src/sourcecode/endpoint_metrics.py +4 -1
- {sourcecode-2.5.13 → sourcecode-2.5.16}/src/sourcecode/path_filters.py +31 -0
- sourcecode-2.5.16/src/sourcecode/perf.py +268 -0
- {sourcecode-2.5.13 → sourcecode-2.5.16}/src/sourcecode/repository_ir.py +39 -1
- {sourcecode-2.5.13 → sourcecode-2.5.16}/src/sourcecode/serializer.py +35 -2
- {sourcecode-2.5.13 → sourcecode-2.5.16}/src/sourcecode/summarizer.py +7 -17
- {sourcecode-2.5.13 → sourcecode-2.5.16}/.github/workflows/build-windows.yml +0 -0
- {sourcecode-2.5.13 → sourcecode-2.5.16}/.gitignore +0 -0
- {sourcecode-2.5.13 → sourcecode-2.5.16}/.ruff.toml +0 -0
- {sourcecode-2.5.13 → sourcecode-2.5.16}/CONTRIBUTING.md +0 -0
- {sourcecode-2.5.13 → sourcecode-2.5.16}/LICENSE +0 -0
- {sourcecode-2.5.13 → sourcecode-2.5.16}/README.md +0 -0
- {sourcecode-2.5.13 → sourcecode-2.5.16}/SECURITY.md +0 -0
- {sourcecode-2.5.13 → sourcecode-2.5.16}/raw +0 -0
- {sourcecode-2.5.13 → sourcecode-2.5.16}/scripts/compare_integration_engines.py +0 -0
- {sourcecode-2.5.13 → sourcecode-2.5.16}/scripts/customer_smoke_test.sh +0 -0
- {sourcecode-2.5.13 → sourcecode-2.5.16}/scripts/generate_jdk_exports.py +0 -0
- {sourcecode-2.5.13 → sourcecode-2.5.16}/src/sourcecode/adaptive_scanner.py +0 -0
- {sourcecode-2.5.13 → sourcecode-2.5.16}/src/sourcecode/archetype.py +0 -0
- {sourcecode-2.5.13 → sourcecode-2.5.16}/src/sourcecode/architecture_analyzer.py +0 -0
- {sourcecode-2.5.13 → sourcecode-2.5.16}/src/sourcecode/architecture_summary.py +0 -0
- {sourcecode-2.5.13 → sourcecode-2.5.16}/src/sourcecode/cache.py +0 -0
- {sourcecode-2.5.13 → sourcecode-2.5.16}/src/sourcecode/call_surface.py +0 -0
- {sourcecode-2.5.13 → sourcecode-2.5.16}/src/sourcecode/caller_metrics.py +0 -0
- {sourcecode-2.5.13 → sourcecode-2.5.16}/src/sourcecode/cir_graphs.py +0 -0
- {sourcecode-2.5.13 → sourcecode-2.5.16}/src/sourcecode/code_notes_analyzer.py +0 -0
- {sourcecode-2.5.13 → sourcecode-2.5.16}/src/sourcecode/confidence_analyzer.py +0 -0
- {sourcecode-2.5.13 → sourcecode-2.5.16}/src/sourcecode/context_cache.py +0 -0
- {sourcecode-2.5.13 → sourcecode-2.5.16}/src/sourcecode/context_scorer.py +0 -0
- {sourcecode-2.5.13 → sourcecode-2.5.16}/src/sourcecode/context_summarizer.py +0 -0
- {sourcecode-2.5.13 → sourcecode-2.5.16}/src/sourcecode/contract_model.py +0 -0
- {sourcecode-2.5.13 → sourcecode-2.5.16}/src/sourcecode/contract_pipeline.py +0 -0
- {sourcecode-2.5.13 → sourcecode-2.5.16}/src/sourcecode/coverage_parser.py +0 -0
- {sourcecode-2.5.13 → sourcecode-2.5.16}/src/sourcecode/dependency_analyzer.py +0 -0
- {sourcecode-2.5.13 → sourcecode-2.5.16}/src/sourcecode/detectors/__init__.py +0 -0
- {sourcecode-2.5.13 → sourcecode-2.5.16}/src/sourcecode/detectors/base.py +0 -0
- {sourcecode-2.5.13 → sourcecode-2.5.16}/src/sourcecode/detectors/csproj_parser.py +0 -0
- {sourcecode-2.5.13 → sourcecode-2.5.16}/src/sourcecode/detectors/dart.py +0 -0
- {sourcecode-2.5.13 → sourcecode-2.5.16}/src/sourcecode/detectors/dotnet.py +0 -0
- {sourcecode-2.5.13 → sourcecode-2.5.16}/src/sourcecode/detectors/elixir.py +0 -0
- {sourcecode-2.5.13 → sourcecode-2.5.16}/src/sourcecode/detectors/go.py +0 -0
- {sourcecode-2.5.13 → sourcecode-2.5.16}/src/sourcecode/detectors/heuristic.py +0 -0
- {sourcecode-2.5.13 → sourcecode-2.5.16}/src/sourcecode/detectors/hybrid.py +0 -0
- {sourcecode-2.5.13 → sourcecode-2.5.16}/src/sourcecode/detectors/jvm_ext.py +0 -0
- {sourcecode-2.5.13 → sourcecode-2.5.16}/src/sourcecode/detectors/nodejs.py +0 -0
- {sourcecode-2.5.13 → sourcecode-2.5.16}/src/sourcecode/detectors/parsers.py +0 -0
- {sourcecode-2.5.13 → sourcecode-2.5.16}/src/sourcecode/detectors/php.py +0 -0
- {sourcecode-2.5.13 → sourcecode-2.5.16}/src/sourcecode/detectors/project.py +0 -0
- {sourcecode-2.5.13 → sourcecode-2.5.16}/src/sourcecode/detectors/python.py +0 -0
- {sourcecode-2.5.13 → sourcecode-2.5.16}/src/sourcecode/detectors/ruby.py +0 -0
- {sourcecode-2.5.13 → sourcecode-2.5.16}/src/sourcecode/detectors/rust.py +0 -0
- {sourcecode-2.5.13 → sourcecode-2.5.16}/src/sourcecode/detectors/systems.py +0 -0
- {sourcecode-2.5.13 → sourcecode-2.5.16}/src/sourcecode/detectors/terraform.py +0 -0
- {sourcecode-2.5.13 → sourcecode-2.5.16}/src/sourcecode/detectors/tooling.py +0 -0
- {sourcecode-2.5.13 → sourcecode-2.5.16}/src/sourcecode/doc_analyzer.py +0 -0
- {sourcecode-2.5.13 → sourcecode-2.5.16}/src/sourcecode/dynamic_argument_surface.py +0 -0
- {sourcecode-2.5.13 → sourcecode-2.5.16}/src/sourcecode/endpoint_literals.py +0 -0
- {sourcecode-2.5.13 → sourcecode-2.5.16}/src/sourcecode/entrypoint_classifier.py +0 -0
- {sourcecode-2.5.13 → sourcecode-2.5.16}/src/sourcecode/env_analyzer.py +0 -0
- {sourcecode-2.5.13 → sourcecode-2.5.16}/src/sourcecode/error_schema.py +0 -0
- {sourcecode-2.5.13 → sourcecode-2.5.16}/src/sourcecode/evidence_provider.py +0 -0
- {sourcecode-2.5.13 → sourcecode-2.5.16}/src/sourcecode/explain.py +0 -0
- {sourcecode-2.5.13 → sourcecode-2.5.16}/src/sourcecode/file_chunker.py +0 -0
- {sourcecode-2.5.13 → sourcecode-2.5.16}/src/sourcecode/file_classifier.py +0 -0
- {sourcecode-2.5.13 → sourcecode-2.5.16}/src/sourcecode/format_contract.py +0 -0
- {sourcecode-2.5.13 → sourcecode-2.5.16}/src/sourcecode/fqn_utils.py +0 -0
- {sourcecode-2.5.13 → sourcecode-2.5.16}/src/sourcecode/git_analyzer.py +0 -0
- {sourcecode-2.5.13 → sourcecode-2.5.16}/src/sourcecode/graph_analyzer.py +0 -0
- {sourcecode-2.5.13 → sourcecode-2.5.16}/src/sourcecode/graph_evidence.py +0 -0
- {sourcecode-2.5.13 → sourcecode-2.5.16}/src/sourcecode/hibernate_strat.py +0 -0
- {sourcecode-2.5.13 → sourcecode-2.5.16}/src/sourcecode/jdk_exports.py +0 -0
- {sourcecode-2.5.13 → sourcecode-2.5.16}/src/sourcecode/license.py +0 -0
- {sourcecode-2.5.13 → sourcecode-2.5.16}/src/sourcecode/mcp/__init__.py +0 -0
- {sourcecode-2.5.13 → sourcecode-2.5.16}/src/sourcecode/mcp/onboarding/__init__.py +0 -0
- {sourcecode-2.5.13 → sourcecode-2.5.16}/src/sourcecode/mcp/onboarding/applier.py +0 -0
- {sourcecode-2.5.13 → sourcecode-2.5.16}/src/sourcecode/mcp/onboarding/backup.py +0 -0
- {sourcecode-2.5.13 → sourcecode-2.5.16}/src/sourcecode/mcp/onboarding/detector.py +0 -0
- {sourcecode-2.5.13 → sourcecode-2.5.16}/src/sourcecode/mcp/onboarding/planner.py +0 -0
- {sourcecode-2.5.13 → sourcecode-2.5.16}/src/sourcecode/mcp/orchestrator.py +0 -0
- {sourcecode-2.5.13 → sourcecode-2.5.16}/src/sourcecode/mcp/registry.py +0 -0
- {sourcecode-2.5.13 → sourcecode-2.5.16}/src/sourcecode/mcp/runner.py +0 -0
- {sourcecode-2.5.13 → sourcecode-2.5.16}/src/sourcecode/mcp/server.py +0 -0
- {sourcecode-2.5.13 → sourcecode-2.5.16}/src/sourcecode/mcp_nudge.py +0 -0
- {sourcecode-2.5.13 → sourcecode-2.5.16}/src/sourcecode/metrics_analyzer.py +0 -0
- {sourcecode-2.5.13 → sourcecode-2.5.16}/src/sourcecode/migrate_check.py +0 -0
- {sourcecode-2.5.13 → sourcecode-2.5.16}/src/sourcecode/openapi_surface.py +0 -0
- {sourcecode-2.5.13 → sourcecode-2.5.16}/src/sourcecode/output_budget.py +0 -0
- {sourcecode-2.5.13 → sourcecode-2.5.16}/src/sourcecode/pr_comment_renderer.py +0 -0
- {sourcecode-2.5.13 → sourcecode-2.5.16}/src/sourcecode/pr_impact.py +0 -0
- {sourcecode-2.5.13 → sourcecode-2.5.16}/src/sourcecode/prepare_context.py +0 -0
- {sourcecode-2.5.13 → sourcecode-2.5.16}/src/sourcecode/progress.py +0 -0
- {sourcecode-2.5.13 → sourcecode-2.5.16}/src/sourcecode/ranking_engine.py +0 -0
- {sourcecode-2.5.13 → sourcecode-2.5.16}/src/sourcecode/reconciliation.py +0 -0
- {sourcecode-2.5.13 → sourcecode-2.5.16}/src/sourcecode/redactor.py +0 -0
- {sourcecode-2.5.13 → sourcecode-2.5.16}/src/sourcecode/relevance_scorer.py +0 -0
- {sourcecode-2.5.13 → sourcecode-2.5.16}/src/sourcecode/rename_refactor.py +0 -0
- {sourcecode-2.5.13 → sourcecode-2.5.16}/src/sourcecode/repo_classifier.py +0 -0
- {sourcecode-2.5.13 → sourcecode-2.5.16}/src/sourcecode/retrieval/__init__.py +0 -0
- {sourcecode-2.5.13 → sourcecode-2.5.16}/src/sourcecode/retrieval/context.py +0 -0
- {sourcecode-2.5.13 → sourcecode-2.5.16}/src/sourcecode/retrieval/errors.py +0 -0
- {sourcecode-2.5.13 → sourcecode-2.5.16}/src/sourcecode/retrieval/executor.py +0 -0
- {sourcecode-2.5.13 → sourcecode-2.5.16}/src/sourcecode/retrieval/planner.py +0 -0
- {sourcecode-2.5.13 → sourcecode-2.5.16}/src/sourcecode/retrieval/query.py +0 -0
- {sourcecode-2.5.13 → sourcecode-2.5.16}/src/sourcecode/retrieval/request.py +0 -0
- {sourcecode-2.5.13 → sourcecode-2.5.16}/src/sourcecode/retrieval/resolution.py +0 -0
- {sourcecode-2.5.13 → sourcecode-2.5.16}/src/sourcecode/retrieval/result.py +0 -0
- {sourcecode-2.5.13 → sourcecode-2.5.16}/src/sourcecode/retrieval/retriever.py +0 -0
- {sourcecode-2.5.13 → sourcecode-2.5.16}/src/sourcecode/retrieval/runtime.py +0 -0
- {sourcecode-2.5.13 → sourcecode-2.5.16}/src/sourcecode/retrieval/steps.py +0 -0
- {sourcecode-2.5.13 → sourcecode-2.5.16}/src/sourcecode/retrieval/steps_endpoint.py +0 -0
- {sourcecode-2.5.13 → sourcecode-2.5.16}/src/sourcecode/retrieval/steps_graph.py +0 -0
- {sourcecode-2.5.13 → sourcecode-2.5.16}/src/sourcecode/retrieval/steps_impact.py +0 -0
- {sourcecode-2.5.13 → sourcecode-2.5.16}/src/sourcecode/retrieval/steps_intf.py +0 -0
- {sourcecode-2.5.13 → sourcecode-2.5.16}/src/sourcecode/retrieval/steps_struct.py +0 -0
- {sourcecode-2.5.13 → sourcecode-2.5.16}/src/sourcecode/retrieval/steps_txsec.py +0 -0
- {sourcecode-2.5.13 → sourcecode-2.5.16}/src/sourcecode/ris.py +0 -0
- {sourcecode-2.5.13 → sourcecode-2.5.16}/src/sourcecode/runtime_classifier.py +0 -0
- {sourcecode-2.5.13 → sourcecode-2.5.16}/src/sourcecode/scanner.py +0 -0
- {sourcecode-2.5.13 → sourcecode-2.5.16}/src/sourcecode/schema.py +0 -0
- {sourcecode-2.5.13 → sourcecode-2.5.16}/src/sourcecode/security_config.py +0 -0
- {sourcecode-2.5.13 → sourcecode-2.5.16}/src/sourcecode/security_posture.py +0 -0
- {sourcecode-2.5.13 → sourcecode-2.5.16}/src/sourcecode/semantic_analyzer.py +0 -0
- {sourcecode-2.5.13 → sourcecode-2.5.16}/src/sourcecode/semantic_impact_engine.py +0 -0
- {sourcecode-2.5.13 → sourcecode-2.5.16}/src/sourcecode/semantic_integration_engine.py +0 -0
- {sourcecode-2.5.13 → sourcecode-2.5.16}/src/sourcecode/semantic_services.py +0 -0
- {sourcecode-2.5.13 → sourcecode-2.5.16}/src/sourcecode/spring_event_topology.py +0 -0
- {sourcecode-2.5.13 → sourcecode-2.5.16}/src/sourcecode/spring_findings.py +0 -0
- {sourcecode-2.5.13 → sourcecode-2.5.16}/src/sourcecode/spring_impact.py +0 -0
- {sourcecode-2.5.13 → sourcecode-2.5.16}/src/sourcecode/spring_model.py +0 -0
- {sourcecode-2.5.13 → sourcecode-2.5.16}/src/sourcecode/spring_security_audit.py +0 -0
- {sourcecode-2.5.13 → sourcecode-2.5.16}/src/sourcecode/spring_semantic.py +0 -0
- {sourcecode-2.5.13 → sourcecode-2.5.16}/src/sourcecode/spring_tx_analyzer.py +0 -0
- {sourcecode-2.5.13 → sourcecode-2.5.16}/src/sourcecode/telemetry/__init__.py +0 -0
- {sourcecode-2.5.13 → sourcecode-2.5.16}/src/sourcecode/telemetry/config.py +0 -0
- {sourcecode-2.5.13 → sourcecode-2.5.16}/src/sourcecode/telemetry/consent.py +0 -0
- {sourcecode-2.5.13 → sourcecode-2.5.16}/src/sourcecode/telemetry/events.py +0 -0
- {sourcecode-2.5.13 → sourcecode-2.5.16}/src/sourcecode/telemetry/filters.py +0 -0
- {sourcecode-2.5.13 → sourcecode-2.5.16}/src/sourcecode/telemetry/transport.py +0 -0
- {sourcecode-2.5.13 → sourcecode-2.5.16}/src/sourcecode/tree_utils.py +0 -0
- {sourcecode-2.5.13 → sourcecode-2.5.16}/src/sourcecode/type_usage_surface.py +0 -0
- {sourcecode-2.5.13 → sourcecode-2.5.16}/src/sourcecode/validation_inference.py +0 -0
- {sourcecode-2.5.13 → sourcecode-2.5.16}/src/sourcecode/validation_surface.py +0 -0
- {sourcecode-2.5.13 → sourcecode-2.5.16}/src/sourcecode/version_check.py +0 -0
- {sourcecode-2.5.13 → sourcecode-2.5.16}/src/sourcecode/workspace.py +0 -0
- {sourcecode-2.5.13 → sourcecode-2.5.16}/supabase/functions/README.md +0 -0
- {sourcecode-2.5.13 → sourcecode-2.5.16}/supabase/functions/get-license/index.ts +0 -0
- {sourcecode-2.5.13 → sourcecode-2.5.16}/supabase/functions/lemonsqueezy-webhook/index.ts +0 -0
- {sourcecode-2.5.13 → sourcecode-2.5.16}/supabase/functions/telemetry/index.ts +0 -0
- {sourcecode-2.5.13 → sourcecode-2.5.16}/supabase/sql/license_event_ordering.sql +0 -0
- {sourcecode-2.5.13 → sourcecode-2.5.16}/supabase/sql/licensing_schema.sql +0 -0
- {sourcecode-2.5.13 → sourcecode-2.5.16}/supabase/sql/telemetry_events.sql +0 -0
|
@@ -1,14 +1,127 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [2.5.16] — 2026-07-19
|
|
4
|
+
|
|
5
|
+
**Performance campaign close-out: one real algorithmic win, cost-per-symbol
|
|
6
|
+
measurable, one candidate refuted by measurement.** The ADR-0006 harness localized
|
|
7
|
+
cold wall to the `analysis` phase (90–96% on every non-trivial repo); profiling
|
|
8
|
+
inside it found — and this release fixes — the one structural bottleneck, populates
|
|
9
|
+
the last empty baseline axes, and records a negative result that stopped a
|
|
10
|
+
non-winning change from landing.
|
|
11
|
+
|
|
12
|
+
### Performance
|
|
13
|
+
|
|
14
|
+
- **Killed an O(files × total_symbols) scan in per-file Java contract projection**
|
|
15
|
+
(`ast_extractor.py`, `context_graph.py`): `_java_contract_from_graph` ran once per
|
|
16
|
+
Java file and called the whole-repo `graph.symbols()`/`relations()` unfiltered, then
|
|
17
|
+
kept only its own file's rows. Added lazy once-built groupings
|
|
18
|
+
`ContextGraph.symbols_in_file(path)` / `relations_from(fqn)` (O(1) after build); the
|
|
19
|
+
projection now walks only edges sourced at its own types. Behavior-equivalent
|
|
20
|
+
(normalized old-vs-new on petclinic + keycloak; 1430 IR/spring/endpoint/impact tests
|
|
21
|
+
green). Cold wall p50, same host, speedup tracking file count as the O(files × N)
|
|
22
|
+
model predicts: neo4j 33.6→16.7s (2.0×), broadleaf 27.9→15.4s (1.8×),
|
|
23
|
+
keycloak 20.8→14.1s (1.5×), openmrs 7.9→5.8s (1.4×), ofbiz 19.4→15.0s (1.3×).
|
|
24
|
+
|
|
25
|
+
### Added
|
|
26
|
+
|
|
27
|
+
- **`repo.classes` / `repo.methods` populated in the perf baseline** from repo-ir's
|
|
28
|
+
symbol taxonomy (`repository_ir.py`, `scripts/perf_harness.py`): `analysis_meta` now
|
|
29
|
+
emits a deterministic `symbol_kinds` breakdown plus two rollups —
|
|
30
|
+
`classes` = class+interface+enum+annotation+record, `methods` = method+constructor —
|
|
31
|
+
and the harness reads them via one `repo-ir --summary-only` pass off the measured path
|
|
32
|
+
(never in `wall_ms`; repo-ir failure falls back to 0, an honest "unmeasured"). Closes
|
|
33
|
+
the last empty axes in the `perf-baseline-v1` schema, enabling cost-per-symbol.
|
|
34
|
+
|
|
35
|
+
### Notes
|
|
36
|
+
|
|
37
|
+
- **A body-fact-scanning optimization was investigated and reverted after measurement
|
|
38
|
+
showed no win.** cProfile flagged per-method body re-slicing (`_method_body` 44508× on
|
|
39
|
+
ofbiz) as a lead; a byte-identical single-pass rewrite (0 mismatches / 5555 files)
|
|
40
|
+
measured 7674→7721 ms — no improvement. The profiler had flagged a call-count-heavy but
|
|
41
|
+
wall-cheap region; the real cost is inherent regex/char scanning, already past its
|
|
42
|
+
algorithmic win. The change was dropped rather than accrue complexity on a hot,
|
|
43
|
+
output-critical path for no gain.
|
|
44
|
+
|
|
45
|
+
## [2.5.15] — 2026-07-19
|
|
46
|
+
|
|
47
|
+
**Bootstrap detection recovers the real entry point on multi-`main` repos, without
|
|
48
|
+
noise.** Post-F1 field validation (neo4j) found the compact `bootstrap` was name-gated
|
|
49
|
+
to `*Application.java` / `*Main.java`: neo4j's real server entry `Neo4jCommunity.java`
|
|
50
|
+
(a `main()` in a differently-named class) was invisible, so a client CLI `Main.java` was
|
|
51
|
+
reported as the sole bootstrap and the repo's 31 `main()` methods were hidden.
|
|
52
|
+
|
|
53
|
+
### Fixed
|
|
54
|
+
|
|
55
|
+
- **`main()`-bearing classes are recovered regardless of filename, comment-aware, and
|
|
56
|
+
the bootstrap list is ordered/capped/counted** (`detectors/java.py`, `serializer.py`,
|
|
57
|
+
`path_filters.py`): the annotation scan (which already reads each candidate file) now
|
|
58
|
+
also emits an `application` entry when a file declares a real `main(String[])` or a
|
|
59
|
+
bootstrap annotation — after stripping comments, so a `main()` / `@SpringBootApplication`
|
|
60
|
+
shown in a **javadoc example** is never mistaken for an entry (fixed Broadleaf
|
|
61
|
+
`*AutoConfiguration` false positives). `_bootstrap_structured` then: excludes entries
|
|
62
|
+
classified `auxiliary`/`development`; excludes mains in **example/demo modules**
|
|
63
|
+
(new structural `is_example_module_path`, e.g. `eureka-examples/`) and **test-fixture
|
|
64
|
+
modules** (`is_test_fixture_module_path`, e.g. `eureka-test-utils/`) — filtered here,
|
|
65
|
+
not in the detector, because workspace sub-scans prefix the module name onto the path
|
|
66
|
+
only after detection; orders deployment-annotated entries first; caps the list at 10;
|
|
67
|
+
and adds a `bootstrap_note` reporting the true count when more than one entry exists.
|
|
68
|
+
Measured (fleet): neo4j surfaces `Neo4jCommunity.java` with note "11 application entry
|
|
69
|
+
points detected; showing 10"; Broadleaf false positives 7 → 0; eureka's example/
|
|
70
|
+
test-util mains dropped (5 → 1); petclinic / spaghetti / openmrs unchanged (single
|
|
71
|
+
bootstrap). Full-output A/B confirms the change is confined to the entry-point surface —
|
|
72
|
+
no drift in stacks, architecture, or confidence. Residual real tool/CLI mains in library
|
|
73
|
+
packages (e.g. jobrunr's `DatabaseCreator`) are reported honestly with the count, as they
|
|
74
|
+
cannot be distinguished from deployment mains without name heuristics. Suite: 3521 passed.
|
|
75
|
+
|
|
76
|
+
## [2.5.14] — 2026-07-18
|
|
77
|
+
|
|
78
|
+
**One authority decides the primary stack; every surface transports its choice.**
|
|
79
|
+
Post-F1 field validation found a polyglot repo (Confluent `examples`: 1 C# file vs 86
|
|
80
|
+
Java files) labelled `.NET` as primary. Root cause was not one coin flip but several:
|
|
81
|
+
`primary` was chosen by `max()` over `(priority, confidence, manifest)` — which **ties**
|
|
82
|
+
for two manifest-detected API stacks, so **list order** decided — and then re-derived
|
|
83
|
+
independently in more than one place (the classifier marked `stacks[].primary`; the
|
|
84
|
+
summarizer re-selected for the `project_summary` prose; the serializer / architecture
|
|
85
|
+
summary invented a `stacks[0]` fallback when the flag was absent). These agreed only by
|
|
86
|
+
sharing the same arbitrary order, so they could — and did — disagree once the input
|
|
87
|
+
changed.
|
|
88
|
+
|
|
89
|
+
### Fixed
|
|
90
|
+
|
|
91
|
+
- **Single primary-stack authority with an evidence-weighted tiebreaker**
|
|
92
|
+
(`classifier.py`, `cli.py`, `summarizer.py`): a new `select_primary_stack(stacks,
|
|
93
|
+
project_type, file_paths)` is the ONE decider. It keeps the existing
|
|
94
|
+
`(priority, confidence, manifest)` ordering and adds **source-file volume as the last
|
|
95
|
+
tuple element** — a pure tiebreaker that decides only when the prior three tie, so a
|
|
96
|
+
1-file .NET example never outranks 86 Java files, and stacks that already differ are
|
|
97
|
+
never reordered. It marks exactly one instance `primary=True` (all others `False`) over
|
|
98
|
+
the final assembled root+workspace set. `TypeClassifier.enrich` now delegates to it (the
|
|
99
|
+
old `_select_primary_stack` name-returning helper is removed), and the main compact path
|
|
100
|
+
re-runs it after workspace stacks are appended. The summarizer's independent
|
|
101
|
+
`_select_summary_primary_stack` is deleted — the prose now reads the same `stack.primary`
|
|
102
|
+
flag as `architecture_summary`, `stacks[].primary` and `confidence_analyzer`. The
|
|
103
|
+
extension table shared by confidence enrichment and the tiebreaker was hoisted to one
|
|
104
|
+
constant (`_STACK_EXTENSIONS`) so the two never diverge. Fleet A/B (12 repos): only
|
|
105
|
+
`examples` changes (primary `dotnet`→`java`; architecture `C#/.NET`→`Java … JAX-RS`;
|
|
106
|
+
summary `Stack: Dotnet`→`Stack: Java … 80 Java classes`; the previously-emitted false
|
|
107
|
+
"polyglot / two programs" anomaly correctly disappears because Java genuinely attests the
|
|
108
|
+
API surface). All 11 other repos are byte-identical. Suite: 3508 passed, 5 skipped.
|
|
109
|
+
|
|
3
110
|
## [2.5.13] — 2026-07-18
|
|
4
111
|
|
|
5
|
-
**
|
|
6
|
-
validation (neo4j, keycloak) found that a JAX-RS / Spring resource shipping
|
|
7
|
-
`src/main` of a test-utility module — a dummy web service, an Arquillian test-harness
|
|
8
|
-
resource — was reported
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
112
|
+
**"Production endpoint" is now a single definition across every user-facing surface.**
|
|
113
|
+
Post-F1 field validation (neo4j, keycloak) found that a JAX-RS / Spring resource shipping
|
|
114
|
+
under `src/main` of a test-utility module — a dummy web service, an Arquillian test-harness
|
|
115
|
+
resource — was reported as production API surface, and that different commands disagreed on
|
|
116
|
+
what counted: `endpoints` / `validation` derived their view one way while `impact`,
|
|
117
|
+
`impact-chain`, `pr-impact`, `review-pr` and `spring-audit` derived theirs from the raw
|
|
118
|
+
`route_surface`, so the same repo had two definitions of "exposed endpoint" depending on the
|
|
119
|
+
command. This release unifies them: every user-facing surface now consumes the same
|
|
120
|
+
production-endpoint view (test scaffolding excluded), while `route_surface` remains the RAW
|
|
121
|
+
structural evidence by design — full and unfiltered — consumed only by `repo-ir`. The
|
|
122
|
+
under-modelling that motivated this (test fixtures counted as HTTP exposure, inflating
|
|
123
|
+
endpoint counts, blast radius, risk scores and security analysis) was the one defect the
|
|
124
|
+
post-F1 review classified as blocking.
|
|
12
125
|
|
|
13
126
|
### Fixed
|
|
14
127
|
|
|
@@ -32,6 +145,37 @@ defect the post-F1 review classified as blocking.
|
|
|
32
145
|
deliberately untouched — the new helper is narrower and wired only into endpoint
|
|
33
146
|
extraction.
|
|
34
147
|
|
|
148
|
+
- **One production-endpoint definition across every CIR consumer** (`repository_ir.py`,
|
|
149
|
+
`canonical_ir.py`, `endpoint_metrics.py`): the fix above only reached the `endpoints` /
|
|
150
|
+
`validation` surface; `impact-chain`, `pr-impact`, `review-pr` and `spring-audit` derive
|
|
151
|
+
their HTTP surface from the canonical `cir.endpoints`, which is built from the RAW
|
|
152
|
+
`route_surface` and did **not** inherit the test-fixture exclusion — so those commands
|
|
153
|
+
still counted test scaffolding as production exposure (e.g. keycloak's 102 Arquillian
|
|
154
|
+
endpoints inflating blast radius / risk score / SEC analysis). `build_repo_ir` now tags
|
|
155
|
+
test-fixture routes with `scope="test_util"` on `route_surface`, and `ir_dict_to_canonical`
|
|
156
|
+
excludes tagged routes when building `cir.endpoints` — the same place the pre-existing
|
|
157
|
+
FQN dynamic-admin filter already reconciles the CIR surface with the `endpoints` command.
|
|
158
|
+
`route_surface` itself is left **raw and complete** (the tag is additive) so `repo-ir`
|
|
159
|
+
keeps full evidence; the divergence is documented in `ENDPOINT_SURFACE_RECONCILIATION`.
|
|
160
|
+
No heuristic, scoring, risk or reconciliation logic was changed — the four consumers change
|
|
161
|
+
only because they now receive a coherent `cir.endpoints`. Measured: keycloak
|
|
162
|
+
`cir.endpoints` / `spring-audit.endpoints_analyzed` 775 → 673 (route_surface stays 775);
|
|
163
|
+
neo4j 18 → 14 (route_surface stays 18); Broadleaf/petclinic/spaghetti/eureka unchanged
|
|
164
|
+
(0 test_util routes). Zero test-fixture controllers leak into `cir.endpoints` on any repo.
|
|
165
|
+
|
|
166
|
+
- **The legacy `impact` command now shares the same production-endpoint definition**
|
|
167
|
+
(`repository_ir.py`): `impact` reads `route_surface` directly via `compute_blast_radius`
|
|
168
|
+
rather than through `cir.endpoints`, so it was the last user surface still counting test
|
|
169
|
+
fixtures as HTTP exposure. `compute_blast_radius` now skips `scope=="test_util"` routes when
|
|
170
|
+
building `endpoints_affected`. The change is a single guard at the consumption point: the
|
|
171
|
+
blast-radius algorithm, caller selection, risk formula, security surface and the FQN-admin
|
|
172
|
+
handling are all unchanged — the endpoint list simply no longer includes test scaffolding.
|
|
173
|
+
Validated by A/B (stash) diff: `impact TestingOIDCEndpointsApplicationResource` on keycloak
|
|
174
|
+
went from 28 endpoints_affected (all test) / risk 87 to 0 / risk 3 with `direct_callers`
|
|
175
|
+
unchanged (1 → 1); `impact CatalogServiceImpl` on Broadleaf is **byte-identical** before and
|
|
176
|
+
after (54 endpoints, 5 FQN-shaped paths preserved, 30 callers). "Production endpoint" is now
|
|
177
|
+
a single definition across every user-facing command.
|
|
178
|
+
|
|
35
179
|
## [2.5.12] — 2026-07-18
|
|
36
180
|
|
|
37
181
|
**F-1 (the Evidence Reconciliation Layer) is closed and its rule core frozen.** This release
|
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Performance harness — runs the reference fleet under the ADR-0006 contract.
|
|
3
|
+
|
|
4
|
+
For every (command x repo x mode) cell it runs the CLI N times, records
|
|
5
|
+
wall-clock, peak RSS and (when the in-process recorder is armed) per-phase
|
|
6
|
+
timings, then writes one ``perf-baseline-v1`` JSON artifact.
|
|
7
|
+
|
|
8
|
+
Methodology is fixed by ADR-0006 §5: cold and warm are separate cells and never
|
|
9
|
+
averaged; the first warm run is discarded as cache fill; central tendency is the
|
|
10
|
+
median; p95 is always reported; cross-machine numbers are recorded, never diffed.
|
|
11
|
+
|
|
12
|
+
Usage:
|
|
13
|
+
python3 scripts/perf_harness.py --quick # petclinic only, 2 runs
|
|
14
|
+
python3 scripts/perf_harness.py # full fleet, 7 runs
|
|
15
|
+
python3 scripts/perf_harness.py --repos keycloak eureka --runs 5
|
|
16
|
+
python3 scripts/perf_harness.py --dry-run # print the plan, run nothing
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
from __future__ import annotations
|
|
20
|
+
|
|
21
|
+
import argparse
|
|
22
|
+
import json
|
|
23
|
+
import os
|
|
24
|
+
import resource
|
|
25
|
+
import shutil
|
|
26
|
+
import subprocess
|
|
27
|
+
import sys
|
|
28
|
+
import tempfile
|
|
29
|
+
import time
|
|
30
|
+
from pathlib import Path
|
|
31
|
+
|
|
32
|
+
# Import the frozen collector from the package under test.
|
|
33
|
+
sys.path.insert(0, str(Path(__file__).resolve().parent.parent / "src"))
|
|
34
|
+
from sourcecode import __version__ as TOOL_VERSION # noqa: E402
|
|
35
|
+
from sourcecode import perf # noqa: E402
|
|
36
|
+
|
|
37
|
+
# ── Fleet definition ────────────────────────────────────────────────────────
|
|
38
|
+
# Directory name under the fleet root. Repos are pinned by their checked-out
|
|
39
|
+
# commit at run time (recorded in each artifact), so a size change is never
|
|
40
|
+
# mistaken for a perf change.
|
|
41
|
+
DEFAULT_FLEET_ROOT = Path(
|
|
42
|
+
os.environ.get("PERF_FLEET_ROOT", "/Users/user/Documents/workspace/testing")
|
|
43
|
+
)
|
|
44
|
+
FLEET: dict[str, str] = {
|
|
45
|
+
"petclinic": "spring-petclinic",
|
|
46
|
+
"broadleaf": "BroadleafCommerce",
|
|
47
|
+
"openmrs": "openmrs-core",
|
|
48
|
+
"keycloak": "keycloak",
|
|
49
|
+
"eureka": "eureka",
|
|
50
|
+
"jobrunr": "jobrunr",
|
|
51
|
+
"ofbiz": "ofbiz-framework",
|
|
52
|
+
"alfresco": "alfresco-community-repo",
|
|
53
|
+
"jenkins": "jenkins",
|
|
54
|
+
"neo4j": "neo4j",
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
# Commands to profile. The empty string is the root analysis (`ask <repo>`);
|
|
58
|
+
# named entries are subcommands (`ask <cmd> <repo>`).
|
|
59
|
+
DEFAULT_COMMANDS = [""]
|
|
60
|
+
|
|
61
|
+
ASK_BIN = os.environ.get("ASK_BIN", "ask")
|
|
62
|
+
BYTES_PER_KB = 1024
|
|
63
|
+
# darwin ru_maxrss is bytes; linux reports kilobytes.
|
|
64
|
+
RSS_UNIT = 1 if sys.platform == "darwin" else BYTES_PER_KB
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def git_commit(repo_path: Path) -> str:
|
|
68
|
+
try:
|
|
69
|
+
out = subprocess.run(
|
|
70
|
+
["git", "-C", str(repo_path), "rev-parse", "--short", "HEAD"],
|
|
71
|
+
capture_output=True, text=True, timeout=15,
|
|
72
|
+
)
|
|
73
|
+
return out.stdout.strip() or "unknown"
|
|
74
|
+
except (subprocess.SubprocessError, OSError):
|
|
75
|
+
return "unknown"
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def repo_size(repo_path: Path) -> dict[str, int]:
|
|
79
|
+
"""Repo scaling axes for cost-per-unit analysis.
|
|
80
|
+
|
|
81
|
+
``files`` / ``bytes`` are a cheap rglob over every ``*.java`` (the frozen file
|
|
82
|
+
axis — includes tests). ``classes`` / ``methods`` come from repo-ir's
|
|
83
|
+
``analysis_meta`` rollup (the IR owns the symbol taxonomy). This is one
|
|
84
|
+
``repo-ir --summary-only`` pass off the measured path; it never touches
|
|
85
|
+
``wall_ms``. If repo-ir fails the symbol counts fall back to 0 — an honest
|
|
86
|
+
"unmeasured", never a fabricated number.
|
|
87
|
+
"""
|
|
88
|
+
files = 0
|
|
89
|
+
total = 0
|
|
90
|
+
for p in repo_path.rglob("*.java"):
|
|
91
|
+
try:
|
|
92
|
+
total += p.stat().st_size
|
|
93
|
+
files += 1
|
|
94
|
+
except OSError:
|
|
95
|
+
continue
|
|
96
|
+
classes, methods = _repo_symbol_counts(repo_path)
|
|
97
|
+
return {"files": files, "bytes": total, "classes": classes, "methods": methods}
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def _repo_symbol_counts(repo_path: Path) -> tuple[int, int]:
|
|
101
|
+
"""(classes, methods) from ``repo-ir --summary-only``; (0, 0) on any failure."""
|
|
102
|
+
with tempfile.TemporaryDirectory(prefix="perf-ir-") as td:
|
|
103
|
+
out = Path(td) / "ir.json"
|
|
104
|
+
try:
|
|
105
|
+
subprocess.run(
|
|
106
|
+
[ASK_BIN, "repo-ir", str(repo_path), "--summary-only", "-o", str(out)],
|
|
107
|
+
capture_output=True, timeout=1800, check=False,
|
|
108
|
+
)
|
|
109
|
+
meta = json.loads(out.read_text(encoding="utf-8")).get("analysis_meta", {})
|
|
110
|
+
return int(meta.get("classes", 0) or 0), int(meta.get("methods", 0) or 0)
|
|
111
|
+
except (subprocess.SubprocessError, OSError, ValueError):
|
|
112
|
+
return 0, 0
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def build_argv(command: str, repo_path: Path, cold: bool) -> list[str]:
|
|
116
|
+
argv = [ASK_BIN]
|
|
117
|
+
if command:
|
|
118
|
+
argv.append(command)
|
|
119
|
+
argv.append(str(repo_path))
|
|
120
|
+
argv += ["--format", "json"]
|
|
121
|
+
if cold:
|
|
122
|
+
argv.append("--no-cache")
|
|
123
|
+
return argv
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def run_once(argv: list[str], env: dict[str, str], perf_log: Path) -> tuple[float, float, dict | None]:
|
|
127
|
+
"""Run the CLI once. Returns (wall_ms, rss_bytes, phase_ms | None)."""
|
|
128
|
+
if perf_log.exists():
|
|
129
|
+
perf_log.unlink()
|
|
130
|
+
rss_before = resource.getrusage(resource.RUSAGE_CHILDREN).ru_maxrss
|
|
131
|
+
t0 = time.perf_counter()
|
|
132
|
+
subprocess.run(argv, env=env, capture_output=True, timeout=1800)
|
|
133
|
+
wall_ms = (time.perf_counter() - t0) * 1000.0
|
|
134
|
+
rss_after = resource.getrusage(resource.RUSAGE_CHILDREN).ru_maxrss
|
|
135
|
+
# ru_maxrss for RUSAGE_CHILDREN is the peak among reaped children; the delta
|
|
136
|
+
# approximates this run's peak. Approximate by construction — documented.
|
|
137
|
+
rss_bytes = max(rss_after - rss_before, rss_after) * RSS_UNIT if rss_after else 0.0
|
|
138
|
+
phase_ms = _read_phase_log(perf_log)
|
|
139
|
+
return wall_ms, float(rss_bytes), phase_ms
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def _read_phase_log(perf_log: Path) -> dict | None:
|
|
143
|
+
if not perf_log.exists():
|
|
144
|
+
return None
|
|
145
|
+
try:
|
|
146
|
+
last = perf_log.read_text(encoding="utf-8").strip().splitlines()[-1]
|
|
147
|
+
return json.loads(last).get("phase_ms") or None
|
|
148
|
+
except (OSError, IndexError, ValueError):
|
|
149
|
+
return None
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
def measure_cell(command: str, repo_path: Path, mode: str, runs: int) -> dict:
|
|
153
|
+
cold = mode == "cold"
|
|
154
|
+
walls: list[float] = []
|
|
155
|
+
rsss: list[float] = []
|
|
156
|
+
phases: list[dict] = []
|
|
157
|
+
|
|
158
|
+
with tempfile.TemporaryDirectory(prefix="perf-cache-") as cache_dir, \
|
|
159
|
+
tempfile.TemporaryDirectory(prefix="perf-log-") as log_dir:
|
|
160
|
+
perf_log = Path(log_dir) / "phases.jsonl"
|
|
161
|
+
base_env = dict(os.environ)
|
|
162
|
+
base_env[perf.ENV_ENABLE] = "1"
|
|
163
|
+
base_env[perf.ENV_LOG] = str(perf_log)
|
|
164
|
+
base_env["SOURCECODE_CACHE_DIR"] = cache_dir
|
|
165
|
+
|
|
166
|
+
# Warm mode: one un-recorded priming run to populate the cache, then the
|
|
167
|
+
# first measured warm run is still discarded (belt and suspenders).
|
|
168
|
+
n = runs + 1 if mode == "warm" else runs
|
|
169
|
+
for i in range(n):
|
|
170
|
+
argv = build_argv(command, repo_path, cold=cold)
|
|
171
|
+
if cold:
|
|
172
|
+
# Each cold run gets a pristine cache dir.
|
|
173
|
+
for child in Path(cache_dir).iterdir():
|
|
174
|
+
shutil.rmtree(child, ignore_errors=True) if child.is_dir() else child.unlink()
|
|
175
|
+
wall, rss, phase = run_once(argv, base_env, perf_log)
|
|
176
|
+
if mode == "warm" and i == 0:
|
|
177
|
+
continue # discard cache-fill run
|
|
178
|
+
walls.append(wall)
|
|
179
|
+
rsss.append(rss)
|
|
180
|
+
if phase:
|
|
181
|
+
phases.append(phase)
|
|
182
|
+
|
|
183
|
+
return perf.build_baseline_cell(
|
|
184
|
+
tool_version=TOOL_VERSION,
|
|
185
|
+
command=command or "ask",
|
|
186
|
+
repo={"name": repo_path.name, "commit": git_commit(repo_path), **repo_size(repo_path)},
|
|
187
|
+
mode=mode,
|
|
188
|
+
wall_ms_runs=walls,
|
|
189
|
+
rss_bytes_runs=rsss,
|
|
190
|
+
phase_ms_runs=phases or None,
|
|
191
|
+
)
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
def main() -> int:
|
|
195
|
+
ap = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
|
|
196
|
+
ap.add_argument("--repos", nargs="*", help="Subset of fleet names (default: all).")
|
|
197
|
+
ap.add_argument("--commands", nargs="*", default=DEFAULT_COMMANDS,
|
|
198
|
+
help="Commands to profile; '' = root analysis (default).")
|
|
199
|
+
ap.add_argument("--modes", nargs="*", default=["cold", "warm"], choices=["cold", "warm"])
|
|
200
|
+
ap.add_argument("--runs", type=int, default=7, help="Measured runs per cell (>=5 per ADR-0006).")
|
|
201
|
+
ap.add_argument("--fleet-root", type=Path, default=DEFAULT_FLEET_ROOT)
|
|
202
|
+
ap.add_argument("--out", type=Path, default=None,
|
|
203
|
+
help="Output dir (default: docs/perf/baselines/<tool_version>).")
|
|
204
|
+
ap.add_argument("--quick", action="store_true", help="petclinic only, 2 runs — smoke test.")
|
|
205
|
+
ap.add_argument("--dry-run", action="store_true", help="Print the plan, run nothing.")
|
|
206
|
+
args = ap.parse_args()
|
|
207
|
+
|
|
208
|
+
if args.quick:
|
|
209
|
+
args.repos = ["petclinic"]
|
|
210
|
+
args.runs = 2
|
|
211
|
+
|
|
212
|
+
repos = args.repos or list(FLEET)
|
|
213
|
+
out_dir = args.out or (Path(__file__).resolve().parent.parent / "docs" / "perf" / "baselines" / TOOL_VERSION)
|
|
214
|
+
|
|
215
|
+
plan = [
|
|
216
|
+
(cmd, name, mode)
|
|
217
|
+
for cmd in args.commands
|
|
218
|
+
for name in repos
|
|
219
|
+
for mode in args.modes
|
|
220
|
+
]
|
|
221
|
+
print(f"perf-harness {TOOL_VERSION} — {len(plan)} cells, {args.runs} runs each")
|
|
222
|
+
print(f"fleet root: {args.fleet_root}")
|
|
223
|
+
print(f"output: {out_dir}")
|
|
224
|
+
if args.dry_run:
|
|
225
|
+
for cmd, name, mode in plan:
|
|
226
|
+
print(f" [{mode:4}] {name:10} {cmd or 'ask'}")
|
|
227
|
+
return 0
|
|
228
|
+
|
|
229
|
+
out_dir.mkdir(parents=True, exist_ok=True)
|
|
230
|
+
for cmd, name, mode in plan:
|
|
231
|
+
subdir = FLEET.get(name, name)
|
|
232
|
+
repo_path = args.fleet_root / subdir
|
|
233
|
+
if not repo_path.is_dir():
|
|
234
|
+
print(f" SKIP {name}: {repo_path} not found", file=sys.stderr)
|
|
235
|
+
continue
|
|
236
|
+
print(f" [{mode:4}] {name:10} {cmd or 'ask'} ...", end="", flush=True)
|
|
237
|
+
try:
|
|
238
|
+
cell = measure_cell(cmd, repo_path, mode, args.runs)
|
|
239
|
+
except subprocess.TimeoutExpired:
|
|
240
|
+
print(" TIMEOUT", file=sys.stderr)
|
|
241
|
+
continue
|
|
242
|
+
fname = f"{name}__{cmd or 'ask'}__{mode}.json"
|
|
243
|
+
(out_dir / fname).write_text(json.dumps(cell, indent=2) + "\n", encoding="utf-8")
|
|
244
|
+
w = cell["wall_ms"]
|
|
245
|
+
print(f" p50={w['p50']}ms p95={w['p95']}ms -> {fname}")
|
|
246
|
+
|
|
247
|
+
print(f"done. artifacts in {out_dir}")
|
|
248
|
+
return 0
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
if __name__ == "__main__":
|
|
252
|
+
raise SystemExit(main())
|
|
@@ -967,25 +967,26 @@ def _java_contract_from_graph(rel_path: str, graph: "Any") -> FileContract:
|
|
|
967
967
|
nodes/edges instead of re-parsing source. Method signatures are the graph's
|
|
968
968
|
canonical compact form (`(Type)->Return`); annotations are node-grounded.
|
|
969
969
|
"""
|
|
970
|
-
file_syms =
|
|
970
|
+
file_syms = graph.symbols_in_file(rel_path)
|
|
971
971
|
type_syms = [s for s in file_syms if s.kind in ("class", "interface", "enum", "annotation")]
|
|
972
972
|
type_fqns = {s.fqn for s in type_syms}
|
|
973
973
|
|
|
974
974
|
# extends / implements edges out of this file's types (targets are simple
|
|
975
975
|
# names for out-of-repo supertypes, resolved FQNs for in-repo ones — the
|
|
976
|
-
# same forms the old regex captured for the unresolved case).
|
|
976
|
+
# same forms the old regex captured for the unresolved case). Only edges
|
|
977
|
+
# sourced at this file's types matter, so index straight to them instead of
|
|
978
|
+
# scanning the whole-repo relation set once per file.
|
|
977
979
|
extends_of: dict[str, list[str]] = {}
|
|
978
980
|
implements_of: dict[str, list[str]] = {}
|
|
979
981
|
import_targets: set[str] = set()
|
|
980
|
-
for
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
import_targets.add(r.target)
|
|
982
|
+
for fqn in type_fqns:
|
|
983
|
+
for r in graph.relations_from(fqn):
|
|
984
|
+
if r.kind == "extends":
|
|
985
|
+
extends_of.setdefault(r.source, []).append(r.target)
|
|
986
|
+
elif r.kind == "implements":
|
|
987
|
+
implements_of.setdefault(r.source, []).append(r.target)
|
|
988
|
+
elif r.kind == "imports":
|
|
989
|
+
import_targets.add(r.target)
|
|
989
990
|
|
|
990
991
|
exports: list[ExportRecord] = []
|
|
991
992
|
types: list[TypeDefinition] = []
|
|
@@ -398,9 +398,17 @@ def ir_dict_to_canonical(
|
|
|
398
398
|
# real REST surface. The CIR endpoint list feeds `spring-audit` (endpoints_analyzed),
|
|
399
399
|
# `impact` and `validation`; it must apply the SAME filter so its count reconciles
|
|
400
400
|
# with `endpoints` instead of being silently inflated by those pseudo-routes.
|
|
401
|
+
# Endpoints declared in a test-fixture / test-utility module are tagged
|
|
402
|
+
# scope="test_util" on route_surface by build_repo_ir. They are excluded from the
|
|
403
|
+
# canonical endpoint list for the same reason as the FQN filter above: the CIR
|
|
404
|
+
# surface feeds impact-chain, pr-impact, review-pr and spring-audit, and test
|
|
405
|
+
# scaffolding is not production HTTP exposure. route_surface itself keeps them
|
|
406
|
+
# (raw evidence); only this canonical projection drops them.
|
|
401
407
|
_seen_ids: set[str] = set()
|
|
402
408
|
raw_endpoints: list[CanonicalEndpoint] = []
|
|
403
409
|
for r in route_surface:
|
|
410
|
+
if r.get("scope") == "test_util":
|
|
411
|
+
continue
|
|
404
412
|
if _FQN_PATH_RE.search(r.get("path", "") or ""):
|
|
405
413
|
continue
|
|
406
414
|
ep = _route_to_canonical_endpoint(r)
|