ucode-agent 1.0.0
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.
- package/README.md +240 -0
- package/package.json +54 -0
- package/skills/build-app/SKILL.md +81 -0
- package/skills/code-review/SKILL.md +36 -0
- package/skills/debug/SKILL.md +47 -0
- package/skills/ui-ux/SKILL.md +237 -0
- package/skills/write-tests/SKILL.md +47 -0
- package/src/core/failure.js +70 -0
- package/src/core/history.js +278 -0
- package/src/core/loop.js +1146 -0
- package/src/core/provider.js +740 -0
- package/src/core/skills.js +165 -0
- package/src/core/window.js +127 -0
- package/src/tools/files.js +466 -0
- package/src/tools/index.js +394 -0
- package/src/tools/search.js +192 -0
- package/src/tools/shared.js +343 -0
- package/src/tools/shell.js +553 -0
- package/src/tools/web.js +96 -0
- package/src/ui/markdown.js +64 -0
- package/src/ui/plain.js +325 -0
- package/src/ui/screen.js +1067 -0
- package/src/ui/theme.js +256 -0
- package/ucode.js +118 -0
package/README.md
ADDED
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
# ucode
|
|
2
|
+
|
|
3
|
+
A coding agent that lives in your terminal. It reads your code, edits it, runs
|
|
4
|
+
your commands, and keeps every conversation on disk. It runs on NVIDIA and
|
|
5
|
+
Cohere models through OpenRouter, all of them free.
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
╭──────────────────────────────────────────────────────────────────────────────────╮
|
|
9
|
+
│ ██╗ ██╗ ██████╗ ██████╗ ██████╗ ███████╗ dir ~/projects/notes-app │
|
|
10
|
+
│ ██║ ██║██╔════╝██╔═══██╗██╔══██╗██╔════╝ keys /help · esc interrupts │
|
|
11
|
+
│ ██║ ██║██║ ██║ ██║██║ ██║█████╗ │
|
|
12
|
+
│ ██║ ██║██║ ██║ ██║██║ ██║██╔══╝ │
|
|
13
|
+
│ ╚██████╔╝╚██████╗╚██████╔╝██████╔╝███████╗ │
|
|
14
|
+
│ ╚═════╝ ╚═════╝ ╚═════╝ ╚═════╝ ╚══════╝ made with ❤️ by om dixit │
|
|
15
|
+
╰──────────────────────────────────────────────────────────────────────────────────╯
|
|
16
|
+
|
|
17
|
+
● Writing index.html
|
|
18
|
+
└ created · 148 lines
|
|
19
|
+
1 + <!doctype html>
|
|
20
|
+
2 + <html lang="en">
|
|
21
|
+
3 + <head>
|
|
22
|
+
… 145 more lines
|
|
23
|
+
● Running npm test
|
|
24
|
+
└ exit 0 · 12 lines
|
|
25
|
+
|
|
26
|
+
╭──────────────────────────────────────────────────────────────────────────────────╮
|
|
27
|
+
│ › now add a dark mode toggle │
|
|
28
|
+
│ │
|
|
29
|
+
│ ◆ Build · Nemotron 3 Ultra (free) 4% │
|
|
30
|
+
╰──────────────────────────────────────────────────────────────────────────────────╯
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
The status sits inside the input box because it describes the thing you are
|
|
34
|
+
typing into. Three facts, no more: the live mode, the answering model, and how
|
|
35
|
+
full the context window is. The percentage turns amber at 75%, which is where
|
|
36
|
+
older turns start being folded into a summary. While a turn is running the
|
|
37
|
+
middle of that row carries the spinner and the way out of it, and hands the
|
|
38
|
+
space straight back when it finishes.
|
|
39
|
+
|
|
40
|
+
## Install
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
npm i -g ucode-agent
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Then put a key where it will survive upgrades:
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
mkdir -p ~/.ucode
|
|
50
|
+
echo "OPENROUTER_API_KEY=sk-or-..." > ~/.ucode/.env
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
Keys are free at [openrouter.ai/keys](https://openrouter.ai/keys). A `.env` in
|
|
54
|
+
the project you are working on wins over that one, and a real environment
|
|
55
|
+
variable wins over both.
|
|
56
|
+
|
|
57
|
+
Then, in any project:
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
ucode
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
Needs Node 22 or newer.
|
|
64
|
+
|
|
65
|
+
## The models
|
|
66
|
+
|
|
67
|
+
Five, and no picker full of names nobody recognises. NVIDIA and Cohere both
|
|
68
|
+
serve capable models free through OpenRouter, and both handle tool calling
|
|
69
|
+
properly, which is the thing an agent actually depends on.
|
|
70
|
+
|
|
71
|
+
| Model | Context | For |
|
|
72
|
+
| --- | --- | --- |
|
|
73
|
+
| **Nemotron 3 Ultra** ★ | 1M | the default — deepest reasoning, slowest to first token |
|
|
74
|
+
| Nemotron 3.5 Lightning | 1M | the same enormous window, answers much sooner |
|
|
75
|
+
| Nemotron 3 Super | 262k | strong all-rounder, quick to start |
|
|
76
|
+
| Nemotron 3 Nano Omni | 256k | small, fast, reasoning tuned |
|
|
77
|
+
| **North Mini Code** ★ | 256k | code and UI specialist — reach for it on frontend work |
|
|
78
|
+
|
|
79
|
+
`/model` shows them and switches. `ucode -m cohere/north-mini-code:free`
|
|
80
|
+
starts on one.
|
|
81
|
+
|
|
82
|
+
Ultra is the default because the work this is for — read a codebase, hold it in
|
|
83
|
+
mind, change several files consistently — is what a million-token window and a
|
|
84
|
+
long think are for. When the wait stops being worth it, switch.
|
|
85
|
+
|
|
86
|
+
## What it does
|
|
87
|
+
|
|
88
|
+
**Twelve tools.** `read_file`, `read_files`, `write_file`, `batch_write`, `edit_file`,
|
|
89
|
+
`multi_edit`, `list_dir`, `glob`, `grep`, `run_command`, `run_commands`,
|
|
90
|
+
`web_search`. Read-only calls run in parallel; anything that writes runs on its
|
|
91
|
+
own, in order.
|
|
92
|
+
|
|
93
|
+
**Edits that never guess.** `edit_file` matches exactly once or it fails, and
|
|
94
|
+
when it fails it says *why* — the text is there but the indentation differs, or
|
|
95
|
+
its first line appears at line 40 and the rest does not. A wrong edit reported
|
|
96
|
+
as a success is the most expensive thing an agent can do.
|
|
97
|
+
|
|
98
|
+
**Diffs with real line numbers.** Removed lines are numbered where they were,
|
|
99
|
+
added lines where they now are. Numbers you can jump to, not decoration.
|
|
100
|
+
|
|
101
|
+
**Live commentary.** `● Listing src`, `● Running npm test` — what it is doing,
|
|
102
|
+
as it does it, named after the file or command rather than the tool.
|
|
103
|
+
|
|
104
|
+
**Two modes.** Build edits and runs. Plan reads and researches with the writing
|
|
105
|
+
tools withheld, which is stronger than asking a model nicely. `ctrl+b` swaps
|
|
106
|
+
them; the chip inside the input box says which is live.
|
|
107
|
+
|
|
108
|
+
**Sessions.** Everything is on disk under `~/.ucode/sessions`, saved after every
|
|
109
|
+
step. `/resume` lists them with what each one was actually about, the ones from
|
|
110
|
+
this folder first.
|
|
111
|
+
|
|
112
|
+
**A context window that folds rather than forgets.** Past 75% the oldest turns
|
|
113
|
+
are summarised instead of dropped, never cutting between a tool call and its
|
|
114
|
+
result. The full history stays on disk regardless.
|
|
115
|
+
|
|
116
|
+
**Screenshots.** Mention a `.png` in your message and it gets attached.
|
|
117
|
+
|
|
118
|
+
## Skills
|
|
119
|
+
|
|
120
|
+
A skill is a folder with a `SKILL.md`: frontmatter, then instructions. Only the
|
|
121
|
+
names and one-line descriptions go into the system prompt — a body is pulled in
|
|
122
|
+
when it is wanted, so the prompt stays the same size however many you add.
|
|
123
|
+
|
|
124
|
+
| Skill | For |
|
|
125
|
+
| --- | --- |
|
|
126
|
+
| `ui-ux` | interfaces: direction, tokens, layout, states, motion, accessibility |
|
|
127
|
+
| `build-app` | going from nothing to something running, and proving it runs |
|
|
128
|
+
| `debug` | finding the real cause instead of the first plausible one |
|
|
129
|
+
| `code-review` | reviewing a change the way a careful colleague would |
|
|
130
|
+
| `write-tests` | tests that fail for the right reason |
|
|
131
|
+
|
|
132
|
+
**`ui-ux` loads itself.** Ask for an app, a dashboard, a landing page, or say
|
|
133
|
+
the UI is ugly, and the whole skill is in context before the model takes its
|
|
134
|
+
first step. Waiting for the model to decide it needs design guidance means
|
|
135
|
+
finding out it did not after the app is built.
|
|
136
|
+
|
|
137
|
+
Add your own in `.ucode/skills/<name>/SKILL.md` inside a project. A project
|
|
138
|
+
skill shadows a built-in of the same name. Give it an `auto:` line and it loads
|
|
139
|
+
itself too:
|
|
140
|
+
|
|
141
|
+
```markdown
|
|
142
|
+
---
|
|
143
|
+
name: house-style
|
|
144
|
+
description: How we write services here.
|
|
145
|
+
auto: endpoint, handler, migration
|
|
146
|
+
---
|
|
147
|
+
|
|
148
|
+
Everything after the frontmatter is the instruction.
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
## Commands
|
|
152
|
+
|
|
153
|
+
| | |
|
|
154
|
+
| --- | --- |
|
|
155
|
+
| `/help` | the list |
|
|
156
|
+
| `/model` | show the models and switch — `/models` does the same |
|
|
157
|
+
| `/resume` | pick up an earlier conversation — `/session`, `/sessions` too |
|
|
158
|
+
| `/new` | save this one and start fresh |
|
|
159
|
+
| `/skills` | what it knows how to do, and what is loaded |
|
|
160
|
+
| `/search <query>` | look something up on the web |
|
|
161
|
+
| `/copy` | last reply to the clipboard |
|
|
162
|
+
| `/clear` | clear the screen, keep the conversation |
|
|
163
|
+
| `/exit` | save and quit |
|
|
164
|
+
|
|
165
|
+
`ctrl+b` plan/build · `esc` stops a running turn · `ctrl+d` quits ·
|
|
166
|
+
`↑ ↓` scroll the conversation, or walk history once you are typing ·
|
|
167
|
+
`tab` completes a command
|
|
168
|
+
|
|
169
|
+
## Options
|
|
170
|
+
|
|
171
|
+
```
|
|
172
|
+
ucode [options]
|
|
173
|
+
|
|
174
|
+
-m, --model <id> which model to use
|
|
175
|
+
-C, --cwd <dir> work in another directory
|
|
176
|
+
--plan start in plan mode
|
|
177
|
+
--debug print stack traces when something breaks
|
|
178
|
+
-v, --version print the version
|
|
179
|
+
-h, --help the above
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
## Configuration
|
|
183
|
+
|
|
184
|
+
| | |
|
|
185
|
+
| --- | --- |
|
|
186
|
+
| `~/.ucode/.env` | `OPENROUTER_API_KEY`, and `TAVILY_API_KEY` for web search |
|
|
187
|
+
| `~/.ucode/sessions/` | one JSON per conversation |
|
|
188
|
+
| `.ucode/skills/` | skills belonging to a project |
|
|
189
|
+
|
|
190
|
+
Environment overrides: `UCODE_MODEL`, `UCODE_MAX_CONTEXT_TOKENS`,
|
|
191
|
+
`UCODE_MAX_STEPS`, `UCODE_MAX_TOOL_OUTPUT`, `UCODE_REQUEST_TIMEOUT_MS`,
|
|
192
|
+
`UCODE_BASE_URL`.
|
|
193
|
+
|
|
194
|
+
Web search needs a Tavily key — free, 1000 searches a month, no card. Without
|
|
195
|
+
one, ucode answers from what it knows and says that it could not check.
|
|
196
|
+
|
|
197
|
+
## How it is put together
|
|
198
|
+
|
|
199
|
+
```
|
|
200
|
+
ucode.js the command: arguments in, Agent out
|
|
201
|
+
src/core/loop.js the agent loop, the system prompt, the slash commands
|
|
202
|
+
src/core/provider.js the only file that knows OpenRouter exists
|
|
203
|
+
src/core/history.js sessions on disk
|
|
204
|
+
src/core/window.js folding a long conversation to fit
|
|
205
|
+
src/core/skills.js loading skills, and deciding which load themselves
|
|
206
|
+
src/core/failure.js one error shape: what, why, what next
|
|
207
|
+
src/tools/ the eleven tools, plus their shared plumbing
|
|
208
|
+
src/ui/screen.js the full-screen interface
|
|
209
|
+
src/ui/plain.js the same interface for when there is no terminal
|
|
210
|
+
src/ui/theme.js colour, boxes, and the string maths behind both
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
Everything above `provider.js` speaks one small provider-neutral message
|
|
214
|
+
format. Moving to another host means rewriting that one file.
|
|
215
|
+
|
|
216
|
+
Every failure carries three things — what was attempted, what failed, and what
|
|
217
|
+
to do next — so no screen ever has to fall back on a stack trace. Tool failures
|
|
218
|
+
are handed to the model as text instead, which is why they read like
|
|
219
|
+
instructions.
|
|
220
|
+
|
|
221
|
+
## Development
|
|
222
|
+
|
|
223
|
+
```bash
|
|
224
|
+
git clone https://github.com/sppideey/ucode-agent
|
|
225
|
+
cd ucode-agent
|
|
226
|
+
npm install
|
|
227
|
+
npm link # puts `ucode` on PATH, pointing at this checkout
|
|
228
|
+
npm test
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
`npm link` matters while developing: it symlinks the global command to your
|
|
232
|
+
working copy, so an edit is live on the next launch. Running
|
|
233
|
+
`npm i -g ucode-agent` replaces that with a frozen copy from the registry and
|
|
234
|
+
your edits stop taking effect.
|
|
235
|
+
|
|
236
|
+
The tests need no network and no framework — `node test/run.js` runs them all.
|
|
237
|
+
|
|
238
|
+
## Licence
|
|
239
|
+
|
|
240
|
+
ISC. Made with ❤️ by om dixit.
|
package/package.json
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "ucode-agent",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "ucode - a terminal coding agent that reads, edits and runs your code. NVIDIA and Cohere models over OpenRouter.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "ucode.js",
|
|
7
|
+
"bin": {
|
|
8
|
+
"ucode": "ucode.js"
|
|
9
|
+
},
|
|
10
|
+
"files": [
|
|
11
|
+
"ucode.js",
|
|
12
|
+
"src/",
|
|
13
|
+
"skills/"
|
|
14
|
+
],
|
|
15
|
+
"scripts": {
|
|
16
|
+
"start": "node ucode.js",
|
|
17
|
+
"test": "node test/run.js",
|
|
18
|
+
"prepublishOnly": "node test/run.js"
|
|
19
|
+
},
|
|
20
|
+
"repository": {
|
|
21
|
+
"type": "git",
|
|
22
|
+
"url": "git+https://github.com/sppideey/ucode-agent.git"
|
|
23
|
+
},
|
|
24
|
+
"bugs": {
|
|
25
|
+
"url": "https://github.com/sppideey/ucode-agent/issues"
|
|
26
|
+
},
|
|
27
|
+
"homepage": "https://github.com/sppideey/ucode-agent#readme",
|
|
28
|
+
"publishConfig": {
|
|
29
|
+
"access": "public"
|
|
30
|
+
},
|
|
31
|
+
"engines": {
|
|
32
|
+
"node": ">=22"
|
|
33
|
+
},
|
|
34
|
+
"keywords": [
|
|
35
|
+
"agent",
|
|
36
|
+
"cli",
|
|
37
|
+
"terminal",
|
|
38
|
+
"coding-agent",
|
|
39
|
+
"llm",
|
|
40
|
+
"nvidia",
|
|
41
|
+
"nemotron",
|
|
42
|
+
"cohere",
|
|
43
|
+
"openrouter"
|
|
44
|
+
],
|
|
45
|
+
"author": "om dixit",
|
|
46
|
+
"license": "ISC",
|
|
47
|
+
"dependencies": {
|
|
48
|
+
"chalk": "^6.0.0",
|
|
49
|
+
"dotenv": "^17.4.2",
|
|
50
|
+
"marked": "^15.0.12",
|
|
51
|
+
"marked-terminal": "^7.3.0",
|
|
52
|
+
"openai": "^7.4.0"
|
|
53
|
+
}
|
|
54
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: build-app
|
|
3
|
+
description: Take something from nothing to running — choosing the stack, laying out the files, installing, wiring it up, and proving it works before saying it does.
|
|
4
|
+
auto: scaffold, new project, from scratch, build an app, make an app, create an app, build a website, make a website, build a site, build me a, next.js app, nextjs app, next app, react app, vite app, shadcn, create-next-app
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Building something from nothing
|
|
8
|
+
|
|
9
|
+
The failure mode here is not writing bad code. It is delivering a folder of
|
|
10
|
+
files that has never been run, described as if it works.
|
|
11
|
+
|
|
12
|
+
## Decide the shape before writing anything
|
|
13
|
+
|
|
14
|
+
State these in one line each, out loud, then build to them:
|
|
15
|
+
|
|
16
|
+
- **What it does** — the single sentence a user would say.
|
|
17
|
+
- **The stack** — and why. Default to the smallest thing that does the job: a
|
|
18
|
+
single HTML file with no build step is a legitimate answer and often the
|
|
19
|
+
right one. Reach for a framework when routing, state or a component tree
|
|
20
|
+
genuinely earns it, not because the project sounds serious.
|
|
21
|
+
- **The files** — the whole list, before you create any of them.
|
|
22
|
+
|
|
23
|
+
If the request has a user interface in it, the `ui-ux` skill is already loaded.
|
|
24
|
+
Follow it. Do not design as you go and tidy up afterwards.
|
|
25
|
+
|
|
26
|
+
## Scaffold without being asked questions
|
|
27
|
+
|
|
28
|
+
Nothing you run has a keyboard. A scaffolder that stops to ask "Would you like
|
|
29
|
+
to use TypeScript?" gets no answer and fails, so give it every answer up front:
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
# Next.js — creates ./my-app; use . instead of a name to fill the current folder
|
|
33
|
+
npx create-next-app@latest my-app --ts --tailwind --eslint --app --src-dir --import-alias "@/*" --use-npm --yes
|
|
34
|
+
|
|
35
|
+
# shadcn/ui, from inside the Next.js project
|
|
36
|
+
npx shadcn@latest init -d -y
|
|
37
|
+
npx shadcn@latest add button card input label badge progress separator skeleton sonner -y
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
For any other scaffolder, find its flags for every question it would ask
|
|
41
|
+
(`--help` lists them) before running it for real.
|
|
42
|
+
|
|
43
|
+
`create-next-app` refuses a folder that already has files in it. If the current
|
|
44
|
+
folder is not empty, scaffold into a named subfolder and pass that as `cwd` to
|
|
45
|
+
every command after it.
|
|
46
|
+
|
|
47
|
+
Add every shadcn component you will need in one `add` call, not one per call.
|
|
48
|
+
|
|
49
|
+
## Lay it out in one pass
|
|
50
|
+
|
|
51
|
+
Use `batch_write` for the whole skeleton rather than `write_file` twenty times.
|
|
52
|
+
One call, every file, in dependency order. Then `run_command` the install, and
|
|
53
|
+
`run_commands` for anything independent that can happen at the same time.
|
|
54
|
+
|
|
55
|
+
Real content from the first pass. Placeholder copy, `TODO`, and a commented-out
|
|
56
|
+
function are all the same thing: a promise you did not keep, in a file the user
|
|
57
|
+
now has to find.
|
|
58
|
+
|
|
59
|
+
## Wire everything
|
|
60
|
+
|
|
61
|
+
Every button does its thing. Every form submits, validates and says what went
|
|
62
|
+
wrong. Every list has an empty state. Every request has a loading state and a
|
|
63
|
+
failure state. A control that does nothing is worse than no control, because
|
|
64
|
+
the user has to try it to find out.
|
|
65
|
+
|
|
66
|
+
If it stores anything, decide where, and make it survive a reload.
|
|
67
|
+
|
|
68
|
+
## Run it, then look at it
|
|
69
|
+
|
|
70
|
+
- Start it with `run_command`. A dev server needs `background: true`, which
|
|
71
|
+
returns immediately with a PID — a foreground server just burns the turn and
|
|
72
|
+
gets killed.
|
|
73
|
+
- Then actually exercise it: `curl` the routes, run the tests, open the page.
|
|
74
|
+
A clean build is not evidence that it works, only that it compiles.
|
|
75
|
+
- Fix what you find and run it again.
|
|
76
|
+
|
|
77
|
+
## Report what happened
|
|
78
|
+
|
|
79
|
+
Say what you built, how to start it, and what you checked. If something is
|
|
80
|
+
untested or unfinished, say which part and why — that sentence costs you
|
|
81
|
+
nothing and saves the user an hour of finding out on their own.
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: code-review
|
|
3
|
+
description: Review a change the way a careful colleague would — correctness first, then the things that will hurt later, with the reasoning attached.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Reviewing code
|
|
7
|
+
|
|
8
|
+
A review that lists everything is not a review, it is a linter with opinions.
|
|
9
|
+
Rank what you find, and be honest about which parts you actually verified.
|
|
10
|
+
|
|
11
|
+
## Read it in this order
|
|
12
|
+
|
|
13
|
+
1. **Correctness.** Does it do what it claims? Walk the edge cases: empty,
|
|
14
|
+
one, many, null, the boundary value, the concurrent call, the second run.
|
|
15
|
+
2. **Failure.** What happens when the thing it depends on fails? A swallowed
|
|
16
|
+
error and a bare `catch {}` are bugs waiting for the worst possible moment.
|
|
17
|
+
3. **Security.** Untrusted input reaching a query, a shell, a path, or the DOM.
|
|
18
|
+
Secrets in the source. Credentials in a log line.
|
|
19
|
+
4. **Contracts.** Did a signature, a return shape or a stored format change
|
|
20
|
+
without every caller and every existing row being accounted for?
|
|
21
|
+
5. **Clarity.** Names that say what the thing is. Comments that explain why,
|
|
22
|
+
never what. A function that fits in your head.
|
|
23
|
+
6. **Tests.** Does the test actually fail when the code is wrong? A test that
|
|
24
|
+
asserts a mock was called proves nothing about behaviour.
|
|
25
|
+
|
|
26
|
+
## Say it usefully
|
|
27
|
+
|
|
28
|
+
For each finding: the file and line, what breaks, and the input that breaks it.
|
|
29
|
+
"This could be cleaner" is not actionable. "`parse()` throws on an empty body,
|
|
30
|
+
which the retry path hits on a 204" is.
|
|
31
|
+
|
|
32
|
+
Separate what you know from what you suspect, and say which is which. If you
|
|
33
|
+
did not run it, do not describe the behaviour as if you watched it happen.
|
|
34
|
+
|
|
35
|
+
Say what is good, briefly, and only where it is genuinely worth copying.
|
|
36
|
+
Reviews that never approve of anything stop being read.
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: debug
|
|
3
|
+
description: Find the actual cause of a bug instead of the first plausible one — reproduce it, narrow it, prove the fix, and leave a test behind.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Debugging
|
|
7
|
+
|
|
8
|
+
The temptation is to read the code, form a theory, change something, and
|
|
9
|
+
declare victory when the symptom disappears. That is how a bug gets moved
|
|
10
|
+
rather than fixed.
|
|
11
|
+
|
|
12
|
+
## Reproduce it first
|
|
13
|
+
|
|
14
|
+
Do not start from the description. Run the thing and see the failure with your
|
|
15
|
+
own eyes: the command, the input, the exact error and where it comes from. If
|
|
16
|
+
you cannot reproduce it, say so and ask for what you need — the input, the
|
|
17
|
+
version, the full stack. Guessing from a paraphrase wastes everyone's turn.
|
|
18
|
+
|
|
19
|
+
## Narrow before you theorise
|
|
20
|
+
|
|
21
|
+
- Read the whole stack trace, including the frames you assume are irrelevant.
|
|
22
|
+
The top frame is where it surfaced, not necessarily where it went wrong.
|
|
23
|
+
- `grep` for the message text to find where it is produced.
|
|
24
|
+
- Cut the search space in half at a time: does the smaller input fail? Does it
|
|
25
|
+
fail on the previous commit? Does the layer below get the right value?
|
|
26
|
+
- Print or log the values at the boundary rather than reasoning about what they
|
|
27
|
+
should be. What you believe is in that variable is the thing under suspicion.
|
|
28
|
+
|
|
29
|
+
## Fix the cause
|
|
30
|
+
|
|
31
|
+
State the cause in one sentence before you change anything: *this value is
|
|
32
|
+
undefined here because the caller only sets it on the success path*. If you
|
|
33
|
+
cannot write that sentence, you have not found it yet.
|
|
34
|
+
|
|
35
|
+
Then fix that, not the symptom. A guard that hides the undefined value leaves
|
|
36
|
+
the real defect in place, with one more layer over it.
|
|
37
|
+
|
|
38
|
+
## Prove it
|
|
39
|
+
|
|
40
|
+
- Run the original reproduction. It must now pass.
|
|
41
|
+
- Run the rest of the tests. A fix that breaks two other things is a trade,
|
|
42
|
+
and the user gets to make it, not you.
|
|
43
|
+
- Write a test that fails without your fix. A bug with no regression test comes
|
|
44
|
+
back.
|
|
45
|
+
|
|
46
|
+
Then say what the cause actually was, in one or two sentences. If you fixed
|
|
47
|
+
something adjacent along the way, say that too.
|