sourcecode 2.0.0__py3-none-any.whl → 2.0.1__py3-none-any.whl
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/__init__.py +2 -6
- sourcecode/cli.py +196 -199
- sourcecode/license.py +13 -31
- sourcecode/mcp/onboarding/applier.py +6 -14
- sourcecode/mcp/registry.py +22 -22
- sourcecode/mcp/runner.py +2 -2
- sourcecode/mcp/server.py +24 -24
- sourcecode/mcp_nudge.py +1 -1
- sourcecode/migrate_check.py +64 -0
- sourcecode/repository_ir.py +1 -1
- sourcecode/ris.py +1 -1
- sourcecode/serializer.py +1 -1
- sourcecode/summarizer.py +33 -2
- sourcecode/telemetry/consent.py +1 -1
- {sourcecode-2.0.0.dist-info → sourcecode-2.0.1.dist-info}/METADATA +114 -117
- {sourcecode-2.0.0.dist-info → sourcecode-2.0.1.dist-info}/RECORD +19 -19
- {sourcecode-2.0.0.dist-info → sourcecode-2.0.1.dist-info}/entry_points.txt +0 -1
- {sourcecode-2.0.0.dist-info → sourcecode-2.0.1.dist-info}/WHEEL +0 -0
- {sourcecode-2.0.0.dist-info → sourcecode-2.0.1.dist-info}/licenses/LICENSE +0 -0
sourcecode/license.py
CHANGED
|
@@ -89,25 +89,9 @@ if _SUPABASE_URL != _DEFAULT_SUPABASE_URL:
|
|
|
89
89
|
# which case prompts point to the documented Pro plans instead of a placeholder link.
|
|
90
90
|
_PURCHASE_URL: str = os.environ.get("SOURCECODE_PURCHASE_URL", "").strip()
|
|
91
91
|
|
|
92
|
-
|
|
93
|
-
# stays READABLE so no existing user loses their license — reads prefer ~/.ask and
|
|
94
|
-
# fall back to ~/.sourcecode; writes always land in ~/.ask. Backward-compatible.
|
|
95
|
-
_LICENSE_DIR: Path = Path.home() / ".ask"
|
|
96
|
-
_LEGACY_LICENSE_DIR: Path = Path.home() / ".sourcecode"
|
|
92
|
+
_LICENSE_DIR: Path = Path.home() / ".sourcecode"
|
|
97
93
|
_LICENSE_FILE: Path = _LICENSE_DIR / "license.json"
|
|
98
94
|
_DELTA_RUNS_FILE: Path = _LICENSE_DIR / "delta_runs.json"
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
def _read_config_path(name: str) -> Path:
|
|
102
|
-
"""Existing config file for `name`, preferring ~/.ask, else legacy ~/.sourcecode.
|
|
103
|
-
Returns the canonical ~/.ask path when neither exists (write target)."""
|
|
104
|
-
new = _LICENSE_DIR / name
|
|
105
|
-
if new.exists():
|
|
106
|
-
return new
|
|
107
|
-
legacy = _LEGACY_LICENSE_DIR / name
|
|
108
|
-
if legacy.exists():
|
|
109
|
-
return legacy
|
|
110
|
-
return new
|
|
111
95
|
_CACHE_TTL_SECONDS: int = 1800 # 30 minutes default; CI env overrides to 24h (see _get_cache_ttl)
|
|
112
96
|
_CACHE_TTL_CI_SECONDS: int = 86400 # 24 hours — CI containers must not re-validate mid-run
|
|
113
97
|
|
|
@@ -212,7 +196,7 @@ _PRO_UNLOCK_ALL = os.environ.get("SOURCECODE_PRO_UNLOCK", "1") != "0"
|
|
|
212
196
|
|
|
213
197
|
|
|
214
198
|
def _secure_dir() -> None:
|
|
215
|
-
"""Create ~/.
|
|
199
|
+
"""Create ~/.sourcecode owner-only (0700). Holds the license secret.
|
|
216
200
|
|
|
217
201
|
``mkdir(mode=...)`` is ignored when the dir already exists, so chmod
|
|
218
202
|
unconditionally. Best-effort: a chmod failure (e.g. Windows) is non-fatal.
|
|
@@ -251,9 +235,8 @@ def _write_license_file(data: dict) -> None:
|
|
|
251
235
|
|
|
252
236
|
def _read_delta_runs() -> dict:
|
|
253
237
|
try:
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
return json.loads(src.read_text(encoding="utf-8"))
|
|
238
|
+
if _DELTA_RUNS_FILE.exists():
|
|
239
|
+
return json.loads(_DELTA_RUNS_FILE.read_text(encoding="utf-8"))
|
|
257
240
|
except Exception:
|
|
258
241
|
pass
|
|
259
242
|
return {}
|
|
@@ -285,12 +268,11 @@ def check_delta_free_tier(repo_path: str) -> "tuple[bool, int, int]":
|
|
|
285
268
|
|
|
286
269
|
|
|
287
270
|
def _load_license_file() -> Optional[dict]:
|
|
288
|
-
"""Read
|
|
289
|
-
~/.sourcecode. Returns parsed dict or None."""
|
|
271
|
+
"""Read ~/.sourcecode/license.json. Returns parsed dict or None."""
|
|
290
272
|
try:
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
return json.loads(
|
|
273
|
+
if _LICENSE_FILE.exists():
|
|
274
|
+
raw = _LICENSE_FILE.read_text(encoding="utf-8")
|
|
275
|
+
return json.loads(raw)
|
|
294
276
|
except Exception:
|
|
295
277
|
pass
|
|
296
278
|
return None
|
|
@@ -427,7 +409,7 @@ def _emit_upgrade_and_exit(headline: str, body_lines: list[str], payload: dict)
|
|
|
427
409
|
lines.append(" Unlock Pro:")
|
|
428
410
|
where = _PURCHASE_URL or "see Pro plans (docs/PRODUCT_TIERS.md)"
|
|
429
411
|
lines.append(f" 1. Get a license: {where}")
|
|
430
|
-
lines.append(" 2. Activate:
|
|
412
|
+
lines.append(" 2. Activate: sourcecode activate <license_key>")
|
|
431
413
|
lines.append("")
|
|
432
414
|
sys.stderr.write("\n".join(lines) + "\n")
|
|
433
415
|
sys.stderr.flush()
|
|
@@ -497,9 +479,9 @@ def require_feature(
|
|
|
497
479
|
"feature": feature_name,
|
|
498
480
|
"message": (
|
|
499
481
|
f"'{display}' requires a Pro license. "
|
|
500
|
-
"Run:
|
|
482
|
+
"Run: sourcecode activate <license_key>"
|
|
501
483
|
),
|
|
502
|
-
"upgrade_hint": "
|
|
484
|
+
"upgrade_hint": "sourcecode activate <license_key>",
|
|
503
485
|
}
|
|
504
486
|
if extra_fields:
|
|
505
487
|
payload.update(extra_fields)
|
|
@@ -548,9 +530,9 @@ def require_repo_or_pro(
|
|
|
548
530
|
f"This repository exceeds the free-tier limit of "
|
|
549
531
|
f"{_FREE_REPO_JAVA_FILE_LIMIT} Java source files. "
|
|
550
532
|
"Pro unlocks enterprise-scale monoliths. "
|
|
551
|
-
"Run:
|
|
533
|
+
"Run: sourcecode activate <license_key>"
|
|
552
534
|
),
|
|
553
|
-
"upgrade_hint": "
|
|
535
|
+
"upgrade_hint": "sourcecode activate <license_key>",
|
|
554
536
|
}
|
|
555
537
|
if extra_fields:
|
|
556
538
|
payload.update(extra_fields)
|
|
@@ -6,10 +6,9 @@ import os
|
|
|
6
6
|
from pathlib import Path
|
|
7
7
|
|
|
8
8
|
_MCP_SERVERS_KEY = "mcpServers"
|
|
9
|
-
_ENTRY_NAME = "
|
|
10
|
-
_LEGACY_ENTRY_NAME = "sourcecode" # deprecated alias — still recognized/cleaned
|
|
9
|
+
_ENTRY_NAME = "sourcecode"
|
|
11
10
|
_ENTRY_VALUE: dict[str, object] = {
|
|
12
|
-
"command": "
|
|
11
|
+
"command": "sourcecode",
|
|
13
12
|
"args": ["mcp", "serve"],
|
|
14
13
|
}
|
|
15
14
|
|
|
@@ -26,31 +25,24 @@ def read_config(path: Path) -> dict:
|
|
|
26
25
|
|
|
27
26
|
|
|
28
27
|
def is_installed(config: dict) -> bool:
|
|
29
|
-
"""True if
|
|
30
|
-
|
|
31
|
-
servers = config.get(_MCP_SERVERS_KEY, {})
|
|
32
|
-
return _ENTRY_NAME in servers or _LEGACY_ENTRY_NAME in servers
|
|
28
|
+
"""True if sourcecode entry already present in mcpServers."""
|
|
29
|
+
return _ENTRY_NAME in config.get(_MCP_SERVERS_KEY, {})
|
|
33
30
|
|
|
34
31
|
|
|
35
32
|
def apply_entry(config: dict) -> dict:
|
|
36
|
-
"""Return new config dict with
|
|
37
|
-
Any legacy `sourcecode` entry is migrated away (removed) so the client launches
|
|
38
|
-
a single server."""
|
|
33
|
+
"""Return new config dict with sourcecode merged into mcpServers."""
|
|
39
34
|
config = dict(config)
|
|
40
35
|
servers: dict = dict(config.get(_MCP_SERVERS_KEY, {}))
|
|
41
|
-
servers.pop(_LEGACY_ENTRY_NAME, None)
|
|
42
36
|
servers[_ENTRY_NAME] = _ENTRY_VALUE
|
|
43
37
|
config[_MCP_SERVERS_KEY] = servers
|
|
44
38
|
return config
|
|
45
39
|
|
|
46
40
|
|
|
47
41
|
def remove_entry(config: dict) -> dict:
|
|
48
|
-
"""Return new config dict with
|
|
49
|
-
`ask` key and any legacy `sourcecode` key."""
|
|
42
|
+
"""Return new config dict with sourcecode removed from mcpServers."""
|
|
50
43
|
config = dict(config)
|
|
51
44
|
servers: dict = dict(config.get(_MCP_SERVERS_KEY, {}))
|
|
52
45
|
servers.pop(_ENTRY_NAME, None)
|
|
53
|
-
servers.pop(_LEGACY_ENTRY_NAME, None)
|
|
54
46
|
if servers:
|
|
55
47
|
config[_MCP_SERVERS_KEY] = servers
|
|
56
48
|
elif _MCP_SERVERS_KEY in config:
|
sourcecode/mcp/registry.py
CHANGED
|
@@ -416,7 +416,7 @@ def make_tool_callable(spec: ToolSpec) -> Callable[..., Any]:
|
|
|
416
416
|
|
|
417
417
|
def _canonical_spec_for_runtime_command(runtime: RuntimeCommand) -> ToolSpec:
|
|
418
418
|
params = tuple(_click_to_param_spec(param) for param in runtime.command.params)
|
|
419
|
-
runtime_command = " ".join(runtime.path) if runtime.path else "
|
|
419
|
+
runtime_command = " ".join(runtime.path) if runtime.path else "sourcecode"
|
|
420
420
|
name = _tool_name_for_path(runtime.path)
|
|
421
421
|
doc = runtime.docstring or runtime.help or ""
|
|
422
422
|
description = _first_doc_line(doc) or f"CLI command: {runtime_command}"
|
|
@@ -510,7 +510,7 @@ def _alias_spec(
|
|
|
510
510
|
not_exposed_to_cli=not_exposed_to_cli,
|
|
511
511
|
mcp_hidden=mcp_hidden,
|
|
512
512
|
docstring=doc,
|
|
513
|
-
runtime_command=" ".join(cli_path) if cli_path else "
|
|
513
|
+
runtime_command=" ".join(cli_path) if cli_path else "sourcecode",
|
|
514
514
|
_argv_builder=argv_builder,
|
|
515
515
|
validator=validator,
|
|
516
516
|
)
|
|
@@ -552,7 +552,7 @@ Returns: stacks, entry points, dependency summary, architecture summary, confide
|
|
|
552
552
|
Includes security_surface, mybatis, and transactional_boundaries for Java/Spring projects.
|
|
553
553
|
For richer machine-oriented detail (deeper signals, more sections), use get_agent_context.
|
|
554
554
|
|
|
555
|
-
Maps to:
|
|
555
|
+
Maps to: sourcecode <repo_path> --compact [--git-context]
|
|
556
556
|
repo_path: absolute path to the repository (default: current working directory).
|
|
557
557
|
git_context: include git log and branch context in the analysis.
|
|
558
558
|
"""
|
|
@@ -565,7 +565,7 @@ lacks sufficient detail. Includes all compact fields plus: env_map, code_notes,
|
|
|
565
565
|
architecture layers, security surface, transactional boundaries, module graph summary.
|
|
566
566
|
Prefer get_compact_context for quick orientation or token-constrained workflows.
|
|
567
567
|
|
|
568
|
-
Maps to:
|
|
568
|
+
Maps to: sourcecode <repo_path> --agent [--git-context]
|
|
569
569
|
repo_path: absolute path to the repository (default: current working directory).
|
|
570
570
|
git_context: include git log and branch context in the analysis.
|
|
571
571
|
"""
|
|
@@ -573,7 +573,7 @@ git_context: include git log and branch context in the analysis.
|
|
|
573
573
|
_GET_MODULE_DOC = """\
|
|
574
574
|
Compact analysis of a specific module or subdirectory within a repository.
|
|
575
575
|
|
|
576
|
-
Maps to:
|
|
576
|
+
Maps to: sourcecode <repo_path>/<module> --compact
|
|
577
577
|
repo_path: absolute path to the repository root.
|
|
578
578
|
module: subdirectory name relative to repo_path (e.g. 'src/auth', 'api', 'core').
|
|
579
579
|
Returns: same fields as get_compact_context but scoped to the module subtree.
|
|
@@ -582,7 +582,7 @@ Returns: same fields as get_compact_context but scoped to the module subtree.
|
|
|
582
582
|
_TELEMETRY_DOC = """\
|
|
583
583
|
Manage telemetry settings.
|
|
584
584
|
|
|
585
|
-
Maps to:
|
|
585
|
+
Maps to: sourcecode telemetry <action>
|
|
586
586
|
action: one of "status" (show current state), "enable" (opt in), "disable" (opt out).
|
|
587
587
|
Valid values: "status" | "enable" | "disable"
|
|
588
588
|
"""
|
|
@@ -689,7 +689,7 @@ def _prepare_context_aliases() -> list[ToolSpec]:
|
|
|
689
689
|
_GET_DELTA_DOC = """\
|
|
690
690
|
Incremental context: git-changed files since a reference commit.
|
|
691
691
|
|
|
692
|
-
Maps to:
|
|
692
|
+
Maps to: sourcecode prepare-context delta <repo_path> --since <since>
|
|
693
693
|
repo_path: absolute path to the repository (default: current working directory).
|
|
694
694
|
since: git ref to diff against (e.g. HEAD~3, main, origin/main).
|
|
695
695
|
If empty or omitted, auto-detects merge-base with origin/main (or
|
|
@@ -700,7 +700,7 @@ since: git ref to diff against (e.g. HEAD~3, main, origin/main).
|
|
|
700
700
|
_REVIEW_PR_DOC = """\
|
|
701
701
|
Execution paths and risk analysis for changed files in a pull request.
|
|
702
702
|
|
|
703
|
-
Maps to:
|
|
703
|
+
Maps to: sourcecode prepare-context review-pr <repo_path> [--since <since>]
|
|
704
704
|
Returns: compact_base + execution_paths (diff-scoped) + hotspots for changed files.
|
|
705
705
|
repo_path: absolute path to the repository (default: current working directory).
|
|
706
706
|
since: git ref to diff against (e.g. HEAD~3, main, origin/main).
|
|
@@ -710,7 +710,7 @@ since: git ref to diff against (e.g. HEAD~3, main, origin/main).
|
|
|
710
710
|
_FIX_BUG_DOC = """\
|
|
711
711
|
Risk-ranked files for bug investigation, optionally focused by symptom.
|
|
712
712
|
|
|
713
|
-
Maps to:
|
|
713
|
+
Maps to: sourcecode prepare-context fix-bug <repo_path> [--symptom <symptom>]
|
|
714
714
|
Includes compact_base: security_surface, transactional_boundaries, spring_profiles.
|
|
715
715
|
repo_path: absolute path to the repository (default: current working directory).
|
|
716
716
|
symptom: optional error message or class name to focus the file ranking
|
|
@@ -722,7 +722,7 @@ symptom: optional error message or class name to focus the file ranking
|
|
|
722
722
|
_ONBOARD_DOC = """\
|
|
723
723
|
Onboarding context: structured overview for new contributors.
|
|
724
724
|
|
|
725
|
-
Maps to:
|
|
725
|
+
Maps to: sourcecode prepare-context onboard <repo_path>
|
|
726
726
|
Returns: project structure, key entry points, architectural patterns, getting-started guide.
|
|
727
727
|
repo_path: absolute path to the repository (default: current working directory).
|
|
728
728
|
"""
|
|
@@ -730,7 +730,7 @@ repo_path: absolute path to the repository (default: current working directory).
|
|
|
730
730
|
_EXPLAIN_DOC = """\
|
|
731
731
|
Architecture and entry-point explanation for a repository.
|
|
732
732
|
|
|
733
|
-
Maps to:
|
|
733
|
+
Maps to: sourcecode prepare-context explain <repo_path>
|
|
734
734
|
Returns: project summary, architecture overview, entry points, key dependencies.
|
|
735
735
|
repo_path: absolute path to the repository (default: current working directory).
|
|
736
736
|
"""
|
|
@@ -738,7 +738,7 @@ repo_path: absolute path to the repository (default: current working directory).
|
|
|
738
738
|
_REFACTOR_DOC = """\
|
|
739
739
|
Structural issues and refactor opportunities for a repository.
|
|
740
740
|
|
|
741
|
-
Maps to:
|
|
741
|
+
Maps to: sourcecode prepare-context refactor <repo_path>
|
|
742
742
|
Returns: structural issues, coupling hotspots, high-churn files, improvement opportunities.
|
|
743
743
|
repo_path: absolute path to the repository (default: current working directory).
|
|
744
744
|
"""
|
|
@@ -746,7 +746,7 @@ repo_path: absolute path to the repository (default: current working directory).
|
|
|
746
746
|
_GENERATE_TESTS_DOC = """\
|
|
747
747
|
Untested source files and test gap analysis for a repository.
|
|
748
748
|
|
|
749
|
-
Maps to:
|
|
749
|
+
Maps to: sourcecode prepare-context generate-tests <repo_path> [--all]
|
|
750
750
|
Returns: test_gaps list of untested files ranked by risk.
|
|
751
751
|
On large repos (>2000 classes) analysis is bounded by SOURCECODE_TESTS_TIMEOUT_MS
|
|
752
752
|
(default: 15000 ms). If timeout elapses, returns truncated=true with partial results.
|
|
@@ -757,7 +757,7 @@ include_all: return full test_gaps list without truncating to top 20.
|
|
|
757
757
|
_IR_SUMMARY_DOC = """\
|
|
758
758
|
Deterministic symbol-level IR summary for Java repositories. Java only.
|
|
759
759
|
|
|
760
|
-
Maps to:
|
|
760
|
+
Maps to: sourcecode repo-ir <repo_path> --summary-only
|
|
761
761
|
Returns: reverse_graph, route_surface (top 50 endpoints),
|
|
762
762
|
subsystems (top 15), impact, analysis. Full graph nodes/edges omitted.
|
|
763
763
|
|
|
@@ -770,7 +770,7 @@ subsystems: list of detected subsystem cluster dicts.
|
|
|
770
770
|
analysis: metadata — total_classes, total_edges, analysis_ms.
|
|
771
771
|
|
|
772
772
|
Output is bounded to ~100 KB for LLM safety. For full IR (can exceed 10 MB
|
|
773
|
-
on large repos), use the CLI:
|
|
773
|
+
on large repos), use the CLI: sourcecode repo-ir <path> --output ir.json
|
|
774
774
|
Use get_compact_context or get_agent_context for non-Java repos.
|
|
775
775
|
|
|
776
776
|
repo_path: absolute path to the Java repository (default: current working directory).
|
|
@@ -779,7 +779,7 @@ repo_path: absolute path to the Java repository (default: current working direct
|
|
|
779
779
|
_IMPACT_CONTEXT_DOC = """\
|
|
780
780
|
Blast-radius analysis: who calls a class and what breaks if it changes? Java only.
|
|
781
781
|
|
|
782
|
-
Maps to:
|
|
782
|
+
Maps to: sourcecode impact <target> <repo_path> [--depth <depth>]
|
|
783
783
|
Returns: direct_callers, indirect_callers, endpoints_affected,
|
|
784
784
|
transactional_boundaries_touched, risk_score, risk_level, stats.
|
|
785
785
|
|
|
@@ -797,7 +797,7 @@ depth: BFS depth for indirect caller traversal (1–8, default: 4).
|
|
|
797
797
|
_MODERNIZE_DOC = """\
|
|
798
798
|
Analyzes codebase for modernization opportunities: dead zones, hotspot scores, upgrade candidates.
|
|
799
799
|
|
|
800
|
-
Maps to:
|
|
800
|
+
Maps to: sourcecode modernize <repo_path>
|
|
801
801
|
Returns: hotspot_candidates (high fan-in + git churn), statically_unreferenced (zero-caller classes — NOT confirmed dead; verify no framework dispatch) + framework_dispatched,
|
|
802
802
|
high_coupling_nodes, subsystem_summary, cross_module_tangles, recommendation.
|
|
803
803
|
|
|
@@ -852,7 +852,7 @@ REST API endpoint surface extraction from Java source files. JAVA ONLY.
|
|
|
852
852
|
Do NOT call this on non-Java repositories — it will return empty results.
|
|
853
853
|
Use get_compact_context or get_agent_context for non-Java repos.
|
|
854
854
|
|
|
855
|
-
Maps to:
|
|
855
|
+
Maps to: sourcecode endpoints <repo_path>
|
|
856
856
|
Returns: endpoints list with method, path, controller, handler fields;
|
|
857
857
|
security dict always present (policy: roles_allowed|permit_all|deny_all|
|
|
858
858
|
authenticated|...|none_detected); none_detected = no auth annotation found.
|
|
@@ -879,7 +879,7 @@ must satisfy before generating a payload, a test, or reasoning about a 400:
|
|
|
879
879
|
* hand-written custom validators (@Constraint + ConstraintValidator, e.g.
|
|
880
880
|
PetAgeValidator), linked to fields via x-field-extra-annotation.
|
|
881
881
|
|
|
882
|
-
Maps to:
|
|
882
|
+
Maps to: sourcecode validation <repo_path>
|
|
883
883
|
Returns: endpoints[] (method, path, controller, handler, schema, validatedFields[
|
|
884
884
|
{name, rules[{kind,value}], customValidators[{annotation,validators,message,resolved}]}]),
|
|
885
885
|
custom_validators[] (catalog: annotation, validators, message, validatedTypes, targets),
|
|
@@ -894,7 +894,7 @@ gaps_only: when true, return only the gaps section (endpoints lacking validation
|
|
|
894
894
|
_CACHE_STATUS_DOC = """\
|
|
895
895
|
Report cache metadata for a repository.
|
|
896
896
|
|
|
897
|
-
Maps to:
|
|
897
|
+
Maps to: sourcecode cache status <repo_path> --json
|
|
898
898
|
Returns: cache entries with git_head, timestamps, size info.
|
|
899
899
|
Use check_freshness for RIS-specific freshness checking (faster, richer).
|
|
900
900
|
repo_path: absolute path to the repository (default: current working directory).
|
|
@@ -903,7 +903,7 @@ repo_path: absolute path to the repository (default: current working directory).
|
|
|
903
903
|
_CACHE_WARM_DOC = """\
|
|
904
904
|
Warm the cache for a repository (builds compact and agent analysis snapshots).
|
|
905
905
|
|
|
906
|
-
Maps to:
|
|
906
|
+
Maps to: sourcecode cache warm <repo_path>
|
|
907
907
|
Builds or refreshes the Repository Intelligence Snapshot (RIS).
|
|
908
908
|
Use before analytical workflows to ensure fast subsequent tool calls (~8s first run,
|
|
909
909
|
instant after).
|
|
@@ -913,7 +913,7 @@ repo_path: absolute path to the repository (default: current working directory).
|
|
|
913
913
|
_CACHE_CLEAR_DOC = """\
|
|
914
914
|
Clear cached analysis for a repository.
|
|
915
915
|
|
|
916
|
-
Maps to:
|
|
916
|
+
Maps to: sourcecode cache clear <repo_path> [--include-ris]
|
|
917
917
|
Removes cached context files. After clearing, run get_compact_context or cache_warm to rebuild.
|
|
918
918
|
include_ris: also remove the RIS snapshot in addition to analysis cache (default: False).
|
|
919
919
|
repo_path: absolute path to the repository (default: current working directory).
|
sourcecode/mcp/runner.py
CHANGED
|
@@ -66,7 +66,7 @@ def run_command(args: list[str]) -> Any:
|
|
|
66
66
|
payload = _parsed
|
|
67
67
|
break
|
|
68
68
|
raise CommandError(
|
|
69
|
-
f"
|
|
69
|
+
f"sourcecode command failed (exit {result.exit_code}). Args: {args}",
|
|
70
70
|
exit_code=result.exit_code,
|
|
71
71
|
stdout=stdout,
|
|
72
72
|
stderr=stderr,
|
|
@@ -76,7 +76,7 @@ def run_command(args: list[str]) -> Any:
|
|
|
76
76
|
output = (result.output or "").strip()
|
|
77
77
|
if not output:
|
|
78
78
|
raise RuntimeError(
|
|
79
|
-
f"
|
|
79
|
+
f"sourcecode command produced no output.\n"
|
|
80
80
|
f"Args: {args}"
|
|
81
81
|
)
|
|
82
82
|
|
sourcecode/mcp/server.py
CHANGED
|
@@ -108,14 +108,14 @@ def _coerce_cli_error(exc: Exception, default_message: str) -> CallToolResult:
|
|
|
108
108
|
and "pro" in payload.get("error", "").lower()
|
|
109
109
|
):
|
|
110
110
|
feature = payload.get("feature", "")
|
|
111
|
-
msg = payload.get("message", f"'{feature}' requires a Pro license. Run:
|
|
111
|
+
msg = payload.get("message", f"'{feature}' requires a Pro license. Run: sourcecode activate <key>")
|
|
112
112
|
structured = {
|
|
113
113
|
"success": False,
|
|
114
114
|
"data": None,
|
|
115
115
|
"error": build_error_object(
|
|
116
116
|
"PRO_REQUIRED",
|
|
117
117
|
msg,
|
|
118
|
-
hint="
|
|
118
|
+
hint="sourcecode activate <license_key>",
|
|
119
119
|
expected="Active Pro license.",
|
|
120
120
|
),
|
|
121
121
|
}
|
|
@@ -627,7 +627,7 @@ def get_compact_context(repo_path: str = ".", git_context: bool = False) -> dict
|
|
|
627
627
|
Includes security_surface, mybatis, and transactional_boundaries for Java/Spring projects.
|
|
628
628
|
For richer machine-oriented detail (deeper signals, more sections), use get_agent_context.
|
|
629
629
|
|
|
630
|
-
Maps to:
|
|
630
|
+
Maps to: sourcecode <repo_path> --compact [--git-context]
|
|
631
631
|
repo_path: absolute path to the repository (default: current working directory).
|
|
632
632
|
git_context: include git log and branch context in the analysis.
|
|
633
633
|
"""
|
|
@@ -661,7 +661,7 @@ def get_agent_context(repo_path: str = ".", git_context: bool = False) -> dict:
|
|
|
661
661
|
architecture layers, security surface, transactional boundaries, module graph summary.
|
|
662
662
|
Prefer get_compact_context for quick orientation or token-constrained workflows.
|
|
663
663
|
|
|
664
|
-
Maps to:
|
|
664
|
+
Maps to: sourcecode <repo_path> --agent [--git-context]
|
|
665
665
|
repo_path: absolute path to the repository (default: current working directory).
|
|
666
666
|
git_context: include git log and branch context in the analysis.
|
|
667
667
|
"""
|
|
@@ -696,7 +696,7 @@ def get_endpoints(repo_path: str = ".") -> dict:
|
|
|
696
696
|
Do NOT call this on non-Java repositories — it will return empty results.
|
|
697
697
|
Use get_compact_context or get_agent_context for non-Java repos.
|
|
698
698
|
|
|
699
|
-
Maps to:
|
|
699
|
+
Maps to: sourcecode endpoints <repo_path>
|
|
700
700
|
Returns: endpoints list with method, path, controller, handler fields;
|
|
701
701
|
security dict always present (policy: roles_allowed|permit_all|deny_all|
|
|
702
702
|
authenticated|...|none_detected); none_detected = no auth annotation found.
|
|
@@ -733,7 +733,7 @@ def get_spring_audit(repo_path: str = ".", scope: str = "all") -> dict:
|
|
|
733
733
|
|
|
734
734
|
Do NOT call this on non-Java repositories — it will return spring_detected=false.
|
|
735
735
|
|
|
736
|
-
Maps to:
|
|
736
|
+
Maps to: sourcecode spring-audit <repo_path> --scope <scope>
|
|
737
737
|
Returns: SpringAuditResult with schema_version, spring_detected, scope, summary,
|
|
738
738
|
findings list (id, pattern_id, category, severity, confidence, title,
|
|
739
739
|
symbol, source_file, evidence, explanation, fix_hint), limitations, metadata.
|
|
@@ -790,7 +790,7 @@ def get_migration_readiness(repo_path: str = ".", min_severity: str = "low") ->
|
|
|
790
790
|
the goal is migration planning — not ongoing audit.
|
|
791
791
|
Do NOT call on non-Java repositories — returns readiness_score=null (N/A) with no findings.
|
|
792
792
|
|
|
793
|
-
Maps to:
|
|
793
|
+
Maps to: sourcecode migrate-check <repo_path> --min-severity <min_severity>
|
|
794
794
|
Returns: MigrationReport with schema_version, readiness_score (0–100, or null=N/A when no migration target applies; 100=ready to migrate),
|
|
795
795
|
jakarta_readiness / boot3_readiness / jdk_modernization / hibernate_readiness
|
|
796
796
|
(per-dimension 0–100), headline_blocker (e.g. "hibernate_rewrite" or null),
|
|
@@ -847,7 +847,7 @@ def get_impact_chain(repo_path: str = ".", symbol: str = "", depth: int = 4) ->
|
|
|
847
847
|
|
|
848
848
|
Do NOT call this on non-Java repositories — it will return resolution=not_found.
|
|
849
849
|
|
|
850
|
-
Maps to:
|
|
850
|
+
Maps to: sourcecode impact-chain <symbol> <repo_path> [--depth <depth>]
|
|
851
851
|
Returns: ImpactChainResult with schema_version, symbol, resolution,
|
|
852
852
|
direct_callers, indirect_callers, endpoints_affected,
|
|
853
853
|
transaction_boundary (propagation/isolation/read_only),
|
|
@@ -901,7 +901,7 @@ def get_impact_chain(repo_path: str = ".", symbol: str = "", depth: int = 4) ->
|
|
|
901
901
|
def get_module_context(repo_path: str = ".", module: str = "") -> dict:
|
|
902
902
|
"""Compact analysis of a specific module or subdirectory within a repository.
|
|
903
903
|
|
|
904
|
-
Maps to:
|
|
904
|
+
Maps to: sourcecode <repo_path>/<module> --compact
|
|
905
905
|
repo_path: absolute path to the repository root.
|
|
906
906
|
module: subdirectory name relative to repo_path (e.g. 'src/auth', 'api', 'core').
|
|
907
907
|
"""
|
|
@@ -944,7 +944,7 @@ def _auto_since(repo_path: str) -> str:
|
|
|
944
944
|
def get_delta(repo_path: str = ".", since: str = "") -> dict:
|
|
945
945
|
"""Incremental context: git-changed files since a reference commit.
|
|
946
946
|
|
|
947
|
-
Maps to:
|
|
947
|
+
Maps to: sourcecode prepare-context delta <repo_path> --since <since>
|
|
948
948
|
repo_path: absolute path to the repository (default: current working directory).
|
|
949
949
|
since: git ref to diff against (e.g. HEAD~3, main, origin/main).
|
|
950
950
|
If empty or omitted, auto-detects merge-base with origin/main (or
|
|
@@ -1055,12 +1055,12 @@ def check_freshness(repo_path: str = ".") -> dict:
|
|
|
1055
1055
|
def get_ir_summary(repo_path: str = ".") -> dict:
|
|
1056
1056
|
"""Deterministic symbol-level IR summary for Java repositories. Java only.
|
|
1057
1057
|
|
|
1058
|
-
Maps to:
|
|
1058
|
+
Maps to: sourcecode repo-ir <repo_path> --summary-only
|
|
1059
1059
|
Returns: reverse_graph (top 10 hubs), route_surface (top 50 endpoints),
|
|
1060
1060
|
subsystems (top 15), impact, analysis. Full graph nodes/edges omitted.
|
|
1061
1061
|
|
|
1062
1062
|
Output is bounded to ~100 KB for LLM safety. For full IR (can exceed 10 MB
|
|
1063
|
-
on large repos), use the CLI:
|
|
1063
|
+
on large repos), use the CLI: sourcecode repo-ir <path> --output ir.json
|
|
1064
1064
|
Use get_compact_context or get_agent_context for non-Java repos.
|
|
1065
1065
|
|
|
1066
1066
|
IR access paths (for full IR via CLI):
|
|
@@ -1097,7 +1097,7 @@ def get_ir_summary(repo_path: str = ".") -> dict:
|
|
|
1097
1097
|
def fix_bug_context(repo_path: str = ".", symptom: str = "") -> dict:
|
|
1098
1098
|
"""Risk-ranked files for bug investigation, optionally focused by symptom.
|
|
1099
1099
|
|
|
1100
|
-
Maps to:
|
|
1100
|
+
Maps to: sourcecode prepare-context fix-bug <repo_path> [--symptom <symptom>]
|
|
1101
1101
|
Includes compact_base: security_surface, transactional_boundaries, spring_profiles.
|
|
1102
1102
|
repo_path: absolute path to the repository (default: current working directory).
|
|
1103
1103
|
symptom: optional error message or class name to focus the file ranking
|
|
@@ -1126,7 +1126,7 @@ def fix_bug_context(repo_path: str = ".", symptom: str = "") -> dict:
|
|
|
1126
1126
|
def review_pr_context(repo_path: str = ".", since: str = "") -> dict:
|
|
1127
1127
|
"""Execution paths and risk analysis for changed files in a pull request.
|
|
1128
1128
|
|
|
1129
|
-
Maps to:
|
|
1129
|
+
Maps to: sourcecode prepare-context review-pr <repo_path> [--since <since>]
|
|
1130
1130
|
Returns: compact_base + execution_paths (diff-scoped) + hotspots for changed files.
|
|
1131
1131
|
repo_path: absolute path to the repository (default: current working directory).
|
|
1132
1132
|
since: git ref to diff against (e.g. HEAD~3, main, origin/main).
|
|
@@ -1155,7 +1155,7 @@ def review_pr_context(repo_path: str = ".", since: str = "") -> dict:
|
|
|
1155
1155
|
def onboard_context(repo_path: str = ".") -> dict:
|
|
1156
1156
|
"""Onboarding context: structured overview for new contributors.
|
|
1157
1157
|
|
|
1158
|
-
Maps to:
|
|
1158
|
+
Maps to: sourcecode prepare-context onboard <repo_path>
|
|
1159
1159
|
repo_path: absolute path to the repository (default: current working directory).
|
|
1160
1160
|
"""
|
|
1161
1161
|
_raw = repo_path
|
|
@@ -1178,7 +1178,7 @@ def onboard_context(repo_path: str = ".") -> dict:
|
|
|
1178
1178
|
def explain_context(repo_path: str = ".") -> dict:
|
|
1179
1179
|
"""Architecture and entry-point explanation for a repository.
|
|
1180
1180
|
|
|
1181
|
-
Maps to:
|
|
1181
|
+
Maps to: sourcecode prepare-context explain <repo_path>
|
|
1182
1182
|
Returns: project summary, architecture, entry points, key dependencies.
|
|
1183
1183
|
repo_path: absolute path to the repository (default: current working directory).
|
|
1184
1184
|
"""
|
|
@@ -1202,7 +1202,7 @@ def explain_context(repo_path: str = ".") -> dict:
|
|
|
1202
1202
|
def refactor_context(repo_path: str = ".") -> dict:
|
|
1203
1203
|
"""Structural issues and refactor opportunities for a repository.
|
|
1204
1204
|
|
|
1205
|
-
Maps to:
|
|
1205
|
+
Maps to: sourcecode prepare-context refactor <repo_path>
|
|
1206
1206
|
Returns: structural issues, coupling hotspots, improvement opportunities.
|
|
1207
1207
|
repo_path: absolute path to the repository (default: current working directory).
|
|
1208
1208
|
"""
|
|
@@ -1226,7 +1226,7 @@ def refactor_context(repo_path: str = ".") -> dict:
|
|
|
1226
1226
|
def generate_tests_context(repo_path: str = ".", include_all: bool = False) -> dict:
|
|
1227
1227
|
"""Untested source files and test gap analysis for a repository.
|
|
1228
1228
|
|
|
1229
|
-
Maps to:
|
|
1229
|
+
Maps to: sourcecode prepare-context generate-tests <repo_path> [--all]
|
|
1230
1230
|
Returns: test_gaps list of untested files ranked by risk.
|
|
1231
1231
|
On large repos (>2000 classes) analysis is bounded by SOURCECODE_TESTS_TIMEOUT_MS
|
|
1232
1232
|
(default: 15000 ms). If timeout elapses, returns truncated=true with partial results.
|
|
@@ -1279,7 +1279,7 @@ def generate_tests_context(repo_path: str = ".", include_all: bool = False) -> d
|
|
|
1279
1279
|
def get_impact_context(repo_path: str = ".", target: str = "", depth: int = 4) -> dict:
|
|
1280
1280
|
"""Blast-radius analysis: who calls a class and what breaks if it changes? Java only.
|
|
1281
1281
|
|
|
1282
|
-
Maps to:
|
|
1282
|
+
Maps to: sourcecode impact <target> <repo_path> [--depth <depth>]
|
|
1283
1283
|
Returns: direct_callers, indirect_callers, endpoints_affected,
|
|
1284
1284
|
transactional_boundaries_touched, risk_score, risk_level, stats.
|
|
1285
1285
|
|
|
@@ -1334,7 +1334,7 @@ def get_impact_context(repo_path: str = ".", target: str = "", depth: int = 4) -
|
|
|
1334
1334
|
def modernize_context(repo_path: str = ".", format: str = "json") -> dict:
|
|
1335
1335
|
"""Analyzes codebase for modernization opportunities: dead zones, hotspot scores, upgrade candidates.
|
|
1336
1336
|
|
|
1337
|
-
Maps to:
|
|
1337
|
+
Maps to: sourcecode modernize <repo_path>
|
|
1338
1338
|
Returns: hotspot_candidates (high fan-in + git churn), statically_unreferenced (zero-caller classes — NOT confirmed dead; verify no framework dispatch) + framework_dispatched,
|
|
1339
1339
|
high_coupling_nodes, subsystem_summary, cross_module_tangles, recommendation.
|
|
1340
1340
|
|
|
@@ -1368,9 +1368,9 @@ _TELEMETRY_ACTIONS = frozenset({"status", "enable", "disable"})
|
|
|
1368
1368
|
|
|
1369
1369
|
@mcp.tool()
|
|
1370
1370
|
def version() -> dict:
|
|
1371
|
-
"""Return
|
|
1371
|
+
"""Return sourcecode version and MCP compatibility metadata.
|
|
1372
1372
|
|
|
1373
|
-
Maps to:
|
|
1373
|
+
Maps to: sourcecode version
|
|
1374
1374
|
Returns structured JSON: cli_version, mcp_schema_version, compatibility_schema_version.
|
|
1375
1375
|
cli_version and mcp_schema_version are always identical (released together).
|
|
1376
1376
|
"""
|
|
@@ -1381,7 +1381,7 @@ def version() -> dict:
|
|
|
1381
1381
|
def config() -> dict:
|
|
1382
1382
|
"""Show sourcecode CLI configuration.
|
|
1383
1383
|
|
|
1384
|
-
Maps to:
|
|
1384
|
+
Maps to: sourcecode config
|
|
1385
1385
|
"""
|
|
1386
1386
|
return _execute(["config"])
|
|
1387
1387
|
|
|
@@ -1390,7 +1390,7 @@ def config() -> dict:
|
|
|
1390
1390
|
def telemetry(action: str) -> dict:
|
|
1391
1391
|
"""Manage telemetry settings.
|
|
1392
1392
|
|
|
1393
|
-
Maps to:
|
|
1393
|
+
Maps to: sourcecode telemetry <action>
|
|
1394
1394
|
action: one of "status" (show current state), "enable" (opt in), "disable" (opt out).
|
|
1395
1395
|
Valid values: "status" | "enable" | "disable"
|
|
1396
1396
|
"""
|
sourcecode/mcp_nudge.py
CHANGED
|
@@ -26,7 +26,7 @@ _FLAG: Path = Path.home() / ".sourcecode" / "nudge_shown"
|
|
|
26
26
|
|
|
27
27
|
_MSG = (
|
|
28
28
|
"→ Claude Desktop detected. "
|
|
29
|
-
"Run `
|
|
29
|
+
"Run `sourcecode mcp init` to enable agent integration.\n"
|
|
30
30
|
)
|
|
31
31
|
|
|
32
32
|
# Module-level imports so names are patchable in tests.
|
sourcecode/migrate_check.py
CHANGED
|
@@ -1308,6 +1308,33 @@ def _dimension_score(
|
|
|
1308
1308
|
return max(0, 100 - deduction)
|
|
1309
1309
|
|
|
1310
1310
|
|
|
1311
|
+
# --- Compact projection (output-shaping only) --------------------------------
|
|
1312
|
+
# `--compact` renders a bounded, decision-grade view of the SAME report: it caps
|
|
1313
|
+
# each large leaf collection to its top-N items and reports total/omitted counts.
|
|
1314
|
+
# It performs NO new analysis — it is a pure projection over the dict `to_dict()`
|
|
1315
|
+
# already produces (Semantic IR / ContextGraph / semantic engines untouched).
|
|
1316
|
+
# The default (full) output is unchanged and remains the baseline-oracle form.
|
|
1317
|
+
# FUTURE (not this increment): invert the default — emit compact by default and
|
|
1318
|
+
# add `--full` for the complete report — once the oracle baseline is recaptured.
|
|
1319
|
+
_COMPACT_TOP_N = 5
|
|
1320
|
+
|
|
1321
|
+
|
|
1322
|
+
def _cap_collection(items: list, top_n: int, key=None) -> dict:
|
|
1323
|
+
"""Project a list into a bounded `{total, shown, omitted, items}` view. Items
|
|
1324
|
+
are optionally reordered by `key` (stable) so the retained slice is the most
|
|
1325
|
+
important; ordering falls back to the engine's own emission order. No values
|
|
1326
|
+
are recomputed — only selected and counted."""
|
|
1327
|
+
total = len(items)
|
|
1328
|
+
ordered = sorted(items, key=key) if key is not None else items
|
|
1329
|
+
shown = ordered[:top_n]
|
|
1330
|
+
return {
|
|
1331
|
+
"total": total,
|
|
1332
|
+
"shown": len(shown),
|
|
1333
|
+
"omitted": max(0, total - len(shown)),
|
|
1334
|
+
"items": shown,
|
|
1335
|
+
}
|
|
1336
|
+
|
|
1337
|
+
|
|
1311
1338
|
@dataclass
|
|
1312
1339
|
class MigrationReport:
|
|
1313
1340
|
schema_version: str = "1.4"
|
|
@@ -1679,6 +1706,43 @@ class MigrationReport:
|
|
|
1679
1706
|
"metadata": self.metadata,
|
|
1680
1707
|
}
|
|
1681
1708
|
|
|
1709
|
+
def to_compact_dict(self, top_n: int = _COMPACT_TOP_N) -> dict:
|
|
1710
|
+
"""Bounded, decision-grade projection of `to_dict()`.
|
|
1711
|
+
|
|
1712
|
+
Keeps every decision field verbatim (readiness_score, per-dimension
|
|
1713
|
+
readiness, headline_blocker, blocking_count, estimated_effort_days,
|
|
1714
|
+
effort_breakdown, summary, Hibernate classification, aggregate counts) and
|
|
1715
|
+
caps only the large leaf collections — top-level `findings` and the
|
|
1716
|
+
Hibernate `rewrite_targets` / `findings` / `critical_call_chains` /
|
|
1717
|
+
`golden_sql_hotspots` — to their top-`top_n` items, each wrapped as
|
|
1718
|
+
`{total, shown, omitted, items}`. It reuses the already-computed
|
|
1719
|
+
`to_dict()` output and recomputes nothing (output-shaping only)."""
|
|
1720
|
+
full = self.to_dict()
|
|
1721
|
+
compact = dict(full)
|
|
1722
|
+
compact["mode"] = "compact"
|
|
1723
|
+
compact["compact_note"] = (
|
|
1724
|
+
f"Bounded decision summary: large collections are capped to the top {top_n} "
|
|
1725
|
+
"items (see each collection's total/omitted). Run without --compact (JSON "
|
|
1726
|
+
"default) for the complete report. A future release will invert this — "
|
|
1727
|
+
"compact by default with --full for the full detail."
|
|
1728
|
+
)
|
|
1729
|
+
# Top-level findings: retain the most severe first, then engine order.
|
|
1730
|
+
compact["findings"] = _cap_collection(
|
|
1731
|
+
full["findings"], top_n,
|
|
1732
|
+
key=lambda f: SEVERITY_ORDER.get(f.get("severity"), 3),
|
|
1733
|
+
)
|
|
1734
|
+
hib = full.get("hibernate")
|
|
1735
|
+
if isinstance(hib, dict):
|
|
1736
|
+
h = dict(hib)
|
|
1737
|
+
for _col in (
|
|
1738
|
+
"rewrite_targets", "findings",
|
|
1739
|
+
"critical_call_chains", "golden_sql_hotspots",
|
|
1740
|
+
):
|
|
1741
|
+
if isinstance(h.get(_col), list):
|
|
1742
|
+
h[_col] = _cap_collection(h[_col], top_n)
|
|
1743
|
+
compact["hibernate"] = h
|
|
1744
|
+
return compact
|
|
1745
|
+
|
|
1682
1746
|
def to_text(self, min_severity: str = "low") -> str:
|
|
1683
1747
|
min_order = SEVERITY_ORDER.get(min_severity, 3)
|
|
1684
1748
|
visible = [f for f in self.findings if SEVERITY_ORDER.get(f.severity, 3) <= min_order]
|