fcp-python 0.1.4__tar.gz → 0.1.6__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.
- {fcp_python-0.1.4 → fcp_python-0.1.6}/.claude-plugin/plugin.json +1 -1
- {fcp_python-0.1.4 → fcp_python-0.1.6}/PKG-INFO +1 -1
- {fcp_python-0.1.4 → fcp_python-0.1.6}/pyproject.toml +1 -1
- {fcp_python-0.1.4 → fcp_python-0.1.6}/src/fcp_python/bridge.py +16 -14
- {fcp_python-0.1.4 → fcp_python-0.1.6}/src/fcp_python/main.py +6 -4
- {fcp_python-0.1.4 → fcp_python-0.1.6}/tests/test_bridge.py +13 -17
- {fcp_python-0.1.4 → fcp_python-0.1.6}/uv.lock +1 -1
- {fcp_python-0.1.4 → fcp_python-0.1.6}/.github/workflows/ci.yml +0 -0
- {fcp_python-0.1.4 → fcp_python-0.1.6}/.github/workflows/release.yml +0 -0
- {fcp_python-0.1.4 → fcp_python-0.1.6}/.mcp.json +0 -0
- {fcp_python-0.1.4 → fcp_python-0.1.6}/CLAUDE.md +0 -0
- {fcp_python-0.1.4 → fcp_python-0.1.6}/Makefile +0 -0
- {fcp_python-0.1.4 → fcp_python-0.1.6}/README.md +0 -0
- {fcp_python-0.1.4 → fcp_python-0.1.6}/src/fcp_python/__init__.py +0 -0
- {fcp_python-0.1.4 → fcp_python-0.1.6}/src/fcp_python/domain/__init__.py +0 -0
- {fcp_python-0.1.4 → fcp_python-0.1.6}/src/fcp_python/domain/format.py +0 -0
- {fcp_python-0.1.4 → fcp_python-0.1.6}/src/fcp_python/domain/model.py +0 -0
- {fcp_python-0.1.4 → fcp_python-0.1.6}/src/fcp_python/domain/mutation.py +0 -0
- {fcp_python-0.1.4 → fcp_python-0.1.6}/src/fcp_python/domain/query.py +0 -0
- {fcp_python-0.1.4 → fcp_python-0.1.6}/src/fcp_python/domain/verbs.py +0 -0
- {fcp_python-0.1.4 → fcp_python-0.1.6}/src/fcp_python/lsp/__init__.py +0 -0
- {fcp_python-0.1.4 → fcp_python-0.1.6}/src/fcp_python/lsp/client.py +0 -0
- {fcp_python-0.1.4 → fcp_python-0.1.6}/src/fcp_python/lsp/lifecycle.py +0 -0
- {fcp_python-0.1.4 → fcp_python-0.1.6}/src/fcp_python/lsp/transport.py +0 -0
- {fcp_python-0.1.4 → fcp_python-0.1.6}/src/fcp_python/lsp/types.py +0 -0
- {fcp_python-0.1.4 → fcp_python-0.1.6}/src/fcp_python/lsp/workspace_edit.py +0 -0
- {fcp_python-0.1.4 → fcp_python-0.1.6}/src/fcp_python/resolver/__init__.py +0 -0
- {fcp_python-0.1.4 → fcp_python-0.1.6}/src/fcp_python/resolver/index.py +0 -0
- {fcp_python-0.1.4 → fcp_python-0.1.6}/src/fcp_python/resolver/pipeline.py +0 -0
- {fcp_python-0.1.4 → fcp_python-0.1.6}/src/fcp_python/resolver/selectors.py +0 -0
- {fcp_python-0.1.4 → fcp_python-0.1.6}/tests/__init__.py +0 -0
- {fcp_python-0.1.4 → fcp_python-0.1.6}/tests/test_client.py +0 -0
- {fcp_python-0.1.4 → fcp_python-0.1.6}/tests/test_format.py +0 -0
- {fcp_python-0.1.4 → fcp_python-0.1.6}/tests/test_index.py +0 -0
- {fcp_python-0.1.4 → fcp_python-0.1.6}/tests/test_lifecycle.py +0 -0
- {fcp_python-0.1.4 → fcp_python-0.1.6}/tests/test_model.py +0 -0
- {fcp_python-0.1.4 → fcp_python-0.1.6}/tests/test_mutation.py +0 -0
- {fcp_python-0.1.4 → fcp_python-0.1.6}/tests/test_pipeline.py +0 -0
- {fcp_python-0.1.4 → fcp_python-0.1.6}/tests/test_query.py +0 -0
- {fcp_python-0.1.4 → fcp_python-0.1.6}/tests/test_selectors.py +0 -0
- {fcp_python-0.1.4 → fcp_python-0.1.6}/tests/test_transport.py +0 -0
- {fcp_python-0.1.4 → fcp_python-0.1.6}/tests/test_types.py +0 -0
- {fcp_python-0.1.4 → fcp_python-0.1.6}/tests/test_verbs.py +0 -0
- {fcp_python-0.1.4 → fcp_python-0.1.6}/tests/test_workspace_edit.py +0 -0
|
@@ -74,8 +74,6 @@ def start_bridge(
|
|
|
74
74
|
"""
|
|
75
75
|
try:
|
|
76
76
|
path = _discover_socket()
|
|
77
|
-
if path is None:
|
|
78
|
-
return None
|
|
79
77
|
t = threading.Thread(
|
|
80
78
|
target=_bridge_thread,
|
|
81
79
|
args=(path, handle_session, handle_query, handle_mutation),
|
|
@@ -100,28 +98,23 @@ def _bridge_thread(
|
|
|
100
98
|
pass
|
|
101
99
|
|
|
102
100
|
|
|
103
|
-
def _discover_socket() -> str
|
|
101
|
+
def _discover_socket() -> str:
|
|
104
102
|
"""Find daemon socket path.
|
|
105
103
|
|
|
106
104
|
Matches slipstream-core's default_socket_path():
|
|
107
105
|
SLIPSTREAM_SOCKET || {XDG_RUNTIME_DIR || TMPDIR || /tmp}/slipstream.sock
|
|
106
|
+
|
|
107
|
+
Always returns a path — the connection attempt is the real test.
|
|
108
108
|
"""
|
|
109
|
-
# 1. SLIPSTREAM_SOCKET env var (set by daemon when it spawns plugins)
|
|
110
109
|
path = os.environ.get("SLIPSTREAM_SOCKET")
|
|
111
|
-
if path
|
|
110
|
+
if path:
|
|
112
111
|
return path
|
|
113
|
-
|
|
114
|
-
# 2. Default path: {runtime_dir}/slipstream.sock
|
|
115
112
|
runtime_dir = (
|
|
116
113
|
os.environ.get("XDG_RUNTIME_DIR")
|
|
117
114
|
or os.environ.get("TMPDIR")
|
|
118
115
|
or "/tmp"
|
|
119
116
|
)
|
|
120
|
-
|
|
121
|
-
if os.path.exists(path):
|
|
122
|
-
return path
|
|
123
|
-
|
|
124
|
-
return None
|
|
117
|
+
return os.path.join(runtime_dir, "slipstream.sock")
|
|
125
118
|
|
|
126
119
|
|
|
127
120
|
async def _run_bridge_at(
|
|
@@ -130,8 +123,17 @@ async def _run_bridge_at(
|
|
|
130
123
|
handle_query: Callable[[str], Awaitable[str]],
|
|
131
124
|
handle_mutation: Callable[[list[str]], Awaitable[str]],
|
|
132
125
|
) -> None:
|
|
133
|
-
"""Async loop: connect, register, then handle NDJSON requests."""
|
|
134
|
-
|
|
126
|
+
"""Async loop: connect with retry, register, then handle NDJSON requests."""
|
|
127
|
+
delays = [0.1, 0.2, 0.5, 1.0, 2.0]
|
|
128
|
+
reader = writer = None
|
|
129
|
+
for delay in delays:
|
|
130
|
+
try:
|
|
131
|
+
reader, writer = await asyncio.open_unix_connection(path)
|
|
132
|
+
break
|
|
133
|
+
except (ConnectionRefusedError, FileNotFoundError, OSError):
|
|
134
|
+
await asyncio.sleep(delay)
|
|
135
|
+
if writer is None:
|
|
136
|
+
return
|
|
135
137
|
|
|
136
138
|
# Send registration
|
|
137
139
|
register = {
|
|
@@ -26,11 +26,13 @@ from fcp_python.lsp.types import PublishDiagnosticsParams, SymbolInformation
|
|
|
26
26
|
from fcp_python.resolver.index import SymbolEntry, SymbolIndex
|
|
27
27
|
|
|
28
28
|
mcp = FastMCP(
|
|
29
|
-
"python
|
|
29
|
+
"fcp-python",
|
|
30
30
|
instructions=(
|
|
31
|
-
"FCP Python server for querying Python codebases via pylsp. "
|
|
32
|
-
"Use python_session to open a workspace
|
|
33
|
-
"
|
|
31
|
+
"FCP Python server for querying and refactoring Python codebases via pylsp. "
|
|
32
|
+
"Use python_session to open a workspace directory containing Python files, "
|
|
33
|
+
"python_query for read-only queries like finding definitions, references, "
|
|
34
|
+
"diagnostics, and symbols, python for refactoring operations, and python_help "
|
|
35
|
+
"for the full verb reference. Start every interaction with python_session."
|
|
34
36
|
),
|
|
35
37
|
)
|
|
36
38
|
|
|
@@ -21,56 +21,52 @@ class TestDiscoverSocket:
|
|
|
21
21
|
os.environ.clear()
|
|
22
22
|
os.environ.update(orig)
|
|
23
23
|
|
|
24
|
-
def
|
|
25
|
-
env
|
|
24
|
+
def test_env_var_no_file(self, tmp_path):
|
|
25
|
+
"""Returns env var path even if file doesn't exist yet (race-safe)."""
|
|
26
|
+
path = str(tmp_path / "nonexistent.sock")
|
|
26
27
|
orig = os.environ.copy()
|
|
27
|
-
os.environ
|
|
28
|
+
os.environ["SLIPSTREAM_SOCKET"] = path
|
|
28
29
|
try:
|
|
29
|
-
# Remove XDG too
|
|
30
|
-
os.environ.pop("XDG_RUNTIME_DIR", None)
|
|
31
30
|
result = _discover_socket()
|
|
32
|
-
|
|
33
|
-
# Just verify it doesn't crash
|
|
31
|
+
assert result == path
|
|
34
32
|
finally:
|
|
35
33
|
os.environ.clear()
|
|
36
34
|
os.environ.update(orig)
|
|
37
35
|
|
|
38
36
|
def test_xdg_runtime_dir(self, tmp_path):
|
|
39
|
-
sock = tmp_path / "slipstream.sock"
|
|
40
|
-
sock.touch()
|
|
41
37
|
orig = os.environ.copy()
|
|
42
38
|
os.environ.pop("SLIPSTREAM_SOCKET", None)
|
|
43
39
|
os.environ["XDG_RUNTIME_DIR"] = str(tmp_path)
|
|
44
40
|
os.environ.pop("TMPDIR", None)
|
|
45
41
|
try:
|
|
46
42
|
result = _discover_socket()
|
|
47
|
-
assert result == str(sock)
|
|
43
|
+
assert result == str(tmp_path / "slipstream.sock")
|
|
48
44
|
finally:
|
|
49
45
|
os.environ.clear()
|
|
50
46
|
os.environ.update(orig)
|
|
51
47
|
|
|
52
48
|
def test_tmpdir_fallback(self, tmp_path):
|
|
53
|
-
sock = tmp_path / "slipstream.sock"
|
|
54
|
-
sock.touch()
|
|
55
49
|
orig = os.environ.copy()
|
|
56
50
|
os.environ.pop("SLIPSTREAM_SOCKET", None)
|
|
57
51
|
os.environ.pop("XDG_RUNTIME_DIR", None)
|
|
58
52
|
os.environ["TMPDIR"] = str(tmp_path)
|
|
59
53
|
try:
|
|
60
54
|
result = _discover_socket()
|
|
61
|
-
assert result == str(sock)
|
|
55
|
+
assert result == str(tmp_path / "slipstream.sock")
|
|
62
56
|
finally:
|
|
63
57
|
os.environ.clear()
|
|
64
58
|
os.environ.update(orig)
|
|
65
59
|
|
|
66
|
-
def
|
|
60
|
+
def test_default_fallback(self, tmp_path):
|
|
61
|
+
"""Always returns a path string, never None."""
|
|
67
62
|
orig = os.environ.copy()
|
|
68
63
|
os.environ.pop("SLIPSTREAM_SOCKET", None)
|
|
69
64
|
os.environ.pop("XDG_RUNTIME_DIR", None)
|
|
65
|
+
os.environ.pop("TMPDIR", None)
|
|
70
66
|
try:
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
67
|
+
result = _discover_socket()
|
|
68
|
+
assert isinstance(result, str)
|
|
69
|
+
assert result.endswith("slipstream.sock")
|
|
74
70
|
finally:
|
|
75
71
|
os.environ.clear()
|
|
76
72
|
os.environ.update(orig)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|