codecortex 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.

Potentially problematic release.


This version of codecortex might be problematic. Click here for more details.

@@ -0,0 +1,197 @@
1
+ """First-run setup — turns doctor's diagnosis into optional, consent-gated action.
2
+
3
+ Every side effect (install uv, install deps, index, warm lsp) is gated by an explicit flag —
4
+ the flag IS the consent, no interactive prompt. Never-raise and bounded: pip installs and
5
+ indexing carry timeouts; warming lsp reuses the one deep `doctor.run_doctor` boot rather than
6
+ booting serena twice. Stdout stays clean for a future --json; progress goes to `out` (stderr).
7
+ """
8
+ from __future__ import annotations
9
+
10
+ import os
11
+ import subprocess
12
+ import sys
13
+ import threading
14
+
15
+ from codeintel import doctor
16
+
17
+ _ENGINES = ("graph", "lsp", "semantic")
18
+
19
+
20
+ def _guidance_for(engine: str, probe: dict) -> str:
21
+ """One-line install instructions for an engine that is not installed."""
22
+ try:
23
+ if engine == "graph":
24
+ return ("install codebase-memory-mcp (a platform-specific binary) and ensure "
25
+ "it's on PATH — see its project; then `codeintel index`")
26
+ if engine == "lsp":
27
+ return "install uv (provides uvx): pip install uv — serena is fetched on first use"
28
+ if engine == "semantic":
29
+ return "pip install fastembed sqlite-vec (or: pip install -e .)"
30
+ return str(probe.get("remediation") or "engine unavailable")
31
+ except Exception:
32
+ return "engine unavailable"
33
+
34
+
35
+ def _pip_install(pkg_args: list[str], *, timeout_s: float = 300.0, out=sys.stderr) -> dict:
36
+ """Run ``pip install <pkg_args>`` against THIS interpreter. Never raises; bounded."""
37
+ try:
38
+ print(f" installing: pip install {' '.join(pkg_args)}", file=out)
39
+ proc = subprocess.run(
40
+ [sys.executable, "-m", "pip", "install", *pkg_args],
41
+ capture_output=True, timeout=timeout_s, text=True,
42
+ )
43
+ if proc.returncode == 0:
44
+ return {"ok": True, "detail": f"installed: {' '.join(pkg_args)}"}
45
+ tail = (proc.stderr or proc.stdout or "").strip().splitlines()
46
+ return {"ok": False, "detail": tail[-1] if tail else f"pip exited {proc.returncode}"}
47
+ except subprocess.TimeoutExpired:
48
+ return {"ok": False, "detail": f"pip install timed out after {timeout_s:.0f}s"}
49
+ except Exception as exc:
50
+ return {"ok": False, "detail": f"pip install failed ({type(exc).__name__})"}
51
+
52
+
53
+ def _bounded_index(project_root: str, *, timeout_s: float, out) -> dict:
54
+ """Semantic-index on a joined daemon thread — never blocks past ``timeout_s``."""
55
+ try:
56
+ from codeintel.config import load_config
57
+ from codeintel.indexer import Indexer
58
+ from codeintel.semantic_db import SemanticDb, default_db_path
59
+ except Exception as exc:
60
+ return {"status": "fail", "chunks": 0, "detail": f"semantic deps unavailable ({type(exc).__name__})"}
61
+ outcome: dict = {}
62
+
63
+ def _work() -> None:
64
+ try:
65
+ cfg = load_config(project_root)
66
+ db_path = default_db_path()
67
+ os.makedirs(os.path.dirname(db_path), exist_ok=True)
68
+ db = SemanticDb(db_path)
69
+ try:
70
+ db.init()
71
+ outcome["count"] = Indexer(
72
+ db, model_name=str(cfg.get("model") or "BAAI/bge-small-en-v1.5"),
73
+ window=int(cfg.get("window", 20)), stride=int(cfg.get("stride", 10)),
74
+ max_chunks=int(cfg.get("max_chunks", 500)),
75
+ ).index(project_root)
76
+ finally:
77
+ db.close()
78
+ except Exception as exc:
79
+ outcome["error"] = f"{type(exc).__name__}: {exc}"
80
+
81
+ t = threading.Thread(target=_work, daemon=True)
82
+ t.start()
83
+ t.join(timeout_s)
84
+ if t.is_alive():
85
+ # The worker is a daemon thread — it is abandoned when this one-shot process exits, so
86
+ # it is NOT durably "still running". Point the user at `codeintel index` (no timeout).
87
+ return {"status": "timeout", "chunks": 0,
88
+ "detail": f"indexing exceeded {timeout_s:.0f}s and was abandoned — run "
89
+ f"`codeintel index` directly for a large repo (no timeout)"}
90
+ if "error" in outcome:
91
+ return {"status": "fail", "chunks": 0, "detail": outcome["error"]}
92
+ count = outcome.get("count", 0)
93
+ if count < 0:
94
+ return {"status": "fail", "chunks": 0, "detail": "indexer reported an unrecoverable failure"}
95
+ return {"status": "ok", "chunks": count, "detail": f"indexed {count} new chunk(s)"}
96
+
97
+
98
+ def run_setup(
99
+ project_root: str,
100
+ *,
101
+ install_uv: bool = False,
102
+ install_deps: bool = False,
103
+ do_index: bool = False,
104
+ warm_lsp: bool = False,
105
+ index_timeout_s: float = 900.0,
106
+ lsp_warm_timeout_s: float = 90.0,
107
+ out=sys.stderr,
108
+ ) -> dict:
109
+ """Diagnose + (opt-in) fix a repo's codeintel setup. Never raises; each flag IS consent."""
110
+ steps: list[dict] = []
111
+
112
+ def _step(name: str, status: str, detail: str = "") -> None:
113
+ steps.append({"name": name, "status": status, "detail": detail})
114
+
115
+ def _empty_doctor() -> dict:
116
+ return {"ok": False, "project_root": root, "engines": {}, "summary": {"ready": 0, "total": 3, "healthy": False}}
117
+
118
+ try:
119
+ root = os.path.abspath(str(project_root)) if project_root else os.getcwd()
120
+ except Exception:
121
+ root = str(project_root or "")
122
+
123
+ try:
124
+ if warm_lsp:
125
+ print(" first serena launch fetches it via uvx; this can be slow the first time…", file=out)
126
+ try:
127
+ report0 = doctor.run_doctor(root, deep=warm_lsp, lsp_deep_timeout_s=lsp_warm_timeout_s)
128
+ engines0 = report0.get("engines", {}) if isinstance(report0, dict) else {}
129
+ except Exception as exc:
130
+ engines0 = {}
131
+ _step("preflight", "fail", f"doctor check failed ({type(exc).__name__})")
132
+ for name in _ENGINES:
133
+ probe = engines0.get(name) or {}
134
+ if probe.get("installed") is False:
135
+ _step(f"{name}: preflight", probe.get("status", "fail"), _guidance_for(name, probe))
136
+
137
+ for flag, pkg_args, step_name in (
138
+ (install_uv, ["uv"], "install uv"),
139
+ (install_deps, ["-e", "."], "install deps (-e .)"),
140
+ ):
141
+ if flag:
142
+ r = _pip_install(pkg_args, out=out)
143
+ _step(step_name, "ok" if r["ok"] else "fail", r["detail"])
144
+
145
+ if do_index:
146
+ print(" first index downloads the embedding model (~50MB, one-time); this may take a minute…", file=out)
147
+ idx = _bounded_index(root, timeout_s=index_timeout_s, out=out)
148
+ idx_status = {"ok": "ok", "timeout": "warn"}.get(idx.get("status"), "fail")
149
+ _step("index: semantic", idx_status, idx.get("detail", ""))
150
+ try:
151
+ from codeintel.reindexer import Reindexer
152
+ Reindexer()._graph_reindex(root)
153
+ _step("index: graph", "ok", "best-effort graph reindex attempted")
154
+ except Exception as exc:
155
+ _step("index: graph", "warn", f"graph reindex skipped ({type(exc).__name__})")
156
+
157
+ if warm_lsp:
158
+ lsp = engines0.get("lsp") or {}
159
+ _step("warm lsp", lsp.get("status", "warn"), lsp.get("detail", ""))
160
+
161
+ try:
162
+ final_doctor = doctor.run_doctor(root)
163
+ except Exception:
164
+ final_doctor = _empty_doctor()
165
+ return {"ok": True, "project_root": root, "steps": steps, "doctor": final_doctor}
166
+ except Exception as exc:
167
+ return {"ok": False, "project_root": root, "steps": steps, "doctor": _empty_doctor(),
168
+ "detail": f"setup failed ({type(exc).__name__})"}
169
+
170
+
171
+ def render_setup_text(report: dict) -> str:
172
+ """Human CLI view: a ``[n/N]`` step list, the doctor table, then the overall summary."""
173
+ from codeintel.term import c # imported at call time so the CLI's term.configure() is honored
174
+
175
+ try:
176
+ root = report.get("project_root", "")
177
+ steps = report.get("steps") or []
178
+ n = len(steps)
179
+ lines = [c.header("setup", root), ""]
180
+ for i, step in enumerate(steps, start=1):
181
+ detail = step.get("detail")
182
+ tail = f" {c.dim(str(detail))}" if detail else ""
183
+ name = c.bold(str(step.get("name", "")))
184
+ lines.append(f" [{i}/{n}] {c.glyph(step.get('status', 'na'))} {name}{tail}")
185
+ if not steps:
186
+ lines.append(c.dim(" (nothing to do — no opt-in flags set)"))
187
+ lines.append("")
188
+ try:
189
+ from codeintel.doctor import render_doctor_text
190
+ lines.append(render_doctor_text(report.get("doctor") or {}))
191
+ except Exception:
192
+ lines.append(c.dim("(doctor report unavailable)"))
193
+ lines.append("")
194
+ lines.append(c.green("setup finished") if report.get("ok") else c.red("setup did not complete cleanly"))
195
+ return "\n".join(lines)
196
+ except Exception:
197
+ return "codeintel setup — (error rendering report)"
codeintel/policy.py ADDED
@@ -0,0 +1,30 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Dict, List, Optional
4
+
5
+
6
+ class TieringPolicy:
7
+ """Role/op access policy — disabled by default (all ops allowed).
8
+
9
+ When enabled, a role present in *rules* is restricted to only the ops
10
+ listed for it. A role absent from *rules* gets full access regardless.
11
+ Immutable after construction; safe to share across threads.
12
+ """
13
+
14
+ def __init__(
15
+ self,
16
+ enabled: bool = False,
17
+ rules: Optional[Dict[str, List[str]]] = None,
18
+ ) -> None:
19
+ self._enabled = enabled
20
+ self._rules: Dict[str, List[str]] = (
21
+ {role: list(ops) for role, ops in rules.items()} if rules else {}
22
+ )
23
+
24
+ def is_allowed(self, role: str, op: str) -> bool:
25
+ if not self._enabled:
26
+ return True
27
+ allowed_ops = self._rules.get(role)
28
+ if allowed_ops is None:
29
+ return True
30
+ return op in allowed_ops
codeintel/provider.py ADDED
@@ -0,0 +1,52 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Any, Optional, Protocol, runtime_checkable
4
+ from typing_extensions import NotRequired, TypedDict
5
+
6
+
7
+ class Result(TypedDict):
8
+ ok: bool
9
+ op: str
10
+ target: str
11
+ result: Optional[Any]
12
+ engine: str
13
+ cached: bool
14
+ reason: NotRequired[str]
15
+ hint: NotRequired[str]
16
+
17
+
18
+ @runtime_checkable
19
+ class CodeProvider(Protocol):
20
+ """Implementors MUST never raise."""
21
+
22
+ def build_result(
23
+ self,
24
+ op: str,
25
+ target: str,
26
+ files: list[str],
27
+ budget: int,
28
+ project_root: str,
29
+ ) -> Result | None: ...
30
+
31
+
32
+ def safe_null_result(
33
+ op: Any,
34
+ target: Any,
35
+ engine: str = "none",
36
+ reason: str = "no-engine",
37
+ hint: Optional[str] = None,
38
+ ) -> Result:
39
+ r: Result = {
40
+ "ok": True,
41
+ "op": str(op or ""),
42
+ "target": str(target or ""),
43
+ "result": None,
44
+ "engine": engine,
45
+ "cached": False,
46
+ "reason": reason,
47
+ }
48
+ # Optional actionable breadcrumb (e.g. "not indexed → run codeintel index"); emit the key
49
+ # only when set, exactly like `reason`, so envelope-shape tests stay unaffected.
50
+ if hint is not None:
51
+ r["hint"] = hint
52
+ return r
File without changes