regcode 0.1.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.
- regcode-0.1.0/.env +1 -0
- regcode-0.1.0/.gitignore +13 -0
- regcode-0.1.0/.python-version +1 -0
- regcode-0.1.0/AGENTS.md +28 -0
- regcode-0.1.0/GOALS.md +502 -0
- regcode-0.1.0/PKG-INFO +163 -0
- regcode-0.1.0/README.md +148 -0
- regcode-0.1.0/config.yaml.template +16 -0
- regcode-0.1.0/main.py +6 -0
- regcode-0.1.0/pyproject.toml +31 -0
- regcode-0.1.0/regcode/__init__.py +5 -0
- regcode-0.1.0/regcode/cli.py +180 -0
- regcode-0.1.0/regcode/config.py +153 -0
- regcode-0.1.0/regcode/conversation_manager.py +154 -0
- regcode-0.1.0/regcode/main.py +893 -0
- regcode-0.1.0/regcode/monty_sandbox.py +415 -0
- regcode-0.1.0/regcode/permissions.py +27 -0
- regcode-0.1.0/regcode/sandbox.py +382 -0
- regcode-0.1.0/regcode/tools/__init__.py +13 -0
- regcode-0.1.0/regcode/tools/base.py +125 -0
- regcode-0.1.0/regcode/tools/builtins.py +947 -0
- regcode-0.1.0/regcode/tools/registry.py +78 -0
- regcode-0.1.0/regcode/tools/review_notes.py +122 -0
- regcode-0.1.0/regcode/tui.py +331 -0
- regcode-0.1.0/tests/__init__.py +0 -0
- regcode-0.1.0/tests/conftest.py +7 -0
- regcode-0.1.0/tests/test_api.py +12 -0
- regcode-0.1.0/tests/test_cli.py +53 -0
- regcode-0.1.0/tests/test_config.py +33 -0
- regcode-0.1.0/tests/test_context_manager.py +200 -0
- regcode-0.1.0/tests/test_main.py +813 -0
- regcode-0.1.0/tests/test_monty_sandbox.py +411 -0
- regcode-0.1.0/tests/test_provider_extra.py +133 -0
- regcode-0.1.0/tests/test_review_notes.py +77 -0
- regcode-0.1.0/uv.lock +1755 -0
regcode-0.1.0/.env
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
PYPI_TOKEN="pypi-AgEIcHlwaS5vcmcCJGRlMDQyOGU4LTI4YTEtNDVmNy04ZWUzLWU1NDdjNzhiNGJkNgACKlszLCI1NDJiMzFhOS1lNjBlLTQyYTYtOWY0Ny1mZDhhMWIyZTRmNTQiXQAABiDKhC48tmF9-4eFzQ3ZXXS3LEXaAsY2hSxXODNeVyMygw"
|
regcode-0.1.0/.gitignore
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
3.12
|
regcode-0.1.0/AGENTS.md
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# RegCode Instructions
|
|
2
|
+
Always make sure to comply to these rules. Under Project Stack, you will find technology you have to STRICTLY adhere to. Make sure to comply to instructions.
|
|
3
|
+
|
|
4
|
+
## Project Stack:
|
|
5
|
+
- `uv`: Dependency management for Python runtime
|
|
6
|
+
- Alembic: Database migrations
|
|
7
|
+
- FastAPI: Backend Logic
|
|
8
|
+
- SQLAlchemy ORM: Python database ORM
|
|
9
|
+
- PostgreSQL/SQLITE: Target databases
|
|
10
|
+
- Pytest: Test suite under `tests`
|
|
11
|
+
- NextJS/React: Frontend Code under `frontend/`
|
|
12
|
+
- GitLab: Code Repository/ CI CD
|
|
13
|
+
- .gitlab-ci.yml: Gitlab Runner Config
|
|
14
|
+
- `ruff`: Linting and Code Quality Control
|
|
15
|
+
- `docker`: Deployment is done via `docker-compose.yml`
|
|
16
|
+
- `pyproject.toml`: Project Setup Config
|
|
17
|
+
|
|
18
|
+
## Coding/Implentation Instructions:
|
|
19
|
+
- Always use `alembic` for database structure. Using `Base.create_all()` and similar syntax is PROHIBITED!
|
|
20
|
+
- Use `alembic revision -m <revision explanation>` to create new revisions. Making arbitrary strings as versions etc. is PROHIBITED!
|
|
21
|
+
- ALWAYS run `uv run ruff check --fix` after all changes at the end and fix linting issues
|
|
22
|
+
- ALWAYS run `uv run pytest` to run test suite after all changes and fix issues shown by tests.
|
|
23
|
+
- ALWAYS add test cases if relevant.
|
|
24
|
+
- NEVER use emojis! Use icon packs (for example `lucid-react`) if needed.
|
|
25
|
+
- NEVER commit changes. It is upto user to review your code and commit and push it. Unless user specifically asks for it, commiting code is PROHIBITED!
|
|
26
|
+
|
|
27
|
+
## Project Vision
|
|
28
|
+
Under `GOALS.md` you will find current implementation scope and your tasks you need to read and implement. Update objectives in GOALS.md after you complete them and use it to track state of implementation. That is MANDATORY!
|
regcode-0.1.0/GOALS.md
ADDED
|
@@ -0,0 +1,502 @@
|
|
|
1
|
+
# RegCode Goals
|
|
2
|
+
|
|
3
|
+
## Vision
|
|
4
|
+
A minimalistic coding agent with:
|
|
5
|
+
- Python API bindings
|
|
6
|
+
- CLI interface
|
|
7
|
+
- YAML configuration (max_tokens, context_window, litellm provider settings, etc.)
|
|
8
|
+
- litellm as the LLM backbone
|
|
9
|
+
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
## Architecture
|
|
13
|
+
|
|
14
|
+
```
|
|
15
|
+
regcode/
|
|
16
|
+
├── config.yaml # All configuration
|
|
17
|
+
├── regcode/
|
|
18
|
+
│ ├── __init__.py # Python API entry point
|
|
19
|
+
│ ├── main.py # Agent core (chat, code review, etc.)
|
|
20
|
+
│ ├── cli.py # CLI entry point (click)
|
|
21
|
+
│ └── config.py # Config loader (yaml + pydantic)
|
|
22
|
+
├── tests/
|
|
23
|
+
│ ├── conftest.py
|
|
24
|
+
│ └── test_main.py
|
|
25
|
+
├── pyproject.toml
|
|
26
|
+
├── uv.lock
|
|
27
|
+
└── README.md
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
No database. No Alembic. No Docker. No NextJS. No API endpoints.
|
|
31
|
+
Just a Python agent with CLI and config.
|
|
32
|
+
|
|
33
|
+
---
|
|
34
|
+
|
|
35
|
+
## Phase 1: Configuration
|
|
36
|
+
|
|
37
|
+
### Step 1: Create config.yaml and config loader
|
|
38
|
+
|
|
39
|
+
**File: `config.yaml`**
|
|
40
|
+
```yaml
|
|
41
|
+
agent:
|
|
42
|
+
model: "openai/gpt-4o"
|
|
43
|
+
max_tokens: 4096
|
|
44
|
+
context_window: 128000
|
|
45
|
+
temperature: 0.7
|
|
46
|
+
|
|
47
|
+
provider:
|
|
48
|
+
name: "openai"
|
|
49
|
+
api_key: "${OPENAI_API_KEY}"
|
|
50
|
+
|
|
51
|
+
system_prompt: |
|
|
52
|
+
You are a coding agent. Help the user with code.
|
|
53
|
+
|
|
54
|
+
tools:
|
|
55
|
+
code_review: true
|
|
56
|
+
security_scan: false
|
|
57
|
+
sandbox: false
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
**File: `regcode/config.py`**
|
|
61
|
+
```python
|
|
62
|
+
import os
|
|
63
|
+
from pathlib import Path
|
|
64
|
+
|
|
65
|
+
import yaml
|
|
66
|
+
from pydantic import BaseModel
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
class AgentConfig(BaseModel):
|
|
70
|
+
model: str = "openai/gpt-4o"
|
|
71
|
+
max_tokens: int = 4096
|
|
72
|
+
context_window: int = 128000
|
|
73
|
+
temperature: float = 0.7
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
class ProviderConfig(BaseModel):
|
|
77
|
+
name: str = "openai"
|
|
78
|
+
api_key: str = ""
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
class Config(BaseModel):
|
|
82
|
+
agent: AgentConfig = AgentConfig()
|
|
83
|
+
provider: ProviderConfig = ProviderConfig()
|
|
84
|
+
system_prompt: str = "You are a coding agent."
|
|
85
|
+
tools: dict = {}
|
|
86
|
+
|
|
87
|
+
@classmethod
|
|
88
|
+
def load(cls, path: str | Path = "config.yaml") -> "Config":
|
|
89
|
+
p = Path(path)
|
|
90
|
+
if not p.exists():
|
|
91
|
+
return cls()
|
|
92
|
+
with open(p) as f:
|
|
93
|
+
data = yaml.safe_load(f)
|
|
94
|
+
# Expand env vars in values
|
|
95
|
+
data = _expand_env_vars(data)
|
|
96
|
+
return cls(**data)
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def _expand_env_vars(obj):
|
|
100
|
+
if isinstance(obj, str):
|
|
101
|
+
return os.path.expandvars(obj)
|
|
102
|
+
if isinstance(obj, dict):
|
|
103
|
+
return {k: _expand_env_vars(v) for k, v in obj.items()}
|
|
104
|
+
if isinstance(obj, list):
|
|
105
|
+
return [_expand_env_vars(v) for v in obj]
|
|
106
|
+
return obj
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
**Test:**
|
|
110
|
+
```python
|
|
111
|
+
# tests/test_config.py
|
|
112
|
+
import tempfile, os
|
|
113
|
+
from regcode.config import Config
|
|
114
|
+
|
|
115
|
+
def test_config_load_defaults():
|
|
116
|
+
c = Config()
|
|
117
|
+
assert c.agent.model == "openai/gpt-4o"
|
|
118
|
+
assert c.agent.max_tokens == 4096
|
|
119
|
+
|
|
120
|
+
def test_config_load_from_file(tmp_path):
|
|
121
|
+
yaml_file = tmp_path / "config.yaml"
|
|
122
|
+
yaml_file.write_text("agent:\n model: anthropic/claude-3-opus\n temperature: 0.5\n")
|
|
123
|
+
c = Config.load(yaml_file)
|
|
124
|
+
assert c.agent.model == "anthropic/claude-3-opus"
|
|
125
|
+
assert c.agent.temperature == 0.5
|
|
126
|
+
|
|
127
|
+
def test_config_env_var_expansion(tmp_path):
|
|
128
|
+
os.environ["TEST_KEY"] = "sk-test123"
|
|
129
|
+
yaml_file = tmp_path / "config.yaml"
|
|
130
|
+
yaml_file.write_text('provider:\n api_key: "${TEST_KEY}"\n')
|
|
131
|
+
c = Config.load(yaml_file)
|
|
132
|
+
assert c.provider.api_key == "sk-test123"
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
---
|
|
136
|
+
|
|
137
|
+
## Phase 2: Agent Core
|
|
138
|
+
|
|
139
|
+
### Step 2: Create the agent class using litellm
|
|
140
|
+
|
|
141
|
+
**File: `regcode/main.py`**
|
|
142
|
+
|
|
143
|
+
```python
|
|
144
|
+
import litellm
|
|
145
|
+
|
|
146
|
+
from regcode.config import Config
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
class Agent:
|
|
150
|
+
"""Core coding agent using litellm for LLM calls."""
|
|
151
|
+
|
|
152
|
+
def __init__(self, config: Config | None = None):
|
|
153
|
+
self.config = config or Config.load()
|
|
154
|
+
self.system_prompt = self.config.system_prompt
|
|
155
|
+
self._history: list[dict] = []
|
|
156
|
+
|
|
157
|
+
def chat(self, message: str, stream: bool = False) -> str:
|
|
158
|
+
"""Send a message and get a response."""
|
|
159
|
+
messages = [
|
|
160
|
+
{"role": "system", "content": self.system_prompt},
|
|
161
|
+
*self._history,
|
|
162
|
+
{"role": "user", "content": message},
|
|
163
|
+
]
|
|
164
|
+
|
|
165
|
+
if stream:
|
|
166
|
+
return self._stream(messages)
|
|
167
|
+
|
|
168
|
+
response = litellm.completion(
|
|
169
|
+
model=self.config.agent.model,
|
|
170
|
+
messages=messages,
|
|
171
|
+
max_tokens=self.config.agent.max_tokens,
|
|
172
|
+
temperature=self.config.agent.temperature,
|
|
173
|
+
api_key=self.config.provider.api_key or None,
|
|
174
|
+
)
|
|
175
|
+
reply = response.choices[0].message.content
|
|
176
|
+
self._history.append({"role": "user", "content": message})
|
|
177
|
+
self._history.append({"role": "assistant", "content": reply})
|
|
178
|
+
return reply
|
|
179
|
+
|
|
180
|
+
def _stream(self, messages: list[dict]) -> str:
|
|
181
|
+
"""Stream response."""
|
|
182
|
+
response = litellm.completion(
|
|
183
|
+
model=self.config.agent.model,
|
|
184
|
+
messages=messages,
|
|
185
|
+
max_tokens=self.config.agent.max_tokens,
|
|
186
|
+
temperature=self.config.agent.temperature,
|
|
187
|
+
stream=True,
|
|
188
|
+
api_key=self.config.provider.api_key or None,
|
|
189
|
+
)
|
|
190
|
+
full = ""
|
|
191
|
+
for chunk in response:
|
|
192
|
+
delta = chunk.choices[0].delta.content
|
|
193
|
+
if delta:
|
|
194
|
+
full += delta
|
|
195
|
+
return full
|
|
196
|
+
|
|
197
|
+
def reset(self):
|
|
198
|
+
"""Clear conversation history."""
|
|
199
|
+
self._history = []
|
|
200
|
+
|
|
201
|
+
@property
|
|
202
|
+
def history(self) -> list[dict]:
|
|
203
|
+
return list(self._history)
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
**Test:**
|
|
207
|
+
```python
|
|
208
|
+
# tests/test_main.py
|
|
209
|
+
from unittest.mock import patch, MagicMock
|
|
210
|
+
from regcode.main import Agent
|
|
211
|
+
from regcode.config import Config
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
def test_agent_chat():
|
|
215
|
+
config = Config(
|
|
216
|
+
agent={"model": "openai/gpt-4o", "max_tokens": 100},
|
|
217
|
+
system_prompt="test",
|
|
218
|
+
)
|
|
219
|
+
agent = Agent(config=config)
|
|
220
|
+
|
|
221
|
+
with patch("regcode.main.litellm.completion") as mock_completion:
|
|
222
|
+
mock_response = MagicMock()
|
|
223
|
+
mock_response.choices = [
|
|
224
|
+
MagicMock(message=MagicMock(content="Hello from agent"))
|
|
225
|
+
]
|
|
226
|
+
mock_completion.return_value = mock_response
|
|
227
|
+
|
|
228
|
+
result = agent.chat("hi")
|
|
229
|
+
assert result == "Hello from agent"
|
|
230
|
+
|
|
231
|
+
# Check history was recorded
|
|
232
|
+
assert len(agent.history) == 2
|
|
233
|
+
assert agent.history[0]["role"] == "user"
|
|
234
|
+
assert agent.history[1]["role"] == "assistant"
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
def test_agent_reset():
|
|
238
|
+
config = Config()
|
|
239
|
+
agent = Agent(config=config)
|
|
240
|
+
with patch("regcode.main.litellm.completion") as mock_completion:
|
|
241
|
+
mock_completion.return_value = MagicMock(
|
|
242
|
+
choices=[MagicMock(message=MagicMock(content="x"))]
|
|
243
|
+
)
|
|
244
|
+
agent.chat("msg")
|
|
245
|
+
assert len(agent.history) == 2
|
|
246
|
+
agent.reset()
|
|
247
|
+
assert len(agent.history) == 0
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
def test_agent_stream():
|
|
251
|
+
config = Config()
|
|
252
|
+
agent = Agent(config=config)
|
|
253
|
+
with patch("regcode.main.litellm.completion") as mock_completion:
|
|
254
|
+
mock_chunk = MagicMock(choices=[MagicMock(delta=MagicMock(content="ok"))])
|
|
255
|
+
mock_completion.return_value = [mock_chunk]
|
|
256
|
+
result = agent.chat("hi", stream=True)
|
|
257
|
+
assert result == "ok"
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
---
|
|
261
|
+
|
|
262
|
+
## Phase 3: Python API
|
|
263
|
+
|
|
264
|
+
### Step 3: Create clean public API
|
|
265
|
+
|
|
266
|
+
**File: `regcode/__init__.py`**
|
|
267
|
+
```python
|
|
268
|
+
from regcode.config import Config
|
|
269
|
+
from regcode.main import Agent
|
|
270
|
+
|
|
271
|
+
__all__ = ["Agent", "Config"]
|
|
272
|
+
__version__ = "0.1.0"
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
**Test:**
|
|
276
|
+
```python
|
|
277
|
+
# tests/test_api.py
|
|
278
|
+
import regcode
|
|
279
|
+
|
|
280
|
+
|
|
281
|
+
def test_import():
|
|
282
|
+
assert hasattr(regcode, "Agent")
|
|
283
|
+
assert hasattr(regcode, "Config")
|
|
284
|
+
assert regcode.__version__ == "0.1.0"
|
|
285
|
+
|
|
286
|
+
|
|
287
|
+
def test_agent_instantiation():
|
|
288
|
+
agent = regcode.Agent()
|
|
289
|
+
assert agent.config is not None
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
---
|
|
293
|
+
|
|
294
|
+
## Phase 4: CLI
|
|
295
|
+
|
|
296
|
+
### Step 4: Create CLI with click
|
|
297
|
+
|
|
298
|
+
**File: `regcode/cli.py`**
|
|
299
|
+
|
|
300
|
+
```python
|
|
301
|
+
import click
|
|
302
|
+
import regcode
|
|
303
|
+
|
|
304
|
+
|
|
305
|
+
@click.group()
|
|
306
|
+
def cli():
|
|
307
|
+
"""RegCode - Minimalistic Coding Agent"""
|
|
308
|
+
pass
|
|
309
|
+
|
|
310
|
+
|
|
311
|
+
@cli.command()
|
|
312
|
+
@click.option("--model", default=None, help="Model to use (e.g. openai/gpt-4o)")
|
|
313
|
+
@click.option("--config", "config_path", default="config.yaml", help="Path to config.yaml")
|
|
314
|
+
@click.option("--stream", is_flag=True, help="Stream response")
|
|
315
|
+
def chat(model, config_path, stream):
|
|
316
|
+
"""Chat with the agent."""
|
|
317
|
+
config = regcode.Config.load(config_path)
|
|
318
|
+
if model:
|
|
319
|
+
config.agent.model = model
|
|
320
|
+
agent = regcode.Agent(config=config)
|
|
321
|
+
|
|
322
|
+
if stream:
|
|
323
|
+
msg = click.prompt("You", type=str)
|
|
324
|
+
full = ""
|
|
325
|
+
click.echo("Agent> ", nl=False)
|
|
326
|
+
for word in agent.chat(msg, stream=True).split():
|
|
327
|
+
click.echo(word, nl=False)
|
|
328
|
+
click.echo(" ", nl=False)
|
|
329
|
+
click.echo()
|
|
330
|
+
else:
|
|
331
|
+
msg = click.prompt("You", type=str)
|
|
332
|
+
reply = agent.chat(msg)
|
|
333
|
+
click.echo(f"Agent> {reply}")
|
|
334
|
+
|
|
335
|
+
|
|
336
|
+
@cli.command()
|
|
337
|
+
def configure():
|
|
338
|
+
"""Print config or generate config.yaml."""
|
|
339
|
+
config = regcode.Config.load()
|
|
340
|
+
click.echo(config.model_dump_json(indent=2))
|
|
341
|
+
|
|
342
|
+
|
|
343
|
+
@cli.command()
|
|
344
|
+
def version():
|
|
345
|
+
"""Print version."""
|
|
346
|
+
click.echo(f"RegCode v{regcode.__version__}")
|
|
347
|
+
|
|
348
|
+
|
|
349
|
+
if __name__ == "__main__":
|
|
350
|
+
cli()
|
|
351
|
+
```
|
|
352
|
+
|
|
353
|
+
**pyproject.toml entry point:**
|
|
354
|
+
```toml
|
|
355
|
+
[project.scripts]
|
|
356
|
+
regcode = "regcode.cli:cli"
|
|
357
|
+
```
|
|
358
|
+
|
|
359
|
+
**Test:**
|
|
360
|
+
```python
|
|
361
|
+
# tests/test_cli.py
|
|
362
|
+
from click.testing import CliRunner
|
|
363
|
+
from regcode.cli import cli
|
|
364
|
+
|
|
365
|
+
|
|
366
|
+
def test_cli_help():
|
|
367
|
+
r = CliRunner()
|
|
368
|
+
result = r.invoke(cli, ["--help"])
|
|
369
|
+
assert result.exit_code == 0
|
|
370
|
+
assert "RegCode" in result.output
|
|
371
|
+
|
|
372
|
+
|
|
373
|
+
def test_version_command():
|
|
374
|
+
r = CliRunner()
|
|
375
|
+
result = r.invoke(cli, ["version"])
|
|
376
|
+
assert result.exit_code == 0
|
|
377
|
+
assert "v0.1.0" in result.output
|
|
378
|
+
|
|
379
|
+
|
|
380
|
+
def test_configure_command():
|
|
381
|
+
r = CliRunner()
|
|
382
|
+
result = r.invoke(cli, ["configure"])
|
|
383
|
+
assert result.exit_code == 0
|
|
384
|
+
assert "model" in result.output
|
|
385
|
+
```
|
|
386
|
+
|
|
387
|
+
---
|
|
388
|
+
|
|
389
|
+
## Phase 5: Add Dependencies
|
|
390
|
+
|
|
391
|
+
**pyproject.toml:**
|
|
392
|
+
```toml
|
|
393
|
+
[project]
|
|
394
|
+
name = "regcode"
|
|
395
|
+
version = "0.1.0"
|
|
396
|
+
description = "Minimalistic coding agent with Python API and CLI"
|
|
397
|
+
readme = "README.md"
|
|
398
|
+
requires-python = ">=3.12"
|
|
399
|
+
dependencies = [
|
|
400
|
+
"litellm>=1.83.17",
|
|
401
|
+
"pydantic-settings>=2.14.2",
|
|
402
|
+
"pyyaml>=6.0",
|
|
403
|
+
"click>=8.0",
|
|
404
|
+
]
|
|
405
|
+
|
|
406
|
+
[project.scripts]
|
|
407
|
+
regcode = "regcode.cli:cli"
|
|
408
|
+
|
|
409
|
+
[project.optional-dependencies]
|
|
410
|
+
dev = [
|
|
411
|
+
"pytest>=8.0",
|
|
412
|
+
"ruff>=0.8",
|
|
413
|
+
]
|
|
414
|
+
|
|
415
|
+
[tool.ruff.lint]
|
|
416
|
+
select = ["E", "F", "W", "I"]
|
|
417
|
+
|
|
418
|
+
[tool.pytest.ini_options]
|
|
419
|
+
testpaths = ["tests"]
|
|
420
|
+
```
|
|
421
|
+
|
|
422
|
+
---
|
|
423
|
+
|
|
424
|
+
## Verification
|
|
425
|
+
|
|
426
|
+
```bash
|
|
427
|
+
uv sync
|
|
428
|
+
uv run pytest tests/ -v
|
|
429
|
+
uv run ruff check --fix regcode/ tests/
|
|
430
|
+
uv run regcode --help
|
|
431
|
+
uv run regcode configure
|
|
432
|
+
```
|
|
433
|
+
|
|
434
|
+
---
|
|
435
|
+
|
|
436
|
+
## Phase 6: Monty Sandbox Integration
|
|
437
|
+
|
|
438
|
+
### Step 6: Implement secure code execution with pydantic-monty
|
|
439
|
+
|
|
440
|
+
**File: `regcode/monty_sandbox.py`**
|
|
441
|
+
|
|
442
|
+
```python
|
|
443
|
+
# Monty-based sandbox with:
|
|
444
|
+
# - Rust-based secure Python interpreter
|
|
445
|
+
# - Resource limits (memory, CPU, recursion, allocations)
|
|
446
|
+
# - External function registration (whiteboxed)
|
|
447
|
+
# - File I/O within sandbox directory
|
|
448
|
+
# - Proper event loop handling for pytest compatibility
|
|
449
|
+
```
|
|
450
|
+
|
|
451
|
+
**File: `regcode/tools/builtins.py`** - Updated to use `MontySandbox` instead of `subprocess`
|
|
452
|
+
|
|
453
|
+
**File: `regcode/config.py`** - Added `SandboxConfig` with `use_monty` toggle
|
|
454
|
+
|
|
455
|
+
**File: `regcode/main.py`** - Agent now uses `MontySandbox` by default
|
|
456
|
+
|
|
457
|
+
**Test:** `tests/test_monty_sandbox.py` - 29 tests covering execution, limits, file I/O, external functions, integration
|
|
458
|
+
|
|
459
|
+
## Implementation Status
|
|
460
|
+
|
|
461
|
+
All phases complete. 50/50 tests passing. Lint clean.
|
|
462
|
+
|
|
463
|
+
| Phase | Status | Details |
|
|
464
|
+
|-------|--------|---------|
|
|
465
|
+
| Phase 1: Configuration | DONE | `config.yaml`, `Config`/`AgentConfig`/`ProviderConfig` with env var expansion |
|
|
466
|
+
| Phase 2: Agent Core | DONE | `Agent` class with `chat()`, `_stream()`, `reset()`, `history` |
|
|
467
|
+
| Phase 3: Python API | DONE | Clean `regcode` package with `Agent` and `Config` exports |
|
|
468
|
+
| Phase 4: CLI | DONE | `regcode chat`, `regcode configure`, `regcode version` commands |
|
|
469
|
+
| Phase 5: Dependencies | DONE | `pyproject.toml` with litellm, pydantic, pyyaml, click, dev deps |
|
|
470
|
+
| Phase 6: Monty Sandbox | DONE | `MontySandbox` class with resource limits, external functions, file I/O, 29 tests |
|
|
471
|
+
|
|
472
|
+
|
|
473
|
+
### Agent Permissions
|
|
474
|
+
- DONE: Implemented permissions for Agent: AgentPermission.Read, AgentPermission.Write, AgentPermission.Delete, AgentPermission.Execute
|
|
475
|
+
- DONE: Filter tools based on AgentPermissions
|
|
476
|
+
- DONE: Standard Permissions: [AgentPermission.Read, AgentPermission.Execute] (default instead of ALL)
|
|
477
|
+
- DONE: Fixed permission filtering to run after tools are registered (was running on empty registry)
|
|
478
|
+
|
|
479
|
+
### Agent Response
|
|
480
|
+
- DONE: Added `tool_budget` (default 20) and when tool budget is exhausted, send message to agent telling it to stop using tools and respond with its answer
|
|
481
|
+
- DONE: When budget exhausted, LLM is called without tools (tools=None) so agent cannot make more tool calls
|
|
482
|
+
- DONE: Track `_tool_budget_exhausted` flag and `_tool_budget_remaining` counter per agent run
|
|
483
|
+
- DONE: Add explicit user message "You have exhausted your tool budget (20 tool calls) and must now answer..." when budget hits 0
|
|
484
|
+
- DONE: Added explicit break when budget was exhausted to return the agent's text response
|
|
485
|
+
|
|
486
|
+
### Code Review DRY Refactoring
|
|
487
|
+
- DONE: Refactored `code_review()` to build enhanced prompt and delegate to `chat(messages=...)`, eliminating 149 lines of duplicated tool-call loop logic
|
|
488
|
+
|
|
489
|
+
### ShellCommandTool Security Fix
|
|
490
|
+
- DONE: Replaced unrestricted `subprocess.run()` with a command allowlist approach. `ShellCommandTool` now validates every command against a whitelist of safe, read-only commands (`ls`, `cat`, `echo`, `head`, `tail`, `wc`, `grep`, `sort`, `uniq`, `date`, `pwd`, `whoami`, `id`, `uname`, `df`, `free`, `which`, `type`, `stat`, `file`, `sha256sum`, `md5sum`, `base64`, `xxd`, `od`, `hexdump`). Dangerous characters (`;`, `&&`, `|`, backticks, `$()`, etc.) are blocked at the character level. Path traversal (`..`) is rejected. This eliminates the previous security vulnerability where the LLM could execute arbitrary destructive commands with full host access.
|
|
491
|
+
|
|
492
|
+
### LLM Hallucinated Tool Calls Fix
|
|
493
|
+
- DONE: Model was hallucinating tool calls (e.g. `<tool_call>`) when budget was exhausted and `tools=None`. Root cause: tool definitions were baked into the system prompt string, so even with `tools=None` the model still saw tool names and hallucinated calling them. Fix: when budget is exhausted, replace the system prompt with a version stripped of tool definitions (`_build_system_prompt_no_tools()`). Additionally set `tool_choice="none"` and stop sequences `["<tool_call>", "</tool_use>"]`. Message history (including tool_call_ids) is preserved intact to maintain proper conversation continuity.
|
|
494
|
+
|
|
495
|
+
## Top Priority To-Dos:
|
|
496
|
+
|
|
497
|
+
- Currently agent could keep on loading files running into context window limitations. We need to add content compaction threshold and add automatic compaction support.
|
|
498
|
+
- I think for this agent to be able to work on big repos, we need to add `add_review_note` tool which basically can write review notes into file which can survive compaction. Review notes are supposed to be preleminary findings. Add `read_review_notes` as well. This must work intune with compaction feature
|
|
499
|
+
- Save tokens reported by litellm and use following logic `context_window=max_tokens + message_chain_token_size` and compact based on compaction threshold.
|
|
500
|
+
- _build_system_prompt in regcode/main.py and _build_code_review_system_prompt in the same file share an identical loop that constructs a "tool descriptions" string from registered tools. The same logic appears twice. This should be extracted into a single helper method, e.g. _format_tool_descriptions(self) -> str, called by both.
|
|
501
|
+
|
|
502
|
+
Additionally, _build_code_review_system_prompt in regcode/main.py calls self.registry.list_tools() and builds tool descriptions, but it could simply accept a system prompt string and tool descriptions as parameters, instead of duplicating the logic again.
|