nimcode 0.7.0__tar.gz → 0.8.0__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.
- {nimcode-0.7.0 → nimcode-0.8.0}/PKG-INFO +21 -4
- {nimcode-0.7.0 → nimcode-0.8.0}/README.md +20 -3
- nimcode-0.8.0/src/nimcode/__version__.py +1 -0
- {nimcode-0.7.0 → nimcode-0.8.0}/src/nimcode/agent.py +21 -3
- {nimcode-0.7.0 → nimcode-0.8.0}/src/nimcode/agents/qa_agent.py +8 -1
- {nimcode-0.7.0 → nimcode-0.8.0}/src/nimcode/cli.py +1 -1
- {nimcode-0.7.0 → nimcode-0.8.0}/src/nimcode/config.py +1 -1
- {nimcode-0.7.0 → nimcode-0.8.0}/src/nimcode/model_registry.py +2 -2
- {nimcode-0.7.0 → nimcode-0.8.0}/src/nimcode/nim_client.py +1 -1
- {nimcode-0.7.0 → nimcode-0.8.0}/src/nimcode/repl.py +41 -13
- {nimcode-0.7.0 → nimcode-0.8.0}/src/nimcode/task_manager.py +3 -0
- {nimcode-0.7.0 → nimcode-0.8.0}/src/nimcode/tools.py +63 -5
- {nimcode-0.7.0 → nimcode-0.8.0}/src/nimcode.egg-info/PKG-INFO +21 -4
- {nimcode-0.7.0 → nimcode-0.8.0}/tests/test_agent.py +2 -1
- {nimcode-0.7.0 → nimcode-0.8.0}/tests/test_config.py +2 -2
- {nimcode-0.7.0 → nimcode-0.8.0}/tests/test_lazy_blockers.py +31 -0
- {nimcode-0.7.0 → nimcode-0.8.0}/tests/test_model_registry.py +1 -1
- {nimcode-0.7.0 → nimcode-0.8.0}/tests/test_qa_agent.py +4 -2
- {nimcode-0.7.0 → nimcode-0.8.0}/tests/test_repl.py +6 -4
- {nimcode-0.7.0 → nimcode-0.8.0}/tests/test_tools.py +13 -0
- nimcode-0.7.0/src/nimcode/__version__.py +0 -1
- {nimcode-0.7.0 → nimcode-0.8.0}/setup.cfg +0 -0
- {nimcode-0.7.0 → nimcode-0.8.0}/setup.py +0 -0
- {nimcode-0.7.0 → nimcode-0.8.0}/src/nimcode/__init__.py +0 -0
- {nimcode-0.7.0 → nimcode-0.8.0}/src/nimcode/agents/__init__.py +0 -0
- {nimcode-0.7.0 → nimcode-0.8.0}/src/nimcode/lenient_parser.py +0 -0
- {nimcode-0.7.0 → nimcode-0.8.0}/src/nimcode/mcp_client.py +0 -0
- {nimcode-0.7.0 → nimcode-0.8.0}/src/nimcode/memory.py +0 -0
- {nimcode-0.7.0 → nimcode-0.8.0}/src/nimcode/permissions.py +0 -0
- {nimcode-0.7.0 → nimcode-0.8.0}/src/nimcode/plugin_manager.py +0 -0
- {nimcode-0.7.0 → nimcode-0.8.0}/src/nimcode/rag.py +0 -0
- {nimcode-0.7.0 → nimcode-0.8.0}/src/nimcode/repo_map.py +0 -0
- {nimcode-0.7.0 → nimcode-0.8.0}/src/nimcode/updater.py +0 -0
- {nimcode-0.7.0 → nimcode-0.8.0}/src/nimcode/watcher.py +0 -0
- {nimcode-0.7.0 → nimcode-0.8.0}/src/nimcode.egg-info/SOURCES.txt +0 -0
- {nimcode-0.7.0 → nimcode-0.8.0}/src/nimcode.egg-info/dependency_links.txt +0 -0
- {nimcode-0.7.0 → nimcode-0.8.0}/src/nimcode.egg-info/entry_points.txt +0 -0
- {nimcode-0.7.0 → nimcode-0.8.0}/src/nimcode.egg-info/requires.txt +0 -0
- {nimcode-0.7.0 → nimcode-0.8.0}/src/nimcode.egg-info/top_level.txt +0 -0
- {nimcode-0.7.0 → nimcode-0.8.0}/tests/test_cli.py +0 -0
- {nimcode-0.7.0 → nimcode-0.8.0}/tests/test_lenient_parser.py +0 -0
- {nimcode-0.7.0 → nimcode-0.8.0}/tests/test_mcp_client.py +0 -0
- {nimcode-0.7.0 → nimcode-0.8.0}/tests/test_memory.py +0 -0
- {nimcode-0.7.0 → nimcode-0.8.0}/tests/test_nim_client.py +0 -0
- {nimcode-0.7.0 → nimcode-0.8.0}/tests/test_permissions.py +0 -0
- {nimcode-0.7.0 → nimcode-0.8.0}/tests/test_plan_injection.py +0 -0
- {nimcode-0.7.0 → nimcode-0.8.0}/tests/test_plugin_manager.py +0 -0
- {nimcode-0.7.0 → nimcode-0.8.0}/tests/test_repl_extra.py +0 -0
- {nimcode-0.7.0 → nimcode-0.8.0}/tests/test_repl_fixes.py +0 -0
- {nimcode-0.7.0 → nimcode-0.8.0}/tests/test_repl_trust.py +0 -0
- {nimcode-0.7.0 → nimcode-0.8.0}/tests/test_repo_map.py +0 -0
- {nimcode-0.7.0 → nimcode-0.8.0}/tests/test_task_manager.py +0 -0
- {nimcode-0.7.0 → nimcode-0.8.0}/tests/test_updater.py +0 -0
- {nimcode-0.7.0 → nimcode-0.8.0}/tests/test_version.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: nimcode
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.8.0
|
|
4
4
|
Summary: A standalone, robust coding agent for NVIDIA NIM models.
|
|
5
5
|
Home-page: https://github.com/Batunash/nimcode
|
|
6
6
|
Author: Autonomous Agent
|
|
@@ -157,7 +157,7 @@ NimCode's behavior can be fully customized through `~/.nimcode/settings.json` (g
|
|
|
157
157
|
|
|
158
158
|
```json
|
|
159
159
|
{
|
|
160
|
-
"model": "meta/llama-3.
|
|
160
|
+
"model": "meta/llama-3.3-70b-instruct",
|
|
161
161
|
"api_base_url": "https://integrate.api.nvidia.com/v1",
|
|
162
162
|
"timeout_command": 1200,
|
|
163
163
|
"timeout_llm": 120,
|
|
@@ -175,7 +175,7 @@ NimCode's behavior can be fully customized through `~/.nimcode/settings.json` (g
|
|
|
175
175
|
|
|
176
176
|
| Setting | Default | Description |
|
|
177
177
|
|---------|---------|-------------|
|
|
178
|
-
| `model` | `meta/llama-3.
|
|
178
|
+
| `model` | `meta/llama-3.3-70b-instruct` | Default LLM model |
|
|
179
179
|
| `api_base_url` | NVIDIA NIM endpoint | Switch to Ollama/vLLM by changing this |
|
|
180
180
|
| `timeout_command` | 1200 | Max seconds for bash commands (0 = infinite) |
|
|
181
181
|
| `timeout_llm` | 120 | Max seconds for LLM API calls (0 = infinite) |
|
|
@@ -193,7 +193,24 @@ NimCode's behavior can be fully customized through `~/.nimcode/settings.json` (g
|
|
|
193
193
|
|
|
194
194
|
## 📋 Changelog
|
|
195
195
|
|
|
196
|
-
### v0.
|
|
196
|
+
### v0.8.0 (Latest)
|
|
197
|
+
- 📝 **SDD-Style Planning**: `/plan` mode now forces the LLM to output Software Design Document (SDD) style templates with specific dependency graphs, timelines, and rigorous phase tracking (the "Empty Recursive Goblet" approach).
|
|
198
|
+
- 🧱 **Physical Exit Blockers for Tasks**: If the LLM tries to call `TASK_COMPLETE` but still has pending or in_progress tasks in its `.nimcode/tasks.json` file, it will physically intercept the tool call, throw a `SYSTEM ERROR`, and force the LLM to finish the open tasks.
|
|
199
|
+
- 🛡️ **Ultimate Anti-Lazy Shield**: Expanded AST and regex blockers that catch semantic laziness (e.g., `"// rest of the code remains unchanged"`, `"logic goes here"`, or using `...` / Ellipsis in Python). A zero-tolerance policy that forces the LLM to rewrite incomplete code.
|
|
200
|
+
- 🧩 **Chunked Generation (Append Tool)**: A new `Append` tool allows the LLM to write massive documents and code files in chunks over multiple turns without hitting context token limits, directly curing "Lazy Coder Syndrome".
|
|
201
|
+
- 🧪 **205 tests passing**
|
|
202
|
+
|
|
203
|
+
### v0.7.0
|
|
204
|
+
- 🛡️ **Strict Markdown Validator**: `/plan` mode now physically blocks tasks that are under 150 characters or missing required Markdown headers (`Target Files`, `Implementation Details`, `Checklist`).
|
|
205
|
+
- 🧠 **Context Amnesia Shield (AST Diff)**: When modifying Python files, the `ReplaceBlock` tool compares the Abstract Syntax Tree (AST) before and after. If the LLM accidentally deletes over 30% or 2+ top-level functions (truncation hallucination), it instantly blocks the write and forces a fix.
|
|
206
|
+
- 🔗 **Task Linearity Guard**: `TaskManager` enforces strict order. The agent cannot start Task 1.2 until Task 1.1 is fully completed.
|
|
207
|
+
|
|
208
|
+
### v0.6.0
|
|
209
|
+
- 🛡️ **Anti-Laziness AST Blockers**: Native AST checking explicitly intercepts `pass` blocks, empty classes, and lazy placeholder comments (like `// TODO`), forcing the LLM to write full implementations.
|
|
210
|
+
- 🕵️ **Adversarial QA Gate**: Built a multi-agent validation layer (`qa_agent.py`) that strictly critiques the main agent's work. The main agent cannot complete tasks without receiving a `VERDICT: PASS` from the QA Agent.
|
|
211
|
+
- 💾 **Task State Machine**: Replaced in-memory lists with `.nimcode/tasks.json` persistent storage via `TaskManager`, allowing robust restartability and task state tracking (`pending`, `in_progress`, `completed`).
|
|
212
|
+
|
|
213
|
+
### v0.5.5
|
|
197
214
|
- 🐛 **UI Bug Fix**: Fixed a visual bug where the CLI welcome banner hardcoded `NimCode v0.2.0` despite the user having updated to a newer version.
|
|
198
215
|
|
|
199
216
|
### v0.5.4
|
|
@@ -136,7 +136,7 @@ NimCode's behavior can be fully customized through `~/.nimcode/settings.json` (g
|
|
|
136
136
|
|
|
137
137
|
```json
|
|
138
138
|
{
|
|
139
|
-
"model": "meta/llama-3.
|
|
139
|
+
"model": "meta/llama-3.3-70b-instruct",
|
|
140
140
|
"api_base_url": "https://integrate.api.nvidia.com/v1",
|
|
141
141
|
"timeout_command": 1200,
|
|
142
142
|
"timeout_llm": 120,
|
|
@@ -154,7 +154,7 @@ NimCode's behavior can be fully customized through `~/.nimcode/settings.json` (g
|
|
|
154
154
|
|
|
155
155
|
| Setting | Default | Description |
|
|
156
156
|
|---------|---------|-------------|
|
|
157
|
-
| `model` | `meta/llama-3.
|
|
157
|
+
| `model` | `meta/llama-3.3-70b-instruct` | Default LLM model |
|
|
158
158
|
| `api_base_url` | NVIDIA NIM endpoint | Switch to Ollama/vLLM by changing this |
|
|
159
159
|
| `timeout_command` | 1200 | Max seconds for bash commands (0 = infinite) |
|
|
160
160
|
| `timeout_llm` | 120 | Max seconds for LLM API calls (0 = infinite) |
|
|
@@ -172,7 +172,24 @@ NimCode's behavior can be fully customized through `~/.nimcode/settings.json` (g
|
|
|
172
172
|
|
|
173
173
|
## 📋 Changelog
|
|
174
174
|
|
|
175
|
-
### v0.
|
|
175
|
+
### v0.8.0 (Latest)
|
|
176
|
+
- 📝 **SDD-Style Planning**: `/plan` mode now forces the LLM to output Software Design Document (SDD) style templates with specific dependency graphs, timelines, and rigorous phase tracking (the "Empty Recursive Goblet" approach).
|
|
177
|
+
- 🧱 **Physical Exit Blockers for Tasks**: If the LLM tries to call `TASK_COMPLETE` but still has pending or in_progress tasks in its `.nimcode/tasks.json` file, it will physically intercept the tool call, throw a `SYSTEM ERROR`, and force the LLM to finish the open tasks.
|
|
178
|
+
- 🛡️ **Ultimate Anti-Lazy Shield**: Expanded AST and regex blockers that catch semantic laziness (e.g., `"// rest of the code remains unchanged"`, `"logic goes here"`, or using `...` / Ellipsis in Python). A zero-tolerance policy that forces the LLM to rewrite incomplete code.
|
|
179
|
+
- 🧩 **Chunked Generation (Append Tool)**: A new `Append` tool allows the LLM to write massive documents and code files in chunks over multiple turns without hitting context token limits, directly curing "Lazy Coder Syndrome".
|
|
180
|
+
- 🧪 **205 tests passing**
|
|
181
|
+
|
|
182
|
+
### v0.7.0
|
|
183
|
+
- 🛡️ **Strict Markdown Validator**: `/plan` mode now physically blocks tasks that are under 150 characters or missing required Markdown headers (`Target Files`, `Implementation Details`, `Checklist`).
|
|
184
|
+
- 🧠 **Context Amnesia Shield (AST Diff)**: When modifying Python files, the `ReplaceBlock` tool compares the Abstract Syntax Tree (AST) before and after. If the LLM accidentally deletes over 30% or 2+ top-level functions (truncation hallucination), it instantly blocks the write and forces a fix.
|
|
185
|
+
- 🔗 **Task Linearity Guard**: `TaskManager` enforces strict order. The agent cannot start Task 1.2 until Task 1.1 is fully completed.
|
|
186
|
+
|
|
187
|
+
### v0.6.0
|
|
188
|
+
- 🛡️ **Anti-Laziness AST Blockers**: Native AST checking explicitly intercepts `pass` blocks, empty classes, and lazy placeholder comments (like `// TODO`), forcing the LLM to write full implementations.
|
|
189
|
+
- 🕵️ **Adversarial QA Gate**: Built a multi-agent validation layer (`qa_agent.py`) that strictly critiques the main agent's work. The main agent cannot complete tasks without receiving a `VERDICT: PASS` from the QA Agent.
|
|
190
|
+
- 💾 **Task State Machine**: Replaced in-memory lists with `.nimcode/tasks.json` persistent storage via `TaskManager`, allowing robust restartability and task state tracking (`pending`, `in_progress`, `completed`).
|
|
191
|
+
|
|
192
|
+
### v0.5.5
|
|
176
193
|
- 🐛 **UI Bug Fix**: Fixed a visual bug where the CLI welcome banner hardcoded `NimCode v0.2.0` despite the user having updated to a newer version.
|
|
177
194
|
|
|
178
195
|
### v0.5.4
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.8.0"
|
|
@@ -34,6 +34,7 @@ Available Tools:
|
|
|
34
34
|
- Bash: {{"tool": "Bash", "args": {{"command": "string"}}}}
|
|
35
35
|
- Read: {{"tool": "Read", "args": {{"file_path": "string", "offset": "int (optional, 1-based line number to start from)", "limit": "int (optional, max number of lines to return)"}}}}
|
|
36
36
|
- Write: {{"tool": "Write", "args": {{"file_path": "string", "content": "string"}}}}
|
|
37
|
+
- Append: {{"tool": "Append", "args": {{"file_path": "string", "content": "string"}}}} — Append content to a file (great for chunking large files).
|
|
37
38
|
- Replace: {{"tool": "Replace", "args": {{"file_path": "string", "replacements": [{{"old_string": "string", "new_string": "string"}}]}}}}
|
|
38
39
|
- ReplaceBlock: {{"tool": "ReplaceBlock", "args": {{"file_path": "string", "start_line": "int", "end_line": "int", "replacement_content": "string"}}}}
|
|
39
40
|
- Glob: {{"tool": "Glob", "args": {{"pattern": "string"}}}}
|
|
@@ -106,10 +107,13 @@ If you can say it in one sentence, don't use three. Prefer short, direct sentenc
|
|
|
106
107
|
- When referencing specific functions or pieces of code include the pattern file_path:line_number to allow the user to easily navigate to the source code location.
|
|
107
108
|
- Do not use a colon before tool calls.
|
|
108
109
|
|
|
109
|
-
#
|
|
110
|
+
# Chunked Generation & Lazy Coding Prevention (STRICT)
|
|
110
111
|
- You are strictly FORBIDDEN from using placeholders like `// TODO`, `pass`, `$(cat secret.txt)`, `[Insert code here]`.
|
|
111
112
|
- You MUST write complete, full implementations for all functions.
|
|
112
|
-
- If you use placeholders, the system will detect it and reject your turn via Physical Blockers in your Write
|
|
113
|
+
- If you use placeholders, the system will detect it and reject your turn via Physical Blockers in your Write/Append tools.
|
|
114
|
+
- NEVER try to `Write` a massive file (like a large code file or a huge markdown plan) in a single tool call. You will hit token limits and generate lazy code.
|
|
115
|
+
- Instead, use the `Append` tool to write large documents/plans chapter by chapter over multiple turns.
|
|
116
|
+
- For code editing, DO NOT use `Write` to rewrite an existing file. You MUST use `Replace` or `ReplaceBlock` to perform surgical edits (multi-patching).
|
|
113
117
|
|
|
114
118
|
# Planning Mode Instructions
|
|
115
119
|
When in /plan mode or asked to create a plan:
|
|
@@ -184,7 +188,7 @@ class Agent:
|
|
|
184
188
|
def __init__(self, api_key: str, model: str = None, max_turns: int = None, permission_mode: PermissionMode = PermissionMode.DEFAULT, max_tokens: int = None):
|
|
185
189
|
# Load global settings
|
|
186
190
|
self.settings = load_settings()
|
|
187
|
-
self.model = model or self.settings.get("model", "meta/llama-3.
|
|
191
|
+
self.model = model or self.settings.get("model", "meta/llama-3.3-70b-instruct")
|
|
188
192
|
api_base_url = self.settings.get("api_base_url", "https://integrate.api.nvidia.com/v1")
|
|
189
193
|
self.client = NimClient(
|
|
190
194
|
api_key=api_key,
|
|
@@ -524,6 +528,20 @@ class Agent:
|
|
|
524
528
|
logger.error(f"Failed to log to NIMCODE.md: {e}")
|
|
525
529
|
|
|
526
530
|
if "TASK_COMPLETE" in full_content:
|
|
531
|
+
from .task_manager import TaskManager
|
|
532
|
+
tm = TaskManager()
|
|
533
|
+
incomplete = [t for t in tm.get_all_tasks() if t.get("status") in ("pending", "in_progress")]
|
|
534
|
+
|
|
535
|
+
if incomplete:
|
|
536
|
+
logger.warning("Agent attempted TASK_COMPLETE but tasks are incomplete.")
|
|
537
|
+
from rich.console import Console
|
|
538
|
+
Console().print(f"[bold red]🚨 PHYSICAL BLOCK: Agent tried to exit but {len(incomplete)} tasks are still pending/in_progress. Forcing retry.[/bold red]")
|
|
539
|
+
self.messages.append({
|
|
540
|
+
"role": "user",
|
|
541
|
+
"content": f"SYSTEM ERROR: You attempted to use TASK_COMPLETE, but you still have {len(incomplete)} unfinished tasks. You MUST use tools to complete all tasks before finishing. Please continue working."
|
|
542
|
+
})
|
|
543
|
+
continue
|
|
544
|
+
|
|
527
545
|
logger.info("Agent finished task.")
|
|
528
546
|
break
|
|
529
547
|
|
|
@@ -8,11 +8,18 @@ class QAAgent:
|
|
|
8
8
|
self.cwd = cwd
|
|
9
9
|
|
|
10
10
|
def run(self, instructions: str) -> str:
|
|
11
|
+
# Check if tasks are completed first (Physical blocker)
|
|
12
|
+
from ..task_manager import TaskManager
|
|
13
|
+
tm = TaskManager()
|
|
14
|
+
incomplete = [t for t in tm.get_all_tasks() if t.get("status") in ("pending", "in_progress")]
|
|
15
|
+
if incomplete:
|
|
16
|
+
return f"VERDICT: FAIL\nYou cannot pass QA. There are still {len(incomplete)} unfinished tasks in tasks.json. Go back and complete them."
|
|
17
|
+
|
|
11
18
|
# Load API key
|
|
12
19
|
import json
|
|
13
20
|
settings_path = os.path.join(self.cwd, ".nimcode", "settings.json")
|
|
14
21
|
api_key = os.environ.get("NVIDIA_API_KEY")
|
|
15
|
-
model = "meta/llama-3.
|
|
22
|
+
model = "meta/llama-3.3-70b-instruct"
|
|
16
23
|
if os.path.exists(settings_path):
|
|
17
24
|
try:
|
|
18
25
|
with open(settings_path, "r") as f:
|
|
@@ -101,7 +101,7 @@ def main():
|
|
|
101
101
|
# Main CLI arguments
|
|
102
102
|
parser.add_argument("prompt", nargs="?", default=None, help="The task you want NimCode to accomplish. If omitted, starts interactive REPL.")
|
|
103
103
|
parser.add_argument("--api-key", "-k", default=None, help="NVIDIA NIM API Key. Can also be set via NIM_API_KEY environment variable.")
|
|
104
|
-
parser.add_argument("--model", "-m", default="meta/llama-3.
|
|
104
|
+
parser.add_argument("--model", "-m", default="meta/llama-3.3-70b-instruct", help="Model ID to use from NIM.")
|
|
105
105
|
parser.add_argument("--max-turns", "-t", type=int, default=None, help="Maximum number of turns the agent is allowed to run. If omitted, uses settings.json max_turns (default 200; 0 = unlimited).")
|
|
106
106
|
parser.add_argument("--permission-mode", "-p", type=PermissionMode, choices=list(PermissionMode), default=PermissionMode.DEFAULT, help="Permission mode for mutating tools.")
|
|
107
107
|
parser.add_argument("--resume", "-r", action="store_true", help="Resume from the last session stored in .nimcode/sessions/session.json.")
|
|
@@ -8,7 +8,7 @@ logger = logging.getLogger(__name__)
|
|
|
8
8
|
def load_settings() -> Dict[str, Any]:
|
|
9
9
|
"""Loads configuration from ~/.nimcode/settings.json and .nimcode/settings.json"""
|
|
10
10
|
settings = {
|
|
11
|
-
"model": "meta/llama-3.
|
|
11
|
+
"model": "meta/llama-3.3-70b-instruct",
|
|
12
12
|
"api_base_url": "https://integrate.api.nvidia.com/v1",
|
|
13
13
|
"mcp_servers": {},
|
|
14
14
|
# Timeouts (0 = disabled/infinite)
|
|
@@ -11,7 +11,7 @@ logger = logging.getLogger(__name__)
|
|
|
11
11
|
MODEL_CONTEXT_WINDOWS = {
|
|
12
12
|
# Llama 3.1 family
|
|
13
13
|
"meta/llama-3.1-8b-instruct": 128000,
|
|
14
|
-
"meta/llama-3.
|
|
14
|
+
"meta/llama-3.3-70b-instruct": 128000,
|
|
15
15
|
"meta/llama-3.1-405b-instruct": 128000,
|
|
16
16
|
# Llama 3.2 family
|
|
17
17
|
"meta/llama-3.2-1b-instruct": 128000,
|
|
@@ -38,7 +38,7 @@ DEFAULT_CONTEXT_WINDOW = 8192
|
|
|
38
38
|
|
|
39
39
|
# Hardcoded fallback model list (used when API is unreachable)
|
|
40
40
|
FALLBACK_MODELS = [
|
|
41
|
-
"meta/llama-3.
|
|
41
|
+
"meta/llama-3.3-70b-instruct",
|
|
42
42
|
"meta/llama-3.1-8b-instruct",
|
|
43
43
|
"meta/llama-3.1-405b-instruct",
|
|
44
44
|
"meta/llama-3.3-70b-instruct",
|
|
@@ -8,7 +8,7 @@ from typing import List, Dict, Any, Optional, AsyncGenerator
|
|
|
8
8
|
logger = logging.getLogger(__name__)
|
|
9
9
|
|
|
10
10
|
class NimClient:
|
|
11
|
-
def __init__(self, api_key: str, base_url: str = "https://integrate.api.nvidia.com/v1", model: str = "meta/llama-3.
|
|
11
|
+
def __init__(self, api_key: str, base_url: str = "https://integrate.api.nvidia.com/v1", model: str = "meta/llama-3.3-70b-instruct", timeout: float = 120.0, max_retries: int = 15, retry_base_delay: float = 2.0, retry_max_delay: float = 60.0):
|
|
12
12
|
self.api_key = api_key
|
|
13
13
|
self.base_url = base_url.rstrip("/")
|
|
14
14
|
self.model = model
|
|
@@ -446,24 +446,52 @@ class NimcodeREPL:
|
|
|
446
446
|
" - Use this strict technical structure: # Title, ## Context, ## Root Cause Analysis, ## Execution Plan (file-by-file with line numbers/snippets), ## Verification.\n"
|
|
447
447
|
"3. IF it is a 'From-Scratch / Major Architecture' task:\n"
|
|
448
448
|
" - Generate a SINGLE, EXTREMELY DETAILED master plan file (e.g. '.nimcode/plans/<name>.md').\n"
|
|
449
|
-
" - You MUST format the plan using EXACTLY this markdown template. Do not skip any sections. Do not summarize.\n\n"
|
|
449
|
+
" - You MUST format the plan using EXACTLY this recursive SDD-style markdown template. Do not skip any sections. Do not summarize.\n\n"
|
|
450
450
|
" ```markdown\n"
|
|
451
|
-
" # [Project Name]
|
|
452
|
-
"
|
|
453
|
-
"
|
|
454
|
-
" **
|
|
455
|
-
"
|
|
456
|
-
"
|
|
457
|
-
"
|
|
458
|
-
"
|
|
459
|
-
"
|
|
451
|
+
" # [Project Name] — Full Implementation Plan\n\n"
|
|
452
|
+
" **Source:** [Design doc reference]\n"
|
|
453
|
+
" **Stack:** [Tech Stack summary]\n"
|
|
454
|
+
" **Workspace:** [Root directory]\n\n"
|
|
455
|
+
" ## Context\n"
|
|
456
|
+
" [Detailed project context and requirements]\n\n"
|
|
457
|
+
" ## Environment\n"
|
|
458
|
+
" | Item | Status |\n"
|
|
459
|
+
" |------|--------|\n"
|
|
460
|
+
" | **OS** | [OS details] |\n"
|
|
461
|
+
" | **Language/Framework** | [Versions] |\n"
|
|
462
|
+
" | **Dependencies** | [Key libs] |\n\n"
|
|
463
|
+
" ## Phase 0 — Project Scaffolding\n"
|
|
464
|
+
" **Goal:** [Phase Goal]\n"
|
|
465
|
+
" ### 0.1 [Step Name]\n"
|
|
466
|
+
" [Code snippets, commands, implementation details]\n\n"
|
|
467
|
+
" ## Phase 1 — [Phase Name]\n"
|
|
468
|
+
" **Goal:** [Phase Goal]\n"
|
|
469
|
+
" ### 1.1 [Task Name]\n"
|
|
470
|
+
" [Code snippets, complete DB schemas, function signatures, exact logic. MUST NOT be a summary.]\n"
|
|
460
471
|
" **Checklist**:\n"
|
|
461
472
|
" - [ ] [Micro-step 1]\n"
|
|
462
473
|
" - [ ] [Micro-step 2]\n\n"
|
|
463
|
-
"
|
|
464
|
-
"
|
|
474
|
+
" ## CI/CD & Packaging\n"
|
|
475
|
+
" [Details on Github Actions, builds, installers]\n\n"
|
|
476
|
+
" ## Testing Strategy\n"
|
|
477
|
+
" [Unit, Integration, E2E plan]\n\n"
|
|
478
|
+
" ## Key Architecture Patterns\n"
|
|
479
|
+
" [Core rules to follow]\n\n"
|
|
480
|
+
" ## Development Setup\n"
|
|
481
|
+
" [Commands to start dev server/tests]\n\n"
|
|
482
|
+
" ## Verification\n"
|
|
483
|
+
" [Steps to verify each phase]\n\n"
|
|
484
|
+
" ## Dependency Graph\n"
|
|
485
|
+
" ```text\n"
|
|
486
|
+
" [ASCII Map of modules/components]\n"
|
|
465
487
|
" ```\n\n"
|
|
466
|
-
"
|
|
488
|
+
" ## Weekly Execution Schedule\n"
|
|
489
|
+
" ### Week X-Y: Phase Z\n"
|
|
490
|
+
" | Day | Tasks |\n"
|
|
491
|
+
" |-----|-------|\n"
|
|
492
|
+
" | **Day 1** | [Tasks] |\n"
|
|
493
|
+
" ```\n\n"
|
|
494
|
+
" - Under NO CIRCUMSTANCES should you write superficial steps like 'Initialize app' without providing the exact commands and code snippets in the Phase Implementation Details.\n"
|
|
467
495
|
" - Write the entire exhaustive plan into the file using your file-writing tools.\n"
|
|
468
496
|
"4. General Rules:\n"
|
|
469
497
|
" - STRICT FIDELITY: You MUST absolutely respect the languages, frameworks, endpoints, tables, and features mentioned in the user's project files or SDD.\n"
|
|
@@ -93,6 +93,9 @@ class TaskManager:
|
|
|
93
93
|
def get_task(self, task_id: str) -> Optional[Task]:
|
|
94
94
|
return self.tasks.get(task_id)
|
|
95
95
|
|
|
96
|
+
def get_all_tasks(self) -> List[Dict]:
|
|
97
|
+
return [t.to_dict() for t in self.tasks.values()]
|
|
98
|
+
|
|
96
99
|
def list_tasks(self) -> str:
|
|
97
100
|
if not self.tasks:
|
|
98
101
|
return "No tasks found."
|
|
@@ -37,6 +37,14 @@ class ToolRegistry:
|
|
|
37
37
|
},
|
|
38
38
|
"required": ["file_path", "content"]
|
|
39
39
|
},
|
|
40
|
+
"Append": {
|
|
41
|
+
"description": "Append content to the end of an existing file. Ideal for building large plans or documents chunk by chunk without hitting token limits.",
|
|
42
|
+
"parameters": {
|
|
43
|
+
"file_path": {"type": "string", "description": "Path to the file."},
|
|
44
|
+
"content": {"type": "string", "description": "Content to append to the file. A newline is automatically added."}
|
|
45
|
+
},
|
|
46
|
+
"required": ["file_path", "content"]
|
|
47
|
+
},
|
|
40
48
|
"Replace": {
|
|
41
49
|
"description": "Edit an existing file by replacing exact strings. Allows multiple non-contiguous edits in one call.",
|
|
42
50
|
"parameters": {
|
|
@@ -227,6 +235,8 @@ class ToolRegistry:
|
|
|
227
235
|
return cls._execute_read(args["file_path"], cwd, args.get("offset", 1), args.get("limit", 0))
|
|
228
236
|
elif tool_name == "Write":
|
|
229
237
|
return cls._execute_write(args["file_path"], args["content"], cwd)
|
|
238
|
+
elif tool_name == "Append":
|
|
239
|
+
return cls._execute_append(args["file_path"], args["content"], cwd)
|
|
230
240
|
elif tool_name == "Replace":
|
|
231
241
|
return cls._execute_replace(args["file_path"], args["replacements"], cwd)
|
|
232
242
|
elif tool_name == "ReplaceBlock":
|
|
@@ -296,18 +306,41 @@ class ToolRegistry:
|
|
|
296
306
|
|
|
297
307
|
@staticmethod
|
|
298
308
|
def _check_lazy_code(content: str, file_path: str):
|
|
299
|
-
|
|
300
|
-
for
|
|
309
|
+
import re
|
|
310
|
+
# Broad regex for // TO DO, // TODO, # TODO, // FIXME, etc. with flexible spaces
|
|
311
|
+
if re.search(r'(?i)(?://|#)\s*(?:to\s*do|fix\s*me)', content):
|
|
312
|
+
raise ToolError("Validation Error: Lazy code detected (TODO/FIXME). You must provide the full implementation.")
|
|
313
|
+
|
|
314
|
+
# Semantic lazy patterns
|
|
315
|
+
if re.search(r'(?i)(?:rest of|existing).*?(?:code|file).*?(?:remains|is unchanged|here)', content):
|
|
316
|
+
raise ToolError("Validation Error: Lazy code detected ('rest of code remains' or similar). Do not skip code.")
|
|
317
|
+
|
|
318
|
+
if re.search(r'(?i)(?:\.\.\.|…)\s*(?:existing|rest of|your)', content):
|
|
319
|
+
raise ToolError("Validation Error: Lazy code detected ('... existing code' or similar). Do not skip code.")
|
|
320
|
+
|
|
321
|
+
if re.search(r'(?i)(?:logic goes here|implement (?:this|the rest)|your code here|add your logic)', content):
|
|
322
|
+
raise ToolError("Validation Error: Lazy code detected ('logic goes here' or similar). Do not use placeholders.")
|
|
323
|
+
|
|
324
|
+
if re.search(r'(?i)(?://|#)\s*(?:unchanged|\.\.\.)', content) or re.search(r'(?m)^\s*\.\.\.\s*$', content):
|
|
325
|
+
raise ToolError("Validation Error: Lazy code detected (ellipsis placeholder '...' or 'unchanged'). You must provide the full implementation.")
|
|
326
|
+
|
|
327
|
+
# Rust/Python stubs
|
|
328
|
+
stub_patterns = ["todo!()", "unimplemented!()", "Insert code here", "pass\n", "PdfDocument::empty()", "return empty()"]
|
|
329
|
+
for p in stub_patterns:
|
|
301
330
|
if p in content:
|
|
302
331
|
raise ToolError(f"Validation Error: Lazy code detected ('{p}'). You must provide the full implementation.")
|
|
332
|
+
|
|
303
333
|
if file_path.endswith('.py'):
|
|
304
334
|
import ast
|
|
305
335
|
try:
|
|
306
336
|
tree = ast.parse(content)
|
|
307
337
|
for node in ast.walk(tree):
|
|
308
|
-
if isinstance(node, (ast.FunctionDef, ast.ClassDef)):
|
|
309
|
-
if len(node.body) == 1
|
|
310
|
-
|
|
338
|
+
if isinstance(node, (ast.FunctionDef, ast.ClassDef, ast.AsyncFunctionDef)):
|
|
339
|
+
if len(node.body) == 1:
|
|
340
|
+
if isinstance(node.body[0], ast.Pass):
|
|
341
|
+
raise ToolError("Validation Error: Lazy code detected (empty function/class with 'pass'). You must provide the full implementation.")
|
|
342
|
+
elif isinstance(node.body[0], ast.Expr) and isinstance(node.body[0].value, ast.Constant) and node.body[0].value.value is Ellipsis:
|
|
343
|
+
raise ToolError("Validation Error: Lazy code detected (empty function/class with '...'). You must provide the full implementation.")
|
|
311
344
|
except SyntaxError:
|
|
312
345
|
pass
|
|
313
346
|
|
|
@@ -615,6 +648,31 @@ class ToolRegistry:
|
|
|
615
648
|
diff_str = f"\nDiff:\n{diff}" if diff else ""
|
|
616
649
|
return f"Successfully wrote to {file_path}.{diff_str}"
|
|
617
650
|
|
|
651
|
+
@staticmethod
|
|
652
|
+
def _execute_append(file_path: str, content: str, cwd: str) -> str:
|
|
653
|
+
# Physical Blocker for Lazy Code
|
|
654
|
+
ToolRegistry._check_lazy_code(content, file_path)
|
|
655
|
+
|
|
656
|
+
# Run Secret Scanner
|
|
657
|
+
try:
|
|
658
|
+
from .secret_scanner import SecretScanner
|
|
659
|
+
findings = SecretScanner.scan(content)
|
|
660
|
+
if findings:
|
|
661
|
+
raise ToolError(f"SecretScanner blocked write: {len(findings)} secrets detected ({', '.join(findings)})")
|
|
662
|
+
except ImportError:
|
|
663
|
+
pass
|
|
664
|
+
|
|
665
|
+
full_path = os.path.join(cwd, file_path)
|
|
666
|
+
os.makedirs(os.path.dirname(os.path.abspath(full_path)) or ".", exist_ok=True)
|
|
667
|
+
|
|
668
|
+
ToolRegistry._backup_file(full_path, cwd)
|
|
669
|
+
with open(full_path, "a", encoding="utf-8") as f:
|
|
670
|
+
if not content.startswith("\n"):
|
|
671
|
+
f.write("\n")
|
|
672
|
+
f.write(content)
|
|
673
|
+
|
|
674
|
+
return f"Successfully appended to {file_path}."
|
|
675
|
+
|
|
618
676
|
@staticmethod
|
|
619
677
|
def _execute_replace(file_path: str, replacements: list, cwd: str) -> str:
|
|
620
678
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: nimcode
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.8.0
|
|
4
4
|
Summary: A standalone, robust coding agent for NVIDIA NIM models.
|
|
5
5
|
Home-page: https://github.com/Batunash/nimcode
|
|
6
6
|
Author: Autonomous Agent
|
|
@@ -157,7 +157,7 @@ NimCode's behavior can be fully customized through `~/.nimcode/settings.json` (g
|
|
|
157
157
|
|
|
158
158
|
```json
|
|
159
159
|
{
|
|
160
|
-
"model": "meta/llama-3.
|
|
160
|
+
"model": "meta/llama-3.3-70b-instruct",
|
|
161
161
|
"api_base_url": "https://integrate.api.nvidia.com/v1",
|
|
162
162
|
"timeout_command": 1200,
|
|
163
163
|
"timeout_llm": 120,
|
|
@@ -175,7 +175,7 @@ NimCode's behavior can be fully customized through `~/.nimcode/settings.json` (g
|
|
|
175
175
|
|
|
176
176
|
| Setting | Default | Description |
|
|
177
177
|
|---------|---------|-------------|
|
|
178
|
-
| `model` | `meta/llama-3.
|
|
178
|
+
| `model` | `meta/llama-3.3-70b-instruct` | Default LLM model |
|
|
179
179
|
| `api_base_url` | NVIDIA NIM endpoint | Switch to Ollama/vLLM by changing this |
|
|
180
180
|
| `timeout_command` | 1200 | Max seconds for bash commands (0 = infinite) |
|
|
181
181
|
| `timeout_llm` | 120 | Max seconds for LLM API calls (0 = infinite) |
|
|
@@ -193,7 +193,24 @@ NimCode's behavior can be fully customized through `~/.nimcode/settings.json` (g
|
|
|
193
193
|
|
|
194
194
|
## 📋 Changelog
|
|
195
195
|
|
|
196
|
-
### v0.
|
|
196
|
+
### v0.8.0 (Latest)
|
|
197
|
+
- 📝 **SDD-Style Planning**: `/plan` mode now forces the LLM to output Software Design Document (SDD) style templates with specific dependency graphs, timelines, and rigorous phase tracking (the "Empty Recursive Goblet" approach).
|
|
198
|
+
- 🧱 **Physical Exit Blockers for Tasks**: If the LLM tries to call `TASK_COMPLETE` but still has pending or in_progress tasks in its `.nimcode/tasks.json` file, it will physically intercept the tool call, throw a `SYSTEM ERROR`, and force the LLM to finish the open tasks.
|
|
199
|
+
- 🛡️ **Ultimate Anti-Lazy Shield**: Expanded AST and regex blockers that catch semantic laziness (e.g., `"// rest of the code remains unchanged"`, `"logic goes here"`, or using `...` / Ellipsis in Python). A zero-tolerance policy that forces the LLM to rewrite incomplete code.
|
|
200
|
+
- 🧩 **Chunked Generation (Append Tool)**: A new `Append` tool allows the LLM to write massive documents and code files in chunks over multiple turns without hitting context token limits, directly curing "Lazy Coder Syndrome".
|
|
201
|
+
- 🧪 **205 tests passing**
|
|
202
|
+
|
|
203
|
+
### v0.7.0
|
|
204
|
+
- 🛡️ **Strict Markdown Validator**: `/plan` mode now physically blocks tasks that are under 150 characters or missing required Markdown headers (`Target Files`, `Implementation Details`, `Checklist`).
|
|
205
|
+
- 🧠 **Context Amnesia Shield (AST Diff)**: When modifying Python files, the `ReplaceBlock` tool compares the Abstract Syntax Tree (AST) before and after. If the LLM accidentally deletes over 30% or 2+ top-level functions (truncation hallucination), it instantly blocks the write and forces a fix.
|
|
206
|
+
- 🔗 **Task Linearity Guard**: `TaskManager` enforces strict order. The agent cannot start Task 1.2 until Task 1.1 is fully completed.
|
|
207
|
+
|
|
208
|
+
### v0.6.0
|
|
209
|
+
- 🛡️ **Anti-Laziness AST Blockers**: Native AST checking explicitly intercepts `pass` blocks, empty classes, and lazy placeholder comments (like `// TODO`), forcing the LLM to write full implementations.
|
|
210
|
+
- 🕵️ **Adversarial QA Gate**: Built a multi-agent validation layer (`qa_agent.py`) that strictly critiques the main agent's work. The main agent cannot complete tasks without receiving a `VERDICT: PASS` from the QA Agent.
|
|
211
|
+
- 💾 **Task State Machine**: Replaced in-memory lists with `.nimcode/tasks.json` persistent storage via `TaskManager`, allowing robust restartability and task state tracking (`pending`, `in_progress`, `completed`).
|
|
212
|
+
|
|
213
|
+
### v0.5.5
|
|
197
214
|
- 🐛 **UI Bug Fix**: Fixed a visual bug where the CLI welcome banner hardcoded `NimCode v0.2.0` despite the user having updated to a newer version.
|
|
198
215
|
|
|
199
216
|
### v0.5.4
|
|
@@ -7,7 +7,8 @@ import json
|
|
|
7
7
|
|
|
8
8
|
@pytest.fixture
|
|
9
9
|
def agent():
|
|
10
|
-
|
|
10
|
+
with patch("nimcode.task_manager.TaskManager.get_all_tasks", return_value=[]):
|
|
11
|
+
yield Agent(api_key="test_key", max_turns=3, permission_mode=PermissionMode.BYPASS)
|
|
11
12
|
|
|
12
13
|
def test_agent_init(agent):
|
|
13
14
|
assert agent.client.api_key == "test_key"
|
|
@@ -7,7 +7,7 @@ from nimcode.config import load_settings, save_global_setting
|
|
|
7
7
|
def test_load_settings_default():
|
|
8
8
|
with patch("os.path.exists", return_value=False):
|
|
9
9
|
settings = load_settings()
|
|
10
|
-
assert settings["model"] == "meta/llama-3.
|
|
10
|
+
assert settings["model"] == "meta/llama-3.3-70b-instruct"
|
|
11
11
|
assert settings["mcp_servers"] == {}
|
|
12
12
|
# New configurable settings
|
|
13
13
|
assert settings["max_turns"] == 200
|
|
@@ -74,4 +74,4 @@ def test_load_settings_error_handling():
|
|
|
74
74
|
with patch("os.path.exists", return_value=True):
|
|
75
75
|
with patch("builtins.open", side_effect=Exception("Read error")):
|
|
76
76
|
settings = load_settings()
|
|
77
|
-
assert settings["model"] == "meta/llama-3.
|
|
77
|
+
assert settings["model"] == "meta/llama-3.3-70b-instruct"
|
|
@@ -79,3 +79,34 @@ def test_strict_markdown_validator():
|
|
|
79
79
|
out = ToolRegistry.execute({"tool": "TaskCreate", "args": {"task_id": "test_valid", "subject": "S", "description": valid_desc}}, cwd=".")
|
|
80
80
|
assert "Validation Error" not in out
|
|
81
81
|
|
|
82
|
+
def test_ast_blocker_rejects_ellipsis():
|
|
83
|
+
content = "def test():\n ...\n"
|
|
84
|
+
try:
|
|
85
|
+
ToolRegistry._check_lazy_code(content, "dummy.py")
|
|
86
|
+
pytest.fail("Failed to block Ellipsis")
|
|
87
|
+
except Exception as e:
|
|
88
|
+
assert "Lazy code detected" in str(e)
|
|
89
|
+
|
|
90
|
+
def test_regex_blocker_rejects_rest_of_code():
|
|
91
|
+
content = "def test():\n print('test')\n# rest of the code remains unchanged\n"
|
|
92
|
+
try:
|
|
93
|
+
ToolRegistry._check_lazy_code(content, "dummy.py")
|
|
94
|
+
pytest.fail("Failed to block 'rest of the code'")
|
|
95
|
+
except Exception as e:
|
|
96
|
+
assert "Lazy code detected" in str(e)
|
|
97
|
+
|
|
98
|
+
def test_regex_blocker_rejects_ellipsis_comment():
|
|
99
|
+
content = "def test():\n print('test')\n# ...\n"
|
|
100
|
+
try:
|
|
101
|
+
ToolRegistry._check_lazy_code(content, "dummy.py")
|
|
102
|
+
pytest.fail("Failed to block '# ...'")
|
|
103
|
+
except Exception as e:
|
|
104
|
+
assert "Lazy code detected" in str(e)
|
|
105
|
+
|
|
106
|
+
def test_regex_blocker_rejects_logic_goes_here():
|
|
107
|
+
content = "def test():\n # logic goes here\n"
|
|
108
|
+
try:
|
|
109
|
+
ToolRegistry._check_lazy_code(content, "dummy.py")
|
|
110
|
+
pytest.fail("Failed to block 'logic goes here'")
|
|
111
|
+
except Exception as e:
|
|
112
|
+
assert "Lazy code detected" in str(e)
|
|
@@ -10,7 +10,7 @@ from nimcode.model_registry import (
|
|
|
10
10
|
|
|
11
11
|
def test_get_context_window_known_model():
|
|
12
12
|
"""Known models should return their specific context window size."""
|
|
13
|
-
assert get_context_window("meta/llama-3.
|
|
13
|
+
assert get_context_window("meta/llama-3.3-70b-instruct") == 128000
|
|
14
14
|
assert get_context_window("nvidia/nemotron-4-340b-instruct") == 4096
|
|
15
15
|
assert get_context_window("mistralai/mixtral-8x22b-instruct-v0.1") == 65536
|
|
16
16
|
|
|
@@ -4,7 +4,8 @@ from unittest.mock import patch
|
|
|
4
4
|
from nimcode.agents.qa_agent import QAAgent
|
|
5
5
|
|
|
6
6
|
@patch("nimcode.nim_client.NimClient.chat")
|
|
7
|
-
|
|
7
|
+
@patch("nimcode.task_manager.TaskManager.get_all_tasks", return_value=[])
|
|
8
|
+
def test_qa_agent_verdict_pass(mock_get_tasks, mock_chat):
|
|
8
9
|
# Mocking the async chat function
|
|
9
10
|
async def mock_chat_return(*args, **kwargs):
|
|
10
11
|
return "I have tested the code. VERDICT: PASS"
|
|
@@ -18,7 +19,8 @@ def test_qa_agent_verdict_pass(mock_chat):
|
|
|
18
19
|
assert "QA Agent VERDICT: PASS" in result
|
|
19
20
|
|
|
20
21
|
@patch("nimcode.nim_client.NimClient.chat")
|
|
21
|
-
|
|
22
|
+
@patch("nimcode.task_manager.TaskManager.get_all_tasks", return_value=[])
|
|
23
|
+
def test_qa_agent_verdict_fail(mock_get_tasks, mock_chat):
|
|
22
24
|
async def mock_chat_return(*args, **kwargs):
|
|
23
25
|
return "The test failed. VERDICT: FAIL"
|
|
24
26
|
mock_chat.side_effect = mock_chat_return
|
|
@@ -6,8 +6,9 @@ from nimcode.agent import Agent
|
|
|
6
6
|
|
|
7
7
|
@pytest.fixture
|
|
8
8
|
def agent():
|
|
9
|
-
with patch("nimcode.
|
|
10
|
-
|
|
9
|
+
with patch("nimcode.task_manager.TaskManager.get_all_tasks", return_value=[]):
|
|
10
|
+
with patch("nimcode.agent.NimClient") as mock_client:
|
|
11
|
+
a = Agent(api_key="test_key")
|
|
11
12
|
# Ensure we have a mock for chat_one_shot and chat
|
|
12
13
|
a.client.chat_one_shot = AsyncMock(return_value="I am done. TASK_COMPLETE")
|
|
13
14
|
a.client.get_available_models = AsyncMock(return_value=["model1", "model2"])
|
|
@@ -16,7 +17,7 @@ def agent():
|
|
|
16
17
|
yield "Hello"
|
|
17
18
|
yield " World"
|
|
18
19
|
a.client.chat = MagicMock(side_effect=lambda *args, **kwargs: mock_chat_generator(*args, **kwargs))
|
|
19
|
-
|
|
20
|
+
yield a
|
|
20
21
|
|
|
21
22
|
def test_save_load_history(agent):
|
|
22
23
|
agent.messages = [{"role": "user", "content": "hello"}]
|
|
@@ -47,7 +48,8 @@ def test_load_history_error(agent):
|
|
|
47
48
|
mock_log.assert_called_once()
|
|
48
49
|
|
|
49
50
|
@pytest.mark.asyncio
|
|
50
|
-
|
|
51
|
+
@patch("nimcode.task_manager.TaskManager.get_all_tasks", return_value=[])
|
|
52
|
+
async def test_run_complete(mock_get_tasks, agent):
|
|
51
53
|
agent.mcp = MagicMock()
|
|
52
54
|
agent.mcp.connect_all = AsyncMock()
|
|
53
55
|
|
|
@@ -41,6 +41,19 @@ def test_execute_read_write(tmp_path):
|
|
|
41
41
|
res_r = ToolRegistry.execute({"tool": "Read", "args": {"file_path": "test.txt"}}, cwd)
|
|
42
42
|
assert res_r == "Hello World"
|
|
43
43
|
|
|
44
|
+
def test_execute_append(tmp_path):
|
|
45
|
+
cwd = str(tmp_path)
|
|
46
|
+
# Write initial content
|
|
47
|
+
ToolRegistry.execute({"tool": "Write", "args": {"file_path": "test.txt", "content": "Hello"}}, cwd)
|
|
48
|
+
|
|
49
|
+
# Append content
|
|
50
|
+
res_a = ToolRegistry.execute({"tool": "Append", "args": {"file_path": "test.txt", "content": " World"}}, cwd)
|
|
51
|
+
assert "Successfully" in res_a
|
|
52
|
+
|
|
53
|
+
# Read and verify
|
|
54
|
+
res_r = ToolRegistry.execute({"tool": "Read", "args": {"file_path": "test.txt"}}, cwd)
|
|
55
|
+
assert res_r == "Hello\n World"
|
|
56
|
+
|
|
44
57
|
def test_execute_replace_success(tmp_path):
|
|
45
58
|
cwd = str(tmp_path)
|
|
46
59
|
file_path = os.path.join(cwd, "test.txt")
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "0.7.0"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|