sourcecode 0.27.0__py3-none-any.whl → 0.28.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 +1 -1
- sourcecode/cli.py +203 -24
- {sourcecode-0.27.0.dist-info → sourcecode-0.28.0.dist-info}/METADATA +1 -1
- {sourcecode-0.27.0.dist-info → sourcecode-0.28.0.dist-info}/RECORD +7 -7
- sourcecode-0.28.0.dist-info/entry_points.txt +2 -0
- sourcecode-0.27.0.dist-info/entry_points.txt +0 -2
- {sourcecode-0.27.0.dist-info → sourcecode-0.28.0.dist-info}/WHEEL +0 -0
- {sourcecode-0.27.0.dist-info → sourcecode-0.28.0.dist-info}/licenses/LICENSE +0 -0
sourcecode/__init__.py
CHANGED
sourcecode/cli.py
CHANGED
|
@@ -9,13 +9,131 @@ import typer
|
|
|
9
9
|
|
|
10
10
|
from sourcecode import __version__
|
|
11
11
|
|
|
12
|
+
_HELP = """\
|
|
13
|
+
Deterministic codebase context for AI coding agents.
|
|
14
|
+
|
|
15
|
+
[bold]Usage:[/bold]
|
|
16
|
+
sourcecode [dim]# analyze current directory[/dim]
|
|
17
|
+
sourcecode /path/to/repo [dim]# analyze specific path[/dim]
|
|
18
|
+
sourcecode --agent [dim]# structured output for AI agents[/dim]
|
|
19
|
+
|
|
20
|
+
[bold]Subcommands:[/bold]
|
|
21
|
+
prepare-context TASK [PATH] [dim]# task-specific context[/dim]
|
|
22
|
+
telemetry status|enable|disable
|
|
23
|
+
version
|
|
24
|
+
config
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
# Known subcommand names — tokens matching these are routed as subcommands,
|
|
28
|
+
# not consumed as a repository path.
|
|
29
|
+
_SUBCOMMANDS: frozenset[str] = frozenset(
|
|
30
|
+
{"telemetry", "prepare-context", "version", "config", "analyze"}
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
# Mutable container holding the path extracted by _preprocess_argv().
|
|
34
|
+
# Default "." means "current directory" when no path is given.
|
|
35
|
+
_detected_path: list[str] = ["."]
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
# Options that take a value token — their next arg must not be treated as a path.
|
|
39
|
+
_OPTIONS_WITH_VALUE: frozenset[str] = frozenset({
|
|
40
|
+
"--format", "-f",
|
|
41
|
+
"--output", "-o",
|
|
42
|
+
"--graph-detail",
|
|
43
|
+
"--graph-edges",
|
|
44
|
+
"--max-nodes",
|
|
45
|
+
"--docs-depth",
|
|
46
|
+
"--depth",
|
|
47
|
+
"--git-depth",
|
|
48
|
+
"--git-days",
|
|
49
|
+
"--since",
|
|
50
|
+
"--path", "-p",
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def _preprocess_args(args: list[str]) -> list[str]:
|
|
55
|
+
"""Extract a repository path token from an args list and store it in _detected_path.
|
|
56
|
+
|
|
57
|
+
Returns the modified args list (path token removed).
|
|
58
|
+
Correctly skips option values (e.g. ``yaml`` in ``--format yaml``).
|
|
59
|
+
If the first non-flag, non-value positional token is a known subcommand name,
|
|
60
|
+
args are returned unchanged so Click can dispatch the subcommand.
|
|
61
|
+
"""
|
|
62
|
+
result = list(args)
|
|
63
|
+
skip_next = False
|
|
64
|
+
for i, arg in enumerate(result):
|
|
65
|
+
if skip_next:
|
|
66
|
+
skip_next = False
|
|
67
|
+
continue
|
|
68
|
+
if arg.startswith("-"):
|
|
69
|
+
# Does this option consume the next token as its value?
|
|
70
|
+
flag_name = arg.split("=")[0]
|
|
71
|
+
if flag_name in _OPTIONS_WITH_VALUE and "=" not in arg:
|
|
72
|
+
skip_next = True
|
|
73
|
+
continue
|
|
74
|
+
if arg in _SUBCOMMANDS:
|
|
75
|
+
return result # known subcommand — leave for Click to dispatch
|
|
76
|
+
# First genuine positional: treat as repository path
|
|
77
|
+
_detected_path[0] = arg
|
|
78
|
+
result.pop(i)
|
|
79
|
+
return result
|
|
80
|
+
return result
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def _preprocess_argv() -> None:
|
|
84
|
+
"""Apply _preprocess_args to sys.argv in-place (used by main_entry)."""
|
|
85
|
+
import sys as _sys
|
|
86
|
+
modified = _preprocess_args(_sys.argv[1:])
|
|
87
|
+
_sys.argv = _sys.argv[:1] + modified
|
|
88
|
+
|
|
89
|
+
|
|
12
90
|
app = typer.Typer(
|
|
13
91
|
name="sourcecode",
|
|
14
|
-
help=
|
|
92
|
+
help=_HELP,
|
|
15
93
|
add_completion=False,
|
|
94
|
+
rich_markup_mode="rich",
|
|
95
|
+
no_args_is_help=False,
|
|
16
96
|
)
|
|
17
97
|
|
|
18
|
-
|
|
98
|
+
# ── Hook preprocessing into the Click command layer ───────────────────────────
|
|
99
|
+
# Typer's CliRunner (and app() itself) calls typer.main.get_command(app) to
|
|
100
|
+
# create a Click Group, then calls click_group.main(args=...).
|
|
101
|
+
# We patch get_command so the returned Click Group always preprocesses args —
|
|
102
|
+
# this covers both main_entry() (sys.argv path) and runner.invoke(app, args).
|
|
103
|
+
import typer.main as _typer_main_module # noqa: E402
|
|
104
|
+
|
|
105
|
+
_orig_get_command = _typer_main_module.get_command
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def _get_command_with_preprocessing(typer_instance: Any) -> Any:
|
|
109
|
+
cmd = _orig_get_command(typer_instance)
|
|
110
|
+
if typer_instance is not app:
|
|
111
|
+
return cmd # only wrap the root app, not telemetry_app etc.
|
|
112
|
+
_orig_cmd_main = cmd.main
|
|
113
|
+
|
|
114
|
+
def _cmd_main(args: Optional[list[str]] = None, **kwargs: Any) -> Any:
|
|
115
|
+
if args is not None:
|
|
116
|
+
# CliRunner / programmatic call: preprocess the explicit args list.
|
|
117
|
+
_detected_path[0] = "."
|
|
118
|
+
args = _preprocess_args(list(args))
|
|
119
|
+
# args=None → Click reads sys.argv; _preprocess_argv() in main_entry handled it.
|
|
120
|
+
return _orig_cmd_main(args=args, **kwargs)
|
|
121
|
+
|
|
122
|
+
cmd.main = _cmd_main
|
|
123
|
+
return cmd
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
_typer_main_module.get_command = _get_command_with_preprocessing
|
|
127
|
+
|
|
128
|
+
# typer.testing imports get_command as a private alias _get_command at module
|
|
129
|
+
# load time; patch that reference too so CliRunner.invoke uses our version.
|
|
130
|
+
try:
|
|
131
|
+
import typer.testing as _typer_testing_module
|
|
132
|
+
_typer_testing_module._get_command = _get_command_with_preprocessing # type: ignore[attr-defined]
|
|
133
|
+
except Exception:
|
|
134
|
+
pass
|
|
135
|
+
|
|
136
|
+
telemetry_app = typer.Typer(help="Manage anonymous telemetry (opt-in).", rich_markup_mode="rich")
|
|
19
137
|
app.add_typer(telemetry_app, name="telemetry")
|
|
20
138
|
|
|
21
139
|
|
|
@@ -73,7 +191,6 @@ def version_callback(value: bool) -> None:
|
|
|
73
191
|
@app.callback(invoke_without_command=True)
|
|
74
192
|
def main(
|
|
75
193
|
ctx: typer.Context,
|
|
76
|
-
path: Path = typer.Argument(Path("."), help="Directorio a analizar (default: directorio actual)"),
|
|
77
194
|
format: str = typer.Option(
|
|
78
195
|
"json",
|
|
79
196
|
"--format",
|
|
@@ -211,9 +328,17 @@ def main(
|
|
|
211
328
|
help="Modo agente: output estructurado y sin ruido para consumo por IA. Incluye identidad, entrypoints, arquitectura, dependencias clave, señales operacionales y gaps. Sin arbol de ficheros ni secciones vacias.",
|
|
212
329
|
),
|
|
213
330
|
) -> None:
|
|
214
|
-
"""
|
|
215
|
-
|
|
216
|
-
|
|
331
|
+
"""Analyze a repository and produce structured context for AI coding agents.
|
|
332
|
+
|
|
333
|
+
\b
|
|
334
|
+
Examples:
|
|
335
|
+
sourcecode analyze current directory
|
|
336
|
+
sourcecode /path/to/repo analyze specific path
|
|
337
|
+
sourcecode --agent agent-optimized output
|
|
338
|
+
sourcecode --agent --git-context include git activity signals
|
|
339
|
+
"""
|
|
340
|
+
# First-run consent (skip for telemetry/version/config subcommands)
|
|
341
|
+
if ctx.invoked_subcommand not in ("telemetry", "version", "config"):
|
|
217
342
|
_maybe_ask_consent()
|
|
218
343
|
|
|
219
344
|
# When a subcommand is invoked, skip the main analysis.
|
|
@@ -242,13 +367,13 @@ def main(
|
|
|
242
367
|
)
|
|
243
368
|
raise typer.Exit(code=1)
|
|
244
369
|
|
|
245
|
-
#
|
|
246
|
-
target =
|
|
370
|
+
# Path was extracted from argv by _preprocess_argv() before Click ran.
|
|
371
|
+
target = Path(_detected_path[0]).resolve()
|
|
247
372
|
if not target.exists():
|
|
248
|
-
typer.echo(f"Error:
|
|
373
|
+
typer.echo(f"Error: directory '{target}' does not exist.", err=True)
|
|
249
374
|
raise typer.Exit(code=1)
|
|
250
375
|
if not target.is_dir():
|
|
251
|
-
typer.echo(f"Error: '{target}'
|
|
376
|
+
typer.echo(f"Error: '{target}' is not a directory.", err=True)
|
|
252
377
|
raise typer.Exit(code=1)
|
|
253
378
|
|
|
254
379
|
# --- Importar modulos de logica ---
|
|
@@ -751,15 +876,14 @@ def prepare_context_cmd(
|
|
|
751
876
|
None,
|
|
752
877
|
help="Task: explain | fix-bug | refactor | generate-tests | onboard | review-pr | delta",
|
|
753
878
|
),
|
|
754
|
-
path: Path = typer.
|
|
879
|
+
path: Path = typer.Argument(
|
|
755
880
|
Path("."),
|
|
756
|
-
"
|
|
757
|
-
help="Project directory to analyze (default: current directory)",
|
|
881
|
+
help="Repository path to analyze (default: current directory)",
|
|
758
882
|
),
|
|
759
883
|
since: Optional[str] = typer.Option(
|
|
760
884
|
None,
|
|
761
885
|
"--since",
|
|
762
|
-
help="Git ref for delta task
|
|
886
|
+
help="Git ref for delta task (e.g. HEAD~3, main)",
|
|
763
887
|
),
|
|
764
888
|
llm_prompt: bool = typer.Option(
|
|
765
889
|
False,
|
|
@@ -777,25 +901,26 @@ def prepare_context_cmd(
|
|
|
777
901
|
help="Show what would be analyzed without running it",
|
|
778
902
|
),
|
|
779
903
|
) -> None:
|
|
780
|
-
"""
|
|
904
|
+
"""Task-specific context for AI coding agents.
|
|
781
905
|
|
|
782
906
|
\b
|
|
783
907
|
Tasks:
|
|
784
|
-
explain
|
|
785
|
-
fix-bug Risk-ranked files, suspected areas,
|
|
908
|
+
explain Architecture, entry points, key dependencies
|
|
909
|
+
fix-bug Risk-ranked files, suspected areas, annotations
|
|
786
910
|
refactor Structural issues, improvement opportunities
|
|
787
911
|
generate-tests Untested source files, test gap analysis
|
|
788
|
-
onboard Full project context for
|
|
789
|
-
review-pr
|
|
912
|
+
onboard Full project context for new agents/developers
|
|
913
|
+
review-pr Changed files + architectural impact
|
|
790
914
|
delta Incremental context: git-changed files only
|
|
791
915
|
|
|
792
916
|
\b
|
|
793
917
|
Examples:
|
|
794
|
-
sourcecode
|
|
795
|
-
sourcecode
|
|
796
|
-
sourcecode
|
|
797
|
-
sourcecode
|
|
798
|
-
sourcecode
|
|
918
|
+
sourcecode prepare-context explain
|
|
919
|
+
sourcecode prepare-context explain /path/to/repo
|
|
920
|
+
sourcecode prepare-context fix-bug
|
|
921
|
+
sourcecode prepare-context delta --since main
|
|
922
|
+
sourcecode prepare-context onboard --llm-prompt
|
|
923
|
+
sourcecode prepare-context --task-help
|
|
799
924
|
"""
|
|
800
925
|
from sourcecode.prepare_context import TASKS, TaskContextBuilder
|
|
801
926
|
|
|
@@ -912,3 +1037,57 @@ def telemetry_disable() -> None:
|
|
|
912
1037
|
set_enabled(False)
|
|
913
1038
|
typer.echo("Telemetry disabled. No data will be collected or sent.")
|
|
914
1039
|
typer.echo("Re-enable at any time: sourcecode telemetry enable")
|
|
1040
|
+
|
|
1041
|
+
|
|
1042
|
+
# ── version ───────────────────────────────────────────────────────────────────
|
|
1043
|
+
|
|
1044
|
+
@app.command("version")
|
|
1045
|
+
def version_cmd() -> None:
|
|
1046
|
+
"""Show version and exit."""
|
|
1047
|
+
typer.echo(f"sourcecode {__version__}")
|
|
1048
|
+
|
|
1049
|
+
|
|
1050
|
+
# ── config ────────────────────────────────────────────────────────────────────
|
|
1051
|
+
|
|
1052
|
+
@app.command("config")
|
|
1053
|
+
def config_cmd() -> None:
|
|
1054
|
+
"""Show current configuration."""
|
|
1055
|
+
from sourcecode.telemetry.config import config_file_path, is_enabled
|
|
1056
|
+
typer.echo(f"sourcecode {__version__}")
|
|
1057
|
+
typer.echo(f"Config: {config_file_path()}")
|
|
1058
|
+
typer.echo(f"Telemetry: {'enabled' if is_enabled() else 'disabled'}")
|
|
1059
|
+
typer.echo("")
|
|
1060
|
+
typer.echo("Manage telemetry:")
|
|
1061
|
+
typer.echo(" sourcecode telemetry enable")
|
|
1062
|
+
typer.echo(" sourcecode telemetry disable")
|
|
1063
|
+
typer.echo(" sourcecode telemetry status")
|
|
1064
|
+
|
|
1065
|
+
|
|
1066
|
+
# ── analyze (legacy alias) ────────────────────────────────────────────────────
|
|
1067
|
+
|
|
1068
|
+
@app.command("analyze", hidden=True)
|
|
1069
|
+
def analyze_cmd(
|
|
1070
|
+
path: Path = typer.Argument(Path("."), help="Repository path to analyze"),
|
|
1071
|
+
) -> None:
|
|
1072
|
+
"""[deprecated] Use: sourcecode [PATH]"""
|
|
1073
|
+
typer.echo(
|
|
1074
|
+
"Warning: 'analyze' subcommand is deprecated.\n"
|
|
1075
|
+
"Use: sourcecode .\n"
|
|
1076
|
+
" sourcecode /path/to/repo",
|
|
1077
|
+
err=True,
|
|
1078
|
+
)
|
|
1079
|
+
raise typer.Exit(code=1)
|
|
1080
|
+
|
|
1081
|
+
|
|
1082
|
+
# ── Entry point ───────────────────────────────────────────────────────────────
|
|
1083
|
+
|
|
1084
|
+
def main_entry() -> None:
|
|
1085
|
+
"""CLI entry point.
|
|
1086
|
+
|
|
1087
|
+
Calls _preprocess_argv() before Typer/Click parses sys.argv so that
|
|
1088
|
+
repository path tokens are extracted before Click's Group callback
|
|
1089
|
+
can consume them as positional arguments (which would prevent subcommand
|
|
1090
|
+
routing for tokens like 'version' or 'config').
|
|
1091
|
+
"""
|
|
1092
|
+
_preprocess_argv()
|
|
1093
|
+
app()
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
sourcecode/__init__.py,sha256=
|
|
1
|
+
sourcecode/__init__.py,sha256=AudH3rLCq_czFEvKZLk75Ofj-c3VkR7PkHcP8mAHIiY,100
|
|
2
2
|
sourcecode/architecture_analyzer.py,sha256=h81uQuSlxAorHve7M4jzXLT2PStOaGRMTlEPgW9zvxo,19741
|
|
3
3
|
sourcecode/architecture_summary.py,sha256=qolHmn6MWUIQHzY9WeHcfN41EJkQdnPQ5F_Z8pqQasA,20251
|
|
4
4
|
sourcecode/classifier.py,sha256=Ft_RfYS-KOe0t7vjgUx04OoCJd1-DXK7k9-I0CFDSnU,6934
|
|
5
|
-
sourcecode/cli.py,sha256=
|
|
5
|
+
sourcecode/cli.py,sha256=VwYcYdiXFLcBdaZoRPNToG0KRjdBJ8X_7pnZVegQIGY,42561
|
|
6
6
|
sourcecode/code_notes_analyzer.py,sha256=rRd8bFYV0krjlxxQV0wenwE9K7pVpUQSR7KvSvUQKw4,9226
|
|
7
7
|
sourcecode/confidence_analyzer.py,sha256=gvQ92XCeGsACskSGU-GWTB7OqU1tT0eTgV-A7XA_lGs,8968
|
|
8
8
|
sourcecode/context_summarizer.py,sha256=CiQrfBEzun949bWvmLabWoj2HhPn6Lw62ofqnsy0FlQ,6503
|
|
@@ -51,8 +51,8 @@ sourcecode/telemetry/consent.py,sha256=wLMvGNJeSSyZoNkQXpoUioY6mMv4Qdvuw7S9jAEWn
|
|
|
51
51
|
sourcecode/telemetry/events.py,sha256=oEvvulfsv5GIDWG2174gSS6tNB95w38AIYiYeifGKlE,2294
|
|
52
52
|
sourcecode/telemetry/filters.py,sha256=Asa71oRl7q3Wt_FMwuufIZJFzSYdgRNKS8LHCIyFeYE,4805
|
|
53
53
|
sourcecode/telemetry/transport.py,sha256=KJeIPCPWMdmbCP3ySGs2iUlia34U6vWne2dZsUezesw,1560
|
|
54
|
-
sourcecode-0.
|
|
55
|
-
sourcecode-0.
|
|
56
|
-
sourcecode-0.
|
|
57
|
-
sourcecode-0.
|
|
58
|
-
sourcecode-0.
|
|
54
|
+
sourcecode-0.28.0.dist-info/METADATA,sha256=GZbo1PkdXBdkNOySSJnfZWMtX4x6Z0qL3zJt1HgwtmQ,25020
|
|
55
|
+
sourcecode-0.28.0.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
|
|
56
|
+
sourcecode-0.28.0.dist-info/entry_points.txt,sha256=ex3F9rmbXeyDIoFQHtkEqTsKSaJow8F0LrVu8XfIktQ,57
|
|
57
|
+
sourcecode-0.28.0.dist-info/licenses/LICENSE,sha256=7DdHrU9Z_3e7dSvq4ISijZNjnuHo5NIHNiHDouMQ9JU,10491
|
|
58
|
+
sourcecode-0.28.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|