lean-lsp-mcp 0.18.0__py3-none-any.whl → 0.19.1__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.
- lean_lsp_mcp/instructions.py +1 -0
- lean_lsp_mcp/models.py +21 -0
- lean_lsp_mcp/profile_utils.py +232 -0
- lean_lsp_mcp/server.py +51 -0
- {lean_lsp_mcp-0.18.0.dist-info → lean_lsp_mcp-0.19.1.dist-info}/METADATA +23 -2
- {lean_lsp_mcp-0.18.0.dist-info → lean_lsp_mcp-0.19.1.dist-info}/RECORD +10 -9
- {lean_lsp_mcp-0.18.0.dist-info → lean_lsp_mcp-0.19.1.dist-info}/WHEEL +0 -0
- {lean_lsp_mcp-0.18.0.dist-info → lean_lsp_mcp-0.19.1.dist-info}/entry_points.txt +0 -0
- {lean_lsp_mcp-0.18.0.dist-info → lean_lsp_mcp-0.19.1.dist-info}/licenses/LICENSE +0 -0
- {lean_lsp_mcp-0.18.0.dist-info → lean_lsp_mcp-0.19.1.dist-info}/top_level.txt +0 -0
lean_lsp_mcp/instructions.py
CHANGED
|
@@ -13,6 +13,7 @@ INSTRUCTIONS = """## General Rules
|
|
|
13
13
|
- **lean_declaration_file**: Get declaration source. Use sparingly (large output).
|
|
14
14
|
- **lean_run_code**: Run standalone snippet. Use rarely.
|
|
15
15
|
- **lean_build**: Rebuild + restart LSP. Only if needed (new imports). SLOW!
|
|
16
|
+
- **lean_profile_proof**: Profile a theorem for performance. Shows tactic hotspots. SLOW!
|
|
16
17
|
|
|
17
18
|
## Search Tools (rate limited)
|
|
18
19
|
- **lean_leansearch** (3/30s): Natural language → mathlib
|
lean_lsp_mcp/models.py
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"""Pydantic models for MCP tool structured outputs."""
|
|
2
2
|
|
|
3
3
|
from typing import List, Optional
|
|
4
|
+
|
|
4
5
|
from pydantic import BaseModel, Field
|
|
5
6
|
|
|
6
7
|
|
|
@@ -210,3 +211,23 @@ class PremiseResults(BaseModel):
|
|
|
210
211
|
items: List[PremiseResult] = Field(
|
|
211
212
|
default_factory=list, description="List of premise results"
|
|
212
213
|
)
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
class LineProfile(BaseModel):
|
|
217
|
+
"""Timing for a single source line."""
|
|
218
|
+
|
|
219
|
+
line: int = Field(description="Source line number (1-indexed)")
|
|
220
|
+
ms: float = Field(description="Time in milliseconds")
|
|
221
|
+
text: str = Field(description="Source line content (truncated)")
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
class ProofProfileResult(BaseModel):
|
|
225
|
+
"""Profiling result for a theorem."""
|
|
226
|
+
|
|
227
|
+
ms: float = Field(description="Total elaboration time in ms")
|
|
228
|
+
lines: List[LineProfile] = Field(
|
|
229
|
+
default_factory=list, description="Time per source line (>1% of total)"
|
|
230
|
+
)
|
|
231
|
+
categories: dict[str, float] = Field(
|
|
232
|
+
default_factory=dict, description="Cumulative time by category in ms"
|
|
233
|
+
)
|
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
"""Lean proof profiling via CLI trace output."""
|
|
2
|
+
|
|
3
|
+
import asyncio
|
|
4
|
+
import os
|
|
5
|
+
import re
|
|
6
|
+
import tempfile
|
|
7
|
+
from collections import defaultdict
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
|
|
10
|
+
from lean_lsp_mcp.models import LineProfile, ProofProfileResult
|
|
11
|
+
|
|
12
|
+
_TRACE_RE = re.compile(r"^(\s*)\[([^\]]+)\]\s+\[([\d.]+)\]\s+(.+)$")
|
|
13
|
+
_CUMULATIVE_RE = re.compile(r"^\s+(\S+(?:\s+\S+)*)\s+([\d.]+)(ms|s)$")
|
|
14
|
+
_DECL_RE = re.compile(r"^\s*(?:private\s+)?(theorem|lemma|def)\s+(\S+)")
|
|
15
|
+
_HEADER_RE = re.compile(r"^(import|open|set_option|universe|variable)\s")
|
|
16
|
+
_SKIP_CATEGORIES = {"import", "initialization", "parsing", "interpretation", "linting"}
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def _find_header_end(lines: list[str]) -> int:
|
|
20
|
+
"""Find where imports/header ends and declarations begin."""
|
|
21
|
+
header_end, in_block = 0, False
|
|
22
|
+
for i, line in enumerate(lines):
|
|
23
|
+
s = line.strip()
|
|
24
|
+
if "/-" in line:
|
|
25
|
+
in_block = True
|
|
26
|
+
if "-/" in line:
|
|
27
|
+
in_block = False
|
|
28
|
+
if in_block or not s or s.startswith("--") or _HEADER_RE.match(line):
|
|
29
|
+
header_end = i + 1
|
|
30
|
+
elif s.startswith(("namespace", "section")):
|
|
31
|
+
header_end = i + 1
|
|
32
|
+
elif _DECL_RE.match(line) or s.startswith(("@[", "private ", "protected ")):
|
|
33
|
+
break
|
|
34
|
+
else:
|
|
35
|
+
header_end = i + 1
|
|
36
|
+
return header_end
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def _find_theorem_end(lines: list[str], start: int) -> int:
|
|
40
|
+
"""Find where theorem ends (next declaration or EOF)."""
|
|
41
|
+
for i in range(start + 1, len(lines)):
|
|
42
|
+
if _DECL_RE.match(lines[i]):
|
|
43
|
+
return i
|
|
44
|
+
return len(lines)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def _extract_theorem_source(lines: list[str], target_line: int) -> tuple[str, str, int]:
|
|
48
|
+
"""Extract imports/header + single theorem. Returns (source, name, theorem_start_in_source)."""
|
|
49
|
+
m = _DECL_RE.match(lines[target_line - 1])
|
|
50
|
+
if not m:
|
|
51
|
+
raise ValueError(f"No theorem/lemma/def at line {target_line}")
|
|
52
|
+
|
|
53
|
+
header_end = _find_header_end(lines)
|
|
54
|
+
theorem_end = _find_theorem_end(lines, target_line - 1)
|
|
55
|
+
|
|
56
|
+
header = "\n".join(lines[:header_end])
|
|
57
|
+
theorem = "\n".join(lines[target_line - 1 : theorem_end])
|
|
58
|
+
return f"{header}\n\n{theorem}\n", m.group(2), header_end + 2
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def _parse_output(
|
|
62
|
+
output: str,
|
|
63
|
+
) -> tuple[list[tuple[int, str, float, str]], dict[str, float]]:
|
|
64
|
+
"""Parse trace output into (traces, cumulative). Traces are (depth, cls, ms, msg)."""
|
|
65
|
+
traces, cumulative, in_cumulative = [], {}, False
|
|
66
|
+
|
|
67
|
+
for line in output.splitlines():
|
|
68
|
+
if "cumulative profiling times:" in line:
|
|
69
|
+
in_cumulative = True
|
|
70
|
+
elif in_cumulative and (m := _CUMULATIVE_RE.match(line)):
|
|
71
|
+
cat, val, unit = m.groups()
|
|
72
|
+
cumulative[cat] = float(val) * (1000 if unit == "s" else 1)
|
|
73
|
+
elif not in_cumulative and (m := _TRACE_RE.match(line)):
|
|
74
|
+
indent, cls, time_s, msg = m.groups()
|
|
75
|
+
traces.append((len(indent) // 2, cls, float(time_s) * 1000, msg))
|
|
76
|
+
|
|
77
|
+
return traces, cumulative
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def _build_proof_items(
|
|
81
|
+
source_lines: list[str], proof_start: int
|
|
82
|
+
) -> list[tuple[int, str, bool]]:
|
|
83
|
+
"""Build list of (line_no, content, is_bullet) for proof lines."""
|
|
84
|
+
items = []
|
|
85
|
+
for i in range(proof_start, len(source_lines)):
|
|
86
|
+
s = source_lines[i].strip()
|
|
87
|
+
if s and not s.startswith("--"):
|
|
88
|
+
is_bullet = s[0] in "·*-"
|
|
89
|
+
items.append((i + 1, s.lstrip("·*- \t"), is_bullet))
|
|
90
|
+
return items
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def _match_line(
|
|
94
|
+
tactic: str, is_bullet: bool, items: list[tuple[int, str, bool]], used: set[int]
|
|
95
|
+
) -> int | None:
|
|
96
|
+
"""Find matching source line for a tactic trace. Returns line number or None."""
|
|
97
|
+
for ln, content, src_bullet in items:
|
|
98
|
+
if ln in used:
|
|
99
|
+
continue
|
|
100
|
+
if is_bullet and src_bullet:
|
|
101
|
+
return ln
|
|
102
|
+
if (
|
|
103
|
+
not is_bullet
|
|
104
|
+
and content
|
|
105
|
+
and (tactic.startswith(content[:25]) or content.startswith(tactic[:25]))
|
|
106
|
+
):
|
|
107
|
+
return ln
|
|
108
|
+
return None
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def _extract_line_times(
|
|
112
|
+
traces: list[tuple[int, str, float, str]],
|
|
113
|
+
name: str,
|
|
114
|
+
proof_items: list[tuple[int, str, bool]],
|
|
115
|
+
) -> tuple[dict[int, float], float]:
|
|
116
|
+
"""Extract per-line timing from traces."""
|
|
117
|
+
line_times: dict[int, float] = defaultdict(float)
|
|
118
|
+
total, value_depth, in_value, tactic_depth = 0.0, 0, False, None
|
|
119
|
+
name_re = re.compile(rf"\b{re.escape(name)}\b")
|
|
120
|
+
used: set[int] = set()
|
|
121
|
+
|
|
122
|
+
for depth, cls, ms, msg in traces:
|
|
123
|
+
if cls == "Elab.definition.value" and name_re.search(msg):
|
|
124
|
+
in_value, value_depth, total = True, depth, ms
|
|
125
|
+
elif cls == "Elab.async" and f"proof of {name}" in msg:
|
|
126
|
+
total = max(total, ms)
|
|
127
|
+
elif in_value:
|
|
128
|
+
if depth <= value_depth:
|
|
129
|
+
break
|
|
130
|
+
if cls == "Elab.step" and not msg.startswith("expected type:"):
|
|
131
|
+
tactic_depth = tactic_depth or depth
|
|
132
|
+
if depth == tactic_depth:
|
|
133
|
+
tactic = msg.split("\n")[0].strip().lstrip("·*- \t")
|
|
134
|
+
if ln := _match_line(tactic, not tactic, proof_items, used):
|
|
135
|
+
line_times[ln] += ms
|
|
136
|
+
used.add(ln)
|
|
137
|
+
|
|
138
|
+
return dict(line_times), total
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def _filter_categories(cumulative: dict[str, float]) -> dict[str, float]:
|
|
142
|
+
"""Filter to relevant categories >= 1ms."""
|
|
143
|
+
return {
|
|
144
|
+
k: round(v, 1)
|
|
145
|
+
for k, v in sorted(cumulative.items(), key=lambda x: -x[1])
|
|
146
|
+
if k not in _SKIP_CATEGORIES and v >= 1.0
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
async def _run_lean_profile(file_path: Path, project_path: Path, timeout: float) -> str:
|
|
151
|
+
"""Run lean --profile, return output."""
|
|
152
|
+
proc = await asyncio.create_subprocess_exec(
|
|
153
|
+
"lake",
|
|
154
|
+
"env",
|
|
155
|
+
"lean",
|
|
156
|
+
"--profile",
|
|
157
|
+
"-Dtrace.profiler=true",
|
|
158
|
+
"-Dtrace.profiler.threshold=0",
|
|
159
|
+
str(file_path.resolve()),
|
|
160
|
+
stdout=asyncio.subprocess.PIPE,
|
|
161
|
+
stderr=asyncio.subprocess.STDOUT,
|
|
162
|
+
cwd=project_path.resolve(),
|
|
163
|
+
env=os.environ.copy(),
|
|
164
|
+
)
|
|
165
|
+
try:
|
|
166
|
+
stdout, _ = await asyncio.wait_for(proc.communicate(), timeout=timeout)
|
|
167
|
+
return stdout.decode("utf-8", errors="replace")
|
|
168
|
+
except asyncio.TimeoutError:
|
|
169
|
+
proc.kill()
|
|
170
|
+
await proc.wait()
|
|
171
|
+
raise TimeoutError(f"Profiling timed out after {timeout}s")
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
def _find_proof_start(source_lines: list[str]) -> int:
|
|
175
|
+
"""Find line after ':= by' in source."""
|
|
176
|
+
for i, line in enumerate(source_lines):
|
|
177
|
+
if ":= by" in line or line.rstrip().endswith(" by"):
|
|
178
|
+
return i + 1
|
|
179
|
+
raise ValueError("No 'by' proof found in theorem")
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
async def profile_theorem(
|
|
183
|
+
file_path: Path,
|
|
184
|
+
theorem_line: int,
|
|
185
|
+
project_path: Path,
|
|
186
|
+
timeout: float = 60.0,
|
|
187
|
+
top_n: int = 5,
|
|
188
|
+
) -> ProofProfileResult:
|
|
189
|
+
"""Profile a theorem via `lean --profile`. Returns per-line timing data."""
|
|
190
|
+
lines = file_path.read_text().splitlines()
|
|
191
|
+
if not (0 < theorem_line <= len(lines)):
|
|
192
|
+
raise ValueError(f"Line {theorem_line} out of range")
|
|
193
|
+
|
|
194
|
+
source, name, src_start = _extract_theorem_source(lines, theorem_line)
|
|
195
|
+
source_lines = source.splitlines()
|
|
196
|
+
line_offset = theorem_line - src_start
|
|
197
|
+
proof_start = _find_proof_start(source_lines)
|
|
198
|
+
proof_items = _build_proof_items(source_lines, proof_start)
|
|
199
|
+
|
|
200
|
+
with tempfile.NamedTemporaryFile(
|
|
201
|
+
mode="w", suffix=".lean", dir=project_path, delete=False
|
|
202
|
+
) as f:
|
|
203
|
+
f.write(source)
|
|
204
|
+
temp_path = Path(f.name)
|
|
205
|
+
|
|
206
|
+
try:
|
|
207
|
+
output = await _run_lean_profile(temp_path, project_path, timeout)
|
|
208
|
+
finally:
|
|
209
|
+
temp_path.unlink(missing_ok=True)
|
|
210
|
+
|
|
211
|
+
traces, cumulative = _parse_output(output)
|
|
212
|
+
line_times, total = _extract_line_times(traces, name, proof_items)
|
|
213
|
+
|
|
214
|
+
top_lines = sorted(
|
|
215
|
+
[(ln, ms) for ln, ms in line_times.items() if ms >= total * 0.01],
|
|
216
|
+
key=lambda x: -x[1],
|
|
217
|
+
)[:top_n]
|
|
218
|
+
|
|
219
|
+
return ProofProfileResult(
|
|
220
|
+
ms=round(total, 1),
|
|
221
|
+
lines=[
|
|
222
|
+
LineProfile(
|
|
223
|
+
line=ln + line_offset,
|
|
224
|
+
ms=round(ms, 1),
|
|
225
|
+
text=source_lines[ln - 1].strip()[:60]
|
|
226
|
+
if ln <= len(source_lines)
|
|
227
|
+
else "",
|
|
228
|
+
)
|
|
229
|
+
for ln, ms in top_lines
|
|
230
|
+
],
|
|
231
|
+
categories=_filter_categories(cumulative),
|
|
232
|
+
)
|
lean_lsp_mcp/server.py
CHANGED
|
@@ -50,6 +50,7 @@ from lean_lsp_mcp.models import (
|
|
|
50
50
|
MultiAttemptResult,
|
|
51
51
|
PremiseResult,
|
|
52
52
|
PremiseResults,
|
|
53
|
+
ProofProfileResult,
|
|
53
54
|
RunResult,
|
|
54
55
|
StateSearchResult,
|
|
55
56
|
StateSearchResults,
|
|
@@ -1300,5 +1301,55 @@ async def hammer_premise(
|
|
|
1300
1301
|
return PremiseResults(items=items)
|
|
1301
1302
|
|
|
1302
1303
|
|
|
1304
|
+
@mcp.tool(
|
|
1305
|
+
"lean_profile_proof",
|
|
1306
|
+
annotations=ToolAnnotations(
|
|
1307
|
+
title="Profile Proof",
|
|
1308
|
+
readOnlyHint=True,
|
|
1309
|
+
idempotentHint=True,
|
|
1310
|
+
openWorldHint=False,
|
|
1311
|
+
),
|
|
1312
|
+
)
|
|
1313
|
+
async def profile_proof(
|
|
1314
|
+
ctx: Context,
|
|
1315
|
+
file_path: Annotated[str, Field(description="Absolute path to Lean file")],
|
|
1316
|
+
line: Annotated[
|
|
1317
|
+
int, Field(description="Line where theorem starts (1-indexed)", ge=1)
|
|
1318
|
+
],
|
|
1319
|
+
top_n: Annotated[
|
|
1320
|
+
int, Field(description="Number of slowest lines to return", ge=1)
|
|
1321
|
+
] = 5,
|
|
1322
|
+
timeout: Annotated[float, Field(description="Max seconds to wait", ge=1)] = 60.0,
|
|
1323
|
+
) -> ProofProfileResult:
|
|
1324
|
+
"""Run `lean --profile` on a theorem. Returns per-line timing and categories."""
|
|
1325
|
+
from lean_lsp_mcp.profile_utils import profile_theorem
|
|
1326
|
+
|
|
1327
|
+
# Get project path
|
|
1328
|
+
lifespan = ctx.request_context.lifespan_context
|
|
1329
|
+
project_path = lifespan.lean_project_path
|
|
1330
|
+
|
|
1331
|
+
if not project_path:
|
|
1332
|
+
infer_project_path(ctx, file_path)
|
|
1333
|
+
project_path = lifespan.lean_project_path
|
|
1334
|
+
|
|
1335
|
+
if not project_path:
|
|
1336
|
+
raise LeanToolError("Lean project not found")
|
|
1337
|
+
|
|
1338
|
+
file_path_obj = Path(file_path)
|
|
1339
|
+
if not file_path_obj.exists():
|
|
1340
|
+
raise LeanToolError(f"File not found: {file_path}")
|
|
1341
|
+
|
|
1342
|
+
try:
|
|
1343
|
+
return await profile_theorem(
|
|
1344
|
+
file_path=file_path_obj,
|
|
1345
|
+
theorem_line=line,
|
|
1346
|
+
project_path=project_path,
|
|
1347
|
+
timeout=timeout,
|
|
1348
|
+
top_n=top_n,
|
|
1349
|
+
)
|
|
1350
|
+
except (ValueError, TimeoutError) as e:
|
|
1351
|
+
raise LeanToolError(str(e)) from e
|
|
1352
|
+
|
|
1353
|
+
|
|
1303
1354
|
if __name__ == "__main__":
|
|
1304
1355
|
mcp.run()
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: lean-lsp-mcp
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.19.1
|
|
4
4
|
Summary: Lean Theorem Prover MCP
|
|
5
5
|
Author-email: Oliver Dressler <hey@oli.show>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -8,7 +8,7 @@ Project-URL: Repository, https://github.com/oOo0oOo/lean-lsp-mcp
|
|
|
8
8
|
Requires-Python: >=3.10
|
|
9
9
|
Description-Content-Type: text/markdown
|
|
10
10
|
License-File: LICENSE
|
|
11
|
-
Requires-Dist: leanclient==0.9.
|
|
11
|
+
Requires-Dist: leanclient==0.9.2
|
|
12
12
|
Requires-Dist: mcp[cli]==1.25.0
|
|
13
13
|
Requires-Dist: orjson>=3.11.1
|
|
14
14
|
Provides-Extra: lint
|
|
@@ -299,6 +299,27 @@ h_neq : ¬P.card = 2 ^ (Fintype.card S - 1)
|
|
|
299
299
|
```
|
|
300
300
|
</details>
|
|
301
301
|
|
|
302
|
+
#### lean_profile_proof
|
|
303
|
+
|
|
304
|
+
Profile a theorem to identify slow tactics. Runs `lean --profile` on an isolated copy of the theorem and returns per-line timing data.
|
|
305
|
+
|
|
306
|
+
<details>
|
|
307
|
+
<summary>Example output (profiling a theorem using simp)</summary>
|
|
308
|
+
|
|
309
|
+
```json
|
|
310
|
+
{
|
|
311
|
+
"ms": 42.5,
|
|
312
|
+
"lines": [
|
|
313
|
+
{"line": 7, "ms": 38.2, "text": "simp [add_comm, add_assoc]"}
|
|
314
|
+
],
|
|
315
|
+
"categories": {
|
|
316
|
+
"simp": 35.1,
|
|
317
|
+
"typeclass inference": 4.2
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
```
|
|
321
|
+
</details>
|
|
322
|
+
|
|
302
323
|
### Local Search Tools
|
|
303
324
|
|
|
304
325
|
#### lean_local_search
|
|
@@ -2,16 +2,17 @@ lean_lsp_mcp/__init__.py,sha256=MN_bNFyb5-p33JWWGbrlUYBd1UUMQKtZYGC9KCh2mtM,1403
|
|
|
2
2
|
lean_lsp_mcp/__main__.py,sha256=XnpTzfJc0T-j9tHtdkA8ovTr1c139ffTewcJGhxYDaM,49
|
|
3
3
|
lean_lsp_mcp/client_utils.py,sha256=HgPuB35rMitn2Xm8SCAErsFLq15trB6VMz3FDFgmPd8,4897
|
|
4
4
|
lean_lsp_mcp/file_utils.py,sha256=kCTYQSfmV-R2cm_NCi_L8W5Dcsm0_rTOPpTtpyAin78,1365
|
|
5
|
-
lean_lsp_mcp/instructions.py,sha256=
|
|
5
|
+
lean_lsp_mcp/instructions.py,sha256=HgENbIe1nwIzKXqf_yYC44f7YQ8j47zJgKBYmmaD_UI,1994
|
|
6
6
|
lean_lsp_mcp/loogle.py,sha256=zUgnDWoTIqa4G6GXStAIxxJUR545YbU8Z-8KMjddKV0,15500
|
|
7
|
-
lean_lsp_mcp/models.py,sha256=
|
|
7
|
+
lean_lsp_mcp/models.py,sha256=dyjM6m36PscEtBiLjGxDxeM5b0ELpRTw1SUayYDeOwA,7549
|
|
8
8
|
lean_lsp_mcp/outline_utils.py,sha256=i7xL27UO2rTT48IdKXkoMq5FVJNxyA3tPuQREOBf_gU,11105
|
|
9
|
+
lean_lsp_mcp/profile_utils.py,sha256=LwtBmgwrywPiNhWS6xK-_QF-FTy0uWkW6NCK5l5rCQI,8144
|
|
9
10
|
lean_lsp_mcp/search_utils.py,sha256=MLqKGe4bhEvyfFLIBCmiDxkbcH4O5J3vl9mWnRSb_v0,6801
|
|
10
|
-
lean_lsp_mcp/server.py,sha256=
|
|
11
|
+
lean_lsp_mcp/server.py,sha256=VFdyRxHJsBM0sehHyvEkdG17HQl78MYgBgH8usBkpUU,45430
|
|
11
12
|
lean_lsp_mcp/utils.py,sha256=dGv84a4E-szOkQVYtSE-q9GbawpiVk47qvrkTN-Clts,13478
|
|
12
|
-
lean_lsp_mcp-0.
|
|
13
|
-
lean_lsp_mcp-0.
|
|
14
|
-
lean_lsp_mcp-0.
|
|
15
|
-
lean_lsp_mcp-0.
|
|
16
|
-
lean_lsp_mcp-0.
|
|
17
|
-
lean_lsp_mcp-0.
|
|
13
|
+
lean_lsp_mcp-0.19.1.dist-info/licenses/LICENSE,sha256=CQlxnf0tQyoVrBE93JYvAUYxv6Z5Yg6sX0pwogOkFvo,1071
|
|
14
|
+
lean_lsp_mcp-0.19.1.dist-info/METADATA,sha256=D2CgfgVE_jKV0Lnz3TDlbCgOdywCk48WXP1Bp9xLZNs,21274
|
|
15
|
+
lean_lsp_mcp-0.19.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
16
|
+
lean_lsp_mcp-0.19.1.dist-info/entry_points.txt,sha256=nQbvwctWkWD7I-2f4VrdVQBZYGUw8CnUnFC6QjXxOSE,51
|
|
17
|
+
lean_lsp_mcp-0.19.1.dist-info/top_level.txt,sha256=LGEK0lgMSNPIQ6mG8EO-adaZEGPi_0daDs004epOTF0,13
|
|
18
|
+
lean_lsp_mcp-0.19.1.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|