akitallm 0.1.0__tar.gz → 1.0.3__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.
- akitallm-1.0.3/PKG-INFO +140 -0
- akitallm-1.0.3/README.md +105 -0
- akitallm-1.0.3/akita/__init__.py +1 -0
- {akitallm-0.1.0 → akitallm-1.0.3}/akita/cli/main.py +75 -10
- akitallm-1.0.3/akita/core/ast_utils.py +77 -0
- akitallm-1.0.3/akita/core/indexing.py +94 -0
- akitallm-1.0.3/akita/core/plugins.py +81 -0
- akitallm-1.0.3/akita/core/trace.py +18 -0
- akitallm-1.0.3/akita/plugins/__init__.py +1 -0
- akitallm-1.0.3/akita/plugins/files.py +34 -0
- {akitallm-0.1.0 → akitallm-1.0.3}/akita/reasoning/engine.py +44 -18
- akitallm-1.0.3/akita/reasoning/session.py +15 -0
- {akitallm-0.1.0 → akitallm-1.0.3}/akita/tools/base.py +6 -1
- {akitallm-0.1.0 → akitallm-1.0.3}/akita/tools/context.py +54 -9
- akitallm-1.0.3/akita/tools/diff.py +116 -0
- akitallm-1.0.3/akitallm.egg-info/PKG-INFO +140 -0
- {akitallm-0.1.0 → akitallm-1.0.3}/akitallm.egg-info/SOURCES.txt +11 -0
- akitallm-1.0.3/akitallm.egg-info/entry_points.txt +5 -0
- {akitallm-0.1.0 → akitallm-1.0.3}/akitallm.egg-info/requires.txt +3 -0
- {akitallm-0.1.0 → akitallm-1.0.3}/pyproject.toml +7 -1
- akitallm-1.0.3/tests/test_ast.py +50 -0
- {akitallm-0.1.0 → akitallm-1.0.3}/tests/test_basic.py +1 -1
- akitallm-1.0.3/tests/test_diff.py +68 -0
- akitallm-1.0.3/tests/test_interactive.py +33 -0
- akitallm-1.0.3/tests/test_plugins.py +41 -0
- akitallm-0.1.0/PKG-INFO +0 -111
- akitallm-0.1.0/README.md +0 -79
- akitallm-0.1.0/akita/__init__.py +0 -1
- akitallm-0.1.0/akita/tools/diff.py +0 -41
- akitallm-0.1.0/akitallm.egg-info/PKG-INFO +0 -111
- akitallm-0.1.0/akitallm.egg-info/entry_points.txt +0 -2
- {akitallm-0.1.0 → akitallm-1.0.3}/LICENSE +0 -0
- {akitallm-0.1.0 → akitallm-1.0.3}/akita/core/config.py +0 -0
- {akitallm-0.1.0 → akitallm-1.0.3}/akita/models/base.py +0 -0
- {akitallm-0.1.0 → akitallm-1.0.3}/akita/schemas/review.py +0 -0
- {akitallm-0.1.0 → akitallm-1.0.3}/akitallm.egg-info/dependency_links.txt +0 -0
- {akitallm-0.1.0 → akitallm-1.0.3}/akitallm.egg-info/top_level.txt +0 -0
- {akitallm-0.1.0 → akitallm-1.0.3}/setup.cfg +0 -0
- {akitallm-0.1.0 → akitallm-1.0.3}/tests/test_review_mock.py +0 -0
akitallm-1.0.3/PKG-INFO
ADDED
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: akitallm
|
|
3
|
+
Version: 1.0.3
|
|
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
|
+
### A deterministic, local-first AI orchestrator for software engineers.
|
|
38
|
+
|
|
39
|
+
---
|
|
40
|
+
|
|
41
|
+
## What is AkitaLLM?
|
|
42
|
+
|
|
43
|
+
AkitaLLM is not another "AI wrapper." It is a command-line utility designed for developers who value engineering rigor over generative "magic." It treats Large Language Models as non-deterministic execution engines that must be constrained within a strict, auditable pipeline: **Analyze → Plan → Execute → Validate**.
|
|
44
|
+
|
|
45
|
+
Built as a local-first tool, it provides you with an AI-augmented workflow that respects your project's context, follows security best practices, and prioritizes structured output over conversational noise.
|
|
46
|
+
|
|
47
|
+
---
|
|
48
|
+
|
|
49
|
+
## Why AkitaLLM exists
|
|
50
|
+
|
|
51
|
+
Most current AI tools (ChatGPT, Copilot, Cursor) operate in a "black-box" conversational mode. They are excellent at text generation but often fail at **software engineering**, which requires:
|
|
52
|
+
- **Project-Level Context**: Understanding how a change in `utils.py` affects `main.py`.
|
|
53
|
+
- **Previsibilty**: Knowing exactly what the AI intends to do before it modifies a single byte.
|
|
54
|
+
- **Verification**: Automatically ensuring that proposed changes don't break existing logic.
|
|
55
|
+
|
|
56
|
+
AkitaLLM was built to bridge this gap, treating AI as a component of a larger, human-controlled engineering process.
|
|
57
|
+
|
|
58
|
+
---
|
|
59
|
+
|
|
60
|
+
## The Engineering Difference
|
|
61
|
+
|
|
62
|
+
| Feature | Generic AI Tools | AkitaLLM |
|
|
63
|
+
| :--- | :--- | :--- |
|
|
64
|
+
| **Logic** | Conversational / Guesswork | Analyze → Plan → Execute → Validate |
|
|
65
|
+
| **Control** | Autocomplete / Chat | Explicit technical plans & reviewable Diffs |
|
|
66
|
+
| **Security** | Cloud-heavy | Local-first, respects `.gitignore` and `.env` |
|
|
67
|
+
| **Validation** | Post-facto manual review | Automated local test execution |
|
|
68
|
+
| **Philosophy** | "It just works" (Hype) | "Understand the internals" (Engineering) |
|
|
69
|
+
|
|
70
|
+
---
|
|
71
|
+
|
|
72
|
+
## Core Principles
|
|
73
|
+
|
|
74
|
+
1. **Local-First**: Your code remains on your machine. AkitaLLM orchestrates local models (via Ollama) or remote APIs (via LiteLLM) through encrypted, controlled channels.
|
|
75
|
+
2. **Contextual Awareness**: It uses recursive file scanning and structure analysis to build a high-fidelity map of your project before making suggestions.
|
|
76
|
+
3. **No Magic**: No hidden prompts, no mysterious "thinking" phases. All actions are logged, auditable, and based on standard engineering patterns.
|
|
77
|
+
4. **Tool-Driven**: AI is a user of tools (linters, test runners, AST parsers), not a replacement for them.
|
|
78
|
+
|
|
79
|
+
---
|
|
80
|
+
|
|
81
|
+
## Key Features
|
|
82
|
+
|
|
83
|
+
- **Structural Code Review**: Detailed analysis of bugs, style, performance, and security risks with prioritized severity levels.
|
|
84
|
+
- **Technical Planning**: Generation of step-by-step implementation plans in Markdown for complex feature requests.
|
|
85
|
+
- **Actionable Diffs**: Proposed changes are generated as standard Unified Diffs for human review before application.
|
|
86
|
+
- **Environment Isolation**: Supports `.env` and local configuration storage (`~/.akita/`) to keep secrets safe.
|
|
87
|
+
- **Model Agnostic**: Seamlessly switch between GPT-4o, Claude 3.5, Llama 3, and more.
|
|
88
|
+
|
|
89
|
+
---
|
|
90
|
+
|
|
91
|
+
## Installation
|
|
92
|
+
|
|
93
|
+
AkitaLLM is available on PyPI. You can install it directly using pip:
|
|
94
|
+
|
|
95
|
+
```bash
|
|
96
|
+
pip install akitallm
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
---
|
|
100
|
+
|
|
101
|
+
## Usage
|
|
102
|
+
|
|
103
|
+
### 1. Project Initialization
|
|
104
|
+
Run any command to trigger the initial configuration and onboarding.
|
|
105
|
+
```bash
|
|
106
|
+
akita review .
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
### 2. Strategic Code Review
|
|
110
|
+
Analyze a directory for potential architectural risks and bugs.
|
|
111
|
+
```bash
|
|
112
|
+
akita review src/
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
### 3. Implementation Planning
|
|
116
|
+
Generate a technical plan for a specific goal.
|
|
117
|
+
```bash
|
|
118
|
+
akita plan "Implement JWT authentication with Redis-based session storage"
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
### 4. Code Problem Solving
|
|
122
|
+
Generate a diff to solve a precise issue or refactor a module.
|
|
123
|
+
```bash
|
|
124
|
+
akita solve "Improve error handling in the reasoning engine to prevent silent failures"
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
---
|
|
128
|
+
|
|
129
|
+
### 🔌 Extensibility
|
|
130
|
+
AkitaLLM is built to be extended. You can create your own tools and plugins. Check the [Plugin Development Guide](PLUGINS.md) for more details.
|
|
131
|
+
|
|
132
|
+
## 🤝 Contributing
|
|
133
|
+
|
|
134
|
+
We are looking for engineers, not just coders. If you value robust abstractions, clean code, and predictable systems, your contribution is welcome.
|
|
135
|
+
|
|
136
|
+
Review our [CONTRIBUTING.md](CONTRIBUTING.md) to understand our engineering standards and PR workflow. High-quality PRs with test coverage are prioritized.
|
|
137
|
+
|
|
138
|
+
---
|
|
139
|
+
|
|
140
|
+
*“Understanding the internals is the first step to excellence.”*
|
akitallm-1.0.3/README.md
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
# AkitaLLM
|
|
2
|
+
### A deterministic, local-first AI orchestrator for software engineers.
|
|
3
|
+
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
## What is AkitaLLM?
|
|
7
|
+
|
|
8
|
+
AkitaLLM is not another "AI wrapper." It is a command-line utility designed for developers who value engineering rigor over generative "magic." It treats Large Language Models as non-deterministic execution engines that must be constrained within a strict, auditable pipeline: **Analyze → Plan → Execute → Validate**.
|
|
9
|
+
|
|
10
|
+
Built as a local-first tool, it provides you with an AI-augmented workflow that respects your project's context, follows security best practices, and prioritizes structured output over conversational noise.
|
|
11
|
+
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
## Why AkitaLLM exists
|
|
15
|
+
|
|
16
|
+
Most current AI tools (ChatGPT, Copilot, Cursor) operate in a "black-box" conversational mode. They are excellent at text generation but often fail at **software engineering**, which requires:
|
|
17
|
+
- **Project-Level Context**: Understanding how a change in `utils.py` affects `main.py`.
|
|
18
|
+
- **Previsibilty**: Knowing exactly what the AI intends to do before it modifies a single byte.
|
|
19
|
+
- **Verification**: Automatically ensuring that proposed changes don't break existing logic.
|
|
20
|
+
|
|
21
|
+
AkitaLLM was built to bridge this gap, treating AI as a component of a larger, human-controlled engineering process.
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
## The Engineering Difference
|
|
26
|
+
|
|
27
|
+
| Feature | Generic AI Tools | AkitaLLM |
|
|
28
|
+
| :--- | :--- | :--- |
|
|
29
|
+
| **Logic** | Conversational / Guesswork | Analyze → Plan → Execute → Validate |
|
|
30
|
+
| **Control** | Autocomplete / Chat | Explicit technical plans & reviewable Diffs |
|
|
31
|
+
| **Security** | Cloud-heavy | Local-first, respects `.gitignore` and `.env` |
|
|
32
|
+
| **Validation** | Post-facto manual review | Automated local test execution |
|
|
33
|
+
| **Philosophy** | "It just works" (Hype) | "Understand the internals" (Engineering) |
|
|
34
|
+
|
|
35
|
+
---
|
|
36
|
+
|
|
37
|
+
## Core Principles
|
|
38
|
+
|
|
39
|
+
1. **Local-First**: Your code remains on your machine. AkitaLLM orchestrates local models (via Ollama) or remote APIs (via LiteLLM) through encrypted, controlled channels.
|
|
40
|
+
2. **Contextual Awareness**: It uses recursive file scanning and structure analysis to build a high-fidelity map of your project before making suggestions.
|
|
41
|
+
3. **No Magic**: No hidden prompts, no mysterious "thinking" phases. All actions are logged, auditable, and based on standard engineering patterns.
|
|
42
|
+
4. **Tool-Driven**: AI is a user of tools (linters, test runners, AST parsers), not a replacement for them.
|
|
43
|
+
|
|
44
|
+
---
|
|
45
|
+
|
|
46
|
+
## Key Features
|
|
47
|
+
|
|
48
|
+
- **Structural Code Review**: Detailed analysis of bugs, style, performance, and security risks with prioritized severity levels.
|
|
49
|
+
- **Technical Planning**: Generation of step-by-step implementation plans in Markdown for complex feature requests.
|
|
50
|
+
- **Actionable Diffs**: Proposed changes are generated as standard Unified Diffs for human review before application.
|
|
51
|
+
- **Environment Isolation**: Supports `.env` and local configuration storage (`~/.akita/`) to keep secrets safe.
|
|
52
|
+
- **Model Agnostic**: Seamlessly switch between GPT-4o, Claude 3.5, Llama 3, and more.
|
|
53
|
+
|
|
54
|
+
---
|
|
55
|
+
|
|
56
|
+
## Installation
|
|
57
|
+
|
|
58
|
+
AkitaLLM is available on PyPI. You can install it directly using pip:
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
pip install akitallm
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
---
|
|
65
|
+
|
|
66
|
+
## Usage
|
|
67
|
+
|
|
68
|
+
### 1. Project Initialization
|
|
69
|
+
Run any command to trigger the initial configuration and onboarding.
|
|
70
|
+
```bash
|
|
71
|
+
akita review .
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
### 2. Strategic Code Review
|
|
75
|
+
Analyze a directory for potential architectural risks and bugs.
|
|
76
|
+
```bash
|
|
77
|
+
akita review src/
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
### 3. Implementation Planning
|
|
81
|
+
Generate a technical plan for a specific goal.
|
|
82
|
+
```bash
|
|
83
|
+
akita plan "Implement JWT authentication with Redis-based session storage"
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
### 4. Code Problem Solving
|
|
87
|
+
Generate a diff to solve a precise issue or refactor a module.
|
|
88
|
+
```bash
|
|
89
|
+
akita solve "Improve error handling in the reasoning engine to prevent silent failures"
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
---
|
|
93
|
+
|
|
94
|
+
### 🔌 Extensibility
|
|
95
|
+
AkitaLLM is built to be extended. You can create your own tools and plugins. Check the [Plugin Development Guide](PLUGINS.md) for more details.
|
|
96
|
+
|
|
97
|
+
## 🤝 Contributing
|
|
98
|
+
|
|
99
|
+
We are looking for engineers, not just coders. If you value robust abstractions, clean code, and predictable systems, your contribution is welcome.
|
|
100
|
+
|
|
101
|
+
Review our [CONTRIBUTING.md](CONTRIBUTING.md) to understand our engineering standards and PR workflow. High-quality PRs with test coverage are prioritized.
|
|
102
|
+
|
|
103
|
+
---
|
|
104
|
+
|
|
105
|
+
*“Understanding the internals is the first step to excellence.”*
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.1.1"
|
|
@@ -2,12 +2,14 @@ import typer
|
|
|
2
2
|
from rich.console import Console
|
|
3
3
|
from rich.panel import Panel
|
|
4
4
|
from akita.reasoning.engine import ReasoningEngine
|
|
5
|
+
from akita.core.indexing import CodeIndexer
|
|
5
6
|
from akita.models.base import get_model
|
|
6
7
|
from akita.core.config import load_config, save_config, reset_config, CONFIG_FILE
|
|
7
8
|
from rich.table import Table
|
|
8
9
|
from rich.markdown import Markdown
|
|
9
10
|
from rich.syntax import Syntax
|
|
10
11
|
from dotenv import load_dotenv
|
|
12
|
+
from akita.tools.diff import DiffApplier
|
|
11
13
|
|
|
12
14
|
# Load environment variables from .env file
|
|
13
15
|
load_dotenv()
|
|
@@ -119,27 +121,53 @@ def review(
|
|
|
119
121
|
@app.command()
|
|
120
122
|
def solve(
|
|
121
123
|
query: str,
|
|
124
|
+
interactive: bool = typer.Option(False, "--interactive", "-i", help="Run in interactive mode to refine the solution."),
|
|
125
|
+
trace: bool = typer.Option(False, "--trace", help="Show the internal reasoning trace."),
|
|
122
126
|
dry_run: bool = typer.Option(False, "--dry-run", help="Run in dry-run mode.")
|
|
123
127
|
):
|
|
124
128
|
"""
|
|
125
|
-
Generate a solution for the given query.
|
|
129
|
+
Generate and apply a solution for the given query.
|
|
126
130
|
"""
|
|
127
131
|
model = get_model()
|
|
128
132
|
engine = ReasoningEngine(model)
|
|
129
133
|
console.print(Panel(f"[bold blue]Akita[/] is thinking about: [italic]{query}[/]", title="Solve Mode"))
|
|
130
134
|
|
|
135
|
+
current_query = query
|
|
136
|
+
session = None
|
|
137
|
+
|
|
131
138
|
try:
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
139
|
+
while True:
|
|
140
|
+
diff_output = engine.run_solve(current_query, session=session)
|
|
141
|
+
session = engine.session
|
|
142
|
+
|
|
143
|
+
if trace:
|
|
144
|
+
console.print(Panel(str(engine.trace), title="[bold cyan]Reasoning Trace[/]", border_style="cyan"))
|
|
145
|
+
console.print(Panel("[bold green]Suggested Code Changes (Unified Diff):[/]"))
|
|
146
|
+
syntax = Syntax(diff_output, "diff", theme="monokai", line_numbers=True)
|
|
147
|
+
console.print(syntax)
|
|
148
|
+
|
|
149
|
+
if interactive:
|
|
150
|
+
action = typer.prompt("\n[A]pprove, [R]efine with feedback, or [C]ancel?", default="A").upper()
|
|
151
|
+
if action == "A":
|
|
152
|
+
break
|
|
153
|
+
elif action == "R":
|
|
154
|
+
current_query = typer.prompt("Enter your feedback/refinement")
|
|
155
|
+
continue
|
|
156
|
+
else:
|
|
157
|
+
console.print("[yellow]Operation cancelled.[/]")
|
|
158
|
+
return
|
|
159
|
+
else:
|
|
160
|
+
break
|
|
161
|
+
|
|
138
162
|
if not dry_run:
|
|
139
163
|
confirm = typer.confirm("\nDo you want to apply these changes?")
|
|
140
164
|
if confirm:
|
|
141
|
-
console.print("[bold yellow]Applying changes...
|
|
142
|
-
|
|
165
|
+
console.print("[bold yellow]🚀 Applying changes...[/]")
|
|
166
|
+
success = DiffApplier.apply_unified_diff(diff_output)
|
|
167
|
+
if success:
|
|
168
|
+
console.print("[bold green]✅ Changes applied successfully![/]")
|
|
169
|
+
else:
|
|
170
|
+
console.print("[bold red]❌ Failed to apply changes.[/]")
|
|
143
171
|
else:
|
|
144
172
|
console.print("[bold yellow]Changes discarded.[/]")
|
|
145
173
|
except Exception as e:
|
|
@@ -165,12 +193,29 @@ def plan(
|
|
|
165
193
|
console.print(f"[bold red]Planning failed:[/] {e}")
|
|
166
194
|
raise typer.Exit(code=1)
|
|
167
195
|
|
|
196
|
+
@app.command()
|
|
197
|
+
def index(
|
|
198
|
+
path: str = typer.Argument(".", help="Path to index for RAG.")
|
|
199
|
+
):
|
|
200
|
+
"""
|
|
201
|
+
Build a local vector index (RAG) for the project.
|
|
202
|
+
"""
|
|
203
|
+
console.print(Panel(f"🔍 [bold blue]Akita[/] is indexing: [yellow]{path}[/]", title="Index Mode"))
|
|
204
|
+
try:
|
|
205
|
+
indexer = CodeIndexer(path)
|
|
206
|
+
with console.status("[bold green]Indexing project files..."):
|
|
207
|
+
indexer.index_project()
|
|
208
|
+
console.print("[bold green]✅ Indexing complete! Semantic search is now active.[/]")
|
|
209
|
+
except Exception as e:
|
|
210
|
+
console.print(f"[bold red]Indexing failed:[/] {e}")
|
|
211
|
+
raise typer.Exit(code=1)
|
|
212
|
+
|
|
168
213
|
@app.command()
|
|
169
214
|
def test():
|
|
170
215
|
"""
|
|
171
216
|
Run automated tests in the project.
|
|
172
217
|
"""
|
|
173
|
-
console.print(Panel("
|
|
218
|
+
console.print(Panel("[bold blue]Akita[/] is running tests...", title="Test Mode"))
|
|
174
219
|
from akita.tools.base import ShellTools
|
|
175
220
|
result = ShellTools.execute("pytest")
|
|
176
221
|
if result.success:
|
|
@@ -180,6 +225,26 @@ def test():
|
|
|
180
225
|
console.print("[bold red]Tests failed![/]")
|
|
181
226
|
console.print(result.error or result.output)
|
|
182
227
|
|
|
228
|
+
@app.command()
|
|
229
|
+
def docs():
|
|
230
|
+
"""
|
|
231
|
+
Start the local documentation server.
|
|
232
|
+
"""
|
|
233
|
+
import subprocess
|
|
234
|
+
import sys
|
|
235
|
+
|
|
236
|
+
console.print(Panel("[bold blue]Akita[/] Documentation", title="Docs Mode"))
|
|
237
|
+
console.print("[dim]Starting MkDocs server...[/]")
|
|
238
|
+
console.print("[bold green]Open your browser at: http://127.0.0.1:8000[/]")
|
|
239
|
+
|
|
240
|
+
try:
|
|
241
|
+
subprocess.run([sys.executable, "-m", "mkdocs", "serve"], check=True)
|
|
242
|
+
except FileNotFoundError:
|
|
243
|
+
console.print("[red]MkDocs not found. Install it with: pip install mkdocs-material[/]")
|
|
244
|
+
raise typer.Exit(code=1)
|
|
245
|
+
except KeyboardInterrupt:
|
|
246
|
+
console.print("[yellow]Documentation server stopped.[/]")
|
|
247
|
+
|
|
183
248
|
# Config Command Group
|
|
184
249
|
config_app = typer.Typer(help="Manage AkitaLLM configuration.")
|
|
185
250
|
app.add_typer(config_app, name="config")
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import tree_sitter_python as tspython
|
|
2
|
+
from tree_sitter import Language, Parser
|
|
3
|
+
import pathlib
|
|
4
|
+
from typing import List, Dict, Any, Optional
|
|
5
|
+
|
|
6
|
+
class ASTParser:
|
|
7
|
+
def __init__(self):
|
|
8
|
+
self.language = Language(tspython.language())
|
|
9
|
+
self.parser = Parser(self.language)
|
|
10
|
+
|
|
11
|
+
def parse_file(self, file_path: str) -> Optional[Any]:
|
|
12
|
+
path = pathlib.Path(file_path)
|
|
13
|
+
if not path.exists():
|
|
14
|
+
return None
|
|
15
|
+
|
|
16
|
+
with open(path, "rb") as f:
|
|
17
|
+
content = f.read()
|
|
18
|
+
|
|
19
|
+
return self.parser.parse(content)
|
|
20
|
+
|
|
21
|
+
def get_definitions(self, file_path: str) -> List[Dict[str, Any]]:
|
|
22
|
+
"""Extract classes and functions with their line ranges using recursive traversal."""
|
|
23
|
+
tree = self.parse_file(file_path)
|
|
24
|
+
if not tree:
|
|
25
|
+
return []
|
|
26
|
+
|
|
27
|
+
with open(file_path, "rb") as f:
|
|
28
|
+
content = f.read()
|
|
29
|
+
|
|
30
|
+
definitions = []
|
|
31
|
+
|
|
32
|
+
def explore(node):
|
|
33
|
+
# Check for definitions
|
|
34
|
+
if node.type in ["class_definition", "function_definition", "decorated_definition"]:
|
|
35
|
+
# Find name
|
|
36
|
+
name_node = node.child_by_field_name("name")
|
|
37
|
+
if not name_node and node.type == "decorated_definition":
|
|
38
|
+
# For decorated definitions, the name is in the class/function child
|
|
39
|
+
inner = node.child_by_field_name("definition")
|
|
40
|
+
if inner:
|
|
41
|
+
name_node = inner.child_by_field_name("name")
|
|
42
|
+
|
|
43
|
+
name = content[name_node.start_byte:name_node.end_byte].decode("utf-8") if name_node else "anonymous"
|
|
44
|
+
|
|
45
|
+
# Docstring extraction
|
|
46
|
+
docstring = None
|
|
47
|
+
body = node.child_by_field_name("body")
|
|
48
|
+
if body and body.children:
|
|
49
|
+
for stmt in body.children:
|
|
50
|
+
if stmt.type == "expression_statement":
|
|
51
|
+
child = stmt.children[0]
|
|
52
|
+
if child.type == "string":
|
|
53
|
+
docstring = content[child.start_byte:child.end_byte].decode("utf-8").strip('"\' \n')
|
|
54
|
+
break # Only first statement
|
|
55
|
+
|
|
56
|
+
definitions.append({
|
|
57
|
+
"name": name,
|
|
58
|
+
"type": "class" if "class" in node.type else "function",
|
|
59
|
+
"start_line": node.start_point[0] + 1,
|
|
60
|
+
"end_line": node.end_point[0] + 1,
|
|
61
|
+
"docstring": docstring
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
# Always explore children regardless of current node type
|
|
65
|
+
for child in node.children:
|
|
66
|
+
explore(child)
|
|
67
|
+
|
|
68
|
+
explore(tree.root_node)
|
|
69
|
+
return definitions
|
|
70
|
+
|
|
71
|
+
def get_source_segment(self, file_path: str, start_line: int, end_line: int) -> str:
|
|
72
|
+
"""Extract a segment of code from a file by line numbers."""
|
|
73
|
+
with open(file_path, "r", encoding="utf-8") as f:
|
|
74
|
+
lines = f.readlines()
|
|
75
|
+
|
|
76
|
+
# Lines are 1-indexed in our definitions, but 0-indexed in the list
|
|
77
|
+
return "".join(lines[start_line-1 : end_line])
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import json
|
|
3
|
+
import re
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from typing import List, Dict, Any, Optional
|
|
6
|
+
from akita.core.ast_utils import ASTParser
|
|
7
|
+
|
|
8
|
+
class CodeIndexer:
|
|
9
|
+
"""
|
|
10
|
+
A lightweight, zero-dependency semantic-keyword indexer.
|
|
11
|
+
Uses basic TF-IDF principles and AST-aware keyword weighting.
|
|
12
|
+
Works perfectly even in restricted environments like Python 3.14.
|
|
13
|
+
"""
|
|
14
|
+
def __init__(self, project_path: str):
|
|
15
|
+
self.project_path = Path(project_path)
|
|
16
|
+
self.index_file = self.project_path / ".akita" / "index.json"
|
|
17
|
+
self.ast_parser = ASTParser()
|
|
18
|
+
self.data: List[Dict[str, Any]] = []
|
|
19
|
+
self.load_index()
|
|
20
|
+
|
|
21
|
+
def load_index(self):
|
|
22
|
+
if self.index_file.exists():
|
|
23
|
+
try:
|
|
24
|
+
with open(self.index_file, "r", encoding="utf-8") as f:
|
|
25
|
+
self.data = json.load(f)
|
|
26
|
+
except Exception:
|
|
27
|
+
self.data = []
|
|
28
|
+
|
|
29
|
+
def save_index(self):
|
|
30
|
+
self.index_file.parent.mkdir(parents=True, exist_ok=True)
|
|
31
|
+
with open(self.index_file, "w", encoding="utf-8") as f:
|
|
32
|
+
json.dump(self.data, f, indent=2)
|
|
33
|
+
|
|
34
|
+
def index_project(self):
|
|
35
|
+
"""Index all Python files in the project."""
|
|
36
|
+
self.data = []
|
|
37
|
+
for root, _, files in os.walk(self.project_path):
|
|
38
|
+
if ".akita" in root or ".git" in root or "__pycache__" in root:
|
|
39
|
+
continue
|
|
40
|
+
|
|
41
|
+
for file in files:
|
|
42
|
+
if file.endswith(".py"):
|
|
43
|
+
full_path = Path(root) / file
|
|
44
|
+
rel_path = full_path.relative_to(self.project_path)
|
|
45
|
+
self._index_file(full_path, str(rel_path))
|
|
46
|
+
self.save_index()
|
|
47
|
+
|
|
48
|
+
def _index_file(self, file_path: Path, rel_path: str):
|
|
49
|
+
try:
|
|
50
|
+
definitions = self.ast_parser.get_definitions(str(file_path))
|
|
51
|
+
for d in definitions:
|
|
52
|
+
source = self.ast_parser.get_source_segment(
|
|
53
|
+
str(file_path), d["start_line"], d["end_line"]
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
# Create a searchable representation (keyword rich)
|
|
57
|
+
# We normalize case and extract meaningful words
|
|
58
|
+
search_blob = f"{d['name']} {d['type']} {d['docstring'] or ''} {source}"
|
|
59
|
+
keywords = set(re.findall(r'\w+', search_blob.lower()))
|
|
60
|
+
|
|
61
|
+
self.data.append({
|
|
62
|
+
"path": rel_path,
|
|
63
|
+
"name": d["name"],
|
|
64
|
+
"type": d["type"],
|
|
65
|
+
"start_line": d["start_line"],
|
|
66
|
+
"end_line": d["end_line"],
|
|
67
|
+
"keywords": list(keywords),
|
|
68
|
+
"content": source[:500] # Store snippet preview
|
|
69
|
+
})
|
|
70
|
+
except Exception:
|
|
71
|
+
pass
|
|
72
|
+
|
|
73
|
+
def search(self, query: str, n_results: int = 5) -> List[Dict[str, Any]]:
|
|
74
|
+
"""Search using Jaccard Similarity on keywords (Lite Contextual Search)."""
|
|
75
|
+
query_keywords = set(re.findall(r'\w+', query.lower()))
|
|
76
|
+
if not query_keywords:
|
|
77
|
+
return []
|
|
78
|
+
|
|
79
|
+
scores = []
|
|
80
|
+
for item in self.data:
|
|
81
|
+
item_keywords = set(item["keywords"])
|
|
82
|
+
intersection = query_keywords.intersection(item_keywords)
|
|
83
|
+
# Simple intersection count as score, weighted by name match
|
|
84
|
+
score = len(intersection)
|
|
85
|
+
if any(qk in item["name"].lower() for qk in query_keywords):
|
|
86
|
+
score += 5 # Boost explicit name matches
|
|
87
|
+
|
|
88
|
+
if score > 0:
|
|
89
|
+
scores.append((score, item))
|
|
90
|
+
|
|
91
|
+
# Sort by score descending
|
|
92
|
+
scores.sort(key=lambda x: x[0], reverse=True)
|
|
93
|
+
|
|
94
|
+
return [s[1] for s in scores[:n_results]]
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import abc
|
|
2
|
+
import importlib
|
|
3
|
+
import importlib.metadata
|
|
4
|
+
import inspect
|
|
5
|
+
import pkgutil
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from typing import List, Dict, Any, Type, Optional
|
|
8
|
+
|
|
9
|
+
class AkitaPlugin(abc.ABC):
|
|
10
|
+
"""Base class for all AkitaLLM plugins."""
|
|
11
|
+
|
|
12
|
+
@property
|
|
13
|
+
@abc.abstractmethod
|
|
14
|
+
def name(self) -> str:
|
|
15
|
+
"""Unique name of the plugin."""
|
|
16
|
+
pass
|
|
17
|
+
|
|
18
|
+
@property
|
|
19
|
+
@abc.abstractmethod
|
|
20
|
+
def description(self) -> str:
|
|
21
|
+
"""Brief description of what the plugin does."""
|
|
22
|
+
pass
|
|
23
|
+
|
|
24
|
+
@abc.abstractmethod
|
|
25
|
+
def get_tools(self) -> List[Dict[str, Any]]:
|
|
26
|
+
"""Return a list of tools (functions) provided by this plugin."""
|
|
27
|
+
pass
|
|
28
|
+
|
|
29
|
+
class PluginManager:
|
|
30
|
+
def __init__(self, internal_plugins_path: Optional[str] = None):
|
|
31
|
+
self.plugins: Dict[str, AkitaPlugin] = {}
|
|
32
|
+
self.internal_path = internal_plugins_path or str(Path(__file__).parent.parent / "plugins")
|
|
33
|
+
|
|
34
|
+
def discover_all(self):
|
|
35
|
+
"""Discover both internal and external plugins."""
|
|
36
|
+
self._discover_internal()
|
|
37
|
+
self._discover_external()
|
|
38
|
+
|
|
39
|
+
def _discover_internal(self):
|
|
40
|
+
"""Load plugins from the akita/plugins directory."""
|
|
41
|
+
path = Path(self.internal_path)
|
|
42
|
+
if not path.exists():
|
|
43
|
+
return
|
|
44
|
+
|
|
45
|
+
for loader, module_name, is_pkg in pkgutil.iter_modules([str(path)]):
|
|
46
|
+
full_module_name = f"akita.plugins.{module_name}"
|
|
47
|
+
try:
|
|
48
|
+
module = importlib.import_module(full_module_name)
|
|
49
|
+
self._load_from_module(module)
|
|
50
|
+
except Exception as e:
|
|
51
|
+
print(f"Error loading internal plugin {module_name}: {e}")
|
|
52
|
+
|
|
53
|
+
def _discover_external(self):
|
|
54
|
+
"""Load plugins registered via entry_points (akitallm.plugins)."""
|
|
55
|
+
try:
|
|
56
|
+
# Python 3.10+
|
|
57
|
+
eps = importlib.metadata.entry_points(group='akitallm.plugins')
|
|
58
|
+
for entry_point in eps:
|
|
59
|
+
try:
|
|
60
|
+
plugin_class = entry_point.load()
|
|
61
|
+
if inspect.isclass(plugin_class) and issubclass(plugin_class, AkitaPlugin):
|
|
62
|
+
instance = plugin_class()
|
|
63
|
+
self.plugins[instance.name] = instance
|
|
64
|
+
except Exception as e:
|
|
65
|
+
print(f"Error loading external plugin {entry_point.name}: {e}")
|
|
66
|
+
except Exception:
|
|
67
|
+
pass
|
|
68
|
+
|
|
69
|
+
def _load_from_module(self, module):
|
|
70
|
+
"""Extract AkitaPlugin classes from a module and instantiate them."""
|
|
71
|
+
for name, obj in inspect.getmembers(module):
|
|
72
|
+
if inspect.isclass(obj) and issubclass(obj, AkitaPlugin) and obj is not AkitaPlugin:
|
|
73
|
+
instance = obj()
|
|
74
|
+
self.plugins[instance.name] = instance
|
|
75
|
+
|
|
76
|
+
def get_all_tools(self) -> List[Dict[str, Any]]:
|
|
77
|
+
"""Collect all tools from all loaded plugins."""
|
|
78
|
+
all_tools = []
|
|
79
|
+
for plugin in self.plugins.values():
|
|
80
|
+
all_tools.extend(plugin.get_tools())
|
|
81
|
+
return all_tools
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
from typing import List, Dict, Any
|
|
2
|
+
from datetime import datetime
|
|
3
|
+
from pydantic import BaseModel, Field
|
|
4
|
+
|
|
5
|
+
class TraceStep(BaseModel):
|
|
6
|
+
timestamp: datetime = Field(default_factory=datetime.now)
|
|
7
|
+
action: str
|
|
8
|
+
details: str
|
|
9
|
+
metadata: Dict[str, Any] = Field(default_factory=dict)
|
|
10
|
+
|
|
11
|
+
class ReasoningTrace(BaseModel):
|
|
12
|
+
steps: List[TraceStep] = Field(default_factory=list)
|
|
13
|
+
|
|
14
|
+
def add_step(self, action: str, details: str, metadata: Dict[str, Any] = None):
|
|
15
|
+
self.steps.append(TraceStep(action=action, details=details, metadata=metadata or {}))
|
|
16
|
+
|
|
17
|
+
def __str__(self):
|
|
18
|
+
return "\n".join([f"[{s.timestamp.strftime('%H:%M:%S')}] {s.action}: {s.details}" for s in self.steps])
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# Official AkitaLLM Plugins
|