local-coding-agent 1.1.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.
Files changed (101) hide show
  1. local_coding_agent/__init__.py +314 -0
  2. local_coding_agent/__main__.py +5 -0
  3. local_coding_agent/acp_server/__init__.py +38 -0
  4. local_coding_agent/acp_server/_codec.py +95 -0
  5. local_coding_agent/acp_server/_protocol.py +13 -0
  6. local_coding_agent/acp_server/_server.py +663 -0
  7. local_coding_agent/acp_server/_session.py +24 -0
  8. local_coding_agent/acp_server/_tools.py +126 -0
  9. local_coding_agent/ast_compactor.py +130 -0
  10. local_coding_agent/atomizer.py +77 -0
  11. local_coding_agent/benchmark/__init__.py +57 -0
  12. local_coding_agent/benchmark/_cases.py +307 -0
  13. local_coding_agent/benchmark/_judge.py +175 -0
  14. local_coding_agent/benchmark/_oracles.py +157 -0
  15. local_coding_agent/benchmark/_runner.py +259 -0
  16. local_coding_agent/benchmark/_stats.py +22 -0
  17. local_coding_agent/benchmark/_summary.py +143 -0
  18. local_coding_agent/benchmark_oracle_worker.py +169 -0
  19. local_coding_agent/calibration.py +154 -0
  20. local_coding_agent/capability.py +450 -0
  21. local_coding_agent/chat_compaction.py +276 -0
  22. local_coding_agent/cli/__init__.py +12 -0
  23. local_coding_agent/cli/_dispatch.py +377 -0
  24. local_coding_agent/cli/_handlers.py +320 -0
  25. local_coding_agent/cli/_handlers2.py +725 -0
  26. local_coding_agent/cli/_input.py +61 -0
  27. local_coding_agent/cli/_main.py +159 -0
  28. local_coding_agent/cli/_parser.py +327 -0
  29. local_coding_agent/context_manager.py +155 -0
  30. local_coding_agent/controller/__init__.py +18 -0
  31. local_coding_agent/controller/_constants.py +108 -0
  32. local_coding_agent/controller/_controller.py +1008 -0
  33. local_coding_agent/controller/_post_apply.py +216 -0
  34. local_coding_agent/delegator.py +180 -0
  35. local_coding_agent/desktop/__init__.py +8 -0
  36. local_coding_agent/desktop/app.py +100 -0
  37. local_coding_agent/desktop/assets/lucide.min.js +12 -0
  38. local_coding_agent/desktop/assets/tailwind.css +1 -0
  39. local_coding_agent/desktop/client_js.py +1271 -0
  40. local_coding_agent/desktop/components.py +555 -0
  41. local_coding_agent/desktop/server/__init__.py +37 -0
  42. local_coding_agent/desktop/server/_handlers.py +2178 -0
  43. local_coding_agent/desktop/server/_models.py +204 -0
  44. local_coding_agent/desktop/server/_server.py +347 -0
  45. local_coding_agent/desktop/server/_telemetry.py +87 -0
  46. local_coding_agent/desktop/styles.py +76 -0
  47. local_coding_agent/desktop/ui.py +53 -0
  48. local_coding_agent/doctor.py +413 -0
  49. local_coding_agent/hooks/__init__.py +19 -0
  50. local_coding_agent/hooks/_adapters.py +458 -0
  51. local_coding_agent/hooks/_bridge.py +346 -0
  52. local_coding_agent/hooks/_decision.py +26 -0
  53. local_coding_agent/lsp/__init__.py +60 -0
  54. local_coding_agent/lsp/_client.py +322 -0
  55. local_coding_agent/lsp/_fallback.py +297 -0
  56. local_coding_agent/lsp/_manager.py +173 -0
  57. local_coding_agent/lsp/_mock.py +200 -0
  58. local_coding_agent/lsp/_types.py +398 -0
  59. local_coding_agent/mcp_config.py +398 -0
  60. local_coding_agent/mcp_server.py +377 -0
  61. local_coding_agent/memory.py +140 -0
  62. local_coding_agent/mode_router.py +200 -0
  63. local_coding_agent/model_scanner.py +525 -0
  64. local_coding_agent/monitor.py +635 -0
  65. local_coding_agent/observation_policy.py +184 -0
  66. local_coding_agent/ollama_adapter.py +736 -0
  67. local_coding_agent/plan_mode.py +653 -0
  68. local_coding_agent/prescriptions.py +278 -0
  69. local_coding_agent/profiles.py +239 -0
  70. local_coding_agent/repository_tools.py +782 -0
  71. local_coding_agent/ripgrep.py +326 -0
  72. local_coding_agent/semantic_linter.py +122 -0
  73. local_coding_agent/service.py +405 -0
  74. local_coding_agent/session_events.py +684 -0
  75. local_coding_agent/session_query.py +578 -0
  76. local_coding_agent/skill_config.py +426 -0
  77. local_coding_agent/smoke.py +268 -0
  78. local_coding_agent/speculative_racing.py +85 -0
  79. local_coding_agent/spill.py +258 -0
  80. local_coding_agent/stats.py +255 -0
  81. local_coding_agent/stdio.py +132 -0
  82. local_coding_agent/subagent.py +516 -0
  83. local_coding_agent/task.py +90 -0
  84. local_coding_agent/task_store.py +142 -0
  85. local_coding_agent/tasks.py +240 -0
  86. local_coding_agent/terminal/__init__.py +35 -0
  87. local_coding_agent/terminal/_manager.py +156 -0
  88. local_coding_agent/terminal/_process.py +148 -0
  89. local_coding_agent/terminal/_session.py +452 -0
  90. local_coding_agent/terminal/_tools.py +233 -0
  91. local_coding_agent/terminal/_types.py +60 -0
  92. local_coding_agent/tool_call_parser.py +178 -0
  93. local_coding_agent/validators.py +563 -0
  94. local_coding_agent/vram_fit.py +240 -0
  95. local_coding_agent/worker_pool.py +516 -0
  96. local_coding_agent-1.1.0.dist-info/METADATA +202 -0
  97. local_coding_agent-1.1.0.dist-info/RECORD +101 -0
  98. local_coding_agent-1.1.0.dist-info/WHEEL +5 -0
  99. local_coding_agent-1.1.0.dist-info/entry_points.txt +3 -0
  100. local_coding_agent-1.1.0.dist-info/licenses/LICENSE +21 -0
  101. local_coding_agent-1.1.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,173 @@
1
+ """LspManager: High-Level Language Server Navigation & Dispatcher."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import os
6
+ from pathlib import Path
7
+ import shutil
8
+ import threading
9
+ from typing import Any, Mapping, Sequence
10
+
11
+ from ._client import LspClient
12
+ from ._fallback import FallbackLspEngine
13
+ from ._types import (
14
+ DEFAULT_SERVER_CANDIDATES,
15
+ EXTENSION_TO_LANGUAGE,
16
+ LspHoverResult,
17
+ LspLocation,
18
+ LspSymbol,
19
+ )
20
+
21
+
22
+ class LspManager:
23
+ """High-level LSP manager routing queries to active language servers or fallback engine."""
24
+
25
+ def __init__(
26
+ self,
27
+ workspace_root: str | Path | None = None,
28
+ *,
29
+ use_fallback_if_missing: bool = True,
30
+ server_candidates: Mapping[str, Sequence[Sequence[str]]] | None = None,
31
+ timeout: float = 10.0,
32
+ ) -> None:
33
+ self.workspace_root = str(Path(workspace_root).resolve()) if workspace_root else os.getcwd()
34
+ self.use_fallback_if_missing = use_fallback_if_missing
35
+ self.timeout = timeout
36
+ self.candidates: dict[str, list[list[str]]] = {
37
+ k: [list(cmd) for cmd in v]
38
+ for k, v in (server_candidates or DEFAULT_SERVER_CANDIDATES).items()
39
+ }
40
+ self._clients: dict[str, LspClient] = {}
41
+ self._fallback_engine = FallbackLspEngine()
42
+ self._lock = threading.Lock()
43
+
44
+ def get_language_id(self, file_path: str | Path) -> str:
45
+ """Map file extension to standard LSP language ID."""
46
+ suffix = Path(file_path).suffix.lower()
47
+ return EXTENSION_TO_LANGUAGE.get(suffix, "plaintext")
48
+
49
+ def get_client(self, language_id: str) -> LspClient | None:
50
+ """Get or spawn an active LspClient for the given language ID, or None if unavailable."""
51
+ with self._lock:
52
+ if language_id in self._clients:
53
+ client = self._clients[language_id]
54
+ if client.process is not None and client.process.poll() is None:
55
+ return client
56
+ # Stale process
57
+ client.stop()
58
+ del self._clients[language_id]
59
+
60
+ commands = self.candidates.get(language_id, [])
61
+ for cmd in commands:
62
+ binary = cmd[0]
63
+ if shutil.which(binary) is not None:
64
+ try:
65
+ client = LspClient(cmd, workspace_root=self.workspace_root, timeout=self.timeout)
66
+ client.start()
67
+ client.initialize(self.workspace_root)
68
+ self._clients[language_id] = client
69
+ return client
70
+ except Exception:
71
+ continue
72
+ return None
73
+
74
+ def go_to_definition(
75
+ self,
76
+ file_path: str | Path,
77
+ line: int,
78
+ character: int,
79
+ workspace_root: str | Path | None = None,
80
+ ) -> list[LspLocation]:
81
+ """Find definitions of symbol at (line, character) using LSP client or fallback."""
82
+ root = workspace_root or self.workspace_root
83
+ lang = self.get_language_id(file_path)
84
+ client = self.get_client(lang)
85
+ if client is not None:
86
+ try:
87
+ client.did_open(file_path)
88
+ return client.definition(file_path, line, character)
89
+ except Exception:
90
+ pass
91
+ if self.use_fallback_if_missing:
92
+ return self._fallback_engine.go_to_definition(file_path, line, character, root)
93
+ return []
94
+
95
+ def find_references(
96
+ self,
97
+ file_path: str | Path,
98
+ line: int,
99
+ character: int,
100
+ workspace_root: str | Path | None = None,
101
+ include_declaration: bool = True,
102
+ ) -> list[LspLocation]:
103
+ """Find references of symbol at (line, character) using LSP client or fallback."""
104
+ root = workspace_root or self.workspace_root
105
+ lang = self.get_language_id(file_path)
106
+ client = self.get_client(lang)
107
+ if client is not None:
108
+ try:
109
+ client.did_open(file_path)
110
+ return client.references(file_path, line, character, include_declaration)
111
+ except Exception:
112
+ pass
113
+ if self.use_fallback_if_missing:
114
+ return self._fallback_engine.find_references(
115
+ file_path, line, character, root, include_declaration
116
+ )
117
+ return []
118
+
119
+ def hover(
120
+ self,
121
+ file_path: str | Path,
122
+ line: int,
123
+ character: int,
124
+ workspace_root: str | Path | None = None,
125
+ ) -> LspHoverResult | None:
126
+ """Hover symbol at (line, character) using LSP client or fallback."""
127
+ root = workspace_root or self.workspace_root
128
+ lang = self.get_language_id(file_path)
129
+ client = self.get_client(lang)
130
+ if client is not None:
131
+ try:
132
+ client.did_open(file_path)
133
+ return client.hover(file_path, line, character)
134
+ except Exception:
135
+ pass
136
+ if self.use_fallback_if_missing:
137
+ return self._fallback_engine.hover(file_path, line, character, root)
138
+ return None
139
+
140
+ def document_symbols(
141
+ self,
142
+ file_path: str | Path,
143
+ workspace_root: str | Path | None = None,
144
+ ) -> list[LspSymbol]:
145
+ """Extract symbol outline for file using LSP client or fallback."""
146
+ root = workspace_root or self.workspace_root
147
+ lang = self.get_language_id(file_path)
148
+ client = self.get_client(lang)
149
+ if client is not None:
150
+ try:
151
+ client.did_open(file_path)
152
+ return client.document_symbols(file_path)
153
+ except Exception:
154
+ pass
155
+ if self.use_fallback_if_missing:
156
+ return self._fallback_engine.document_symbols(file_path)
157
+ return []
158
+
159
+ def close_all(self) -> None:
160
+ """Shut down and stop all running language servers."""
161
+ with self._lock:
162
+ for client in self._clients.values():
163
+ try:
164
+ client.stop()
165
+ except Exception:
166
+ pass
167
+ self._clients.clear()
168
+
169
+ def __enter__(self) -> LspManager:
170
+ return self
171
+
172
+ def __exit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> None:
173
+ self.close_all()
@@ -0,0 +1,200 @@
1
+ """Mock Language Server for Testing & Verification."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Any, BinaryIO, Mapping
6
+
7
+ from ._types import MessageDecoder, encode_message
8
+
9
+
10
+ class MockLspServer:
11
+ """Mock LSP server implementing standard JSON-RPC over stdio streams."""
12
+
13
+ def __init__(self) -> None:
14
+ self.documents: dict[str, str] = {}
15
+ self.shutdown_received = False
16
+
17
+ def handle_message(self, message: Mapping[str, Any]) -> dict[str, Any] | None:
18
+ """Process one incoming message and return an optional response dict."""
19
+ msg_id = message.get("id")
20
+ method = message.get("method")
21
+ params = message.get("params") or {}
22
+
23
+ if method == "initialize":
24
+ return {
25
+ "jsonrpc": "2.0",
26
+ "id": msg_id,
27
+ "result": {
28
+ "capabilities": {
29
+ "definitionProvider": True,
30
+ "referencesProvider": True,
31
+ "hoverProvider": True,
32
+ "documentSymbolProvider": True,
33
+ "textDocumentSync": 1,
34
+ }
35
+ },
36
+ }
37
+
38
+ if method == "initialized":
39
+ return None
40
+
41
+ if method == "textDocument/didOpen":
42
+ doc = params.get("textDocument", {})
43
+ uri = doc.get("uri", "")
44
+ text = doc.get("text", "")
45
+ self.documents[uri] = text
46
+ return None
47
+
48
+ if method == "textDocument/didClose":
49
+ doc = params.get("textDocument", {})
50
+ uri = doc.get("uri", "")
51
+ self.documents.pop(uri, None)
52
+ return None
53
+
54
+ if method == "textDocument/definition":
55
+ uri = params.get("textDocument", {}).get("uri", "")
56
+ pos = params.get("position", {})
57
+ line = pos.get("line", 0)
58
+ char = pos.get("character", 0)
59
+ return {
60
+ "jsonrpc": "2.0",
61
+ "id": msg_id,
62
+ "result": [
63
+ {
64
+ "uri": uri,
65
+ "range": {
66
+ "start": {"line": line, "character": char},
67
+ "end": {"line": line, "character": char + 5},
68
+ },
69
+ }
70
+ ],
71
+ }
72
+
73
+ if method == "textDocument/references":
74
+ uri = params.get("textDocument", {}).get("uri", "")
75
+ pos = params.get("position", {})
76
+ line = pos.get("line", 0)
77
+ char = pos.get("character", 0)
78
+ return {
79
+ "jsonrpc": "2.0",
80
+ "id": msg_id,
81
+ "result": [
82
+ {
83
+ "uri": uri,
84
+ "range": {
85
+ "start": {"line": line, "character": char},
86
+ "end": {"line": line, "character": char + 5},
87
+ },
88
+ },
89
+ {
90
+ "uri": uri,
91
+ "range": {
92
+ "start": {"line": line + 2, "character": 0},
93
+ "end": {"line": line + 2, "character": 5},
94
+ },
95
+ },
96
+ ],
97
+ }
98
+
99
+ if method == "textDocument/hover":
100
+ pos = params.get("position", {})
101
+ line = pos.get("line", 0)
102
+ char = pos.get("character", 0)
103
+ return {
104
+ "jsonrpc": "2.0",
105
+ "id": msg_id,
106
+ "result": {
107
+ "contents": {
108
+ "kind": "markdown",
109
+ "value": f"```python\ndef mock_symbol() -> None\n```\nMock documentation at L{line+1}:C{char+1}.",
110
+ },
111
+ "range": {
112
+ "start": {"line": line, "character": char},
113
+ "end": {"line": line, "character": char + 5},
114
+ },
115
+ },
116
+ }
117
+
118
+ if method == "textDocument/documentSymbol":
119
+ uri = params.get("textDocument", {}).get("uri", "")
120
+ return {
121
+ "jsonrpc": "2.0",
122
+ "id": msg_id,
123
+ "result": [
124
+ {
125
+ "name": "MockClass",
126
+ "kind": 5,
127
+ "range": {
128
+ "start": {"line": 0, "character": 0},
129
+ "end": {"line": 10, "character": 0},
130
+ },
131
+ "selectionRange": {
132
+ "start": {"line": 0, "character": 6},
133
+ "end": {"line": 0, "character": 15},
134
+ },
135
+ "children": [
136
+ {
137
+ "name": "mock_method",
138
+ "kind": 6,
139
+ "range": {
140
+ "start": {"line": 1, "character": 4},
141
+ "end": {"line": 3, "character": 0},
142
+ },
143
+ "selectionRange": {
144
+ "start": {"line": 1, "character": 8},
145
+ "end": {"line": 1, "character": 19},
146
+ },
147
+ }
148
+ ],
149
+ },
150
+ {
151
+ "name": "mock_function",
152
+ "kind": 12,
153
+ "range": {
154
+ "start": {"line": 12, "character": 0},
155
+ "end": {"line": 15, "character": 0},
156
+ },
157
+ "selectionRange": {
158
+ "start": {"line": 12, "character": 4},
159
+ "end": {"line": 12, "character": 17},
160
+ },
161
+ },
162
+ ],
163
+ }
164
+
165
+ if method == "shutdown":
166
+ self.shutdown_received = True
167
+ return {"jsonrpc": "2.0", "id": msg_id, "result": None}
168
+
169
+ if method == "exit":
170
+ return None
171
+
172
+ # Fallback for unknown methods
173
+ if msg_id is not None:
174
+ return {
175
+ "jsonrpc": "2.0",
176
+ "id": msg_id,
177
+ "error": {"code": -32601, "message": f"Method not found: {method}"},
178
+ }
179
+ return None
180
+
181
+ def serve(self, input_stream: BinaryIO, output_stream: BinaryIO) -> None:
182
+ """Run standard stdio server loop reading from input_stream and writing to output_stream."""
183
+ decoder = MessageDecoder()
184
+ read_fn = getattr(input_stream, "read1", input_stream.read)
185
+ while True:
186
+ try:
187
+ chunk = read_fn(4096)
188
+ except Exception:
189
+ break
190
+ if not chunk:
191
+ break
192
+ messages = decoder.push(chunk)
193
+ for msg in messages:
194
+ resp = self.handle_message(msg)
195
+ if resp is not None:
196
+ data = encode_message(resp)
197
+ output_stream.write(data)
198
+ output_stream.flush()
199
+ if msg.get("method") == "exit":
200
+ return