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/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
- # 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"
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 ~/.ask owner-only (0700). Holds the license secret.
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
- src = _read_config_path("delta_runs.json")
255
- if src.exists():
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 the license file, preferring ~/.ask and falling back to the legacy
289
- ~/.sourcecode. Returns parsed dict or None."""
271
+ """Read ~/.sourcecode/license.json. Returns parsed dict or None."""
290
272
  try:
291
- src = _read_config_path("license.json")
292
- if src.exists():
293
- return json.loads(src.read_text(encoding="utf-8"))
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: ask activate <license_key>")
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: ask activate <license_key>"
482
+ "Run: sourcecode activate <license_key>"
501
483
  ),
502
- "upgrade_hint": "ask activate <license_key>",
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: ask activate <license_key>"
533
+ "Run: sourcecode activate <license_key>"
552
534
  ),
553
- "upgrade_hint": "ask activate <license_key>",
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 = "ask" # canonical (product = ASK Engine)
10
- _LEGACY_ENTRY_NAME = "sourcecode" # deprecated alias — still recognized/cleaned
9
+ _ENTRY_NAME = "sourcecode"
11
10
  _ENTRY_VALUE: dict[str, object] = {
12
- "command": "ask",
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 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
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 the canonical `ask` entry merged into mcpServers.
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 the ASK Engine entry removed both the canonical
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:
@@ -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 "ask"
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 "ask",
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: ask <repo_path> --compact [--git-context]
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: ask <repo_path> --agent [--git-context]
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: ask <repo_path>/<module> --compact
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: ask telemetry <action>
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: ask prepare-context delta <repo_path> --since <since>
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: ask prepare-context review-pr <repo_path> [--since <since>]
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: ask prepare-context fix-bug <repo_path> [--symptom <symptom>]
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: ask prepare-context onboard <repo_path>
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: ask prepare-context explain <repo_path>
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: ask prepare-context refactor <repo_path>
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: ask prepare-context generate-tests <repo_path> [--all]
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: ask repo-ir <repo_path> --summary-only
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: ask repo-ir <path> --output ir.json
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: ask impact <target> <repo_path> [--depth <depth>]
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: ask modernize <repo_path>
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: ask endpoints <repo_path>
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: ask validation <repo_path>
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: ask cache status <repo_path> --json
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: ask cache warm <repo_path>
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: ask cache clear <repo_path> [--include-ris]
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"ask command failed (exit {result.exit_code}). Args: {args}",
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"ask command produced no output.\n"
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: ask activate <key>")
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="ask activate <license_key>",
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: ask <repo_path> --compact [--git-context]
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: ask <repo_path> --agent [--git-context]
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: ask endpoints <repo_path>
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: ask spring-audit <repo_path> --scope <scope>
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: ask migrate-check <repo_path> --min-severity <min_severity>
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: ask impact-chain <symbol> <repo_path> [--depth <depth>]
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: ask <repo_path>/<module> --compact
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: ask prepare-context delta <repo_path> --since <since>
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: ask repo-ir <repo_path> --summary-only
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: ask repo-ir <path> --output ir.json
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: ask prepare-context fix-bug <repo_path> [--symptom <symptom>]
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: ask prepare-context review-pr <repo_path> [--since <since>]
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: ask prepare-context onboard <repo_path>
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: ask prepare-context explain <repo_path>
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: ask prepare-context refactor <repo_path>
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: ask prepare-context generate-tests <repo_path> [--all]
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: ask impact <target> <repo_path> [--depth <depth>]
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: ask modernize <repo_path>
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 ASK Engine version and MCP compatibility metadata.
1371
+ """Return sourcecode version and MCP compatibility metadata.
1372
1372
 
1373
- Maps to: ask version
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: ask config
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: ask telemetry <action>
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 `ask mcp init` to enable agent integration.\n"
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.
@@ -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]