sourcecode 1.73.0__tar.gz → 2.0.1__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.73.0 → sourcecode-2.0.1}/.gitignore +4 -0
- {sourcecode-1.73.0 → sourcecode-2.0.1}/CHANGELOG.md +26 -0
- {sourcecode-1.73.0 → sourcecode-2.0.1}/PKG-INFO +22 -10
- {sourcecode-1.73.0 → sourcecode-2.0.1}/README.md +21 -9
- {sourcecode-1.73.0 → sourcecode-2.0.1}/pyproject.toml +1 -1
- sourcecode-2.0.1/scripts/compare_integration_engines.py +62 -0
- sourcecode-2.0.1/scripts/customer_smoke_test.sh +87 -0
- {sourcecode-1.73.0 → sourcecode-2.0.1}/src/sourcecode/__init__.py +1 -1
- {sourcecode-1.73.0 → sourcecode-2.0.1}/src/sourcecode/architecture_analyzer.py +63 -55
- {sourcecode-1.73.0 → sourcecode-2.0.1}/src/sourcecode/ast_extractor.py +130 -135
- sourcecode-2.0.1/src/sourcecode/call_surface.py +112 -0
- {sourcecode-1.73.0 → sourcecode-2.0.1}/src/sourcecode/canonical_ir.py +62 -1
- {sourcecode-1.73.0 → sourcecode-2.0.1}/src/sourcecode/cli.py +41 -16
- sourcecode-2.0.1/src/sourcecode/context_graph.py +1022 -0
- {sourcecode-1.73.0 → sourcecode-2.0.1}/src/sourcecode/contract_pipeline.py +16 -1
- sourcecode-2.0.1/src/sourcecode/dynamic_argument_surface.py +54 -0
- sourcecode-2.0.1/src/sourcecode/endpoint_literals.py +71 -0
- sourcecode-2.0.1/src/sourcecode/evidence_provider.py +90 -0
- {sourcecode-1.73.0 → sourcecode-2.0.1}/src/sourcecode/explain.py +28 -13
- {sourcecode-1.73.0 → sourcecode-2.0.1}/src/sourcecode/graph_analyzer.py +82 -8
- {sourcecode-1.73.0 → sourcecode-2.0.1}/src/sourcecode/license.py +12 -1
- {sourcecode-1.73.0 → sourcecode-2.0.1}/src/sourcecode/migrate_check.py +64 -0
- {sourcecode-1.73.0 → sourcecode-2.0.1}/src/sourcecode/pr_comment_renderer.py +7 -6
- {sourcecode-1.73.0 → sourcecode-2.0.1}/src/sourcecode/prepare_context.py +10 -13
- {sourcecode-1.73.0 → sourcecode-2.0.1}/src/sourcecode/repository_ir.py +881 -140
- {sourcecode-1.73.0 → sourcecode-2.0.1}/src/sourcecode/semantic_analyzer.py +146 -9
- sourcecode-2.0.1/src/sourcecode/semantic_impact_engine.py +403 -0
- sourcecode-2.0.1/src/sourcecode/semantic_integration_engine.py +299 -0
- sourcecode-2.0.1/src/sourcecode/semantic_services.py +229 -0
- {sourcecode-1.73.0 → sourcecode-2.0.1}/src/sourcecode/serializer.py +23 -31
- {sourcecode-1.73.0 → sourcecode-2.0.1}/src/sourcecode/summarizer.py +33 -2
- sourcecode-2.0.1/src/sourcecode/type_usage_surface.py +101 -0
- {sourcecode-1.73.0 → sourcecode-2.0.1}/src/sourcecode/validation_surface.py +173 -205
- sourcecode-2.0.1/supabase/sql/licensing_schema.sql +108 -0
- sourcecode-1.73.0/src/sourcecode/flow_analyzer.py +0 -671
- sourcecode-1.73.0/src/sourcecode/integration_detector.py +0 -435
- {sourcecode-1.73.0 → sourcecode-2.0.1}/.github/workflows/build-windows.yml +0 -0
- {sourcecode-1.73.0 → sourcecode-2.0.1}/.ruff.toml +0 -0
- {sourcecode-1.73.0 → sourcecode-2.0.1}/CONTRIBUTING.md +0 -0
- {sourcecode-1.73.0 → sourcecode-2.0.1}/LICENSE +0 -0
- {sourcecode-1.73.0 → sourcecode-2.0.1}/SECURITY.md +0 -0
- {sourcecode-1.73.0 → sourcecode-2.0.1}/raw +0 -0
- {sourcecode-1.73.0 → sourcecode-2.0.1}/scripts/generate_jdk_exports.py +0 -0
- {sourcecode-1.73.0 → sourcecode-2.0.1}/src/sourcecode/adaptive_scanner.py +0 -0
- {sourcecode-1.73.0 → sourcecode-2.0.1}/src/sourcecode/architecture_summary.py +0 -0
- {sourcecode-1.73.0 → sourcecode-2.0.1}/src/sourcecode/cache.py +0 -0
- {sourcecode-1.73.0 → sourcecode-2.0.1}/src/sourcecode/cir_graphs.py +0 -0
- {sourcecode-1.73.0 → sourcecode-2.0.1}/src/sourcecode/classifier.py +0 -0
- {sourcecode-1.73.0 → sourcecode-2.0.1}/src/sourcecode/code_notes_analyzer.py +0 -0
- {sourcecode-1.73.0 → sourcecode-2.0.1}/src/sourcecode/confidence_analyzer.py +0 -0
- {sourcecode-1.73.0 → sourcecode-2.0.1}/src/sourcecode/context_scorer.py +0 -0
- {sourcecode-1.73.0 → sourcecode-2.0.1}/src/sourcecode/context_summarizer.py +0 -0
- {sourcecode-1.73.0 → sourcecode-2.0.1}/src/sourcecode/contract_model.py +0 -0
- {sourcecode-1.73.0 → sourcecode-2.0.1}/src/sourcecode/coverage_parser.py +0 -0
- {sourcecode-1.73.0 → sourcecode-2.0.1}/src/sourcecode/dependency_analyzer.py +0 -0
- {sourcecode-1.73.0 → sourcecode-2.0.1}/src/sourcecode/detectors/__init__.py +0 -0
- {sourcecode-1.73.0 → sourcecode-2.0.1}/src/sourcecode/detectors/base.py +0 -0
- {sourcecode-1.73.0 → sourcecode-2.0.1}/src/sourcecode/detectors/csproj_parser.py +0 -0
- {sourcecode-1.73.0 → sourcecode-2.0.1}/src/sourcecode/detectors/dart.py +0 -0
- {sourcecode-1.73.0 → sourcecode-2.0.1}/src/sourcecode/detectors/dotnet.py +0 -0
- {sourcecode-1.73.0 → sourcecode-2.0.1}/src/sourcecode/detectors/elixir.py +0 -0
- {sourcecode-1.73.0 → sourcecode-2.0.1}/src/sourcecode/detectors/go.py +0 -0
- {sourcecode-1.73.0 → sourcecode-2.0.1}/src/sourcecode/detectors/heuristic.py +0 -0
- {sourcecode-1.73.0 → sourcecode-2.0.1}/src/sourcecode/detectors/hybrid.py +0 -0
- {sourcecode-1.73.0 → sourcecode-2.0.1}/src/sourcecode/detectors/java.py +0 -0
- {sourcecode-1.73.0 → sourcecode-2.0.1}/src/sourcecode/detectors/jvm_ext.py +0 -0
- {sourcecode-1.73.0 → sourcecode-2.0.1}/src/sourcecode/detectors/nodejs.py +0 -0
- {sourcecode-1.73.0 → sourcecode-2.0.1}/src/sourcecode/detectors/parsers.py +0 -0
- {sourcecode-1.73.0 → sourcecode-2.0.1}/src/sourcecode/detectors/php.py +0 -0
- {sourcecode-1.73.0 → sourcecode-2.0.1}/src/sourcecode/detectors/project.py +0 -0
- {sourcecode-1.73.0 → sourcecode-2.0.1}/src/sourcecode/detectors/python.py +0 -0
- {sourcecode-1.73.0 → sourcecode-2.0.1}/src/sourcecode/detectors/ruby.py +0 -0
- {sourcecode-1.73.0 → sourcecode-2.0.1}/src/sourcecode/detectors/rust.py +0 -0
- {sourcecode-1.73.0 → sourcecode-2.0.1}/src/sourcecode/detectors/systems.py +0 -0
- {sourcecode-1.73.0 → sourcecode-2.0.1}/src/sourcecode/detectors/terraform.py +0 -0
- {sourcecode-1.73.0 → sourcecode-2.0.1}/src/sourcecode/detectors/tooling.py +0 -0
- {sourcecode-1.73.0 → sourcecode-2.0.1}/src/sourcecode/doc_analyzer.py +0 -0
- {sourcecode-1.73.0 → sourcecode-2.0.1}/src/sourcecode/entrypoint_classifier.py +0 -0
- {sourcecode-1.73.0 → sourcecode-2.0.1}/src/sourcecode/env_analyzer.py +0 -0
- {sourcecode-1.73.0 → sourcecode-2.0.1}/src/sourcecode/error_schema.py +0 -0
- {sourcecode-1.73.0 → sourcecode-2.0.1}/src/sourcecode/file_chunker.py +0 -0
- {sourcecode-1.73.0 → sourcecode-2.0.1}/src/sourcecode/file_classifier.py +0 -0
- {sourcecode-1.73.0 → sourcecode-2.0.1}/src/sourcecode/format_contract.py +0 -0
- {sourcecode-1.73.0 → sourcecode-2.0.1}/src/sourcecode/fqn_utils.py +0 -0
- {sourcecode-1.73.0 → sourcecode-2.0.1}/src/sourcecode/git_analyzer.py +0 -0
- {sourcecode-1.73.0 → sourcecode-2.0.1}/src/sourcecode/hibernate_strat.py +0 -0
- {sourcecode-1.73.0 → sourcecode-2.0.1}/src/sourcecode/jdk_exports.py +0 -0
- {sourcecode-1.73.0 → sourcecode-2.0.1}/src/sourcecode/mcp/__init__.py +0 -0
- {sourcecode-1.73.0 → sourcecode-2.0.1}/src/sourcecode/mcp/onboarding/__init__.py +0 -0
- {sourcecode-1.73.0 → sourcecode-2.0.1}/src/sourcecode/mcp/onboarding/applier.py +0 -0
- {sourcecode-1.73.0 → sourcecode-2.0.1}/src/sourcecode/mcp/onboarding/backup.py +0 -0
- {sourcecode-1.73.0 → sourcecode-2.0.1}/src/sourcecode/mcp/onboarding/detector.py +0 -0
- {sourcecode-1.73.0 → sourcecode-2.0.1}/src/sourcecode/mcp/onboarding/planner.py +0 -0
- {sourcecode-1.73.0 → sourcecode-2.0.1}/src/sourcecode/mcp/orchestrator.py +0 -0
- {sourcecode-1.73.0 → sourcecode-2.0.1}/src/sourcecode/mcp/registry.py +0 -0
- {sourcecode-1.73.0 → sourcecode-2.0.1}/src/sourcecode/mcp/runner.py +0 -0
- {sourcecode-1.73.0 → sourcecode-2.0.1}/src/sourcecode/mcp/server.py +0 -0
- {sourcecode-1.73.0 → sourcecode-2.0.1}/src/sourcecode/mcp_nudge.py +0 -0
- {sourcecode-1.73.0 → sourcecode-2.0.1}/src/sourcecode/metrics_analyzer.py +0 -0
- {sourcecode-1.73.0 → sourcecode-2.0.1}/src/sourcecode/openapi_surface.py +0 -0
- {sourcecode-1.73.0 → sourcecode-2.0.1}/src/sourcecode/output_budget.py +0 -0
- {sourcecode-1.73.0 → sourcecode-2.0.1}/src/sourcecode/path_filters.py +0 -0
- {sourcecode-1.73.0 → sourcecode-2.0.1}/src/sourcecode/pr_impact.py +0 -0
- {sourcecode-1.73.0 → sourcecode-2.0.1}/src/sourcecode/progress.py +0 -0
- {sourcecode-1.73.0 → sourcecode-2.0.1}/src/sourcecode/ranking_engine.py +0 -0
- {sourcecode-1.73.0 → sourcecode-2.0.1}/src/sourcecode/redactor.py +0 -0
- {sourcecode-1.73.0 → sourcecode-2.0.1}/src/sourcecode/relevance_scorer.py +0 -0
- {sourcecode-1.73.0 → sourcecode-2.0.1}/src/sourcecode/rename_refactor.py +0 -0
- {sourcecode-1.73.0 → sourcecode-2.0.1}/src/sourcecode/repo_classifier.py +0 -0
- {sourcecode-1.73.0 → sourcecode-2.0.1}/src/sourcecode/ris.py +0 -0
- {sourcecode-1.73.0 → sourcecode-2.0.1}/src/sourcecode/runtime_classifier.py +0 -0
- {sourcecode-1.73.0 → sourcecode-2.0.1}/src/sourcecode/scanner.py +0 -0
- {sourcecode-1.73.0 → sourcecode-2.0.1}/src/sourcecode/schema.py +0 -0
- {sourcecode-1.73.0 → sourcecode-2.0.1}/src/sourcecode/security_config.py +0 -0
- {sourcecode-1.73.0 → sourcecode-2.0.1}/src/sourcecode/spring_event_topology.py +0 -0
- {sourcecode-1.73.0 → sourcecode-2.0.1}/src/sourcecode/spring_findings.py +0 -0
- {sourcecode-1.73.0 → sourcecode-2.0.1}/src/sourcecode/spring_impact.py +0 -0
- {sourcecode-1.73.0 → sourcecode-2.0.1}/src/sourcecode/spring_model.py +0 -0
- {sourcecode-1.73.0 → sourcecode-2.0.1}/src/sourcecode/spring_security_audit.py +0 -0
- {sourcecode-1.73.0 → sourcecode-2.0.1}/src/sourcecode/spring_semantic.py +0 -0
- {sourcecode-1.73.0 → sourcecode-2.0.1}/src/sourcecode/spring_tx_analyzer.py +0 -0
- {sourcecode-1.73.0 → sourcecode-2.0.1}/src/sourcecode/telemetry/__init__.py +0 -0
- {sourcecode-1.73.0 → sourcecode-2.0.1}/src/sourcecode/telemetry/config.py +0 -0
- {sourcecode-1.73.0 → sourcecode-2.0.1}/src/sourcecode/telemetry/consent.py +0 -0
- {sourcecode-1.73.0 → sourcecode-2.0.1}/src/sourcecode/telemetry/events.py +0 -0
- {sourcecode-1.73.0 → sourcecode-2.0.1}/src/sourcecode/telemetry/filters.py +0 -0
- {sourcecode-1.73.0 → sourcecode-2.0.1}/src/sourcecode/telemetry/transport.py +0 -0
- {sourcecode-1.73.0 → sourcecode-2.0.1}/src/sourcecode/tree_utils.py +0 -0
- {sourcecode-1.73.0 → sourcecode-2.0.1}/src/sourcecode/version_check.py +0 -0
- {sourcecode-1.73.0 → sourcecode-2.0.1}/src/sourcecode/workspace.py +0 -0
- {sourcecode-1.73.0 → sourcecode-2.0.1}/supabase/functions/README.md +0 -0
- {sourcecode-1.73.0 → sourcecode-2.0.1}/supabase/functions/get-license/index.ts +0 -0
- {sourcecode-1.73.0 → sourcecode-2.0.1}/supabase/functions/lemonsqueezy-webhook/index.ts +0 -0
- {sourcecode-1.73.0 → sourcecode-2.0.1}/supabase/functions/telemetry/index.ts +0 -0
- {sourcecode-1.73.0 → sourcecode-2.0.1}/supabase/sql/license_event_ordering.sql +0 -0
- {sourcecode-1.73.0 → sourcecode-2.0.1}/supabase/sql/telemetry_events.sql +0 -0
|
@@ -35,6 +35,10 @@ Thumbs.db
|
|
|
35
35
|
|
|
36
36
|
.planning
|
|
37
37
|
|
|
38
|
+
# ContextGraph baseline — full canonical outputs (multi-MB, from external repos).
|
|
39
|
+
# The light oracle (index.json) and convergence.json ARE committed.
|
|
40
|
+
tests/contextgraph_baseline/baseline/artifacts/
|
|
41
|
+
|
|
38
42
|
# Supabase local temp
|
|
39
43
|
supabase/.temp/
|
|
40
44
|
# Internal agent playbook (not distributed)
|
|
@@ -1,5 +1,31 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [2.0.1] — 2026-07-10
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
|
|
7
|
+
- **`migrate-check --compact` — bounded decision summary.** A new opt-in view of the
|
|
8
|
+
migration report that keeps every decision field (readiness scores, headline blocker,
|
|
9
|
+
blocking count, estimated effort, executive summary, Hibernate classification, aggregate
|
|
10
|
+
counts) and caps each large collection to its top items with a `+N more` count. On real
|
|
11
|
+
monoliths the report shrinks ~97% (BroadleafCommerce 1.2 MB → 32 KB, OpenMRS 908 KB →
|
|
12
|
+
23 KB) — an agent or reviewer gets the verdict without the leaf-level firehose. The full
|
|
13
|
+
report remains the default output.
|
|
14
|
+
|
|
15
|
+
### Fixed
|
|
16
|
+
|
|
17
|
+
- **`onboard` project summary no longer leaks raw README markup.** READMEs that open with
|
|
18
|
+
a logo block (`<img>` / `<p>` HTML) or use inline markdown links (`[label](url)`) had
|
|
19
|
+
produced summaries like `API: <img src="…"/>`. The summary is now rendered to clean prose
|
|
20
|
+
— HTML/badge blocks are skipped and inline links are unwrapped to their label.
|
|
21
|
+
- **More complete blast radius in `impact` and `review-pr`.** Change-impact analysis now
|
|
22
|
+
surfaces every affected entry point for a changed repository/service, reducing missed
|
|
23
|
+
callers on interface- and DI-wired code.
|
|
24
|
+
|
|
25
|
+
### Notes
|
|
26
|
+
|
|
27
|
+
- No breaking changes. Default command outputs are unchanged except where explicitly noted.
|
|
28
|
+
|
|
3
29
|
## [1.73.0] — 2026-07-01
|
|
4
30
|
|
|
5
31
|
### Fixed — audit reliability on a large non-Boot Spring monolith (Alfresco Community Repo field test)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: sourcecode
|
|
3
|
-
Version:
|
|
3
|
+
Version: 2.0.1
|
|
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
|
|
@@ -36,22 +36,26 @@ Provides-Extra: mcp
|
|
|
36
36
|
Requires-Dist: mcp>=1.0.0; extra == 'mcp'
|
|
37
37
|
Description-Content-Type: text/markdown
|
|
38
38
|
|
|
39
|
-
#
|
|
39
|
+
# ASK Engine
|
|
40
40
|
|
|
41
41
|
**Persistent structural context and ultra-fast repeated analysis for AI coding agents.**
|
|
42
42
|
|
|
43
|
-

|
|
44
44
|

|
|
45
45
|
|
|
46
|
+
> **ASK Engine** is the product. The CLI you install and run is **`sourcecode`** (the
|
|
47
|
+
> executable and Python package). The authoritative version is whatever
|
|
48
|
+
> `sourcecode version` reports.
|
|
49
|
+
|
|
46
50
|
---
|
|
47
51
|
|
|
48
52
|
## The problem
|
|
49
53
|
|
|
50
54
|
Every time an AI coding agent starts a new session, it has to re-parse the repository from scratch. For a large Java or TypeScript monolith, that means 5–15 seconds per invocation. Multiply by dozens of agent turns per hour, and repo context acquisition becomes a real bottleneck — not just latency, but tokens, compute, and iteration velocity.
|
|
51
55
|
|
|
52
|
-
|
|
56
|
+
ASK Engine solves this with a persistent structural cache keyed on file content hashes. After the first scan, every subsequent invocation returns pre-built context in milliseconds. The repo doesn't change? The cache doesn't expire.
|
|
53
57
|
|
|
54
|
-
**The cache is not a performance optimization. It is what makes
|
|
58
|
+
**The cache is not a performance optimization. It is what makes ASK Engine usable as infrastructure rather than a one-off tool.**
|
|
55
59
|
|
|
56
60
|
---
|
|
57
61
|
|
|
@@ -114,7 +118,7 @@ pipx install sourcecode
|
|
|
114
118
|
|
|
115
119
|
```bash
|
|
116
120
|
sourcecode version
|
|
117
|
-
# sourcecode
|
|
121
|
+
# sourcecode 2.0.1
|
|
118
122
|
```
|
|
119
123
|
|
|
120
124
|
---
|
|
@@ -168,7 +172,7 @@ sourcecode fix-bug /path/to/repo --symptom "NullPointerException in checkout"
|
|
|
168
172
|
|
|
169
173
|
## Cache system
|
|
170
174
|
|
|
171
|
-
|
|
175
|
+
ASK Engine maintains a persistent cache at `.sourcecode-cache/` inside each repository. Two layers:
|
|
172
176
|
|
|
173
177
|
- **L1 (core):** analysis result keyed by `(git_sha, analysis_flags)`. Survives format changes — you can regenerate `--compact` vs `--agent` views from the same core.
|
|
174
178
|
- **L2 (view):** rendered output keyed by `(core_hash, view_flags)`. Exact output match — no recomputation.
|
|
@@ -269,9 +273,9 @@ if [ "$DECISION" != "analysis_success" ]; then echo "Review failed: $DECISION";
|
|
|
269
273
|
|
|
270
274
|
---
|
|
271
275
|
|
|
272
|
-
## What
|
|
276
|
+
## What ASK Engine does (and doesn't)
|
|
273
277
|
|
|
274
|
-
**
|
|
278
|
+
**ASK Engine reduces exploration cost.** It accelerates context acquisition and minimizes repeated repo parsing. It does not replace reading code — it reduces how often an agent needs to.
|
|
275
279
|
|
|
276
280
|
Specifically:
|
|
277
281
|
|
|
@@ -317,7 +321,7 @@ when the work gets bigger or automated.
|
|
|
317
321
|
| Offline, no data egress, no account | ✅ | ✅ |
|
|
318
322
|
|
|
319
323
|
**Non-Java repos are free at any size** — the size limit counts Java source
|
|
320
|
-
files only, by design.
|
|
324
|
+
files only, by design. ASK Engine monetises enterprise Java monoliths.
|
|
321
325
|
|
|
322
326
|
```bash
|
|
323
327
|
sourcecode activate <key> # activate a license key
|
|
@@ -595,11 +599,19 @@ High-coupling nodes (high fan-in = risky to change), dead zone candidates (isola
|
|
|
595
599
|
|
|
596
600
|
```bash
|
|
597
601
|
sourcecode migrate-check /path/to/repo
|
|
602
|
+
sourcecode migrate-check . --compact # bounded decision summary (score, blocker, effort, top items)
|
|
598
603
|
sourcecode migrate-check . --min-severity high
|
|
599
604
|
sourcecode migrate-check . --format text
|
|
600
605
|
sourcecode migrate-check . --output migration.json
|
|
601
606
|
```
|
|
602
607
|
|
|
608
|
+
`--compact` returns the decision-grade envelope — readiness scores, headline blocker,
|
|
609
|
+
blocking count, estimated effort, executive summary, Hibernate classification and
|
|
610
|
+
aggregate counts — with each large collection capped to its top items (`+N more`). On
|
|
611
|
+
large monoliths this is ~97% smaller than the full report (e.g. 1.2 MB → 32 KB), so an
|
|
612
|
+
agent or reviewer gets the verdict without the full leaf-level firehose. The complete
|
|
613
|
+
report remains the default.
|
|
614
|
+
|
|
603
615
|
Detects migration blockers across Java source files, Spring XML config files, and Maven/Gradle build files. 27 rules organized by target:
|
|
604
616
|
|
|
605
617
|
**Jakarta namespace (MIG-001..009) — javax→jakarta**
|
|
@@ -1,19 +1,23 @@
|
|
|
1
|
-
#
|
|
1
|
+
# ASK Engine
|
|
2
2
|
|
|
3
3
|
**Persistent structural context and ultra-fast repeated analysis for AI coding agents.**
|
|
4
4
|
|
|
5
|
-

|
|
6
6
|

|
|
7
7
|
|
|
8
|
+
> **ASK Engine** is the product. The CLI you install and run is **`sourcecode`** (the
|
|
9
|
+
> executable and Python package). The authoritative version is whatever
|
|
10
|
+
> `sourcecode version` reports.
|
|
11
|
+
|
|
8
12
|
---
|
|
9
13
|
|
|
10
14
|
## The problem
|
|
11
15
|
|
|
12
16
|
Every time an AI coding agent starts a new session, it has to re-parse the repository from scratch. For a large Java or TypeScript monolith, that means 5–15 seconds per invocation. Multiply by dozens of agent turns per hour, and repo context acquisition becomes a real bottleneck — not just latency, but tokens, compute, and iteration velocity.
|
|
13
17
|
|
|
14
|
-
|
|
18
|
+
ASK Engine solves this with a persistent structural cache keyed on file content hashes. After the first scan, every subsequent invocation returns pre-built context in milliseconds. The repo doesn't change? The cache doesn't expire.
|
|
15
19
|
|
|
16
|
-
**The cache is not a performance optimization. It is what makes
|
|
20
|
+
**The cache is not a performance optimization. It is what makes ASK Engine usable as infrastructure rather than a one-off tool.**
|
|
17
21
|
|
|
18
22
|
---
|
|
19
23
|
|
|
@@ -76,7 +80,7 @@ pipx install sourcecode
|
|
|
76
80
|
|
|
77
81
|
```bash
|
|
78
82
|
sourcecode version
|
|
79
|
-
# sourcecode
|
|
83
|
+
# sourcecode 2.0.1
|
|
80
84
|
```
|
|
81
85
|
|
|
82
86
|
---
|
|
@@ -130,7 +134,7 @@ sourcecode fix-bug /path/to/repo --symptom "NullPointerException in checkout"
|
|
|
130
134
|
|
|
131
135
|
## Cache system
|
|
132
136
|
|
|
133
|
-
|
|
137
|
+
ASK Engine maintains a persistent cache at `.sourcecode-cache/` inside each repository. Two layers:
|
|
134
138
|
|
|
135
139
|
- **L1 (core):** analysis result keyed by `(git_sha, analysis_flags)`. Survives format changes — you can regenerate `--compact` vs `--agent` views from the same core.
|
|
136
140
|
- **L2 (view):** rendered output keyed by `(core_hash, view_flags)`. Exact output match — no recomputation.
|
|
@@ -231,9 +235,9 @@ if [ "$DECISION" != "analysis_success" ]; then echo "Review failed: $DECISION";
|
|
|
231
235
|
|
|
232
236
|
---
|
|
233
237
|
|
|
234
|
-
## What
|
|
238
|
+
## What ASK Engine does (and doesn't)
|
|
235
239
|
|
|
236
|
-
**
|
|
240
|
+
**ASK Engine reduces exploration cost.** It accelerates context acquisition and minimizes repeated repo parsing. It does not replace reading code — it reduces how often an agent needs to.
|
|
237
241
|
|
|
238
242
|
Specifically:
|
|
239
243
|
|
|
@@ -279,7 +283,7 @@ when the work gets bigger or automated.
|
|
|
279
283
|
| Offline, no data egress, no account | ✅ | ✅ |
|
|
280
284
|
|
|
281
285
|
**Non-Java repos are free at any size** — the size limit counts Java source
|
|
282
|
-
files only, by design.
|
|
286
|
+
files only, by design. ASK Engine monetises enterprise Java monoliths.
|
|
283
287
|
|
|
284
288
|
```bash
|
|
285
289
|
sourcecode activate <key> # activate a license key
|
|
@@ -557,11 +561,19 @@ High-coupling nodes (high fan-in = risky to change), dead zone candidates (isola
|
|
|
557
561
|
|
|
558
562
|
```bash
|
|
559
563
|
sourcecode migrate-check /path/to/repo
|
|
564
|
+
sourcecode migrate-check . --compact # bounded decision summary (score, blocker, effort, top items)
|
|
560
565
|
sourcecode migrate-check . --min-severity high
|
|
561
566
|
sourcecode migrate-check . --format text
|
|
562
567
|
sourcecode migrate-check . --output migration.json
|
|
563
568
|
```
|
|
564
569
|
|
|
570
|
+
`--compact` returns the decision-grade envelope — readiness scores, headline blocker,
|
|
571
|
+
blocking count, estimated effort, executive summary, Hibernate classification and
|
|
572
|
+
aggregate counts — with each large collection capped to its top items (`+N more`). On
|
|
573
|
+
large monoliths this is ~97% smaller than the full report (e.g. 1.2 MB → 32 KB), so an
|
|
574
|
+
agent or reviewer gets the verdict without the full leaf-level firehose. The complete
|
|
575
|
+
report remains the default.
|
|
576
|
+
|
|
565
577
|
Detects migration blockers across Java source files, Spring XML config files, and Maven/Gradle build files. 27 rules organized by target:
|
|
566
578
|
|
|
567
579
|
**Jakarta namespace (MIG-001..009) — javax→jakarta**
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Reproducible comparison harness for Increment #6 (Semantic Integration Engine spike).
|
|
3
|
+
|
|
4
|
+
Runs the legacy heuristic `integration_detector` and the new read-only
|
|
5
|
+
`SemanticIntegrationEngine` over one or more reference repos and prints the metrics
|
|
6
|
+
used in docs/architecture/REPORT-p6-integration-detector-comparison.md.
|
|
7
|
+
|
|
8
|
+
Usage:
|
|
9
|
+
python3 scripts/compare_integration_engines.py <repo_path> [<repo_path> ...]
|
|
10
|
+
|
|
11
|
+
Neither engine is wired into the product; evidence-gathering only.
|
|
12
|
+
"""
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
import sys
|
|
16
|
+
import time
|
|
17
|
+
from collections import Counter
|
|
18
|
+
from pathlib import Path
|
|
19
|
+
|
|
20
|
+
from sourcecode.context_graph import ContextGraph
|
|
21
|
+
from sourcecode.integration_detector import detect_integrations
|
|
22
|
+
from sourcecode.semantic_integration_engine import SemanticIntegrationEngine
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def run(repo: str) -> None:
|
|
26
|
+
root = Path(repo)
|
|
27
|
+
files = [str(p.relative_to(root)) for p in sorted(root.rglob("*.java"))
|
|
28
|
+
if "/test/" not in str(p)]
|
|
29
|
+
|
|
30
|
+
t0 = time.time()
|
|
31
|
+
det = detect_integrations(files, root)
|
|
32
|
+
det_s = time.time() - t0
|
|
33
|
+
|
|
34
|
+
t1 = time.time()
|
|
35
|
+
g = ContextGraph.build(files, root)
|
|
36
|
+
build_s = time.time() - t1
|
|
37
|
+
t2 = time.time()
|
|
38
|
+
sem = SemanticIntegrationEngine(g).as_report()
|
|
39
|
+
sem_s = time.time() - t2
|
|
40
|
+
|
|
41
|
+
det_kinds = Counter(r["kind"] for r in det["integrations"])
|
|
42
|
+
sem_kinds = Counter(r["kind"] for r in sem["integrations"])
|
|
43
|
+
det_targets = sum(1 for r in det["integrations"] if r.get("target"))
|
|
44
|
+
sem_targets = sum(1 for r in sem["integrations"] if r.get("target"))
|
|
45
|
+
|
|
46
|
+
print(f"### {root.name} (files={len(files)})")
|
|
47
|
+
print(f" detector : count={det['count']:>3} targets={det_targets:>3} "
|
|
48
|
+
f"kinds={dict(sorted(det_kinds.items()))} time={det_s:.2f}s source_read=yes")
|
|
49
|
+
print(f" semantic : count={sem['count']:>3} targets={sem_targets:>3} "
|
|
50
|
+
f"kinds={dict(sorted(sem_kinds.items()))} build={build_s:.2f}s detect={sem_s:.2f}s source_read=no")
|
|
51
|
+
only_sem = sorted(set(sem_kinds) - set(det_kinds))
|
|
52
|
+
only_det = sorted(set(det_kinds) - set(sem_kinds))
|
|
53
|
+
print(f" kinds only-semantic={only_sem} only-detector={only_det}")
|
|
54
|
+
print()
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
if __name__ == "__main__":
|
|
58
|
+
if len(sys.argv) < 2:
|
|
59
|
+
print(__doc__)
|
|
60
|
+
sys.exit(1)
|
|
61
|
+
for repo in sys.argv[1:]:
|
|
62
|
+
run(repo)
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Customer Smoke Test — verify in < 5 minutes that a real customer can install,
|
|
3
|
+
# activate, and use ASK Engine (Free + Pro), and that revocation removes access.
|
|
4
|
+
#
|
|
5
|
+
# Reproducible. Sections A/B/C escalate in what they require:
|
|
6
|
+
# A. Offline, no license — always runnable (also covered by tests/test_customer_smoke.py)
|
|
7
|
+
# B. Real activation — needs a real Pro key + network (set SC_SMOKE_KEY)
|
|
8
|
+
# C. Revocation — needs the key to be cancelled/expired server-side first
|
|
9
|
+
#
|
|
10
|
+
# Env:
|
|
11
|
+
# SC_CLI command to invoke the CLI (default: "sourcecode")
|
|
12
|
+
# SC_SMOKE_SMALL path to a small repo (<=500 Java) (default: the repo fixture)
|
|
13
|
+
# SC_SMOKE_LARGE path to a large repo (>500 Java) (optional; enables Pro gate check)
|
|
14
|
+
# SC_SMOKE_KEY a real Pro license key (optional; enables section B/C)
|
|
15
|
+
#
|
|
16
|
+
# Exit non-zero on the first failed check in a runnable section.
|
|
17
|
+
set -u
|
|
18
|
+
|
|
19
|
+
CLI="${SC_CLI:-sourcecode}"
|
|
20
|
+
HERE="$(cd "$(dirname "$0")/.." && pwd)"
|
|
21
|
+
SMALL="${SC_SMOKE_SMALL:-$HERE/tests/fixtures/spring_boot_minimal}"
|
|
22
|
+
pass() { printf ' \033[32mPASS\033[0m %s\n' "$1"; }
|
|
23
|
+
fail() { printf ' \033[31mFAIL\033[0m %s\n' "$1"; exit 1; }
|
|
24
|
+
skip() { printf ' \033[33mSKIP\033[0m %s\n' "$1"; }
|
|
25
|
+
|
|
26
|
+
echo "== A. Install + Free (offline, no license) =="
|
|
27
|
+
|
|
28
|
+
$CLI version >/dev/null 2>&1 || fail "install: 'version' did not run"
|
|
29
|
+
pass "installed — 'version' runs"
|
|
30
|
+
|
|
31
|
+
$CLI config >/dev/null 2>&1 || fail "'config' did not run"
|
|
32
|
+
pass "'config' runs"
|
|
33
|
+
|
|
34
|
+
# A Free command on a small repo must succeed (exit 0) with no license.
|
|
35
|
+
if $CLI endpoints "$SMALL" >/dev/null 2>&1; then
|
|
36
|
+
pass "Free command 'endpoints' runs on a small repo"
|
|
37
|
+
else
|
|
38
|
+
fail "Free command 'endpoints' failed on a small repo"
|
|
39
|
+
fi
|
|
40
|
+
|
|
41
|
+
# auth status with no license → unauthenticated.
|
|
42
|
+
if $CLI auth status 2>/dev/null | grep -q '"status": "unauthenticated"\|"status":"unauthenticated"'; then
|
|
43
|
+
pass "'auth status' reports unauthenticated with no license"
|
|
44
|
+
else
|
|
45
|
+
skip "'auth status' not unauthenticated (a license may already be present)"
|
|
46
|
+
fi
|
|
47
|
+
|
|
48
|
+
echo "== B. Activation (needs SC_SMOKE_KEY + network) =="
|
|
49
|
+
if [ -z "${SC_SMOKE_KEY:-}" ]; then
|
|
50
|
+
skip "SC_SMOKE_KEY not set — skipping real activation"
|
|
51
|
+
else
|
|
52
|
+
if $CLI activate "$SC_SMOKE_KEY" 2>/dev/null | grep -q '"status": *"activated"\|"status":"activated"'; then
|
|
53
|
+
pass "activation succeeded"
|
|
54
|
+
else
|
|
55
|
+
fail "activation did NOT return status=activated (check key + Edge Function deploy)"
|
|
56
|
+
fi
|
|
57
|
+
if $CLI auth status 2>/dev/null | grep -q '"pro": *true'; then
|
|
58
|
+
pass "'auth status' reports pro=true after activation"
|
|
59
|
+
else
|
|
60
|
+
fail "'auth status' does not report pro=true after activation"
|
|
61
|
+
fi
|
|
62
|
+
if [ -n "${SC_SMOKE_LARGE:-}" ]; then
|
|
63
|
+
if $CLI impact --help >/dev/null 2>&1 && $CLI modernize "$SC_SMOKE_LARGE" >/dev/null 2>&1; then
|
|
64
|
+
pass "Pro command 'modernize' runs on a large repo while activated"
|
|
65
|
+
else
|
|
66
|
+
fail "Pro command failed on a large repo while activated"
|
|
67
|
+
fi
|
|
68
|
+
else
|
|
69
|
+
skip "SC_SMOKE_LARGE not set — skipping Pro-on-large-repo check"
|
|
70
|
+
fi
|
|
71
|
+
fi
|
|
72
|
+
|
|
73
|
+
echo "== C. Revocation (cancel/expire the subscription server-side FIRST) =="
|
|
74
|
+
if [ -z "${SC_SMOKE_KEY:-}" ] || [ -z "${SC_SMOKE_LARGE:-}" ]; then
|
|
75
|
+
skip "needs SC_SMOKE_KEY + SC_SMOKE_LARGE (and a revoked key) — skipping"
|
|
76
|
+
else
|
|
77
|
+
echo " (assumes the key is now cancelled/expired and the revalidation TTL has elapsed;"
|
|
78
|
+
echo " set SOURCECODE_SUPABASE_URL to force a fresh check or wait out the 30-min TTL)"
|
|
79
|
+
out="$($CLI modernize "$SC_SMOKE_LARGE" 2>&1)"; code=$?
|
|
80
|
+
if [ "$code" = "2" ]; then
|
|
81
|
+
pass "revoked license → Pro command on large repo exits 2 (access removed)"
|
|
82
|
+
else
|
|
83
|
+
fail "revoked license did NOT gate (exit=$code) — revocation path is broken"
|
|
84
|
+
fi
|
|
85
|
+
fi
|
|
86
|
+
|
|
87
|
+
echo "Smoke test complete."
|
|
@@ -960,75 +960,83 @@ class ArchitectureAnalyzer:
|
|
|
960
960
|
# Java annotation / base-class secondary scanner (C4)
|
|
961
961
|
# ------------------------------------------------------------------
|
|
962
962
|
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
)
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
963
|
+
# Annotations never reported as "custom": Java/Spring/JPA/bean-validation
|
|
964
|
+
# built-ins. (Kept from the historical text scan; the counts now come from
|
|
965
|
+
# the ContextGraph's node-grounded `annotated_with` relations.)
|
|
966
|
+
_BUILTIN_ANNOTATIONS: frozenset = frozenset({
|
|
967
|
+
"Override", "SuppressWarnings", "Deprecated", "FunctionalInterface",
|
|
968
|
+
"SafeVarargs", "Retention", "Target", "Documented", "Inherited",
|
|
969
|
+
"RestController", "Service", "Repository", "Component", "Controller",
|
|
970
|
+
"Autowired", "Value", "Bean", "Configuration", "SpringBootApplication",
|
|
971
|
+
"EnableAutoConfiguration", "Transactional", "RequestMapping",
|
|
972
|
+
"GetMapping", "PostMapping", "PutMapping", "DeleteMapping",
|
|
973
|
+
"PathVariable", "RequestBody", "RequestParam", "ResponseBody",
|
|
974
|
+
"NotNull", "NotBlank", "Size", "Min", "Max", "Valid",
|
|
975
|
+
})
|
|
976
|
+
# @interface member return types the historical scan reported as parameters.
|
|
977
|
+
_ANNOTATION_PARAM_TYPES: frozenset = frozenset({"String", "int", "boolean", "Class"})
|
|
974
978
|
|
|
975
979
|
def _scan_java_patterns(
|
|
976
980
|
self,
|
|
977
981
|
root: Path,
|
|
978
982
|
java_paths: list[str],
|
|
979
983
|
) -> tuple[list[dict], list[dict]]:
|
|
980
|
-
"""
|
|
984
|
+
"""Derive custom @interface annotations and common base classes from the
|
|
985
|
+
ContextGraph (the single structural access layer — no source parsing).
|
|
986
|
+
|
|
987
|
+
Counts are node-grounded: an annotation usage is an `annotated_with`
|
|
988
|
+
relation on a real symbol, and a subclass is an `extends` relation —
|
|
989
|
+
occurrences inside string literals, comments, or dead text never count
|
|
990
|
+
(the historical text scan could).
|
|
981
991
|
|
|
982
992
|
Returns (custom_annotations, base_classes) lists for ArchitectureAnalysis.
|
|
983
993
|
"""
|
|
984
|
-
from sourcecode.
|
|
994
|
+
from sourcecode.context_graph import ContextGraph
|
|
995
|
+
|
|
996
|
+
graph = ContextGraph.build(java_paths, root)
|
|
985
997
|
|
|
986
|
-
annotation_decls: dict[str, dict] = {} # name → {params,
|
|
987
|
-
annotation_usage: dict[str, int] = {} # name →
|
|
998
|
+
annotation_decls: dict[str, dict] = {} # name → {params, usage_count, purpose}
|
|
999
|
+
annotation_usage: dict[str, int] = {} # name → node-grounded usages
|
|
988
1000
|
extends_counts: dict[str, int] = {} # base_class_name → subclass count
|
|
989
1001
|
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
1002
|
+
# Custom annotation declarations: @interface nodes + their members with
|
|
1003
|
+
# the historically-reported parameter return types.
|
|
1004
|
+
for ann in graph.annotation_types():
|
|
1005
|
+
ann_name = ann.fqn.rsplit(".", 1)[-1]
|
|
1006
|
+
if ann_name in annotation_decls:
|
|
995
1007
|
continue
|
|
1008
|
+
prefix = ann.fqn + "#"
|
|
1009
|
+
# Member return type from the node signature "(params)->ReturnType".
|
|
1010
|
+
params = [
|
|
1011
|
+
m.fqn[len(prefix):]
|
|
1012
|
+
for m in graph.symbols(kind="method")
|
|
1013
|
+
if m.fqn.startswith(prefix)
|
|
1014
|
+
and (m.signature or "").rsplit("->", 1)[-1] in self._ANNOTATION_PARAM_TYPES
|
|
1015
|
+
]
|
|
1016
|
+
annotation_decls[ann_name] = {
|
|
1017
|
+
"name": ann_name,
|
|
1018
|
+
"parameters": params,
|
|
1019
|
+
"usage_count": 0,
|
|
1020
|
+
"purpose": "",
|
|
1021
|
+
}
|
|
996
1022
|
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
ann_name = m.group(1)
|
|
1015
|
-
# Skip common Java built-ins
|
|
1016
|
-
if ann_name in {
|
|
1017
|
-
"Override", "SuppressWarnings", "Deprecated", "FunctionalInterface",
|
|
1018
|
-
"SafeVarargs", "Retention", "Target", "Documented", "Inherited",
|
|
1019
|
-
"RestController", "Service", "Repository", "Component", "Controller",
|
|
1020
|
-
"Autowired", "Value", "Bean", "Configuration", "SpringBootApplication",
|
|
1021
|
-
"EnableAutoConfiguration", "Transactional", "RequestMapping",
|
|
1022
|
-
"GetMapping", "PostMapping", "PutMapping", "DeleteMapping",
|
|
1023
|
-
"PathVariable", "RequestBody", "RequestParam", "ResponseBody",
|
|
1024
|
-
"NotNull", "NotBlank", "Size", "Min", "Max", "Valid",
|
|
1025
|
-
}:
|
|
1026
|
-
continue
|
|
1027
|
-
annotation_usage[ann_name] = annotation_usage.get(ann_name, 0) + 1
|
|
1028
|
-
|
|
1029
|
-
# extends BaseClass — count subclasses per base
|
|
1030
|
-
for m in self._EXTENDS_RE.finditer(content):
|
|
1031
|
-
base = m.group(1)
|
|
1023
|
+
# Annotation usage frequency — one count per annotated_with relation.
|
|
1024
|
+
for rel in graph.relations(kind="annotated_with"):
|
|
1025
|
+
ann_name = rel.target.lstrip("@").rsplit(".", 1)[-1]
|
|
1026
|
+
if not ann_name or not ann_name[0].isupper():
|
|
1027
|
+
continue
|
|
1028
|
+
if ann_name in self._BUILTIN_ANNOTATIONS:
|
|
1029
|
+
continue
|
|
1030
|
+
annotation_usage[ann_name] = annotation_usage.get(ann_name, 0) + 1
|
|
1031
|
+
|
|
1032
|
+
# extends BaseClass — count subclasses per base (class extends only,
|
|
1033
|
+
# matching the historical `class X extends Y` scan).
|
|
1034
|
+
class_fqns = {s.fqn for s in graph.symbols(kind="class")}
|
|
1035
|
+
for rel in graph.relations(kind="extends"):
|
|
1036
|
+
if rel.source not in class_fqns:
|
|
1037
|
+
continue # interface-extends-interface is not a subclass count
|
|
1038
|
+
base = re.sub(r"<.*", "", rel.target).rsplit(".", 1)[-1].strip()
|
|
1039
|
+
if base and base[0].isupper():
|
|
1032
1040
|
extends_counts[base] = extends_counts.get(base, 0) + 1
|
|
1033
1041
|
|
|
1034
1042
|
# Merge usage counts into annotation_decls
|