devstuff 1.15.0__tar.gz → 1.16.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.
- {devstuff-1.15.0 → devstuff-1.16.0}/CHANGELOG.md +22 -0
- {devstuff-1.15.0 → devstuff-1.16.0}/CLAUDE.md +79 -0
- {devstuff-1.15.0 → devstuff-1.16.0}/PKG-INFO +176 -2
- {devstuff-1.15.0 → devstuff-1.16.0}/README.md +175 -1
- devstuff-1.16.0/dev/Makefile +60 -0
- devstuff-1.16.0/docs/specs/README.md +41 -0
- devstuff-1.16.0/docs/specs/agent/development-plan.md +194 -0
- devstuff-1.16.0/docs/specs/agent/specifications.md +204 -0
- devstuff-1.16.0/docs/specs/agent/stack-decisions.md +177 -0
- {devstuff-1.15.0 → devstuff-1.16.0}/pyproject.toml +1 -1
- devstuff-1.16.0/src/dev_setup/agent/__init__.py +6 -0
- devstuff-1.16.0/src/dev_setup/agent/approval.py +102 -0
- devstuff-1.16.0/src/dev_setup/agent/bridges.py +125 -0
- devstuff-1.16.0/src/dev_setup/agent/catalog.py +158 -0
- devstuff-1.16.0/src/dev_setup/agent/completion.py +50 -0
- devstuff-1.16.0/src/dev_setup/agent/config.py +170 -0
- devstuff-1.16.0/src/dev_setup/agent/loop.py +129 -0
- devstuff-1.16.0/src/dev_setup/agent/ollama.py +243 -0
- devstuff-1.16.0/src/dev_setup/agent/preflight.py +98 -0
- devstuff-1.16.0/src/dev_setup/agent/primitives.py +101 -0
- devstuff-1.16.0/src/dev_setup/agent/registry.py +156 -0
- devstuff-1.16.0/src/dev_setup/agent/sandbox.py +286 -0
- devstuff-1.16.0/src/dev_setup/agent/session.py +334 -0
- devstuff-1.16.0/src/dev_setup/agent/transcript.py +52 -0
- devstuff-1.16.0/src/dev_setup/agent/wizard.py +104 -0
- devstuff-1.16.0/src/dev_setup/agent_tools.schema.json +112 -0
- devstuff-1.16.0/src/dev_setup/agent_tools.yaml +145 -0
- {devstuff-1.15.0 → devstuff-1.16.0}/src/dev_setup/cli.py +2 -0
- devstuff-1.16.0/src/dev_setup/commands/agent_cmd.py +143 -0
- {devstuff-1.15.0 → devstuff-1.16.0}/src/dev_setup/commands/help_cmd.py +4 -0
- {devstuff-1.15.0 → devstuff-1.16.0}/src/dev_setup/function_runner.py +18 -2
- {devstuff-1.15.0 → devstuff-1.16.0}/src/dev_setup/tools.yaml +20 -5
- devstuff-1.16.0/tests/integration/test_agent_smoke.py +107 -0
- devstuff-1.16.0/tests/integration/test_agent_tui.py +92 -0
- devstuff-1.16.0/tests/test_agent.py +379 -0
- devstuff-1.16.0/tests/test_agent_completion.py +197 -0
- devstuff-1.16.0/tests/test_agent_loop.py +368 -0
- devstuff-1.16.0/tests/test_agent_sandbox.py +348 -0
- devstuff-1.16.0/tests/test_agent_tools.py +417 -0
- devstuff-1.16.0/tests/test_agent_wizard.py +205 -0
- {devstuff-1.15.0 → devstuff-1.16.0}/tests/test_functions.py +29 -0
- {devstuff-1.15.0 → devstuff-1.16.0}/uv.lock +1 -1
- devstuff-1.15.0/dev/Makefile +0 -39
- {devstuff-1.15.0 → devstuff-1.16.0}/.claude/skills/run-stuff/SKILL.md +0 -0
- {devstuff-1.15.0 → devstuff-1.16.0}/.claude/skills/run-stuff/smoke.sh +0 -0
- {devstuff-1.15.0 → devstuff-1.16.0}/.github/workflows/bump.yml +0 -0
- {devstuff-1.15.0 → devstuff-1.16.0}/.github/workflows/ci.yml +0 -0
- {devstuff-1.15.0 → devstuff-1.16.0}/.github/workflows/publish.yml +0 -0
- {devstuff-1.15.0 → devstuff-1.16.0}/.github/workflows/test-installs.yml +0 -0
- {devstuff-1.15.0 → devstuff-1.16.0}/.gitignore +0 -0
- {devstuff-1.15.0 → devstuff-1.16.0}/dev/Dockerfile +0 -0
- {devstuff-1.15.0 → devstuff-1.16.0}/dev/Dockerfile.ci +0 -0
- {devstuff-1.15.0 → devstuff-1.16.0}/dev/docker-compose.yml +0 -0
- {devstuff-1.15.0 → devstuff-1.16.0}/dev-setup +0 -0
- {devstuff-1.15.0 → devstuff-1.16.0}/install.sh +0 -0
- {devstuff-1.15.0 → devstuff-1.16.0}/src/dev_setup/__init__.py +0 -0
- {devstuff-1.15.0 → devstuff-1.16.0}/src/dev_setup/__main__.py +0 -0
- {devstuff-1.15.0 → devstuff-1.16.0}/src/dev_setup/base.py +0 -0
- {devstuff-1.15.0 → devstuff-1.16.0}/src/dev_setup/catalog.py +0 -0
- {devstuff-1.15.0 → devstuff-1.16.0}/src/dev_setup/commands/__init__.py +0 -0
- {devstuff-1.15.0 → devstuff-1.16.0}/src/dev_setup/commands/add_cmd.py +0 -0
- {devstuff-1.15.0 → devstuff-1.16.0}/src/dev_setup/commands/catalog_cmd.py +0 -0
- {devstuff-1.15.0 → devstuff-1.16.0}/src/dev_setup/commands/delete_cmd.py +0 -0
- {devstuff-1.15.0 → devstuff-1.16.0}/src/dev_setup/commands/docs_cmd.py +0 -0
- {devstuff-1.15.0 → devstuff-1.16.0}/src/dev_setup/commands/functions_cmd.py +0 -0
- {devstuff-1.15.0 → devstuff-1.16.0}/src/dev_setup/commands/install_cmd.py +0 -0
- {devstuff-1.15.0 → devstuff-1.16.0}/src/dev_setup/commands/list_cmd.py +0 -0
- {devstuff-1.15.0 → devstuff-1.16.0}/src/dev_setup/commands/remove_cmd.py +0 -0
- {devstuff-1.15.0 → devstuff-1.16.0}/src/dev_setup/commands/run_cmd.py +0 -0
- {devstuff-1.15.0 → devstuff-1.16.0}/src/dev_setup/commands/skills_cmd.py +0 -0
- {devstuff-1.15.0 → devstuff-1.16.0}/src/dev_setup/commands/update_cmd.py +0 -0
- {devstuff-1.15.0 → devstuff-1.16.0}/src/dev_setup/functions.schema.json +0 -0
- {devstuff-1.15.0 → devstuff-1.16.0}/src/dev_setup/functions.yaml +0 -0
- {devstuff-1.15.0 → devstuff-1.16.0}/src/dev_setup/functions_catalog.py +0 -0
- {devstuff-1.15.0 → devstuff-1.16.0}/src/dev_setup/functions_registry.py +0 -0
- {devstuff-1.15.0 → devstuff-1.16.0}/src/dev_setup/generic.py +0 -0
- {devstuff-1.15.0 → devstuff-1.16.0}/src/dev_setup/registry.py +0 -0
- {devstuff-1.15.0 → devstuff-1.16.0}/src/dev_setup/ui.py +0 -0
- {devstuff-1.15.0 → devstuff-1.16.0}/tests/__init__.py +0 -0
- {devstuff-1.15.0 → devstuff-1.16.0}/tests/integration/__init__.py +0 -0
- {devstuff-1.15.0 → devstuff-1.16.0}/tests/integration/conftest.py +0 -0
- {devstuff-1.15.0 → devstuff-1.16.0}/tests/integration/test_tools.py +0 -0
- {devstuff-1.15.0 → devstuff-1.16.0}/tests/test_catalog.py +0 -0
- {devstuff-1.15.0 → devstuff-1.16.0}/tests/test_generic.py +0 -0
- {devstuff-1.15.0 → devstuff-1.16.0}/tests/test_registry.py +0 -0
|
@@ -1,3 +1,25 @@
|
|
|
1
|
+
## v1.16.0 (2026-07-24)
|
|
2
|
+
|
|
3
|
+
### Feat
|
|
4
|
+
|
|
5
|
+
- **agent**: first-run setup wizard for agent configuration
|
|
6
|
+
- **agent**: multi-line input and slash completion in the REPL
|
|
7
|
+
- **agent**: milestone 4 — transcripts, /history, docs, schema
|
|
8
|
+
- **agent**: complete milestone 2 — tools, sandbox denylist, approval loop
|
|
9
|
+
- **agent**: add workspace sandbox, launch guard, and smoke suite
|
|
10
|
+
- **agent**: add ollama-backed agent scaffold (milestone 1)
|
|
11
|
+
|
|
12
|
+
### Fix
|
|
13
|
+
|
|
14
|
+
- **agent**: correct argument mapping and output capture for function tools
|
|
15
|
+
|
|
16
|
+
## v1.15.1 (2026-07-20)
|
|
17
|
+
|
|
18
|
+
### Fix
|
|
19
|
+
|
|
20
|
+
- make aws update idempotent (pass --update over existing install)
|
|
21
|
+
- repair broken update paths for llm-checker and ruby
|
|
22
|
+
|
|
1
23
|
## v1.15.0 (2026-07-18)
|
|
2
24
|
|
|
3
25
|
### Feat
|
|
@@ -167,6 +167,78 @@ non-interactive `bash <tmpfile>`, so `~/.bashrc`/nvm's shell init never runs on
|
|
|
167
167
|
Not yet built: an `add` wizard and `catalog import`/`export` for functions, analogous to the
|
|
168
168
|
ones tools already have.
|
|
169
169
|
|
|
170
|
+
## Specs (`docs/specs/`)
|
|
171
|
+
|
|
172
|
+
Design documents live in `docs/specs/<feature>/` — `specifications.md` (numbered, testable
|
|
173
|
+
requirements), `stack-decisions.md` (choices *and* rejected alternatives with reasons), and
|
|
174
|
+
`development-plan.md` (milestones, testing strategy, risks). See `docs/specs/README.md` for the
|
|
175
|
+
conventions.
|
|
176
|
+
|
|
177
|
+
When working on a feature that has a spec, read it first and **keep it current in the same PR** —
|
|
178
|
+
a spec that no longer matches the code is worse than no spec. Record resolved open questions with
|
|
179
|
+
a date and the answer rather than deleting them, and when a live finding contradicts a
|
|
180
|
+
requirement, update the requirement and note what was learned. New features of any size should
|
|
181
|
+
get a spec directory before implementation starts.
|
|
182
|
+
|
|
183
|
+
## The agent (`devstuff agent`) — a third catalog subsystem
|
|
184
|
+
|
|
185
|
+
`src/dev_setup/agent/` + `agent_tools.yaml` is an interactive session where a local Ollama model
|
|
186
|
+
calls devstuff's tools plus a workspace-scoped filesystem/shell kit. It follows the same
|
|
187
|
+
catalog-driven shape as tools and functions: `agent/catalog.py` validates `agent_tools.yaml`
|
|
188
|
+
(bundled → user override), `agent/registry.py` turns it into `AgentTool` objects, and
|
|
189
|
+
`agent/primitives.py` dispatches by `impl` through a `_PRIMITIVES` dict — the same
|
|
190
|
+
strategy-dispatch pattern as `_INSTALLERS` in `generic.py`.
|
|
191
|
+
|
|
192
|
+
**Adding an agent tool**: if it bridges to something that already exists (a catalog tool, a
|
|
193
|
+
`functions.yaml` entry), it is a pure YAML edit — `impl: catalog` or `impl: function` plus a
|
|
194
|
+
`target`. Only a genuinely new mechanism needs a new `impl: primitive` callable registered in
|
|
195
|
+
`_PRIMITIVES`. `type: script` functions are auto-exposed as `fn_<key>` tools with no edit at all.
|
|
196
|
+
|
|
197
|
+
**Security invariants — do not weaken these without deliberate thought:**
|
|
198
|
+
- `Workspace.resolve()` in `agent/sandbox.py` is the *only* thing standing between the model and
|
|
199
|
+
the filesystem. It resolves symlinks and `..` **before** the containment check; reordering that
|
|
200
|
+
reintroduces a symlink escape. Prompt instructions are not a control and never will be.
|
|
201
|
+
- The command denylist (`check_command`) runs **before** any confirmation prompt and is
|
|
202
|
+
deliberately not disabled by `--yolo`. The prompt is a human attention filter; attention
|
|
203
|
+
degrades over a session, the denylist does not.
|
|
204
|
+
- Credential dirs are blocked for **read** as well as write — exfiltrating an SSH key into a model
|
|
205
|
+
context is as bad as overwriting one. `~/.config/dev-setup` is readable but not writable, so
|
|
206
|
+
the agent cannot author catalogs (FR-14a).
|
|
207
|
+
- `assess()` (the launch guard) is advisory UX, not a control. Keep that distinction in comments;
|
|
208
|
+
the risk is a future reader mistaking a warning for enforcement.
|
|
209
|
+
|
|
210
|
+
**Everything in the loop returns errors to the model rather than raising.** Unknown tool, bad
|
|
211
|
+
arguments, sandbox refusal, a crashing tool — all become `role: tool` messages so the agent can
|
|
212
|
+
re-plan. A malformed tool call must never end a session. `max_iterations` is what stops a runaway.
|
|
213
|
+
|
|
214
|
+
**`cd` is a tool, not a shell command**, for the same reason `shell-eval` functions exist: each
|
|
215
|
+
`run_command` is its own subprocess, so a shell `cd` evaporates on exit. And `shell-eval`
|
|
216
|
+
functions are excluded from the toolbox entirely — they exist to mutate the calling shell, which
|
|
217
|
+
an agent subprocess has no way to do (mirroring the guard in `run_cmd.py`).
|
|
218
|
+
|
|
219
|
+
**Ollama response-shape handling all lives in `ollama.parse_message()`.** Builds differ on
|
|
220
|
+
whether reasoning arrives in `message.thinking` or as inline `<think>` tags in `content`, and
|
|
221
|
+
whether tool calls arrive in `tool_calls` or as JSON inside `content`. Think-stripping must stay
|
|
222
|
+
*ahead* of the content-JSON fallback, or a reasoning preamble hides the tool call. Keep new
|
|
223
|
+
quirks in that one function.
|
|
224
|
+
|
|
225
|
+
**Model choice is measured, not assumed.** `ollama show` reports a `capabilities` array;
|
|
226
|
+
preflight requires `tools` in it. The default (`gemma4:latest`) was picked by running the same
|
|
227
|
+
scaffolding prompt across local models — lfm2.5 had `write_file` available and still shelled out
|
|
228
|
+
to `echo >`, corrupting the content through shell quoting. If you change the default, re-run that
|
|
229
|
+
comparison rather than reasoning from parameter counts.
|
|
230
|
+
|
|
231
|
+
`agent_tools.schema.json` is hand-maintained for editor tooling and **not** enforced at runtime —
|
|
232
|
+
same arrangement, and same drift hazard, as `functions.schema.json`.
|
|
233
|
+
|
|
234
|
+
First-run UX: `agent/wizard.py` builds `agent.yaml` on the first interactive run (host → a
|
|
235
|
+
pick-list of tool-capable models → reasoning visibility), re-runnable via `devstuff agent
|
|
236
|
+
--setup`. It configures only those three fields deliberately — a first-run wizard asking about
|
|
237
|
+
`num_ctx` and timeouts would be worse than one asking nothing; everything else keeps its default
|
|
238
|
+
and is hand-editable.
|
|
239
|
+
|
|
240
|
+
Not yet built: an `add` wizard for agent tools, and `catalog import`/`export` for them.
|
|
241
|
+
|
|
170
242
|
## Key design decisions (don't relitigate these)
|
|
171
243
|
|
|
172
244
|
- **uv owns Python provisioning.** The bash wrapper only guarantees `uv` is present; Python
|
|
@@ -179,6 +251,13 @@ ones tools already have.
|
|
|
179
251
|
fields, bad `requires` all raise `CatalogError` immediately rather than silently degrading.
|
|
180
252
|
- **Custom install/remove scripts are plain strings**, written to a temp file at run time, so
|
|
181
253
|
`bash` gets full script-parsing fidelity instead of `bash -c "..."` string quoting problems.
|
|
254
|
+
- **The agent's safety boundary is the workspace root, enforced in code.** Not a sandbox
|
|
255
|
+
technology (bubblewrap/firejail) and not model instructions — `Path.resolve()` containment plus
|
|
256
|
+
a command denylist, both unit-tested. Chosen so it works with zero new dependencies and fails
|
|
257
|
+
closed; if you want stronger isolation, add it *around* this, not instead of it.
|
|
258
|
+
- **No new runtime dependencies for the agent.** The Ollama transport is stdlib `urllib` against
|
|
259
|
+
`/api/chat`; the REPL uses `prompt_toolkit`, already vendored via questionary. devstuff is a
|
|
260
|
+
globally installed CLI, so every dependency is a cost paid by users who never run `agent`.
|
|
182
261
|
- **Functions get a parallel catalog/registry instead of extending `GenericTool`.** The
|
|
183
262
|
schemas diverge enough (no `requires` inference, a `params` list, no install/remove
|
|
184
263
|
lifecycle) that folding them into the tool catalog would be lossy; some duplication with
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: devstuff
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.16.0
|
|
4
4
|
Summary: Development environment setup CLI for Linux
|
|
5
5
|
Project-URL: Repository, https://github.com/thesawdawg/dev-setup-py
|
|
6
6
|
Author-email: Sawyer <sawyerksu@gmail.com>
|
|
@@ -291,6 +291,160 @@ confirmation.
|
|
|
291
291
|
|
|
292
292
|
---
|
|
293
293
|
|
|
294
|
+
## Agent
|
|
295
|
+
|
|
296
|
+
`devstuff agent` opens an interactive session with a **local** model (via [Ollama](https://ollama.com))
|
|
297
|
+
that can call devstuff's own tools plus a workspace-scoped filesystem/shell kit. Nothing leaves
|
|
298
|
+
the machine, there are no API keys, and it works offline.
|
|
299
|
+
|
|
300
|
+
```bash
|
|
301
|
+
devstuff agent # interactive REPL
|
|
302
|
+
devstuff agent --setup # (re)run the configuration wizard
|
|
303
|
+
devstuff agent --dir ~/projects # skip the workspace prompt
|
|
304
|
+
devstuff agent --model granite4.1:8b # override the configured model
|
|
305
|
+
devstuff agent --print "which node tools do I have?" # one-shot, non-interactive
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
```
|
|
309
|
+
you ❯ create a python project called xyz-project with a hello world main.py
|
|
310
|
+
|
|
311
|
+
↳ run_command(command='mkdir xyz-project')
|
|
312
|
+
|
|
313
|
+
╭─ run in ./xyz-project ──────────────────────╮
|
|
314
|
+
│ mkdir xyz-project │
|
|
315
|
+
╰─────────────────────────────────────────────╯
|
|
316
|
+
? Run run_command? Yes / No / Always allow this tool
|
|
317
|
+
```
|
|
318
|
+
|
|
319
|
+
### Setup
|
|
320
|
+
|
|
321
|
+
First, make sure a tool-capable model is available:
|
|
322
|
+
|
|
323
|
+
```bash
|
|
324
|
+
devstuff install ollama
|
|
325
|
+
ollama pull gemma4 # or any model reporting the `tools` capability
|
|
326
|
+
```
|
|
327
|
+
|
|
328
|
+
Then the **first time you run `devstuff agent`** with no configuration, a wizard walks you
|
|
329
|
+
through it — Ollama host, then a pick-list of your locally available tool-capable models, then
|
|
330
|
+
whether to show the model's reasoning. It writes `~/.config/dev-setup/agent.yaml` and continues
|
|
331
|
+
into the session. Re-run it any time with `devstuff agent --setup`.
|
|
332
|
+
|
|
333
|
+
The wizard only lists models that report the `tools` capability (`ollama show <model>` shows it
|
|
334
|
+
under Capabilities), so you can't accidentally pick one that can't call tools. `devstuff agent`
|
|
335
|
+
re-verifies this at startup regardless, and tells you which local models qualify if the
|
|
336
|
+
configured one doesn't.
|
|
337
|
+
|
|
338
|
+
The wizard is skipped when you pass `--model`/`--host` (you're already steering) or run
|
|
339
|
+
non-interactively (`--print`), which fall back to built-in defaults.
|
|
340
|
+
|
|
341
|
+
### Safety model
|
|
342
|
+
|
|
343
|
+
The workspace root, chosen at launch (prompted, defaulting to the current directory), is the
|
|
344
|
+
boundary:
|
|
345
|
+
|
|
346
|
+
- **Path containment** — every path a tool touches is resolved (collapsing `..` and following
|
|
347
|
+
symlinks) and must land inside the workspace root. A symlink planted inside the workspace is
|
|
348
|
+
not a way out.
|
|
349
|
+
- **Protected paths** — `~/.ssh`, `~/.aws`, `~/.gnupg` and `~/.config/gh` are refused for read
|
|
350
|
+
*and* write even if the workspace root contains them. `~/.config/dev-setup` is readable but
|
|
351
|
+
never writable: catalog authoring stays a human action.
|
|
352
|
+
- **Confirmation** — every mutating tool call shows the exact command, or a unified diff for
|
|
353
|
+
`write_file`, and waits for yes / no / always-this-session. Read-only calls run silently.
|
|
354
|
+
- **Denylist** — `sudo`, pipe-to-shell installers, catastrophic deletes, disk and service
|
|
355
|
+
commands, and redirects out of the workspace are refused before any prompt is shown, and are
|
|
356
|
+
**not** enabled by `--yolo`.
|
|
357
|
+
- **Launch guard** — warns before handing the agent `$HOME`, a system directory, or a git repo
|
|
358
|
+
with uncommitted changes.
|
|
359
|
+
|
|
360
|
+
`--yolo` skips confirmations for a session; the denylist and path containment still apply.
|
|
361
|
+
In `--print` mode without `--yolo`, mutating calls are refused rather than auto-approved, so a
|
|
362
|
+
scripted invocation cannot become an unattended agent with write access.
|
|
363
|
+
|
|
364
|
+
### Configuration
|
|
365
|
+
|
|
366
|
+
`~/.config/dev-setup/agent.yaml` (all fields optional):
|
|
367
|
+
|
|
368
|
+
```yaml
|
|
369
|
+
version: 1
|
|
370
|
+
model: gemma4:latest
|
|
371
|
+
host: http://localhost:11434 # a remote daemon works; the local binary is then not required
|
|
372
|
+
temperature: 0.2
|
|
373
|
+
num_ctx: 16384
|
|
374
|
+
think: false # true renders the model's reasoning, dimmed
|
|
375
|
+
max_iterations: 12 # tool calls per turn before the loop gives up
|
|
376
|
+
request_timeout: 120
|
|
377
|
+
command_timeout: 120
|
|
378
|
+
max_tool_output_bytes: 8000 # tool output is truncated to this before going back to the model
|
|
379
|
+
auto_approve: [] # tool keys that never ask, e.g. ["write_file"]
|
|
380
|
+
deny_patterns: [] # extra regexes refused on top of the built-in denylist
|
|
381
|
+
```
|
|
382
|
+
|
|
383
|
+
### Tools
|
|
384
|
+
|
|
385
|
+
| Tool | Mutating | Purpose |
|
|
386
|
+
|------|----------|---------|
|
|
387
|
+
| `read_file` | | Read a UTF-8 file in the workspace |
|
|
388
|
+
| `write_file` | ! | Create or overwrite a file (shows a diff) |
|
|
389
|
+
| `list_dir` | | List a directory |
|
|
390
|
+
| `cd` | | Move the working directory used by later calls |
|
|
391
|
+
| `run_command` | ! | Run a shell command in the workspace |
|
|
392
|
+
| `list_tools` | | List the devstuff catalog with install state |
|
|
393
|
+
| `search_catalog` | | Find a catalog tool by name or description |
|
|
394
|
+
| `tool_info` | | Details for one catalog tool |
|
|
395
|
+
| `install_tool` | ! | Install a catalog tool (handles sudo itself) |
|
|
396
|
+
| `fn_<key>` | ! | Every `type: script` entry in `functions.yaml`, exposed automatically |
|
|
397
|
+
|
|
398
|
+
`cd` is a tool rather than a shell command because each `run_command` is its own subprocess — a
|
|
399
|
+
shell `cd` would evaporate when it exits. `shell-eval` functions are deliberately excluded: they
|
|
400
|
+
exist to mutate the calling shell, which a subprocess cannot do.
|
|
401
|
+
|
|
402
|
+
### agent_tools.yaml
|
|
403
|
+
|
|
404
|
+
The toolbox is a catalog, like everything else. Bundled at `src/dev_setup/agent_tools.yaml`,
|
|
405
|
+
user overrides at `~/.config/dev-setup/agent_tools.yaml`, same merge precedence as `tools.yaml`.
|
|
406
|
+
|
|
407
|
+
```yaml
|
|
408
|
+
version: 1
|
|
409
|
+
expose_functions: true
|
|
410
|
+
tools:
|
|
411
|
+
count_lines:
|
|
412
|
+
name: Count Lines
|
|
413
|
+
description: Count the lines in a file. Use this instead of reading a large file.
|
|
414
|
+
impl: primitive # primitive | catalog | function
|
|
415
|
+
mutating: false
|
|
416
|
+
params:
|
|
417
|
+
- name: path
|
|
418
|
+
type: string
|
|
419
|
+
description: Path to the file, relative to the current directory.
|
|
420
|
+
required: true
|
|
421
|
+
```
|
|
422
|
+
|
|
423
|
+
`impl: primitive` dispatches to a callable in `agent/primitives.py` keyed by the tool key (adding
|
|
424
|
+
a new one is a code change). `impl: catalog` and `impl: function` bridge to the tool registry and
|
|
425
|
+
`functions.yaml` respectively via `target`, and need no code. `src/dev_setup/agent_tools.schema.json`
|
|
426
|
+
documents every field for editor autocomplete; it is not enforced at runtime.
|
|
427
|
+
|
|
428
|
+
### Session state
|
|
429
|
+
|
|
430
|
+
- Prompt history: `~/.local/share/dev-setup/agent/history`
|
|
431
|
+
- Transcripts: `~/.local/share/dev-setup/agent/transcripts/<timestamp>.json`, written after every
|
|
432
|
+
turn so a session that ends in a crash is still readable. `/history` shows the current session
|
|
433
|
+
and the transcript path.
|
|
434
|
+
|
|
435
|
+
### Using the prompt
|
|
436
|
+
|
|
437
|
+
- **Enter** sends. **Alt+Enter** (or **Ctrl-J**) inserts a newline, for pasting or writing
|
|
438
|
+
multi-line instructions.
|
|
439
|
+
- **Type `/`** for a completion menu of session commands *and* every available tool, mutating
|
|
440
|
+
ones flagged with `!`. **Tab** cycles, **Enter** takes the highlighted entry.
|
|
441
|
+
- `/<tool>` describes a tool and its parameters, e.g. `/write_file`. It does not run it — the
|
|
442
|
+
agent decides when tools run, so there is no back door around the confirmation flow.
|
|
443
|
+
|
|
444
|
+
In-session commands: `/tools`, `/history`, `/cwd`, `/model`, `/reset`, `/help`, `/exit`.
|
|
445
|
+
|
|
446
|
+
---
|
|
447
|
+
|
|
294
448
|
## Functions/Scripts
|
|
295
449
|
|
|
296
450
|
Reusable shell functions/snippets, tracked in a separate catalog from installable tools
|
|
@@ -586,6 +740,21 @@ dev-setup-py/
|
|
|
586
740
|
├── functions_registry.py # Loads bundled + user YAML into the live function registry
|
|
587
741
|
├── function_runner.py # Param resolution + script/eval/bashrc rendering & execution
|
|
588
742
|
├── functions.yaml # Bundled built-in function catalog
|
|
743
|
+
├── agent_tools.yaml # Bundled agent tool catalog
|
|
744
|
+
├── agent/ # Local-model agent (see "Agent" above)
|
|
745
|
+
│ ├── config.py # agent.yaml load/validate/save
|
|
746
|
+
│ ├── wizard.py # first-run setup wizard (devstuff agent --setup)
|
|
747
|
+
│ ├── ollama.py # stdlib-urllib client for /api/chat, /api/tags, /api/show
|
|
748
|
+
│ ├── preflight.py # installed / reachable / pulled / tool-capable checks
|
|
749
|
+
│ ├── sandbox.py # Workspace containment + command denylist + launch guard
|
|
750
|
+
│ ├── catalog.py # agent_tools.yaml load/validate/merge
|
|
751
|
+
│ ├── registry.py # AgentTool -> Ollama tool schema; function auto-exposure
|
|
752
|
+
│ ├── primitives.py # _PRIMITIVES dispatch: read_file/write_file/list_dir/cd/run_command
|
|
753
|
+
│ ├── bridges.py # catalog + functions.yaml bridges
|
|
754
|
+
│ ├── approval.py # confirmation prompts, unified diffs
|
|
755
|
+
│ ├── loop.py # tool-calling loop
|
|
756
|
+
│ ├── transcript.py # per-session JSON transcript
|
|
757
|
+
│ └── session.py # REPL, slash commands
|
|
589
758
|
├── ui.py # Rich console helpers, questionary wrappers, styled prompts
|
|
590
759
|
├── commands/
|
|
591
760
|
│ ├── list_cmd.py
|
|
@@ -596,7 +765,12 @@ dev-setup-py/
|
|
|
596
765
|
│ ├── delete_cmd.py
|
|
597
766
|
│ ├── catalog_cmd.py
|
|
598
767
|
│ ├── run_cmd.py
|
|
599
|
-
│
|
|
768
|
+
│ ├── functions_cmd.py
|
|
769
|
+
│ ├── skills_cmd.py
|
|
770
|
+
│ └── agent_cmd.py
|
|
771
|
+
└── docs/
|
|
772
|
+
└── specs/ # Design docs per feature (see docs/specs/README.md)
|
|
773
|
+
└── agent/
|
|
600
774
|
```
|
|
601
775
|
|
|
602
776
|
### Adding a new built-in tool
|
|
@@ -264,6 +264,160 @@ confirmation.
|
|
|
264
264
|
|
|
265
265
|
---
|
|
266
266
|
|
|
267
|
+
## Agent
|
|
268
|
+
|
|
269
|
+
`devstuff agent` opens an interactive session with a **local** model (via [Ollama](https://ollama.com))
|
|
270
|
+
that can call devstuff's own tools plus a workspace-scoped filesystem/shell kit. Nothing leaves
|
|
271
|
+
the machine, there are no API keys, and it works offline.
|
|
272
|
+
|
|
273
|
+
```bash
|
|
274
|
+
devstuff agent # interactive REPL
|
|
275
|
+
devstuff agent --setup # (re)run the configuration wizard
|
|
276
|
+
devstuff agent --dir ~/projects # skip the workspace prompt
|
|
277
|
+
devstuff agent --model granite4.1:8b # override the configured model
|
|
278
|
+
devstuff agent --print "which node tools do I have?" # one-shot, non-interactive
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
```
|
|
282
|
+
you ❯ create a python project called xyz-project with a hello world main.py
|
|
283
|
+
|
|
284
|
+
↳ run_command(command='mkdir xyz-project')
|
|
285
|
+
|
|
286
|
+
╭─ run in ./xyz-project ──────────────────────╮
|
|
287
|
+
│ mkdir xyz-project │
|
|
288
|
+
╰─────────────────────────────────────────────╯
|
|
289
|
+
? Run run_command? Yes / No / Always allow this tool
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
### Setup
|
|
293
|
+
|
|
294
|
+
First, make sure a tool-capable model is available:
|
|
295
|
+
|
|
296
|
+
```bash
|
|
297
|
+
devstuff install ollama
|
|
298
|
+
ollama pull gemma4 # or any model reporting the `tools` capability
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
Then the **first time you run `devstuff agent`** with no configuration, a wizard walks you
|
|
302
|
+
through it — Ollama host, then a pick-list of your locally available tool-capable models, then
|
|
303
|
+
whether to show the model's reasoning. It writes `~/.config/dev-setup/agent.yaml` and continues
|
|
304
|
+
into the session. Re-run it any time with `devstuff agent --setup`.
|
|
305
|
+
|
|
306
|
+
The wizard only lists models that report the `tools` capability (`ollama show <model>` shows it
|
|
307
|
+
under Capabilities), so you can't accidentally pick one that can't call tools. `devstuff agent`
|
|
308
|
+
re-verifies this at startup regardless, and tells you which local models qualify if the
|
|
309
|
+
configured one doesn't.
|
|
310
|
+
|
|
311
|
+
The wizard is skipped when you pass `--model`/`--host` (you're already steering) or run
|
|
312
|
+
non-interactively (`--print`), which fall back to built-in defaults.
|
|
313
|
+
|
|
314
|
+
### Safety model
|
|
315
|
+
|
|
316
|
+
The workspace root, chosen at launch (prompted, defaulting to the current directory), is the
|
|
317
|
+
boundary:
|
|
318
|
+
|
|
319
|
+
- **Path containment** — every path a tool touches is resolved (collapsing `..` and following
|
|
320
|
+
symlinks) and must land inside the workspace root. A symlink planted inside the workspace is
|
|
321
|
+
not a way out.
|
|
322
|
+
- **Protected paths** — `~/.ssh`, `~/.aws`, `~/.gnupg` and `~/.config/gh` are refused for read
|
|
323
|
+
*and* write even if the workspace root contains them. `~/.config/dev-setup` is readable but
|
|
324
|
+
never writable: catalog authoring stays a human action.
|
|
325
|
+
- **Confirmation** — every mutating tool call shows the exact command, or a unified diff for
|
|
326
|
+
`write_file`, and waits for yes / no / always-this-session. Read-only calls run silently.
|
|
327
|
+
- **Denylist** — `sudo`, pipe-to-shell installers, catastrophic deletes, disk and service
|
|
328
|
+
commands, and redirects out of the workspace are refused before any prompt is shown, and are
|
|
329
|
+
**not** enabled by `--yolo`.
|
|
330
|
+
- **Launch guard** — warns before handing the agent `$HOME`, a system directory, or a git repo
|
|
331
|
+
with uncommitted changes.
|
|
332
|
+
|
|
333
|
+
`--yolo` skips confirmations for a session; the denylist and path containment still apply.
|
|
334
|
+
In `--print` mode without `--yolo`, mutating calls are refused rather than auto-approved, so a
|
|
335
|
+
scripted invocation cannot become an unattended agent with write access.
|
|
336
|
+
|
|
337
|
+
### Configuration
|
|
338
|
+
|
|
339
|
+
`~/.config/dev-setup/agent.yaml` (all fields optional):
|
|
340
|
+
|
|
341
|
+
```yaml
|
|
342
|
+
version: 1
|
|
343
|
+
model: gemma4:latest
|
|
344
|
+
host: http://localhost:11434 # a remote daemon works; the local binary is then not required
|
|
345
|
+
temperature: 0.2
|
|
346
|
+
num_ctx: 16384
|
|
347
|
+
think: false # true renders the model's reasoning, dimmed
|
|
348
|
+
max_iterations: 12 # tool calls per turn before the loop gives up
|
|
349
|
+
request_timeout: 120
|
|
350
|
+
command_timeout: 120
|
|
351
|
+
max_tool_output_bytes: 8000 # tool output is truncated to this before going back to the model
|
|
352
|
+
auto_approve: [] # tool keys that never ask, e.g. ["write_file"]
|
|
353
|
+
deny_patterns: [] # extra regexes refused on top of the built-in denylist
|
|
354
|
+
```
|
|
355
|
+
|
|
356
|
+
### Tools
|
|
357
|
+
|
|
358
|
+
| Tool | Mutating | Purpose |
|
|
359
|
+
|------|----------|---------|
|
|
360
|
+
| `read_file` | | Read a UTF-8 file in the workspace |
|
|
361
|
+
| `write_file` | ! | Create or overwrite a file (shows a diff) |
|
|
362
|
+
| `list_dir` | | List a directory |
|
|
363
|
+
| `cd` | | Move the working directory used by later calls |
|
|
364
|
+
| `run_command` | ! | Run a shell command in the workspace |
|
|
365
|
+
| `list_tools` | | List the devstuff catalog with install state |
|
|
366
|
+
| `search_catalog` | | Find a catalog tool by name or description |
|
|
367
|
+
| `tool_info` | | Details for one catalog tool |
|
|
368
|
+
| `install_tool` | ! | Install a catalog tool (handles sudo itself) |
|
|
369
|
+
| `fn_<key>` | ! | Every `type: script` entry in `functions.yaml`, exposed automatically |
|
|
370
|
+
|
|
371
|
+
`cd` is a tool rather than a shell command because each `run_command` is its own subprocess — a
|
|
372
|
+
shell `cd` would evaporate when it exits. `shell-eval` functions are deliberately excluded: they
|
|
373
|
+
exist to mutate the calling shell, which a subprocess cannot do.
|
|
374
|
+
|
|
375
|
+
### agent_tools.yaml
|
|
376
|
+
|
|
377
|
+
The toolbox is a catalog, like everything else. Bundled at `src/dev_setup/agent_tools.yaml`,
|
|
378
|
+
user overrides at `~/.config/dev-setup/agent_tools.yaml`, same merge precedence as `tools.yaml`.
|
|
379
|
+
|
|
380
|
+
```yaml
|
|
381
|
+
version: 1
|
|
382
|
+
expose_functions: true
|
|
383
|
+
tools:
|
|
384
|
+
count_lines:
|
|
385
|
+
name: Count Lines
|
|
386
|
+
description: Count the lines in a file. Use this instead of reading a large file.
|
|
387
|
+
impl: primitive # primitive | catalog | function
|
|
388
|
+
mutating: false
|
|
389
|
+
params:
|
|
390
|
+
- name: path
|
|
391
|
+
type: string
|
|
392
|
+
description: Path to the file, relative to the current directory.
|
|
393
|
+
required: true
|
|
394
|
+
```
|
|
395
|
+
|
|
396
|
+
`impl: primitive` dispatches to a callable in `agent/primitives.py` keyed by the tool key (adding
|
|
397
|
+
a new one is a code change). `impl: catalog` and `impl: function` bridge to the tool registry and
|
|
398
|
+
`functions.yaml` respectively via `target`, and need no code. `src/dev_setup/agent_tools.schema.json`
|
|
399
|
+
documents every field for editor autocomplete; it is not enforced at runtime.
|
|
400
|
+
|
|
401
|
+
### Session state
|
|
402
|
+
|
|
403
|
+
- Prompt history: `~/.local/share/dev-setup/agent/history`
|
|
404
|
+
- Transcripts: `~/.local/share/dev-setup/agent/transcripts/<timestamp>.json`, written after every
|
|
405
|
+
turn so a session that ends in a crash is still readable. `/history` shows the current session
|
|
406
|
+
and the transcript path.
|
|
407
|
+
|
|
408
|
+
### Using the prompt
|
|
409
|
+
|
|
410
|
+
- **Enter** sends. **Alt+Enter** (or **Ctrl-J**) inserts a newline, for pasting or writing
|
|
411
|
+
multi-line instructions.
|
|
412
|
+
- **Type `/`** for a completion menu of session commands *and* every available tool, mutating
|
|
413
|
+
ones flagged with `!`. **Tab** cycles, **Enter** takes the highlighted entry.
|
|
414
|
+
- `/<tool>` describes a tool and its parameters, e.g. `/write_file`. It does not run it — the
|
|
415
|
+
agent decides when tools run, so there is no back door around the confirmation flow.
|
|
416
|
+
|
|
417
|
+
In-session commands: `/tools`, `/history`, `/cwd`, `/model`, `/reset`, `/help`, `/exit`.
|
|
418
|
+
|
|
419
|
+
---
|
|
420
|
+
|
|
267
421
|
## Functions/Scripts
|
|
268
422
|
|
|
269
423
|
Reusable shell functions/snippets, tracked in a separate catalog from installable tools
|
|
@@ -559,6 +713,21 @@ dev-setup-py/
|
|
|
559
713
|
├── functions_registry.py # Loads bundled + user YAML into the live function registry
|
|
560
714
|
├── function_runner.py # Param resolution + script/eval/bashrc rendering & execution
|
|
561
715
|
├── functions.yaml # Bundled built-in function catalog
|
|
716
|
+
├── agent_tools.yaml # Bundled agent tool catalog
|
|
717
|
+
├── agent/ # Local-model agent (see "Agent" above)
|
|
718
|
+
│ ├── config.py # agent.yaml load/validate/save
|
|
719
|
+
│ ├── wizard.py # first-run setup wizard (devstuff agent --setup)
|
|
720
|
+
│ ├── ollama.py # stdlib-urllib client for /api/chat, /api/tags, /api/show
|
|
721
|
+
│ ├── preflight.py # installed / reachable / pulled / tool-capable checks
|
|
722
|
+
│ ├── sandbox.py # Workspace containment + command denylist + launch guard
|
|
723
|
+
│ ├── catalog.py # agent_tools.yaml load/validate/merge
|
|
724
|
+
│ ├── registry.py # AgentTool -> Ollama tool schema; function auto-exposure
|
|
725
|
+
│ ├── primitives.py # _PRIMITIVES dispatch: read_file/write_file/list_dir/cd/run_command
|
|
726
|
+
│ ├── bridges.py # catalog + functions.yaml bridges
|
|
727
|
+
│ ├── approval.py # confirmation prompts, unified diffs
|
|
728
|
+
│ ├── loop.py # tool-calling loop
|
|
729
|
+
│ ├── transcript.py # per-session JSON transcript
|
|
730
|
+
│ └── session.py # REPL, slash commands
|
|
562
731
|
├── ui.py # Rich console helpers, questionary wrappers, styled prompts
|
|
563
732
|
├── commands/
|
|
564
733
|
│ ├── list_cmd.py
|
|
@@ -569,7 +738,12 @@ dev-setup-py/
|
|
|
569
738
|
│ ├── delete_cmd.py
|
|
570
739
|
│ ├── catalog_cmd.py
|
|
571
740
|
│ ├── run_cmd.py
|
|
572
|
-
│
|
|
741
|
+
│ ├── functions_cmd.py
|
|
742
|
+
│ ├── skills_cmd.py
|
|
743
|
+
│ └── agent_cmd.py
|
|
744
|
+
└── docs/
|
|
745
|
+
└── specs/ # Design docs per feature (see docs/specs/README.md)
|
|
746
|
+
└── agent/
|
|
573
747
|
```
|
|
574
748
|
|
|
575
749
|
### Adding a new built-in tool
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
ROOT := ..
|
|
2
|
+
|
|
3
|
+
# Pass TOOL=<key> to run-tests to test a single tool, e.g. make run-tests TOOL=uv
|
|
4
|
+
# Pass PYTEST_ARGS="..." for arbitrary pytest flags, e.g. make run-tests PYTEST_ARGS="-x"
|
|
5
|
+
TOOL ?=
|
|
6
|
+
PYTEST_ARGS ?=
|
|
7
|
+
|
|
8
|
+
.PHONY: build build-ci run shell run-tests smoke-agent tui-tests clean
|
|
9
|
+
|
|
10
|
+
# Build a fresh wheel from source, then build the Docker image.
|
|
11
|
+
# Clean dist first so the glob in the Dockerfile matches exactly one file.
|
|
12
|
+
build:
|
|
13
|
+
cd $(ROOT) && rm -f dist/*.whl dist/*.tar.gz && uv build
|
|
14
|
+
docker compose build
|
|
15
|
+
|
|
16
|
+
# Build the CI image (sandbox + pytest + integration tests baked in)
|
|
17
|
+
build-ci: build
|
|
18
|
+
docker build -f Dockerfile.ci -t dev-setup-ci ..
|
|
19
|
+
|
|
20
|
+
# Drop into an interactive shell as the 'developer' user
|
|
21
|
+
run: build
|
|
22
|
+
docker compose run --rm sandbox
|
|
23
|
+
|
|
24
|
+
shell: run
|
|
25
|
+
|
|
26
|
+
# Run the tool-install integration tests inside the CI container.
|
|
27
|
+
# Scoped to test_tools.py on purpose: the agent suites are also integration-marked
|
|
28
|
+
# but belong on the host (smoke-agent needs a daemon, tui-tests needs a PTY and
|
|
29
|
+
# realistic timing), and neither has anything to do with installing tools.
|
|
30
|
+
# Examples:
|
|
31
|
+
# make run-tests — run every tool's install test
|
|
32
|
+
# make run-tests TOOL=uv — run only the uv test
|
|
33
|
+
# make run-tests PYTEST_ARGS="-x --tb=long"
|
|
34
|
+
run-tests: build-ci
|
|
35
|
+
$(eval _NODESEL := $(if $(TOOL),tests/integration/test_tools.py::test_install[$(TOOL)],tests/integration/test_tools.py))
|
|
36
|
+
docker run --rm dev-setup-ci \
|
|
37
|
+
pytest $(_NODESEL) -m integration -v --tb=short $(PYTEST_ARGS)
|
|
38
|
+
|
|
39
|
+
# Smoke-test `devstuff agent` against a live Ollama daemon.
|
|
40
|
+
# Runs on the HOST, not in Docker -- the CI image has no daemon, and yours may be
|
|
41
|
+
# on another machine. Skips cleanly if nothing is reachable.
|
|
42
|
+
# make smoke-agent
|
|
43
|
+
# make smoke-agent AGENT_HOST=http://192.168.1.69:11434 AGENT_MODEL=gemma4:latest
|
|
44
|
+
AGENT_HOST ?=
|
|
45
|
+
AGENT_MODEL ?=
|
|
46
|
+
smoke-agent:
|
|
47
|
+
cd $(ROOT) && \
|
|
48
|
+
$(if $(AGENT_HOST),DEVSTUFF_AGENT_HOST=$(AGENT_HOST)) \
|
|
49
|
+
$(if $(AGENT_MODEL),DEVSTUFF_AGENT_MODEL=$(AGENT_MODEL)) \
|
|
50
|
+
uv run pytest tests/integration/test_agent_smoke.py -m integration -v $(PYTEST_ARGS)
|
|
51
|
+
|
|
52
|
+
# Keystroke tests for the agent prompt, driven through a real PTY. Timing-dependent
|
|
53
|
+
# and slow (~16s), so they are excluded from the default suite. No daemon needed.
|
|
54
|
+
tui-tests:
|
|
55
|
+
cd $(ROOT) && uv run pytest tests/integration/test_agent_tui.py -m integration -v $(PYTEST_ARGS)
|
|
56
|
+
|
|
57
|
+
# Remove the built images and named volume
|
|
58
|
+
clean:
|
|
59
|
+
docker compose down --rmi local --volumes 2>/dev/null || true
|
|
60
|
+
docker rmi dev-setup-ci 2>/dev/null || true
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# Specs
|
|
2
|
+
|
|
3
|
+
Design documents for features and significant changes, one directory per feature.
|
|
4
|
+
|
|
5
|
+
```
|
|
6
|
+
docs/specs/
|
|
7
|
+
└── <feature>/
|
|
8
|
+
├── specifications.md # what it does: numbered, testable requirements
|
|
9
|
+
├── stack-decisions.md # what we chose and what we rejected, with reasons
|
|
10
|
+
└── development-plan.md # milestones, testing strategy, risks
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Why these exist
|
|
14
|
+
|
|
15
|
+
Git history records *what changed*. These record *what was decided and why* — including the
|
|
16
|
+
options that were rejected, which is the part that gets re-litigated eighteen months later when
|
|
17
|
+
nobody remembers why the obvious approach wasn't taken.
|
|
18
|
+
|
|
19
|
+
They are most useful when they capture:
|
|
20
|
+
|
|
21
|
+
- **Rejected alternatives with their reasons.** "We didn't use the official Ollama package
|
|
22
|
+
because it pulls httpx + pydantic into every install" is worth more than the decision alone.
|
|
23
|
+
- **Findings that changed the design.** A spec that still describes the original guess is worse
|
|
24
|
+
than no spec. When reality contradicts a requirement, update it and say when and why.
|
|
25
|
+
- **Explicit non-goals.** Scope creep is easiest to resist against a written out-of-scope list.
|
|
26
|
+
|
|
27
|
+
## Conventions
|
|
28
|
+
|
|
29
|
+
- **Keep them current, or delete them.** A stale spec is actively misleading. If a document no
|
|
30
|
+
longer matches the code, fix it in the same PR that broke it.
|
|
31
|
+
- **Mark resolved questions with a date and the answer**, rather than deleting the row — the fact
|
|
32
|
+
that something was once open is information.
|
|
33
|
+
- **Requirements are numbered and testable** (FR-1, NFR-1) so tests and commits can cite them.
|
|
34
|
+
- **Distinguish advisory behaviour from enforced behaviour.** Where a spec describes a security
|
|
35
|
+
property, say which mechanism is the control and which is merely a warning.
|
|
36
|
+
|
|
37
|
+
## Existing specs
|
|
38
|
+
|
|
39
|
+
| Feature | Status |
|
|
40
|
+
|---------|--------|
|
|
41
|
+
| [`agent/`](agent/) — `devstuff agent`, the local-model agent | Complete (v1) |
|