sourcecode 0.27.0__tar.gz → 0.28.0__tar.gz

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.
Files changed (132) hide show
  1. {sourcecode-0.27.0 → sourcecode-0.28.0}/PKG-INFO +1 -1
  2. {sourcecode-0.27.0 → sourcecode-0.28.0}/pyproject.toml +2 -2
  3. {sourcecode-0.27.0 → sourcecode-0.28.0}/src/sourcecode/__init__.py +1 -1
  4. {sourcecode-0.27.0 → sourcecode-0.28.0}/src/sourcecode/cli.py +203 -24
  5. sourcecode-0.28.0/tests/test_cli.py +93 -0
  6. {sourcecode-0.27.0 → sourcecode-0.28.0}/tests/test_packaging.py +1 -1
  7. sourcecode-0.27.0/tests/test_cli.py +0 -58
  8. {sourcecode-0.27.0 → sourcecode-0.28.0}/.gitignore +0 -0
  9. {sourcecode-0.27.0 → sourcecode-0.28.0}/.ruff.toml +0 -0
  10. {sourcecode-0.27.0 → sourcecode-0.28.0}/CONTRIBUTING.md +0 -0
  11. {sourcecode-0.27.0 → sourcecode-0.28.0}/LICENSE +0 -0
  12. {sourcecode-0.27.0 → sourcecode-0.28.0}/README.md +0 -0
  13. {sourcecode-0.27.0 → sourcecode-0.28.0}/SECURITY.md +0 -0
  14. {sourcecode-0.27.0 → sourcecode-0.28.0}/docs/privacy.md +0 -0
  15. {sourcecode-0.27.0 → sourcecode-0.28.0}/docs/schema.md +0 -0
  16. {sourcecode-0.27.0 → sourcecode-0.28.0}/raw +0 -0
  17. {sourcecode-0.27.0 → sourcecode-0.28.0}/src/sourcecode/architecture_analyzer.py +0 -0
  18. {sourcecode-0.27.0 → sourcecode-0.28.0}/src/sourcecode/architecture_summary.py +0 -0
  19. {sourcecode-0.27.0 → sourcecode-0.28.0}/src/sourcecode/classifier.py +0 -0
  20. {sourcecode-0.27.0 → sourcecode-0.28.0}/src/sourcecode/code_notes_analyzer.py +0 -0
  21. {sourcecode-0.27.0 → sourcecode-0.28.0}/src/sourcecode/confidence_analyzer.py +0 -0
  22. {sourcecode-0.27.0 → sourcecode-0.28.0}/src/sourcecode/context_summarizer.py +0 -0
  23. {sourcecode-0.27.0 → sourcecode-0.28.0}/src/sourcecode/coverage_parser.py +0 -0
  24. {sourcecode-0.27.0 → sourcecode-0.28.0}/src/sourcecode/dependency_analyzer.py +0 -0
  25. {sourcecode-0.27.0 → sourcecode-0.28.0}/src/sourcecode/detectors/__init__.py +0 -0
  26. {sourcecode-0.27.0 → sourcecode-0.28.0}/src/sourcecode/detectors/base.py +0 -0
  27. {sourcecode-0.27.0 → sourcecode-0.28.0}/src/sourcecode/detectors/csproj_parser.py +0 -0
  28. {sourcecode-0.27.0 → sourcecode-0.28.0}/src/sourcecode/detectors/dart.py +0 -0
  29. {sourcecode-0.27.0 → sourcecode-0.28.0}/src/sourcecode/detectors/dotnet.py +0 -0
  30. {sourcecode-0.27.0 → sourcecode-0.28.0}/src/sourcecode/detectors/elixir.py +0 -0
  31. {sourcecode-0.27.0 → sourcecode-0.28.0}/src/sourcecode/detectors/go.py +0 -0
  32. {sourcecode-0.27.0 → sourcecode-0.28.0}/src/sourcecode/detectors/heuristic.py +0 -0
  33. {sourcecode-0.27.0 → sourcecode-0.28.0}/src/sourcecode/detectors/hybrid.py +0 -0
  34. {sourcecode-0.27.0 → sourcecode-0.28.0}/src/sourcecode/detectors/java.py +0 -0
  35. {sourcecode-0.27.0 → sourcecode-0.28.0}/src/sourcecode/detectors/jvm_ext.py +0 -0
  36. {sourcecode-0.27.0 → sourcecode-0.28.0}/src/sourcecode/detectors/nodejs.py +0 -0
  37. {sourcecode-0.27.0 → sourcecode-0.28.0}/src/sourcecode/detectors/parsers.py +0 -0
  38. {sourcecode-0.27.0 → sourcecode-0.28.0}/src/sourcecode/detectors/php.py +0 -0
  39. {sourcecode-0.27.0 → sourcecode-0.28.0}/src/sourcecode/detectors/project.py +0 -0
  40. {sourcecode-0.27.0 → sourcecode-0.28.0}/src/sourcecode/detectors/python.py +0 -0
  41. {sourcecode-0.27.0 → sourcecode-0.28.0}/src/sourcecode/detectors/ruby.py +0 -0
  42. {sourcecode-0.27.0 → sourcecode-0.28.0}/src/sourcecode/detectors/rust.py +0 -0
  43. {sourcecode-0.27.0 → sourcecode-0.28.0}/src/sourcecode/detectors/systems.py +0 -0
  44. {sourcecode-0.27.0 → sourcecode-0.28.0}/src/sourcecode/detectors/terraform.py +0 -0
  45. {sourcecode-0.27.0 → sourcecode-0.28.0}/src/sourcecode/detectors/tooling.py +0 -0
  46. {sourcecode-0.27.0 → sourcecode-0.28.0}/src/sourcecode/doc_analyzer.py +0 -0
  47. {sourcecode-0.27.0 → sourcecode-0.28.0}/src/sourcecode/env_analyzer.py +0 -0
  48. {sourcecode-0.27.0 → sourcecode-0.28.0}/src/sourcecode/git_analyzer.py +0 -0
  49. {sourcecode-0.27.0 → sourcecode-0.28.0}/src/sourcecode/graph_analyzer.py +0 -0
  50. {sourcecode-0.27.0 → sourcecode-0.28.0}/src/sourcecode/metrics_analyzer.py +0 -0
  51. {sourcecode-0.27.0 → sourcecode-0.28.0}/src/sourcecode/prepare_context.py +0 -0
  52. {sourcecode-0.27.0 → sourcecode-0.28.0}/src/sourcecode/redactor.py +0 -0
  53. {sourcecode-0.27.0 → sourcecode-0.28.0}/src/sourcecode/relevance_scorer.py +0 -0
  54. {sourcecode-0.27.0 → sourcecode-0.28.0}/src/sourcecode/runtime_classifier.py +0 -0
  55. {sourcecode-0.27.0 → sourcecode-0.28.0}/src/sourcecode/scanner.py +0 -0
  56. {sourcecode-0.27.0 → sourcecode-0.28.0}/src/sourcecode/schema.py +0 -0
  57. {sourcecode-0.27.0 → sourcecode-0.28.0}/src/sourcecode/semantic_analyzer.py +0 -0
  58. {sourcecode-0.27.0 → sourcecode-0.28.0}/src/sourcecode/serializer.py +0 -0
  59. {sourcecode-0.27.0 → sourcecode-0.28.0}/src/sourcecode/summarizer.py +0 -0
  60. {sourcecode-0.27.0 → sourcecode-0.28.0}/src/sourcecode/telemetry/__init__.py +0 -0
  61. {sourcecode-0.27.0 → sourcecode-0.28.0}/src/sourcecode/telemetry/config.py +0 -0
  62. {sourcecode-0.27.0 → sourcecode-0.28.0}/src/sourcecode/telemetry/consent.py +0 -0
  63. {sourcecode-0.27.0 → sourcecode-0.28.0}/src/sourcecode/telemetry/events.py +0 -0
  64. {sourcecode-0.27.0 → sourcecode-0.28.0}/src/sourcecode/telemetry/filters.py +0 -0
  65. {sourcecode-0.27.0 → sourcecode-0.28.0}/src/sourcecode/telemetry/transport.py +0 -0
  66. {sourcecode-0.27.0 → sourcecode-0.28.0}/src/sourcecode/tree_utils.py +0 -0
  67. {sourcecode-0.27.0 → sourcecode-0.28.0}/src/sourcecode/workspace.py +0 -0
  68. {sourcecode-0.27.0 → sourcecode-0.28.0}/tests/__init__.py +0 -0
  69. {sourcecode-0.27.0 → sourcecode-0.28.0}/tests/conftest.py +0 -0
  70. {sourcecode-0.27.0 → sourcecode-0.28.0}/tests/fixtures/coverage.xml +0 -0
  71. {sourcecode-0.27.0 → sourcecode-0.28.0}/tests/fixtures/fastapi_app/pyproject.toml +0 -0
  72. {sourcecode-0.27.0 → sourcecode-0.28.0}/tests/fixtures/fastapi_app/src/main.py +0 -0
  73. {sourcecode-0.27.0 → sourcecode-0.28.0}/tests/fixtures/go_service/cmd/api/main.go +0 -0
  74. {sourcecode-0.27.0 → sourcecode-0.28.0}/tests/fixtures/go_service/go.mod +0 -0
  75. {sourcecode-0.27.0 → sourcecode-0.28.0}/tests/fixtures/jacoco.xml +0 -0
  76. {sourcecode-0.27.0 → sourcecode-0.28.0}/tests/fixtures/lcov.info +0 -0
  77. {sourcecode-0.27.0 → sourcecode-0.28.0}/tests/fixtures/nextjs_app/app/page.tsx +0 -0
  78. {sourcecode-0.27.0 → sourcecode-0.28.0}/tests/fixtures/nextjs_app/package.json +0 -0
  79. {sourcecode-0.27.0 → sourcecode-0.28.0}/tests/fixtures/nextjs_app/pnpm-lock.yaml +0 -0
  80. {sourcecode-0.27.0 → sourcecode-0.28.0}/tests/fixtures/pnpm_monorepo/apps/web/app/page.tsx +0 -0
  81. {sourcecode-0.27.0 → sourcecode-0.28.0}/tests/fixtures/pnpm_monorepo/apps/web/package.json +0 -0
  82. {sourcecode-0.27.0 → sourcecode-0.28.0}/tests/fixtures/pnpm_monorepo/packages/api/main.py +0 -0
  83. {sourcecode-0.27.0 → sourcecode-0.28.0}/tests/fixtures/pnpm_monorepo/packages/api/pyproject.toml +0 -0
  84. {sourcecode-0.27.0 → sourcecode-0.28.0}/tests/fixtures/pnpm_monorepo/pnpm-workspace.yaml +0 -0
  85. {sourcecode-0.27.0 → sourcecode-0.28.0}/tests/test_architecture_analyzer.py +0 -0
  86. {sourcecode-0.27.0 → sourcecode-0.28.0}/tests/test_architecture_summary.py +0 -0
  87. {sourcecode-0.27.0 → sourcecode-0.28.0}/tests/test_classifier.py +0 -0
  88. {sourcecode-0.27.0 → sourcecode-0.28.0}/tests/test_code_notes_analyzer.py +0 -0
  89. {sourcecode-0.27.0 → sourcecode-0.28.0}/tests/test_coverage_parser.py +0 -0
  90. {sourcecode-0.27.0 → sourcecode-0.28.0}/tests/test_cross_consistency.py +0 -0
  91. {sourcecode-0.27.0 → sourcecode-0.28.0}/tests/test_dependency_analyzer_node_python.py +0 -0
  92. {sourcecode-0.27.0 → sourcecode-0.28.0}/tests/test_dependency_analyzer_polyglot.py +0 -0
  93. {sourcecode-0.27.0 → sourcecode-0.28.0}/tests/test_dependency_schema.py +0 -0
  94. {sourcecode-0.27.0 → sourcecode-0.28.0}/tests/test_detector_dotnet.py +0 -0
  95. {sourcecode-0.27.0 → sourcecode-0.28.0}/tests/test_detector_go_rust_java.py +0 -0
  96. {sourcecode-0.27.0 → sourcecode-0.28.0}/tests/test_detector_nodejs.py +0 -0
  97. {sourcecode-0.27.0 → sourcecode-0.28.0}/tests/test_detector_php_ruby_dart.py +0 -0
  98. {sourcecode-0.27.0 → sourcecode-0.28.0}/tests/test_detector_python.py +0 -0
  99. {sourcecode-0.27.0 → sourcecode-0.28.0}/tests/test_detector_universal_managed.py +0 -0
  100. {sourcecode-0.27.0 → sourcecode-0.28.0}/tests/test_detector_universal_systems.py +0 -0
  101. {sourcecode-0.27.0 → sourcecode-0.28.0}/tests/test_detectors_base.py +0 -0
  102. {sourcecode-0.27.0 → sourcecode-0.28.0}/tests/test_doc_analyzer_jsdom.py +0 -0
  103. {sourcecode-0.27.0 → sourcecode-0.28.0}/tests/test_doc_analyzer_python.py +0 -0
  104. {sourcecode-0.27.0 → sourcecode-0.28.0}/tests/test_graph_analyzer_polyglot.py +0 -0
  105. {sourcecode-0.27.0 → sourcecode-0.28.0}/tests/test_graph_analyzer_python_node.py +0 -0
  106. {sourcecode-0.27.0 → sourcecode-0.28.0}/tests/test_graph_schema.py +0 -0
  107. {sourcecode-0.27.0 → sourcecode-0.28.0}/tests/test_hybrid_inference.py +0 -0
  108. {sourcecode-0.27.0 → sourcecode-0.28.0}/tests/test_integration.py +0 -0
  109. {sourcecode-0.27.0 → sourcecode-0.28.0}/tests/test_integration_dependencies.py +0 -0
  110. {sourcecode-0.27.0 → sourcecode-0.28.0}/tests/test_integration_detection.py +0 -0
  111. {sourcecode-0.27.0 → sourcecode-0.28.0}/tests/test_integration_docs.py +0 -0
  112. {sourcecode-0.27.0 → sourcecode-0.28.0}/tests/test_integration_graph_modules.py +0 -0
  113. {sourcecode-0.27.0 → sourcecode-0.28.0}/tests/test_integration_lqn.py +0 -0
  114. {sourcecode-0.27.0 → sourcecode-0.28.0}/tests/test_integration_metrics.py +0 -0
  115. {sourcecode-0.27.0 → sourcecode-0.28.0}/tests/test_integration_multistack.py +0 -0
  116. {sourcecode-0.27.0 → sourcecode-0.28.0}/tests/test_integration_semantics.py +0 -0
  117. {sourcecode-0.27.0 → sourcecode-0.28.0}/tests/test_integration_universal.py +0 -0
  118. {sourcecode-0.27.0 → sourcecode-0.28.0}/tests/test_metrics_analyzer.py +0 -0
  119. {sourcecode-0.27.0 → sourcecode-0.28.0}/tests/test_phase1_improvements.py +0 -0
  120. {sourcecode-0.27.0 → sourcecode-0.28.0}/tests/test_real_projects.py +0 -0
  121. {sourcecode-0.27.0 → sourcecode-0.28.0}/tests/test_redactor.py +0 -0
  122. {sourcecode-0.27.0 → sourcecode-0.28.0}/tests/test_scanner.py +0 -0
  123. {sourcecode-0.27.0 → sourcecode-0.28.0}/tests/test_schema.py +0 -0
  124. {sourcecode-0.27.0 → sourcecode-0.28.0}/tests/test_schema_normalization.py +0 -0
  125. {sourcecode-0.27.0 → sourcecode-0.28.0}/tests/test_semantic_analyzer_node.py +0 -0
  126. {sourcecode-0.27.0 → sourcecode-0.28.0}/tests/test_semantic_analyzer_python.py +0 -0
  127. {sourcecode-0.27.0 → sourcecode-0.28.0}/tests/test_semantic_import_resolution.py +0 -0
  128. {sourcecode-0.27.0 → sourcecode-0.28.0}/tests/test_semantic_schema.py +0 -0
  129. {sourcecode-0.27.0 → sourcecode-0.28.0}/tests/test_signal_hierarchy.py +0 -0
  130. {sourcecode-0.27.0 → sourcecode-0.28.0}/tests/test_summarizer.py +0 -0
  131. {sourcecode-0.27.0 → sourcecode-0.28.0}/tests/test_telemetry.py +0 -0
  132. {sourcecode-0.27.0 → sourcecode-0.28.0}/tests/test_workspace_analyzer.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sourcecode
3
- Version: 0.27.0
3
+ Version: 0.28.0
4
4
  Summary: Deterministic codebase context for AI coding agents
5
5
  License: Apache License
6
6
  Version 2.0, January 2004
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "sourcecode"
7
- version = "0.27.0"
7
+ version = "0.28.0"
8
8
  description = "Deterministic codebase context for AI coding agents"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.9"
@@ -32,7 +32,7 @@ dependencies = [
32
32
  ]
33
33
 
34
34
  [project.scripts]
35
- sourcecode = "sourcecode.cli:app"
35
+ sourcecode = "sourcecode.cli:main_entry"
36
36
 
37
37
  [project.optional-dependencies]
38
38
  dev = [
@@ -1,3 +1,3 @@
1
1
  """sourcecode — Genera mapas de contexto estructurado para agentes IA."""
2
2
 
3
- __version__ = "0.27.0"
3
+ __version__ = "0.28.0"
@@ -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="Deterministic codebase context for AI coding agents.",
92
+ help=_HELP,
15
93
  add_completion=False,
94
+ rich_markup_mode="rich",
95
+ no_args_is_help=False,
16
96
  )
17
97
 
18
- telemetry_app = typer.Typer(help="Manage anonymous telemetry (opt-in).")
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
- """Generate structured codebase context for AI coding agents."""
215
- # First-run consent (skip for telemetry subcommand itself)
216
- if ctx.invoked_subcommand != "telemetry":
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
- # Resolver y validar path
246
- target = path.resolve()
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: el directorio '{target}' no existe.", err=True)
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}' no es un directorio.", err=True)
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.Option(
879
+ path: Path = typer.Argument(
755
880
  Path("."),
756
- "--path", "-p",
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: show files changed since this ref (e.g. HEAD~3, main)",
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
- """Compile task-aware context for AI coding agents.
904
+ """Task-specific context for AI coding agents.
781
905
 
782
906
  \b
783
907
  Tasks:
784
- explain Project overview: structure, entry points, dependencies
785
- fix-bug Risk-ranked files, suspected areas, code annotations
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 a new agent or developer
789
- review-pr PR review context: changed files + architecture
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 . prepare-context explain
795
- sourcecode . prepare-context fix-bug --path /my/project
796
- sourcecode . prepare-context delta --since main
797
- sourcecode . prepare-context onboard --llm-prompt
798
- sourcecode . prepare-context --task-help
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()
@@ -0,0 +1,93 @@
1
+ """Integration tests for the CLI."""
2
+ from pathlib import Path
3
+
4
+ import tomllib
5
+ from typer.testing import CliRunner
6
+
7
+ from sourcecode.cli import _detected_path, _preprocess_args, app
8
+
9
+ _runner = CliRunner()
10
+ PROJECT_VERSION = tomllib.loads(Path("pyproject.toml").read_text(encoding="utf-8"))["project"]["version"]
11
+
12
+
13
+ def invoke(args: list[str]):
14
+ """Invoke the CLI with argv preprocessing (mirrors main_entry behaviour)."""
15
+ _detected_path[0] = "."
16
+ processed = _preprocess_args(list(args))
17
+ return _runner.invoke(app, processed)
18
+
19
+
20
+ def test_version():
21
+ result = invoke(["--version"])
22
+ assert result.exit_code == 0
23
+ assert "sourcecode" in result.output
24
+ assert PROJECT_VERSION in result.output
25
+
26
+
27
+ def test_version_subcommand():
28
+ result = invoke(["version"])
29
+ assert result.exit_code == 0
30
+ assert PROJECT_VERSION in result.output
31
+
32
+
33
+ def test_config_subcommand():
34
+ result = invoke(["config"])
35
+ assert result.exit_code == 0
36
+ assert "sourcecode" in result.output
37
+
38
+
39
+ def test_telemetry_status():
40
+ result = invoke(["telemetry", "status"])
41
+ assert result.exit_code == 0
42
+ assert "Telemetry" in result.output
43
+
44
+
45
+ def test_help_contains_all_flags():
46
+ result = invoke(["--help"])
47
+ assert result.exit_code == 0
48
+ assert "--format" in result.output
49
+ assert "--output" in result.output
50
+ assert "--compact" in result.output
51
+ assert "--dependencies" in result.output
52
+ assert "--graph-modules" in result.output
53
+ assert "--graph-detail" in result.output
54
+ assert "--max-nodes" in result.output
55
+ assert "--graph-edges" in result.output
56
+ assert "--no-redact" in result.output
57
+
58
+
59
+ def test_no_args_runs_without_exception(tmp_project: Path):
60
+ result = invoke([str(tmp_project)])
61
+ assert result.exit_code == 0
62
+
63
+
64
+ def test_path_before_flag(tmp_project: Path):
65
+ result = invoke([str(tmp_project), "--compact"])
66
+ assert result.exit_code == 0
67
+
68
+
69
+ def test_flag_before_path(tmp_project: Path):
70
+ result = invoke(["--compact", str(tmp_project)])
71
+ assert result.exit_code == 0
72
+
73
+
74
+ def test_format_yaml(tmp_project: Path):
75
+ result = invoke(["--format", "yaml", str(tmp_project)])
76
+ assert result.exit_code == 0
77
+
78
+
79
+ def test_format_invalid(tmp_project: Path):
80
+ result = invoke(["--format", "xml", str(tmp_project)])
81
+ assert result.exit_code != 0
82
+
83
+
84
+ def test_output_file(tmp_project: Path, tmp_path: Path):
85
+ out = tmp_path / "out.json"
86
+ result = invoke(["--output", str(out), str(tmp_project)])
87
+ assert result.exit_code == 0
88
+ assert out.exists()
89
+
90
+
91
+ def test_compact(tmp_project: Path):
92
+ result = invoke(["--compact", str(tmp_project)])
93
+ assert result.exit_code == 0
@@ -41,7 +41,7 @@ def test_pyproject_declares_packaging_contract() -> None:
41
41
  assert data["build-system"]["build-backend"] == "hatchling.build"
42
42
  assert "hatchling" in data["build-system"]["requires"]
43
43
  assert data["project"]["name"] == "sourcecode"
44
- assert data["project"]["scripts"]["sourcecode"] == "sourcecode.cli:app"
44
+ assert data["project"]["scripts"]["sourcecode"] == "sourcecode.cli:main_entry"
45
45
  assert data["tool"]["hatch"]["build"]["targets"]["wheel"]["packages"] == [
46
46
  "src/sourcecode"
47
47
  ]
@@ -1,58 +0,0 @@
1
- """Tests de integracion de la CLI."""
2
- from pathlib import Path
3
-
4
- import tomllib
5
- from typer.testing import CliRunner
6
-
7
- from sourcecode.cli import app
8
-
9
- runner = CliRunner()
10
- PROJECT_VERSION = tomllib.loads(Path("pyproject.toml").read_text(encoding="utf-8"))["project"]["version"]
11
-
12
-
13
- def test_version():
14
- result = runner.invoke(app, ["--version"])
15
- assert result.exit_code == 0
16
- assert "sourcecode" in result.output
17
- assert PROJECT_VERSION in result.output
18
-
19
-
20
- def test_help_contains_all_flags():
21
- result = runner.invoke(app, ["--help"])
22
- assert result.exit_code == 0
23
- assert "--format" in result.output
24
- assert "--output" in result.output
25
- assert "--compact" in result.output
26
- assert "--dependencies" in result.output
27
- assert "--graph-modules" in result.output
28
- assert "--graph-detail" in result.output
29
- assert "--max-nodes" in result.output
30
- assert "--graph-edges" in result.output
31
- assert "--no-redact" in result.output
32
-
33
-
34
- def test_no_args_runs_without_exception(tmp_project: Path):
35
- result = runner.invoke(app, [str(tmp_project)])
36
- assert result.exit_code == 0
37
-
38
-
39
- def test_format_yaml(tmp_project: Path):
40
- result = runner.invoke(app, ["--format", "yaml", str(tmp_project)])
41
- assert result.exit_code == 0
42
-
43
-
44
- def test_format_invalid(tmp_project: Path):
45
- result = runner.invoke(app, ["--format", "xml", str(tmp_project)])
46
- assert result.exit_code != 0
47
-
48
-
49
- def test_output_file(tmp_project: Path, tmp_path: Path):
50
- out = tmp_path / "out.json"
51
- result = runner.invoke(app, ["--output", str(out), str(tmp_project)])
52
- assert result.exit_code == 0
53
- assert out.exists()
54
-
55
-
56
- def test_compact(tmp_project: Path):
57
- result = runner.invoke(app, ["--compact", str(tmp_project)])
58
- assert result.exit_code == 0
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes