akitallm 1.1.1__py3-none-any.whl → 1.2.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.
- akita/__init__.py +1 -1
- akita/cli/doctor.py +123 -0
- akita/cli/main.py +167 -57
- akita/core/config.py +1 -0
- akita/core/i18n.py +163 -0
- akita/models/base.py +10 -4
- akita/reasoning/engine.py +33 -2
- akita/tools/diff.py +118 -61
- akitallm-1.2.1.dist-info/METADATA +217 -0
- {akitallm-1.1.1.dist-info → akitallm-1.2.1.dist-info}/RECORD +14 -12
- akitallm-1.1.1.dist-info/METADATA +0 -140
- {akitallm-1.1.1.dist-info → akitallm-1.2.1.dist-info}/WHEEL +0 -0
- {akitallm-1.1.1.dist-info → akitallm-1.2.1.dist-info}/entry_points.txt +0 -0
- {akitallm-1.1.1.dist-info → akitallm-1.2.1.dist-info}/licenses/LICENSE +0 -0
- {akitallm-1.1.1.dist-info → akitallm-1.2.1.dist-info}/top_level.txt +0 -0
akita/models/base.py
CHANGED
|
@@ -12,21 +12,26 @@ class ModelResponse(BaseModel):
|
|
|
12
12
|
raw: Any
|
|
13
13
|
|
|
14
14
|
class AIModel:
|
|
15
|
-
def __init__(self, model_name: str, api_key: Optional[str] = None, base_url: Optional[str] = None):
|
|
15
|
+
def __init__(self, model_name: str, api_key: Optional[str] = None, base_url: Optional[str] = None, temperature: float = 0.7):
|
|
16
16
|
self.model_name = model_name
|
|
17
17
|
self.api_key = api_key
|
|
18
18
|
self.base_url = base_url
|
|
19
|
+
self.temperature = temperature
|
|
19
20
|
|
|
20
21
|
def chat(self, messages: List[Dict[str, str]], **kwargs) -> ModelResponse:
|
|
21
22
|
"""
|
|
22
23
|
Send a chat completion request.
|
|
23
24
|
"""
|
|
25
|
+
# Merge global temperature with specific kwargs if provided
|
|
26
|
+
request_kwargs = {"temperature": self.temperature}
|
|
27
|
+
request_kwargs.update(kwargs)
|
|
28
|
+
|
|
24
29
|
response = litellm.completion(
|
|
25
30
|
model=self.model_name,
|
|
26
31
|
messages=messages,
|
|
27
32
|
api_key=self.api_key,
|
|
28
33
|
base_url=self.base_url,
|
|
29
|
-
**
|
|
34
|
+
**request_kwargs
|
|
30
35
|
)
|
|
31
36
|
content = response.choices[0].message.content
|
|
32
37
|
return ModelResponse(content=content, raw=response)
|
|
@@ -37,6 +42,7 @@ def get_model(model_name: Optional[str] = None) -> AIModel:
|
|
|
37
42
|
"""
|
|
38
43
|
provider = get_config_value("model", "provider", "openai")
|
|
39
44
|
api_key = get_config_value("model", "api_key")
|
|
45
|
+
temperature = get_config_value("model", "temperature", 0.7)
|
|
40
46
|
|
|
41
47
|
if model_name is None:
|
|
42
48
|
model_name = get_config_value("model", "name", "gpt-4o-mini")
|
|
@@ -49,5 +55,5 @@ def get_model(model_name: Optional[str] = None) -> AIModel:
|
|
|
49
55
|
else:
|
|
50
56
|
full_model_name = f"{provider}/{model_name}"
|
|
51
57
|
|
|
52
|
-
#
|
|
53
|
-
return AIModel(model_name=full_model_name, api_key=api_key)
|
|
58
|
+
# Pass temperature to the model
|
|
59
|
+
return AIModel(model_name=full_model_name, api_key=api_key, temperature=temperature)
|
akita/reasoning/engine.py
CHANGED
|
@@ -7,6 +7,8 @@ from akita.schemas.review import ReviewResult
|
|
|
7
7
|
from akita.core.trace import ReasoningTrace
|
|
8
8
|
from akita.reasoning.session import ConversationSession
|
|
9
9
|
import json
|
|
10
|
+
from akita.core.i18n import t
|
|
11
|
+
import pydantic
|
|
10
12
|
from rich.console import Console
|
|
11
13
|
|
|
12
14
|
console = Console()
|
|
@@ -133,8 +135,37 @@ class ReasoningEngine:
|
|
|
133
135
|
else:
|
|
134
136
|
session.add_message("user", query)
|
|
135
137
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
+
# --- ROBUST EXECUTION ---
|
|
139
|
+
try:
|
|
140
|
+
console.print(t("solve.thinking"))
|
|
141
|
+
response = self.model.chat(session.get_messages_dict())
|
|
142
|
+
except pydantic.ValidationError as e:
|
|
143
|
+
# Friendly error for output contract violations
|
|
144
|
+
error_msg = t("error.validation", type=str(e))
|
|
145
|
+
console.print(f"[bold red]{error_msg}[/]")
|
|
146
|
+
# Log debug info if needed
|
|
147
|
+
raise ValueError(error_msg)
|
|
148
|
+
except Exception as e:
|
|
149
|
+
if "validation error" in str(e).lower():
|
|
150
|
+
error_msg = t("error.validation", type="ModelResponse")
|
|
151
|
+
console.print(f"[bold red]{error_msg}[/]")
|
|
152
|
+
raise ValueError(error_msg)
|
|
153
|
+
raise e
|
|
154
|
+
|
|
155
|
+
# --- CONTRACT ENFORCEMENT ---
|
|
156
|
+
if not isinstance(response.content, str):
|
|
157
|
+
error_msg = t("error.validation", type=type(response.content))
|
|
158
|
+
console.print(f"[bold red]{error_msg}[/]")
|
|
159
|
+
raise ValueError(error_msg)
|
|
160
|
+
|
|
161
|
+
if "+++" not in response.content or "---" not in response.content:
|
|
162
|
+
# Relaxed check: Sometimes it's a valid string but forgets headers?
|
|
163
|
+
# No, strictly require headers for safety.
|
|
164
|
+
error_msg = "Solve aborted: Model returned content without Unified Diff headers (+++/---)."
|
|
165
|
+
console.print(f"[bold red]{error_msg}[/]")
|
|
166
|
+
console.print(f"[dim]Output start: {response.content[:100]}...[/]")
|
|
167
|
+
raise ValueError(error_msg)
|
|
168
|
+
|
|
138
169
|
session.add_message("assistant", response.content)
|
|
139
170
|
|
|
140
171
|
self.trace.add_step("LLM Response", "Received solution from model")
|
akita/tools/diff.py
CHANGED
|
@@ -3,107 +3,164 @@ import shutil
|
|
|
3
3
|
import pathlib
|
|
4
4
|
from pathlib import Path
|
|
5
5
|
import whatthepatch
|
|
6
|
-
from typing import List, Tuple, Optional
|
|
6
|
+
from typing import List, Tuple, Optional, Any
|
|
7
7
|
|
|
8
8
|
class DiffApplier:
|
|
9
|
+
@staticmethod
|
|
10
|
+
def validate_diff_context(patch: Any, file_content: str) -> bool:
|
|
11
|
+
"""
|
|
12
|
+
Strictly validates that the context lines in the patch exist in the file content
|
|
13
|
+
at the expected locations.
|
|
14
|
+
"""
|
|
15
|
+
if not patch.changes:
|
|
16
|
+
return True
|
|
17
|
+
|
|
18
|
+
file_lines = file_content.splitlines()
|
|
19
|
+
|
|
20
|
+
# We need to simulate the patch application to check context
|
|
21
|
+
# whatthepatch.apply_diff does this, returning None if context doesn't match
|
|
22
|
+
try:
|
|
23
|
+
result = whatthepatch.apply_diff(patch, file_lines)
|
|
24
|
+
return result is not None
|
|
25
|
+
except Exception:
|
|
26
|
+
return False
|
|
27
|
+
|
|
9
28
|
@staticmethod
|
|
10
29
|
def apply_unified_diff(diff_text: str, base_path: str = ".") -> bool:
|
|
11
30
|
"""
|
|
12
31
|
Applies a unified diff to files in the base_path.
|
|
13
|
-
Includes
|
|
32
|
+
Includes PRE-FLIGHT DRY-RUN, strict context checking, and then atomic application.
|
|
14
33
|
"""
|
|
15
|
-
|
|
34
|
+
try:
|
|
35
|
+
patches = list(whatthepatch.parse_patch(diff_text))
|
|
36
|
+
except Exception as e:
|
|
37
|
+
print(f"❌ ERROR: Failed to parse diff: {e}")
|
|
38
|
+
return False
|
|
39
|
+
|
|
16
40
|
if not patches:
|
|
17
|
-
print("ERROR: No valid patches found in the diff text.")
|
|
41
|
+
print("❌ ERROR: No valid patches found in the diff text.")
|
|
18
42
|
return False
|
|
19
43
|
|
|
20
|
-
backups: List[Tuple[Path, Path]] = []
|
|
21
44
|
base = Path(base_path)
|
|
22
45
|
backup_dir = base / ".akita" / "backups"
|
|
23
|
-
|
|
46
|
+
|
|
47
|
+
# --- PHASE 1: PRE-FLIGHT VALIDATION (DRY RUN) ---
|
|
48
|
+
print("🛡️ Running Structural Guard (Dry Run)...")
|
|
49
|
+
pending_changes: List[Tuple[Path, List[str]]] = []
|
|
50
|
+
|
|
51
|
+
for patch in patches:
|
|
52
|
+
if not patch.header:
|
|
53
|
+
continue
|
|
54
|
+
|
|
55
|
+
# Resolve path
|
|
56
|
+
rel_path = patch.header.new_path
|
|
57
|
+
is_new = (patch.header.old_path == "/dev/null")
|
|
58
|
+
is_delete = (patch.header.new_path == "/dev/null")
|
|
24
59
|
|
|
25
|
-
|
|
26
|
-
for patch in patches:
|
|
27
|
-
if not patch.header:
|
|
28
|
-
continue
|
|
29
|
-
|
|
30
|
-
# whatthepatch identifies the target file in the header
|
|
31
|
-
# We usually want the 'new' filename (the +++ part)
|
|
60
|
+
if is_new:
|
|
32
61
|
rel_path = patch.header.new_path
|
|
33
|
-
|
|
34
|
-
|
|
62
|
+
elif is_delete:
|
|
63
|
+
rel_path = patch.header.old_path
|
|
64
|
+
else:
|
|
65
|
+
rel_path = patch.header.new_path or patch.header.old_path
|
|
35
66
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
67
|
+
if not rel_path or rel_path == "/dev/null":
|
|
68
|
+
continue
|
|
69
|
+
|
|
70
|
+
# Clean up path
|
|
71
|
+
if rel_path.startswith("a/") or rel_path.startswith("b/"):
|
|
72
|
+
rel_path = rel_path[2:]
|
|
73
|
+
|
|
74
|
+
target_file = (base / rel_path).resolve()
|
|
75
|
+
|
|
76
|
+
# Check existence scenarios
|
|
77
|
+
if not is_new and not target_file.exists():
|
|
78
|
+
print(f"❌ ERROR: Target file {target_file} does not exist.")
|
|
79
|
+
return False
|
|
49
80
|
|
|
50
|
-
|
|
81
|
+
# Read content
|
|
82
|
+
content = ""
|
|
83
|
+
if target_file.exists():
|
|
84
|
+
try:
|
|
85
|
+
with open(target_file, "r", encoding="utf-8") as f:
|
|
86
|
+
content = f.read()
|
|
87
|
+
except UnicodeDecodeError:
|
|
88
|
+
print(f"❌ ERROR: Could not verify context for binary/non-utf8 file: {rel_path}")
|
|
89
|
+
return False
|
|
90
|
+
|
|
91
|
+
# Strict Context Check & Dry Apply
|
|
92
|
+
if not is_new:
|
|
93
|
+
# This uses whatthepatch's internal context verification
|
|
94
|
+
# If it raises HunkApplyException or returns None, it means context mismatch
|
|
95
|
+
try:
|
|
96
|
+
new_lines = whatthepatch.apply_diff(patch, content.splitlines())
|
|
97
|
+
except Exception as e:
|
|
98
|
+
new_lines = None # Treat exception as failure
|
|
51
99
|
|
|
52
|
-
if
|
|
53
|
-
print(f"ERROR:
|
|
100
|
+
if new_lines is None:
|
|
101
|
+
print(f"❌ ERROR: Context Mismatch in {rel_path}.")
|
|
102
|
+
print(" The code the AI 'saw' does not match the file on disk.")
|
|
103
|
+
print(" Action aborted to prevent corruption.")
|
|
54
104
|
return False
|
|
105
|
+
pending_changes.append((target_file, new_lines))
|
|
106
|
+
elif is_new:
|
|
107
|
+
# valid new file
|
|
108
|
+
# reconstruct from patch changes for new file
|
|
109
|
+
new_lines = []
|
|
110
|
+
for change in patch.changes:
|
|
111
|
+
if change.line is not None:
|
|
112
|
+
new_lines.append(change.line)
|
|
113
|
+
pending_changes.append((target_file, new_lines))
|
|
114
|
+
elif is_delete:
|
|
115
|
+
# We mark for deletion by setting new_lines to None
|
|
116
|
+
pending_changes.append((target_file, None))
|
|
117
|
+
|
|
118
|
+
print("✅ Structural Guard Passed. Applying changes...")
|
|
55
119
|
|
|
56
|
-
|
|
120
|
+
# --- PHASE 2: ATOMIC APPLICATION ---
|
|
121
|
+
backups: List[Tuple[Path, Path]] = []
|
|
122
|
+
backup_dir.mkdir(parents=True, exist_ok=True)
|
|
123
|
+
|
|
124
|
+
try:
|
|
125
|
+
for target_file, new_lines in pending_changes:
|
|
126
|
+
# 1. Backup
|
|
57
127
|
if target_file.exists():
|
|
58
128
|
backup_file = backup_dir / f"{target_file.name}.bak"
|
|
59
129
|
shutil.copy2(target_file, backup_file)
|
|
60
130
|
backups.append((target_file, backup_file))
|
|
61
131
|
else:
|
|
62
|
-
backups.append((target_file, None))
|
|
132
|
+
backups.append((target_file, None))
|
|
63
133
|
|
|
64
|
-
# 2.
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
if patched_lines is None:
|
|
75
|
-
print(f"ERROR: Failed to apply patch to {rel_path}.")
|
|
76
|
-
raise Exception(f"Patch failure on {rel_path}")
|
|
77
|
-
|
|
78
|
-
# 3. Write new content
|
|
79
|
-
target_file.parent.mkdir(parents=True, exist_ok=True)
|
|
80
|
-
with open(target_file, "w", encoding="utf-8") as f:
|
|
81
|
-
f.write("\n".join(patched_lines) + "\n")
|
|
82
|
-
|
|
83
|
-
print(f"SUCCESS: Applied {len(patches)} patches successfully.")
|
|
134
|
+
# 2. Write (or delete)
|
|
135
|
+
if new_lines is None: # Delete
|
|
136
|
+
target_file.unlink()
|
|
137
|
+
else:
|
|
138
|
+
target_file.parent.mkdir(parents=True, exist_ok=True)
|
|
139
|
+
with open(target_file, "w", encoding="utf-8") as f:
|
|
140
|
+
f.write("\n".join(new_lines) + "\n")
|
|
141
|
+
|
|
142
|
+
print(f"SUCCESS: Applied changes to {len(pending_changes)} files.")
|
|
84
143
|
|
|
85
|
-
#
|
|
86
|
-
# Run tests to ensure the patch didn't break anything
|
|
144
|
+
# 3. Post-flight Validation (Tests)
|
|
87
145
|
if (base / "tests").exists():
|
|
88
|
-
print("🧪 Running
|
|
146
|
+
print("🧪 Running post-flight validation (pytest)...")
|
|
89
147
|
import subprocess
|
|
90
|
-
# Run pytest in the base_path
|
|
91
148
|
result = subprocess.run(["pytest"], cwd=str(base), capture_output=True, text=True)
|
|
92
149
|
if result.returncode != 0:
|
|
93
150
|
print(f"❌ Validation FAILED:\n{result.stdout}")
|
|
94
|
-
raise Exception("
|
|
151
|
+
raise Exception("Post-flight validation failed. Tests are broken.")
|
|
95
152
|
else:
|
|
96
|
-
print("✅
|
|
153
|
+
print("✅ Post-flight validation passed!")
|
|
97
154
|
|
|
98
155
|
return True
|
|
99
156
|
|
|
100
157
|
except Exception as e:
|
|
101
|
-
print(f"CRITICAL ERROR: {e}. Starting rollback...")
|
|
158
|
+
print(f"CRITICAL ERROR during write: {e}. Starting rollback...")
|
|
102
159
|
for target, backup in backups:
|
|
103
160
|
if backup and backup.exists():
|
|
104
161
|
shutil.move(str(backup), str(target))
|
|
105
162
|
elif not backup and target.exists():
|
|
106
|
-
target.unlink()
|
|
163
|
+
target.unlink()
|
|
107
164
|
return False
|
|
108
165
|
|
|
109
166
|
@staticmethod
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: akitallm
|
|
3
|
+
Version: 1.2.1
|
|
4
|
+
Summary: AkitaLLM: An open-source local-first AI system for programming.
|
|
5
|
+
Author: KerubinDev
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/KerubinDev/AkitaLLM
|
|
8
|
+
Project-URL: Repository, https://github.com/KerubinDev/AkitaLLM
|
|
9
|
+
Project-URL: Issues, https://github.com/KerubinDev/AkitaLLM/issues
|
|
10
|
+
Keywords: ai,cli,programming,local-first,llm
|
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Requires-Python: >=3.10
|
|
19
|
+
Description-Content-Type: text/markdown
|
|
20
|
+
License-File: LICENSE
|
|
21
|
+
Requires-Dist: typer[all]
|
|
22
|
+
Requires-Dist: litellm
|
|
23
|
+
Requires-Dist: pydantic
|
|
24
|
+
Requires-Dist: rich
|
|
25
|
+
Requires-Dist: python-dotenv
|
|
26
|
+
Requires-Dist: pytest
|
|
27
|
+
Requires-Dist: pytest-mock
|
|
28
|
+
Requires-Dist: gitpython
|
|
29
|
+
Requires-Dist: tomli-w
|
|
30
|
+
Requires-Dist: tomli
|
|
31
|
+
Requires-Dist: whatthepatch>=1.0.5
|
|
32
|
+
Requires-Dist: tree-sitter>=0.21.3
|
|
33
|
+
Requires-Dist: tree-sitter-python>=0.21.0
|
|
34
|
+
Dynamic: license-file
|
|
35
|
+
|
|
36
|
+
# AkitaLLM
|
|
37
|
+
|
|
38
|
+

|
|
39
|
+

|
|
40
|
+

|
|
41
|
+
[](https://github.com/KerubinDev/AkitaLLM/actions)
|
|
42
|
+

|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Analyze → Plan → Execute → Validate
|
|
48
|
+
|
|
49
|
+
````
|
|
50
|
+
|
|
51
|
+
**A deterministic, local-first AI orchestrator for software engineers.**
|
|
52
|
+
|
|
53
|
+
AkitaLLM is not a chat interface.
|
|
54
|
+
It is not autocomplete.
|
|
55
|
+
It is not “AI magic”.
|
|
56
|
+
|
|
57
|
+
It is an engineering tool.
|
|
58
|
+
|
|
59
|
+
---
|
|
60
|
+
|
|
61
|
+
## What AkitaLLM is (and what it is not)
|
|
62
|
+
|
|
63
|
+
AkitaLLM treats Large Language Models as **non-deterministic execution engines** that must operate inside a **strict, auditable pipeline**.
|
|
64
|
+
|
|
65
|
+
Instead of asking an AI *“please fix my code”*, you force it to:
|
|
66
|
+
|
|
67
|
+
1. **Analyze** the real project structure
|
|
68
|
+
2. **Plan** concrete technical steps
|
|
69
|
+
3. **Execute** changes as reviewable diffs
|
|
70
|
+
4. **Validate** results with real tooling
|
|
71
|
+
|
|
72
|
+
No hidden prompts.
|
|
73
|
+
No blind edits.
|
|
74
|
+
No guessing.
|
|
75
|
+
|
|
76
|
+
---
|
|
77
|
+
|
|
78
|
+
## Why this project exists
|
|
79
|
+
|
|
80
|
+
Most AI coding tools optimize for **speed of output**.
|
|
81
|
+
|
|
82
|
+
Software engineering optimizes for:
|
|
83
|
+
- correctness
|
|
84
|
+
- predictability
|
|
85
|
+
- debuggability
|
|
86
|
+
- long-term maintainability
|
|
87
|
+
|
|
88
|
+
That mismatch causes real problems:
|
|
89
|
+
|
|
90
|
+
- Code is generated without understanding the project
|
|
91
|
+
- Developers approve changes they don’t fully understand
|
|
92
|
+
- Bugs are pushed faster, not fewer
|
|
93
|
+
|
|
94
|
+
AkitaLLM exists to **slow AI down** and force it to behave like a junior engineer working under strict supervision.
|
|
95
|
+
|
|
96
|
+
---
|
|
97
|
+
|
|
98
|
+
## The core difference
|
|
99
|
+
|
|
100
|
+
| Aspect | Typical AI Tools | AkitaLLM |
|
|
101
|
+
|------|-----------------|----------|
|
|
102
|
+
| Interaction | Chat / Autocomplete | Structured pipeline |
|
|
103
|
+
| Control | Implicit | Explicit and reviewable |
|
|
104
|
+
| Output | Raw code | Unified diffs |
|
|
105
|
+
| Context | Prompt-limited | Project-aware |
|
|
106
|
+
| Validation | Manual | Automated |
|
|
107
|
+
| Philosophy | “Trust the model” | “Trust the process” |
|
|
108
|
+
|
|
109
|
+
---
|
|
110
|
+
|
|
111
|
+
## Design principles
|
|
112
|
+
|
|
113
|
+
**Local-first**
|
|
114
|
+
Your code stays on your machine. AkitaLLM runs locally and only sends what is strictly necessary to the model.
|
|
115
|
+
|
|
116
|
+
**No magic**
|
|
117
|
+
Every decision is logged. Every step is inspectable. Every change is explicit.
|
|
118
|
+
|
|
119
|
+
**Tool-driven**
|
|
120
|
+
The AI uses tools (AST parsing, tests, linters). It does not replace them.
|
|
121
|
+
|
|
122
|
+
**Human-in-the-loop**
|
|
123
|
+
Nothing is applied without your approval.
|
|
124
|
+
|
|
125
|
+
---
|
|
126
|
+
|
|
127
|
+
## What AkitaLLM can do today
|
|
128
|
+
|
|
129
|
+
- 🔍 **Structural code reviews**
|
|
130
|
+
Detect bugs, architectural risks, performance issues, and security problems.
|
|
131
|
+
|
|
132
|
+
- 🧭 **Technical planning**
|
|
133
|
+
Generate step-by-step implementation plans in Markdown.
|
|
134
|
+
|
|
135
|
+
- 🧩 **Diff-based solutions**
|
|
136
|
+
Propose changes as standard unified diffs — no direct file mutation.
|
|
137
|
+
|
|
138
|
+
- 🧪 **Local validation**
|
|
139
|
+
Run tests and tooling before applying changes.
|
|
140
|
+
|
|
141
|
+
- 🔌 **Extensible architecture**
|
|
142
|
+
Plugin system for custom tools and workflows.
|
|
143
|
+
|
|
144
|
+
- 🤖 **Model agnostic**
|
|
145
|
+
Works with OpenAI, Anthropic, Ollama, and any LiteLLM-compatible provider.
|
|
146
|
+
|
|
147
|
+
---
|
|
148
|
+
|
|
149
|
+
## Installation
|
|
150
|
+
|
|
151
|
+
```bash
|
|
152
|
+
pip install akitallm
|
|
153
|
+
````
|
|
154
|
+
|
|
155
|
+
Python 3.10+ required.
|
|
156
|
+
|
|
157
|
+
---
|
|
158
|
+
|
|
159
|
+
## Basic usage
|
|
160
|
+
|
|
161
|
+
### Initialize / Review a project
|
|
162
|
+
|
|
163
|
+
```bash
|
|
164
|
+
akita review .
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
### Generate a technical plan
|
|
168
|
+
|
|
169
|
+
```bash
|
|
170
|
+
akita plan "Refactor authentication to use JWT with refresh tokens"
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
### Solve a concrete problem
|
|
174
|
+
|
|
175
|
+
```bash
|
|
176
|
+
akita solve "Fix silent failures in the reasoning engine error handling"
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
All commands follow the same pipeline:
|
|
180
|
+
|
|
181
|
+
```
|
|
182
|
+
Analyze → Plan → Execute → Validate
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
---
|
|
186
|
+
|
|
187
|
+
## Extending AkitaLLM
|
|
188
|
+
|
|
189
|
+
AkitaLLM is designed to be extended by engineers.
|
|
190
|
+
|
|
191
|
+
* Custom tools
|
|
192
|
+
* Custom validators
|
|
193
|
+
* Custom reasoning steps
|
|
194
|
+
|
|
195
|
+
See the [Plugin Development Guide](PLUGINS.md).
|
|
196
|
+
|
|
197
|
+
---
|
|
198
|
+
|
|
199
|
+
## Contributing
|
|
200
|
+
|
|
201
|
+
AkitaLLM is not looking for volume.
|
|
202
|
+
It is looking for **engineering-quality contributions**.
|
|
203
|
+
|
|
204
|
+
If you care about:
|
|
205
|
+
|
|
206
|
+
* clean abstractions
|
|
207
|
+
* predictable systems
|
|
208
|
+
* readable diffs
|
|
209
|
+
* testable behavior
|
|
210
|
+
|
|
211
|
+
You’ll fit right in.
|
|
212
|
+
|
|
213
|
+
See [CONTRIBUTING.md](CONTRIBUTING.md).
|
|
214
|
+
|
|
215
|
+
---
|
|
216
|
+
|
|
217
|
+
> “Understanding the internals is the first step to excellence.”
|
|
@@ -1,24 +1,26 @@
|
|
|
1
|
-
akita/__init__.py,sha256=
|
|
2
|
-
akita/cli/
|
|
1
|
+
akita/__init__.py,sha256=Mlm4Gvmb_6yQxwUbv2Ksc-BJFXLPg9H1Vt2iV7wXrA4,22
|
|
2
|
+
akita/cli/doctor.py,sha256=7AgcxPz5i91XkAm6fEKEAjM-YHl-qAu87ArQiSPfJ80,5149
|
|
3
|
+
akita/cli/main.py,sha256=rk0u4IhVD2_5rOBekuPlKGVRDsGJ1bIAPqiyLtxA2Zc,16764
|
|
3
4
|
akita/core/ast_utils.py,sha256=8JrTZgfWjIvbzY5KzV2G9PuyOi8IxVdLMjDCPPLiz_I,3127
|
|
4
|
-
akita/core/config.py,sha256=
|
|
5
|
+
akita/core/config.py,sha256=JlROCAxV3uHgKexClr3vr0Zrfmjg5BUa46bnCi_1K-M,1682
|
|
6
|
+
akita/core/i18n.py,sha256=paM3tXuc6-gh9IB5KZwZ55a_s5vDt5ANrvOGnej23YY,9893
|
|
5
7
|
akita/core/indexing.py,sha256=2j_NK8buZ1ugH3foa9KFQEtGOD-Lgoo2Se3Lx6Q7ZO4,3686
|
|
6
8
|
akita/core/plugins.py,sha256=P3azOFJ-yTw-kDdvjmHfNiU7nfvXQFadVPRnp1O7h-c,2951
|
|
7
9
|
akita/core/providers.py,sha256=SwCb2aTYJ-iNtWdah9IYzLf3vfTDA472i5nhJmsaZLs,6168
|
|
8
10
|
akita/core/trace.py,sha256=AxXUVZ7P8a0l5QTK5w9iSnUncUe62FGfRzDNN9xG5dg,692
|
|
9
|
-
akita/models/base.py,sha256=
|
|
11
|
+
akita/models/base.py,sha256=qIP7FxI8D5osZCHvosgd_64QgGoS_9bN4PJoBOs9NSM,2013
|
|
10
12
|
akita/plugins/__init__.py,sha256=kfjmQqBhzhqQrH-Rd0jh0KxXyIT9T5DtEh-BETQD0FM,28
|
|
11
13
|
akita/plugins/files.py,sha256=Ha4YxmCz2G7iafqdr2TRE_xRlq1oeOBo6By3_S86jkE,1113
|
|
12
|
-
akita/reasoning/engine.py,sha256=
|
|
14
|
+
akita/reasoning/engine.py,sha256=GXw-2829O_ePnOKhGeiFw415ZUW3_C6-9Irw8hF7m1M,10081
|
|
13
15
|
akita/reasoning/session.py,sha256=rcJxcJXNjObjRwfuCY8NQKpKCqxeIppqkUpN-3mVRpE,472
|
|
14
16
|
akita/schemas/review.py,sha256=zzjLzTuiEpJfu4etS0NUBWfS3wyNobNDmDMhb5amWTI,905
|
|
15
17
|
akita/tools/base.py,sha256=jDA3jTP2qo6TjoTF6BSIb71BSfCJGSqbueIQz6lxuCM,1235
|
|
16
18
|
akita/tools/context.py,sha256=i6QjKMsKCZMIdCx82hkhMUzBQJolrcch2v1x-6nLy8U,5008
|
|
17
|
-
akita/tools/diff.py,sha256=
|
|
19
|
+
akita/tools/diff.py,sha256=d5Q3yRvCKvaCTHW3PibAu1n4AXuFq1zcEeIFD_jxhbo,6942
|
|
18
20
|
akita/tools/git.py,sha256=58ZCI2ZL7NYUQdRIe3969t6gRpVmCPD8B-UbP7cPBNY,2798
|
|
19
|
-
akitallm-1.
|
|
20
|
-
akitallm-1.
|
|
21
|
-
akitallm-1.
|
|
22
|
-
akitallm-1.
|
|
23
|
-
akitallm-1.
|
|
24
|
-
akitallm-1.
|
|
21
|
+
akitallm-1.2.1.dist-info/licenses/LICENSE,sha256=WE7_tfGR-IzkulSh6Pos02gucCXKboaXguAdr0bI9V0,1067
|
|
22
|
+
akitallm-1.2.1.dist-info/METADATA,sha256=J91P4UIErpPmKWGGh-gGWUbIpTL3L3UP4bmU_SSCrFg,5274
|
|
23
|
+
akitallm-1.2.1.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
|
|
24
|
+
akitallm-1.2.1.dist-info/entry_points.txt,sha256=Au1aAXCO2lX4kgElgknSVDpq7BcN5xAJJ0WvOAkhLzU,105
|
|
25
|
+
akitallm-1.2.1.dist-info/top_level.txt,sha256=duGU-i6qCRLqjo_b1XUqfhlSQky3QIO0Hlvfn2OV3hU,6
|
|
26
|
+
akitallm-1.2.1.dist-info/RECORD,,
|