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,126 @@
1
+ """ACP tool catalog schemas."""
2
+
3
+ from typing import Any
4
+
5
+ ACP_TOOLS: list[dict[str, Any]] = [
6
+ {
7
+ "name": "spill_read",
8
+ "description": "Read or paginate a spilled tool output artifact (R24).",
9
+ "inputSchema": {
10
+ "type": "object",
11
+ "properties": {
12
+ "locator": {"type": "string", "description": "Spill locator token (e.g. locator:spill:... or path)"},
13
+ "offset": {"type": "integer", "description": "0-based line offset", "default": 0},
14
+ "limit": {"type": "integer", "description": "Maximum number of lines to read", "default": 1000},
15
+ },
16
+ "required": ["locator"],
17
+ },
18
+ },
19
+ {
20
+ "name": "grep",
21
+ "description": "Fast ripgrep / regex code search across workspace (R24).",
22
+ "inputSchema": {
23
+ "type": "object",
24
+ "properties": {
25
+ "query": {"type": "string", "description": "Search query or regex pattern"},
26
+ "paths": {"type": "array", "items": {"type": "string"}, "description": "Optional glob filters or paths"},
27
+ "regex": {"type": "boolean", "description": "Treat query as regular expression", "default": False},
28
+ "case_sensitive": {"type": "boolean", "description": "Perform case-sensitive matching", "default": False},
29
+ "max_results": {"type": "integer", "description": "Maximum match results", "default": 100},
30
+ "workspace": {"type": "string", "description": "Workspace root directory override"},
31
+ },
32
+ "required": ["query"],
33
+ },
34
+ },
35
+ {
36
+ "name": "lsp",
37
+ "description": "Run LSP code intelligence query (definition, references, hover, symbols) (R25).",
38
+ "inputSchema": {
39
+ "type": "object",
40
+ "properties": {
41
+ "operation": {
42
+ "type": "string",
43
+ "enum": ["definition", "references", "hover", "symbols"],
44
+ "description": "LSP operation",
45
+ },
46
+ "file": {"type": "string", "description": "Target source file path"},
47
+ "line": {"type": "integer", "description": "0-based line number", "default": 0},
48
+ "char": {"type": "integer", "description": "0-based character/column offset", "default": 0},
49
+ "workspace": {"type": "string", "description": "Workspace root directory override"},
50
+ },
51
+ "required": ["operation", "file"],
52
+ },
53
+ },
54
+ {
55
+ "name": "skeletonize",
56
+ "description": "Parse and skeletonize code symbols using AST compactor (R17).",
57
+ "inputSchema": {
58
+ "type": "object",
59
+ "properties": {
60
+ "file": {"type": "string", "description": "Target source file path"},
61
+ "symbols": {"type": "array", "items": {"type": "string"}, "description": "Target symbols to retain in full"},
62
+ },
63
+ "required": ["file"],
64
+ },
65
+ },
66
+ {
67
+ "name": "lint_patch",
68
+ "description": "Fast pre-test semantic linter checking patch in memory (R18).",
69
+ "inputSchema": {
70
+ "type": "object",
71
+ "properties": {
72
+ "patch": {"type": "string", "description": "Unified diff or SEARCH/REPLACE patch to check"},
73
+ "workspace": {"type": "string", "description": "Workspace root directory override"},
74
+ },
75
+ "required": ["patch"],
76
+ },
77
+ },
78
+ {
79
+ "name": "read_file",
80
+ "description": "Read one UTF-8 file from workspace.",
81
+ "inputSchema": {
82
+ "type": "object",
83
+ "properties": {
84
+ "path": {"type": "string", "description": "File path relative to workspace"},
85
+ "workspace": {"type": "string", "description": "Workspace root directory override"},
86
+ },
87
+ "required": ["path"],
88
+ },
89
+ },
90
+ {
91
+ "name": "list_files",
92
+ "description": "List files below workspace directory.",
93
+ "inputSchema": {
94
+ "type": "object",
95
+ "properties": {
96
+ "path": {"type": "string", "description": "Directory path relative to workspace", "default": "."},
97
+ "workspace": {"type": "string", "description": "Workspace root directory override"},
98
+ },
99
+ },
100
+ },
101
+ {
102
+ "name": "run_tests",
103
+ "description": "Run test or shell check command in workspace.",
104
+ "inputSchema": {
105
+ "type": "object",
106
+ "properties": {
107
+ "command": {"type": "string", "description": "Shell command to execute"},
108
+ "workspace": {"type": "string", "description": "Workspace root directory override"},
109
+ "timeout": {"type": "number", "description": "Timeout in seconds", "default": 60},
110
+ },
111
+ "required": ["command"],
112
+ },
113
+ },
114
+ {
115
+ "name": "propose_patch",
116
+ "description": "Validate whether a patch applies cleanly to the workspace.",
117
+ "inputSchema": {
118
+ "type": "object",
119
+ "properties": {
120
+ "patch": {"type": "string", "description": "Patch content to validate"},
121
+ "workspace": {"type": "string", "description": "Workspace root directory override"},
122
+ },
123
+ "required": ["patch"],
124
+ },
125
+ },
126
+ ]
@@ -0,0 +1,130 @@
1
+ """AST-Guided Context Compaction & Skeletonization (R17).
2
+
3
+ Pre-processor that collapses non-target functions and classes down to signatures
4
+ and docstrings using standard library AST parsing, reducing context footprint by 60–85%.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import ast
10
+ from pathlib import Path
11
+ from typing import Sequence
12
+
13
+
14
+ class _SkeletonTransformer(ast.NodeTransformer):
15
+ def __init__(self, target_symbols: set[str]) -> None:
16
+ self.target_symbols = target_symbols
17
+ self.in_target_class = False
18
+
19
+ def _collapse_body(self, body: list[ast.stmt]) -> list[ast.stmt]:
20
+ docstring = ast.get_docstring(ast.Module(body=body, type_ignores=[]))
21
+ new_body: list[ast.stmt] = []
22
+ if docstring:
23
+ new_body.append(ast.Expr(value=ast.Constant(value=docstring)))
24
+ new_body.append(ast.Expr(value=ast.Constant(value=Ellipsis)))
25
+ return new_body
26
+
27
+ def visit_FunctionDef(self, node: ast.FunctionDef) -> ast.AST:
28
+ if self.in_target_class or node.name in self.target_symbols:
29
+ return node
30
+ node_copy = ast.FunctionDef(
31
+ name=node.name,
32
+ args=node.args,
33
+ body=self._collapse_body(node.body),
34
+ decorator_list=node.decorator_list,
35
+ returns=node.returns,
36
+ type_comment=getattr(node, "type_comment", None),
37
+ type_params=getattr(node, "type_params", []),
38
+ )
39
+ return node_copy
40
+
41
+ def visit_AsyncFunctionDef(self, node: ast.AsyncFunctionDef) -> ast.AST:
42
+ if self.in_target_class or node.name in self.target_symbols:
43
+ return node
44
+ node_copy = ast.AsyncFunctionDef(
45
+ name=node.name,
46
+ args=node.args,
47
+ body=self._collapse_body(node.body),
48
+ decorator_list=node.decorator_list,
49
+ returns=node.returns,
50
+ type_comment=getattr(node, "type_comment", None),
51
+ type_params=getattr(node, "type_params", []),
52
+ )
53
+ return node_copy
54
+
55
+ def visit_ClassDef(self, node: ast.ClassDef) -> ast.AST:
56
+ if node.name in self.target_symbols:
57
+ prev = self.in_target_class
58
+ self.in_target_class = True
59
+ try:
60
+ return self.generic_visit(node)
61
+ finally:
62
+ self.in_target_class = prev
63
+
64
+ # Transform internal methods
65
+ new_body: list[ast.stmt] = []
66
+ docstring = ast.get_docstring(ast.Module(body=node.body, type_ignores=[]))
67
+ if docstring:
68
+ new_body.append(ast.Expr(value=ast.Constant(value=docstring)))
69
+
70
+ for item in node.body:
71
+ if isinstance(item, (ast.FunctionDef, ast.AsyncFunctionDef)):
72
+ if item.name in self.target_symbols:
73
+ new_body.append(item)
74
+ else:
75
+ transformed = self.visit(item)
76
+ if isinstance(transformed, ast.stmt):
77
+ new_body.append(transformed)
78
+ elif isinstance(item, ast.Expr) and isinstance(item.value, ast.Constant) and isinstance(item.value.value, str):
79
+ # Docstring handled above
80
+ continue
81
+ elif isinstance(item, (ast.Assign, ast.AnnAssign)):
82
+ new_body.append(item)
83
+ else:
84
+ new_body.append(item)
85
+
86
+ if not new_body:
87
+ new_body.append(ast.Expr(value=ast.Constant(value=Ellipsis)))
88
+
89
+ node_copy = ast.ClassDef(
90
+ name=node.name,
91
+ bases=node.bases,
92
+ keywords=node.keywords,
93
+ body=new_body,
94
+ decorator_list=node.decorator_list,
95
+ type_params=getattr(node, "type_params", []),
96
+ )
97
+ return node_copy
98
+
99
+
100
+ def skeletonize_python(source: str, target_symbols: Sequence[str] | None = None) -> str:
101
+ """Skeletonize Python code by collapsing non-target function and class bodies."""
102
+ if not source.strip():
103
+ return source
104
+
105
+ try:
106
+ tree = ast.parse(source)
107
+ except SyntaxError:
108
+ return source
109
+
110
+ targets = set(target_symbols) if target_symbols else set()
111
+ transformer = _SkeletonTransformer(targets)
112
+ transformed_tree = transformer.visit(tree)
113
+ ast.fix_missing_locations(transformed_tree)
114
+
115
+ try:
116
+ return ast.unparse(transformed_tree)
117
+ except Exception:
118
+ return source
119
+
120
+
121
+ def skeletonize_file(file_path: str, target_symbols: Sequence[str] | None = None) -> str:
122
+ """Skeletonize a file based on its extension."""
123
+ path = Path(file_path)
124
+ if not path.exists() or not path.is_file():
125
+ raise FileNotFoundError(f"File not found: {file_path}")
126
+
127
+ content = path.read_text(encoding="utf-8", errors="replace")
128
+ if path.suffix == ".py":
129
+ return skeletonize_python(content, target_symbols=target_symbols)
130
+ return content
@@ -0,0 +1,77 @@
1
+ """Formal task budget and deterministic decomposition (R7 task atomization)."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import math
6
+ from dataclasses import dataclass
7
+
8
+ from .task import TaskEnvelope
9
+
10
+
11
+ @dataclass(frozen=True)
12
+ class TaskBudget:
13
+ max_files: int = 5
14
+ max_context_bytes: int = 32_000
15
+ max_checks: int = 3
16
+
17
+ def __post_init__(self) -> None:
18
+ if self.max_files <= 0:
19
+ raise ValueError("max_files must be positive")
20
+ if self.max_context_bytes <= 0:
21
+ raise ValueError("max_context_bytes must be positive")
22
+ if self.max_checks <= 0:
23
+ raise ValueError("max_checks must be positive")
24
+
25
+
26
+ @dataclass(frozen=True)
27
+ class PreflightReport:
28
+ accepted: bool
29
+ reason: str | None
30
+ issues: tuple[str, ...]
31
+
32
+
33
+ @dataclass(frozen=True)
34
+ class Decomposition:
35
+ children: tuple[TaskEnvelope, ...]
36
+
37
+
38
+ def preflight(task: TaskEnvelope, budget: TaskBudget) -> PreflightReport:
39
+ if len(task.files) > budget.max_files:
40
+ return PreflightReport(False, "too_many_files", ("too_many_files",))
41
+ if len(task.context.encode("utf-8")) > budget.max_context_bytes:
42
+ return PreflightReport(False, "context_too_large", ("context_too_large",))
43
+ if len(task.checks) > budget.max_checks:
44
+ return PreflightReport(False, "too_many_checks", ("too_many_checks",))
45
+ return PreflightReport(True, None, ())
46
+
47
+
48
+ def decompose(task: TaskEnvelope, budget: TaskBudget) -> Decomposition:
49
+ if len(task.context.encode("utf-8")) > budget.max_context_bytes:
50
+ raise ValueError("cannot decompose task: context_too_large")
51
+ if len(task.checks) > budget.max_checks:
52
+ raise ValueError("cannot decompose task: too_many_checks")
53
+
54
+ report = preflight(task, budget)
55
+ if not report.accepted and report.reason != "too_many_files":
56
+ raise ValueError(f"cannot decompose task: {report.reason}")
57
+
58
+ count = math.ceil(len(task.files) / budget.max_files)
59
+ if count <= 1:
60
+ return Decomposition((task,))
61
+
62
+ children = []
63
+ for index in range(count):
64
+ start = index * budget.max_files
65
+ files = task.files[start : start + budget.max_files]
66
+ children.append(
67
+ TaskEnvelope(
68
+ id=f"{task.id}#{index + 1}",
69
+ goal=task.goal,
70
+ files=files,
71
+ context=task.context,
72
+ constraints=task.constraints,
73
+ checks=task.checks,
74
+ acceptance=task.acceptance,
75
+ )
76
+ )
77
+ return Decomposition(tuple(children))
@@ -0,0 +1,57 @@
1
+ """Comparable, proposal-only benchmark for local coding models."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from ._cases import BenchmarkCase, default_cases
6
+ from ._oracles import (
7
+ _unique_oracle,
8
+ _limit_oracle,
9
+ _utf8_oracle,
10
+ _no_mutation_oracle,
11
+ _count_positives_oracle,
12
+ _max_value_oracle,
13
+ _abs_sum_oracle,
14
+ _reverse_str_oracle,
15
+ _filter_evens_oracle,
16
+ _count_words_oracle,
17
+ _dict_default_oracle,
18
+ _strip_text_oracle,
19
+ _join_words_oracle,
20
+ _last_element_oracle,
21
+ _sorted_copy_oracle,
22
+ _replace_dash_oracle,
23
+ _starts_with_oracle,
24
+ _dot_product_oracle,
25
+ _min_value_oracle,
26
+ _title_case_oracle,
27
+ )
28
+ from ._runner import (
29
+ BenchmarkCaseResult,
30
+ ChatModel,
31
+ InstrumentedModel,
32
+ run_benchmark,
33
+ run_case,
34
+ write_artifact,
35
+ )
36
+ from ._stats import _percent, _wilson_score_interval
37
+ from ._summary import (
38
+ CAPABILITY_FAILURE_CATEGORIES,
39
+ CONTRACT_FRICTION_CATEGORIES,
40
+ categorize_failure_taxonomy,
41
+ summarize_results,
42
+ )
43
+
44
+ __all__ = [
45
+ "BenchmarkCase",
46
+ "ChatModel",
47
+ "InstrumentedModel",
48
+ "BenchmarkCaseResult",
49
+ "default_cases",
50
+ "run_case",
51
+ "run_benchmark",
52
+ "summarize_results",
53
+ "write_artifact",
54
+ "categorize_failure_taxonomy",
55
+ "CONTRACT_FRICTION_CATEGORIES",
56
+ "CAPABILITY_FAILURE_CATEGORIES",
57
+ ]