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.
Files changed (39) hide show
  1. akitallm-1.0.3/PKG-INFO +140 -0
  2. akitallm-1.0.3/README.md +105 -0
  3. akitallm-1.0.3/akita/__init__.py +1 -0
  4. {akitallm-0.1.0 → akitallm-1.0.3}/akita/cli/main.py +75 -10
  5. akitallm-1.0.3/akita/core/ast_utils.py +77 -0
  6. akitallm-1.0.3/akita/core/indexing.py +94 -0
  7. akitallm-1.0.3/akita/core/plugins.py +81 -0
  8. akitallm-1.0.3/akita/core/trace.py +18 -0
  9. akitallm-1.0.3/akita/plugins/__init__.py +1 -0
  10. akitallm-1.0.3/akita/plugins/files.py +34 -0
  11. {akitallm-0.1.0 → akitallm-1.0.3}/akita/reasoning/engine.py +44 -18
  12. akitallm-1.0.3/akita/reasoning/session.py +15 -0
  13. {akitallm-0.1.0 → akitallm-1.0.3}/akita/tools/base.py +6 -1
  14. {akitallm-0.1.0 → akitallm-1.0.3}/akita/tools/context.py +54 -9
  15. akitallm-1.0.3/akita/tools/diff.py +116 -0
  16. akitallm-1.0.3/akitallm.egg-info/PKG-INFO +140 -0
  17. {akitallm-0.1.0 → akitallm-1.0.3}/akitallm.egg-info/SOURCES.txt +11 -0
  18. akitallm-1.0.3/akitallm.egg-info/entry_points.txt +5 -0
  19. {akitallm-0.1.0 → akitallm-1.0.3}/akitallm.egg-info/requires.txt +3 -0
  20. {akitallm-0.1.0 → akitallm-1.0.3}/pyproject.toml +7 -1
  21. akitallm-1.0.3/tests/test_ast.py +50 -0
  22. {akitallm-0.1.0 → akitallm-1.0.3}/tests/test_basic.py +1 -1
  23. akitallm-1.0.3/tests/test_diff.py +68 -0
  24. akitallm-1.0.3/tests/test_interactive.py +33 -0
  25. akitallm-1.0.3/tests/test_plugins.py +41 -0
  26. akitallm-0.1.0/PKG-INFO +0 -111
  27. akitallm-0.1.0/README.md +0 -79
  28. akitallm-0.1.0/akita/__init__.py +0 -1
  29. akitallm-0.1.0/akita/tools/diff.py +0 -41
  30. akitallm-0.1.0/akitallm.egg-info/PKG-INFO +0 -111
  31. akitallm-0.1.0/akitallm.egg-info/entry_points.txt +0 -2
  32. {akitallm-0.1.0 → akitallm-1.0.3}/LICENSE +0 -0
  33. {akitallm-0.1.0 → akitallm-1.0.3}/akita/core/config.py +0 -0
  34. {akitallm-0.1.0 → akitallm-1.0.3}/akita/models/base.py +0 -0
  35. {akitallm-0.1.0 → akitallm-1.0.3}/akita/schemas/review.py +0 -0
  36. {akitallm-0.1.0 → akitallm-1.0.3}/akitallm.egg-info/dependency_links.txt +0 -0
  37. {akitallm-0.1.0 → akitallm-1.0.3}/akitallm.egg-info/top_level.txt +0 -0
  38. {akitallm-0.1.0 → akitallm-1.0.3}/setup.cfg +0 -0
  39. {akitallm-0.1.0 → akitallm-1.0.3}/tests/test_review_mock.py +0 -0
@@ -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.”*
@@ -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
- diff_output = engine.run_solve(query)
133
-
134
- console.print(Panel("[bold green]Suggested Code Changes (Unified Diff):[/]"))
135
- syntax = Syntax(diff_output, "diff", theme="monokai", line_numbers=True)
136
- console.print(syntax)
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... (DiffApplier to be implemented next)[/]")
142
- # We will implement DiffApplier in the next step
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("🐶 [bold blue]Akita[/] is running tests...", title="Test Mode"))
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