klapcontext 0.2.0__tar.gz → 0.2.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. {klapcontext-0.2.0/src/klapcontext.egg-info → klapcontext-0.2.2}/PKG-INFO +1 -1
  2. {klapcontext-0.2.0 → klapcontext-0.2.2}/src/klapcontext/__init__.py +1 -1
  3. {klapcontext-0.2.0 → klapcontext-0.2.2}/src/klapcontext/context_builder.py +1 -1
  4. {klapcontext-0.2.0 → klapcontext-0.2.2}/src/klapcontext/detector.py +23 -2
  5. {klapcontext-0.2.0 → klapcontext-0.2.2}/src/klapcontext/frameworks/laravel.py +4 -1
  6. {klapcontext-0.2.0 → klapcontext-0.2.2}/src/klapcontext/portal.py +5 -5
  7. {klapcontext-0.2.0 → klapcontext-0.2.2/src/klapcontext.egg-info}/PKG-INFO +1 -1
  8. {klapcontext-0.2.0 → klapcontext-0.2.2}/tests/test_klapcontext.py +18 -0
  9. {klapcontext-0.2.0 → klapcontext-0.2.2}/LICENSE +0 -0
  10. {klapcontext-0.2.0 → klapcontext-0.2.2}/README.md +0 -0
  11. {klapcontext-0.2.0 → klapcontext-0.2.2}/pyproject.toml +0 -0
  12. {klapcontext-0.2.0 → klapcontext-0.2.2}/setup.cfg +0 -0
  13. {klapcontext-0.2.0 → klapcontext-0.2.2}/src/klapcontext/agent/__init__.py +0 -0
  14. {klapcontext-0.2.0 → klapcontext-0.2.2}/src/klapcontext/agent/capabilities.py +0 -0
  15. {klapcontext-0.2.0 → klapcontext-0.2.2}/src/klapcontext/agent/compiler.py +0 -0
  16. {klapcontext-0.2.0 → klapcontext-0.2.2}/src/klapcontext/agent/impact.py +0 -0
  17. {klapcontext-0.2.0 → klapcontext-0.2.2}/src/klapcontext/agent/intents.py +0 -0
  18. {klapcontext-0.2.0 → klapcontext-0.2.2}/src/klapcontext/agent/models.py +0 -0
  19. {klapcontext-0.2.0 → klapcontext-0.2.2}/src/klapcontext/agent/planner.py +0 -0
  20. {klapcontext-0.2.0 → klapcontext-0.2.2}/src/klapcontext/agent/verification.py +0 -0
  21. {klapcontext-0.2.0 → klapcontext-0.2.2}/src/klapcontext/agent/workflows.py +0 -0
  22. {klapcontext-0.2.0 → klapcontext-0.2.2}/src/klapcontext/agent_context.py +0 -0
  23. {klapcontext-0.2.0 → klapcontext-0.2.2}/src/klapcontext/cli.py +0 -0
  24. {klapcontext-0.2.0 → klapcontext-0.2.2}/src/klapcontext/evidence.py +0 -0
  25. {klapcontext-0.2.0 → klapcontext-0.2.2}/src/klapcontext/frameworks/__init__.py +0 -0
  26. {klapcontext-0.2.0 → klapcontext-0.2.2}/src/klapcontext/frameworks/base.py +0 -0
  27. {klapcontext-0.2.0 → klapcontext-0.2.2}/src/klapcontext/frameworks/fastapi.py +0 -0
  28. {klapcontext-0.2.0 → klapcontext-0.2.2}/src/klapcontext/frameworks/generic_php.py +0 -0
  29. {klapcontext-0.2.0 → klapcontext-0.2.2}/src/klapcontext/frameworks/node.py +0 -0
  30. {klapcontext-0.2.0 → klapcontext-0.2.2}/src/klapcontext/git.py +0 -0
  31. {klapcontext-0.2.0 → klapcontext-0.2.2}/src/klapcontext/graphify.py +0 -0
  32. {klapcontext-0.2.0 → klapcontext-0.2.2}/src/klapcontext/providers/__init__.py +0 -0
  33. {klapcontext-0.2.0 → klapcontext-0.2.2}/src/klapcontext/providers/base.py +0 -0
  34. {klapcontext-0.2.0 → klapcontext-0.2.2}/src/klapcontext/providers/code_intelligence.py +0 -0
  35. {klapcontext-0.2.0 → klapcontext-0.2.2}/src/klapcontext/providers/graphify.py +0 -0
  36. {klapcontext-0.2.0 → klapcontext-0.2.2}/src/klapcontext/providers/javascript_intelligence.py +0 -0
  37. {klapcontext-0.2.0 → klapcontext-0.2.2}/src/klapcontext/providers/python_intelligence.py +0 -0
  38. {klapcontext-0.2.0 → klapcontext-0.2.2}/src/klapcontext/semantic.py +0 -0
  39. {klapcontext-0.2.0 → klapcontext-0.2.2}/src/klapcontext/system.py +0 -0
  40. {klapcontext-0.2.0 → klapcontext-0.2.2}/src/klapcontext.egg-info/SOURCES.txt +0 -0
  41. {klapcontext-0.2.0 → klapcontext-0.2.2}/src/klapcontext.egg-info/dependency_links.txt +0 -0
  42. {klapcontext-0.2.0 → klapcontext-0.2.2}/src/klapcontext.egg-info/entry_points.txt +0 -0
  43. {klapcontext-0.2.0 → klapcontext-0.2.2}/src/klapcontext.egg-info/requires.txt +0 -0
  44. {klapcontext-0.2.0 → klapcontext-0.2.2}/src/klapcontext.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: klapcontext
3
- Version: 0.2.0
3
+ Version: 0.2.2
4
4
  Summary: Make your repository understandable to humans and AI.
5
5
  Author: Willy Cordon
6
6
  License-Expression: Apache-2.0
@@ -1,3 +1,3 @@
1
1
  """KlapContext local engineering-context generator."""
2
2
 
3
- __version__ = "0.2.0"
3
+ __version__ = "0.2.2"
@@ -86,7 +86,7 @@ def _empty_raw() -> dict:
86
86
 
87
87
 
88
88
  def _semantic(root: Path, stack: dict) -> tuple[SemanticModel, dict]:
89
- if "Laravel" in stack["frameworks"]:
89
+ if "Laravel" in stack["frameworks"] or "Lumen" in stack["frameworks"]:
90
90
  model = LaravelAdapter().analyze(root)
91
91
  return model, model.metadata["raw"]
92
92
  if "FastAPI" in stack["frameworks"]:
@@ -8,16 +8,20 @@ from .evidence import Evidence
8
8
 
9
9
  def detect_project(root: Path) -> tuple[dict, list[Evidence]]:
10
10
  evidence: list[Evidence] = []
11
- stack = {"languages": [], "frameworks": [], "databases": [], "runtime": [], "infrastructure": []}
11
+ stack = {"languages": [], "frameworks": [], "databases": [], "runtime": [], "infrastructure": [], "package_managers": [], "project_type": "unknown"}
12
12
  def add(bucket: str, value: str, path: str, reason: str) -> None:
13
13
  if value not in stack[bucket]: stack[bucket].append(value)
14
14
  evidence.append(Evidence("file", path, reason, "CONFIRMED", 1.0))
15
15
  composer = root / "composer.json"
16
16
  if composer.exists():
17
17
  add("languages", "PHP", "composer.json", "Composer manifest detected")
18
+ stack["runtime"].append("PHP"); stack["package_managers"].append("composer")
18
19
  try: deps = {**json.loads(composer.read_text(encoding="utf-8")).get("require", {}), **json.loads(composer.read_text(encoding="utf-8")).get("require-dev", {})}
19
20
  except json.JSONDecodeError: deps = {}
20
21
  if "laravel/framework" in deps: add("frameworks", "Laravel", "composer.json", "laravel/framework dependency detected")
22
+ if "laravel/lumen-framework" in deps:
23
+ add("frameworks", "Lumen", "composer.json", "laravel/lumen-framework dependency detected")
24
+ stack["project_type"] = "backend-api"
21
25
  elif any(root.rglob("*.php")):
22
26
  # A standalone PHP repository has no Composer manifest to identify it,
23
27
  # but language analysis can still provide a useful generic model.
@@ -26,16 +30,33 @@ def detect_project(root: Path) -> tuple[dict, list[Evidence]]:
26
30
  package = root / "package.json"
27
31
  if package.exists():
28
32
  add("languages", "JavaScript", "package.json", "npm manifest detected")
33
+ stack["runtime"].append("Node.js"); stack["package_managers"].append("npm")
29
34
  try: deps = {**json.loads(package.read_text(encoding="utf-8")).get("dependencies", {}), **json.loads(package.read_text(encoding="utf-8")).get("devDependencies", {})}
30
35
  except json.JSONDecodeError: deps = {}
31
36
  for name, label in (("next", "Next.js"), ("vue", "Vue"), ("react", "React"), ("express", "Express"), ("@nestjs/core", "NestJS")):
32
37
  if name in deps: add("frameworks", label, "package.json", f"{name} dependency detected")
38
+ if any(root.rglob("*.ts")) or any(root.rglob("*.tsx")): add("languages", "TypeScript", "package.json", "TypeScript source detected")
33
39
  if (root / "pyproject.toml").exists() or (root / "requirements.txt").exists():
34
40
  python_manifest = root / "pyproject.toml" if (root / "pyproject.toml").exists() else root / "requirements.txt"
35
41
  add("languages", "Python", python_manifest.name, "Python manifest detected")
42
+ stack["runtime"].append("Python"); stack["package_managers"].append("pip")
36
43
  if "fastapi" in python_manifest.read_text(encoding="utf-8", errors="ignore").casefold(): add("frameworks", "FastAPI", python_manifest.name, "FastAPI dependency detected")
37
44
  if (root / "manage.py").exists(): add("frameworks", "Django", "manage.py", "Django entry point detected")
38
- if list(root.glob("*.csproj")): add("languages", "C#", next(root.glob("*.csproj")).name, ".NET project detected")
45
+ if list(root.glob("*.csproj")):
46
+ add("languages", "C#", next(root.glob("*.csproj")).name, ".NET project detected"); stack["runtime"].append(".NET"); stack["package_managers"].append("NuGet")
47
+ if (root / "Program.cs").exists(): add("frameworks", ".NET Web API", "Program.cs", "ASP.NET entry point detected")
48
+ if (root / "go.mod").exists():
49
+ add("languages", "Go", "go.mod", "Go module detected"); stack["runtime"].append("Go"); stack["package_managers"].append("go modules")
50
+ if (root / "Cargo.toml").exists():
51
+ add("languages", "Rust", "Cargo.toml", "Rust manifest detected"); stack["runtime"].append("Rust"); stack["package_managers"].append("cargo")
52
+ if (root / "pom.xml").exists() or (root / "build.gradle").exists() or (root / "build.gradle.kts").exists():
53
+ manifest = "pom.xml" if (root / "pom.xml").exists() else "build.gradle" if (root / "build.gradle").exists() else "build.gradle.kts"
54
+ add("languages", "Java", manifest, "Java build manifest detected"); stack["runtime"].append("JVM"); stack["package_managers"].append("maven" if manifest == "pom.xml" else "gradle")
55
+ # Keep generic analysis useful for small repositories without a manifest.
56
+ for suffix, language in (("*.py", "Python"), ("*.js", "JavaScript"), ("*.ts", "TypeScript"), ("*.java", "Java"), ("*.go", "Go"), ("*.rs", "Rust")):
57
+ if language not in stack["languages"] and any(root.rglob(suffix)):
58
+ first = next(root.rglob(suffix)); add("languages", language, first.relative_to(root).as_posix(), f"{language} source file detected")
59
+ if any(name in stack["frameworks"] for name in ("Laravel", "Lumen", "FastAPI", "Express", "NestJS", ".NET Web API")): stack["project_type"] = "backend-api"
39
60
  if (root / "Dockerfile").exists(): add("infrastructure", "Docker", "Dockerfile", "Dockerfile detected")
40
61
  if list(root.glob("docker-compose*")) or list(root.glob("compose*")): add("infrastructure", "Docker Compose", "docker-compose", "Compose configuration detected")
41
62
  if (root / ".github" / "workflows").exists(): add("infrastructure", "GitHub Actions", ".github/workflows", "GitHub Actions workflows detected")
@@ -61,7 +61,7 @@ def _routes(root: Path) -> tuple[list[dict], list[dict]]:
61
61
  directory = root / "routes"
62
62
  if not directory.is_dir():
63
63
  return routes, scheduled
64
- route_pattern = re.compile(r"Route::(get|post|put|patch|delete|options|any)\s*\(\s*['\"]([^'\"]+)['\"]\s*,\s*(.+?)\);", re.S | re.I)
64
+ route_pattern = re.compile(r"(?:Route|\$app|\$router)(?:::|->)(get|post|put|patch|delete|options|any)\s*\(\s*['\"]([^'\"]+)['\"]\s*,\s*(.+?)\);", re.S | re.I)
65
65
  resource_pattern = re.compile(r"Route::(?:api)?resource\s*\(\s*['\"]([^'\"]+)['\"]\s*,\s*([A-Za-z_]\w*(?:\\[A-Za-z_]\w*)*)::class", re.I)
66
66
  schedule_pattern = re.compile(r"Schedule::(?:command|job)\s*\(\s*['\"]([^'\"]+)['\"]\s*\).*?->([A-Za-z]\w*)\s*\(", re.S)
67
67
  for file in directory.glob("*.php"):
@@ -69,6 +69,9 @@ def _routes(root: Path) -> tuple[list[dict], list[dict]]:
69
69
  for match in route_pattern.finditer(text):
70
70
  method, uri, target = match.groups()
71
71
  handler = _symbol(target)
72
+ if not handler:
73
+ callback = re.search(r"['\"]([A-Za-z_]\w*)@([A-Za-z_]\w*)['\"]", target)
74
+ handler = f"{callback.group(1)}::{callback.group(2)}" if callback else None
72
75
  name = f"{method.upper()} /{uri.lstrip('/')}"
73
76
  routes.append({"type": "http", "method": method.upper(), "uri": "/" + uri.lstrip("/"), "name": name, "path": str(file.relative_to(root)), "source": str(file.relative_to(root)), "line": text.count("\n", 0, match.start()) + 1, "target": handler, "handler": handler, "middleware": [], "status": "CONFIRMED", "evidence": _e(root, file, match.start(), "Declaración de ruta Laravel", symbol=name)})
74
77
  for match in resource_pattern.finditer(text):
@@ -43,7 +43,7 @@ def system_map(interactions: dict) -> str:
43
43
  if edge.get("source") in points and edge.get("target") in points
44
44
  )
45
45
  boxes = "".join(
46
- f"<g class='map-node'><rect x='{points[node['name']][0] - 65}' y='{points[node['name']][1] - 24}' width='130' height='48' rx='8'/>"
46
+ f"<g class='map-node' tabindex='0' role='button' data-node='{e(node['name'])}' data-kind='{e(node.get('type', 'componente'))}'><rect x='{points[node['name']][0] - 65}' y='{points[node['name']][1] - 24}' width='130' height='48' rx='8'/>"
47
47
  f"<text x='{points[node['name']][0]}' y='{points[node['name']][1] - 2}'>{e(node['name'])[:18]}</text>"
48
48
  f"<text class='map-kind' x='{points[node['name']][0]}' y='{points[node['name']][1] + 13}'>{e(node.get('type', 'componente'))[:18]}</text></g>"
49
49
  for node in nodes
@@ -113,10 +113,10 @@ def render(context: dict, agent_text: str, graphify_files: list[str]) -> str:
113
113
  </section>
114
114
  <section class='map-card' aria-label='Mapa de interacción del sistema'><header><div><span class='icon'>⌘</span><div><h2>Mapa del sistema</h2><p>Componentes, relaciones y puntos de entrada en una sola lectura.</p></div></div><button class='outline' data-go='tecnico'>Explorar evidencia <span>→</span></button></header>{system_map(system.get('system_interactions', {}))}</section>
115
115
  <section class='quick-grid'>
116
- <article><b>{len(points)}</b><span>Puntos de entrada</span><small>CLI, HTTP, archivos o scheduler</small></article>
117
- <article><b>{len(flows)}</b><span>Flujos principales</span><small>Procesos y secuencias detectadas</small></article>
118
- <article><b>{len(evidence)}</b><span>Hechos confirmados</span><small>Con respaldo en el código</small></article>
119
- <article class='attention'><b>{len(unknowns)}</b><span>Incertidumbres</span><small>Aspectos pendientes de validar</small></article>
116
+ <article data-go='entradas'><b>{len(points)}</b><span>Puntos de entrada</span><small>CLI, HTTP, archivos o scheduler</small></article>
117
+ <article data-go='flujos'><b>{len(flows)}</b><span>Flujos principales</span><small>Procesos y secuencias detectadas</small></article>
118
+ <article data-go='evidencia'><b>{len(evidence)}</b><span>Hechos confirmados</span><small>Con respaldo en el código</small></article>
119
+ <article class='attention' data-go='evidencia'><b>{len(unknowns)}</b><span>Pendientes de validar</span><small>Aspectos con evidencia insuficiente</small></article>
120
120
  </section>
121
121
  <section class='dashboard-bottom'><div class='compact-panel'><header><h2>Cómo se ejecuta</h2><button class='text-button' data-go='entradas'>Ver entradas →</button></header><div class='surfaces'>{surface_cards}</div></div>
122
122
  <div class='compact-panel'><header><h2>Lectura recomendada</h2><button class='text-button' data-go='flujos'>Ver flujos →</button></header><ul class='start-list'>{start_rows}</ul></div></section>
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: klapcontext
3
- Version: 0.2.0
3
+ Version: 0.2.2
4
4
  Summary: Make your repository understandable to humans and AI.
5
5
  Author: Willy Cordon
6
6
  License-Expression: Apache-2.0
@@ -37,6 +37,22 @@ def test_detector_collects_confirmed_evidence(tmp_path):
37
37
  assert all(item.status == "CONFIRMED" for item in evidence)
38
38
 
39
39
 
40
+ def test_detector_identifies_manifest_and_source_languages(tmp_path):
41
+ (tmp_path / "go.mod").write_text("module example")
42
+ (tmp_path / "src").mkdir(); (tmp_path / "src" / "main.ts").write_text("export {}")
43
+ stack, _ = detect_project(tmp_path)
44
+ assert {"Go", "TypeScript"} <= set(stack["languages"])
45
+ assert "go modules" in stack["package_managers"]
46
+
47
+
48
+ def test_lumen_detection_and_route_adapter():
49
+ root = Path(__file__).parent / "fixtures" / "lumen_semantic"
50
+ stack, _ = detect_project(root)
51
+ assert "Lumen" in stack["frameworks"]
52
+ context = build(root, {"nodes": []})
53
+ assert context["system_model"]["routes"][0]["name"] == "POST /api/v1/call-vtex"
54
+
55
+
40
56
  def test_portal_has_dashboard_and_real_views(tmp_path):
41
57
  context = build(fixture_repo(tmp_path), {"nodes": [{"name": "UserController", "path": "app/Http/UserController.php", "type": "controller"}]})
42
58
  page = render_portal(context, render_agent(context), ["graph.html"])
@@ -45,6 +61,8 @@ def test_portal_has_dashboard_and_real_views(tmp_path):
45
61
  assert "data-view='flujos'" in page
46
62
  assert "data-go='evidencia'" in page
47
63
  assert "Copiar contexto" in page
64
+ assert "data-go='entradas'" in page
65
+ assert "data-node=" in page or "map-empty" in page
48
66
 
49
67
 
50
68
  def test_exclude_klap_is_idempotent(tmp_path, monkeypatch):
File without changes
File without changes
File without changes
File without changes