sourcecode 0.1.1__py3-none-any.whl → 0.2.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.1.0"
3
+ __version__ = "0.2.0"
sourcecode/classifier.py CHANGED
@@ -9,6 +9,7 @@ from sourcecode.schema import EntryPoint, StackDetection
9
9
  from sourcecode.tree_utils import flatten_file_tree
10
10
 
11
11
  _API_FRAMEWORKS = {
12
+ "ASP.NET Core",
12
13
  "FastAPI",
13
14
  "Django",
14
15
  "Flask",
@@ -22,9 +23,12 @@ _API_FRAMEWORKS = {
22
23
  "Quarkus",
23
24
  "Laravel",
24
25
  "Symfony",
26
+ "Ktor",
27
+ "Play",
25
28
  }
26
- _WEB_FRAMEWORKS = {"Next.js", "React", "Vue", "Svelte", "Vite", "Flutter"}
29
+ _WEB_FRAMEWORKS = {"Next.js", "React", "Vue", "Svelte", "Vite", "Flutter", "Phoenix"}
27
30
  _CLI_FRAMEWORKS = {"Typer", "Cobra", "Clap"}
31
+ _API_STACKS = {"python", "go", "java", "php", "ruby", "dotnet", "kotlin", "scala"}
28
32
  ConfidenceLevel = Literal["high", "medium", "low"]
29
33
 
30
34
 
@@ -113,8 +117,10 @@ class TypeClassifier:
113
117
 
114
118
  if stack_names:
115
119
  single = next(iter(stack_names))
116
- if single in {"python", "go", "java", "php", "ruby"} and framework_names & _API_FRAMEWORKS:
120
+ if single in _API_STACKS and framework_names & _API_FRAMEWORKS:
117
121
  return "api"
122
+ if single in {"cpp", "dotnet"} and any(entry.kind == "cli" for entry in entry_points):
123
+ return "cli"
118
124
 
119
125
  return "unknown" if stacks else None
120
126
 
@@ -125,7 +131,7 @@ class TypeClassifier:
125
131
  frameworks = {framework.name for framework in stack.frameworks}
126
132
  if frameworks & _WEB_FRAMEWORKS or stack.stack == "nodejs":
127
133
  has_web = True
128
- if frameworks & _API_FRAMEWORKS or stack.stack in {"python", "go", "java", "php", "ruby"}:
134
+ if frameworks & _API_FRAMEWORKS or stack.stack in _API_STACKS:
129
135
  has_api = True
130
136
  return has_web and has_api
131
137
 
@@ -139,9 +145,9 @@ class TypeClassifier:
139
145
  score = {"low": 0, "medium": 1, "high": 2}.get(stack.confidence, 0)
140
146
  manifest_weight = 1 if stack.manifests else 0
141
147
  priority = 0
142
- if project_type in {"webapp", "fullstack"} and stack.stack == "nodejs" or project_type == "api" and stack.stack in {"python", "go", "java", "php", "ruby"} or project_type == "cli" and any(
148
+ if project_type in {"webapp", "fullstack"} and stack.stack in {"nodejs", "elixir"} or project_type == "api" and stack.stack in _API_STACKS or project_type == "cli" and any(
143
149
  framework.name in _CLI_FRAMEWORKS for framework in stack.frameworks
144
- ):
150
+ ) or project_type == "cli" and stack.stack in {"cpp", "dotnet"}:
145
151
  priority = 3
146
152
  return (priority, score, manifest_weight)
147
153
 
@@ -157,6 +163,12 @@ class TypeClassifier:
157
163
  "php": (".php",),
158
164
  "ruby": (".rb",),
159
165
  "dart": (".dart",),
166
+ "dotnet": (".cs", ".fs"),
167
+ "elixir": (".ex", ".exs"),
168
+ "kotlin": (".kt", ".kts"),
169
+ "scala": (".scala",),
170
+ "terraform": (".tf",),
171
+ "cpp": (".c", ".cc", ".cpp", ".cxx", ".hpp", ".h"),
160
172
  }.get(stack, ())
161
173
  return sum(1 for path in flatten_file_tree(file_tree) if path.endswith(extensions))
162
174
 
@@ -8,15 +8,20 @@ from sourcecode.detectors.base import (
8
8
  StackDetection,
9
9
  )
10
10
  from sourcecode.detectors.dart import DartDetector
11
+ from sourcecode.detectors.dotnet import DotnetDetector
12
+ from sourcecode.detectors.elixir import ElixirDetector
11
13
  from sourcecode.detectors.go import GoDetector
12
14
  from sourcecode.detectors.heuristic import HeuristicDetector
13
15
  from sourcecode.detectors.java import JavaDetector
16
+ from sourcecode.detectors.jvm_ext import JvmExtDetector
14
17
  from sourcecode.detectors.nodejs import NodejsDetector
15
18
  from sourcecode.detectors.php import PhpDetector
16
19
  from sourcecode.detectors.project import ProjectDetector
17
20
  from sourcecode.detectors.python import PythonDetector
18
21
  from sourcecode.detectors.ruby import RubyDetector
19
22
  from sourcecode.detectors.rust import RustDetector
23
+ from sourcecode.detectors.systems import SystemsDetector
24
+ from sourcecode.detectors.terraform import TerraformDetector
20
25
 
21
26
 
22
27
  def build_default_detectors() -> list[AbstractDetector]:
@@ -27,9 +32,14 @@ def build_default_detectors() -> list[AbstractDetector]:
27
32
  GoDetector(),
28
33
  RustDetector(),
29
34
  JavaDetector(),
35
+ DotnetDetector(),
36
+ ElixirDetector(),
37
+ JvmExtDetector(),
30
38
  PhpDetector(),
31
39
  RubyDetector(),
32
40
  DartDetector(),
41
+ TerraformDetector(),
42
+ SystemsDetector(),
33
43
  HeuristicDetector(),
34
44
  ]
35
45
 
@@ -39,11 +49,14 @@ __all__ = [
39
49
  "build_default_detectors",
40
50
  "DartDetector",
41
51
  "DetectionContext",
52
+ "DotnetDetector",
53
+ "ElixirDetector",
42
54
  "EntryPoint",
43
55
  "FrameworkDetection",
44
56
  "GoDetector",
45
57
  "HeuristicDetector",
46
58
  "JavaDetector",
59
+ "JvmExtDetector",
47
60
  "NodejsDetector",
48
61
  "PhpDetector",
49
62
  "PythonDetector",
@@ -51,4 +64,6 @@ __all__ = [
51
64
  "RubyDetector",
52
65
  "RustDetector",
53
66
  "StackDetection",
67
+ "SystemsDetector",
68
+ "TerraformDetector",
54
69
  ]
@@ -0,0 +1,58 @@
1
+ """Detector de proyectos .NET / C#."""
2
+ from __future__ import annotations
3
+
4
+ from sourcecode.detectors.base import (
5
+ AbstractDetector,
6
+ DetectionContext,
7
+ EntryPoint,
8
+ StackDetection,
9
+ )
10
+ from sourcecode.detectors.parsers import read_text_lines, unique_strings
11
+ from sourcecode.schema import FrameworkDetection
12
+ from sourcecode.tree_utils import flatten_file_tree, path_exists_in_tree
13
+
14
+
15
+ class DotnetDetector(AbstractDetector):
16
+ name = "dotnet"
17
+ priority = 32
18
+
19
+ def can_detect(self, context: DetectionContext) -> bool:
20
+ return any(
21
+ path.endswith((".csproj", ".sln"))
22
+ for path in flatten_file_tree(context.file_tree)
23
+ )
24
+
25
+ def detect(self, context: DetectionContext) -> tuple[list[StackDetection], list[EntryPoint]]:
26
+ manifests = [
27
+ path
28
+ for path in flatten_file_tree(context.file_tree)
29
+ if path.endswith((".csproj", ".sln"))
30
+ ]
31
+ content = "\n".join(
32
+ "\n".join(read_text_lines(context.root / manifest))
33
+ for manifest in manifests
34
+ ).lower()
35
+
36
+ frameworks: list[FrameworkDetection] = []
37
+ if "microsoft.net.sdk.web" in content or "microsoft.aspnetcore" in content:
38
+ frameworks.append(FrameworkDetection(name="ASP.NET Core", source="manifest"))
39
+
40
+ entry_points: list[EntryPoint] = []
41
+ for path in unique_strings(
42
+ ["Program.cs", "src/Program.cs", "Program.fs", "src/Program.fs"]
43
+ ):
44
+ if path_exists_in_tree(context.file_tree, path):
45
+ kind = "api" if frameworks else "cli"
46
+ entry_points.append(
47
+ EntryPoint(path=path, stack="dotnet", kind=kind, source="manifest")
48
+ )
49
+
50
+ stack = StackDetection(
51
+ stack="dotnet",
52
+ detection_method="manifest",
53
+ confidence="high",
54
+ frameworks=frameworks,
55
+ package_manager="nuget",
56
+ manifests=manifests,
57
+ )
58
+ return [stack], entry_points
@@ -0,0 +1,49 @@
1
+ """Detector de proyectos Elixir / Phoenix."""
2
+ from __future__ import annotations
3
+
4
+ from sourcecode.detectors.base import (
5
+ AbstractDetector,
6
+ DetectionContext,
7
+ EntryPoint,
8
+ StackDetection,
9
+ )
10
+ from sourcecode.detectors.parsers import read_text_lines, unique_strings
11
+ from sourcecode.schema import FrameworkDetection
12
+ from sourcecode.tree_utils import flatten_file_tree, path_exists_in_tree
13
+
14
+
15
+ class ElixirDetector(AbstractDetector):
16
+ name = "elixir"
17
+ priority = 33
18
+
19
+ def can_detect(self, context: DetectionContext) -> bool:
20
+ return path_exists_in_tree(context.file_tree, "mix.exs")
21
+
22
+ def detect(self, context: DetectionContext) -> tuple[list[StackDetection], list[EntryPoint]]:
23
+ content = "\n".join(read_text_lines(context.root / "mix.exs")).lower()
24
+ frameworks: list[FrameworkDetection] = []
25
+ if "phoenix" in content:
26
+ frameworks.append(FrameworkDetection(name="Phoenix", source="mix.exs"))
27
+
28
+ candidates = [
29
+ path
30
+ for path in flatten_file_tree(context.file_tree)
31
+ if path.endswith("/application.ex")
32
+ ]
33
+ entry_points: list[EntryPoint] = []
34
+ for path in unique_strings(candidates + ["mix.exs"]):
35
+ if path_exists_in_tree(context.file_tree, path):
36
+ kind = "web" if frameworks else "app"
37
+ entry_points.append(
38
+ EntryPoint(path=path, stack="elixir", kind=kind, source="mix.exs")
39
+ )
40
+
41
+ stack = StackDetection(
42
+ stack="elixir",
43
+ detection_method="manifest",
44
+ confidence="high",
45
+ frameworks=frameworks,
46
+ package_manager="mix",
47
+ manifests=["mix.exs"],
48
+ )
49
+ return [stack], entry_points
@@ -0,0 +1,69 @@
1
+ """Detector JVM ampliado para Kotlin y Scala."""
2
+ from __future__ import annotations
3
+
4
+ from sourcecode.detectors.base import (
5
+ AbstractDetector,
6
+ DetectionContext,
7
+ EntryPoint,
8
+ StackDetection,
9
+ )
10
+ from sourcecode.detectors.parsers import read_text_lines, unique_strings
11
+ from sourcecode.schema import FrameworkDetection
12
+ from sourcecode.tree_utils import flatten_file_tree, path_exists_in_tree
13
+
14
+
15
+ class JvmExtDetector(AbstractDetector):
16
+ name = "jvm_ext"
17
+ priority = 36
18
+
19
+ def can_detect(self, context: DetectionContext) -> bool:
20
+ flat_paths = flatten_file_tree(context.file_tree)
21
+ return any(
22
+ path in {"build.gradle.kts", "settings.gradle.kts", "build.sbt"}
23
+ for path in flat_paths
24
+ )
25
+
26
+ def detect(self, context: DetectionContext) -> tuple[list[StackDetection], list[EntryPoint]]:
27
+ flat_paths = flatten_file_tree(context.file_tree)
28
+ manifests = [
29
+ path
30
+ for path in flat_paths
31
+ if path in {"build.gradle.kts", "settings.gradle.kts", "build.sbt"}
32
+ ]
33
+ content = "\n".join(
34
+ "\n".join(read_text_lines(context.root / manifest))
35
+ for manifest in manifests
36
+ ).lower()
37
+
38
+ stack_name = "scala" if "build.sbt" in manifests or any(path.endswith(".scala") for path in flat_paths) else "kotlin"
39
+ frameworks: list[FrameworkDetection] = []
40
+ if "spring-boot" in content:
41
+ frameworks.append(FrameworkDetection(name="Spring Boot", source="manifest"))
42
+ if "ktor" in content:
43
+ frameworks.append(FrameworkDetection(name="Ktor", source="manifest"))
44
+ if "play" in content and stack_name == "scala":
45
+ frameworks.append(FrameworkDetection(name="Play", source="manifest"))
46
+
47
+ entry_candidates = [
48
+ "src/main/kotlin/Application.kt",
49
+ "src/main/kotlin/Main.kt",
50
+ "src/main/scala/Main.scala",
51
+ "src/main/scala/Application.scala",
52
+ ]
53
+ entry_points: list[EntryPoint] = []
54
+ for path in unique_strings(entry_candidates):
55
+ if path_exists_in_tree(context.file_tree, path):
56
+ kind = "api" if frameworks else "app"
57
+ entry_points.append(
58
+ EntryPoint(path=path, stack=stack_name, kind=kind, source="manifest")
59
+ )
60
+
61
+ stack = StackDetection(
62
+ stack=stack_name,
63
+ detection_method="manifest",
64
+ confidence="high",
65
+ frameworks=frameworks,
66
+ package_manager="gradle" if stack_name == "kotlin" else "sbt",
67
+ manifests=manifests,
68
+ )
69
+ return [stack], entry_points
@@ -65,6 +65,7 @@ class NodejsDetector(AbstractDetector):
65
65
 
66
66
  def _detect_package_manager(self, context: DetectionContext) -> str | None:
67
67
  for filename, package_manager in (
68
+ ("bun.lockb", "bun"),
68
69
  ("pnpm-lock.yaml", "pnpm"),
69
70
  ("package-lock.json", "npm"),
70
71
  ("yarn.lock", "yarn"),
@@ -7,6 +7,7 @@ from typing import Any
7
7
 
8
8
  from sourcecode.classifier import TypeClassifier
9
9
  from sourcecode.detectors.base import AbstractDetector, DetectionContext
10
+ from sourcecode.detectors.tooling import collect_tooling_signals, infer_package_manager
10
11
  from sourcecode.schema import EntryPoint, FrameworkDetection, StackDetection
11
12
 
12
13
  _CONFIDENCE_RANK = {"low": 0, "medium": 1, "high": 2}
@@ -45,6 +46,12 @@ class ProjectDetector:
45
46
  copied = self._copy_stack(stack)
46
47
  if copied.root is None:
47
48
  copied.root = "."
49
+ copied.signals = self._merge_values(
50
+ copied.signals,
51
+ collect_tooling_signals(file_tree),
52
+ )
53
+ if copied.package_manager is None:
54
+ copied.package_manager = infer_package_manager(copied.stack, file_tree)
48
55
  merged_stacks[stack.stack] = copied
49
56
  continue
50
57
  merged_stacks[stack.stack] = self._merge_stack(existing, stack)
@@ -93,6 +100,7 @@ class ProjectDetector:
93
100
  def _merge_stack(self, current: StackDetection, incoming: StackDetection) -> StackDetection:
94
101
  current.frameworks = self._merge_frameworks(current.frameworks, incoming.frameworks)
95
102
  current.manifests = self._merge_manifests(current.manifests, incoming.manifests)
103
+ current.signals = self._merge_values(current.signals, incoming.signals)
96
104
  if incoming.package_manager and not current.package_manager:
97
105
  current.package_manager = incoming.package_manager
98
106
  if self._confidence_rank(incoming.confidence) > self._confidence_rank(current.confidence):
@@ -117,6 +125,9 @@ class ProjectDetector:
117
125
  return list(merged.values())
118
126
 
119
127
  def _merge_manifests(self, current: Iterable[str], incoming: Iterable[str]) -> list[str]:
128
+ return self._merge_values(current, incoming)
129
+
130
+ def _merge_values(self, current: Iterable[str], incoming: Iterable[str]) -> list[str]:
120
131
  merged: list[str] = []
121
132
  seen: set[str] = set()
122
133
  for manifest in list(current) + list(incoming):
@@ -28,8 +28,19 @@ class PythonDetector(AbstractDetector):
28
28
  priority = 30
29
29
 
30
30
  def can_detect(self, context: DetectionContext) -> bool:
31
- supported = {"pyproject.toml", "requirements.txt", "setup.py", "Pipfile", "uv.lock"}
32
- return any(manifest in supported for manifest in context.manifests)
31
+ supported = {
32
+ "pyproject.toml",
33
+ "requirements.txt",
34
+ "setup.py",
35
+ "Pipfile",
36
+ "Pipfile.lock",
37
+ "uv.lock",
38
+ "poetry.lock",
39
+ }
40
+ return any(manifest in supported for manifest in context.manifests) or any(
41
+ path_exists_in_tree(context.file_tree, path)
42
+ for path in ("poetry.lock", "Pipfile.lock")
43
+ )
33
44
 
34
45
  def detect(self, context: DetectionContext) -> tuple[list[StackDetection], list[EntryPoint]]:
35
46
  dependencies = self._collect_dependencies(context)
@@ -42,15 +53,23 @@ class PythonDetector(AbstractDetector):
42
53
 
43
54
  manifests = [
44
55
  manifest
45
- for manifest in ("pyproject.toml", "requirements.txt", "setup.py", "Pipfile", "uv.lock")
46
- if manifest in context.manifests
56
+ for manifest in (
57
+ "pyproject.toml",
58
+ "requirements.txt",
59
+ "setup.py",
60
+ "Pipfile",
61
+ "Pipfile.lock",
62
+ "uv.lock",
63
+ "poetry.lock",
64
+ )
65
+ if manifest in context.manifests or path_exists_in_tree(context.file_tree, manifest)
47
66
  ]
48
67
  stack = StackDetection(
49
68
  stack="python",
50
69
  detection_method="manifest",
51
70
  confidence="high" if "pyproject.toml" in manifests else "medium",
52
71
  frameworks=frameworks,
53
- package_manager="pip",
72
+ package_manager=self._detect_package_manager(context, pyproject=load_toml_file(context.root / "pyproject.toml")),
54
73
  manifests=manifests,
55
74
  )
56
75
  return [stack], entry_points
@@ -67,6 +86,22 @@ class PythonDetector(AbstractDetector):
67
86
  for group in optional.values():
68
87
  if isinstance(group, list):
69
88
  deps.update(self._normalize_requirement(dep) for dep in group)
89
+ tool = pyproject.get("tool", {})
90
+ if isinstance(tool, dict):
91
+ poetry = tool.get("poetry", {})
92
+ if isinstance(poetry, dict):
93
+ deps.update(
94
+ self._normalize_requirement(dep_name)
95
+ for dep_name in poetry.get("dependencies", {})
96
+ )
97
+ groups = poetry.get("group", {})
98
+ if isinstance(groups, dict):
99
+ for group in groups.values():
100
+ if isinstance(group, dict):
101
+ deps.update(
102
+ self._normalize_requirement(dep_name)
103
+ for dep_name in group.get("dependencies", {})
104
+ )
70
105
 
71
106
  deps.update(self._read_requirements(context.root / "requirements.txt"))
72
107
  deps.update(self._read_requirements(context.root / "Pipfile"))
@@ -118,3 +153,20 @@ class PythonDetector(AbstractDetector):
118
153
  EntryPoint(path=path, stack="python", kind=kind, source="manifest")
119
154
  )
120
155
  return entry_points
156
+
157
+ def _detect_package_manager(
158
+ self, context: DetectionContext, *, pyproject: dict[str, Any] | None
159
+ ) -> str:
160
+ if path_exists_in_tree(context.file_tree, "poetry.lock"):
161
+ return "poetry"
162
+ if path_exists_in_tree(context.file_tree, "Pipfile.lock") or path_exists_in_tree(
163
+ context.file_tree, "Pipfile"
164
+ ):
165
+ return "pipenv"
166
+ if path_exists_in_tree(context.file_tree, "uv.lock"):
167
+ return "uv"
168
+ if pyproject:
169
+ tool = pyproject.get("tool", {})
170
+ if isinstance(tool, dict) and "poetry" in tool:
171
+ return "poetry"
172
+ return "pip"
@@ -0,0 +1,41 @@
1
+ """Detector de proyectos C / C++ y build systems asociados."""
2
+ from __future__ import annotations
3
+
4
+ from sourcecode.detectors.base import (
5
+ AbstractDetector,
6
+ DetectionContext,
7
+ EntryPoint,
8
+ StackDetection,
9
+ )
10
+ from sourcecode.detectors.parsers import unique_strings
11
+ from sourcecode.tree_utils import flatten_file_tree, path_exists_in_tree
12
+
13
+
14
+ class SystemsDetector(AbstractDetector):
15
+ name = "systems"
16
+ priority = 45
17
+
18
+ def can_detect(self, context: DetectionContext) -> bool:
19
+ flat_paths = flatten_file_tree(context.file_tree)
20
+ has_code = any(path.endswith((".c", ".cc", ".cpp", ".cxx", ".hpp", ".h")) for path in flat_paths)
21
+ has_build = any(path in {"CMakeLists.txt", "Makefile", "meson.build"} for path in flat_paths)
22
+ return has_code and has_build
23
+
24
+ def detect(self, context: DetectionContext) -> tuple[list[StackDetection], list[EntryPoint]]:
25
+ flat_paths = flatten_file_tree(context.file_tree)
26
+ manifests = [path for path in flat_paths if path in {"CMakeLists.txt", "Makefile", "meson.build"}]
27
+ entry_points: list[EntryPoint] = []
28
+ for path in unique_strings(["main.c", "main.cpp", "src/main.c", "src/main.cpp"]):
29
+ if path_exists_in_tree(context.file_tree, path):
30
+ entry_points.append(
31
+ EntryPoint(path=path, stack="cpp", kind="cli", source="manifest")
32
+ )
33
+
34
+ stack = StackDetection(
35
+ stack="cpp",
36
+ detection_method="manifest",
37
+ confidence="medium",
38
+ package_manager="cmake" if "CMakeLists.txt" in manifests else None,
39
+ manifests=manifests,
40
+ )
41
+ return [stack], entry_points
@@ -0,0 +1,47 @@
1
+ """Detector de proyectos Terraform."""
2
+ from __future__ import annotations
3
+
4
+ from sourcecode.detectors.base import (
5
+ AbstractDetector,
6
+ DetectionContext,
7
+ EntryPoint,
8
+ StackDetection,
9
+ )
10
+ from sourcecode.detectors.parsers import read_text_lines
11
+ from sourcecode.tree_utils import flatten_file_tree, path_exists_in_tree
12
+
13
+
14
+ class TerraformDetector(AbstractDetector):
15
+ name = "terraform"
16
+ priority = 40
17
+
18
+ def can_detect(self, context: DetectionContext) -> bool:
19
+ return any(path.endswith(".tf") for path in flatten_file_tree(context.file_tree))
20
+
21
+ def detect(self, context: DetectionContext) -> tuple[list[StackDetection], list[EntryPoint]]:
22
+ manifests = [path for path in flatten_file_tree(context.file_tree) if path.endswith(".tf")]
23
+ signals: list[str] = []
24
+ for manifest in manifests:
25
+ content = "\n".join(read_text_lines(context.root / manifest)).lower()
26
+ if 'provider "aws"' in content:
27
+ signals.append("provider:aws")
28
+ if 'provider "azurerm"' in content:
29
+ signals.append("provider:azure")
30
+ if 'provider "google"' in content:
31
+ signals.append("provider:gcp")
32
+
33
+ entry_points: list[EntryPoint] = []
34
+ if path_exists_in_tree(context.file_tree, "main.tf"):
35
+ entry_points.append(
36
+ EntryPoint(path="main.tf", stack="terraform", kind="infra", source="manifest")
37
+ )
38
+
39
+ stack = StackDetection(
40
+ stack="terraform",
41
+ detection_method="manifest",
42
+ confidence="medium",
43
+ package_manager="terraform",
44
+ manifests=manifests,
45
+ signals=signals,
46
+ )
47
+ return [stack], entry_points
@@ -0,0 +1,53 @@
1
+ """Senales universales de tooling y helpers transversales."""
2
+ from __future__ import annotations
3
+
4
+ from sourcecode.tree_utils import flatten_file_tree
5
+
6
+
7
+ def collect_tooling_signals(file_tree: dict[str, object]) -> list[str]:
8
+ """Recolecta senales operativas reutilizables sin crear stacks por si solas."""
9
+ flat_paths = set(flatten_file_tree(file_tree))
10
+ signals: list[str] = []
11
+ for path, label in (
12
+ ("Dockerfile", "tooling:docker"),
13
+ ("docker-compose.yml", "tooling:docker-compose"),
14
+ ("docker-compose.yaml", "tooling:docker-compose"),
15
+ ("Taskfile.yml", "tooling:taskfile"),
16
+ ("Taskfile.yaml", "tooling:taskfile"),
17
+ ("justfile", "tooling:just"),
18
+ ("Procfile", "tooling:procfile"),
19
+ ("Makefile", "tooling:make"),
20
+ ):
21
+ if path in flat_paths:
22
+ signals.append(label)
23
+ return signals
24
+
25
+
26
+ def infer_package_manager(stack: str, file_tree: dict[str, object]) -> str | None:
27
+ """Infiera package manager por lockfiles o tooling conocido."""
28
+ flat_paths = set(flatten_file_tree(file_tree))
29
+ if stack == "nodejs":
30
+ for path, manager in (
31
+ ("bun.lockb", "bun"),
32
+ ("pnpm-lock.yaml", "pnpm"),
33
+ ("package-lock.json", "npm"),
34
+ ("yarn.lock", "yarn"),
35
+ ):
36
+ if path in flat_paths:
37
+ return manager
38
+ if stack == "python":
39
+ for path, manager in (
40
+ ("poetry.lock", "poetry"),
41
+ ("Pipfile.lock", "pipenv"),
42
+ ("uv.lock", "uv"),
43
+ ("requirements.txt", "pip"),
44
+ ):
45
+ if path in flat_paths:
46
+ return manager
47
+ if stack == "php" and "composer.lock" in flat_paths:
48
+ return "composer"
49
+ if stack == "ruby" and "Gemfile.lock" in flat_paths:
50
+ return "bundler"
51
+ if stack == "terraform" and any(path.endswith(".tf") for path in flat_paths):
52
+ return "terraform"
53
+ return None
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sourcecode
3
- Version: 0.1.1
3
+ Version: 0.2.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,5 +1,5 @@
1
- sourcecode/__init__.py,sha256=zFh8ACgMMXbAd5f8rHwMQHBQ6irzFx0iQ1NkQc34ff8,99
2
- sourcecode/classifier.py,sha256=GgJE9DjsMkXgK8DIprr1cfa_FiQBZsd_4ZxzyGYw1b8,6405
1
+ sourcecode/__init__.py,sha256=kSF53Kglqc3H1-xnzSg40MVrMtrK-Fq-mxBVtQDGJig,99
2
+ sourcecode/classifier.py,sha256=7ubGTn5vfw9z-9eosLIYDtQUu7d8dvB4XKKDxysUVSM,6910
3
3
  sourcecode/cli.py,sha256=sf1nf-BmPWzscFvoScNkKIlfU10wepOgTfFE-lf4NO4,6962
4
4
  sourcecode/redactor.py,sha256=abSf5jH_IzYzpjF3lEN6hCwdXxvmyHs-3DHz9fd8Mic,2883
5
5
  sourcecode/scanner.py,sha256=TvcuD77m_NF8OlTj3XH1b_jO0qrto8c90fND96ecqb0,6182
@@ -7,20 +7,26 @@ sourcecode/schema.py,sha256=E2_EnuYWHWZOLarxENvL5S66QBkZIpyV1BzKetCJWIY,2487
7
7
  sourcecode/serializer.py,sha256=hezTYOAAvZ2yPn7Te7Eu42ajbMhVCsUkuUZtqx4wjAs,3085
8
8
  sourcecode/tree_utils.py,sha256=5WHQc2L-AGqxKFfYYcQftE6hETxgUxP9MWDuaThcAUw,991
9
9
  sourcecode/workspace.py,sha256=1L4xH38gU89fLeMYSuF3ft7lSdCyTfMfJ9NQt5q4Ric,6904
10
- sourcecode/detectors/__init__.py,sha256=RMSr1E3uIK7MvfAQdVo4lfkcDptW3tBzLPFZwQju6C4,1455
10
+ sourcecode/detectors/__init__.py,sha256=A0AACJFF6HWf_RgatNtWu3PUzstcKtIGM9f1PoFcJug,1987
11
11
  sourcecode/detectors/base.py,sha256=mLqDNil8VkFRNbzwO2wPbmw_aaVg3WR_WzzRjU58xbU,1070
12
12
  sourcecode/detectors/dart.py,sha256=77KDgBY-xkbzLjs0Emw6c0NuQY-tQZR5PCHa9r0t2OE,1476
13
+ sourcecode/detectors/dotnet.py,sha256=BofpMPVOJmiUpCFpkd0y7zSzzBj4jeXZkZR5AksBg8w,2017
14
+ sourcecode/detectors/elixir.py,sha256=Aurq99BvJvZ1k9lNBiI-RGOJzi8e7maQd225IG128PE,1759
13
15
  sourcecode/detectors/go.py,sha256=oifvbih6i4yrUO2zQs3kVXEdirr6jVriiV1fJ_-NF2I,1748
14
16
  sourcecode/detectors/heuristic.py,sha256=Phimturkvi5ZwNdt005TizYTrllZk1pqHpfXOkFZJbc,1943
15
17
  sourcecode/detectors/java.py,sha256=XdONBlcBb0Vo-2hvG77Fw00X41d2KlHFCPXJCuzwAl0,3547
16
- sourcecode/detectors/nodejs.py,sha256=2LDNoqrQcOm_ne66PKdIDBJR_7XEFTfB42Luhz2IoEg,3885
18
+ sourcecode/detectors/jvm_ext.py,sha256=ViHKwUzlKXjpjNNB6_wK0XptR29VUsdLobW2U-mS5BM,2665
19
+ sourcecode/detectors/nodejs.py,sha256=rfUG2oT5SsV5nMCuXY4v99IrAWF0lL5i47m9RHOM2mM,3919
17
20
  sourcecode/detectors/parsers.py,sha256=CQhhFGk8lSXruIfLe571-1HOId3W0ssnY2ioHMGtOrg,2338
18
21
  sourcecode/detectors/php.py,sha256=6l5pFlUuKrrC6XlWPSgYTEnnPm3hoFxMM4DRAnjL4nM,1920
19
- sourcecode/detectors/project.py,sha256=Fu3N_5ZKtv5b0F7i8Om36k_SZKvm24nRURkHyLogp6Q,5379
20
- sourcecode/detectors/python.py,sha256=DMrlKXapsaRu8jVmO-w-FfnFVXTmQnmpQTaV2PBvIZA,4875
22
+ sourcecode/detectors/project.py,sha256=QOTt2p55Wt6FNUSo0_1P7iojM_CsVevvQqHQmLqVxYk,6022
23
+ sourcecode/detectors/python.py,sha256=UVB5M_B8t-zQkSrIqmNQR5KD7RqDHzT8C3CcazgxEi8,6900
21
24
  sourcecode/detectors/ruby.py,sha256=3_Hd5YqlErpTtyTRcUTPnMp1TF92hT-TnYYeclLbF1c,1625
22
25
  sourcecode/detectors/rust.py,sha256=iUz9lE4HfBy8CBaDwGhfh6kLsNIovrH2ay2v3y6qNDc,2298
23
- sourcecode-0.1.1.dist-info/METADATA,sha256=YqiDeYoUkKdnYy8NWiJgGLBiiCKta1P8FP2qWTO0pBg,4086
24
- sourcecode-0.1.1.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
25
- sourcecode-0.1.1.dist-info/entry_points.txt,sha256=voDtdlGU2-jR3wCZpQoOls_0eUKaDVCKIfajGHFDATs,50
26
- sourcecode-0.1.1.dist-info/RECORD,,
26
+ sourcecode/detectors/systems.py,sha256=qf5M9tio1H6XDorSstTWIxKe0xVLR_QGTJpFGyhDz4A,1690
27
+ sourcecode/detectors/terraform.py,sha256=5EOKQtTViWo7u3tFK0h45YD8GQPwhrww0cOicxGPPn4,1732
28
+ sourcecode/detectors/tooling.py,sha256=wPvudLCJZ8_cr9GIx44C8jkLAlEN9uTUZcJsP7eVYnQ,1937
29
+ sourcecode-0.2.0.dist-info/METADATA,sha256=kfVit3IrZnWnPAtbKcLjMK3SXCfHbNlSP8JrxB6zI0o,4086
30
+ sourcecode-0.2.0.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
31
+ sourcecode-0.2.0.dist-info/entry_points.txt,sha256=voDtdlGU2-jR3wCZpQoOls_0eUKaDVCKIfajGHFDATs,50
32
+ sourcecode-0.2.0.dist-info/RECORD,,