sourcecode 0.10.0__py3-none-any.whl → 0.12.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 CHANGED
@@ -1,3 +1,3 @@
1
1
  """sourcecode — Genera mapas de contexto estructurado para agentes IA."""
2
2
 
3
- __version__ = "0.10.0"
3
+ __version__ = "0.12.0"
sourcecode/cli.py CHANGED
@@ -1,4 +1,5 @@
1
1
  """CLI de sourcecode — interfaz de linea de comandos principal."""
2
+ from sourcecode.prepare_context import prepare_context as build_llm_context
2
3
  from __future__ import annotations
3
4
 
4
5
  import json
@@ -473,10 +474,10 @@ def main(
473
474
  semantic_summary=sem_sum,
474
475
  )
475
476
 
476
- # Phase 9: LLM Output Quality — poblar campos derivados
477
- from sourcecode.architecture_summary import ArchitectureSummarizer
478
- from sourcecode.summarizer import ProjectSummarizer
479
- from sourcecode.tree_utils import flatten_file_tree
477
+ # Phase 9: LLM Output Quality — poblar campos derivados
478
+ from sourcecode.architecture_summary import ArchitectureSummarizer
479
+ from sourcecode.summarizer import ProjectSummarizer
480
+ from sourcecode.tree_utils import flatten_file_tree
480
481
 
481
482
  # LQN-01: lista plana de paths del file_tree con separador forward-slash
482
483
  sm.file_paths = [
@@ -496,9 +497,9 @@ def main(
496
497
 
497
498
  sm.key_dependencies = sorted(direct_deps, key=_dep_sort_key)[:15]
498
499
 
499
- # LQN-02: resumen NL deterministico
500
- sm.project_summary = ProjectSummarizer(target).generate(sm)
501
- sm.architecture_summary = ArchitectureSummarizer(target).generate(sm)
500
+ # LQN-02: resumen NL deterministico
501
+ sm.project_summary = ProjectSummarizer(target).generate(sm)
502
+ sm.architecture_summary = ArchitectureSummarizer(target).generate(sm)
502
503
 
503
504
  # 4. Serializar (con o sin modo compact)
504
505
  if compact:
@@ -535,3 +536,34 @@ def main(
535
536
 
536
537
  # 5. Escribir output (CLI-04)
537
538
  write_output(content, output=output)
539
+
540
+ from sourcecode.prepare_context import ContextBuilder
541
+
542
+ @app.command("prepare-context")
543
+ def prepare_context_cmd(
544
+ task: str = typer.Argument(..., help="Descripción de la tarea"),
545
+ path: Path = typer.Option(".", help="Directorio del proyecto"),
546
+ ):
547
+ """
548
+ Prepara contexto mínimo optimizado para que un LLM modifique el código.
549
+ """
550
+ target = path.resolve()
551
+
552
+ if not target.exists() or not target.is_dir():
553
+ typer.echo(f"Error: '{target}' no es un directorio válido.", err=True)
554
+ raise typer.Exit(code=1)
555
+
556
+ builder = ContextBuilder(target)
557
+ result = builder.prepare(task)
558
+
559
+ output = {
560
+ "task": result.task,
561
+ "entry_points": result.entry_points,
562
+ "relevant_files": [f.__dict__ for f in result.relevant_files],
563
+ "call_flow": result.call_flow,
564
+ "snippets": [s.__dict__ for s in result.snippets],
565
+ "tests": result.tests,
566
+ "notes": result.notes,
567
+ }
568
+
569
+ typer.echo(json.dumps(output, indent=2, ensure_ascii=False))
@@ -0,0 +1,197 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass
4
+ from pathlib import Path
5
+ from typing import Any, Dict, List, Optional
6
+
7
+ from sourcecode.semantic_analyzer import SemanticAnalyzer
8
+
9
+
10
+ # =========================
11
+ # Output schema (simple)
12
+ # =========================
13
+
14
+ @dataclass
15
+ class ContextSnippet:
16
+ file: str
17
+ symbol: str
18
+ code: str
19
+ reason: str
20
+
21
+
22
+ @dataclass
23
+ class ContextFile:
24
+ path: str
25
+ role: str
26
+
27
+
28
+ @dataclass
29
+ class ContextResult:
30
+ task: str
31
+ entry_points: List[Dict[str, str]]
32
+ relevant_files: List[ContextFile]
33
+ call_flow: List[Dict[str, Any]]
34
+ snippets: List[ContextSnippet]
35
+ tests: List[Dict[str, str]]
36
+ notes: List[str]
37
+
38
+
39
+ # =========================
40
+ # Core Builder
41
+ # =========================
42
+
43
+ class ContextBuilder:
44
+ def __init__(self, root: Path) -> None:
45
+ self.root = root
46
+ self.analyzer = SemanticAnalyzer(root)
47
+
48
+ # -------------------------
49
+ # Public API
50
+ # -------------------------
51
+
52
+ def prepare(self, task: str) -> ContextResult:
53
+ """
54
+ Entry point principal.
55
+ """
56
+ semantic = self.analyzer.analyze()
57
+
58
+ entry_points = self._detect_entrypoints(semantic)
59
+ relevant_files = self._select_relevant_files(task, entry_points)
60
+ snippets = self._extract_snippets(relevant_files)
61
+ tests = self._find_related_tests(relevant_files)
62
+ call_flow = self._build_call_flow(entry_points)
63
+
64
+ notes = self._generate_notes(task)
65
+
66
+ return ContextResult(
67
+ task=task,
68
+ entry_points=entry_points,
69
+ relevant_files=relevant_files,
70
+ call_flow=call_flow,
71
+ snippets=snippets,
72
+ tests=tests,
73
+ notes=notes,
74
+ )
75
+
76
+ # -------------------------
77
+ # Heurísticas básicas
78
+ # -------------------------
79
+
80
+ def _detect_entrypoints(self, semantic: Any) -> List[Dict[str, str]]:
81
+ """
82
+ MVP: detecta cli.py o main functions
83
+ """
84
+ entrypoints: List[Dict[str, str]] = []
85
+
86
+ for path in semantic.file_paths:
87
+ if path.endswith("cli.py") or path.endswith("__main__.py"):
88
+ entrypoints.append({
89
+ "file": path,
90
+ "symbol": "main",
91
+ "reason": "CLI entrypoint"
92
+ })
93
+
94
+ return entrypoints
95
+
96
+ def _select_relevant_files(
97
+ self,
98
+ task: str,
99
+ entry_points: List[Dict[str, str]],
100
+ ) -> List[ContextFile]:
101
+ """
102
+ Heurística simple:
103
+ - incluir entrypoints
104
+ - incluir archivos con palabras clave del task
105
+ """
106
+ files: List[ContextFile] = []
107
+
108
+ for ep in entry_points:
109
+ files.append(ContextFile(path=ep["file"], role="entrypoint"))
110
+
111
+ keywords = task.lower().split()
112
+
113
+ for path in self.analyzer.scan.file_paths: # fallback simple
114
+ if any(k in path.lower() for k in keywords):
115
+ files.append(ContextFile(path=path, role="matched"))
116
+
117
+ # deduplicar
118
+ seen = set()
119
+ unique = []
120
+ for f in files:
121
+ if f.path not in seen:
122
+ seen.add(f.path)
123
+ unique.append(f)
124
+
125
+ return unique[:8]
126
+
127
+ def _extract_snippets(self, files: List[ContextFile]) -> List[ContextSnippet]:
128
+ """
129
+ Extrae primeras ~30 líneas como snippet simple.
130
+ """
131
+ snippets: List[ContextSnippet] = []
132
+
133
+ for f in files:
134
+ abs_path = self.root / f.path
135
+ if not abs_path.exists():
136
+ continue
137
+
138
+ try:
139
+ content = abs_path.read_text(encoding="utf-8", errors="ignore")
140
+ except Exception:
141
+ continue
142
+
143
+ snippet = "\n".join(content.splitlines()[:30])
144
+
145
+ snippets.append(ContextSnippet(
146
+ file=f.path,
147
+ symbol="module",
148
+ code=snippet,
149
+ reason=f"contenido inicial de {f.role}"
150
+ ))
151
+
152
+ return snippets[:12]
153
+
154
+ def _find_related_tests(self, files: List[ContextFile]) -> List[Dict[str, str]]:
155
+ """
156
+ Busca tests por naming convention.
157
+ """
158
+ tests: List[Dict[str, str]] = []
159
+
160
+ for f in files:
161
+ name = Path(f.path).name
162
+ test_name = f"test_{name}"
163
+
164
+ test_path = self.root / "tests" / test_name
165
+ if test_path.exists():
166
+ tests.append({
167
+ "file": f"tests/{test_name}",
168
+ "reason": "posible test relacionado"
169
+ })
170
+
171
+ return tests
172
+
173
+ def _build_call_flow(self, entry_points: List[Dict[str, str]]) -> List[Dict[str, Any]]:
174
+ """
175
+ MVP: placeholder (hasta que uses call graph real)
176
+ """
177
+ flow: List[Dict[str, Any]] = []
178
+
179
+ for ep in entry_points:
180
+ flow.append({
181
+ "from": ep["symbol"],
182
+ "to": ["unknown"]
183
+ })
184
+
185
+ return flow
186
+
187
+ def _generate_notes(self, task: str) -> List[str]:
188
+ notes: List[str] = []
189
+
190
+ if "flag" in task or "--" in task:
191
+ notes.append("probablemente necesitas modificar argumentos CLI")
192
+ if "test" in task:
193
+ notes.append("asegúrate de actualizar tests relacionados")
194
+ if "refactor" in task:
195
+ notes.append("mantener compatibilidad hacia atrás")
196
+
197
+ return notes
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sourcecode
3
- Version: 0.10.0
3
+ Version: 0.12.0
4
4
  Summary: Genera un mapa de contexto estructurado de proyectos de software para agentes IA
5
5
  License: MIT
6
6
  Requires-Python: >=3.9
@@ -1,12 +1,13 @@
1
- sourcecode/__init__.py,sha256=fi6ngxtFGN3xBcGhKYcwR2ZILxi4G0VeAOwlM8oXok0,100
1
+ sourcecode/__init__.py,sha256=91oDq_gbQES16CvJ5D_TTPZVZO_icb14Ho9LrvvJj_c,100
2
2
  sourcecode/architecture_summary.py,sha256=6FCTEHmbESxx4lmc4A61x5DPvI57oJAUnaJIZ-GjRkc,10371
3
3
  sourcecode/classifier.py,sha256=7ubGTn5vfw9z-9eosLIYDtQUu7d8dvB4XKKDxysUVSM,6910
4
- sourcecode/cli.py,sha256=2NWnQ7SoheABop6XduOCxa6eRCyRf0aZrE1viQg9soc,20064
4
+ sourcecode/cli.py,sha256=q6ypel6n5IOraS52bN6el5FbwDxaqtnbt0yHNuypnzU,21294
5
5
  sourcecode/coverage_parser.py,sha256=0IfE-0BNF1NxTjRnAuHnn1U0VzcB_aGBQMITR6Qsy3E,19701
6
6
  sourcecode/dependency_analyzer.py,sha256=c8tSbUpug-2avKNWCP4-_e2_uhBwWl96LwL1Be8O90M,39067
7
7
  sourcecode/doc_analyzer.py,sha256=nnTOsO1tfZvaDPy5PKjRLlg6EQZ-2ONRbXlxDeogH8o,19891
8
8
  sourcecode/graph_analyzer.py,sha256=S5z8mrGfLwN26Mt2IaCR4dOpQ_APU16cfihSp640eGQ,45241
9
9
  sourcecode/metrics_analyzer.py,sha256=zQrIPKDrGIUq4iptXbckflOj2V9897e_6vGZKEwa4b4,20124
10
+ sourcecode/prepare_context.py,sha256=X43dEPe2cb7uMwlpQwVJMe0rQIZ0juaJ7H3F37sPbNc,5371
10
11
  sourcecode/redactor.py,sha256=abSf5jH_IzYzpjF3lEN6hCwdXxvmyHs-3DHz9fd8Mic,2883
11
12
  sourcecode/scanner.py,sha256=TvcuD77m_NF8OlTj3XH1b_jO0qrto8c90fND96ecqb0,6182
12
13
  sourcecode/schema.py,sha256=PAXaAGhJOseyjSYi24vwqOQr52ugjFfMuTe-pP6lnXs,10217
@@ -34,7 +35,7 @@ sourcecode/detectors/rust.py,sha256=iUz9lE4HfBy8CBaDwGhfh6kLsNIovrH2ay2v3y6qNDc,
34
35
  sourcecode/detectors/systems.py,sha256=qf5M9tio1H6XDorSstTWIxKe0xVLR_QGTJpFGyhDz4A,1690
35
36
  sourcecode/detectors/terraform.py,sha256=5EOKQtTViWo7u3tFK0h45YD8GQPwhrww0cOicxGPPn4,1732
36
37
  sourcecode/detectors/tooling.py,sha256=wPvudLCJZ8_cr9GIx44C8jkLAlEN9uTUZcJsP7eVYnQ,1937
37
- sourcecode-0.10.0.dist-info/METADATA,sha256=fsy8ZP-sKE3o61g6ITicvaufEDGH-vLmbSnsIWNuSec,15093
38
- sourcecode-0.10.0.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
39
- sourcecode-0.10.0.dist-info/entry_points.txt,sha256=voDtdlGU2-jR3wCZpQoOls_0eUKaDVCKIfajGHFDATs,50
40
- sourcecode-0.10.0.dist-info/RECORD,,
38
+ sourcecode-0.12.0.dist-info/METADATA,sha256=KtbQW0eomY6kzyJnzy8CvgaRwIe2FD27OdWGHelFlEQ,15093
39
+ sourcecode-0.12.0.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
40
+ sourcecode-0.12.0.dist-info/entry_points.txt,sha256=voDtdlGU2-jR3wCZpQoOls_0eUKaDVCKIfajGHFDATs,50
41
+ sourcecode-0.12.0.dist-info/RECORD,,