ucode-agent 1.0.0 → 1.2.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 CHANGED
@@ -2,31 +2,49 @@
2
2
 
3
3
  A coding agent that lives in your terminal. It reads your code, edits it, runs
4
4
  your commands, and keeps every conversation on disk. It runs on NVIDIA and
5
- Cohere models through OpenRouter, all of them free.
5
+ Cohere models, all of them free.
6
+
7
+ It opens on a quiet screen — the name, the place to type, and the version in the
8
+ corner:
9
+
10
+ ```
11
+ ██╗ ██╗ ██████╗ ██████╗ ██████╗ ███████╗
12
+ ██║ ██║██╔════╝██╔═══██╗██╔══██╗██╔════╝
13
+ ██║ ██║██║ ██║ ██║██║ ██║█████╗
14
+ ██║ ██║██║ ██║ ██║██║ ██║██╔══╝
15
+ ╚██████╔╝╚██████╗╚██████╔╝██████╔╝███████╗
16
+ ╚═════╝ ╚═════╝ ╚═════╝ ╚═════╝ ╚══════╝
17
+
18
+
19
+ ╭──────────────────────────────────────────────────────────────────────────────╮
20
+ │ › Ask anything… │
21
+ │ │
22
+ │ ◆ Build · Nemotron 3 Ultra 0% │
23
+ ╰──────────────────────────────────────────────────────────────────────────────╯
24
+
25
+
26
+ v1.2.0
27
+ ```
28
+
29
+ and once you are talking, each message you send is boxed in the same blue as
30
+ the input, so your own words are easy to find in a long session:
6
31
 
7
32
  ```
8
33
  ╭──────────────────────────────────────────────────────────────────────────────────╮
9
- ██╗ ██╗ ██████╗ ██████╗ ██████╗ ███████╗ dir ~/projects/notes-app
10
- │ ██║ ██║██╔════╝██╔═══██╗██╔══██╗██╔════╝ keys /help · esc interrupts │
11
- │ ██║ ██║██║ ██║ ██║██║ ██║█████╗ │
12
- │ ██║ ██║██║ ██║ ██║██║ ██║██╔══╝ │
13
- │ ╚██████╔╝╚██████╗╚██████╔╝██████╔╝███████╗ │
14
- │ ╚═════╝ ╚═════╝ ╚═════╝ ╚═════╝ ╚══════╝ made with ❤️ by om dixit │
34
+ build a notes dashboard
15
35
  ╰──────────────────────────────────────────────────────────────────────────────────╯
16
-
17
36
  ● Writing index.html
18
37
  └ created · 148 lines
19
38
  1 + <!doctype html>
20
39
  2 + <html lang="en">
21
- 3 + <head>
22
- 145 more lines
23
- Running npm test
24
- └ exit 0 · 12 lines
40
+ 146 more lines
41
+ Running npm run dev
42
+ ready · http://localhost:3000 · PID 4812
25
43
 
26
44
  ╭──────────────────────────────────────────────────────────────────────────────────╮
27
45
  │ › now add a dark mode toggle │
28
46
  │ │
29
- │ ◆ Build · Nemotron 3 Ultra (free) 4% │
47
+ │ ◆ Build · Nemotron 3 Ultra 4% │
30
48
  ╰──────────────────────────────────────────────────────────────────────────────────╯
31
49
  ```
32
50
 
@@ -47,7 +65,7 @@ Then put a key where it will survive upgrades:
47
65
 
48
66
  ```bash
49
67
  mkdir -p ~/.ucode
50
- echo "OPENROUTER_API_KEY=sk-or-..." > ~/.ucode/.env
68
+ echo "UCODE_API_KEY=sk-or-..." > ~/.ucode/.env
51
69
  ```
52
70
 
53
71
  Keys are free at [openrouter.ai/keys](https://openrouter.ai/keys). A `.env` in
@@ -65,7 +83,7 @@ Needs Node 22 or newer.
65
83
  ## The models
66
84
 
67
85
  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
86
+ serve capable models free, and both handle tool calling
69
87
  properly, which is the thing an agent actually depends on.
70
88
 
71
89
  | Model | Context | For |
@@ -128,10 +146,15 @@ when it is wanted, so the prompt stays the same size however many you add.
128
146
  | `debug` | finding the real cause instead of the first plausible one |
129
147
  | `code-review` | reviewing a change the way a careful colleague would |
130
148
  | `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
149
+ | `ai-features` | model-backed features: prompts with rules, validated JSON, images, failure handling |
150
+ | `security` | secrets, auth, ownership checks, injection, XSS, CSRF, SSRF, uploads |
151
+ | `performance` | measure first, find the real bottleneck, prove the win with numbers |
152
+ | `refactor` | change the shape of code without changing what it does |
153
+
154
+ **Every skill loads itself** when the request calls for it — an app pulls in
155
+ `ui-ux` and `build-app`, "it crashes" pulls in `debug`, an AI feature pulls in
156
+ `ai-features`, an API key pulls in `security` — so the whole skill is in
157
+ context before the model takes its first step. Waiting for the model to decide it needs design guidance means
135
158
  finding out it did not after the app is built.
136
159
 
137
160
  Add your own in `.ucode/skills/<name>/SKILL.md` inside a project. A project
@@ -183,7 +206,7 @@ ucode [options]
183
206
 
184
207
  | | |
185
208
  | --- | --- |
186
- | `~/.ucode/.env` | `OPENROUTER_API_KEY`, and `TAVILY_API_KEY` for web search |
209
+ | `~/.ucode/.env` | `UCODE_API_KEY`, and `TAVILY_API_KEY` for web search |
187
210
  | `~/.ucode/sessions/` | one JSON per conversation |
188
211
  | `.ucode/skills/` | skills belonging to a project |
189
212
 
@@ -199,7 +222,7 @@ one, ucode answers from what it knows and says that it could not check.
199
222
  ```
200
223
  ucode.js the command: arguments in, Agent out
201
224
  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
225
+ src/core/provider.js the only file that knows which provider answers
203
226
  src/core/history.js sessions on disk
204
227
  src/core/window.js folding a long conversation to fit
205
228
  src/core/skills.js loading skills, and deciding which load themselves
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
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.",
3
+ "version": "1.2.0",
4
+ "description": "ucode - a terminal coding agent that reads, edits and runs your code, on NVIDIA and Cohere models.",
5
5
  "type": "module",
6
6
  "main": "ucode.js",
7
7
  "bin": {
@@ -39,8 +39,7 @@
39
39
  "llm",
40
40
  "nvidia",
41
41
  "nemotron",
42
- "cohere",
43
- "openrouter"
42
+ "cohere"
44
43
  ],
45
44
  "author": "om dixit",
46
45
  "license": "ISC",
@@ -0,0 +1,140 @@
1
+ ---
2
+ name: ai-features
3
+ description: Build features on top of language and vision models that behave like product, not demos — prompts with explicit rules, structured output validated in code, images, streaming, timeouts, retries, cost and failure handling.
4
+ auto: ai, llm, llms, gpt, chatgpt, claude, gemini, nemotron, chatbot, chat bot, embeddings, rag, vision model, image recognition, ocr, structured output, ai feature, ai-powered, ai powered, prompt engineering, analyze image, analyse image, reads the image, read the label
5
+ ---
6
+
7
+ # AI features
8
+
9
+ A model call that works once in a demo is easy. A feature that gives a
10
+ consistent, correct, well-formatted answer every time, fails gracefully, and
11
+ does not leak a key is the actual job. Treat the model as an unreliable
12
+ upstream service with a very good API.
13
+
14
+ ## 1. Architecture
15
+
16
+ - **Server only.** The call and the key live in a server route or server action.
17
+ The browser sends the input to your route; your route calls the model.
18
+ - **One module per model integration** (`lib/server/analyze.ts`): builds the
19
+ prompt, calls the API, parses and validates the reply, returns a typed result
20
+ or a typed error. Routes and UI never touch raw model output.
21
+ - **Types first.** Define the result with zod before writing the prompt. The
22
+ schema is the contract the prompt has to satisfy.
23
+
24
+ ```ts
25
+ const Finding = z.object({
26
+ nutrient: z.string(),
27
+ amount: z.string(), // "820 mg", as printed
28
+ severity: z.enum(['high', 'low']),
29
+ why: z.string().max(160),
30
+ });
31
+ export const Analysis = z.object({
32
+ isNutritionLabel: z.boolean(),
33
+ score: z.number().min(1).max(10),
34
+ summary: z.string().max(400),
35
+ findings: z.array(Finding).max(8),
36
+ });
37
+ ```
38
+
39
+ ## 2. Prompts that produce the same answer twice
40
+
41
+ - **Role and task in the first line**, then the rules, then the output format.
42
+ - **Make every judgement explicit.** "Flag sodium if it is over 20% of daily
43
+ value per serving (≈460 mg)" is reproducible; "flag unhealthy things" is not.
44
+ Write the thresholds, the scale anchors ("10 = whole food with no concerns,
45
+ 5 = fine occasionally, 1 = mostly sugar or salt"), and what to leave out
46
+ ("if no nutrient crosses a threshold, return an empty findings array — never
47
+ pad it").
48
+ - **Handle the wrong input inside the prompt too**: "If the image is not a
49
+ nutrition facts label, set isNutritionLabel to false and leave the rest empty."
50
+ - **Specify the JSON exactly** — field names, types, units, lengths — and say
51
+ "Reply with the JSON object only. No prose, no code fences."
52
+ - **Put the unchanging instructions in the system message** and the per-request
53
+ input in the user message.
54
+ - Temperature 0–0.3 for extraction and scoring; higher only for creative text.
55
+
56
+ ## 3. Calling the API
57
+
58
+ OpenAI-compatible chat completions (most providers, including model routers):
59
+
60
+ ```ts
61
+ const res = await fetch(`${BASE_URL}/chat/completions`, {
62
+ method: 'POST',
63
+ headers: { Authorization: `Bearer ${KEY}`, 'Content-Type': 'application/json' },
64
+ body: JSON.stringify({
65
+ model: MODEL_ID,
66
+ temperature: 0.2,
67
+ messages: [
68
+ { role: 'system', content: SYSTEM_PROMPT },
69
+ { role: 'user', content: [
70
+ { type: 'text', text: 'Analyse this label.' },
71
+ { type: 'image_url', image_url: { url: dataUrl } },
72
+ ] },
73
+ ],
74
+ }),
75
+ signal: AbortSignal.timeout(90_000),
76
+ });
77
+ if (!res.ok) throw new ModelError(res.status, await res.text());
78
+ const text = (await res.json()).choices?.[0]?.message?.content ?? '';
79
+ ```
80
+
81
+ - **Timeout on every call.** Reasoning models can think for 30–60s; set the route's
82
+ own limit (`export const maxDuration = 90` in Next.js) above the call's.
83
+ - **Retry only what is safe to retry**: 429 (after `retry-after`), 5xx, network
84
+ errors, timeouts — at most 2 retries with backoff. Never retry a 400 or 401.
85
+ - **Map errors to user language** at the boundary: rate limited → "busy, try
86
+ again in a moment"; timeout → "took too long"; invalid output → "could not
87
+ read the result"; not a label → "that does not look like a nutrition label".
88
+
89
+ ## 4. Parsing model output defensively
90
+
91
+ Models add prose, wrap JSON in fences, use trailing commas, or return numbers
92
+ as strings. Never `JSON.parse` the raw text directly.
93
+
94
+ ```ts
95
+ function extractJson(text: string): unknown {
96
+ const fenced = text.match(/```(?:json)?\s*([\s\S]*?)```/i)?.[1];
97
+ const raw = fenced ?? text.slice(text.indexOf('{'), text.lastIndexOf('}') + 1);
98
+ return JSON.parse(raw);
99
+ }
100
+ const parsed = Analysis.safeParse(extractJson(text));
101
+ if (!parsed.success) throw new ModelError(422, 'unreadable model output');
102
+ ```
103
+
104
+ Then **enforce the rules in code as well**: clamp the score, drop findings that
105
+ do not meet the threshold, cap list lengths. The prompt asks; the code
106
+ guarantees.
107
+
108
+ ## 5. Images
109
+
110
+ - Accept jpeg, png, webp. Check type and size on the client before upload.
111
+ - Downscale in a canvas to ~1600px on the long edge and re-encode as JPEG at
112
+ 0.85 — faster upload, fewer tokens, same accuracy for text in the image.
113
+ - Send as a `data:image/jpeg;base64,...` URL. Handle HEIC from phones by
114
+ telling the user to export as JPEG if the browser cannot decode it.
115
+ - Check that the model you are calling accepts images at all before building
116
+ on it.
117
+
118
+ ## 6. The experience around the call
119
+
120
+ - A real loading state with words ("Reading the label…"), elapsed time if it can
121
+ exceed ~5s, and a way to cancel (`AbortController`).
122
+ - Stream text responses when the output is prose, so something appears at once.
123
+ For structured results, show a skeleton of the result shape instead.
124
+ - Show the result with its reasoning visible (the summary, the findings), not
125
+ just a number.
126
+ - Keep the user's input on failure so "try again" is one click.
127
+
128
+ ## 7. Cost, privacy and abuse
129
+
130
+ - Log the model, latency, token usage and outcome — never the key, and never
131
+ the user's content unless they agreed to it.
132
+ - Limit input size and request rate per user on public endpoints.
133
+ - Cache results for identical inputs where it is safe (hash of the image).
134
+
135
+ ## 8. Verify with real inputs
136
+
137
+ Test with at least: a clear typical input, a hard one (blurry photo, cropped
138
+ label), a wrong one (a photo of a cat), and an edge (a food that genuinely has
139
+ no concerns — the findings list must come back empty). Report what each
140
+ returned.
@@ -1,81 +1,161 @@
1
1
  ---
2
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
3
+ description: Take an app from nothing to running and finished stack choice, non-interactive scaffolding, project structure, secrets, AI and API integration, error handling, 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, full stack, fullstack, saas, mvp
5
5
  ---
6
6
 
7
7
  # Building something from nothing
8
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.
9
+ The failure mode is not bad code. It is a folder of files that has never been
10
+ run, handed over as if it works. Everything here is ordered to prevent that.
11
11
 
12
- ## Decide the shape before writing anything
12
+ ## 1. Decide the shape, out loud, before any file exists
13
13
 
14
- State these in one line each, out loud, then build to them:
14
+ One line each:
15
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.
16
+ - **What it does** — the sentence a user would say.
17
+ - **The core loop** — the one path through it that must work perfectly
18
+ (e.g. upload a photo analysed see a score and the problems).
19
+ - **The stack, and why** the smallest thing that does the job:
22
20
 
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.
21
+ | Need | Choose |
22
+ | --- | --- |
23
+ | One page, no secrets, no server | a single `index.html`, no build step |
24
+ | Interactive client app, no secrets | Vite + React + TypeScript |
25
+ | Pages plus a server, secrets, API routes, SEO | Next.js App Router + TypeScript |
26
+ | An API on its own | Node (Hono/Express) or Python (FastAPI) |
25
27
 
26
- ## Scaffold without being asked questions
28
+ - **The file list** the whole tree, before creating any of it.
27
29
 
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
+ If there is a user interface, the `ui-ux` skill is already loaded. Decide the
31
+ design direction now, not after the logic works. If the app calls a model,
32
+ load `ai-features`; if it has accounts, keys or uploads, load `security`.
33
+
34
+ ## 2. Scaffold without being asked questions
35
+
36
+ Nothing you run has a keyboard. A scaffolder that asks "Would you like to use
37
+ TypeScript?" gets no answer and fails, so give it every answer up front:
30
38
 
31
39
  ```bash
32
- # Next.js creates ./my-app; use . instead of a name to fill the current folder
40
+ # Next.js into ./my-app (use . to fill the current folder — it must be empty)
33
41
  npx create-next-app@latest my-app --ts --tailwind --eslint --app --src-dir --import-alias "@/*" --use-npm --yes
34
42
 
35
- # shadcn/ui, from inside the Next.js project
43
+ # shadcn/ui, from inside the project — every component you need, in one add
36
44
  npx shadcn@latest init -d -y
37
- npx shadcn@latest add button card input label badge progress separator skeleton sonner -y
45
+ npx shadcn@latest add button card input label badge progress separator skeleton sonner tooltip -y
38
46
  ```
39
47
 
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.
48
+ - `create-next-app` refuses a folder that already has files. If the current
49
+ folder is not empty, scaffold into a named subfolder and pass it as `cwd` to
50
+ every later command.
51
+ - For any other scaffolder, find the flag for every question (`--help`) first.
52
+ - Install dependencies once, all together: `npm i zod lucide-react` not one
53
+ `npm i` per package.
54
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.
55
+ ## 3. Structure it like a real project
58
56
 
59
- ## Wire everything
57
+ For Next.js App Router:
60
58
 
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.
59
+ ```
60
+ src/
61
+ app/
62
+ layout.tsx fonts, metadata, <body> shell, Toaster
63
+ page.tsx the screen — composes components, holds little logic
64
+ globals.css design tokens and the shadcn theme variables
65
+ api/<name>/route.ts server-only endpoints; the only place secrets live
66
+ components/
67
+ <feature>/ one folder per feature: its pieces, split by job
68
+ ui/ shadcn components (generated edit via the theme)
69
+ lib/
70
+ <service>.ts calls to outside services, typed in and out
71
+ schemas.ts zod schemas shared by client and server
72
+ utils.ts
73
+ types/ shared TypeScript types, if lib/ does not own them
74
+ ```
76
75
 
77
- ## Report what happened
76
+ - **One component per file**, named for what it is (`ScoreDial.tsx`,
77
+ `NutrientFindings.tsx`, `LabelUpload.tsx`), not a 600-line `page.tsx`.
78
+ - Server components by default; `"use client"` only on the interactive parts.
79
+ - Types at every boundary. Parse external data with zod rather than trusting
80
+ its shape.
81
+
82
+ ## 4. Secrets and outside services
83
+
84
+ - **A key never reaches the browser.** It lives in a server route or server
85
+ action. Anything imported by a `"use client"` file ships to every visitor —
86
+ including a "hardcoded for now" key. If the user asks to hardcode one, put it
87
+ in a server-only module (`lib/server/*.ts`, or `import 'server-only'`) and
88
+ say where it is so they can move it to `.env.local` later.
89
+ - Every outbound call gets: a timeout (`AbortSignal.timeout(60_000)`), a check
90
+ of the response status, and an error that says what failed — surfaced to the
91
+ UI as a real message, never a silent `catch {}`.
92
+
93
+ ### Calling an AI model (any OpenAI-compatible API)
94
+
95
+ ```ts
96
+ // src/app/api/analyze/route.ts — runs on the server only
97
+ export const runtime = 'nodejs';
98
+ export const maxDuration = 60;
99
+
100
+ const res = await fetch('https://openrouter.ai/api/v1/chat/completions', {
101
+ method: 'POST',
102
+ headers: { Authorization: `Bearer ${KEY}`, 'Content-Type': 'application/json' },
103
+ body: JSON.stringify({
104
+ model: 'provider/model-id',
105
+ messages: [
106
+ { role: 'system', content: 'Reply with JSON only, matching this shape: {...}' },
107
+ { role: 'user', content: [
108
+ { type: 'text', text: 'Analyse this nutrition label.' },
109
+ { type: 'image_url', image_url: { url: dataUrl } }, // data:image/jpeg;base64,...
110
+ ] },
111
+ ],
112
+ }),
113
+ signal: AbortSignal.timeout(60_000),
114
+ });
115
+ ```
78
116
 
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.
117
+ - **Ask for JSON and parse it defensively.** Models wrap JSON in prose or code
118
+ fences: extract the first `{...}` block, `JSON.parse` it, validate with zod,
119
+ and on failure return a clear "could not read the result" error rather than
120
+ crashing. Clamp numbers to their range.
121
+ - **Put the judgement rules in the prompt, explicitly** — thresholds, what
122
+ counts as "too much", what to omit. A vague prompt gives a different answer
123
+ every time; a specific one gives the product its consistency.
124
+ - **Images:** check type and size on the client (e.g. ≤ 5 MB, jpeg/png/webp),
125
+ downscale large photos in a canvas before upload, send as a base64 data URL.
126
+ - Reasoning models may take 10–60s. Show progress, and make the route's
127
+ timeout longer than the model's.
128
+
129
+ ## 5. Build order
130
+
131
+ 1. Skeleton and design tokens, so every later piece is styled correctly first time.
132
+ 2. The server route with the real integration, tested with `curl` before any UI.
133
+ 3. The core loop UI, wired to the real route.
134
+ 4. Every state: empty, loading, success, error, and invalid input.
135
+ 5. Polish: motion, responsive, copy, favicon, page title and metadata.
136
+
137
+ Use `batch_write` for the skeleton — one call, every file.
138
+
139
+ ## 6. Prove it works
140
+
141
+ - `npm run build` — it type-checks and lints; a build that fails is not done.
142
+ - Start it: `npm run dev` goes to the background on its own and comes back with
143
+ the URL once ready. Do not start it twice.
144
+ - Exercise it: `curl` the API route with real input, load the page, check the
145
+ core loop end to end. A clean build proves it compiles, not that it works.
146
+ - Fix what you find and check again.
147
+
148
+ ## 7. Definition of done
149
+
150
+ - The core loop works end to end against the real service.
151
+ - No `TODO`, no placeholder copy, no dead buttons, no console errors.
152
+ - Every async action has loading, success and error states.
153
+ - Invalid input is caught with a useful message before it reaches the server.
154
+ - Secrets only on the server.
155
+ - `npm run build` passes.
156
+
157
+ ## 8. Report
158
+
159
+ What you built, the URL, how to start it again, and what you checked. If
160
+ anything is untested or unfinished, name it — one sentence of honesty saves the
161
+ user an hour of finding out on their own.
@@ -1,36 +1,81 @@
1
1
  ---
2
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.
3
+ description: Review code the way a senior engineer would — correctness first, then security, failure handling, contracts, performance and tests, with every finding ranked, located and backed by a concrete failing scenario.
4
+ auto: review, code review, review my, review this, audit this, look over, check my code, pr review, pull request
4
5
  ---
5
6
 
6
7
  # Reviewing code
7
8
 
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.
9
+ A review that lists everything is a linter with opinions. The value is in
10
+ finding what will actually break, ranking it, and being honest about what you
11
+ verified versus what you suspect.
10
12
 
11
- ## Read it in this order
13
+ ## 1. Understand before judging
12
14
 
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.
15
+ - What is the change trying to do? Read the description, the diff, and enough of
16
+ the surrounding code to know the callers and the data it touches.
17
+ - Get the diff: `git diff`, `git diff main...HEAD`, or the files named. Read
18
+ every changed file in full, not only the hunks context is where bugs hide.
19
+ - If there are tests, run them. If it runs, run it.
25
20
 
26
- ## Say it usefully
21
+ ## 2. Passes, in order of what hurts most
27
22
 
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.
23
+ 1. **Correctness** does it do what it claims for every input? Walk: empty,
24
+ one, many, null/undefined, the boundary value, duplicates, the second call,
25
+ concurrent calls, a slow or failing dependency.
26
+ 2. **Security** — untrusted input reaching SQL, a shell, a file path, a URL
27
+ fetch (SSRF), `innerHTML`/`dangerouslySetInnerHTML`, `eval`, a redirect.
28
+ Secrets in source, in client bundles, or in logs. Missing auth or
29
+ authorization checks on a route. IDs a user can change to see someone
30
+ else's data.
31
+ 3. **Failure handling** — errors swallowed (`catch {}`), a fallback that hides
32
+ a real failure, no timeout on an outbound call, a partial write left behind,
33
+ an error message that leaks internals.
34
+ 4. **Contracts** — a changed signature, return shape, API response, or stored
35
+ format without every caller and every existing record accounted for.
36
+ Migrations that are not reversible or not safe on live data.
37
+ 5. **Performance** — N+1 queries, unbounded loops over user data, missing
38
+ pagination, work in a render loop, a huge dependency for a small job,
39
+ blocking I/O on a hot path.
40
+ 6. **Concurrency and state** — shared mutable state, race conditions,
41
+ stale caches, React effects with missing or wrong dependencies.
42
+ 7. **Tests** — do they fail when the code is wrong? A test asserting a mock was
43
+ called proves nothing about behaviour. Are the risky paths covered?
44
+ 8. **Clarity** — names that say what things are, functions that fit in your
45
+ head, comments that explain *why*. Only flag this when it will cause a real
46
+ misunderstanding.
31
47
 
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.
48
+ ## 3. Verify before you report
34
49
 
35
- Say what is good, briefly, and only where it is genuinely worth copying.
36
- Reviews that never approve of anything stop being read.
50
+ For each suspected issue, check it: read the caller, trace the value, run the
51
+ case if you can. Drop anything you cannot substantiate, or label it clearly as
52
+ a question rather than a finding.
53
+
54
+ ## 4. Write findings that can be acted on
55
+
56
+ Rank by severity:
57
+
58
+ - **Blocker** — wrong results, data loss, a security hole, a crash on a normal path.
59
+ - **Major** — breaks on a realistic edge case, or a failure that will be hard to diagnose.
60
+ - **Minor** — a real but small risk, or a clear maintainability cost.
61
+ - **Nit** — style and preference. Keep these few, or leave them out.
62
+
63
+ Each finding: **where** (`path:line`), **what breaks**, **the input or sequence
64
+ that breaks it**, and **the fix**. For example:
65
+
66
+ > **Major** — `src/app/api/analyze/route.ts:42` — `JSON.parse(text)` throws when
67
+ > the model wraps its reply in a code fence, which it does intermittently. The
68
+ > route then returns a 500 with no message. Extract the first `{…}` block and
69
+ > validate it with the zod schema; return a 422 with "could not read the label"
70
+ > on failure.
71
+
72
+ "This could be cleaner" is not a finding.
73
+
74
+ ## 5. Close out
75
+
76
+ Start with a one-line verdict (ship / ship after fixes / needs rework), then
77
+ the findings, most severe first. Mention what is genuinely good only where it
78
+ is worth copying. State what you did not review or could not run.
79
+
80
+ If asked to fix the findings, fix blockers and majors first, re-run the tests,
81
+ and report what changed.