sourcecode 1.31.0__py3-none-any.whl → 1.31.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 +1 -1
- sourcecode/cli.py +81 -25
- sourcecode/mcp/onboarding/detector.py +61 -16
- sourcecode/mcp/runner.py +10 -3
- sourcecode/mcp/server.py +59 -87
- {sourcecode-1.31.0.dist-info → sourcecode-1.31.1.dist-info}/METADATA +4 -3
- {sourcecode-1.31.0.dist-info → sourcecode-1.31.1.dist-info}/RECORD +10 -10
- {sourcecode-1.31.0.dist-info → sourcecode-1.31.1.dist-info}/WHEEL +0 -0
- {sourcecode-1.31.0.dist-info → sourcecode-1.31.1.dist-info}/entry_points.txt +0 -0
- {sourcecode-1.31.0.dist-info → sourcecode-1.31.1.dist-info}/licenses/LICENSE +0 -0
sourcecode/__init__.py
CHANGED
sourcecode/cli.py
CHANGED
|
@@ -2466,10 +2466,6 @@ def analyze_cmd(
|
|
|
2466
2466
|
def mcp_serve() -> None:
|
|
2467
2467
|
"""Start the MCP server on stdio for AI agent integration.
|
|
2468
2468
|
|
|
2469
|
-
\b
|
|
2470
|
-
Requires the 'mcp' extra:
|
|
2471
|
-
pip install sourcecode[mcp]
|
|
2472
|
-
|
|
2473
2469
|
\b
|
|
2474
2470
|
Configure in your MCP client (e.g. Claude Desktop):
|
|
2475
2471
|
{
|
|
@@ -2487,15 +2483,7 @@ def mcp_serve() -> None:
|
|
|
2487
2483
|
level=logging.INFO,
|
|
2488
2484
|
format="[sourcecode-mcp] %(levelname)s %(message)s",
|
|
2489
2485
|
)
|
|
2490
|
-
|
|
2491
|
-
from sourcecode.mcp.server import mcp as _mcp
|
|
2492
|
-
except ImportError:
|
|
2493
|
-
typer.echo(
|
|
2494
|
-
"MCP support not available. Install with:\n"
|
|
2495
|
-
" pip install sourcecode[mcp]",
|
|
2496
|
-
err=True,
|
|
2497
|
-
)
|
|
2498
|
-
raise typer.Exit(code=1)
|
|
2486
|
+
from sourcecode.mcp.server import mcp as _mcp
|
|
2499
2487
|
|
|
2500
2488
|
log = logging.getLogger(__name__)
|
|
2501
2489
|
log.info("sourcecode-mcp starting (stdio transport)")
|
|
@@ -2513,21 +2501,44 @@ def mcp_serve() -> None:
|
|
|
2513
2501
|
@mcp_app.command("init")
|
|
2514
2502
|
def mcp_init(
|
|
2515
2503
|
yes: bool = typer.Option(False, "--yes", "-y", help="Skip confirmation prompt."),
|
|
2504
|
+
target: Optional[str] = typer.Option(
|
|
2505
|
+
None,
|
|
2506
|
+
"--target",
|
|
2507
|
+
"-t",
|
|
2508
|
+
help="Target client: claude-desktop | cursor. Default: auto-detect all.",
|
|
2509
|
+
),
|
|
2516
2510
|
) -> None:
|
|
2517
2511
|
"""Setup MCP integration for Claude Desktop, Cursor, and other clients.
|
|
2518
2512
|
|
|
2519
2513
|
\b
|
|
2520
2514
|
Detects installed MCP clients, backs up their config files, and safely
|
|
2521
2515
|
inserts the sourcecode server entry. Fully idempotent — safe to re-run.
|
|
2516
|
+
|
|
2517
|
+
\b
|
|
2518
|
+
Examples:
|
|
2519
|
+
sourcecode mcp init
|
|
2520
|
+
sourcecode mcp init --target claude-desktop
|
|
2521
|
+
sourcecode mcp init --target cursor --yes
|
|
2522
2522
|
"""
|
|
2523
|
-
from sourcecode.mcp.onboarding.detector import detect_clients
|
|
2523
|
+
from sourcecode.mcp.onboarding.detector import detect_clients, is_client_running
|
|
2524
2524
|
from sourcecode.mcp.onboarding.planner import build_install_plan
|
|
2525
2525
|
from sourcecode.mcp.onboarding import backup, applier
|
|
2526
2526
|
|
|
2527
2527
|
typer.echo("Detecting MCP clients...")
|
|
2528
2528
|
typer.echo("")
|
|
2529
2529
|
|
|
2530
|
-
|
|
2530
|
+
all_clients = detect_clients()
|
|
2531
|
+
|
|
2532
|
+
if target:
|
|
2533
|
+
target_slug = target.lower()
|
|
2534
|
+
clients = [c for c in all_clients if c.slug == target_slug]
|
|
2535
|
+
if not clients:
|
|
2536
|
+
valid = ", ".join(c.slug for c in all_clients)
|
|
2537
|
+
typer.echo(f"Unknown target '{target}'. Valid: {valid}", err=True)
|
|
2538
|
+
raise typer.Exit(code=1)
|
|
2539
|
+
else:
|
|
2540
|
+
clients = all_clients
|
|
2541
|
+
|
|
2531
2542
|
if not clients:
|
|
2532
2543
|
typer.echo("No MCP clients found on this system.")
|
|
2533
2544
|
typer.echo("")
|
|
@@ -2603,36 +2614,81 @@ def mcp_init(
|
|
|
2603
2614
|
typer.echo("MCP integration active.")
|
|
2604
2615
|
typer.echo("")
|
|
2605
2616
|
|
|
2606
|
-
|
|
2607
|
-
|
|
2608
|
-
|
|
2617
|
+
# Post-write: validate config and warn if client not running
|
|
2618
|
+
for a in actionable:
|
|
2619
|
+
if not is_client_running(a.client):
|
|
2620
|
+
typer.echo(
|
|
2621
|
+
f" ⚠ Config written but {a.client.name} is not running. "
|
|
2622
|
+
f"Start {a.client.name} and run sourcecode mcp status to verify.",
|
|
2623
|
+
err=False,
|
|
2624
|
+
)
|
|
2625
|
+
else:
|
|
2626
|
+
restart_msg = "" if a.will_create_file else f" Restart {a.client.name} to apply."
|
|
2627
|
+
typer.echo(f" ✓ {a.client.name} is running.{restart_msg}")
|
|
2628
|
+
|
|
2629
|
+
typer.echo("")
|
|
2609
2630
|
typer.echo(" Remove: sourcecode mcp remove")
|
|
2610
2631
|
|
|
2611
2632
|
|
|
2612
2633
|
@mcp_app.command("status")
|
|
2613
2634
|
def mcp_status() -> None:
|
|
2614
|
-
"""Show MCP integration status
|
|
2615
|
-
from sourcecode.mcp.onboarding.detector import detect_clients
|
|
2635
|
+
"""Show MCP integration status: dependencies, config files, and connectivity."""
|
|
2636
|
+
from sourcecode.mcp.onboarding.detector import detect_clients, is_client_running
|
|
2616
2637
|
from sourcecode.mcp.onboarding import applier
|
|
2617
2638
|
|
|
2618
|
-
|
|
2619
|
-
|
|
2639
|
+
sep = "─" * 46
|
|
2640
|
+
|
|
2641
|
+
typer.echo("MCP Status")
|
|
2642
|
+
typer.echo(sep)
|
|
2643
|
+
|
|
2644
|
+
# Stage 1: Dependencies
|
|
2645
|
+
try:
|
|
2646
|
+
import mcp as _mcp_pkg # noqa: F401
|
|
2647
|
+
typer.echo("Dependencies ✓ installed")
|
|
2648
|
+
except ImportError:
|
|
2649
|
+
typer.echo("Dependencies ✗ missing")
|
|
2650
|
+
typer.echo(" Fix: pip install sourcecode[mcp]")
|
|
2620
2651
|
typer.echo("")
|
|
2621
2652
|
|
|
2653
|
+
clients = detect_clients()
|
|
2622
2654
|
if not clients:
|
|
2623
2655
|
typer.echo(" No MCP clients detected on this system.")
|
|
2656
|
+
typer.echo(sep)
|
|
2657
|
+
typer.echo(" Setup: sourcecode mcp init")
|
|
2624
2658
|
raise typer.Exit(code=0)
|
|
2625
2659
|
|
|
2660
|
+
# Stage 2: Config files
|
|
2661
|
+
typer.echo("Config files")
|
|
2626
2662
|
for client in clients:
|
|
2627
2663
|
if not client.app_installed:
|
|
2628
|
-
typer.echo(f"
|
|
2664
|
+
typer.echo(f" {client.name:<20} ✗ not found")
|
|
2665
|
+
typer.echo(f" Expected: {client.config_path}")
|
|
2666
|
+
typer.echo(f" Fix: sourcecode mcp init --target {client.slug}")
|
|
2629
2667
|
continue
|
|
2630
2668
|
config = applier.read_config(client.config_path)
|
|
2631
2669
|
if applier.is_installed(config):
|
|
2632
|
-
typer.echo(f"
|
|
2670
|
+
typer.echo(f" {client.name:<20} ✓ configured {client.config_path}")
|
|
2633
2671
|
else:
|
|
2634
|
-
typer.echo(f"
|
|
2672
|
+
typer.echo(f" {client.name:<20} ✗ not configured")
|
|
2673
|
+
typer.echo(f" Fix: sourcecode mcp init --target {client.slug}")
|
|
2635
2674
|
typer.echo("")
|
|
2675
|
+
|
|
2676
|
+
# Stage 3: Connectivity
|
|
2677
|
+
typer.echo("Connectivity")
|
|
2678
|
+
any_installed = any(c.app_installed for c in clients)
|
|
2679
|
+
if not any_installed:
|
|
2680
|
+
typer.echo(" (no clients to check)")
|
|
2681
|
+
else:
|
|
2682
|
+
for client in clients:
|
|
2683
|
+
if not client.app_installed:
|
|
2684
|
+
continue
|
|
2685
|
+
if is_client_running(client):
|
|
2686
|
+
typer.echo(f" {client.name:<20} ✓ running")
|
|
2687
|
+
else:
|
|
2688
|
+
typer.echo(f" {client.name:<20} ✗ not running")
|
|
2689
|
+
typer.echo(f" Fix: open {client.name}, then run sourcecode mcp status")
|
|
2690
|
+
|
|
2691
|
+
typer.echo(sep)
|
|
2636
2692
|
typer.echo(" Setup: sourcecode mcp init")
|
|
2637
2693
|
typer.echo(" Remove: sourcecode mcp remove")
|
|
2638
2694
|
|
|
@@ -2,25 +2,43 @@
|
|
|
2
2
|
from __future__ import annotations
|
|
3
3
|
|
|
4
4
|
import os
|
|
5
|
+
import subprocess
|
|
5
6
|
import sys
|
|
6
7
|
from dataclasses import dataclass
|
|
7
8
|
from pathlib import Path
|
|
9
|
+
from typing import Any, Dict, List
|
|
8
10
|
|
|
9
11
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
"
|
|
15
|
-
|
|
16
|
-
|
|
12
|
+
_CLIENT_REGISTRY: List[Dict[str, Any]] = [
|
|
13
|
+
{
|
|
14
|
+
"name": "Claude Desktop",
|
|
15
|
+
"slug": "claude-desktop",
|
|
16
|
+
"paths": {
|
|
17
|
+
"darwin": "~/Library/Application Support/Claude/claude_desktop_config.json",
|
|
18
|
+
"linux": "~/.config/Claude/claude_desktop_config.json",
|
|
19
|
+
"win32": "{APPDATA}/Claude/claude_desktop_config.json",
|
|
20
|
+
},
|
|
21
|
+
"process": {
|
|
22
|
+
"darwin": "Claude",
|
|
23
|
+
"linux": "claude-desktop",
|
|
24
|
+
"win32": "Claude",
|
|
25
|
+
},
|
|
17
26
|
},
|
|
18
|
-
|
|
19
|
-
"
|
|
20
|
-
"
|
|
21
|
-
"
|
|
27
|
+
{
|
|
28
|
+
"name": "Cursor",
|
|
29
|
+
"slug": "cursor",
|
|
30
|
+
"paths": {
|
|
31
|
+
"darwin": "~/.cursor/mcp.json",
|
|
32
|
+
"linux": "~/.cursor/mcp.json",
|
|
33
|
+
"win32": "{USERPROFILE}/.cursor/mcp.json",
|
|
34
|
+
},
|
|
35
|
+
"process": {
|
|
36
|
+
"darwin": "Cursor",
|
|
37
|
+
"linux": "cursor",
|
|
38
|
+
"win32": "Cursor",
|
|
39
|
+
},
|
|
22
40
|
},
|
|
23
|
-
|
|
41
|
+
]
|
|
24
42
|
|
|
25
43
|
|
|
26
44
|
@dataclass(frozen=True)
|
|
@@ -28,6 +46,8 @@ class MCPClient:
|
|
|
28
46
|
name: str
|
|
29
47
|
config_path: Path
|
|
30
48
|
app_installed: bool # True if the config file (or its parent dir) exists
|
|
49
|
+
process_name: str # OS process name for connectivity check
|
|
50
|
+
slug: str # --target identifier (e.g. "claude-desktop")
|
|
31
51
|
|
|
32
52
|
|
|
33
53
|
def _resolve(template: str) -> Path:
|
|
@@ -44,16 +64,41 @@ def detect_clients() -> list[MCPClient]:
|
|
|
44
64
|
"""Return all known MCP clients with their resolved config paths."""
|
|
45
65
|
plat = sys.platform
|
|
46
66
|
clients: list[MCPClient] = []
|
|
47
|
-
for
|
|
48
|
-
|
|
67
|
+
for entry in _CLIENT_REGISTRY:
|
|
68
|
+
paths: Dict[str, str] = entry["paths"]
|
|
69
|
+
processes: Dict[str, str] = entry["process"]
|
|
70
|
+
template = paths.get(plat) or paths.get("linux", "")
|
|
49
71
|
if not template:
|
|
50
72
|
continue
|
|
51
73
|
config_path = _resolve(template)
|
|
52
|
-
# Consider client "installed" if its config file OR parent app dir exists.
|
|
53
74
|
app_installed = config_path.exists() or config_path.parent.exists()
|
|
75
|
+
process_name = processes.get(plat) or processes.get("linux", "")
|
|
54
76
|
clients.append(MCPClient(
|
|
55
|
-
name=name,
|
|
77
|
+
name=entry["name"],
|
|
56
78
|
config_path=config_path,
|
|
57
79
|
app_installed=app_installed,
|
|
80
|
+
process_name=process_name,
|
|
81
|
+
slug=entry["slug"],
|
|
58
82
|
))
|
|
59
83
|
return clients
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def is_client_running(client: MCPClient) -> bool:
|
|
87
|
+
"""True if the client process is currently running."""
|
|
88
|
+
if not client.process_name:
|
|
89
|
+
return False
|
|
90
|
+
try:
|
|
91
|
+
if sys.platform == "win32":
|
|
92
|
+
result = subprocess.run(
|
|
93
|
+
["tasklist", "/fi", f"imagename eq {client.process_name}.exe"],
|
|
94
|
+
capture_output=True, text=True, timeout=5,
|
|
95
|
+
)
|
|
96
|
+
return client.process_name.lower() in result.stdout.lower()
|
|
97
|
+
else:
|
|
98
|
+
result = subprocess.run(
|
|
99
|
+
["pgrep", "-x", client.process_name],
|
|
100
|
+
capture_output=True, timeout=5,
|
|
101
|
+
)
|
|
102
|
+
return result.returncode == 0
|
|
103
|
+
except Exception:
|
|
104
|
+
return False
|
sourcecode/mcp/runner.py
CHANGED
|
@@ -6,14 +6,18 @@ lookup, no process fork, no stdout encoding issues.
|
|
|
6
6
|
"""
|
|
7
7
|
from __future__ import annotations
|
|
8
8
|
|
|
9
|
+
import json
|
|
10
|
+
from typing import Any
|
|
11
|
+
|
|
9
12
|
from typer.testing import CliRunner
|
|
10
13
|
|
|
11
14
|
_runner = CliRunner()
|
|
12
15
|
|
|
13
16
|
|
|
14
|
-
def run_command(args: list[str]) ->
|
|
15
|
-
"""Invoke a sourcecode CLI command in-process and return
|
|
17
|
+
def run_command(args: list[str]) -> Any:
|
|
18
|
+
"""Invoke a sourcecode CLI command in-process and return parsed output.
|
|
16
19
|
|
|
20
|
+
Returns parsed JSON dict when output is valid JSON, else the raw string.
|
|
17
21
|
Raises RuntimeError on non-zero exit or empty output.
|
|
18
22
|
"""
|
|
19
23
|
from sourcecode.cli import _detected_path, _preprocess_args, app
|
|
@@ -37,4 +41,7 @@ def run_command(args: list[str]) -> str:
|
|
|
37
41
|
f"Args: {args}"
|
|
38
42
|
)
|
|
39
43
|
|
|
40
|
-
|
|
44
|
+
try:
|
|
45
|
+
return json.loads(output)
|
|
46
|
+
except json.JSONDecodeError:
|
|
47
|
+
return output
|
sourcecode/mcp/server.py
CHANGED
|
@@ -4,12 +4,14 @@ Exposes sourcecode capabilities as MCP tools. Each tool maps to a CLI command
|
|
|
4
4
|
and delegates execution to the in-process runner — no subprocess, no binary
|
|
5
5
|
lookup, same process as the CLI.
|
|
6
6
|
|
|
7
|
-
All tools return
|
|
8
|
-
{"success": bool, "data": str | None, "error": {"code": str, "message": str} | None}
|
|
7
|
+
All tools return:
|
|
8
|
+
{"success": bool, "data": dict | str | None, "error": {"code": str, "message": str} | None}
|
|
9
|
+
data is the parsed JSON object from the CLI output, not a shell string.
|
|
9
10
|
"""
|
|
10
11
|
from __future__ import annotations
|
|
11
12
|
|
|
12
|
-
|
|
13
|
+
import os
|
|
14
|
+
from typing import Any
|
|
13
15
|
|
|
14
16
|
from mcp.server.fastmcp import FastMCP
|
|
15
17
|
|
|
@@ -17,14 +19,8 @@ from sourcecode.mcp.runner import run_command
|
|
|
17
19
|
|
|
18
20
|
mcp = FastMCP("sourcecode")
|
|
19
21
|
|
|
20
|
-
_PREPARE_CONTEXT_TASKS = frozenset({
|
|
21
|
-
"delta", "review-pr", "fix-bug", "onboard",
|
|
22
|
-
"explain", "refactor", "generate-tests",
|
|
23
|
-
})
|
|
24
|
-
_TELEMETRY_ACTIONS = frozenset({"status", "enable", "disable"})
|
|
25
22
|
|
|
26
|
-
|
|
27
|
-
def _ok(data: str) -> dict:
|
|
23
|
+
def _ok(data: Any) -> dict:
|
|
28
24
|
return {"success": True, "data": data, "error": None}
|
|
29
25
|
|
|
30
26
|
|
|
@@ -40,108 +36,84 @@ def _execute(args: list[str]) -> dict:
|
|
|
40
36
|
|
|
41
37
|
|
|
42
38
|
@mcp.tool()
|
|
43
|
-
def
|
|
44
|
-
"""
|
|
39
|
+
def get_compact_context(repo_path: str = ".") -> dict:
|
|
40
|
+
"""High-signal summary of a repository (~1000-3000 tokens).
|
|
45
41
|
|
|
46
|
-
Maps to: sourcecode <
|
|
42
|
+
Maps to: sourcecode <repo_path> --compact
|
|
43
|
+
Returns: stacks, entry points, dependency summary, confidence, gaps.
|
|
44
|
+
repo_path: absolute path to the repository (default: current working directory).
|
|
47
45
|
"""
|
|
48
|
-
if not isinstance(
|
|
49
|
-
return _err("
|
|
50
|
-
|
|
51
|
-
return _err("git_context must be boolean", "INVALID_ARGUMENT")
|
|
52
|
-
args = [path, "--compact"]
|
|
53
|
-
if git_context:
|
|
54
|
-
args.append("--git-context")
|
|
55
|
-
return _execute(args)
|
|
46
|
+
if not isinstance(repo_path, str):
|
|
47
|
+
return _err("repo_path must be a string", "INVALID_ARGUMENT")
|
|
48
|
+
return _execute([repo_path, "--compact"])
|
|
56
49
|
|
|
57
50
|
|
|
58
51
|
@mcp.tool()
|
|
59
|
-
def
|
|
52
|
+
def get_agent_context(repo_path: str = ".") -> dict:
|
|
60
53
|
"""Agent-optimised analysis: identity, entry points, dependencies, gaps.
|
|
61
54
|
|
|
62
|
-
Maps to: sourcecode <
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
return _err("path must be a string", "INVALID_ARGUMENT")
|
|
66
|
-
if not isinstance(git_context, bool):
|
|
67
|
-
return _err("git_context must be boolean", "INVALID_ARGUMENT")
|
|
68
|
-
args = [path, "--agent"]
|
|
69
|
-
if git_context:
|
|
70
|
-
args.append("--git-context")
|
|
71
|
-
return _execute(args)
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
@mcp.tool()
|
|
75
|
-
def prepare_context(
|
|
76
|
-
task: Literal[
|
|
77
|
-
"delta", "review-pr", "fix-bug", "onboard",
|
|
78
|
-
"explain", "refactor", "generate-tests",
|
|
79
|
-
],
|
|
80
|
-
path: str,
|
|
81
|
-
) -> dict:
|
|
82
|
-
"""Task-specific context for AI coding agents.
|
|
83
|
-
|
|
84
|
-
Maps to: sourcecode prepare-context <task> <path>
|
|
85
|
-
|
|
86
|
-
task must be one of:
|
|
87
|
-
explain Architecture, entry points, key dependencies
|
|
88
|
-
fix-bug Risk-ranked files, suspected areas, annotations
|
|
89
|
-
refactor Structural issues, improvement opportunities
|
|
90
|
-
generate-tests Untested source files, test gap analysis
|
|
91
|
-
onboard Full project context for new agents/developers
|
|
92
|
-
review-pr PR diff with runtime signals and security impact
|
|
93
|
-
delta Incremental context: git-changed files only
|
|
55
|
+
Maps to: sourcecode <repo_path> --agent
|
|
56
|
+
Returns: structured noise-free JSON for AI agents.
|
|
57
|
+
repo_path: absolute path to the repository (default: current working directory).
|
|
94
58
|
"""
|
|
95
|
-
if
|
|
96
|
-
return _err(
|
|
97
|
-
|
|
98
|
-
"INVALID_ARGUMENT",
|
|
99
|
-
)
|
|
100
|
-
if not isinstance(path, str):
|
|
101
|
-
return _err("path must be a string", "INVALID_ARGUMENT")
|
|
102
|
-
return _execute(["prepare-context", task, path])
|
|
59
|
+
if not isinstance(repo_path, str):
|
|
60
|
+
return _err("repo_path must be a string", "INVALID_ARGUMENT")
|
|
61
|
+
return _execute([repo_path, "--agent"])
|
|
103
62
|
|
|
104
63
|
|
|
105
64
|
@mcp.tool()
|
|
106
|
-
def
|
|
107
|
-
"""
|
|
65
|
+
def get_endpoints(repo_path: str = ".") -> dict:
|
|
66
|
+
"""API endpoint surface extraction.
|
|
108
67
|
|
|
109
|
-
Maps to: sourcecode
|
|
110
|
-
|
|
68
|
+
Maps to: sourcecode <repo_path> --endpoints (pending CLI implementation)
|
|
69
|
+
repo_path: absolute path to the repository (default: current working directory).
|
|
111
70
|
"""
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
71
|
+
return _err(
|
|
72
|
+
"get_endpoints requires --endpoints CLI flag (pending implementation). "
|
|
73
|
+
"Use get_compact_context for now — the output includes api_endpoint-classified files.",
|
|
74
|
+
"NOT_IMPLEMENTED",
|
|
75
|
+
)
|
|
115
76
|
|
|
116
77
|
|
|
117
78
|
@mcp.tool()
|
|
118
|
-
def
|
|
119
|
-
"""
|
|
79
|
+
def get_module_context(repo_path: str = ".", module: str = "") -> dict:
|
|
80
|
+
"""Compact analysis of a specific module or subdirectory within a repository.
|
|
120
81
|
|
|
121
|
-
Maps to: sourcecode
|
|
82
|
+
Maps to: sourcecode <repo_path>/<module> --compact
|
|
83
|
+
repo_path: absolute path to the repository root.
|
|
84
|
+
module: subdirectory name relative to repo_path (e.g. 'src/auth', 'api', 'core').
|
|
122
85
|
"""
|
|
123
|
-
|
|
86
|
+
if not isinstance(repo_path, str):
|
|
87
|
+
return _err("repo_path must be a string", "INVALID_ARGUMENT")
|
|
88
|
+
if not isinstance(module, str) or not module.strip():
|
|
89
|
+
return _err("module must be a non-empty string", "INVALID_ARGUMENT")
|
|
90
|
+
module_path = os.path.join(repo_path, module)
|
|
91
|
+
return _execute([module_path, "--compact"])
|
|
124
92
|
|
|
125
93
|
|
|
126
94
|
@mcp.tool()
|
|
127
|
-
def
|
|
128
|
-
"""
|
|
95
|
+
def get_delta(repo_path: str = ".", since: str = "HEAD~1") -> dict:
|
|
96
|
+
"""Incremental context: git-changed files since a reference commit.
|
|
129
97
|
|
|
130
|
-
Maps to: sourcecode
|
|
98
|
+
Maps to: sourcecode prepare-context delta <repo_path> --since <since>
|
|
99
|
+
repo_path: absolute path to the repository (default: current working directory).
|
|
100
|
+
since: git ref to diff against (e.g. HEAD~3, main, origin/main).
|
|
131
101
|
"""
|
|
132
|
-
|
|
102
|
+
if not isinstance(repo_path, str):
|
|
103
|
+
return _err("repo_path must be a string", "INVALID_ARGUMENT")
|
|
104
|
+
if not isinstance(since, str) or not since.strip():
|
|
105
|
+
return _err("since must be a non-empty git ref", "INVALID_ARGUMENT")
|
|
106
|
+
return _execute(["prepare-context", "delta", repo_path, "--since", since])
|
|
133
107
|
|
|
134
108
|
|
|
135
109
|
@mcp.tool()
|
|
136
|
-
def
|
|
137
|
-
"""
|
|
110
|
+
def get_ir_summary(repo_path: str = ".") -> dict:
|
|
111
|
+
"""Deterministic symbol-level IR summary for Java repositories.
|
|
138
112
|
|
|
139
|
-
Maps to: sourcecode
|
|
140
|
-
|
|
113
|
+
Maps to: sourcecode repo-ir <repo_path> --summary-only
|
|
114
|
+
Returns: analysis summary, impact, and change_set — omits full graph nodes/edges.
|
|
115
|
+
repo_path: absolute path to the repository (default: current working directory).
|
|
141
116
|
"""
|
|
142
|
-
if
|
|
143
|
-
return _err(
|
|
144
|
-
|
|
145
|
-
"INVALID_ARGUMENT",
|
|
146
|
-
)
|
|
147
|
-
return _execute(["telemetry", action])
|
|
117
|
+
if not isinstance(repo_path, str):
|
|
118
|
+
return _err("repo_path must be a string", "INVALID_ARGUMENT")
|
|
119
|
+
return _execute(["repo-ir", repo_path, "--summary-only"])
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: sourcecode
|
|
3
|
-
Version: 1.31.
|
|
3
|
+
Version: 1.31.1
|
|
4
4
|
Summary: Deterministic codebase context for AI coding agents
|
|
5
5
|
License: Apache License
|
|
6
6
|
Version 2.0, January 2004
|
|
@@ -203,6 +203,7 @@ Classifier: Programming Language :: Python :: 3.12
|
|
|
203
203
|
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
204
204
|
Classifier: Topic :: Utilities
|
|
205
205
|
Requires-Python: >=3.9
|
|
206
|
+
Requires-Dist: mcp>=1.0.0
|
|
206
207
|
Requires-Dist: pathspec>=1.0
|
|
207
208
|
Requires-Dist: ruamel-yaml>=0.18
|
|
208
209
|
Requires-Dist: tomli>=2.0; python_version < '3.11'
|
|
@@ -224,7 +225,7 @@ Description-Content-Type: text/markdown
|
|
|
224
225
|
|
|
225
226
|
**Deterministic, behavior-aware codebase context for AI agents and PR review.**
|
|
226
227
|
|
|
227
|
-

|
|
228
229
|

|
|
229
230
|
|
|
230
231
|
---
|
|
@@ -260,7 +261,7 @@ pipx install sourcecode
|
|
|
260
261
|
|
|
261
262
|
```bash
|
|
262
263
|
sourcecode version
|
|
263
|
-
# sourcecode 1.31.
|
|
264
|
+
# sourcecode 1.31.1
|
|
264
265
|
```
|
|
265
266
|
|
|
266
267
|
---
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
sourcecode/__init__.py,sha256=
|
|
1
|
+
sourcecode/__init__.py,sha256=6_eP1xYY6TSz7m1YdF9GvL5eOo0J9rRzaYP9OD56okA,103
|
|
2
2
|
sourcecode/adaptive_scanner.py,sha256=XffluXKzJUXrMtjEiAOnSNPZnztdIcts17T9ouHeID0,10521
|
|
3
3
|
sourcecode/architecture_analyzer.py,sha256=MyBa0Hf5HmkudZQDLKrjcWDKETXETXl0mQX1swtTwAA,39091
|
|
4
4
|
sourcecode/architecture_summary.py,sha256=z34_6v7cSwy98cof2UVciGho7SCrZ93tiqMmq5WNzRQ,20405
|
|
5
5
|
sourcecode/ast_extractor.py,sha256=XgrZg2DcWcUm9r87cRG3KGO7IK2TIL_N-CvhSbUmmh4,49901
|
|
6
6
|
sourcecode/classifier.py,sha256=-0t0HLc9L9UleMLfclfLM3AXhBjUb_AYyBPDbvgWtac,7755
|
|
7
|
-
sourcecode/cli.py,sha256=
|
|
7
|
+
sourcecode/cli.py,sha256=I3h-Do1vFUEJzj7tK19yacZsYQA84t3Nlwfo1hNKjZo,112353
|
|
8
8
|
sourcecode/code_notes_analyzer.py,sha256=y1MJBnPZHYp4i6cQCXUb9ATIyifS_qMQWjw_8lPkpsU,9215
|
|
9
9
|
sourcecode/confidence_analyzer.py,sha256=H9VHYRzZhqMFlSCZffjtsMUGYLnDvrq1g5FjzyQ1hxE,16381
|
|
10
10
|
sourcecode/context_scorer.py,sha256=QpChSpsmaAYz91rXA4Ue5xzQmNz_ZboZN09YOHScq1U,14679
|
|
@@ -59,12 +59,12 @@ sourcecode/detectors/systems.py,sha256=nYaKbGDFu0EOXFcd_1doWFT3tTUdkbxc2DjHUF5Tc
|
|
|
59
59
|
sourcecode/detectors/terraform.py,sha256=cxORPR_zVLOJpHlh4e9JnFpkQsn_UnqMMom5yG65hZ4,1693
|
|
60
60
|
sourcecode/detectors/tooling.py,sha256=8CKbtxwQoABP-WyBRNmdAmHDOvAH57AR1cF4UKuWEdQ,2074
|
|
61
61
|
sourcecode/mcp/__init__.py,sha256=XU4HfRGbdid8wdUA0x_4f7uKZD1z3mv_XUY_WU_T9Mw,179
|
|
62
|
-
sourcecode/mcp/runner.py,sha256=
|
|
63
|
-
sourcecode/mcp/server.py,sha256=
|
|
62
|
+
sourcecode/mcp/runner.py,sha256=7PnFjKYbgxFeDnqVeSntXHxZX7ZtK3-krDkEuVjI24M,1386
|
|
63
|
+
sourcecode/mcp/server.py,sha256=Za6R1bGI7gQ9BI7M-UdBGK637W9wlowGcwjB0kRHZAs,4454
|
|
64
64
|
sourcecode/mcp/onboarding/__init__.py,sha256=sj2PWqEBmMc4zBNkomg89WtL0M6S7A9yb7_wAuSWNP4,66
|
|
65
65
|
sourcecode/mcp/onboarding/applier.py,sha256=yfSMT0NKdZsjavtLkC8yQ7OtkfepOl5IXGByqg6bdEY,1894
|
|
66
66
|
sourcecode/mcp/onboarding/backup.py,sha256=ihqGOR8QTX8HASRSEDyfFyXr5bkXrygPHamv4p9KTmk,1452
|
|
67
|
-
sourcecode/mcp/onboarding/detector.py,sha256=
|
|
67
|
+
sourcecode/mcp/onboarding/detector.py,sha256=kDc0U6kXMuq_GivqwKrgJzIVLVeoLr3RQl63ksW10I8,3327
|
|
68
68
|
sourcecode/mcp/onboarding/planner.py,sha256=Fopg5f72FDiPfldF7NOxYjcBA_w8hi_jBJpSz39lPb8,1332
|
|
69
69
|
sourcecode/telemetry/__init__.py,sha256=M0eQZFNkmJiLbI_oNP4QEXwVju1dQ2d4P-E1-Bw8PxE,3116
|
|
70
70
|
sourcecode/telemetry/config.py,sha256=Pir0WHp4z-9Qclnn2NDZ3vwitqsMkOAJckmwjUSxrk4,1795
|
|
@@ -72,8 +72,8 @@ sourcecode/telemetry/consent.py,sha256=wLMvGNJeSSyZoNkQXpoUioY6mMv4Qdvuw7S9jAEWn
|
|
|
72
72
|
sourcecode/telemetry/events.py,sha256=oEvvulfsv5GIDWG2174gSS6tNB95w38AIYiYeifGKlE,2294
|
|
73
73
|
sourcecode/telemetry/filters.py,sha256=Asa71oRl7q3Wt_FMwuufIZJFzSYdgRNKS8LHCIyFeYE,4805
|
|
74
74
|
sourcecode/telemetry/transport.py,sha256=KJeIPCPWMdmbCP3ySGs2iUlia34U6vWne2dZsUezesw,1560
|
|
75
|
-
sourcecode-1.31.
|
|
76
|
-
sourcecode-1.31.
|
|
77
|
-
sourcecode-1.31.
|
|
78
|
-
sourcecode-1.31.
|
|
79
|
-
sourcecode-1.31.
|
|
75
|
+
sourcecode-1.31.1.dist-info/METADATA,sha256=rNR0kignzicNPRwVkhFULiLqGAbSqzLZgkGckgA0E-Y,29083
|
|
76
|
+
sourcecode-1.31.1.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
|
|
77
|
+
sourcecode-1.31.1.dist-info/entry_points.txt,sha256=ex3F9rmbXeyDIoFQHtkEqTsKSaJow8F0LrVu8XfIktQ,57
|
|
78
|
+
sourcecode-1.31.1.dist-info/licenses/LICENSE,sha256=7DdHrU9Z_3e7dSvq4ISijZNjnuHo5NIHNiHDouMQ9JU,10491
|
|
79
|
+
sourcecode-1.31.1.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|