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/cli.py CHANGED
@@ -2,6 +2,7 @@ from __future__ import annotations
2
2
 
3
3
  import hashlib
4
4
  import json
5
+ import os
5
6
  import sys
6
7
  import threading
7
8
  import time
@@ -152,10 +153,10 @@ def _build_help_text() -> str:
152
153
  if _is_pro:
153
154
  plan_badge = "[bold green]● Pro[/bold green]"
154
155
  else:
155
- plan_badge = "[yellow]Free[/yellow] · [dim]sourcecode activate <key>[/dim] to unlock Pro"
156
+ plan_badge = "[yellow]Free[/yellow] · [dim]ask activate <key>[/dim] to unlock Pro"
156
157
 
157
158
  text = f"""\
158
- [bold]ASK Engine[/bold] [dim]· CLI: sourcecode[/dim] {plan_badge}
159
+ [bold]ASK Engine[/bold] [dim]· CLI: ask[/dim] {plan_badge}
159
160
 
160
161
  Persistent structural context and ultra-fast repeated analysis for AI coding agents.
161
162
 
@@ -163,9 +164,9 @@ Cache warms on first scan; every subsequent call returns pre-built context in mi
163
164
  Cold scan: 2–10s depending on repo size. Warm cache: 0.3–0.6s.
164
165
 
165
166
  [bold]Primary usage:[/bold]
166
- sourcecode --compact high-signal summary (~2,500–4,000 tokens)
167
- sourcecode --compact --git-context include git hotspots and uncommitted files
168
- sourcecode --agent full structured JSON for AI agents
167
+ ask --compact high-signal summary (~2,500–4,000 tokens)
168
+ ask --compact --git-context include git hotspots and uncommitted files
169
+ ask --agent full structured JSON for AI agents
169
170
 
170
171
  [bold]Auth commands:[/bold]
171
172
  auth status [dim]# show current plan and auth state[/dim]
@@ -177,11 +178,11 @@ Cold scan: 2–10s depending on repo size. Warm cache: 0.3–0.6s.
177
178
  cache clear [dim]# clear all cached results for this repo[/dim]
178
179
 
179
180
  [bold]Examples:[/bold]
180
- sourcecode my-project --compact
181
- sourcecode . --compact --git-context --copy
182
- sourcecode . --changed-only --git-context
183
- sourcecode prepare-context onboard my-project
184
- sourcecode prepare-context delta . --since main
181
+ ask my-project --compact
182
+ ask . --compact --git-context --copy
183
+ ask . --changed-only --git-context
184
+ ask prepare-context onboard my-project
185
+ ask prepare-context delta . --since main
185
186
 
186
187
  [bold]Subcommands:[/bold]
187
188
  prepare-context TASK [PATH] [dim]# task-specific context (onboard, delta, fix-bug, ...)[/dim]
@@ -206,7 +207,7 @@ Cold scan: 2–10s depending on repo size. Warm cache: 0.3–0.6s.
206
207
 
207
208
  [dim]Non-Java repos are free at any size. Local MCP serve is free.[/dim]
208
209
 
209
- [dim cyan]→ sourcecode activate <key>[/dim cyan]
210
+ [dim cyan]→ ask activate <key>[/dim cyan]
210
211
  """
211
212
 
212
213
  return text
@@ -523,7 +524,7 @@ def _copy_to_clipboard(content: str) -> bool:
523
524
 
524
525
 
525
526
  app = typer.Typer(
526
- name="sourcecode",
527
+ name="ask",
527
528
  help=_HELP,
528
529
  add_completion=False,
529
530
  rich_markup_mode="rich",
@@ -624,7 +625,7 @@ def _maybe_show_mcp_hint() -> None:
624
625
  return
625
626
  typer.echo("", err=True)
626
627
  typer.echo(" MCP integration available:", err=True)
627
- typer.echo(" → sourcecode mcp init", err=True)
628
+ typer.echo(" → ask mcp init", err=True)
628
629
  typer.echo("", err=True)
629
630
  data.setdefault("mcp", {})["hint_shown"] = True
630
631
  _save(data)
@@ -673,47 +674,40 @@ _IMPACT_PRIORITY_THRESHOLDS: list[tuple[float, str]] = [
673
674
 
674
675
  def version_callback(value: bool) -> None:
675
676
  if value:
676
- typer.echo(f"sourcecode {__version__}")
677
+ typer.echo(f"ask {__version__}")
677
678
  raise typer.Exit()
678
679
 
679
680
 
680
- # ANSI Shadow block wordmark, stacked "source" / "code" so it fits an 80-col
681
- # terminal (the single-line "sourcecode" is 83 wide and would wrap).
682
- _WELCOME_SOURCE = (
683
- "███████╗ ██████╗ ██╗ ██╗██████╗ ██████╗███████╗",
684
- "██╔════╝██╔═══██╗██║ ██║██╔══██╗██╔════╝██╔════╝",
685
- "███████╗██║ ██║██║ ██║██████╔╝██║ █████╗ ",
686
- "╚════██║██║ ██║██║ ██║██╔══██╗██║ ██╔══╝ ",
687
- "███████║╚██████╔╝╚██████╔╝██║ ██║╚██████╗███████╗",
688
- "╚══════╝ ╚═════╝ ╚═════╝ ╚═╝ ╚═╝ ╚═════╝╚══════╝",
689
- )
690
- _WELCOME_CODE = (
691
- " ██████╗ ██████╗ ██████╗ ███████╗",
692
- "██╔════╝██╔═══██╗██╔══██╗██╔════╝",
693
- "██║ ██║ ██║██║ ██║█████╗ ",
694
- "██║ ██║ ██║██║ ██║██╔══╝ ",
695
- "╚██████╗╚██████╔╝██████╔╝███████╗",
696
- " ╚═════╝ ╚═════╝ ╚═════╝ ╚══════╝",
681
+ # ANSI Shadow block wordmark: large "ASK" with a spaced "ENGINE" label beneath
682
+ # the product identity (ASK Engine), not the legacy "sourcecode".
683
+ _WELCOME_ASK = (
684
+ " █████╗ ███████╗██╗ ██╗",
685
+ "██╔══██╗██╔════╝██║ ██╔╝",
686
+ "███████║███████╗█████╔╝ ",
687
+ "██╔══██║╚════██║██╔═██╗ ",
688
+ "██║ ██║███████║██║ ██╗",
689
+ "╚═╝ ╚═╝╚══════╝╚═╝ ╚═╝",
697
690
  )
691
+ _WELCOME_ENGINE = "E N G I N E"
698
692
 
699
693
  _WELCOME_CMDS = (
700
- ("sourcecode --compact", "repo summary"),
701
- ("sourcecode migrate-check", "upgrade readiness (free)"),
702
- ("sourcecode prepare-context onboard", "onboarding"),
703
- ("sourcecode mcp init", "connect IDE"),
694
+ ("ask --compact", "repo summary"),
695
+ ("ask migrate-check", "upgrade readiness (free)"),
696
+ ("ask prepare-context onboard", "onboarding"),
697
+ ("ask mcp init", "connect IDE"),
704
698
  )
705
699
 
706
700
 
707
701
  def _print_welcome_plain(tier: str) -> None:
708
702
  """Plain-text welcome — fallback when rich is unavailable."""
709
- lines = ["", f" ASK Engine {__version__} · {tier} · CLI: sourcecode", "",
703
+ lines = ["", f" ASK Engine {__version__} · {tier} · CLI: ask", "",
710
704
  " AI coding-agent context, instant.", "", " Get started:"]
711
705
  for cmd, desc in _WELCOME_CMDS:
712
706
  lines.append(f" {cmd.ljust(34)}{desc}")
713
707
  lines.append("")
714
- lines.append(" sourcecode --help all commands")
708
+ lines.append(" ask --help all commands")
715
709
  if tier != "Pro":
716
- lines.append(" sourcecode activate <key> unlock Pro")
710
+ lines.append(" ask activate <key> unlock Pro")
717
711
  lines.append("")
718
712
  typer.echo("\n".join(lines))
719
713
 
@@ -741,20 +735,20 @@ def _print_welcome() -> None:
741
735
 
742
736
  pad = max(len(c) for c, _ in _WELCOME_CMDS) + 2
743
737
  tier_style = "green" if tier != "Pro" else "magenta"
744
- code_w = max(len(s) for s in _WELCOME_CODE)
738
+ ask_w = max(len(s) for s in _WELCOME_ASK)
745
739
 
746
740
  t = Text()
747
- for ln in _WELCOME_SOURCE:
748
- t.append(ln + "\n", style="bold cyan")
749
- # Append version + tier to the right of the "code" block's middle rows.
750
- for i, ln in enumerate(_WELCOME_CODE):
751
- t.append(ln.ljust(code_w), style="bold cyan")
741
+ # Large "ASK" wordmark; version + tier ride the right of its middle rows.
742
+ for i, ln in enumerate(_WELCOME_ASK):
743
+ t.append(ln.ljust(ask_w), style="bold cyan")
752
744
  if i == 2:
753
745
  t.append(f" {__version__}", style="dim")
754
746
  elif i == 3:
755
747
  t.append(" ")
756
748
  t.append(tier, style=tier_style)
757
749
  t.append("\n")
750
+ # "ENGINE" label beneath the wordmark.
751
+ t.append(_WELCOME_ENGINE + "\n", style="bold dim cyan")
758
752
 
759
753
  t.append("\nAI coding-agent context, instant.\n\n", style="white")
760
754
 
@@ -1016,10 +1010,10 @@ def main(
1016
1010
 
1017
1011
  \b
1018
1012
  Examples:
1019
- sourcecode --compact high-signal summary (recommended)
1020
- sourcecode --compact --git-context include git hotspots
1021
- sourcecode /path/to/repo --compact analyze specific path
1022
- sourcecode --agent agent-optimized output (full detail)
1013
+ ask --compact high-signal summary (recommended)
1014
+ ask --compact --git-context include git hotspots
1015
+ ask /path/to/repo --compact analyze specific path
1016
+ ask --agent agent-optimized output (full detail)
1023
1017
  """
1024
1018
  # First-run telemetry notice (skip for telemetry/version/config subcommands)
1025
1019
  if ctx.invoked_subcommand not in ("telemetry", "version", "config"):
@@ -2777,14 +2771,14 @@ def prepare_context_cmd(
2777
2771
 
2778
2772
  \b
2779
2773
  Examples:
2780
- sourcecode prepare-context explain
2781
- sourcecode prepare-context explain /path/to/repo
2782
- sourcecode prepare-context fix-bug
2783
- sourcecode prepare-context delta --since main
2784
- sourcecode prepare-context review-pr --since origin/main
2785
- sourcecode prepare-context review-pr . --since main --output review.json
2786
- sourcecode prepare-context onboard --llm-prompt
2787
- sourcecode prepare-context --task-help
2774
+ ask prepare-context explain
2775
+ ask prepare-context explain /path/to/repo
2776
+ ask prepare-context fix-bug
2777
+ ask prepare-context delta --since main
2778
+ ask prepare-context review-pr --since origin/main
2779
+ ask prepare-context review-pr . --since main --output review.json
2780
+ ask prepare-context onboard --llm-prompt
2781
+ ask prepare-context --task-help
2788
2782
  """
2789
2783
  from sourcecode.prepare_context import TASKS, TaskContextBuilder
2790
2784
 
@@ -2835,7 +2829,7 @@ def prepare_context_cmd(
2835
2829
  "free_tier_note": (
2836
2830
  f"Free quota of {30} delta runs per repository exhausted."
2837
2831
  ),
2838
- "free_tier_alternative": "sourcecode prepare-context review-pr --since <ref>",
2832
+ "free_tier_alternative": "ask prepare-context review-pr --since <ref>",
2839
2833
  },
2840
2834
  )
2841
2835
  # Within quota: emit a header note so CI logs show remaining runs.
@@ -3347,7 +3341,7 @@ def telemetry_status() -> None:
3347
3341
  if not asked:
3348
3342
  typer.echo(" (first-run notice not yet shown — will show on next run)")
3349
3343
  typer.echo(f" Config: {config_file_path()}")
3350
- typer.echo(" Disable: sourcecode telemetry disable")
3344
+ typer.echo(" Disable: ask telemetry disable")
3351
3345
  typer.echo(" Or set env var: SOURCECODE_TELEMETRY=0 (or DO_NOT_TRACK=1)")
3352
3346
 
3353
3347
 
@@ -3360,7 +3354,7 @@ def telemetry_enable() -> None:
3360
3354
  typer.echo("Telemetry enabled. Thank you — this helps improve sourcecode.")
3361
3355
  typer.echo("What is collected: version, OS, commands, flags, duration, repo size range, errors.")
3362
3356
  typer.echo("What is never collected: source code, paths, secrets, or any output content.")
3363
- typer.echo("Disable at any time: sourcecode telemetry disable")
3357
+ typer.echo("Disable at any time: ask telemetry disable")
3364
3358
  _tel.record("telemetry_enabled", cmd="telemetry")
3365
3359
 
3366
3360
 
@@ -3371,7 +3365,7 @@ def telemetry_disable() -> None:
3371
3365
  set_enabled(False)
3372
3366
  typer.echo("Telemetry disabled. No data will be collected or sent.")
3373
3367
  typer.echo("Telemetry is on by default; this opt-out is remembered.")
3374
- typer.echo("Re-enable at any time: sourcecode telemetry enable")
3368
+ typer.echo("Re-enable at any time: ask telemetry enable")
3375
3369
 
3376
3370
 
3377
3371
  def _serialize_dict(data: dict, format: str) -> str:
@@ -3474,13 +3468,13 @@ def repo_ir_cmd(
3474
3468
 
3475
3469
  \b
3476
3470
  Examples:
3477
- sourcecode repo-ir
3478
- sourcecode repo-ir /path/to/repo --since HEAD~1
3479
- sourcecode repo-ir --files src/main/java/UserService.java
3480
- sourcecode repo-ir --since main --output ir.json
3481
- sourcecode repo-ir --since HEAD~3 --summary-only --output ir-small.json
3482
- sourcecode repo-ir --max-nodes 200 --max-edges 500
3483
- sourcecode repo-ir --output ir.json.gz --gzip
3471
+ ask repo-ir
3472
+ ask repo-ir /path/to/repo --since HEAD~1
3473
+ ask repo-ir --files src/main/java/UserService.java
3474
+ ask repo-ir --since main --output ir.json
3475
+ ask repo-ir --since HEAD~3 --summary-only --output ir-small.json
3476
+ ask repo-ir --max-nodes 200 --max-edges 500
3477
+ ask repo-ir --output ir.json.gz --gzip
3484
3478
  """
3485
3479
  import json as _json
3486
3480
 
@@ -3705,13 +3699,13 @@ def impact_cmd(
3705
3699
 
3706
3700
  \b
3707
3701
  NOTE: Free on repos up to the size limit; Pro unlocks enterprise-scale
3708
- monoliths. Run 'sourcecode license' for details.
3702
+ monoliths. Run 'ask license' for details.
3709
3703
 
3710
3704
  \b
3711
3705
  Examples:
3712
- sourcecode impact UserService
3713
- sourcecode impact org.keycloak.services.DefaultKeycloakSession /path/to/keycloak
3714
- sourcecode impact UserService --depth 6 --output impact.json
3706
+ ask impact UserService
3707
+ ask impact org.keycloak.services.DefaultKeycloakSession /path/to/keycloak
3708
+ ask impact UserService --depth 6 --output impact.json
3715
3709
  """
3716
3710
  from sourcecode.license import require_repo_or_pro as _require_repo_or_pro
3717
3711
  _require_repo_or_pro(str(path.resolve()), "impact")
@@ -3733,7 +3727,7 @@ def impact_cmd(
3733
3727
  sys.stderr.write(
3734
3728
  f"[impact] Legacy argument order detected: '{target}' is a directory, not a class name.\n"
3735
3729
  f"[impact] Swapping: target='{path}', path='{target}'. "
3736
- f"New syntax: sourcecode impact <target> [path]\n"
3730
+ f"New syntax: ask impact <target> [path]\n"
3737
3731
  )
3738
3732
  sys.stderr.flush()
3739
3733
  target, path = str(path), _target_as_path
@@ -3742,7 +3736,7 @@ def impact_cmd(
3742
3736
  _emit_error_json(
3743
3737
  INVALID_INPUT_CODE,
3744
3738
  "Class name must not be empty.",
3745
- hint="Pass a class name or FQN. Example: sourcecode impact OrderService .",
3739
+ hint="Pass a class name or FQN. Example: ask impact OrderService .",
3746
3740
  expected="A non-empty class name or FQN.",
3747
3741
  )
3748
3742
  raise typer.Exit(1)
@@ -3755,7 +3749,7 @@ def impact_cmd(
3755
3749
  path=str(root),
3756
3750
  hint=(
3757
3751
  "Pass an existing repository directory as the second argument. "
3758
- "New syntax: sourcecode impact <target> [path] — "
3752
+ "New syntax: ask impact <target> [path] — "
3759
3753
  "target is the class name, path is the repo root."
3760
3754
  ),
3761
3755
  expected="A directory path.",
@@ -3896,13 +3890,13 @@ def endpoints_cmd(
3896
3890
 
3897
3891
  \b
3898
3892
  Examples:
3899
- sourcecode endpoints .
3900
- sourcecode endpoints /path/to/repo
3901
- sourcecode endpoints . --output endpoints.json
3902
- sourcecode endpoints . --format yaml
3903
- sourcecode endpoints . --path-prefix /v1/liquidacion
3904
- sourcecode endpoints . --controller LiquidacionJornada
3905
- sourcecode endpoints . --limit 10
3893
+ ask endpoints .
3894
+ ask endpoints /path/to/repo
3895
+ ask endpoints . --output endpoints.json
3896
+ ask endpoints . --format yaml
3897
+ ask endpoints . --path-prefix /v1/liquidacion
3898
+ ask endpoints . --controller LiquidacionJornada
3899
+ ask endpoints . --limit 10
3906
3900
  """
3907
3901
  _enforce_format("endpoints", format)
3908
3902
 
@@ -4481,10 +4475,10 @@ def validation_cmd(
4481
4475
 
4482
4476
  \b
4483
4477
  Examples:
4484
- sourcecode validation .
4485
- sourcecode validation . --gaps-only
4486
- sourcecode validation . --path-prefix /owners
4487
- sourcecode validation . --format yaml
4478
+ ask validation .
4479
+ ask validation . --gaps-only
4480
+ ask validation . --path-prefix /owners
4481
+ ask validation . --format yaml
4488
4482
  """
4489
4483
  _enforce_format("validation", format)
4490
4484
 
@@ -4502,7 +4496,18 @@ def validation_cmd(
4502
4496
  from sourcecode.context_graph import ContextGraph
4503
4497
  from sourcecode.validation_surface import build_validation_surface
4504
4498
  # Structural facts come from the ContextGraph — the single access layer.
4505
- data = build_validation_surface(target, graph=ContextGraph.build_from_root(target))
4499
+ _graph = ContextGraph.build_from_root(target)
4500
+ data = build_validation_surface(target, graph=_graph)
4501
+
4502
+ # P1-C: classify the request-body validation *pattern* so a repo with a
4503
+ # validation framework on the classpath but no @Valid reads as
4504
+ # "present-but-unused" instead of a silent sea of zeros (DESIGN §2/§5).
4505
+ try:
4506
+ from sourcecode.validation_inference import infer_validation_pattern
4507
+
4508
+ data["validation_pattern"] = infer_validation_pattern(_graph.cir).to_dict()
4509
+ except Exception:
4510
+ pass
4506
4511
 
4507
4512
  if path_prefix:
4508
4513
  data["endpoints"] = [
@@ -4676,17 +4681,17 @@ def spring_audit_cmd(
4676
4681
 
4677
4682
  \b
4678
4683
  CI/CD usage:
4679
- sourcecode spring-audit . --ci # exit 1 on any finding
4680
- sourcecode spring-audit . --ci --min-severity high # exit 1 only on high/critical
4681
- sourcecode spring-audit . --ci --format github-comment # Markdown PR comment + exit 1
4684
+ ask spring-audit . --ci # exit 1 on any finding
4685
+ ask spring-audit . --ci --min-severity high # exit 1 only on high/critical
4686
+ ask spring-audit . --ci --format github-comment # Markdown PR comment + exit 1
4682
4687
 
4683
4688
  \b
4684
4689
  Examples:
4685
- sourcecode spring-audit .
4686
- sourcecode spring-audit /path/to/repo
4687
- sourcecode spring-audit . --scope security
4688
- sourcecode spring-audit . --min-severity high
4689
- sourcecode spring-audit . --output audit.json
4690
+ ask spring-audit .
4691
+ ask spring-audit /path/to/repo
4692
+ ask spring-audit . --scope security
4693
+ ask spring-audit . --min-severity high
4694
+ ask spring-audit . --output audit.json
4690
4695
  """
4691
4696
  import json as _json
4692
4697
 
@@ -4882,11 +4887,11 @@ def migrate_check_cmd(
4882
4887
 
4883
4888
  \b
4884
4889
  Examples:
4885
- sourcecode migrate-check .
4886
- sourcecode migrate-check . --compact bounded decision summary
4887
- sourcecode migrate-check /path/to/repo --format text
4888
- sourcecode migrate-check . --min-severity high
4889
- sourcecode migrate-check . --output migration.json
4890
+ ask migrate-check .
4891
+ ask migrate-check . --compact bounded decision summary
4892
+ ask migrate-check /path/to/repo --format text
4893
+ ask migrate-check . --min-severity high
4894
+ ask migrate-check . --output migration.json
4890
4895
  """
4891
4896
  from sourcecode.repository_ir import find_java_files
4892
4897
  from sourcecode.migrate_check import run_migrate_check
@@ -5011,9 +5016,9 @@ def impact_chain_cmd(
5011
5016
 
5012
5017
  \b
5013
5018
  Examples:
5014
- sourcecode impact-chain OrderService .
5015
- sourcecode impact-chain com.example.OrderService#placeOrder /path/to/repo
5016
- sourcecode impact-chain PaymentService . --depth 6 --output impact.json
5019
+ ask impact-chain OrderService .
5020
+ ask impact-chain com.example.OrderService#placeOrder /path/to/repo
5021
+ ask impact-chain PaymentService . --depth 6 --output impact.json
5017
5022
  """
5018
5023
  import json as _json
5019
5024
 
@@ -5027,7 +5032,7 @@ def impact_chain_cmd(
5027
5032
  _emit_error_json(
5028
5033
  INVALID_INPUT_CODE,
5029
5034
  "Symbol name must not be empty.",
5030
- hint="Pass a class name or FQN. Example: sourcecode impact-chain OrderService .",
5035
+ hint="Pass a class name or FQN. Example: ask impact-chain OrderService .",
5031
5036
  expected="A non-empty class name or FQN.",
5032
5037
  )
5033
5038
  raise typer.Exit(code=1)
@@ -5155,9 +5160,9 @@ def pr_impact_cmd(
5155
5160
 
5156
5161
  \b
5157
5162
  Examples:
5158
- sourcecode pr-impact --files changed_files.txt
5159
- sourcecode pr-impact /path/to/repo --files diff.txt --format json
5160
- sourcecode pr-impact --files changes.txt --output pr_report.txt
5163
+ ask pr-impact --files changed_files.txt
5164
+ ask pr-impact /path/to/repo --files diff.txt --format json
5165
+ ask pr-impact --files changes.txt --output pr_report.txt
5161
5166
  """
5162
5167
  import json as _json
5163
5168
 
@@ -5184,7 +5189,7 @@ def pr_impact_cmd(
5184
5189
  path=str(files),
5185
5190
  hint=(
5186
5191
  "Create a file with one changed Java file path per line, then pass it with --files. "
5187
- "Example: git diff --name-only HEAD~1 > changed.txt && sourcecode pr-impact . --files changed.txt"
5192
+ "Example: git diff --name-only HEAD~1 > changed.txt && ask pr-impact . --files changed.txt"
5188
5193
  ),
5189
5194
  expected="A text file containing one Java file path per line.",
5190
5195
  )
@@ -5291,9 +5296,9 @@ def explain_cmd(
5291
5296
 
5292
5297
  \b
5293
5298
  Examples:
5294
- sourcecode explain UserService
5295
- sourcecode explain OrderController /path/to/repo
5296
- sourcecode explain UserService --format json
5299
+ ask explain UserService
5300
+ ask explain OrderController /path/to/repo
5301
+ ask explain UserService --format json
5297
5302
  """
5298
5303
  import json as _json
5299
5304
 
@@ -5306,7 +5311,7 @@ def explain_cmd(
5306
5311
  _emit_error_json(
5307
5312
  INVALID_INPUT_CODE,
5308
5313
  "Class name must not be empty.",
5309
- hint="Pass a class name. Example: sourcecode explain UserService .",
5314
+ hint="Pass a class name. Example: ask explain UserService .",
5310
5315
  expected="A non-empty class name.",
5311
5316
  )
5312
5317
  raise typer.Exit(code=1)
@@ -5338,7 +5343,7 @@ def explain_cmd(
5338
5343
  _emit_error_json(
5339
5344
  INVALID_INPUT_CODE,
5340
5345
  f"No Java files found in '{target}'.",
5341
- hint="sourcecode explain requires a Java/Spring repository.",
5346
+ hint="ask explain requires a Java/Spring repository.",
5342
5347
  expected="A directory containing .java source files.",
5343
5348
  )
5344
5349
  raise typer.Exit(code=1)
@@ -5399,16 +5404,16 @@ def onboard_cmd(
5399
5404
 
5400
5405
  \b
5401
5406
  Workflow:
5402
- sourcecode onboard .
5403
- sourcecode onboard /path/to/repo --llm-prompt
5404
- sourcecode onboard . --output onboard.json
5407
+ ask onboard .
5408
+ ask onboard /path/to/repo --llm-prompt
5409
+ ask onboard . --output onboard.json
5405
5410
 
5406
5411
  \b
5407
5412
  Related workflows:
5408
- sourcecode impact <target> — What breaks if I touch this?
5409
- sourcecode review-pr — Risk-rank a pending PR
5410
- sourcecode modernize . — Where should I refactor first?
5411
- sourcecode fix-bug — Where does this symptom live?
5413
+ ask impact <target> — What breaks if I touch this?
5414
+ ask review-pr — Risk-rank a pending PR
5415
+ ask modernize . — Where should I refactor first?
5416
+ ask fix-bug — Where does this symptom live?
5412
5417
  """
5413
5418
  ctx.invoke(
5414
5419
  prepare_context_cmd,
@@ -5468,14 +5473,14 @@ def review_pr_cmd(
5468
5473
 
5469
5474
  \b
5470
5475
  Workflow:
5471
- sourcecode review-pr --since origin/main
5472
- sourcecode review-pr . --since main --format github-comment
5473
- sourcecode review-pr . --since HEAD~3 --output review.json
5476
+ ask review-pr --since origin/main
5477
+ ask review-pr . --since main --format github-comment
5478
+ ask review-pr . --since HEAD~3 --output review.json
5474
5479
 
5475
5480
  \b
5476
5481
  Related workflows:
5477
- sourcecode impact <target> — Single-symbol blast radius
5478
- sourcecode onboard . — Full architecture onboarding
5482
+ ask impact <target> — Single-symbol blast radius
5483
+ ask onboard . — Full architecture onboarding
5479
5484
  """
5480
5485
  ctx.invoke(
5481
5486
  prepare_context_cmd,
@@ -5528,14 +5533,14 @@ def fix_bug_cmd(
5528
5533
 
5529
5534
  \b
5530
5535
  Workflow:
5531
- sourcecode fix-bug --symptom "NullPointerException in UserService"
5532
- sourcecode fix-bug . --symptom "401 on /api/orders"
5533
- sourcecode fix-bug . --output bug-context.json
5536
+ ask fix-bug --symptom "NullPointerException in UserService"
5537
+ ask fix-bug . --symptom "401 on /api/orders"
5538
+ ask fix-bug . --output bug-context.json
5534
5539
 
5535
5540
  \b
5536
5541
  Related workflows:
5537
- sourcecode impact <target> — Propagate impact from a specific class
5538
- sourcecode onboard . — Full architecture context first
5542
+ ask impact <target> — Propagate impact from a specific class
5543
+ ask onboard . — Full architecture context first
5539
5544
  """
5540
5545
  # Detect misuse: `fix-bug "symptom text" /path` — path arg looks like a symptom.
5541
5546
  _path_str = str(path)
@@ -5547,7 +5552,7 @@ def fix_bug_cmd(
5547
5552
  _emit_error_json(
5548
5553
  INVALID_INPUT_CODE,
5549
5554
  f"'{_path_str}' is not a valid directory. Did you mean to use --symptom?",
5550
- hint=f"Use: sourcecode fix-bug . --symptom {_path_str!r}",
5555
+ hint=f"Use: ask fix-bug . --symptom {_path_str!r}",
5551
5556
  expected="A repository directory path as first argument.",
5552
5557
  )
5553
5558
  raise typer.Exit(code=1)
@@ -5697,13 +5702,13 @@ def modernize_cmd(
5697
5702
 
5698
5703
  \b
5699
5704
  Workflow:
5700
- sourcecode modernize .
5701
- sourcecode modernize /path/to/repo --output modernize.json
5705
+ ask modernize .
5706
+ ask modernize /path/to/repo --output modernize.json
5702
5707
 
5703
5708
  \b
5704
5709
  Related workflows:
5705
- sourcecode onboard . — Architecture overview first
5706
- sourcecode impact <target> — Verify impact before touching a hotspot
5710
+ ask onboard . — Architecture overview first
5711
+ ask impact <target> — Verify impact before touching a hotspot
5707
5712
  """
5708
5713
  import json as _json
5709
5714
  from sourcecode.repository_ir import build_repo_ir, find_java_files, apply_ir_size_limits
@@ -5948,7 +5953,7 @@ def modernize_cmd(
5948
5953
  "in_degree = raw count of incoming graph edges across ALL edge types "
5949
5954
  "(imports, injects, extends/implements, references, annotations), "
5950
5955
  "counted at symbol level. This is deliberately larger than and NOT "
5951
- "equal to `sourcecode explain`'s caller count, which reports DISTINCT "
5956
+ "equal to `ask explain`'s caller count, which reports DISTINCT "
5952
5957
  "dependent classes (DI dependents + reverse-call-graph callers, "
5953
5958
  "deduplicated to class level). Use in_degree for blast-radius ranking, "
5954
5959
  "explain's caller list for the concrete dependents to inspect."
@@ -6061,10 +6066,10 @@ def rename_class_cmd(
6061
6066
 
6062
6067
  \b
6063
6068
  Examples:
6064
- sourcecode rename-class . --from ServiceA --to ServiceB
6065
- sourcecode rename-class /path/to/repo --from OrderManager --to OrderService
6066
- sourcecode rename-class . --from OldName --to NewName --dry-run
6067
- sourcecode rename-class . --from OldName --to NewName --output rename-audit.json
6069
+ ask rename-class . --from ServiceA --to ServiceB
6070
+ ask rename-class /path/to/repo --from OrderManager --to OrderService
6071
+ ask rename-class . --from OldName --to NewName --dry-run
6072
+ ask rename-class . --from OldName --to NewName --output rename-audit.json
6068
6073
  """
6069
6074
  import json as _json
6070
6075
  from sourcecode.rename_refactor import rename_class
@@ -6167,10 +6172,10 @@ def chunk_file_cmd(
6167
6172
 
6168
6173
  \b
6169
6174
  Examples:
6170
- sourcecode chunk-file NominasCalculoService.java
6171
- sourcecode chunk-file BigService.java --max-lines 300
6172
- sourcecode chunk-file BigService.java --chunk 5 # read chunk 5 only
6173
- sourcecode chunk-file BigService.java --metadata-only # sizes/boundaries only
6175
+ ask chunk-file NominasCalculoService.java
6176
+ ask chunk-file BigService.java --max-lines 300
6177
+ ask chunk-file BigService.java --chunk 5 # read chunk 5 only
6178
+ ask chunk-file BigService.java --metadata-only # sizes/boundaries only
6174
6179
  """
6175
6180
  import json as _json
6176
6181
  from sourcecode.file_chunker import chunk_java_file
@@ -6234,7 +6239,7 @@ def activate_cmd(
6234
6239
 
6235
6240
  \b
6236
6241
  Examples:
6237
- sourcecode activate SC-XXXX-XXXX-XXXX
6242
+ ask activate SC-XXXX-XXXX-XXXX
6238
6243
  """
6239
6244
  from sourcecode.license import activate_license as _activate
6240
6245
  _activate(license_key)
@@ -6300,7 +6305,7 @@ def version_cmd() -> None:
6300
6305
  "compatibility_schema_version": "1.0"}
6301
6306
  """
6302
6307
  if getattr(sys.stdout, "isatty", lambda: False)():
6303
- typer.echo(f"sourcecode {__version__}")
6308
+ typer.echo(f"ask {__version__}")
6304
6309
  else:
6305
6310
  import json as _json_ver
6306
6311
  typer.echo(_json_ver.dumps({
@@ -6316,14 +6321,14 @@ def version_cmd() -> None:
6316
6321
  def config_cmd() -> None:
6317
6322
  """Show current configuration."""
6318
6323
  from sourcecode.telemetry.config import config_file_path, is_enabled
6319
- typer.echo(f"sourcecode {__version__}")
6324
+ typer.echo(f"ask {__version__}")
6320
6325
  typer.echo(f"Config: {config_file_path()}")
6321
6326
  typer.echo(f"Telemetry: {'enabled' if is_enabled() else 'disabled'}")
6322
6327
  typer.echo("")
6323
6328
  typer.echo("Manage telemetry:")
6324
- typer.echo(" sourcecode telemetry enable")
6325
- typer.echo(" sourcecode telemetry disable")
6326
- typer.echo(" sourcecode telemetry status")
6329
+ typer.echo(" ask telemetry enable")
6330
+ typer.echo(" ask telemetry disable")
6331
+ typer.echo(" ask telemetry status")
6327
6332
 
6328
6333
 
6329
6334
  # ── cold-start (RIS bootstrap for external MCP and agents) ───────────────────
@@ -6367,7 +6372,7 @@ def cold_start_cmd(
6367
6372
  if isinstance(result.get("endpoints"), list):
6368
6373
  result["endpoints"] = result["endpoints"][:30]
6369
6374
  result["_meta"] = {**(result.get("_meta") or {}), "compact_mode": True,
6370
- "full_available": "sourcecode cold-start (without --compact)"}
6375
+ "full_available": "ask cold-start (without --compact)"}
6371
6376
  _out = _json.dumps(result, indent=2, ensure_ascii=False)
6372
6377
  _size = len(_out.encode("utf-8"))
6373
6378
  _tokens = _size // 4
@@ -6458,13 +6463,13 @@ def mcp_init(
6458
6463
 
6459
6464
  \b
6460
6465
  Detects installed MCP clients, backs up their config files, and safely
6461
- inserts the sourcecode server entry. Fully idempotent — safe to re-run.
6466
+ inserts the ASK Engine server entry. Fully idempotent — safe to re-run.
6462
6467
 
6463
6468
  \b
6464
6469
  Examples:
6465
- sourcecode mcp init
6466
- sourcecode mcp init --target claude-desktop
6467
- sourcecode mcp init --target cursor --yes
6470
+ ask mcp init
6471
+ ask mcp init --target claude-desktop
6472
+ ask mcp init --target cursor --yes
6468
6473
  """
6469
6474
  from sourcecode.mcp.onboarding.detector import detect_clients, is_client_running
6470
6475
  from sourcecode.mcp.onboarding.planner import build_install_plan
@@ -6509,7 +6514,7 @@ def mcp_init(
6509
6514
  for a in already_done:
6510
6515
  typer.echo(f" ✓ {a.client.name} {a.client.config_path}")
6511
6516
  typer.echo("")
6512
- typer.echo("Nothing to do. Remove: sourcecode mcp remove")
6517
+ typer.echo("Nothing to do. Remove: ask mcp remove")
6513
6518
  raise typer.Exit(code=0)
6514
6519
 
6515
6520
  if already_done:
@@ -6566,7 +6571,7 @@ def mcp_init(
6566
6571
  if not is_client_running(a.client):
6567
6572
  typer.echo(
6568
6573
  f" ⚠ Config written but {a.client.name} is not running. "
6569
- f"Start {a.client.name} and run sourcecode mcp status to verify.",
6574
+ f"Start {a.client.name} and run ask mcp status to verify.",
6570
6575
  err=False,
6571
6576
  )
6572
6577
  else:
@@ -6574,7 +6579,7 @@ def mcp_init(
6574
6579
  typer.echo(f" ✓ {a.client.name} is running.{restart_msg}")
6575
6580
 
6576
6581
  typer.echo("")
6577
- typer.echo(" Remove: sourcecode mcp remove")
6582
+ typer.echo(" Remove: ask mcp remove")
6578
6583
 
6579
6584
  # Clear nudge flag: next run finds is_installed=True → no nudge.
6580
6585
  from sourcecode.mcp_nudge import clear_nudge_flag as _clear_nudge
@@ -6611,18 +6616,18 @@ def mcp_status() -> None:
6611
6616
  if not clients:
6612
6617
  typer.echo(" No MCP clients detected on this system.")
6613
6618
  typer.echo(sep)
6614
- typer.echo(" Setup: sourcecode mcp init")
6619
+ typer.echo(" Setup: ask mcp init")
6615
6620
  raise typer.Exit(code=0)
6616
6621
 
6617
6622
  # Stage 2: Config files — is sourcecode registered in the client's config?
6618
6623
  # FIX-P0-6: "configured" and "running" are distinct, independent checks.
6619
6624
  # Also detect external server installs (different Python/executable than CLI).
6620
- typer.echo("Config (sourcecode registered in client config?)")
6625
+ typer.echo("Config (ASK Engine registered in client config?)")
6621
6626
  for client in clients:
6622
6627
  if not client.app_installed:
6623
6628
  typer.echo(f" {client.name:<20} ✗ app not found at expected path")
6624
6629
  typer.echo(f" Expected: {client.config_path}")
6625
- typer.echo(f" Fix: sourcecode mcp init --target {client.slug}")
6630
+ typer.echo(f" Fix: ask mcp init --target {client.slug}")
6626
6631
  continue
6627
6632
  config = applier.read_config(client.config_path)
6628
6633
  if applier.is_installed(config):
@@ -6638,11 +6643,11 @@ def mcp_status() -> None:
6638
6643
  or (_reg_args and _reg_args[:2] == ["mcp", "serve"])
6639
6644
  )
6640
6645
  if _is_builtin:
6641
- typer.echo(f" Server: built-in (sourcecode mcp serve) version={_cli_version}")
6646
+ typer.echo(f" Server: built-in (ask mcp serve) version={_cli_version}")
6642
6647
  else:
6643
6648
  # External server — different Python or custom server.py
6644
6649
  typer.echo(f" Server: ⚠ EXTERNAL — {_reg_cmd} {' '.join(_reg_args)}")
6645
- # Try to get the external server's sourcecode version by finding the
6650
+ # Try to get the external server's ask version by finding the
6646
6651
  # sourcecode binary relative to the registered Python executable,
6647
6652
  # or falling back to probing the Python for the installed package.
6648
6653
  _ext_ver: str = "unknown"
@@ -6676,18 +6681,18 @@ def mcp_status() -> None:
6676
6681
  f"CLI version={_cli_version}"
6677
6682
  )
6678
6683
  typer.echo(
6679
- " To fix: sourcecode mcp init (re-register using CLI built-in server)"
6684
+ " To fix: ask mcp init (re-register using CLI built-in server)"
6680
6685
  )
6681
6686
  elif _ext_ver != "unknown":
6682
6687
  typer.echo(f" External server version={_ext_ver} (matches CLI ✓)")
6683
6688
  else:
6684
6689
  typer.echo(
6685
6690
  f" ⚠ Cannot verify external server version (CLI={_cli_version}). "
6686
- "Re-run: sourcecode mcp init to switch to built-in server."
6691
+ "Re-run: ask mcp init to switch to built-in server."
6687
6692
  )
6688
6693
  else:
6689
- typer.echo(f" {client.name:<20} ✗ not configured (app found, but sourcecode entry missing)")
6690
- typer.echo(f" Fix: sourcecode mcp init --target {client.slug}")
6694
+ typer.echo(f" {client.name:<20} ✗ not configured (app found, but ASK Engine entry missing)")
6695
+ typer.echo(f" Fix: ask mcp init --target {client.slug}")
6691
6696
  typer.echo("")
6692
6697
 
6693
6698
  # Build config state map for cross-check in Stage 3.
@@ -6715,19 +6720,19 @@ def mcp_status() -> None:
6715
6720
  _action_required.append(client.name)
6716
6721
  else:
6717
6722
  typer.echo(f" {client.name:<20} ✗ not running")
6718
- typer.echo(f" Fix: open {client.name}, then run sourcecode mcp status")
6723
+ typer.echo(f" Fix: open {client.name}, then run ask mcp status")
6719
6724
 
6720
6725
  typer.echo(sep)
6721
6726
  if _action_required:
6722
6727
  for _name in _action_required:
6723
- typer.echo(f" ⚠ ACTION REQUIRED: {_name} is running but sourcecode is not configured.")
6724
- typer.echo(" Run: sourcecode mcp init")
6728
+ typer.echo(f" ⚠ ACTION REQUIRED: {_name} is running but ASK Engine is not configured.")
6729
+ typer.echo(" Run: ask mcp init")
6725
6730
  typer.echo("")
6726
6731
  typer.echo(" Note: 'configured' and 'running' are checked independently.")
6727
6732
  typer.echo(" A running app still needs restart after first-time config.")
6728
6733
  typer.echo(" Path: repo_path must use forward slashes: C:/Users/... or /unix/path")
6729
- typer.echo(" Setup: sourcecode mcp init")
6730
- typer.echo(" Remove: sourcecode mcp remove")
6734
+ typer.echo(" Setup: ask mcp init")
6735
+ typer.echo(" Remove: ask mcp remove")
6731
6736
 
6732
6737
 
6733
6738
  @mcp_app.command("remove")
@@ -6738,7 +6743,7 @@ def mcp_remove(
6738
6743
 
6739
6744
  \b
6740
6745
  Backs up config files before modifying. Restores from backup when available,
6741
- otherwise removes the sourcecode entry while preserving all other config.
6746
+ otherwise removes the ASK Engine entry while preserving all other config.
6742
6747
  """
6743
6748
  from sourcecode.mcp.onboarding.detector import detect_clients
6744
6749
  from sourcecode.mcp.onboarding.planner import build_remove_plan
@@ -6750,7 +6755,7 @@ def mcp_remove(
6750
6755
 
6751
6756
  if not installed:
6752
6757
  typer.echo("sourcecode MCP integration not found in any client config.")
6753
- typer.echo(" Setup: sourcecode mcp init")
6758
+ typer.echo(" Setup: ask mcp init")
6754
6759
  raise typer.Exit(code=0)
6755
6760
 
6756
6761
  typer.echo("Remove sourcecode MCP integration from:")
@@ -6793,23 +6798,23 @@ def mcp_remove(
6793
6798
  raise typer.Exit(code=1)
6794
6799
 
6795
6800
  typer.echo("MCP integration removed.")
6796
- typer.echo(" Re-add: sourcecode mcp init")
6801
+ typer.echo(" Re-add: ask mcp init")
6797
6802
 
6798
6803
 
6799
6804
  @mcp_app.command("list-tools")
6800
6805
  def mcp_list_tools(
6801
6806
  json_output: bool = typer.Option(False, "--json", help="Output as JSON."),
6802
6807
  ) -> None:
6803
- """List all MCP tools exposed by the sourcecode server.
6808
+ """List all MCP tools exposed by the ASK Engine MCP server.
6804
6809
 
6805
6810
  \b
6806
6811
  Shows each tool name, its description, and the CLI command it maps to.
6807
- Useful for discovering capabilities when using sourcecode as an MCP server.
6812
+ Useful for discovering capabilities when using ASK Engine as an MCP server.
6808
6813
 
6809
6814
  \b
6810
6815
  Examples:
6811
- sourcecode mcp list-tools
6812
- sourcecode mcp list-tools --json
6816
+ ask mcp list-tools
6817
+ ask mcp list-tools --json
6813
6818
  """
6814
6819
  import asyncio
6815
6820
  import json as _json
@@ -6832,8 +6837,8 @@ def mcp_list_tools(
6832
6837
  desc_first_line = (t.description or "").strip().splitlines()[0] if t.description else ""
6833
6838
  typer.echo(f" {t.name:<35} {desc_first_line}")
6834
6839
  typer.echo("")
6835
- typer.echo("Use: sourcecode mcp serve — start MCP server on stdio")
6836
- typer.echo("Use: sourcecode mcp init — configure MCP client")
6840
+ typer.echo("Use: ask mcp serve — start MCP server on stdio")
6841
+ typer.echo("Use: ask mcp init — configure MCP client")
6837
6842
 
6838
6843
 
6839
6844
  # ── Cache subcommands ─────────────────────────────────────────────────────────
@@ -7034,8 +7039,18 @@ def cache_freshness_cmd(
7034
7039
 
7035
7040
  # ── Entry point ───────────────────────────────────────────────────────────────
7036
7041
 
7042
+ def _stderr_is_interactive() -> bool:
7043
+ """True when stderr is a human terminal — used to gate the alias deprecation
7044
+ line so non-interactive callers keep a clean (JSON-only) stderr."""
7045
+ try:
7046
+ return bool(sys.stderr.isatty())
7047
+ except Exception:
7048
+ return False
7049
+
7050
+
7037
7051
  def main_entry() -> None:
7038
- """CLI entry point.
7052
+ """CLI entry point for both the canonical ``ask`` command and the deprecated
7053
+ ``sourcecode`` compat alias — one implementation, no duplication.
7039
7054
 
7040
7055
  Calls _preprocess_argv() before Typer/Click parses sys.argv so that
7041
7056
  repository path tokens are extracted before Click's Group callback
@@ -7049,9 +7064,23 @@ def main_entry() -> None:
7049
7064
  sys.stdout.reconfigure(encoding="utf-8")
7050
7065
  except Exception:
7051
7066
  pass
7067
+ # Deprecation notice when invoked through the legacy `sourcecode` alias.
7068
+ # One line, and only on an interactive terminal — pipes/agents that still call
7069
+ # `sourcecode` keep clean stderr (error envelopes are JSON on stderr), so the
7070
+ # machine contract is untouched. Humans at a TTY get nudged toward `ask`.
7071
+ try:
7072
+ _invoked = os.path.basename(sys.argv[0] or "").lower()
7073
+ if _invoked.startswith("sourcecode") and _stderr_is_interactive():
7074
+ sys.stderr.write(
7075
+ "⚠ 'sourcecode' is a deprecated alias for 'ask' and will be removed "
7076
+ "in a future major release. Use 'ask' instead.\n"
7077
+ )
7078
+ except Exception:
7079
+ pass
7052
7080
  _preprocess_argv()
7053
7081
  try:
7054
- app()
7082
+ # prog_name pins usage/help to the canonical `ask`, whatever the alias.
7083
+ app(prog_name="ask")
7055
7084
  finally:
7056
7085
  # Best-effort "new version available" nudge. Only speaks on an
7057
7086
  # interactive terminal; never blocks, raises, or affects exit status.