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 +44 -21
- package/package.json +3 -4
- package/skills/ai-features/SKILL.md +140 -0
- package/skills/build-app/SKILL.md +137 -57
- package/skills/code-review/SKILL.md +69 -24
- package/skills/debug/SKILL.md +73 -31
- package/skills/performance/SKILL.md +84 -0
- package/skills/refactor/SKILL.md +72 -0
- package/skills/security/SKILL.md +110 -0
- package/skills/ui-ux/SKILL.md +256 -193
- package/skills/write-tests/SKILL.md +72 -34
- package/src/core/loop.js +3 -2
- package/src/core/provider.js +21 -19
- package/src/core/version.js +16 -0
- package/src/ui/screen.js +1187 -1067
- package/ucode.js +3 -4
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
|
|
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
|
-
│
|
|
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
|
-
|
|
22
|
-
|
|
23
|
-
|
|
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
|
|
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 "
|
|
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
|
|
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
|
-
|
|
133
|
-
|
|
134
|
-
|
|
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` | `
|
|
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
|
|
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.
|
|
4
|
-
"description": "ucode - a terminal coding agent that reads, edits and runs your code
|
|
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
|
|
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
|
|
10
|
-
|
|
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
|
|
12
|
+
## 1. Decide the shape, out loud, before any file exists
|
|
13
13
|
|
|
14
|
-
|
|
14
|
+
One line each:
|
|
15
15
|
|
|
16
|
-
- **What it does** — the
|
|
17
|
-
- **The
|
|
18
|
-
|
|
19
|
-
|
|
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
|
-
|
|
24
|
-
|
|
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
|
-
|
|
28
|
+
- **The file list** — the whole tree, before creating any of it.
|
|
27
29
|
|
|
28
|
-
|
|
29
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
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
|
-
|
|
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
|
-
|
|
57
|
+
For Next.js App Router:
|
|
60
58
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
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
|
-
|
|
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
|
-
|
|
80
|
-
|
|
81
|
-
|
|
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
|
|
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
|
|
9
|
-
|
|
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
|
-
##
|
|
13
|
+
## 1. Understand before judging
|
|
12
14
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
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
|
-
##
|
|
21
|
+
## 2. Passes, in order of what hurts most
|
|
27
22
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
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
|
-
|
|
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
|
-
|
|
36
|
-
|
|
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.
|