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.
Files changed (44) hide show
  1. {fcp_python-0.1.4 → fcp_python-0.1.6}/.claude-plugin/plugin.json +1 -1
  2. {fcp_python-0.1.4 → fcp_python-0.1.6}/PKG-INFO +1 -1
  3. {fcp_python-0.1.4 → fcp_python-0.1.6}/pyproject.toml +1 -1
  4. {fcp_python-0.1.4 → fcp_python-0.1.6}/src/fcp_python/bridge.py +16 -14
  5. {fcp_python-0.1.4 → fcp_python-0.1.6}/src/fcp_python/main.py +6 -4
  6. {fcp_python-0.1.4 → fcp_python-0.1.6}/tests/test_bridge.py +13 -17
  7. {fcp_python-0.1.4 → fcp_python-0.1.6}/uv.lock +1 -1
  8. {fcp_python-0.1.4 → fcp_python-0.1.6}/.github/workflows/ci.yml +0 -0
  9. {fcp_python-0.1.4 → fcp_python-0.1.6}/.github/workflows/release.yml +0 -0
  10. {fcp_python-0.1.4 → fcp_python-0.1.6}/.mcp.json +0 -0
  11. {fcp_python-0.1.4 → fcp_python-0.1.6}/CLAUDE.md +0 -0
  12. {fcp_python-0.1.4 → fcp_python-0.1.6}/Makefile +0 -0
  13. {fcp_python-0.1.4 → fcp_python-0.1.6}/README.md +0 -0
  14. {fcp_python-0.1.4 → fcp_python-0.1.6}/src/fcp_python/__init__.py +0 -0
  15. {fcp_python-0.1.4 → fcp_python-0.1.6}/src/fcp_python/domain/__init__.py +0 -0
  16. {fcp_python-0.1.4 → fcp_python-0.1.6}/src/fcp_python/domain/format.py +0 -0
  17. {fcp_python-0.1.4 → fcp_python-0.1.6}/src/fcp_python/domain/model.py +0 -0
  18. {fcp_python-0.1.4 → fcp_python-0.1.6}/src/fcp_python/domain/mutation.py +0 -0
  19. {fcp_python-0.1.4 → fcp_python-0.1.6}/src/fcp_python/domain/query.py +0 -0
  20. {fcp_python-0.1.4 → fcp_python-0.1.6}/src/fcp_python/domain/verbs.py +0 -0
  21. {fcp_python-0.1.4 → fcp_python-0.1.6}/src/fcp_python/lsp/__init__.py +0 -0
  22. {fcp_python-0.1.4 → fcp_python-0.1.6}/src/fcp_python/lsp/client.py +0 -0
  23. {fcp_python-0.1.4 → fcp_python-0.1.6}/src/fcp_python/lsp/lifecycle.py +0 -0
  24. {fcp_python-0.1.4 → fcp_python-0.1.6}/src/fcp_python/lsp/transport.py +0 -0
  25. {fcp_python-0.1.4 → fcp_python-0.1.6}/src/fcp_python/lsp/types.py +0 -0
  26. {fcp_python-0.1.4 → fcp_python-0.1.6}/src/fcp_python/lsp/workspace_edit.py +0 -0
  27. {fcp_python-0.1.4 → fcp_python-0.1.6}/src/fcp_python/resolver/__init__.py +0 -0
  28. {fcp_python-0.1.4 → fcp_python-0.1.6}/src/fcp_python/resolver/index.py +0 -0
  29. {fcp_python-0.1.4 → fcp_python-0.1.6}/src/fcp_python/resolver/pipeline.py +0 -0
  30. {fcp_python-0.1.4 → fcp_python-0.1.6}/src/fcp_python/resolver/selectors.py +0 -0
  31. {fcp_python-0.1.4 → fcp_python-0.1.6}/tests/__init__.py +0 -0
  32. {fcp_python-0.1.4 → fcp_python-0.1.6}/tests/test_client.py +0 -0
  33. {fcp_python-0.1.4 → fcp_python-0.1.6}/tests/test_format.py +0 -0
  34. {fcp_python-0.1.4 → fcp_python-0.1.6}/tests/test_index.py +0 -0
  35. {fcp_python-0.1.4 → fcp_python-0.1.6}/tests/test_lifecycle.py +0 -0
  36. {fcp_python-0.1.4 → fcp_python-0.1.6}/tests/test_model.py +0 -0
  37. {fcp_python-0.1.4 → fcp_python-0.1.6}/tests/test_mutation.py +0 -0
  38. {fcp_python-0.1.4 → fcp_python-0.1.6}/tests/test_pipeline.py +0 -0
  39. {fcp_python-0.1.4 → fcp_python-0.1.6}/tests/test_query.py +0 -0
  40. {fcp_python-0.1.4 → fcp_python-0.1.6}/tests/test_selectors.py +0 -0
  41. {fcp_python-0.1.4 → fcp_python-0.1.6}/tests/test_transport.py +0 -0
  42. {fcp_python-0.1.4 → fcp_python-0.1.6}/tests/test_types.py +0 -0
  43. {fcp_python-0.1.4 → fcp_python-0.1.6}/tests/test_verbs.py +0 -0
  44. {fcp_python-0.1.4 → fcp_python-0.1.6}/tests/test_workspace_edit.py +0 -0
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "fcp-python",
3
3
  "description": "Query and refactor Python codebases through intent-level commands",
4
- "version": "0.1.4",
4
+ "version": "0.1.6",
5
5
  "license": "MIT",
6
6
  "author": {
7
7
  "name": "Scott Meyer"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fcp-python
3
- Version: 0.1.4
3
+ Version: 0.1.6
4
4
  Summary: Python Code Intelligence FCP — semantic Python code operations for LLMs via pylsp
5
5
  Requires-Python: <3.14,>=3.11
6
6
  Requires-Dist: fastmcp>=3.0
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "fcp-python"
3
- version = "0.1.4"
3
+ version = "0.1.6"
4
4
  description = "Python Code Intelligence FCP — semantic Python code operations for LLMs via pylsp"
5
5
  requires-python = ">=3.11,<3.14"
6
6
  dependencies = [
@@ -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 | None:
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 and os.path.exists(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
- path = os.path.join(runtime_dir, "slipstream.sock")
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
- reader, writer = await asyncio.open_unix_connection(path)
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-fcp",
29
+ "fcp-python",
30
30
  instructions=(
31
- "FCP Python server for querying Python codebases via pylsp. "
32
- "Use python_session to open a workspace, python_query for read-only queries, "
33
- "and python_help for the reference card."
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 test_env_var_missing_file(self, tmp_path):
25
- env = {"SLIPSTREAM_SOCKET": str(tmp_path / "nonexistent.sock")}
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.update(env)
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
- # May return None or fallback to /tmp path
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 test_no_socket_found(self, tmp_path):
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
- # This may or may not find a socket via /tmp fallback
72
- # depending on system state. Just verify no crash.
73
- _discover_socket()
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)
@@ -386,7 +386,7 @@ wheels = [
386
386
 
387
387
  [[package]]
388
388
  name = "fcp-python"
389
- version = "0.1.4"
389
+ version = "0.1.6"
390
390
  source = { editable = "." }
391
391
  dependencies = [
392
392
  { name = "fastmcp" },
File without changes
File without changes
File without changes
File without changes
File without changes