sourcecode 0.19.0__py3-none-any.whl → 0.21.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 +117 -18
- sourcecode/dependency_analyzer.py +157 -2
- sourcecode/prepare_context.py +449 -160
- sourcecode/semantic_analyzer.py +2 -0
- sourcecode/serializer.py +18 -11
- {sourcecode-0.19.0.dist-info → sourcecode-0.21.0.dist-info}/METADATA +92 -9
- {sourcecode-0.19.0.dist-info → sourcecode-0.21.0.dist-info}/RECORD +10 -10
- {sourcecode-0.19.0.dist-info → sourcecode-0.21.0.dist-info}/WHEEL +0 -0
- {sourcecode-0.19.0.dist-info → sourcecode-0.21.0.dist-info}/entry_points.txt +0 -0
sourcecode/__init__.py
CHANGED
sourcecode/cli.py
CHANGED
|
@@ -75,6 +75,11 @@ def main(
|
|
|
75
75
|
"--graph-edges",
|
|
76
76
|
help="Tipos de arista para `--graph-modules` separados por comas: imports,calls,contains,extends",
|
|
77
77
|
),
|
|
78
|
+
no_tree: bool = typer.Option(
|
|
79
|
+
False,
|
|
80
|
+
"--no-tree",
|
|
81
|
+
help="Suprimir file_tree y file_paths del output (ideal con --dependencies en proyectos grandes)",
|
|
82
|
+
),
|
|
78
83
|
no_redact: bool = typer.Option(
|
|
79
84
|
False,
|
|
80
85
|
"--no-redact",
|
|
@@ -151,8 +156,17 @@ def main(
|
|
|
151
156
|
"--code-notes",
|
|
152
157
|
help="Extraer anotaciones TODO/FIXME/HACK/NOTE/DEPRECATED/WARNING/BUG/XXX/OPTIMIZE con ubicacion y simbolo envolvente, y detectar ADRs en docs/decisions/, docs/adr/ y similares",
|
|
153
158
|
),
|
|
159
|
+
agent: bool = typer.Option(
|
|
160
|
+
False,
|
|
161
|
+
"--agent",
|
|
162
|
+
help="Modo agente: selecciona automaticamente --compact --dependencies --env-map --code-notes y activa --no-tree en proyectos Java/Gradle o grandes",
|
|
163
|
+
),
|
|
154
164
|
) -> None:
|
|
155
165
|
"""Genera un mapa de contexto estructurado del proyecto en formato JSON o YAML."""
|
|
166
|
+
# When a subcommand (e.g. prepare-context) is invoked, skip the main analysis.
|
|
167
|
+
if ctx.invoked_subcommand is not None:
|
|
168
|
+
return
|
|
169
|
+
|
|
156
170
|
# Validar formato
|
|
157
171
|
if format not in FORMAT_CHOICES:
|
|
158
172
|
typer.echo(
|
|
@@ -221,6 +235,16 @@ def main(
|
|
|
221
235
|
_java_min_depth = 8
|
|
222
236
|
effective_depth = max(depth, _java_min_depth) if _is_java and depth < _java_min_depth else depth
|
|
223
237
|
|
|
238
|
+
# --agent: auto-select flags based on project characteristics
|
|
239
|
+
if agent:
|
|
240
|
+
compact = True
|
|
241
|
+
if _is_java:
|
|
242
|
+
no_tree = True
|
|
243
|
+
_agent_flags = ["--compact", "--dependencies", "--env-map", "--code-notes"]
|
|
244
|
+
if no_tree:
|
|
245
|
+
_agent_flags.append("--no-tree")
|
|
246
|
+
typer.echo(f"[agent] {' '.join(_agent_flags)}", err=True)
|
|
247
|
+
|
|
224
248
|
scanner = FileScanner(target, max_depth=effective_depth)
|
|
225
249
|
raw_tree = scanner.scan_tree()
|
|
226
250
|
|
|
@@ -594,7 +618,7 @@ def main(
|
|
|
594
618
|
|
|
595
619
|
# 4. Serializar (con o sin modo compact)
|
|
596
620
|
if compact:
|
|
597
|
-
data = compact_view(sm)
|
|
621
|
+
data = compact_view(sm, no_tree=no_tree)
|
|
598
622
|
# Aplicar redaccion sobre el dict del compact view
|
|
599
623
|
if not no_redact:
|
|
600
624
|
data = redact_dict(data)
|
|
@@ -602,6 +626,9 @@ def main(
|
|
|
602
626
|
else:
|
|
603
627
|
# Redactar sobre el dict serializado (SEC-01, SEC-03)
|
|
604
628
|
raw_dict = asdict(sm)
|
|
629
|
+
if no_tree:
|
|
630
|
+
raw_dict.pop("file_tree", None)
|
|
631
|
+
raw_dict.pop("file_paths", None)
|
|
605
632
|
if not no_redact:
|
|
606
633
|
raw_dict = redact_dict(raw_dict)
|
|
607
634
|
|
|
@@ -631,31 +658,103 @@ def main(
|
|
|
631
658
|
|
|
632
659
|
@app.command("prepare-context")
|
|
633
660
|
def prepare_context_cmd(
|
|
634
|
-
task: str = typer.Argument(
|
|
635
|
-
|
|
661
|
+
task: Optional[str] = typer.Argument(
|
|
662
|
+
None,
|
|
663
|
+
help="Task: explain | fix-bug | refactor | generate-tests",
|
|
664
|
+
),
|
|
665
|
+
path: Path = typer.Option(
|
|
666
|
+
Path("."),
|
|
667
|
+
"--path", "-p",
|
|
668
|
+
help="Project directory to analyze (default: current directory)",
|
|
669
|
+
),
|
|
670
|
+
llm_prompt: bool = typer.Option(
|
|
671
|
+
False,
|
|
672
|
+
"--llm-prompt",
|
|
673
|
+
help="Append a ready-to-use LLM prompt to the output",
|
|
674
|
+
),
|
|
675
|
+
task_help: bool = typer.Option(
|
|
676
|
+
False,
|
|
677
|
+
"--task-help",
|
|
678
|
+
help="List available tasks with descriptions and exit",
|
|
679
|
+
),
|
|
680
|
+
dry_run: bool = typer.Option(
|
|
681
|
+
False,
|
|
682
|
+
"--dry-run",
|
|
683
|
+
help="Show what would be analyzed without running it",
|
|
684
|
+
),
|
|
636
685
|
) -> None:
|
|
637
|
-
"""
|
|
638
|
-
|
|
686
|
+
"""Prepare task-aware context optimized for LLM reasoning.
|
|
687
|
+
|
|
688
|
+
\b
|
|
689
|
+
Note: PATH must be provided before the subcommand (default: '.'):
|
|
690
|
+
sourcecode . prepare-context explain
|
|
691
|
+
sourcecode . prepare-context fix-bug --path /my/project
|
|
692
|
+
sourcecode . prepare-context generate-tests --llm-prompt
|
|
693
|
+
sourcecode . prepare-context --task-help
|
|
694
|
+
"""
|
|
695
|
+
from sourcecode.prepare_context import TASKS, TaskContextBuilder
|
|
696
|
+
|
|
697
|
+
if task_help:
|
|
698
|
+
typer.echo("Available tasks:\n")
|
|
699
|
+
for name, spec in TASKS.items():
|
|
700
|
+
typer.echo(f" {name:<20} {spec.description}")
|
|
701
|
+
typer.echo(f" {'':20} Output: {spec.output_hint}\n")
|
|
702
|
+
raise typer.Exit()
|
|
639
703
|
|
|
640
|
-
|
|
704
|
+
if task is None:
|
|
705
|
+
typer.echo(
|
|
706
|
+
f"Error: task is required. Available: {', '.join(TASKS)}\n"
|
|
707
|
+
"Use --task-help for descriptions.",
|
|
708
|
+
err=True,
|
|
709
|
+
)
|
|
710
|
+
raise typer.Exit(code=1)
|
|
641
711
|
|
|
642
|
-
|
|
712
|
+
if task not in TASKS:
|
|
713
|
+
typer.echo(
|
|
714
|
+
f"Error: unknown task '{task}'. Available: {', '.join(TASKS)}",
|
|
715
|
+
err=True,
|
|
716
|
+
)
|
|
717
|
+
raise typer.Exit(code=1)
|
|
643
718
|
|
|
719
|
+
target = path.resolve()
|
|
644
720
|
if not target.exists() or not target.is_dir():
|
|
645
721
|
typer.echo(f"Error: '{target}' no es un directorio válido.", err=True)
|
|
646
722
|
raise typer.Exit(code=1)
|
|
647
723
|
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
"
|
|
653
|
-
"
|
|
654
|
-
|
|
655
|
-
"
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
724
|
+
if dry_run:
|
|
725
|
+
spec = TASKS[task]
|
|
726
|
+
typer.echo(f"task: {task}")
|
|
727
|
+
typer.echo(f"goal: {spec.goal}")
|
|
728
|
+
typer.echo(f"path: {target}")
|
|
729
|
+
typer.echo(f"analyzers: dependencies={'yes' if spec.enable_dependencies else 'no'}"
|
|
730
|
+
f", code_notes={'yes' if spec.enable_code_notes else 'no'}")
|
|
731
|
+
typer.echo(f"output: {spec.output_hint}")
|
|
732
|
+
raise typer.Exit()
|
|
733
|
+
|
|
734
|
+
from dataclasses import asdict
|
|
735
|
+
|
|
736
|
+
builder = TaskContextBuilder(target)
|
|
737
|
+
output = builder.build(task)
|
|
738
|
+
|
|
739
|
+
out: dict[str, Any] = {
|
|
740
|
+
"task": output.task,
|
|
741
|
+
"goal": output.goal,
|
|
742
|
+
"project_summary": output.project_summary,
|
|
743
|
+
"architecture_summary": output.architecture_summary,
|
|
744
|
+
"relevant_files": [asdict(f) for f in output.relevant_files],
|
|
745
|
+
"key_dependencies": output.key_dependencies,
|
|
659
746
|
}
|
|
747
|
+
if output.suspected_areas:
|
|
748
|
+
out["suspected_areas"] = output.suspected_areas
|
|
749
|
+
if output.improvement_opportunities:
|
|
750
|
+
out["improvement_opportunities"] = output.improvement_opportunities
|
|
751
|
+
if output.test_gaps:
|
|
752
|
+
out["test_gaps"] = output.test_gaps
|
|
753
|
+
if output.code_notes_summary:
|
|
754
|
+
out["code_notes_summary"] = output.code_notes_summary
|
|
755
|
+
if output.limitations:
|
|
756
|
+
out["limitations"] = output.limitations
|
|
757
|
+
if llm_prompt:
|
|
758
|
+
out["llm_prompt"] = builder.render_prompt(output)
|
|
660
759
|
|
|
661
760
|
typer.echo(json.dumps(out, indent=2, ensure_ascii=False))
|
|
@@ -34,6 +34,7 @@ class DependencyAnalyzer:
|
|
|
34
34
|
self._analyze_go,
|
|
35
35
|
self._analyze_dotnet,
|
|
36
36
|
self._analyze_java,
|
|
37
|
+
self._analyze_gradle,
|
|
37
38
|
):
|
|
38
39
|
handler_records, handler_limitations = handler(root)
|
|
39
40
|
records.extend(replace(record, workspace=workspace) for record in handler_records)
|
|
@@ -925,6 +926,47 @@ class DependencyAnalyzer:
|
|
|
925
926
|
result.append(resolved)
|
|
926
927
|
return self._dedupe(result)
|
|
927
928
|
|
|
929
|
+
def _parse_maven_properties(self, root_elem: ET.Element, ns: str) -> dict[str, str]:
|
|
930
|
+
properties: dict[str, str] = {}
|
|
931
|
+
props_elem = root_elem.find(f"{ns}properties")
|
|
932
|
+
if props_elem is not None:
|
|
933
|
+
for prop in props_elem:
|
|
934
|
+
tag = prop.tag.replace(ns, "") if ns else prop.tag
|
|
935
|
+
if prop.text:
|
|
936
|
+
properties[tag] = prop.text.strip()
|
|
937
|
+
return properties
|
|
938
|
+
|
|
939
|
+
def _resolve_maven_version(self, version_raw: Optional[str], properties: dict[str, str]) -> Optional[str]:
|
|
940
|
+
if not version_raw:
|
|
941
|
+
return None
|
|
942
|
+
if not version_raw.startswith("${"):
|
|
943
|
+
return version_raw
|
|
944
|
+
prop_name = version_raw[2:-1] if version_raw.endswith("}") else None
|
|
945
|
+
if prop_name and prop_name in properties:
|
|
946
|
+
return properties[prop_name]
|
|
947
|
+
return None
|
|
948
|
+
|
|
949
|
+
def _parse_dependency_management(
|
|
950
|
+
self, root_elem: ET.Element, ns: str, properties: dict[str, str]
|
|
951
|
+
) -> dict[str, str]:
|
|
952
|
+
dm_versions: dict[str, str] = {}
|
|
953
|
+
dm_elem = root_elem.find(f"{ns}dependencyManagement")
|
|
954
|
+
if dm_elem is None:
|
|
955
|
+
return dm_versions
|
|
956
|
+
deps_elem = dm_elem.find(f"{ns}dependencies")
|
|
957
|
+
if deps_elem is None:
|
|
958
|
+
return dm_versions
|
|
959
|
+
for dep in deps_elem.findall(f"{ns}dependency"):
|
|
960
|
+
group_id = (dep.findtext(f"{ns}groupId") or "").strip()
|
|
961
|
+
artifact_id = (dep.findtext(f"{ns}artifactId") or "").strip()
|
|
962
|
+
if not group_id or not artifact_id:
|
|
963
|
+
continue
|
|
964
|
+
version_raw = (dep.findtext(f"{ns}version") or "").strip() or None
|
|
965
|
+
resolved = self._resolve_maven_version(version_raw, properties)
|
|
966
|
+
if resolved:
|
|
967
|
+
dm_versions[f"{group_id}:{artifact_id}"] = resolved
|
|
968
|
+
return dm_versions
|
|
969
|
+
|
|
928
970
|
def _analyze_java(self, root: Path) -> tuple[list[DependencyRecord], list[str]]:
|
|
929
971
|
pom = root / "pom.xml"
|
|
930
972
|
if not pom.exists():
|
|
@@ -938,6 +980,9 @@ class DependencyAnalyzer:
|
|
|
938
980
|
ns_match = re.match(r"\{[^}]+\}", root_elem.tag)
|
|
939
981
|
ns = ns_match.group(0) if ns_match else ""
|
|
940
982
|
|
|
983
|
+
properties = self._parse_maven_properties(root_elem, ns)
|
|
984
|
+
dm_versions = self._parse_dependency_management(root_elem, ns, properties)
|
|
985
|
+
|
|
941
986
|
records: list[DependencyRecord] = []
|
|
942
987
|
deps_elem = root_elem.find(f"{ns}dependencies")
|
|
943
988
|
if deps_elem is None:
|
|
@@ -949,8 +994,9 @@ class DependencyAnalyzer:
|
|
|
949
994
|
if not group_id or not artifact_id:
|
|
950
995
|
continue
|
|
951
996
|
version_raw = (dep.findtext(f"{ns}version") or "").strip() or None
|
|
952
|
-
|
|
953
|
-
|
|
997
|
+
declared = self._resolve_maven_version(version_raw, properties)
|
|
998
|
+
if declared is None:
|
|
999
|
+
declared = dm_versions.get(f"{group_id}:{artifact_id}")
|
|
954
1000
|
scope_text = (dep.findtext(f"{ns}scope") or "compile").strip().lower()
|
|
955
1001
|
scope = "dev" if scope_text == "test" else "direct"
|
|
956
1002
|
records.append(
|
|
@@ -969,6 +1015,115 @@ class DependencyAnalyzer:
|
|
|
969
1015
|
limitations.append("java: pom.xml sin dependencias parseables (puede usar BOM o propiedades)")
|
|
970
1016
|
return records, limitations
|
|
971
1017
|
|
|
1018
|
+
def _analyze_gradle(self, root: Path) -> tuple[list[DependencyRecord], list[str]]:
|
|
1019
|
+
for filename in ("build.gradle", "build.gradle.kts"):
|
|
1020
|
+
gradle_file = root / filename
|
|
1021
|
+
if gradle_file.exists():
|
|
1022
|
+
try:
|
|
1023
|
+
content = gradle_file.read_text(encoding="utf-8", errors="replace")
|
|
1024
|
+
except OSError:
|
|
1025
|
+
return [], [f"gradle: error al leer {filename}"]
|
|
1026
|
+
props = self._parse_gradle_properties(root, content)
|
|
1027
|
+
records = self._parse_gradle_dependencies(content, props, filename)
|
|
1028
|
+
return records, ["gradle: sin lockfile compatible; dependencias transitivas no disponibles"]
|
|
1029
|
+
return [], []
|
|
1030
|
+
|
|
1031
|
+
def _parse_gradle_properties(self, root: Path, content: str) -> dict[str, str]:
|
|
1032
|
+
props: dict[str, str] = {}
|
|
1033
|
+
# gradle.properties file (key=value format)
|
|
1034
|
+
gp = root / "gradle.properties"
|
|
1035
|
+
if gp.exists():
|
|
1036
|
+
try:
|
|
1037
|
+
for line in gp.read_text(encoding="utf-8", errors="replace").splitlines():
|
|
1038
|
+
stripped = line.strip()
|
|
1039
|
+
if stripped and not stripped.startswith("#") and "=" in stripped:
|
|
1040
|
+
k, _, v = stripped.partition("=")
|
|
1041
|
+
props[k.strip()] = v.strip()
|
|
1042
|
+
except OSError:
|
|
1043
|
+
pass
|
|
1044
|
+
# Variables declared in the build file itself.
|
|
1045
|
+
# Match: val/var/def x = "v", or bare x = "v" anywhere (ext blocks, top-level).
|
|
1046
|
+
# Negative lookbehind (?<![.\w]) prevents matching mid-expression (e.g. obj.field = "v").
|
|
1047
|
+
for m in re.finditer(r"""(?:(?:val|var|def)\s+)?(?<![.\w])(\w+)\s*=\s*["']([^"']+)["']""", content):
|
|
1048
|
+
props.setdefault(m.group(1), m.group(2))
|
|
1049
|
+
return props
|
|
1050
|
+
|
|
1051
|
+
def _resolve_gradle_version(self, version_raw: Optional[str], props: dict[str, str]) -> Optional[str]:
|
|
1052
|
+
if not version_raw:
|
|
1053
|
+
return None
|
|
1054
|
+
# ${varName} — Groovy string interpolation
|
|
1055
|
+
m = re.fullmatch(r"\$\{(\w+)\}", version_raw)
|
|
1056
|
+
if m:
|
|
1057
|
+
return props.get(m.group(1))
|
|
1058
|
+
# $varName — Kotlin string interpolation
|
|
1059
|
+
m = re.fullmatch(r"\$(\w+)", version_raw)
|
|
1060
|
+
if m:
|
|
1061
|
+
return props.get(m.group(1))
|
|
1062
|
+
return version_raw
|
|
1063
|
+
|
|
1064
|
+
def _parse_gradle_dependencies(
|
|
1065
|
+
self, content: str, props: dict[str, str], manifest_path: str
|
|
1066
|
+
) -> list[DependencyRecord]:
|
|
1067
|
+
_DIRECT = frozenset({
|
|
1068
|
+
"implementation", "api", "compileOnly", "runtimeOnly", "compile", "provided",
|
|
1069
|
+
"compileClasspath", "runtimeClasspath",
|
|
1070
|
+
})
|
|
1071
|
+
_DEV = frozenset({
|
|
1072
|
+
"testImplementation", "testRuntimeOnly", "testCompileOnly", "testApi",
|
|
1073
|
+
"testCompile", "androidTestImplementation", "annotationProcessor", "kapt",
|
|
1074
|
+
"debugImplementation", "releaseImplementation",
|
|
1075
|
+
})
|
|
1076
|
+
all_scopes = _DIRECT | _DEV
|
|
1077
|
+
records: list[DependencyRecord] = []
|
|
1078
|
+
|
|
1079
|
+
# String notation: scope("group:artifact") or scope("group:artifact:version")
|
|
1080
|
+
str_pat = re.compile(
|
|
1081
|
+
r"""(\w+)\s*\(?\s*["']([A-Za-z][\w.\-]*:[A-Za-z][\w.\-]*)(?::([^"'\s)]+))?["']"""
|
|
1082
|
+
)
|
|
1083
|
+
for m in str_pat.finditer(content):
|
|
1084
|
+
scope_kw = m.group(1)
|
|
1085
|
+
if scope_kw not in all_scopes:
|
|
1086
|
+
continue
|
|
1087
|
+
parts = m.group(2).split(":")
|
|
1088
|
+
if len(parts) < 2:
|
|
1089
|
+
continue
|
|
1090
|
+
group_id, artifact_id = parts[0].strip(), parts[1].strip()
|
|
1091
|
+
if not group_id or not artifact_id:
|
|
1092
|
+
continue
|
|
1093
|
+
version = self._resolve_gradle_version(m.group(3), props)
|
|
1094
|
+
records.append(DependencyRecord(
|
|
1095
|
+
name=f"{group_id}:{artifact_id}",
|
|
1096
|
+
ecosystem="java",
|
|
1097
|
+
scope="dev" if scope_kw in _DEV else "direct",
|
|
1098
|
+
declared_version=version,
|
|
1099
|
+
source="manifest",
|
|
1100
|
+
manifest_path=manifest_path,
|
|
1101
|
+
))
|
|
1102
|
+
|
|
1103
|
+
# Map notation: scope(group: "g", name: "a", version: "v") — Groovy uses ':', Kotlin uses '='
|
|
1104
|
+
map_pat = re.compile(
|
|
1105
|
+
r"""(\w+)\s*\(?\s*group\s*[=:]\s*["']([^"']+)["']\s*,\s*name\s*[=:]\s*["']([^"']+)["']"""
|
|
1106
|
+
r"""(?:\s*,\s*version\s*[=:]\s*["']([^"']+)["'])?"""
|
|
1107
|
+
)
|
|
1108
|
+
for m in map_pat.finditer(content):
|
|
1109
|
+
scope_kw = m.group(1)
|
|
1110
|
+
if scope_kw not in all_scopes:
|
|
1111
|
+
continue
|
|
1112
|
+
group_id, artifact_id = m.group(2).strip(), m.group(3).strip()
|
|
1113
|
+
if not group_id or not artifact_id:
|
|
1114
|
+
continue
|
|
1115
|
+
version = self._resolve_gradle_version(m.group(4), props)
|
|
1116
|
+
records.append(DependencyRecord(
|
|
1117
|
+
name=f"{group_id}:{artifact_id}",
|
|
1118
|
+
ecosystem="java",
|
|
1119
|
+
scope="dev" if scope_kw in _DEV else "direct",
|
|
1120
|
+
declared_version=version,
|
|
1121
|
+
source="manifest",
|
|
1122
|
+
manifest_path=manifest_path,
|
|
1123
|
+
))
|
|
1124
|
+
|
|
1125
|
+
return self._dedupe(records)
|
|
1126
|
+
|
|
972
1127
|
def _load_yaml_file(self, path: Path) -> Optional[dict[str, Any]]:
|
|
973
1128
|
if not path.exists():
|
|
974
1129
|
return None
|
sourcecode/prepare_context.py
CHANGED
|
@@ -1,197 +1,486 @@
|
|
|
1
|
+
"""prepare_context.py — Task-aware context optimizer for LLM reasoning."""
|
|
2
|
+
|
|
1
3
|
from __future__ import annotations
|
|
2
4
|
|
|
3
5
|
from dataclasses import dataclass
|
|
4
6
|
from pathlib import Path
|
|
5
|
-
from typing import Any,
|
|
7
|
+
from typing import Any, Optional
|
|
6
8
|
|
|
7
|
-
from sourcecode.semantic_analyzer import SemanticAnalyzer
|
|
8
9
|
|
|
10
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
11
|
+
# Prompt templates
|
|
12
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
9
13
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
# =========================
|
|
14
|
+
_EXPLAIN_PROMPT = """\
|
|
15
|
+
You are an expert software engineer. Your task is to explain this project clearly.
|
|
13
16
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
17
|
+
## Project Summary
|
|
18
|
+
|
|
19
|
+
{project_summary}
|
|
20
|
+
|
|
21
|
+
{architecture_section}
|
|
22
|
+
|
|
23
|
+
## Key Files
|
|
24
|
+
|
|
25
|
+
{relevant_files_section}
|
|
26
|
+
|
|
27
|
+
{dependencies_section}
|
|
28
|
+
|
|
29
|
+
## Instructions
|
|
30
|
+
|
|
31
|
+
1. Summarize what this project does and who it is for.
|
|
32
|
+
2. Describe the main components and how they interact.
|
|
33
|
+
3. Identify the primary entry point and the main execution flow.
|
|
34
|
+
4. Highlight any non-obvious design decisions or constraints.
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
_FIX_BUG_PROMPT = """\
|
|
38
|
+
You are an expert debugger. Your task is to identify and fix a bug in this codebase.
|
|
39
|
+
|
|
40
|
+
## Project Summary
|
|
41
|
+
|
|
42
|
+
{project_summary}
|
|
43
|
+
|
|
44
|
+
## Most Relevant Files
|
|
45
|
+
|
|
46
|
+
{relevant_files_section}
|
|
47
|
+
|
|
48
|
+
{suspected_areas_section}
|
|
49
|
+
|
|
50
|
+
{code_notes_section}
|
|
51
|
+
|
|
52
|
+
## Instructions
|
|
53
|
+
|
|
54
|
+
1. Review the relevant files listed above, paying close attention to suspected areas.
|
|
55
|
+
2. Identify the root cause of the bug.
|
|
56
|
+
3. Propose a minimal, targeted fix with a concrete code patch.
|
|
57
|
+
4. Explain why the fix is correct and what side effects to watch for.
|
|
58
|
+
"""
|
|
59
|
+
|
|
60
|
+
_REFACTOR_PROMPT = """\
|
|
61
|
+
You are an expert software engineer focused on code quality. \
|
|
62
|
+
Your task is to propose refactoring improvements.
|
|
63
|
+
|
|
64
|
+
## Project Summary
|
|
65
|
+
|
|
66
|
+
{project_summary}
|
|
67
|
+
|
|
68
|
+
{architecture_section}
|
|
69
|
+
|
|
70
|
+
## Files to Refactor
|
|
71
|
+
|
|
72
|
+
{relevant_files_section}
|
|
73
|
+
|
|
74
|
+
{improvement_opportunities_section}
|
|
75
|
+
|
|
76
|
+
## Instructions
|
|
77
|
+
|
|
78
|
+
1. Identify the top 3–5 most impactful refactoring opportunities.
|
|
79
|
+
2. For each, describe the current problem, the proposed change, and the expected benefit.
|
|
80
|
+
3. Prioritize changes that reduce complexity or improve testability.
|
|
81
|
+
4. Do not suggest changes that break public APIs without noting the impact.
|
|
82
|
+
"""
|
|
83
|
+
|
|
84
|
+
_GENERATE_TESTS_PROMPT = """\
|
|
85
|
+
You are an expert in software testing. Your task is to write tests for \
|
|
86
|
+
untested or undertested areas of this codebase.
|
|
87
|
+
|
|
88
|
+
## Project Summary
|
|
89
|
+
|
|
90
|
+
{project_summary}
|
|
20
91
|
|
|
92
|
+
## Files Needing Tests
|
|
93
|
+
|
|
94
|
+
{relevant_files_section}
|
|
95
|
+
|
|
96
|
+
{test_gaps_section}
|
|
97
|
+
|
|
98
|
+
{dependencies_section}
|
|
99
|
+
|
|
100
|
+
## Instructions
|
|
101
|
+
|
|
102
|
+
1. Write unit tests for the most critical untested functions and classes.
|
|
103
|
+
2. Cover edge cases and error paths, not just the happy path.
|
|
104
|
+
3. Use the same testing framework already present in the project.
|
|
105
|
+
4. Each test must have a clear name that describes exactly what it verifies.
|
|
106
|
+
"""
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
110
|
+
# Task registry
|
|
111
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
21
112
|
|
|
22
113
|
@dataclass
|
|
23
|
-
class
|
|
114
|
+
class TaskSpec:
|
|
115
|
+
name: str
|
|
116
|
+
goal: str
|
|
117
|
+
description: str
|
|
118
|
+
ranking_boosts: list[str]
|
|
119
|
+
ranking_penalties: list[str]
|
|
120
|
+
enable_code_notes: bool
|
|
121
|
+
enable_dependencies: bool
|
|
122
|
+
prompt_template: str
|
|
123
|
+
output_hint: str
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
TASKS: dict[str, TaskSpec] = {
|
|
127
|
+
"explain": TaskSpec(
|
|
128
|
+
name="explain",
|
|
129
|
+
goal="Generate a comprehensive project summary for onboarding an LLM or developer.",
|
|
130
|
+
description="Analyze project structure, entry points, and key dependencies.",
|
|
131
|
+
ranking_boosts=["main", "cli", "app", "core", "index", "readme", "schema", "config"],
|
|
132
|
+
ranking_penalties=["test_", "spec_", ".min.", "__pycache__", "docs/"],
|
|
133
|
+
enable_code_notes=False,
|
|
134
|
+
enable_dependencies=True,
|
|
135
|
+
prompt_template=_EXPLAIN_PROMPT,
|
|
136
|
+
output_hint="project_summary, architecture_summary, relevant_files, key_dependencies",
|
|
137
|
+
),
|
|
138
|
+
"fix-bug": TaskSpec(
|
|
139
|
+
name="fix-bug",
|
|
140
|
+
goal="Identify the most likely files and areas where a bug may be located.",
|
|
141
|
+
description="Rank files by annotation density, surface TODOs/FIXMEs/BUGs.",
|
|
142
|
+
ranking_boosts=["handler", "service", "middleware", "router", "controller",
|
|
143
|
+
"processor", "parser", "validator"],
|
|
144
|
+
ranking_penalties=["test_", "spec_", ".min.", "__pycache__", "docs/"],
|
|
145
|
+
enable_code_notes=True,
|
|
146
|
+
enable_dependencies=False,
|
|
147
|
+
prompt_template=_FIX_BUG_PROMPT,
|
|
148
|
+
output_hint="relevant_files (ranked by risk), suspected_areas, code_notes_summary",
|
|
149
|
+
),
|
|
150
|
+
"refactor": TaskSpec(
|
|
151
|
+
name="refactor",
|
|
152
|
+
goal="Highlight structural issues and improvement opportunities across the codebase.",
|
|
153
|
+
description="Surface large files, high-annotation areas, and architectural patterns.",
|
|
154
|
+
ranking_boosts=["core", "utils", "helper", "base", "common", "shared", "lib"],
|
|
155
|
+
ranking_penalties=["test_", ".min.", "__pycache__"],
|
|
156
|
+
enable_code_notes=True,
|
|
157
|
+
enable_dependencies=False,
|
|
158
|
+
prompt_template=_REFACTOR_PROMPT,
|
|
159
|
+
output_hint="relevant_files, improvement_opportunities, architecture_summary",
|
|
160
|
+
),
|
|
161
|
+
"generate-tests": TaskSpec(
|
|
162
|
+
name="generate-tests",
|
|
163
|
+
goal="Identify untested source files and generate targeted test stubs.",
|
|
164
|
+
description="Find source files without matching test files and rank by complexity.",
|
|
165
|
+
ranking_boosts=["service", "handler", "controller", "router", "parser",
|
|
166
|
+
"validator", "processor"],
|
|
167
|
+
ranking_penalties=[".min.", "__pycache__", "docs/"],
|
|
168
|
+
enable_code_notes=False,
|
|
169
|
+
enable_dependencies=True,
|
|
170
|
+
prompt_template=_GENERATE_TESTS_PROMPT,
|
|
171
|
+
output_hint="test_gaps, relevant_files (source without tests), key_dependencies",
|
|
172
|
+
),
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
177
|
+
# Output schema
|
|
178
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
179
|
+
|
|
180
|
+
@dataclass
|
|
181
|
+
class RelevantFile:
|
|
24
182
|
path: str
|
|
25
|
-
role: str
|
|
183
|
+
role: str # entrypoint | source | test
|
|
184
|
+
score: float
|
|
185
|
+
reason: str
|
|
26
186
|
|
|
27
187
|
|
|
28
188
|
@dataclass
|
|
29
|
-
class
|
|
189
|
+
class TaskOutput:
|
|
30
190
|
task: str
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
191
|
+
goal: str
|
|
192
|
+
project_summary: Optional[str]
|
|
193
|
+
architecture_summary: Optional[str]
|
|
194
|
+
relevant_files: list[RelevantFile]
|
|
195
|
+
suspected_areas: list[str]
|
|
196
|
+
improvement_opportunities: list[str]
|
|
197
|
+
test_gaps: list[str]
|
|
198
|
+
key_dependencies: list[dict[str, Any]]
|
|
199
|
+
code_notes_summary: Optional[dict[str, Any]]
|
|
200
|
+
limitations: list[str]
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
204
|
+
# Builder
|
|
205
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
37
206
|
|
|
207
|
+
_SOURCE_EXTENSIONS: frozenset[str] = frozenset({
|
|
208
|
+
".py", ".js", ".ts", ".tsx", ".jsx", ".java", ".kt",
|
|
209
|
+
".go", ".rs", ".rb", ".php", ".cs", ".dart",
|
|
210
|
+
})
|
|
38
211
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
212
|
+
_ALL_EXTENSIONS: frozenset[str] = _SOURCE_EXTENSIONS | frozenset({
|
|
213
|
+
".md", ".toml", ".yaml", ".yml", ".json", ".xml",
|
|
214
|
+
})
|
|
42
215
|
|
|
43
|
-
|
|
216
|
+
|
|
217
|
+
class TaskContextBuilder:
|
|
44
218
|
def __init__(self, root: Path) -> None:
|
|
45
219
|
self.root = root
|
|
46
|
-
self.analyzer = SemanticAnalyzer(root)
|
|
47
220
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
221
|
+
def build(self, task_name: str) -> TaskOutput:
|
|
222
|
+
if task_name not in TASKS:
|
|
223
|
+
raise ValueError(
|
|
224
|
+
f"Unknown task '{task_name}'. Available: {', '.join(TASKS)}"
|
|
225
|
+
)
|
|
226
|
+
spec = TASKS[task_name]
|
|
227
|
+
|
|
228
|
+
# ── 1. Scan ────────────────────────────────────────────────────────
|
|
229
|
+
from sourcecode.scanner import FileScanner
|
|
230
|
+
from sourcecode.tree_utils import flatten_file_tree
|
|
231
|
+
|
|
232
|
+
scanner = FileScanner(self.root, max_depth=6)
|
|
233
|
+
file_tree = scanner.scan_tree()
|
|
234
|
+
manifests = scanner.find_manifests()
|
|
235
|
+
all_paths = [p.replace("\\", "/") for p in flatten_file_tree(file_tree)]
|
|
236
|
+
|
|
237
|
+
# ── 2. Detect stacks + entry points ───────────────────────────────
|
|
238
|
+
from sourcecode.detectors import ProjectDetector, build_default_detectors
|
|
239
|
+
|
|
240
|
+
detector = ProjectDetector(build_default_detectors())
|
|
241
|
+
stacks, entry_points, _ = detector.detect(self.root, file_tree, manifests)
|
|
242
|
+
stacks, project_type = detector.classify_results(file_tree, stacks, entry_points)
|
|
243
|
+
|
|
244
|
+
# ── 3. Summarize ───────────────────────────────────────────────────
|
|
245
|
+
from sourcecode.schema import AnalysisMetadata, SourceMap
|
|
246
|
+
from sourcecode.summarizer import ProjectSummarizer
|
|
247
|
+
from sourcecode.architecture_summary import ArchitectureSummarizer
|
|
248
|
+
|
|
249
|
+
sm = SourceMap(
|
|
250
|
+
metadata=AnalysisMetadata(analyzed_path=str(self.root)),
|
|
251
|
+
file_tree=file_tree,
|
|
252
|
+
stacks=stacks,
|
|
253
|
+
project_type=project_type,
|
|
254
|
+
entry_points=entry_points,
|
|
255
|
+
)
|
|
256
|
+
sm.file_paths = all_paths
|
|
257
|
+
|
|
258
|
+
project_summary = ProjectSummarizer(self.root).generate(sm)
|
|
259
|
+
architecture_summary = ArchitectureSummarizer(self.root).generate(sm)
|
|
260
|
+
|
|
261
|
+
# ── 4. Dependencies ────────────────────────────────────────────────
|
|
262
|
+
key_dependencies: list[dict[str, Any]] = []
|
|
263
|
+
limitations: list[str] = []
|
|
264
|
+
|
|
265
|
+
if spec.enable_dependencies:
|
|
266
|
+
from dataclasses import asdict
|
|
267
|
+
from sourcecode.dependency_analyzer import DependencyAnalyzer
|
|
268
|
+
|
|
269
|
+
dep_records, dep_summary = DependencyAnalyzer().analyze(self.root)
|
|
270
|
+
primary_eco = stacks[0].stack if stacks else ""
|
|
271
|
+
direct = [
|
|
272
|
+
d for d in dep_records
|
|
273
|
+
if d.scope != "transitive" and d.source in {"manifest", "lockfile"}
|
|
274
|
+
]
|
|
275
|
+
direct.sort(key=lambda d: (0 if d.ecosystem == primary_eco else 1, d.name.lower()))
|
|
276
|
+
key_dependencies = [asdict(d) for d in direct[:15]]
|
|
277
|
+
limitations.extend(dep_summary.limitations)
|
|
278
|
+
|
|
279
|
+
# ── 5. Code notes ──────────────────────────────────────────────────
|
|
280
|
+
code_notes_summary: Optional[dict[str, Any]] = None
|
|
281
|
+
suspected_areas: list[str] = []
|
|
282
|
+
improvement_opportunities: list[str] = []
|
|
283
|
+
|
|
284
|
+
if spec.enable_code_notes:
|
|
285
|
+
from dataclasses import asdict
|
|
286
|
+
from sourcecode.code_notes_analyzer import CodeNotesAnalyzer
|
|
287
|
+
|
|
288
|
+
cn_notes, _cn_adrs, cn_summary = CodeNotesAnalyzer().analyze(self.root)
|
|
289
|
+
code_notes_summary = asdict(cn_summary)
|
|
290
|
+
|
|
291
|
+
if task_name == "fix-bug":
|
|
292
|
+
bug_kinds = {"FIXME", "BUG", "HACK", "XXX"}
|
|
293
|
+
counts: dict[str, int] = {}
|
|
294
|
+
for note in cn_notes:
|
|
295
|
+
if note.kind in bug_kinds:
|
|
296
|
+
counts[note.path] = counts.get(note.path, 0) + 1
|
|
297
|
+
suspected_areas = [
|
|
298
|
+
f"{p} ({n} annotation{'s' if n > 1 else ''})"
|
|
299
|
+
for p, n in sorted(counts.items(), key=lambda x: -x[1])[:8]
|
|
300
|
+
]
|
|
301
|
+
|
|
302
|
+
elif task_name == "refactor":
|
|
303
|
+
ref_kinds = {"TODO", "DEPRECATED", "OPTIMIZE", "HACK"}
|
|
304
|
+
counts2: dict[str, int] = {}
|
|
305
|
+
for note in cn_notes:
|
|
306
|
+
if note.kind in ref_kinds:
|
|
307
|
+
counts2[note.path] = counts2.get(note.path, 0) + 1
|
|
308
|
+
improvement_opportunities = [
|
|
309
|
+
f"{p}: {n} refactoring annotation{'s' if n > 1 else ''}"
|
|
310
|
+
for p, n in sorted(counts2.items(), key=lambda x: -x[1])[:8]
|
|
311
|
+
]
|
|
312
|
+
|
|
313
|
+
# ── 6. Rank files ──────────────────────────────────────────────────
|
|
314
|
+
entry_set = {ep.path for ep in entry_points}
|
|
315
|
+
test_set = {p for p in all_paths if self._is_test(p)}
|
|
316
|
+
source_set = {p for p in all_paths if not self._is_test(p) and self._is_source(p)}
|
|
317
|
+
|
|
318
|
+
relevant_files = self._rank_files(
|
|
319
|
+
task_name, spec, all_paths, entry_set, test_set
|
|
320
|
+
)
|
|
321
|
+
|
|
322
|
+
# ── 7. Test gaps (generate-tests only) ────────────────────────────
|
|
323
|
+
test_gaps: list[str] = []
|
|
324
|
+
if task_name == "generate-tests":
|
|
325
|
+
test_stems = {
|
|
326
|
+
Path(p).stem.removeprefix("test_").removesuffix("_test")
|
|
327
|
+
for p in test_set
|
|
328
|
+
}
|
|
329
|
+
untested = [
|
|
330
|
+
p for p in source_set
|
|
331
|
+
if Path(p).stem not in test_stems
|
|
332
|
+
and not any(pen in p for pen in spec.ranking_penalties)
|
|
333
|
+
]
|
|
334
|
+
untested.sort(key=lambda p: (len(p.split("/")), p))
|
|
335
|
+
test_gaps = untested[:15]
|
|
336
|
+
|
|
337
|
+
return TaskOutput(
|
|
338
|
+
task=task_name,
|
|
339
|
+
goal=spec.goal,
|
|
340
|
+
project_summary=project_summary,
|
|
341
|
+
architecture_summary=architecture_summary,
|
|
342
|
+
relevant_files=relevant_files,
|
|
343
|
+
suspected_areas=suspected_areas,
|
|
344
|
+
improvement_opportunities=improvement_opportunities,
|
|
345
|
+
test_gaps=test_gaps,
|
|
346
|
+
key_dependencies=key_dependencies,
|
|
347
|
+
code_notes_summary=code_notes_summary,
|
|
348
|
+
limitations=limitations,
|
|
349
|
+
)
|
|
51
350
|
|
|
52
|
-
def
|
|
53
|
-
|
|
54
|
-
Entry point principal.
|
|
55
|
-
"""
|
|
56
|
-
semantic = self.analyzer.analyze()
|
|
351
|
+
def render_prompt(self, output: TaskOutput) -> str:
|
|
352
|
+
spec = TASKS[output.task]
|
|
57
353
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
snippets = self._extract_snippets(relevant_files)
|
|
61
|
-
tests = self._find_related_tests(relevant_files)
|
|
62
|
-
call_flow = self._build_call_flow(entry_points)
|
|
354
|
+
def _section(title: str, body: str) -> str:
|
|
355
|
+
return f"## {title}\n\n{body}" if body.strip() else ""
|
|
63
356
|
|
|
64
|
-
|
|
357
|
+
project_summary = output.project_summary or "No project summary available."
|
|
65
358
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
entry_points=entry_points,
|
|
69
|
-
relevant_files=relevant_files,
|
|
70
|
-
call_flow=call_flow,
|
|
71
|
-
snippets=snippets,
|
|
72
|
-
tests=tests,
|
|
73
|
-
notes=notes,
|
|
359
|
+
architecture_section = _section(
|
|
360
|
+
"Architecture", output.architecture_summary or ""
|
|
74
361
|
)
|
|
75
362
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
363
|
+
relevant_files_section = "\n".join(
|
|
364
|
+
f"- `{f.path}` [{f.role}] — {f.reason}"
|
|
365
|
+
for f in output.relevant_files
|
|
366
|
+
) or "No relevant files identified."
|
|
367
|
+
|
|
368
|
+
deps_lines = [
|
|
369
|
+
f"- {d['name']} "
|
|
370
|
+
f"{d.get('declared_version') or d.get('resolved_version') or '(unknown)'}"
|
|
371
|
+
for d in output.key_dependencies[:10]
|
|
372
|
+
]
|
|
373
|
+
dependencies_section = _section("Key Dependencies", "\n".join(deps_lines))
|
|
374
|
+
|
|
375
|
+
suspected_areas_section = _section(
|
|
376
|
+
"Suspected Problem Areas",
|
|
377
|
+
"\n".join(f"- {a}" for a in output.suspected_areas),
|
|
378
|
+
)
|
|
79
379
|
|
|
80
|
-
|
|
81
|
-
""
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
380
|
+
code_notes_section = ""
|
|
381
|
+
if output.code_notes_summary and output.code_notes_summary.get("total", 0) > 0:
|
|
382
|
+
by_kind = output.code_notes_summary.get("by_kind", {})
|
|
383
|
+
kinds_str = ", ".join(f"{k}: {v}" for k, v in by_kind.items() if v > 0)
|
|
384
|
+
top_files = ", ".join(output.code_notes_summary.get("top_files", [])[:5])
|
|
385
|
+
code_notes_section = _section(
|
|
386
|
+
"Code Annotations",
|
|
387
|
+
f"Total: {output.code_notes_summary['total']} ({kinds_str})\n"
|
|
388
|
+
f"Most annotated: {top_files}",
|
|
389
|
+
)
|
|
390
|
+
|
|
391
|
+
improvement_opportunities_section = _section(
|
|
392
|
+
"Improvement Opportunities",
|
|
393
|
+
"\n".join(f"- {o}" for o in output.improvement_opportunities),
|
|
394
|
+
)
|
|
85
395
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
396
|
+
test_gaps_section = _section(
|
|
397
|
+
"Untested Files",
|
|
398
|
+
"\n".join(f"- `{p}`" for p in output.test_gaps),
|
|
399
|
+
)
|
|
400
|
+
|
|
401
|
+
return spec.prompt_template.format(
|
|
402
|
+
project_summary=project_summary,
|
|
403
|
+
architecture_section=architecture_section,
|
|
404
|
+
relevant_files_section=relevant_files_section,
|
|
405
|
+
dependencies_section=dependencies_section,
|
|
406
|
+
suspected_areas_section=suspected_areas_section,
|
|
407
|
+
code_notes_section=code_notes_section,
|
|
408
|
+
improvement_opportunities_section=improvement_opportunities_section,
|
|
409
|
+
test_gaps_section=test_gaps_section,
|
|
410
|
+
).strip()
|
|
93
411
|
|
|
94
|
-
|
|
412
|
+
# ── Helpers ───────────────────────────────────────────────────────────────
|
|
95
413
|
|
|
96
|
-
def
|
|
414
|
+
def _rank_files(
|
|
97
415
|
self,
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
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():
|
|
416
|
+
task_name: str,
|
|
417
|
+
spec: TaskSpec,
|
|
418
|
+
all_paths: list[str],
|
|
419
|
+
entry_set: set[str],
|
|
420
|
+
test_set: set[str],
|
|
421
|
+
) -> list[RelevantFile]:
|
|
422
|
+
scored: list[tuple[float, RelevantFile]] = []
|
|
423
|
+
|
|
424
|
+
for path in all_paths:
|
|
425
|
+
if Path(path).suffix.lower() not in _ALL_EXTENSIONS:
|
|
426
|
+
continue
|
|
427
|
+
if any(pen in path for pen in spec.ranking_penalties):
|
|
136
428
|
continue
|
|
137
429
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
except Exception:
|
|
430
|
+
is_test = path in test_set
|
|
431
|
+
if is_test and task_name != "generate-tests":
|
|
141
432
|
continue
|
|
142
433
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
)
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
if
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
return notes
|
|
434
|
+
score = 0.0
|
|
435
|
+
reasons: list[str] = []
|
|
436
|
+
|
|
437
|
+
if path in entry_set:
|
|
438
|
+
score += 3.0
|
|
439
|
+
reasons.append("entry point")
|
|
440
|
+
|
|
441
|
+
path_lower = path.lower()
|
|
442
|
+
for keyword in spec.ranking_boosts:
|
|
443
|
+
if keyword in path_lower:
|
|
444
|
+
score += 1.5
|
|
445
|
+
reasons.append(f"matches '{keyword}'")
|
|
446
|
+
break
|
|
447
|
+
|
|
448
|
+
if is_test:
|
|
449
|
+
score += 2.0
|
|
450
|
+
reasons.append("existing test")
|
|
451
|
+
elif self._is_source(path):
|
|
452
|
+
score += 0.5
|
|
453
|
+
if not reasons:
|
|
454
|
+
reasons.append("source file")
|
|
455
|
+
|
|
456
|
+
if score <= 0:
|
|
457
|
+
continue
|
|
458
|
+
|
|
459
|
+
role = (
|
|
460
|
+
"entrypoint" if path in entry_set
|
|
461
|
+
else ("test" if is_test else "source")
|
|
462
|
+
)
|
|
463
|
+
scored.append((score, RelevantFile(
|
|
464
|
+
path=path,
|
|
465
|
+
role=role,
|
|
466
|
+
score=round(score, 1),
|
|
467
|
+
reason=", ".join(reasons) if reasons else "source file",
|
|
468
|
+
)))
|
|
469
|
+
|
|
470
|
+
scored.sort(key=lambda x: -x[0])
|
|
471
|
+
return [f for _, f in scored[:15]]
|
|
472
|
+
|
|
473
|
+
def _is_test(self, path: str) -> bool:
|
|
474
|
+
name = Path(path).name.lower()
|
|
475
|
+
return (
|
|
476
|
+
name.startswith("test_")
|
|
477
|
+
or name.endswith("_test.py")
|
|
478
|
+
or name.endswith(".test.ts")
|
|
479
|
+
or name.endswith(".spec.ts")
|
|
480
|
+
or "/tests/" in path
|
|
481
|
+
or "/test/" in path
|
|
482
|
+
or "/spec/" in path
|
|
483
|
+
)
|
|
484
|
+
|
|
485
|
+
def _is_source(self, path: str) -> bool:
|
|
486
|
+
return Path(path).suffix.lower() in _SOURCE_EXTENSIONS
|
sourcecode/semantic_analyzer.py
CHANGED
|
@@ -70,12 +70,14 @@ class SemanticAnalyzer:
|
|
|
70
70
|
|
|
71
71
|
def __init__(
|
|
72
72
|
self,
|
|
73
|
+
root: Optional[Path] = None,
|
|
73
74
|
*,
|
|
74
75
|
max_files: int = _MAX_FILES,
|
|
75
76
|
max_file_size: int = _MAX_FILE_SIZE,
|
|
76
77
|
max_calls: int = _MAX_CALLS,
|
|
77
78
|
max_symbols: int = _MAX_SYMBOLS,
|
|
78
79
|
) -> None:
|
|
80
|
+
self.root = root
|
|
79
81
|
self.max_files = max_files
|
|
80
82
|
self.max_file_size = max_file_size
|
|
81
83
|
self.max_calls = max_calls
|
sourcecode/serializer.py
CHANGED
|
@@ -54,27 +54,31 @@ def to_yaml(sm: SourceMap) -> str:
|
|
|
54
54
|
return stream.getvalue()
|
|
55
55
|
|
|
56
56
|
|
|
57
|
-
def compact_view(sm: SourceMap) -> dict[str, Any]:
|
|
57
|
+
def compact_view(sm: SourceMap, *, no_tree: bool = False) -> dict[str, Any]:
|
|
58
58
|
"""Proyeccion compacta del SourceMap (~500-700 tokens).
|
|
59
59
|
|
|
60
60
|
Incluye: schema_version, project_type, stacks, entry_points,
|
|
61
61
|
project_summary (siempre), architecture_summary (siempre),
|
|
62
|
-
dependency_summary (cuando requested=True),
|
|
63
|
-
file_tree_depth1 (
|
|
62
|
+
dependency_summary + key_dependencies (cuando requested=True),
|
|
63
|
+
file_tree_depth1 (omitido si no_tree=True).
|
|
64
64
|
|
|
65
65
|
Excluye: dependencies (lista larga), docs, module_graph.
|
|
66
66
|
"""
|
|
67
|
-
depth1: dict[str, Any] =
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
67
|
+
depth1: dict[str, Any] | None = None
|
|
68
|
+
if not no_tree:
|
|
69
|
+
depth1 = {}
|
|
70
|
+
for name, value in sm.file_tree.items():
|
|
71
|
+
if isinstance(value, dict):
|
|
72
|
+
depth1[name] = {}
|
|
73
|
+
else:
|
|
74
|
+
depth1[name] = None
|
|
73
75
|
|
|
74
76
|
dep_summary_dict: Any = None
|
|
77
|
+
key_deps: Any = None
|
|
75
78
|
if sm.dependency_summary is not None and sm.dependency_summary.requested:
|
|
76
79
|
dep_summary_dict = asdict(sm.dependency_summary)
|
|
77
80
|
dep_summary_dict.pop("dependencies", None)
|
|
81
|
+
key_deps = [asdict(d) for d in sm.key_dependencies]
|
|
78
82
|
|
|
79
83
|
env_summary_dict: Any = None
|
|
80
84
|
if sm.env_summary is not None and sm.env_summary.requested:
|
|
@@ -84,18 +88,21 @@ def compact_view(sm: SourceMap) -> dict[str, Any]:
|
|
|
84
88
|
if sm.code_notes_summary is not None and sm.code_notes_summary.requested:
|
|
85
89
|
code_notes_summary_dict = asdict(sm.code_notes_summary)
|
|
86
90
|
|
|
87
|
-
|
|
91
|
+
result: dict[str, Any] = {
|
|
88
92
|
"schema_version": sm.metadata.schema_version,
|
|
89
93
|
"project_type": sm.project_type,
|
|
90
94
|
"project_summary": sm.project_summary,
|
|
91
95
|
"architecture_summary": sm.architecture_summary,
|
|
92
96
|
"stacks": [asdict(stack) for stack in sm.stacks],
|
|
93
97
|
"entry_points": [asdict(entry_point) for entry_point in sm.entry_points],
|
|
94
|
-
"file_tree_depth1": depth1,
|
|
95
98
|
"dependency_summary": dep_summary_dict,
|
|
99
|
+
"key_dependencies": key_deps,
|
|
96
100
|
"env_summary": env_summary_dict,
|
|
97
101
|
"code_notes_summary": code_notes_summary_dict,
|
|
98
102
|
}
|
|
103
|
+
if not no_tree:
|
|
104
|
+
result["file_tree_depth1"] = depth1
|
|
105
|
+
return result
|
|
99
106
|
|
|
100
107
|
|
|
101
108
|
def normalize_source_map(sm: SourceMap) -> SourceMap:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: sourcecode
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.21.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
|
|
@@ -81,7 +81,7 @@ sourcecode --version
|
|
|
81
81
|
|
|
82
82
|
## What It Detects
|
|
83
83
|
|
|
84
|
-
- Stacks: Node.js, Python, Go, Rust, Java, PHP, Ruby, and Dart.
|
|
84
|
+
- Stacks: Node.js, Python, Go, Rust, Java (Maven `pom.xml` and Gradle `build.gradle`/`build.gradle.kts`), PHP, Ruby, and Dart.
|
|
85
85
|
- Frameworks associated with each stack when enough signals are present.
|
|
86
86
|
- `project_type`: `webapp`, `api`, `library`, `cli`, `fullstack`, `monorepo`, or `unknown`.
|
|
87
87
|
- Relevant `entry_points`, such as `main.py`, `cmd/api/main.go`, or `app/page.tsx`.
|
|
@@ -111,6 +111,8 @@ sourcecode --version
|
|
|
111
111
|
| `--env-map` | off | Map all environment variables referenced in source code: key name, required/optional status, inferred type (`string`, `int`, `bool`, `url`, `path`, `enum`), functional category (`database`, `auth`, `cache`, `storage`, `service`, `observability`, `feature_flag`, `server`, `general`), default value when present, and source file locations. Supplements with descriptions from `.env.example`, `.env.sample`, and similar reference files. |
|
|
112
112
|
| `--code-notes` | off | Extract inline code annotations — `TODO`, `FIXME`, `HACK`, `NOTE`, `DEPRECATED`, `WARNING`, `XXX`, `BUG`, `OPTIMIZE` — with file path, line number, annotation text, and the nearest enclosing function or class. Also detects Architecture Decision Records (ADRs) in `docs/decisions/`, `docs/adr/`, `adr/`, and similar directories, extracting title, status, and summary. |
|
|
113
113
|
| `--depth INTEGER` | `4` | Maximum file tree depth. Range: 1–20. |
|
|
114
|
+
| `--no-tree` | off | Suppress `file_tree`, `file_paths` (and `file_tree_depth1` in `--compact`) from the output. Ideal with `--dependencies` on large projects to get dependency data without a multi-thousand-line file tree. |
|
|
115
|
+
| `--agent` | off | Agent mode: auto-selects `--compact --dependencies --env-map --code-notes` and adds `--no-tree` automatically for Java/Gradle projects or large repositories. Prints selected flags to stderr. Can be combined with other flags (e.g. `--agent --git-context`). |
|
|
114
116
|
| `--no-redact` | off | Disable secret redaction (enabled by default). |
|
|
115
117
|
| `--version` | — | Show version and exit. |
|
|
116
118
|
|
|
@@ -123,8 +125,8 @@ The full schema (`SourceMap`) includes the following fields:
|
|
|
123
125
|
| Field | Type | Description |
|
|
124
126
|
|-------|------|-------------|
|
|
125
127
|
| `metadata` | object | Schema version, timestamp, `sourcecode` version, and analyzed path. |
|
|
126
|
-
| `file_tree` | object | Repository tree where `null` represents a file and `{}` represents a directory. |
|
|
127
|
-
| `file_paths` | array | Flat list of all project paths derived from `file_tree`, with forward-slash separators.
|
|
128
|
+
| `file_tree` | object | Repository tree where `null` represents a file and `{}` represents a directory. Suppressed when `--no-tree` is active. |
|
|
129
|
+
| `file_paths` | array | Flat list of all project paths derived from `file_tree`, with forward-slash separators. Respects `--depth`. Suppressed when `--no-tree` is active. |
|
|
128
130
|
| `project_summary` | string\|null | Deterministic natural-language description of the project. Includes: manifest/README description when available, detected architecture pattern (`layered`, `mvc`, `hexagonal`, `fullstack`), business domain names inferred from directory structure, entry points (when no domains are detected), and dependency count. Present when stacks are detected. |
|
|
129
131
|
| `architecture_summary` | string\|null | Static summary of the main execution flow, orchestrated modules, and output produced by the project. Present when enough structural evidence is available. |
|
|
130
132
|
| `stacks` | array | Stack detections with confidence, frameworks, manifests, `primary`, `root`, `workspace`, and `signals`. |
|
|
@@ -223,7 +225,7 @@ ADR detection looks for Markdown files in `docs/decisions/`, `docs/adr/`, `adr/`
|
|
|
223
225
|
|
|
224
226
|
## Compact Mode
|
|
225
227
|
|
|
226
|
-
`--compact` returns a reduced JSON view optimized for LLM prompts
|
|
228
|
+
`--compact` returns a reduced JSON view optimized for LLM prompts. It excludes the full `dependencies` list, `docs`, and `module_graph`, while retaining the fields most useful for project orientation. When optional flags are also active, their summaries are included: `dependency_summary` + `key_dependencies` (with `--dependencies`), `env_summary` (with `--env-map`), and `code_notes_summary` (with `--code-notes`). Adding `--no-tree` further removes `file_tree_depth1`, bringing the output to ~500-800 tokens depending on how many flags are active.
|
|
227
229
|
|
|
228
230
|
Real output from a Python FastAPI project:
|
|
229
231
|
|
|
@@ -266,7 +268,7 @@ Real output from a Python FastAPI project:
|
|
|
266
268
|
}
|
|
267
269
|
```
|
|
268
270
|
|
|
269
|
-
When `--compact --dependencies` is used, `dependency_summary` is populated
|
|
271
|
+
When `--compact --dependencies` is used, `dependency_summary` is populated and `key_dependencies` lists the top-15 direct dependencies with resolved versions. Add `--no-tree` to drop `file_tree_depth1` for the smallest possible context footprint.
|
|
270
272
|
|
|
271
273
|
## Docs Mode
|
|
272
274
|
|
|
@@ -444,15 +446,27 @@ In a monorepo, each stack includes its own `root` and `workspace`, and one of th
|
|
|
444
446
|
|
|
445
447
|
Different modes optimize for different tradeoffs between context size and depth of information.
|
|
446
448
|
|
|
447
|
-
**`--
|
|
449
|
+
**`--agent` — zero-config mode for AI pipelines**
|
|
450
|
+
|
|
451
|
+
Best for: running `sourcecode` from inside an agent without knowing the project in advance. Auto-selects the right flag combination based on detected stack and project size.
|
|
452
|
+
|
|
453
|
+
```bash
|
|
454
|
+
sourcecode --agent .
|
|
455
|
+
sourcecode --agent --git-context . # add git context on top of auto-selected flags
|
|
456
|
+
```
|
|
457
|
+
|
|
458
|
+
On a Java/Gradle project the output is: `--compact --dependencies --env-map --code-notes --no-tree` (~800 tokens). On a Node/Python project: same without `--no-tree` (~900 tokens including depth-1 tree). Selected flags are always printed to stderr so the agent can log them.
|
|
459
|
+
|
|
460
|
+
**`--compact` — minimal context**
|
|
448
461
|
|
|
449
462
|
Best for: initial orientation, deciding what to explore next, fast handoffs between agents.
|
|
450
463
|
|
|
451
|
-
Includes: `project_summary
|
|
464
|
+
Includes: `project_summary`, `architecture_summary`, `stacks`, `entry_points`, `file_tree_depth1`, and — when the respective flags are active — `dependency_summary` + `key_dependencies`, `env_summary`, `code_notes_summary`.
|
|
452
465
|
|
|
453
466
|
```bash
|
|
454
467
|
sourcecode --compact .
|
|
455
|
-
sourcecode --compact --dependencies .
|
|
468
|
+
sourcecode --compact --dependencies . # + key dependencies with versions
|
|
469
|
+
sourcecode --compact --dependencies --no-tree . # smallest footprint: no file tree
|
|
456
470
|
```
|
|
457
471
|
|
|
458
472
|
**Full output — deep analysis**
|
|
@@ -622,6 +636,75 @@ Combine flags for a comprehensive project handoff:
|
|
|
622
636
|
sourcecode --compact --env-map --code-notes --git-context .
|
|
623
637
|
```
|
|
624
638
|
|
|
639
|
+
## `prepare-context` — Task-aware context for LLMs
|
|
640
|
+
|
|
641
|
+
`prepare-context` is a subcommand that builds a focused, task-specific context optimized for LLM reasoning. Instead of a full project dump, it returns only the data an LLM needs for a specific goal.
|
|
642
|
+
|
|
643
|
+
> **Note:** Because `sourcecode` has a positional `PATH` argument, you must provide it explicitly before the subcommand:
|
|
644
|
+
> ```bash
|
|
645
|
+
> sourcecode . prepare-context <task> # current directory
|
|
646
|
+
> sourcecode /my/project prepare-context <task>
|
|
647
|
+
> ```
|
|
648
|
+
|
|
649
|
+
### Available tasks
|
|
650
|
+
|
|
651
|
+
| Task | Goal | Output |
|
|
652
|
+
|------|------|--------|
|
|
653
|
+
| `explain` | Onboard an LLM to the project | `project_summary`, `architecture_summary`, `relevant_files`, `key_dependencies` |
|
|
654
|
+
| `fix-bug` | Identify likely bug locations | `relevant_files` (ranked by risk), `suspected_areas`, `code_notes_summary` |
|
|
655
|
+
| `refactor` | Surface improvement opportunities | `relevant_files`, `improvement_opportunities`, `architecture_summary` |
|
|
656
|
+
| `generate-tests` | Find untested areas | `test_gaps`, `relevant_files` (source without tests), `key_dependencies` |
|
|
657
|
+
|
|
658
|
+
### Usage
|
|
659
|
+
|
|
660
|
+
```bash
|
|
661
|
+
# List tasks with descriptions
|
|
662
|
+
sourcecode . prepare-context --task-help
|
|
663
|
+
|
|
664
|
+
# Explain the project
|
|
665
|
+
sourcecode . prepare-context explain
|
|
666
|
+
|
|
667
|
+
# Find bug areas, with a ready-to-paste LLM prompt
|
|
668
|
+
sourcecode . prepare-context fix-bug --llm-prompt
|
|
669
|
+
|
|
670
|
+
# Find untested files in a specific project
|
|
671
|
+
sourcecode . prepare-context generate-tests --path /my/project
|
|
672
|
+
|
|
673
|
+
# Preview what will be analyzed (no analysis run)
|
|
674
|
+
sourcecode . prepare-context refactor --dry-run
|
|
675
|
+
```
|
|
676
|
+
|
|
677
|
+
### Output format
|
|
678
|
+
|
|
679
|
+
```json
|
|
680
|
+
{
|
|
681
|
+
"task": "fix-bug",
|
|
682
|
+
"goal": "Identify the most likely files and areas where a bug may be located.",
|
|
683
|
+
"project_summary": "CLI en Python (Typer). Entry points: src/cli.py. 4 dependencias.",
|
|
684
|
+
"architecture_summary": null,
|
|
685
|
+
"relevant_files": [
|
|
686
|
+
{ "path": "src/handler.py", "role": "source", "score": 2.0, "reason": "matches 'handler'" },
|
|
687
|
+
{ "path": "src/cli.py", "role": "entrypoint", "score": 3.0, "reason": "entry point" }
|
|
688
|
+
],
|
|
689
|
+
"suspected_areas": ["src/handler.py (2 annotations)", "src/parser.py (1 annotation)"],
|
|
690
|
+
"code_notes_summary": { "total": 5, "by_kind": { "FIXME": 3, "BUG": 2 }, "top_files": ["src/handler.py"] }
|
|
691
|
+
}
|
|
692
|
+
```
|
|
693
|
+
|
|
694
|
+
### `--llm-prompt`
|
|
695
|
+
|
|
696
|
+
Add `--llm-prompt` to include a ready-to-use prompt that you can paste directly into any LLM:
|
|
697
|
+
|
|
698
|
+
```bash
|
|
699
|
+
sourcecode . prepare-context explain --llm-prompt | jq -r '.llm_prompt'
|
|
700
|
+
```
|
|
701
|
+
|
|
702
|
+
The prompt is task-specific and includes the project context, relevant files, and concrete instructions for the LLM.
|
|
703
|
+
|
|
704
|
+
### Task auto-selection with `--agent`
|
|
705
|
+
|
|
706
|
+
For zero-config usage, `--agent` on the main command automatically selects the right flags for the project type. For task-aware context, use `prepare-context` explicitly with the task that matches your goal.
|
|
707
|
+
|
|
625
708
|
## Development
|
|
626
709
|
|
|
627
710
|
Editable install with development dependencies:
|
|
@@ -1,22 +1,22 @@
|
|
|
1
|
-
sourcecode/__init__.py,sha256=
|
|
1
|
+
sourcecode/__init__.py,sha256=Wl0aL5XpGhZsWRJOan0FtriAIMa21IHgxlCXLjVcxOI,100
|
|
2
2
|
sourcecode/architecture_analyzer.py,sha256=zxgqeqn2FhIUDn06X-whjGKiXbrHFkRlzZcr2zKd0Gw,16874
|
|
3
3
|
sourcecode/architecture_summary.py,sha256=r9IciS9z-7h8xpiME7DKBLhLV36xzXtSzbZ1cRDWklo,11709
|
|
4
4
|
sourcecode/classifier.py,sha256=fcp2wIq198wq3Rsh_bDmZIK_W5UI6FEL5oqm1RrQfpA,6846
|
|
5
|
-
sourcecode/cli.py,sha256=
|
|
5
|
+
sourcecode/cli.py,sha256=1ozxlC43rd2boOZbARb3l2csVpTFiBw8DRDzCtLywTI,29065
|
|
6
6
|
sourcecode/code_notes_analyzer.py,sha256=rRd8bFYV0krjlxxQV0wenwE9K7pVpUQSR7KvSvUQKw4,9226
|
|
7
7
|
sourcecode/coverage_parser.py,sha256=q0LeZJaX1bnntLu-ImksdBsMlpsVmk_iUfSaB4eaJGo,19702
|
|
8
|
-
sourcecode/dependency_analyzer.py,sha256=
|
|
8
|
+
sourcecode/dependency_analyzer.py,sha256=2TYBIpenhfWf_TVBZZfq3T0SHFv8xCKFIyn1XnFHG6s,48146
|
|
9
9
|
sourcecode/doc_analyzer.py,sha256=Ec3orx6vBKsh5cNM3-F4y2Got2KuKx8w3dErwtdtM-A,19891
|
|
10
10
|
sourcecode/env_analyzer.py,sha256=JZxBOuIxnM0xw0IaJFL6LiPJBErL848L3XoTOHGBqZI,13554
|
|
11
11
|
sourcecode/git_analyzer.py,sha256=S9PGt8RDasBQYQUsZh9-H_KWVlPvzwR4jgM7TKN9ZCk,7643
|
|
12
12
|
sourcecode/graph_analyzer.py,sha256=cxl7Xb88aGxIVOCsatZJAE1CtgXGzIbJfqytV-9kkFs,45172
|
|
13
13
|
sourcecode/metrics_analyzer.py,sha256=4uh11v-Q0gdrN87BOxuFWUym3N3AOkOuy21K5N8peB8,20126
|
|
14
|
-
sourcecode/prepare_context.py,sha256=
|
|
14
|
+
sourcecode/prepare_context.py,sha256=yPRohYZ3wvX156GQSo1vYuoCDBIG3uy3G-3DdNbGHKU,19271
|
|
15
15
|
sourcecode/redactor.py,sha256=xuGcadGEHaPw4qZXlMDvzMCsr4VOkdp3oBQptHyJk8c,2884
|
|
16
16
|
sourcecode/scanner.py,sha256=Ga4jDRAOhkcwlRmlE2ko6cE39qKy0aI4iE1lGBWmAlk,6410
|
|
17
17
|
sourcecode/schema.py,sha256=dnNdnQxJtBv0PRVWi6-xnpr16dj6IsUGbso9PeeEolA,15743
|
|
18
|
-
sourcecode/semantic_analyzer.py,sha256=
|
|
19
|
-
sourcecode/serializer.py,sha256=
|
|
18
|
+
sourcecode/semantic_analyzer.py,sha256=asQfJf-EhzYaOTA-iMuZsrVXtbW7SV2WEKCxgsxa88Y,79413
|
|
19
|
+
sourcecode/serializer.py,sha256=ck0gK3PV-rY5UrAvykKh8x56Lb1135J23xzbcEsvUW8,13772
|
|
20
20
|
sourcecode/summarizer.py,sha256=2Yz5xJ1bhtJLlrUpKcd74wijypRRo9GJmGT8u971EW0,11572
|
|
21
21
|
sourcecode/tree_utils.py,sha256=xla45Whq24j6U9kEgTztJXkMpmOEDieAulKxP3nl89s,935
|
|
22
22
|
sourcecode/workspace.py,sha256=fQlVoNx8S-fSHpKoJ0JBvEHCFkxszH0KZVJed1i3TRk,6845
|
|
@@ -39,7 +39,7 @@ sourcecode/detectors/rust.py,sha256=V23NO6Y8NsrhrUlGC2feUEcNK63hf3gc1y3jI4tes1Q,
|
|
|
39
39
|
sourcecode/detectors/systems.py,sha256=nYaKbGDFu0EOXFcd_1doWFT3tTUdkbxc2DjHUF5TcqQ,1627
|
|
40
40
|
sourcecode/detectors/terraform.py,sha256=cxORPR_zVLOJpHlh4e9JnFpkQsn_UnqMMom5yG65hZ4,1693
|
|
41
41
|
sourcecode/detectors/tooling.py,sha256=hIvop80No22pqyGVJ32NKliSdjkHRePQkIRroqG01bY,1875
|
|
42
|
-
sourcecode-0.
|
|
43
|
-
sourcecode-0.
|
|
44
|
-
sourcecode-0.
|
|
45
|
-
sourcecode-0.
|
|
42
|
+
sourcecode-0.21.0.dist-info/METADATA,sha256=9gqiONC_cEbjYwoYox60Dx-2m8qzP3KwkNRqU8kis88,30326
|
|
43
|
+
sourcecode-0.21.0.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
|
|
44
|
+
sourcecode-0.21.0.dist-info/entry_points.txt,sha256=voDtdlGU2-jR3wCZpQoOls_0eUKaDVCKIfajGHFDATs,50
|
|
45
|
+
sourcecode-0.21.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|