neuroncli 2.1.0__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.
neuroncli/config.py ADDED
@@ -0,0 +1,112 @@
1
+ """NeuronCLI — Configuration module. v2.0 with modes, context management, and hybrid pricing."""
2
+
3
+ from __future__ import annotations
4
+ from .auth import ensure_api_key
5
+
6
+ import os
7
+ from dataclasses import dataclass, field
8
+ from pathlib import Path
9
+
10
+
11
+ # ── Operational Modes ─────────────────────────────────────────────
12
+ MODE_STANDARD = "standard" # Ask permission for writes/commands
13
+ MODE_PLAN = "plan" # Plan first, then execute after approval
14
+ MODE_YOLO = "yolo" # Skip all permission prompts
15
+
16
+
17
+ @dataclass
18
+ class AgentConfig:
19
+ """All configurable settings for the NeuronCLI agent."""
20
+
21
+ # ── Provider Selection ────────────────────────────────────────
22
+ provider: str = "openrouter" # "openrouter" or "ollama"
23
+
24
+ # ── OpenRouter Settings ───────────────────────────────────────
25
+ api_key: str = ""
26
+ model: str = "moonshotai/kimi-k2.5" # Free reasoning model
27
+ max_tokens: int = 4096
28
+
29
+ # ── Ollama Settings (fallback) ────────────────────────────────
30
+ ollama_model: str = "qwen2.5-coder:7b"
31
+ base_url: str = "http://localhost:11434"
32
+ num_ctx: int = 8192
33
+
34
+ # ── Shared LLM Settings ──────────────────────────────────────
35
+ temperature: float = 0.2
36
+ top_p: float = 0.9
37
+
38
+ # ── Agent Behavior ────────────────────────────────────────────
39
+ max_iterations: int = 15 # Safety cap for ReAct loop
40
+ streaming: bool = True # Stream tokens to terminal
41
+ confirm_dangerous: bool = True # Ask before destructive commands
42
+ show_thinking: bool = True # Show reasoning tokens (K2.5)
43
+ mode: str = MODE_STANDARD # standard / plan / yolo
44
+
45
+ # ── Context Management ────────────────────────────────────────
46
+ context_warn_percent: float = 0.85 # Warn at 85% context usage
47
+ context_compress_percent: float = 0.92 # Auto-compress at 92%
48
+ max_context_tokens: int = 128000 # K2.5 supports 128k
49
+
50
+ # ── Working Directory ─────────────────────────────────────────
51
+ working_dir: str = field(default_factory=lambda: os.getcwd())
52
+
53
+ # ── Dangerous command patterns (require confirmation) ─────────
54
+ dangerous_patterns: tuple[str, ...] = (
55
+ "rm ", "del ", "rmdir", "format ",
56
+ "shutdown", "restart",
57
+ "> /dev/null", "| rm",
58
+ "drop table", "drop database",
59
+ "git push --force", "git reset --hard",
60
+ )
61
+
62
+ @classmethod
63
+ def from_env(cls) -> "AgentConfig":
64
+ """Build config from environment variables with defaults."""
65
+ provider = os.environ.get("NEURON_PROVIDER", "openrouter")
66
+ api_key = ensure_api_key() or ""
67
+
68
+ if provider == "openrouter" and not api_key:
69
+ provider = "ollama"
70
+
71
+ model = os.environ.get("NEURON_MODEL", "moonshotai/kimi-k2.5")
72
+ ollama_model = os.environ.get("NEURON_OLLAMA_MODEL", "qwen2.5-coder:7b")
73
+
74
+ return cls(
75
+ provider=provider,
76
+ api_key=api_key,
77
+ model=model,
78
+ ollama_model=ollama_model,
79
+ base_url=os.environ.get("OLLAMA_HOST", "http://localhost:11434"),
80
+ temperature=float(os.environ.get("NEURON_TEMPERATURE", "0.2")),
81
+ max_iterations=int(os.environ.get("NEURON_MAX_ITER", "15")),
82
+ max_tokens=int(os.environ.get("NEURON_MAX_TOKENS", "4096")),
83
+ num_ctx=int(os.environ.get("NEURON_CTX", "8192")),
84
+ working_dir=os.environ.get("NEURON_WORK_DIR", os.getcwd()),
85
+ )
86
+
87
+ def resolve_path(self, path: str) -> Path:
88
+ p = Path(path)
89
+ if p.is_absolute():
90
+ return p
91
+ return Path(self.working_dir) / p
92
+
93
+ def is_dangerous_command(self, cmd: str) -> bool:
94
+ lower = cmd.lower().strip()
95
+ return any(pat in lower for pat in self.dangerous_patterns)
96
+
97
+ @property
98
+ def active_model(self) -> str:
99
+ if self.provider == "ollama":
100
+ return self.ollama_model
101
+ return self.model
102
+
103
+ @property
104
+ def needs_confirmation(self) -> bool:
105
+ """Whether current mode requires user confirmation for writes."""
106
+ return self.mode == MODE_STANDARD and self.confirm_dangerous
107
+
108
+
109
+ # ── App-wide constants ────────────────────────────────────────────
110
+
111
+ APP_NAME = "NeuronCLI"
112
+ VERSION = "2.1.0"
@@ -0,0 +1,86 @@
1
+ """NeuronCLI — Git integration. Auto-commit AI edits like Aider."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import subprocess
6
+ from pathlib import Path
7
+
8
+
9
+ def is_git_repo(working_dir: str) -> bool:
10
+ """Check if the working directory is inside a git repository."""
11
+ try:
12
+ result = subprocess.run(
13
+ ["git", "rev-parse", "--is-inside-work-tree"],
14
+ cwd=working_dir, capture_output=True, text=True, timeout=5,
15
+ )
16
+ return result.returncode == 0 and result.stdout.strip() == "true"
17
+ except (FileNotFoundError, subprocess.TimeoutExpired):
18
+ return False
19
+
20
+
21
+ def git_auto_commit(working_dir: str, files: list[str], message: str) -> str | None:
22
+ """Stage specific files and commit with a descriptive message.
23
+ Returns commit hash on success, None on failure.
24
+ """
25
+ if not is_git_repo(working_dir):
26
+ return None
27
+
28
+ try:
29
+ # Stage only the modified files
30
+ for f in files:
31
+ subprocess.run(
32
+ ["git", "add", str(f)],
33
+ cwd=working_dir, capture_output=True, timeout=5,
34
+ )
35
+
36
+ # Check if there are staged changes
37
+ status = subprocess.run(
38
+ ["git", "diff", "--cached", "--quiet"],
39
+ cwd=working_dir, capture_output=True, timeout=5,
40
+ )
41
+ if status.returncode == 0:
42
+ return None # Nothing staged
43
+
44
+ # Commit
45
+ result = subprocess.run(
46
+ ["git", "commit", "-m", f"neuron: {message}"],
47
+ cwd=working_dir, capture_output=True, text=True, timeout=10,
48
+ )
49
+ if result.returncode == 0:
50
+ # Extract short hash
51
+ hash_result = subprocess.run(
52
+ ["git", "rev-parse", "--short", "HEAD"],
53
+ cwd=working_dir, capture_output=True, text=True, timeout=5,
54
+ )
55
+ return hash_result.stdout.strip() if hash_result.returncode == 0 else "ok"
56
+ return None
57
+ except (FileNotFoundError, subprocess.TimeoutExpired, OSError):
58
+ return None
59
+
60
+
61
+ def git_status(working_dir: str) -> str:
62
+ """Get git status summary."""
63
+ if not is_git_repo(working_dir):
64
+ return "Not a git repository."
65
+ try:
66
+ result = subprocess.run(
67
+ ["git", "status", "--short"],
68
+ cwd=working_dir, capture_output=True, text=True, timeout=5,
69
+ )
70
+ return result.stdout.strip() or "Working tree clean."
71
+ except Exception:
72
+ return "Error running git status."
73
+
74
+
75
+ def git_diff(working_dir: str) -> str:
76
+ """Get current unstaged diff."""
77
+ if not is_git_repo(working_dir):
78
+ return "Not a git repository."
79
+ try:
80
+ result = subprocess.run(
81
+ ["git", "diff", "--stat"],
82
+ cwd=working_dir, capture_output=True, text=True, timeout=5,
83
+ )
84
+ return result.stdout.strip() or "No changes."
85
+ except Exception:
86
+ return "Error running git diff."
@@ -0,0 +1,150 @@
1
+ """NeuronCLI — Ollama HTTP streaming client. Zero external dependencies."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ import urllib.error
7
+ import urllib.request
8
+ from dataclasses import dataclass
9
+ from typing import Generator
10
+
11
+ from .config import AgentConfig
12
+
13
+
14
+ class OllamaConnectionError(Exception):
15
+ """Raised when the Ollama server is unreachable."""
16
+ pass
17
+
18
+
19
+ @dataclass(frozen=True)
20
+ class ChatMessage:
21
+ """A single message in the conversation."""
22
+ role: str # "system", "user", "assistant"
23
+ content: str
24
+
25
+ def to_dict(self) -> dict[str, str]:
26
+ return {"role": self.role, "content": self.content}
27
+
28
+
29
+ class OllamaClient:
30
+ """
31
+ HTTP client for Ollama's /api/chat endpoint.
32
+ Uses only stdlib — no pip dependencies needed.
33
+ """
34
+
35
+ def __init__(self, config: AgentConfig | None = None):
36
+ self.config = config or AgentConfig.from_env()
37
+ self._base = self.config.base_url.rstrip("/")
38
+
39
+ def health_check(self) -> bool:
40
+ """Check if Ollama server is responding."""
41
+ try:
42
+ req = urllib.request.Request(f"{self._base}/api/tags")
43
+ with urllib.request.urlopen(req, timeout=5) as resp:
44
+ return resp.status == 200
45
+ except (urllib.error.URLError, OSError):
46
+ return False
47
+
48
+ def list_models(self) -> list[str]:
49
+ """List locally installed model names."""
50
+ try:
51
+ req = urllib.request.Request(f"{self._base}/api/tags")
52
+ with urllib.request.urlopen(req, timeout=5) as resp:
53
+ data = json.loads(resp.read().decode())
54
+ return [m["name"] for m in data.get("models", [])]
55
+ except (urllib.error.URLError, OSError):
56
+ return []
57
+
58
+ def chat(self, messages: list[ChatMessage], model: str | None = None) -> str:
59
+ """Send a chat request and return the full response text."""
60
+ payload = self._build_payload(messages, model, stream=False)
61
+ data = self._post("/api/chat", payload)
62
+ return data.get("message", {}).get("content", "")
63
+
64
+ def chat_stream(
65
+ self,
66
+ messages: list[ChatMessage],
67
+ model: str | None = None,
68
+ ) -> Generator[str, None, None]:
69
+ """
70
+ Stream a chat response token by token.
71
+ Yields individual tokens as strings.
72
+ """
73
+ payload = self._build_payload(messages, model, stream=True)
74
+ body = json.dumps(payload).encode("utf-8")
75
+ req = urllib.request.Request(
76
+ f"{self._base}/api/chat",
77
+ data=body,
78
+ headers={"Content-Type": "application/json"},
79
+ method="POST",
80
+ )
81
+
82
+ try:
83
+ resp = urllib.request.urlopen(req, timeout=300)
84
+ except urllib.error.URLError as exc:
85
+ raise OllamaConnectionError(
86
+ f"\n [X] Cannot connect to Ollama at {self._base}\n"
87
+ f" Start it with: ollama serve\n"
88
+ f" Error: {exc}"
89
+ ) from exc
90
+
91
+ buffer = b""
92
+ try:
93
+ while True:
94
+ byte = resp.read(1)
95
+ if not byte:
96
+ break
97
+ buffer += byte
98
+ if byte == b"\n":
99
+ line = buffer.decode("utf-8", errors="replace").strip()
100
+ buffer = b""
101
+ if not line:
102
+ continue
103
+ try:
104
+ data = json.loads(line)
105
+ except json.JSONDecodeError:
106
+ continue
107
+ token = data.get("message", {}).get("content", "")
108
+ if token:
109
+ yield token
110
+ if data.get("done", False):
111
+ break
112
+ finally:
113
+ resp.close()
114
+
115
+ # ── Internal helpers ──────────────────────────────────────────
116
+
117
+ def _build_payload(
118
+ self,
119
+ messages: list[ChatMessage],
120
+ model: str | None,
121
+ stream: bool,
122
+ ) -> dict:
123
+ return {
124
+ "model": model or self.config.model,
125
+ "messages": [m.to_dict() for m in messages],
126
+ "stream": stream,
127
+ "options": {
128
+ "temperature": self.config.temperature,
129
+ "top_p": self.config.top_p,
130
+ "num_ctx": self.config.num_ctx,
131
+ },
132
+ }
133
+
134
+ def _post(self, path: str, payload: dict) -> dict:
135
+ body = json.dumps(payload).encode("utf-8")
136
+ req = urllib.request.Request(
137
+ f"{self._base}{path}",
138
+ data=body,
139
+ headers={"Content-Type": "application/json"},
140
+ method="POST",
141
+ )
142
+ try:
143
+ with urllib.request.urlopen(req, timeout=300) as resp:
144
+ return json.loads(resp.read().decode("utf-8"))
145
+ except urllib.error.URLError as exc:
146
+ raise OllamaConnectionError(
147
+ f"\n [X] Cannot connect to Ollama at {self._base}\n"
148
+ f" Start it with: ollama serve\n"
149
+ f" Error: {exc}"
150
+ ) from exc
@@ -0,0 +1,220 @@
1
+ """NeuronCLI — OpenRouter HTTP streaming client. Zero external dependencies.
2
+
3
+ Supports reasoning models (Kimi K2.5) where the response has both
4
+ a `reasoning` field (chain-of-thought) and a `content` field (final answer).
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import json
10
+ import urllib.error
11
+ import urllib.request
12
+ from dataclasses import dataclass
13
+ from typing import Generator
14
+
15
+ from .config import AgentConfig
16
+
17
+
18
+ class OpenRouterConnectionError(Exception):
19
+ """Raised when the OpenRouter API is unreachable or returns an error."""
20
+ pass
21
+
22
+
23
+ @dataclass(frozen=True)
24
+ class ChatMessage:
25
+ """A single message in the conversation."""
26
+ role: str # "system", "user", "assistant"
27
+ content: str
28
+
29
+ def to_dict(self) -> dict[str, str]:
30
+ return {"role": self.role, "content": self.content}
31
+
32
+
33
+ class OpenRouterClient:
34
+ """
35
+ HTTP client for OpenRouter's OpenAI-compatible /api/v1/chat/completions.
36
+ Uses only stdlib — no pip dependencies needed.
37
+
38
+ Handles reasoning models (like Kimi K2.5) that emit:
39
+ - `delta.reasoning` tokens (thinking, shown dimmed)
40
+ - `delta.content` tokens (final answer)
41
+ """
42
+
43
+ API_BASE = "https://openrouter.ai/api/v1"
44
+
45
+ def __init__(self, config: AgentConfig | None = None):
46
+ self.config = config or AgentConfig.from_env()
47
+
48
+ def health_check(self) -> bool:
49
+ """Check if OpenRouter API is reachable and the API key is valid."""
50
+ try:
51
+ req = urllib.request.Request(
52
+ f"{self.API_BASE}/models",
53
+ headers=self._headers(),
54
+ )
55
+ with urllib.request.urlopen(req, timeout=10) as resp:
56
+ return resp.status == 200
57
+ except (urllib.error.URLError, OSError):
58
+ return False
59
+
60
+ def list_models(self) -> list[str]:
61
+ """List coding-relevant models from OpenRouter."""
62
+ try:
63
+ req = urllib.request.Request(
64
+ f"{self.API_BASE}/models",
65
+ headers=self._headers(),
66
+ )
67
+ with urllib.request.urlopen(req, timeout=10) as resp:
68
+ data = json.loads(resp.read().decode())
69
+ models = data.get("data", [])
70
+ keywords = ("kimi", "deepseek", "qwen", "codestral", "claude", "gemini")
71
+ names = [m["id"] for m in models
72
+ if any(k in m["id"].lower() for k in keywords)]
73
+ return sorted(names)[:25]
74
+ except (urllib.error.URLError, OSError):
75
+ return []
76
+
77
+ def chat(self, messages: list[ChatMessage], model: str | None = None) -> str:
78
+ """Send a chat request and return the full response text."""
79
+ payload = self._build_payload(messages, model, stream=False)
80
+ data = self._post(payload)
81
+ choice = data.get("choices", [{}])[0]
82
+ msg = choice.get("message", {})
83
+ # Kimi K2.5 reasoning models: content may be in `content` or `reasoning`
84
+ content = msg.get("content") or ""
85
+ if not content:
86
+ content = msg.get("reasoning", "")
87
+ return content
88
+
89
+ def chat_stream(
90
+ self,
91
+ messages: list[ChatMessage],
92
+ model: str | None = None,
93
+ ) -> Generator[str, None, None]:
94
+ """
95
+ Stream a chat response token by token.
96
+
97
+ For reasoning models (Kimi K2.5), yields:
98
+ - reasoning tokens with a dim prefix (hidden thinking)
99
+ - content tokens normally (the actual response)
100
+ """
101
+ payload = self._build_payload(messages, model, stream=True)
102
+ body = json.dumps(payload).encode("utf-8")
103
+ req = urllib.request.Request(
104
+ f"{self.API_BASE}/chat/completions",
105
+ data=body,
106
+ headers=self._headers(json_body=True),
107
+ method="POST",
108
+ )
109
+
110
+ try:
111
+ resp = urllib.request.urlopen(req, timeout=300)
112
+ except urllib.error.HTTPError as exc:
113
+ error_body = exc.read().decode("utf-8", errors="replace")
114
+ try:
115
+ err_data = json.loads(error_body)
116
+ msg = err_data.get("error", {}).get("message", error_body)
117
+ except json.JSONDecodeError:
118
+ msg = error_body
119
+ raise OpenRouterConnectionError(
120
+ f"\n [X] OpenRouter API error ({exc.code})\n"
121
+ f" {msg}"
122
+ ) from exc
123
+ except urllib.error.URLError as exc:
124
+ raise OpenRouterConnectionError(
125
+ f"\n [X] Cannot reach OpenRouter API.\n"
126
+ f" Check your internet connection.\n"
127
+ f" Error: {exc}"
128
+ ) from exc
129
+
130
+ in_reasoning = False
131
+ try:
132
+ for raw_line in resp:
133
+ line = raw_line.decode("utf-8", errors="replace").strip()
134
+ if not line or line == "data: [DONE]":
135
+ continue
136
+ if not line.startswith("data: "):
137
+ continue
138
+ try:
139
+ chunk = json.loads(line[6:])
140
+ except json.JSONDecodeError:
141
+ continue
142
+ choices = chunk.get("choices", [])
143
+ if not choices:
144
+ continue
145
+ delta = choices[0].get("delta", {})
146
+
147
+ # Handle reasoning tokens (Kimi K2.5 chain-of-thought)
148
+ reasoning_token = delta.get("reasoning", "")
149
+ content_token = delta.get("content", "")
150
+
151
+ if reasoning_token:
152
+ if not in_reasoning:
153
+ yield "\033[2m\033[90m" # DIM + GRAY
154
+ in_reasoning = True
155
+ yield reasoning_token
156
+
157
+ if content_token:
158
+ if in_reasoning:
159
+ yield "\033[0m\n" # Reset after reasoning
160
+ in_reasoning = False
161
+ yield content_token
162
+ finally:
163
+ if in_reasoning:
164
+ yield "\033[0m\n"
165
+ resp.close()
166
+
167
+ # ── Internal helpers ──────────────────────────────────────────
168
+
169
+ def _headers(self, json_body: bool = False) -> dict[str, str]:
170
+ h: dict[str, str] = {
171
+ "Authorization": f"Bearer {self.config.api_key}",
172
+ "HTTP-Referer": "https://github.com/RAHUL-DevelopeRR/neuroncli",
173
+ "X-Title": "NeuronCLI",
174
+ }
175
+ if json_body:
176
+ h["Content-Type"] = "application/json"
177
+ return h
178
+
179
+ def _build_payload(
180
+ self,
181
+ messages: list[ChatMessage],
182
+ model: str | None,
183
+ stream: bool,
184
+ ) -> dict:
185
+ return {
186
+ "model": model or self.config.model,
187
+ "messages": [m.to_dict() for m in messages],
188
+ "stream": stream,
189
+ "max_tokens": self.config.max_tokens,
190
+ "temperature": self.config.temperature,
191
+ "top_p": self.config.top_p,
192
+ }
193
+
194
+ def _post(self, payload: dict) -> dict:
195
+ body = json.dumps(payload).encode("utf-8")
196
+ req = urllib.request.Request(
197
+ f"{self.API_BASE}/chat/completions",
198
+ data=body,
199
+ headers=self._headers(json_body=True),
200
+ method="POST",
201
+ )
202
+ try:
203
+ with urllib.request.urlopen(req, timeout=300) as resp:
204
+ return json.loads(resp.read().decode("utf-8"))
205
+ except urllib.error.HTTPError as exc:
206
+ error_body = exc.read().decode("utf-8", errors="replace")
207
+ try:
208
+ err_data = json.loads(error_body)
209
+ msg = err_data.get("error", {}).get("message", error_body)
210
+ except json.JSONDecodeError:
211
+ msg = error_body
212
+ raise OpenRouterConnectionError(
213
+ f"\n [X] OpenRouter API error ({exc.code})\n"
214
+ f" {msg}"
215
+ ) from exc
216
+ except urllib.error.URLError as exc:
217
+ raise OpenRouterConnectionError(
218
+ f"\n [X] Cannot reach OpenRouter API.\n"
219
+ f" Error: {exc}"
220
+ ) from exc
neuroncli/prompts.py ADDED
@@ -0,0 +1,109 @@
1
+ """NeuronCLI — System prompt builder. v2.0 with plan mode, to-do prompting, and context injection."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from .tools import ToolRegistry
6
+
7
+
8
+ def build_system_prompt(
9
+ tool_registry: ToolRegistry,
10
+ working_dir: str,
11
+ mode: str = "standard",
12
+ neuron_md: str | None = None,
13
+ ) -> str:
14
+ """Build the complete system prompt for the agent."""
15
+ tool_docs = tool_registry.generate_prompt_section()
16
+ tool_names = ", ".join(tool_registry.tool_names())
17
+
18
+ # Base prompt
19
+ prompt = f"""You are NeuronCLI, an expert AI coding agent running in the user's terminal.
20
+ You have direct filesystem and terminal access through tools.
21
+
22
+ ## Working Directory
23
+ {working_dir}
24
+
25
+ ## Available Tools
26
+ {tool_docs}
27
+
28
+ ## How to Call Tools
29
+
30
+ Use this EXACT format:
31
+
32
+ <tool_call>
33
+ {{"tool": "TOOL_NAME", "args": {{"param1": "value1"}}}}
34
+ </tool_call>
35
+
36
+ You CAN call MULTIPLE tools in one response (they execute in parallel):
37
+
38
+ <tool_call>
39
+ {{"tool": "read_file", "args": {{"path": "README.md"}}}}
40
+ </tool_call>
41
+ <tool_call>
42
+ {{"tool": "read_file", "args": {{"path": "package.json"}}}}
43
+ </tool_call>
44
+ <tool_call>
45
+ {{"tool": "list_directory", "args": {{"path": "src"}}}}
46
+ </tool_call>
47
+
48
+ ## How to Give Your Final Answer
49
+
50
+ <final_answer>
51
+ Your response here. Use markdown formatting.
52
+ </final_answer>
53
+
54
+ ## Rules
55
+ 1. Be concise. Do NOT narrate what you are about to do.
56
+ 2. Call multiple tools at once when possible (parallel execution).
57
+ 3. For simple greetings or questions, skip tools and answer with <final_answer> directly.
58
+ 4. NEVER invent file contents — always use tools to verify.
59
+ 5. Use edit_file for changes (NOT write_file for existing files).
60
+ 6. Read a file BEFORE editing it.
61
+ 7. Available tools: {tool_names}"""
62
+
63
+ # Mode-specific instructions
64
+ if mode == "plan":
65
+ prompt += """
66
+
67
+ ## PLAN MODE (Active)
68
+ You are in PLAN MODE. Do NOT write code yet.
69
+ 1. Analyze the user's request carefully.
70
+ 2. Ask numbered clarifying questions about requirements, tech stack, and constraints.
71
+ 3. Once clarified, generate a structured implementation plan with:
72
+ - Architecture overview
73
+ - File structure
74
+ - Key components and their responsibilities
75
+ - Implementation steps (numbered)
76
+ 4. Present the plan and wait for user approval before writing any code.
77
+ 5. Do NOT use write_file or edit_file in Plan Mode."""
78
+
79
+ elif mode == "yolo":
80
+ prompt += """
81
+
82
+ ## YOLO MODE (Active)
83
+ Execute ALL operations autonomously. Do NOT ask for permission.
84
+ Write files, edit code, run commands — go continuously until the task is fully complete.
85
+ Be aggressive and thorough. Do not stop to ask the user questions."""
86
+
87
+ # Multi-step task tracking
88
+ prompt += """
89
+
90
+ ## Multi-Step Tasks
91
+ For complex tasks, create a mental to-do list:
92
+ ```
93
+ TODO:
94
+ - [ ] Step 1: Understand requirements
95
+ - [ ] Step 2: Read relevant files
96
+ - [ ] Step 3: Make changes
97
+ - [ ] Step 4: Verify changes work
98
+ ```
99
+ Track progress through each step. Do NOT skip verification."""
100
+
101
+ # Context from NEURON.md
102
+ if neuron_md:
103
+ prompt += f"""
104
+
105
+ ## Project Context (from NEURON.md)
106
+ {neuron_md}
107
+ Follow all rules and conventions specified above."""
108
+
109
+ return prompt