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/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]
|
|
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:
|
|
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
|
-
|
|
167
|
-
|
|
168
|
-
|
|
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
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
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]→
|
|
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="
|
|
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(" →
|
|
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"
|
|
677
|
+
typer.echo(f"ask {__version__}")
|
|
677
678
|
raise typer.Exit()
|
|
678
679
|
|
|
679
680
|
|
|
680
|
-
# ANSI Shadow block wordmark
|
|
681
|
-
#
|
|
682
|
-
|
|
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
|
-
("
|
|
701
|
-
("
|
|
702
|
-
("
|
|
703
|
-
("
|
|
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:
|
|
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("
|
|
708
|
+
lines.append(" ask --help all commands")
|
|
715
709
|
if tier != "Pro":
|
|
716
|
-
lines.append("
|
|
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
|
-
|
|
738
|
+
ask_w = max(len(s) for s in _WELCOME_ASK)
|
|
745
739
|
|
|
746
740
|
t = Text()
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
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
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
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
|
-
|
|
2781
|
-
|
|
2782
|
-
|
|
2783
|
-
|
|
2784
|
-
|
|
2785
|
-
|
|
2786
|
-
|
|
2787
|
-
|
|
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": "
|
|
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:
|
|
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:
|
|
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:
|
|
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
|
-
|
|
3478
|
-
|
|
3479
|
-
|
|
3480
|
-
|
|
3481
|
-
|
|
3482
|
-
|
|
3483
|
-
|
|
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 '
|
|
3702
|
+
monoliths. Run 'ask license' for details.
|
|
3709
3703
|
|
|
3710
3704
|
\b
|
|
3711
3705
|
Examples:
|
|
3712
|
-
|
|
3713
|
-
|
|
3714
|
-
|
|
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:
|
|
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:
|
|
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:
|
|
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
|
-
|
|
3900
|
-
|
|
3901
|
-
|
|
3902
|
-
|
|
3903
|
-
|
|
3904
|
-
|
|
3905
|
-
|
|
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
|
-
|
|
4485
|
-
|
|
4486
|
-
|
|
4487
|
-
|
|
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
|
-
|
|
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
|
-
|
|
4680
|
-
|
|
4681
|
-
|
|
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
|
-
|
|
4686
|
-
|
|
4687
|
-
|
|
4688
|
-
|
|
4689
|
-
|
|
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
|
-
|
|
4886
|
-
|
|
4887
|
-
|
|
4888
|
-
|
|
4889
|
-
|
|
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
|
-
|
|
5015
|
-
|
|
5016
|
-
|
|
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:
|
|
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
|
-
|
|
5159
|
-
|
|
5160
|
-
|
|
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 &&
|
|
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
|
-
|
|
5295
|
-
|
|
5296
|
-
|
|
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:
|
|
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="
|
|
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
|
-
|
|
5403
|
-
|
|
5404
|
-
|
|
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
|
-
|
|
5409
|
-
|
|
5410
|
-
|
|
5411
|
-
|
|
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
|
-
|
|
5472
|
-
|
|
5473
|
-
|
|
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
|
-
|
|
5478
|
-
|
|
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
|
-
|
|
5532
|
-
|
|
5533
|
-
|
|
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
|
-
|
|
5538
|
-
|
|
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:
|
|
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
|
-
|
|
5701
|
-
|
|
5705
|
+
ask modernize .
|
|
5706
|
+
ask modernize /path/to/repo --output modernize.json
|
|
5702
5707
|
|
|
5703
5708
|
\b
|
|
5704
5709
|
Related workflows:
|
|
5705
|
-
|
|
5706
|
-
|
|
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 `
|
|
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
|
-
|
|
6065
|
-
|
|
6066
|
-
|
|
6067
|
-
|
|
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
|
-
|
|
6171
|
-
|
|
6172
|
-
|
|
6173
|
-
|
|
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
|
-
|
|
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"
|
|
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"
|
|
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("
|
|
6325
|
-
typer.echo("
|
|
6326
|
-
typer.echo("
|
|
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": "
|
|
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
|
|
6466
|
+
inserts the ASK Engine server entry. Fully idempotent — safe to re-run.
|
|
6462
6467
|
|
|
6463
6468
|
\b
|
|
6464
6469
|
Examples:
|
|
6465
|
-
|
|
6466
|
-
|
|
6467
|
-
|
|
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:
|
|
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
|
|
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:
|
|
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:
|
|
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 (
|
|
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:
|
|
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 (
|
|
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
|
|
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:
|
|
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:
|
|
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
|
|
6690
|
-
typer.echo(f" Fix:
|
|
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
|
|
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
|
|
6724
|
-
typer.echo(" Run:
|
|
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:
|
|
6730
|
-
typer.echo(" 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
|
|
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:
|
|
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:
|
|
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
|
|
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
|
|
6812
|
+
Useful for discovering capabilities when using ASK Engine as an MCP server.
|
|
6808
6813
|
|
|
6809
6814
|
\b
|
|
6810
6815
|
Examples:
|
|
6811
|
-
|
|
6812
|
-
|
|
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:
|
|
6836
|
-
typer.echo("Use:
|
|
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
|
-
|
|
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.
|