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/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
- **kwargs
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
- # For Ollama, we might need a base_url, but for now we assume default
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
- console.print("🤖 [bold green]Thinking...[/]")
137
- response = self.model.chat(session.get_messages_dict())
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 backup and rollback logic for atomicity.
32
+ Includes PRE-FLIGHT DRY-RUN, strict context checking, and then atomic application.
14
33
  """
15
- patches = list(whatthepatch.parse_patch(diff_text))
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
- backup_dir.mkdir(parents=True, exist_ok=True)
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
- try:
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
- is_new = (patch.header.old_path == "/dev/null")
34
- is_delete = (patch.header.new_path == "/dev/null")
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
- if is_new:
37
- rel_path = patch.header.new_path
38
- elif is_delete:
39
- rel_path = patch.header.old_path
40
- else:
41
- rel_path = patch.header.new_path or patch.header.old_path
42
-
43
- if not rel_path or rel_path == "/dev/null":
44
- continue
45
-
46
- # Clean up path (sometimes they have a/ or b/ prefixes)
47
- if rel_path.startswith("a/") or rel_path.startswith("b/"):
48
- rel_path = rel_path[2:]
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
- target_file = (base / rel_path).resolve()
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 not is_new and not target_file.exists():
53
- print(f"ERROR: Target file {target_file} does not exist for patching.")
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
- # 1. Create backup
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)) # Mark for deletion on rollback if it's a new file
132
+ backups.append((target_file, None))
63
133
 
64
- # 2. Apply patch
65
- content = ""
66
- if target_file.exists():
67
- with open(target_file, "r", encoding="utf-8") as f:
68
- content = f.read()
69
-
70
- lines = content.splitlines()
71
- # whatthepatch apply_diff returns a generator of lines
72
- patched_lines = whatthepatch.apply_diff(patch, lines)
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
- # 4. Pre-flight Validation
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 pre-flight validation (pytest)...")
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("Pre-flight validation failed. Tests are broken.")
151
+ raise Exception("Post-flight validation failed. Tests are broken.")
95
152
  else:
96
- print("✅ Pre-flight validation passed!")
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() # Delete newly created file
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
+ ![PyPI](https://img.shields.io/pypi/v/akitallm)
39
+ ![Python](https://img.shields.io/pypi/pyversions/akitallm)
40
+ ![License](https://img.shields.io/github/license/KerubinDev/AkitaLLM)
41
+ [![Tests](https://img.shields.io/github/actions/workflow/status/KerubinDev/AkitaLLM/tests.yml)](https://github.com/KerubinDev/AkitaLLM/actions)
42
+ ![Downloads](https://img.shields.io/pypi/dm/akitallm)
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=q8_5C0f-8mHWNb6mMw02zlYPnEGXBqvOmP3z0CEwZKM,22
2
- akita/cli/main.py,sha256=6jR4Dfg0HpDGkrr8kdpF6AfzLm6-5nStvsPN1VZd5as,12820
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=0nlA5AiGjm0Kv5yUxqaHV2zA4Ld1rU_j2s2IjnYBB_Y,1656
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=GC3WB9kMXpg1GCAX5104uovgdOZBIVIHJBq2SO8aU00,1723
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=w1gB-Y_Tzoan66T-EFd04X7V5mDcVOkSP2G6X3lmvxU,8634
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=bVH6_vHWoC9oYoS1RU4eOEnZHh6eFNtt6HCCzeGb6wY,4805
19
+ akita/tools/diff.py,sha256=d5Q3yRvCKvaCTHW3PibAu1n4AXuFq1zcEeIFD_jxhbo,6942
18
20
  akita/tools/git.py,sha256=58ZCI2ZL7NYUQdRIe3969t6gRpVmCPD8B-UbP7cPBNY,2798
19
- akitallm-1.1.1.dist-info/licenses/LICENSE,sha256=WE7_tfGR-IzkulSh6Pos02gucCXKboaXguAdr0bI9V0,1067
20
- akitallm-1.1.1.dist-info/METADATA,sha256=BfinRkQicqI9tshCIqPlFZxyxOwVNuXT6z_ZEL4mC9E,5500
21
- akitallm-1.1.1.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
22
- akitallm-1.1.1.dist-info/entry_points.txt,sha256=Au1aAXCO2lX4kgElgknSVDpq7BcN5xAJJ0WvOAkhLzU,105
23
- akitallm-1.1.1.dist-info/top_level.txt,sha256=duGU-i6qCRLqjo_b1XUqfhlSQky3QIO0Hlvfn2OV3hU,6
24
- akitallm-1.1.1.dist-info/RECORD,,
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,,