sourcecode 2.0.1__py3-none-any.whl → 2.1.0__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 +6 -2
- sourcecode/cli.py +211 -182
- sourcecode/license.py +31 -13
- sourcecode/mcp/onboarding/applier.py +14 -6
- 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/repository_ir.py +1 -1
- sourcecode/ris.py +1 -1
- sourcecode/security_posture.py +608 -0
- sourcecode/serializer.py +1 -1
- sourcecode/spring_findings.py +4 -0
- sourcecode/spring_security_audit.py +31 -0
- sourcecode/telemetry/consent.py +1 -1
- sourcecode/validation_inference.py +249 -0
- sourcecode-2.1.0.dist-info/METADATA +241 -0
- {sourcecode-2.0.1.dist-info → sourcecode-2.1.0.dist-info}/RECORD +21 -19
- {sourcecode-2.0.1.dist-info → sourcecode-2.1.0.dist-info}/entry_points.txt +1 -0
- sourcecode-2.0.1.dist-info/METADATA +0 -922
- {sourcecode-2.0.1.dist-info → sourcecode-2.1.0.dist-info}/WHEEL +0 -0
- {sourcecode-2.0.1.dist-info → sourcecode-2.1.0.dist-info}/licenses/LICENSE +0 -0
sourcecode/license.py
CHANGED
|
@@ -89,9 +89,25 @@ 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
|
-
|
|
92
|
+
# Config home: ~/.ask is canonical (product = ASK Engine). The legacy ~/.sourcecode
|
|
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"
|
|
93
97
|
_LICENSE_FILE: Path = _LICENSE_DIR / "license.json"
|
|
94
98
|
_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
|
|
95
111
|
_CACHE_TTL_SECONDS: int = 1800 # 30 minutes default; CI env overrides to 24h (see _get_cache_ttl)
|
|
96
112
|
_CACHE_TTL_CI_SECONDS: int = 86400 # 24 hours — CI containers must not re-validate mid-run
|
|
97
113
|
|
|
@@ -196,7 +212,7 @@ _PRO_UNLOCK_ALL = os.environ.get("SOURCECODE_PRO_UNLOCK", "1") != "0"
|
|
|
196
212
|
|
|
197
213
|
|
|
198
214
|
def _secure_dir() -> None:
|
|
199
|
-
"""Create ~/.
|
|
215
|
+
"""Create ~/.ask owner-only (0700). Holds the license secret.
|
|
200
216
|
|
|
201
217
|
``mkdir(mode=...)`` is ignored when the dir already exists, so chmod
|
|
202
218
|
unconditionally. Best-effort: a chmod failure (e.g. Windows) is non-fatal.
|
|
@@ -235,8 +251,9 @@ def _write_license_file(data: dict) -> None:
|
|
|
235
251
|
|
|
236
252
|
def _read_delta_runs() -> dict:
|
|
237
253
|
try:
|
|
238
|
-
|
|
239
|
-
|
|
254
|
+
src = _read_config_path("delta_runs.json")
|
|
255
|
+
if src.exists():
|
|
256
|
+
return json.loads(src.read_text(encoding="utf-8"))
|
|
240
257
|
except Exception:
|
|
241
258
|
pass
|
|
242
259
|
return {}
|
|
@@ -268,11 +285,12 @@ def check_delta_free_tier(repo_path: str) -> "tuple[bool, int, int]":
|
|
|
268
285
|
|
|
269
286
|
|
|
270
287
|
def _load_license_file() -> Optional[dict]:
|
|
271
|
-
"""Read
|
|
288
|
+
"""Read the license file, preferring ~/.ask and falling back to the legacy
|
|
289
|
+
~/.sourcecode. Returns parsed dict or None."""
|
|
272
290
|
try:
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
return json.loads(
|
|
291
|
+
src = _read_config_path("license.json")
|
|
292
|
+
if src.exists():
|
|
293
|
+
return json.loads(src.read_text(encoding="utf-8"))
|
|
276
294
|
except Exception:
|
|
277
295
|
pass
|
|
278
296
|
return None
|
|
@@ -409,7 +427,7 @@ def _emit_upgrade_and_exit(headline: str, body_lines: list[str], payload: dict)
|
|
|
409
427
|
lines.append(" Unlock Pro:")
|
|
410
428
|
where = _PURCHASE_URL or "see Pro plans (docs/PRODUCT_TIERS.md)"
|
|
411
429
|
lines.append(f" 1. Get a license: {where}")
|
|
412
|
-
lines.append(" 2. Activate:
|
|
430
|
+
lines.append(" 2. Activate: ask activate <license_key>")
|
|
413
431
|
lines.append("")
|
|
414
432
|
sys.stderr.write("\n".join(lines) + "\n")
|
|
415
433
|
sys.stderr.flush()
|
|
@@ -479,9 +497,9 @@ def require_feature(
|
|
|
479
497
|
"feature": feature_name,
|
|
480
498
|
"message": (
|
|
481
499
|
f"'{display}' requires a Pro license. "
|
|
482
|
-
"Run:
|
|
500
|
+
"Run: ask activate <license_key>"
|
|
483
501
|
),
|
|
484
|
-
"upgrade_hint": "
|
|
502
|
+
"upgrade_hint": "ask activate <license_key>",
|
|
485
503
|
}
|
|
486
504
|
if extra_fields:
|
|
487
505
|
payload.update(extra_fields)
|
|
@@ -530,9 +548,9 @@ def require_repo_or_pro(
|
|
|
530
548
|
f"This repository exceeds the free-tier limit of "
|
|
531
549
|
f"{_FREE_REPO_JAVA_FILE_LIMIT} Java source files. "
|
|
532
550
|
"Pro unlocks enterprise-scale monoliths. "
|
|
533
|
-
"Run:
|
|
551
|
+
"Run: ask activate <license_key>"
|
|
534
552
|
),
|
|
535
|
-
"upgrade_hint": "
|
|
553
|
+
"upgrade_hint": "ask activate <license_key>",
|
|
536
554
|
}
|
|
537
555
|
if extra_fields:
|
|
538
556
|
payload.update(extra_fields)
|
|
@@ -6,9 +6,10 @@ import os
|
|
|
6
6
|
from pathlib import Path
|
|
7
7
|
|
|
8
8
|
_MCP_SERVERS_KEY = "mcpServers"
|
|
9
|
-
_ENTRY_NAME = "
|
|
9
|
+
_ENTRY_NAME = "ask" # canonical (product = ASK Engine)
|
|
10
|
+
_LEGACY_ENTRY_NAME = "sourcecode" # deprecated alias — still recognized/cleaned
|
|
10
11
|
_ENTRY_VALUE: dict[str, object] = {
|
|
11
|
-
"command": "
|
|
12
|
+
"command": "ask",
|
|
12
13
|
"args": ["mcp", "serve"],
|
|
13
14
|
}
|
|
14
15
|
|
|
@@ -25,24 +26,31 @@ def read_config(path: Path) -> dict:
|
|
|
25
26
|
|
|
26
27
|
|
|
27
28
|
def is_installed(config: dict) -> bool:
|
|
28
|
-
"""True if
|
|
29
|
-
|
|
29
|
+
"""True if the ASK Engine entry (canonical `ask` or legacy `sourcecode`) is
|
|
30
|
+
already present in mcpServers."""
|
|
31
|
+
servers = config.get(_MCP_SERVERS_KEY, {})
|
|
32
|
+
return _ENTRY_NAME in servers or _LEGACY_ENTRY_NAME in servers
|
|
30
33
|
|
|
31
34
|
|
|
32
35
|
def apply_entry(config: dict) -> dict:
|
|
33
|
-
"""Return new config dict with
|
|
36
|
+
"""Return new config dict with the canonical `ask` entry merged into mcpServers.
|
|
37
|
+
Any legacy `sourcecode` entry is migrated away (removed) so the client launches
|
|
38
|
+
a single server."""
|
|
34
39
|
config = dict(config)
|
|
35
40
|
servers: dict = dict(config.get(_MCP_SERVERS_KEY, {}))
|
|
41
|
+
servers.pop(_LEGACY_ENTRY_NAME, None)
|
|
36
42
|
servers[_ENTRY_NAME] = _ENTRY_VALUE
|
|
37
43
|
config[_MCP_SERVERS_KEY] = servers
|
|
38
44
|
return config
|
|
39
45
|
|
|
40
46
|
|
|
41
47
|
def remove_entry(config: dict) -> dict:
|
|
42
|
-
"""Return new config dict with
|
|
48
|
+
"""Return new config dict with the ASK Engine entry removed — both the canonical
|
|
49
|
+
`ask` key and any legacy `sourcecode` key."""
|
|
43
50
|
config = dict(config)
|
|
44
51
|
servers: dict = dict(config.get(_MCP_SERVERS_KEY, {}))
|
|
45
52
|
servers.pop(_ENTRY_NAME, None)
|
|
53
|
+
servers.pop(_LEGACY_ENTRY_NAME, None)
|
|
46
54
|
if servers:
|
|
47
55
|
config[_MCP_SERVERS_KEY] = servers
|
|
48
56
|
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 "ask"
|
|
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 "ask",
|
|
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: ask <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: ask <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: ask <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: ask 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: ask 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: ask 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: ask 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: ask 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: ask 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: ask 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: ask 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: ask 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: ask 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: ask 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: ask 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: ask 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: ask 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: ask 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: ask 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: ask 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"ask 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"ask 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: ask 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="ask 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: ask <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: ask <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: ask 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: ask 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: ask 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: ask 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: ask <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: ask 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: ask 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: ask 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: ask 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: ask 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: ask 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: ask 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: ask 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: ask 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: ask 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: ask 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 ASK Engine version and MCP compatibility metadata.
|
|
1372
1372
|
|
|
1373
|
-
Maps to:
|
|
1373
|
+
Maps to: ask 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: ask 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: ask 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 `ask mcp init` to enable agent integration.\n"
|
|
30
30
|
)
|
|
31
31
|
|
|
32
32
|
# Module-level imports so names are patchable in tests.
|
sourcecode/repository_ir.py
CHANGED
|
@@ -5648,7 +5648,7 @@ def compute_blast_radius(
|
|
|
5648
5648
|
"message": (
|
|
5649
5649
|
f"No symbol matching {target!r} found in IR. "
|
|
5650
5650
|
"Verify the class name or FQN. "
|
|
5651
|
-
"Run `
|
|
5651
|
+
"Run `ask repo-ir <repo> --output ir.json` to inspect available symbols."
|
|
5652
5652
|
),
|
|
5653
5653
|
"direct_callers": [],
|
|
5654
5654
|
"indirect_callers": [],
|
sourcecode/ris.py
CHANGED
|
@@ -506,7 +506,7 @@ def get_cold_start_context(repo_root: Path) -> dict:
|
|
|
506
506
|
if not endpoints and _is_java:
|
|
507
507
|
result["endpoints_hint"] = (
|
|
508
508
|
"Java repo detected but no endpoint index found. "
|
|
509
|
-
"Call get_endpoints (or:
|
|
509
|
+
"Call get_endpoints (or: ask endpoints <path>) to populate."
|
|
510
510
|
)
|
|
511
511
|
return result
|
|
512
512
|
except Exception:
|