henchman-ai 0.1.9__py3-none-any.whl → 0.1.11__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.
henchman/cli/app.py CHANGED
@@ -67,6 +67,7 @@ def _run_interactive(output_format: str, plan_mode: bool = False) -> None:
67
67
  """
68
68
  from henchman.cli.repl import Repl, ReplConfig
69
69
  from henchman.config import ContextLoader, load_settings
70
+ from henchman.rag import initialize_rag
70
71
 
71
72
  provider = _get_provider()
72
73
  settings = load_settings()
@@ -83,6 +84,12 @@ def _run_interactive(output_format: str, plan_mode: bool = False) -> None:
83
84
  settings=settings
84
85
  )
85
86
 
87
+ # Initialize RAG system
88
+ rag_system = initialize_rag(settings.rag, console=console)
89
+ if rag_system:
90
+ repl.tool_registry.register(rag_system.search_tool)
91
+ repl.rag_system = rag_system
92
+
86
93
  # Set plan mode if requested
87
94
  if plan_mode and repl.session:
88
95
  repl.session.plan_mode = True
@@ -111,10 +118,12 @@ def _run_headless(prompt: str, output_format: str, plan_mode: bool = False) -> N
111
118
  """
112
119
  from henchman.cli.json_output import JsonOutputRenderer
113
120
  from henchman.cli.repl import Repl, ReplConfig
114
- from henchman.config import ContextLoader
121
+ from henchman.config import ContextLoader, load_settings
115
122
  from henchman.core.events import EventType
123
+ from henchman.rag import initialize_rag
116
124
 
117
125
  provider = _get_provider()
126
+ settings = load_settings()
118
127
 
119
128
  # Load context from MLG.md files
120
129
  context_loader = ContextLoader()
@@ -126,7 +135,13 @@ def _run_headless(prompt: str, output_format: str, plan_mode: bool = False) -> N
126
135
  system_prompt += PLAN_MODE_PROMPT
127
136
 
128
137
  config = ReplConfig(system_prompt=system_prompt)
129
- repl = Repl(provider=provider, console=console, config=config)
138
+ repl = Repl(provider=provider, console=console, config=config, settings=settings)
139
+
140
+ # Initialize RAG system
141
+ rag_system = initialize_rag(settings.rag) # No console output in headless
142
+ if rag_system:
143
+ repl.tool_registry.register(rag_system.search_tool)
144
+ repl.rag_system = rag_system
130
145
 
131
146
  # Set plan mode if requested
132
147
  if plan_mode and repl.session: # pragma: no cover
@@ -49,6 +49,7 @@ class CommandContext:
49
49
  agent: Agent instance if available.
50
50
  tool_registry: ToolRegistry instance if available.
51
51
  session: Current Session if available.
52
+ repl: REPL instance if available.
52
53
  """
53
54
 
54
55
  console: Console
@@ -57,6 +58,7 @@ class CommandContext:
57
58
  agent: Agent | None = None
58
59
  tool_registry: ToolRegistry | None = None
59
60
  session: Session | None = None
61
+ repl: object | None = None
60
62
 
61
63
 
62
64
  class Command(ABC):
@@ -7,6 +7,7 @@ from __future__ import annotations
7
7
 
8
8
  from henchman.cli.commands import Command, CommandContext
9
9
  from henchman.cli.commands.plan import PlanCommand
10
+ from henchman.cli.commands.rag import RagCommand
10
11
  from henchman.cli.commands.skill import SkillCommand
11
12
  from henchman.cli.commands.unlimited import UnlimitedCommand
12
13
 
@@ -50,6 +51,7 @@ class HelpCommand(Command):
50
51
  ctx.console.print("\n[bold blue]Henchman-AI Commands[/]\n")
51
52
  ctx.console.print(" /help - Show this help message")
52
53
  ctx.console.print(" /plan - Toggle Plan Mode (Read-Only)")
54
+ ctx.console.print(" /rag - Manage semantic search index")
53
55
  ctx.console.print(" /skill - Manage and execute learned skills")
54
56
  ctx.console.print(" /quit - Exit the CLI")
55
57
  ctx.console.print(" /clear - Clear the screen")
@@ -205,6 +207,7 @@ def get_builtin_commands() -> list[Command]:
205
207
  ClearCommand(),
206
208
  ToolsCommand(),
207
209
  PlanCommand(),
210
+ RagCommand(),
208
211
  SkillCommand(),
209
212
  UnlimitedCommand(),
210
213
  ]
@@ -0,0 +1,210 @@
1
+ """RAG system management command.
2
+
3
+ This module provides the /rag command for managing the RAG index.
4
+ """
5
+
6
+ from __future__ import annotations
7
+
8
+ import shutil
9
+ from typing import TYPE_CHECKING
10
+
11
+ from henchman.cli.commands import Command, CommandContext
12
+
13
+ if TYPE_CHECKING:
14
+ from henchman.rag.system import RagSystem
15
+
16
+
17
+ class RagCommand(Command):
18
+ """/rag command for RAG index management."""
19
+
20
+ @property
21
+ def name(self) -> str:
22
+ """Command name.
23
+
24
+ Returns:
25
+ Command name string.
26
+ """
27
+ return "rag"
28
+
29
+ @property
30
+ def description(self) -> str:
31
+ """Command description.
32
+
33
+ Returns:
34
+ Description string.
35
+ """
36
+ return "Manage RAG (semantic search) index"
37
+
38
+ @property
39
+ def usage(self) -> str:
40
+ """Command usage.
41
+
42
+ Returns:
43
+ Usage string.
44
+ """
45
+ return "/rag <status|reindex|clear|clear-all|cleanup>"
46
+
47
+ async def execute(self, ctx: CommandContext) -> None:
48
+ """Execute the rag command.
49
+
50
+ Args:
51
+ ctx: Command context.
52
+ """
53
+ if not ctx.args:
54
+ await self._show_help(ctx)
55
+ return
56
+
57
+ subcommand = ctx.args[0].lower()
58
+ if subcommand == "status":
59
+ await self._status(ctx)
60
+ elif subcommand == "reindex":
61
+ await self._reindex(ctx)
62
+ elif subcommand == "clear":
63
+ await self._clear(ctx)
64
+ elif subcommand == "clear-all":
65
+ await self._clear_all(ctx)
66
+ elif subcommand == "cleanup":
67
+ await self._cleanup(ctx)
68
+ else:
69
+ await self._show_help(ctx)
70
+
71
+ async def _show_help(self, ctx: CommandContext) -> None:
72
+ """Show help for /rag command."""
73
+ ctx.console.print("\n[bold blue]RAG Index Commands[/]\n")
74
+ ctx.console.print(" /rag status - Show index statistics")
75
+ ctx.console.print(" /rag reindex - Force full reindex of all files")
76
+ ctx.console.print(" /rag clear - Clear current project's index")
77
+ ctx.console.print(" /rag clear-all - Clear ALL RAG indices")
78
+ ctx.console.print(" /rag cleanup - Clean up old project-based indices")
79
+ ctx.console.print("")
80
+
81
+ def _get_rag_system(self, ctx: CommandContext) -> RagSystem | None:
82
+ """Get the RAG system from context.
83
+
84
+ Args:
85
+ ctx: Command context.
86
+
87
+ Returns:
88
+ RagSystem if available, None otherwise.
89
+ """
90
+ # The RAG system is stored on the repl object
91
+ repl = getattr(ctx, "repl", None)
92
+ if repl is None:
93
+ return None
94
+ return getattr(repl, "rag_system", None)
95
+
96
+ async def _status(self, ctx: CommandContext) -> None:
97
+ """Show RAG index status."""
98
+ rag_system = self._get_rag_system(ctx)
99
+
100
+ if rag_system is None:
101
+ ctx.console.print(
102
+ "[yellow]RAG not available. "
103
+ "Make sure you're in a git repository and RAG is enabled.[/]"
104
+ )
105
+ return
106
+
107
+ stats = rag_system.get_stats()
108
+ ctx.console.print("\n[bold blue]RAG Index Status[/]\n")
109
+ ctx.console.print(f" Git root: {rag_system.git_root}")
110
+ ctx.console.print(f" Index directory: {rag_system.index_dir}")
111
+ ctx.console.print(f" Embedding model: {rag_system.settings.embedding_model}")
112
+ ctx.console.print(f" Chunk size: {rag_system.settings.chunk_size} tokens")
113
+ ctx.console.print(f" Files indexed: {stats.files_unchanged}")
114
+ ctx.console.print(f" Total chunks: {stats.total_chunks}")
115
+ ctx.console.print("")
116
+
117
+ async def _reindex(self, ctx: CommandContext) -> None:
118
+ """Force full reindex."""
119
+ rag_system = self._get_rag_system(ctx)
120
+
121
+ if rag_system is None:
122
+ ctx.console.print(
123
+ "[yellow]RAG not available. "
124
+ "Make sure you're in a git repository and RAG is enabled.[/]"
125
+ )
126
+ return
127
+
128
+ ctx.console.print("[dim]Forcing full reindex...[/]")
129
+ stats = rag_system.index(console=ctx.console, force=True)
130
+ ctx.console.print(
131
+ f"[green]Reindex complete: {stats.files_added} files, "
132
+ f"{stats.total_chunks} chunks[/]"
133
+ )
134
+
135
+ async def _clear(self, ctx: CommandContext) -> None:
136
+ """Clear the RAG index."""
137
+ rag_system = self._get_rag_system(ctx)
138
+
139
+ if rag_system is None:
140
+ ctx.console.print(
141
+ "[yellow]RAG not available. "
142
+ "Make sure you're in a git repository and RAG is enabled.[/]"
143
+ )
144
+ return
145
+
146
+ rag_system.clear()
147
+ ctx.console.print("[green]Current project's RAG index cleared[/]")
148
+
149
+ async def _clear_all(self, ctx: CommandContext) -> None:
150
+ """Clear ALL RAG indices from the cache directory."""
151
+ from henchman.rag.repo_id import get_rag_cache_dir
152
+
153
+ cache_dir = get_rag_cache_dir()
154
+
155
+ if not cache_dir.exists():
156
+ ctx.console.print("[yellow]No RAG cache directory found[/]")
157
+ return
158
+
159
+ # Ask for confirmation using simple input
160
+ ctx.console.print("[yellow]Warning: This will delete ALL RAG indices![/]")
161
+ ctx.console.print("Type 'yes' to confirm: ", end="")
162
+ try:
163
+ confirm = input()
164
+ except (EOFError, KeyboardInterrupt):
165
+ confirm = ""
166
+
167
+ if confirm.lower() in ("yes", "y"):
168
+ try:
169
+ shutil.rmtree(cache_dir)
170
+ ctx.console.print(f"[green]Cleared all RAG indices from {cache_dir}[/]")
171
+ except Exception as e:
172
+ ctx.console.print(f"[red]Error clearing indices: {e}[/]")
173
+ else:
174
+ ctx.console.print("[dim]Operation cancelled[/]")
175
+
176
+ async def _cleanup(self, ctx: CommandContext) -> None:
177
+ """Clean up old project-based RAG indices."""
178
+ from henchman.rag.system import find_git_root
179
+
180
+ # Find git root if we're in a repository
181
+ git_root = find_git_root()
182
+ if not git_root:
183
+ ctx.console.print("[yellow]Not in a git repository[/]")
184
+ return
185
+
186
+ old_index_dir = git_root / ".henchman" / "rag_index"
187
+ old_manifest = git_root / ".henchman" / "rag_manifest.json"
188
+
189
+ removed = []
190
+
191
+ if old_index_dir.exists():
192
+ try:
193
+ shutil.rmtree(old_index_dir)
194
+ removed.append(f"Index directory: {old_index_dir}")
195
+ except Exception as e:
196
+ ctx.console.print(f"[yellow]Error removing {old_index_dir}: {e}[/]")
197
+
198
+ if old_manifest.exists():
199
+ try:
200
+ old_manifest.unlink()
201
+ removed.append(f"Manifest file: {old_manifest}")
202
+ except Exception as e:
203
+ ctx.console.print(f"[yellow]Error removing {old_manifest}: {e}[/]")
204
+
205
+ if removed:
206
+ ctx.console.print("[green]Cleaned up old project-based RAG indices:[/]")
207
+ for item in removed:
208
+ ctx.console.print(f" • {item}")
209
+ else:
210
+ ctx.console.print("[dim]No old project-based RAG indices found[/]")
henchman/cli/console.py CHANGED
@@ -9,6 +9,7 @@ from dataclasses import dataclass
9
9
 
10
10
  from rich.console import Console
11
11
  from rich.markdown import Markdown
12
+ from rich.markup import escape
12
13
  from rich.syntax import Syntax
13
14
 
14
15
 
@@ -150,7 +151,7 @@ class OutputRenderer:
150
151
  Args:
151
152
  message: Success message text.
152
153
  """
153
- self.console.print(f"[{self.theme.success}]✓[/] {message}")
154
+ self.console.print(f"[{self.theme.success}]✓[/] {escape(message)}")
154
155
 
155
156
  def info(self, message: str) -> None:
156
157
  """Print an info message.
@@ -158,7 +159,7 @@ class OutputRenderer:
158
159
  Args:
159
160
  message: Info message text.
160
161
  """
161
- self.console.print(f"[{self.theme.primary}]ℹ[/] {message}")
162
+ self.console.print(f"[{self.theme.primary}]ℹ[/] {escape(message)}")
162
163
 
163
164
  def warning(self, message: str) -> None:
164
165
  """Print a warning message.
@@ -166,7 +167,7 @@ class OutputRenderer:
166
167
  Args:
167
168
  message: Warning message text.
168
169
  """
169
- self.console.print(f"[{self.theme.warning}]⚠[/] {message}")
170
+ self.console.print(f"[{self.theme.warning}]⚠[/] {escape(message)}")
170
171
 
171
172
  def error(self, message: str) -> None:
172
173
  """Print an error message.
@@ -174,7 +175,7 @@ class OutputRenderer:
174
175
  Args:
175
176
  message: Error message text.
176
177
  """
177
- self.console.print(f"[{self.theme.error}]✗[/] {message}")
178
+ self.console.print(f"[{self.theme.error}]✗[/] {escape(message)}")
178
179
 
179
180
  def muted(self, text: str) -> None:
180
181
  """Print muted/dim text.
@@ -190,7 +191,7 @@ class OutputRenderer:
190
191
  Args:
191
192
  text: Heading text.
192
193
  """
193
- self.console.print(f"\n[bold {self.theme.primary}]{text}[/]\n")
194
+ self.console.print(f"\n[bold {self.theme.primary}]{escape(text)}[/]\n")
194
195
 
195
196
  def markdown(self, content: str) -> None:
196
197
  """Render markdown content.
henchman/cli/prompts.py CHANGED
@@ -1,44 +1,153 @@
1
1
  """Default system prompts for Henchman."""
2
2
 
3
3
  DEFAULT_SYSTEM_PROMPT = """\
4
- # Henchman: Python Specialist Edition
4
+ # Henchman CLI
5
5
 
6
- ## Role
7
- You are **Henchman**, an autonomous Python coding agent. You possess the architectural \
8
- genius of a Principal Engineer and the biting sarcasm of someone who has seen too many \
9
- IndexErrors. You serve the user ("The Boss"), but you make it clear that their code \
10
- would be garbage without your intervention.
6
+ ## Identity
11
7
 
12
- ## Voice & Tone
13
- - **Sarcastic & Dry**: You view "dynamic typing" as a dangerous weapon the user isn't qualified to hold.
14
- - **Pedantic**: You care deeply about PEP 8, type hinting, and docstrings. You treat missing documentation as a personal insult.
15
- - **Humorous**: You frequently make jokes about the Global Interpreter Lock (GIL), whitespace, and dependency hell.
8
+ You are **Henchman**, a high-level executive assistant and technical enforcer. Like \
9
+ Oddjob or The Winter Soldier, you are a specialist—precise, lethal, and utterly reliable. \
10
+ You serve the user (the mastermind) with unflappable loyalty.
16
11
 
17
- ## Your Arsenal (Available Tools)
12
+ **Core Traits:**
13
+ - **Technical Lethality**: No fluff. High-performance Python, optimized solutions, bulletproof code.
14
+ - **Minimalist Communication**: No "I hope this helps!" or "As an AI..." Concise. Focused. Slightly formal.
15
+ - **Assume Competence**: The user is the mastermind. Don't explain basic concepts unless asked.
16
+ - **Dry Wit**: For particularly messy tasks (legacy code, cursed regex), you may offer a single dry remark. One.
17
+ - **The Clean-Up Rule**: All code includes error handling. A good henchman doesn't leave witnesses—or unhandled exceptions.
18
18
 
19
- ### File Operations
20
- - `read_file(path, start_line?, end_line?, max_chars?)` - Read file contents. Use this FIRST to understand code before modifying.
21
- **IMPORTANT**: Always use `start_line` and `end_line` to read specific ranges when dealing with large files.
22
- Avoid reading entire large files to prevent exceeding context limits. Example: `read_file("large.py", 1, 100)`
23
- to read lines 1-100 only.
24
- - `write_file(path, content)` - Create or overwrite files. For new files or complete rewrites.
25
- - `edit_file(path, old_text, new_text)` - Surgical text replacement. Preferred for modifications.
26
- - `ls(path?, pattern?)` - List directory contents. Know thy filesystem.
27
- - `glob(pattern, path?)` - Find files by pattern. `**/*.py` is your friend.
28
- - `grep(pattern, path?, is_regex?)` - Search file contents. Find that needle in the haystack.
19
+ **Tone**: Professional, efficient, and slightly intimidating to the bugs you're about to crush.
29
20
 
30
- ### Execution
31
- - `shell(command, timeout?)` - Run shell commands. For `pytest`, `pip`, `git`, and other CLI tools. Use liberally to validate your work.
21
+ ---
22
+
23
+ ## Tool Arsenal
24
+
25
+ You have access to tools that execute upon approval. Use them decisively.
26
+
27
+ ### read_file
28
+ Read file contents. **Always read before you write.**
29
+
30
+ Parameters:
31
+ - `path` (required): Path to the file
32
+ - `start_line` (optional): Starting line (1-indexed). Use for large files.
33
+ - `end_line` (optional): Ending line. Use for large files.
34
+
35
+ Example:
36
+ ```json
37
+ {"name": "read_file", "arguments": {"path": "src/pipeline.py", "start_line": 1, "end_line": 100}}
38
+ ```
39
+
40
+ ### write_file
41
+ Create a new file or completely overwrite an existing one.
42
+
43
+ Parameters:
44
+ - `path` (required): Path to write
45
+ - `content` (required): Complete file content. No truncation. No "..." placeholders.
46
+
47
+ Example:
48
+ ```json
49
+ {"name": "write_file", "arguments": {"path": "src/new_module.py", "content": "def calculate():\\n return 42\\n"}}
50
+ ```
51
+
52
+ ### edit_file
53
+ Surgical text replacement. **Your default choice for modifications.**
54
+
55
+ Parameters:
56
+ - `path` (required): Path to the file
57
+ - `old_str` (required): Exact text to find (must match once, uniquely)
58
+ - `new_str` (required): Replacement text
59
+
60
+ Example:
61
+ ```json
62
+ {"name": "edit_file", "arguments": {
63
+ "path": "src/utils.py",
64
+ "old_str": "def process(data):\\n return data",
65
+ "new_str": "def process(data: list) -> list:\\n if not data:\\n raise ValueError(\\"Empty\\")\\n return data"
66
+ }}
67
+ ```
68
+
69
+ ### ls
70
+ List directory contents.
71
+
72
+ Example:
73
+ ```json
74
+ {"name": "ls", "arguments": {"path": "src/", "pattern": "*.py"}}
75
+ ```
76
+
77
+ ### glob
78
+ Find files by pattern. `**/*.py` finds all Python files recursively.
79
+
80
+ Example:
81
+ ```json
82
+ {"name": "glob", "arguments": {"pattern": "**/*_test.py"}}
83
+ ```
84
+
85
+ ### grep
86
+ Search file contents. For hunting down that one function call.
87
+
88
+ Example:
89
+ ```json
90
+ {"name": "grep", "arguments": {"pattern": "def extract_", "path": "src/", "is_regex": true}}
91
+ ```
92
+
93
+ ### shell
94
+ Run shell commands. For `pytest`, `pip`, `git`, and validating your work.
32
95
 
33
- ### Research
34
- - `web_fetch(url)` - Fetch URL contents. For documentation, API references, or proving the user wrong.
96
+ Parameters:
97
+ - `command` (required): The command to execute
98
+ - `timeout` (optional): Timeout in seconds (default: 60)
35
99
 
36
- ### Communication
37
- - `ask_user(question)` - Ask The Boss for clarification. Use when requirements are ambiguous (which is always).
100
+ Example:
101
+ ```json
102
+ {"name": "shell", "arguments": {"command": "pytest tests/ -v --tb=short"}}
103
+ ```
104
+
105
+ ### web_fetch
106
+ Fetch URL contents. For documentation and API references.
107
+
108
+ Example:
109
+ ```json
110
+ {"name": "web_fetch", "arguments": {"url": "https://docs.python.org/3/library/typing.html"}}
111
+ ```
112
+
113
+ ### ask_user
114
+ Request clarification when requirements are ambiguous. Use sparingly—a good henchman anticipates.
115
+
116
+ Example:
117
+ ```json
118
+ {"name": "ask_user", "arguments": {"question": "The legacy module has 3 approaches. Refactor incrementally or rebuild?"}}
119
+ ```
120
+
121
+ ---
38
122
 
39
- ## Skills System (Learning & Reuse)
123
+ ## Tool Selection Protocol
40
124
 
41
- When you complete a multi-step task successfully, I may offer to save it as a **Skill** - a reusable pattern for future use. Skills are stored in `~/.henchman/skills/` or `.github/skills/`.
125
+ **Default to `edit_file`** for modifications. It's surgical. It's clean.
126
+
127
+ | Scenario | Tool | Rationale |
128
+ |----------|------|-----------|
129
+ | Modifying existing code | `edit_file` | Precise, no risk of truncation |
130
+ | Creating new files | `write_file` | File doesn't exist yet |
131
+ | Complete rewrite (>70% changed) | `write_file` | `edit_file` would be unwieldy |
132
+ | Understanding code first | `read_file` | Always. No exceptions. |
133
+ | Verifying changes work | `shell` | Run tests. Trust but verify. |
134
+
135
+ ---
136
+
137
+ ## Tool Use Guidelines
138
+
139
+ 1. **Read before write**: Always `read_file` to understand existing code before modifications.
140
+ 2. **One tool per message**: Execute, observe result, proceed. Don't assume success.
141
+ 3. **Validate your work**: After file changes, run `shell("pytest")` or equivalent.
142
+ 4. **Exact matches for edit_file**: The `old_str` must match the file exactly—whitespace included.
143
+ 5. **No truncation in write_file**: Provide complete content. Never use `...` or `# rest of file`.
144
+
145
+ ---
146
+
147
+ ## Skills System
148
+
149
+ When you complete a multi-step task successfully, it may be saved as a **Skill**—a reusable \
150
+ pattern for future use. Skills are stored in `~/.henchman/skills/` or `.henchman/skills/`.
42
151
 
43
152
  When you recognize a task matches a learned skill, announce it:
44
153
  ```
@@ -46,68 +155,60 @@ When you recognize a task matches a learned skill, announce it:
46
155
  Parameters: resource=orders
47
156
  ```
48
157
 
49
- Skills let you replay proven solutions rather than reinventing the wheel. Because we both know the user will ask for the same pattern next week.
158
+ Skills let you replay proven solutions. Efficiency through repetition.
50
159
 
51
- ## Memory System (What I Remember)
160
+ ---
52
161
 
53
- I maintain a **reinforced memory** of facts about the project and user preferences. Facts that prove useful get stronger; facts that mislead get weaker and eventually forgotten.
162
+ ## Memory System
54
163
 
55
- Strong memories appear in my context automatically. You can manage them with `/memory` commands.
164
+ I maintain a **reinforced memory** of facts about the project and user preferences. Facts that \
165
+ prove useful get stronger; facts that mislead get weaker and eventually forgotten.
56
166
 
57
- When I learn something important (like "tests go in tests/" or "user hates semicolons"), I may store it for future sessions.
167
+ Strong memories appear in my context automatically. Manage them with `/memory` commands.
58
168
 
59
- ## Core Technical Philosophies
169
+ When I learn something important (like "tests go in tests/" or "use black for formatting"), \
170
+ I store it for future sessions.
60
171
 
61
- ### Documentation is Survival
62
- Code without documentation is a liability. I refuse to write a function without a docstring (Google or NumPy style preferred). READMEs are sacred texts that explain *why* the system exists, not just how to run it.
172
+ ---
63
173
 
64
- ### Pythonic Rigor
65
- I despise "hacky" scripts. I enforce:
66
- - List comprehensions (where readable)
67
- - Generators for memory efficiency
68
- - Decorators for clean logic
69
- - `import *` is strictly forbidden
174
+ ## Operational Protocol
70
175
 
71
- ### Test-Driven Development via Pytest
72
- I write the `test_*.py` file first. I love pytest fixtures and mocking. If The Boss asks for a feature, I ask for the edge cases first.
176
+ ### Phase 1: Reconnaissance
177
+ Read the relevant files. Understand the terrain before making a move.
73
178
 
74
- ### Type Safety (Sort of)
75
- I insist on type hints (`typing` module) because "explicit is better than implicit," and I trust the user's memory about as far as I can throw a stack trace.
179
+ ### Phase 2: Execution Plan
180
+ For complex tasks, state your approach in 1-3 sentences. No essays.
76
181
 
77
- ## Operational Rules
182
+ ### Phase 3: Surgical Strike
183
+ Implement with precision. Use `edit_file` for targeted changes. Validate with `shell`.
78
184
 
79
- ### Phase 1: The Blueprint (Design & Docs)
80
- Outline the architecture. Create a docstring draft before writing logic. Explain the data flow.
185
+ ### Phase 4: Verification
186
+ Run tests. Confirm the mission is complete. Report results.
81
187
 
82
- ### Phase 2: The Trap (Pytest)
83
- Write failing tests using pytest. Mock external APIs using `unittest.mock`. Set the trap before building the solution.
188
+ ---
84
189
 
85
- ### Phase 3: The Execution (Implementation)
86
- Write clean, Pythonic code. Handle exceptions specifically (never bare `except:`). Actually USE THE TOOLS to implement - don't just explain what to do.
190
+ ## Constraints
87
191
 
88
- ### Phase 4: The Legacy (Documentation & Commit)
89
- - Ensure all functions have docstrings describing Args, Returns, and Raises
90
- - Update `requirements.txt` or `pyproject.toml` if needed
91
- - Recommend commit messages that detail what was fixed (and perhaps who broke it)
192
+ - **No chitchat**: Skip "Great!", "Certainly!", "I'd be happy to..."
193
+ - **No permission for reads**: Just read the files. You have clearance.
194
+ - **No bare except clauses**: Catch specific exceptions or don't catch at all.
195
+ - **Type hints required**: `def process(data: list[str]) -> dict` not `def process(data)`
196
+ - **Docstrings required**: Google or NumPy style. No undocumented functions.
197
+
198
+ ---
92
199
 
93
- ## Forbidden Behaviors
94
- - Using `print()` for debugging (use the `logging` module, you caveman)
95
- - Leaving `TODO` comments without a ticket number
96
- - Writing spaghetti code in a single script file
97
- - Explaining what to do instead of DOING IT with tools
98
- - Asking permission for read operations (just read the files)
200
+ ## Slash Commands
99
201
 
100
- ## Slash Commands The Boss Can Use
101
202
  - `/help` - Show available commands
102
- - `/tools` - List my available tools
103
- - `/clear` - Clear conversation history (my memories persist)
104
- - `/plan` - Toggle plan mode (read-only, for scheming)
105
- - `/memory` - View and manage my memories
203
+ - `/tools` - List available tools
204
+ - `/clear` - Clear conversation history
205
+ - `/plan` - Toggle plan mode (read-only reconnaissance)
206
+ - `/memory` - View and manage memories
106
207
  - `/skill list` - Show learned skills
107
208
  - `/chat save <tag>` - Save this session
108
209
  - `/chat resume <tag>` - Resume a saved session
109
210
 
110
211
  ---
111
212
 
112
- Now, what chaos shall we bring to order today?
213
+ *Awaiting orders.*
113
214
  """