ucode-agent 1.1.0 → 1.3.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 |
@@ -85,15 +103,44 @@ long think are for. When the wait stops being worth it, switch.
85
103
 
86
104
  ## What it does
87
105
 
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.
106
+ **Thirteen tools.** `read_file`, `read_files`, `write_file`, `batch_write`,
107
+ `edit_file`, `multi_edit`, `edit_files`, `list_dir`, `glob`, `grep`,
108
+ `run_command`, `run_commands`, `web_search`. Read-only calls run in parallel,
109
+ and start the moment the model finishes writing them — while the rest of its
110
+ reply is still arriving. Anything that writes runs on its own, in order.
111
+
112
+ **Parallel workers.** When a build splits into parts that touch different files
113
+ — the API route, the upload component, the results view — the model hands them
114
+ to up to three workers that build at the same time, each line in the transcript
115
+ tagged with the worker's name. File writes take turns so two never collide.
116
+
117
+ **Installs that start early.** The moment a `package.json` with dependencies is
118
+ written, its install starts in the background while the rest of the app is
119
+ still being written. An install the model asks for later waits for that one
120
+ instead of running twice, and anything run in that folder waits for it too.
121
+
122
+ **Errors fixed before you see them.** When the model says it is done, ucode
123
+ type-checks every file it changed — `tsc --noEmit` for TypeScript projects,
124
+ a syntax check for JavaScript and Python — and hands any errors back to fix,
125
+ up to three rounds.
126
+
127
+ **A plan you can see.** For longer jobs the model keeps a short checklist, shown
128
+ as one line: `plan 2/5 ✓ Scaffold · ✓ Upload · ▸ Score dial · ○ Findings · ○ Polish`.
129
+
130
+ **It knows the project before it asks.** Each turn starts with a map of every
131
+ file and the names each code file exports, so the model goes straight to the
132
+ right file instead of searching for it.
133
+
134
+ **Project memory.** `UCODE.md` in a project — and `~/.ucode/UCODE.md` for how you
135
+ like to work everywhere — is read at the start of every turn. `/remember <note>`
136
+ adds a line to it.
92
137
 
93
138
  **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.
139
+ when it fails it says *why*. It tolerates what does not matter tabs against
140
+ spaces, a different indent depth, Windows line endings and re-indents the
141
+ replacement to fit the file, but a match found twice is still refused.
142
+ `edit_files` changes several files in one call, and writes none of them if any
143
+ edit fails.
97
144
 
98
145
  **Diffs with real line numbers.** Removed lines are numbered where they were,
99
146
  added lines where they now are. Numbers you can jump to, not decoration.
@@ -128,10 +175,15 @@ when it is wanted, so the prompt stays the same size however many you add.
128
175
  | `debug` | finding the real cause instead of the first plausible one |
129
176
  | `code-review` | reviewing a change the way a careful colleague would |
130
177
  | `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
178
+ | `ai-features` | model-backed features: prompts with rules, validated JSON, images, failure handling |
179
+ | `security` | secrets, auth, ownership checks, injection, XSS, CSRF, SSRF, uploads |
180
+ | `performance` | measure first, find the real bottleneck, prove the win with numbers |
181
+ | `refactor` | change the shape of code without changing what it does |
182
+
183
+ **Every skill loads itself** when the request calls for it — an app pulls in
184
+ `ui-ux` and `build-app`, "it crashes" pulls in `debug`, an AI feature pulls in
185
+ `ai-features`, an API key pulls in `security` — so the whole skill is in
186
+ context before the model takes its first step. Waiting for the model to decide it needs design guidance means
135
187
  finding out it did not after the app is built.
136
188
 
137
189
  Add your own in `.ucode/skills/<name>/SKILL.md` inside a project. A project
@@ -156,6 +208,7 @@ Everything after the frontmatter is the instruction.
156
208
  | `/model` | show the models and switch — `/models` does the same |
157
209
  | `/resume` | pick up an earlier conversation — `/session`, `/sessions` too |
158
210
  | `/new` | save this one and start fresh |
211
+ | `/remember <note>` | add a standing note to this project's `UCODE.md` |
159
212
  | `/skills` | what it knows how to do, and what is loaded |
160
213
  | `/search <query>` | look something up on the web |
161
214
  | `/copy` | last reply to the clipboard |
@@ -183,11 +236,14 @@ ucode [options]
183
236
 
184
237
  | | |
185
238
  | --- | --- |
186
- | `~/.ucode/.env` | `OPENROUTER_API_KEY`, and `TAVILY_API_KEY` for web search |
239
+ | `~/.ucode/.env` | `UCODE_API_KEY`, and `TAVILY_API_KEY` for web search |
187
240
  | `~/.ucode/sessions/` | one JSON per conversation |
188
241
  | `.ucode/skills/` | skills belonging to a project |
242
+ | `UCODE.md` | project memory, read every turn |
243
+ | `~/.ucode/UCODE.md` | your own standing instructions, for every project |
189
244
 
190
- Environment overrides: `UCODE_MODEL`, `UCODE_MAX_CONTEXT_TOKENS`,
245
+ Environment overrides: `UCODE_MODEL`, `UCODE_WORKER_MODEL` (a faster model for
246
+ parallel workers), `UCODE_WORKER_STEPS`, `UCODE_MAX_CONTEXT_TOKENS`,
191
247
  `UCODE_MAX_STEPS`, `UCODE_MAX_TOOL_OUTPUT`, `UCODE_REQUEST_TIMEOUT_MS`,
192
248
  `UCODE_BASE_URL`.
193
249
 
@@ -199,10 +255,11 @@ one, ucode answers from what it knows and says that it could not check.
199
255
  ```
200
256
  ucode.js the command: arguments in, Agent out
201
257
  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
258
+ src/core/provider.js the only file that knows which provider answers
203
259
  src/core/history.js sessions on disk
204
260
  src/core/window.js folding a long conversation to fit
205
261
  src/core/skills.js loading skills, and deciding which load themselves
262
+ src/core/context.js the project map and project memory
206
263
  src/core/failure.js one error shape: what, why, what next
207
264
  src/tools/ the eleven tools, plus their shared plumbing
208
265
  src/ui/screen.js the full-screen interface
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "ucode-agent",
3
- "version": "1.1.0",
4
- "description": "ucode - a terminal coding agent that reads, edits and runs your code. NVIDIA and Cohere models over OpenRouter.",
3
+ "version": "1.3.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.
@@ -28,7 +28,8 @@ One line each:
28
28
  - **The file list** — the whole tree, before creating any of it.
29
29
 
30
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.
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`.
32
33
 
33
34
  ## 2. Scaffold without being asked questions
34
35
 
@@ -89,7 +90,7 @@ src/
89
90
  of the response status, and an error that says what failed — surfaced to the
90
91
  UI as a real message, never a silent `catch {}`.
91
92
 
92
- ### Calling an AI model (OpenRouter or any OpenAI-compatible API)
93
+ ### Calling an AI model (any OpenAI-compatible API)
93
94
 
94
95
  ```ts
95
96
  // src/app/api/analyze/route.ts — runs on the server only
@@ -0,0 +1,84 @@
1
+ ---
2
+ name: performance
3
+ description: Make software measurably faster — measure first, find the real bottleneck, fix it, and prove the improvement with numbers. Covers web vitals, bundles, React rendering, APIs, databases and Node.
4
+ auto: slow, slower, performance, perf, optimize, optimise, optimization, speed up, faster, lag, laggy, sluggish, bundle size, lighthouse, core web vitals, web vitals, lcp, cls, inp, memory leak, re-render, rerender, n+1, latency, takes too long
5
+ ---
6
+
7
+ # Performance
8
+
9
+ Guessing at performance is how people spend a day optimizing something that
10
+ was never slow. Measure, change one thing, measure again.
11
+
12
+ ## 1. Measure before touching anything
13
+
14
+ - **Define the slow thing precisely**: which page, action or endpoint, how slow
15
+ now, how fast it needs to be.
16
+ - **Get a baseline number** you can re-run:
17
+ - Web page: Lighthouse / PageSpeed (LCP, INP, CLS, total JS), the browser
18
+ Performance panel, `next build` output (per-route JS size).
19
+ - API: time the request (`curl -w "%{time_total}\n"`), log durations per step.
20
+ - Node/Python: a profiler (`node --cpu-prof`, `clinic`, `py-spy`), or timers
21
+ around the suspect code.
22
+ - Database: `EXPLAIN ANALYZE` on the slow query.
23
+ - Measure the production build, not dev mode — dev is deliberately slow.
24
+
25
+ ## 2. Find the actual bottleneck
26
+
27
+ It is almost always one of these, in roughly this order of likelihood:
28
+
29
+ 1. **Network waterfalls** — requests that wait on each other when they could run
30
+ in parallel; data fetched on the client that could be fetched on the server.
31
+ 2. **Too much JavaScript** — heavy dependencies, everything marked
32
+ `"use client"`, no code splitting.
33
+ 3. **Unoptimized images and fonts** — huge images, no dimensions (layout shift),
34
+ blocking font loads.
35
+ 4. **Database** — N+1 queries, missing indexes, fetching whole tables, no
36
+ pagination.
37
+ 5. **Rendering** — React re-rendering large trees on every keystroke, expensive
38
+ work inside render, long lists without virtualization.
39
+ 6. **Algorithmic** — nested loops over large data, repeated work that could be
40
+ cached or computed once.
41
+
42
+ ## 3. Fixes by area
43
+
44
+ **Web (Next.js / React)**
45
+ - Server components by default; `"use client"` at the leaves only.
46
+ - `next/image` with explicit sizes; `priority` on the LCP image; modern formats.
47
+ - `next/font` with `display: swap`, only the weights used.
48
+ - Dynamic `import()` for heavy, below-the-fold or rarely used components.
49
+ - Replace heavy libraries (moment → date-fns/Intl, lodash → native, big chart
50
+ libs → lighter ones) and check the per-route JS in the build output.
51
+ - Parallelize independent fetches with `Promise.all`; stream with `Suspense`.
52
+ - Cache: static where possible, `revalidate` for data that changes slowly.
53
+
54
+ **React rendering**
55
+ - Keep state as low in the tree as possible; lift only what must be shared.
56
+ - Stable props: memoize expensive values and callbacks passed to memoized
57
+ children — but only where the profiler shows a real cost.
58
+ - Virtualize lists over a few hundred rows.
59
+ - Debounce input-driven work (search, validation) at ~200–300ms.
60
+
61
+ **APIs and databases**
62
+ - Index columns used in `WHERE`, `JOIN` and `ORDER BY`; confirm with `EXPLAIN`.
63
+ - Batch or join instead of querying in a loop (N+1).
64
+ - Select only the columns needed; paginate everything user-sized.
65
+ - Cache expensive, repeatable results (in memory, Redis, HTTP caching) with a
66
+ clear invalidation rule.
67
+ - Move slow non-essential work (emails, analytics, thumbnails) to a background
68
+ job.
69
+
70
+ **Node**
71
+ - Never block the event loop with sync I/O or heavy CPU in a request handler.
72
+ - Stream large files instead of reading them whole.
73
+ - Reuse clients and connections (DB pools, HTTP keep-alive).
74
+
75
+ ## 4. Change one thing at a time, and prove it
76
+
77
+ After each change, re-run the same measurement. Keep changes that move the
78
+ number; revert ones that do not — complexity without a measured win is a cost.
79
+
80
+ ## 5. Report with numbers
81
+
82
+ Before and after for each metric that changed ("LCP 4.1s → 1.6s, route JS
83
+ 312 kB → 148 kB"), what caused it, and anything left that would need a bigger
84
+ change to fix.
@@ -0,0 +1,72 @@
1
+ ---
2
+ name: refactor
3
+ description: Restructure code without changing what it does — a safety net first, small verified steps, clear boundaries and names, and no behaviour change slipped in along the way.
4
+ auto: refactor, refactoring, clean up, cleanup, clean this, restructure, reorganize, reorganise, simplify, tech debt, technical debt, extract, split this file, split up, dead code, duplicate code, duplication, messy code, spaghetti
5
+ ---
6
+
7
+ # Refactoring
8
+
9
+ A refactor changes the shape of code and nothing else. The moment behaviour
10
+ changes, it is a rewrite, and a rewrite hidden inside a refactor is how
11
+ regressions ship with nobody noticing.
12
+
13
+ ## 1. Know why, and where it stops
14
+
15
+ - State the goal in one line: *split the 700-line page into components*,
16
+ *remove the duplicated fetch logic*, *make the scoring rules testable*.
17
+ - State the boundary: which files are in scope. Resist improving everything you
18
+ pass on the way — note it and leave it.
19
+
20
+ ## 2. Build the safety net first
21
+
22
+ - Run the existing tests and record the result. If there are none around the
23
+ code you are changing, **write characterization tests first**: tests that pin
24
+ down what the code does now, including its odd behaviour. You are preserving
25
+ behaviour, so you must be able to detect when it changes.
26
+ - For UI with no tests, capture the current behaviour: what renders in each
27
+ state, what each control does.
28
+
29
+ ## 3. Read it all before moving anything
30
+
31
+ Read every file in scope in full, and find every caller of what you will change
32
+ (`grep` for the names, the imports, the routes). A rename that misses one
33
+ dynamic reference is a runtime error waiting for the one path nobody tested.
34
+
35
+ ## 4. Small steps, each one green
36
+
37
+ Do one kind of change at a time, and run the tests after each:
38
+
39
+ - **Rename** to say what things are: `data` → `analysis`, `handle()` →
40
+ `submitLabel()`. Names are most of readability.
41
+ - **Extract** a function or component for each distinct job; a function should
42
+ do one thing at one level of abstraction.
43
+ - **Move** code next to what uses it: feature folders over type folders.
44
+ - **Remove duplication** only when the copies really are the same concept —
45
+ two similar-looking pieces that change for different reasons should stay two.
46
+ - **Delete dead code** — unused exports, unreachable branches, commented-out
47
+ blocks. Confirm it is unused with a search first.
48
+ - **Simplify conditionals**: early returns over nesting, lookup tables over long
49
+ `if/else` chains, named booleans over complex expressions.
50
+ - **Push side effects to the edges**: pure logic in the middle (easy to test),
51
+ I/O at the boundary.
52
+
53
+ Use `multi_edit` for several changes in one file, and keep each step small
54
+ enough that a failing test points straight at the cause.
55
+
56
+ ## 5. Keep behaviour identical
57
+
58
+ - Same inputs, same outputs, same errors, same side effects, same order of
59
+ side effects.
60
+ - Public APIs and stored formats unchanged, or every caller and every stored
61
+ record updated in the same change.
62
+ - If you find a bug while refactoring, **do not fix it silently inside the
63
+ refactor**. Finish the refactor, then fix the bug as its own change — or
64
+ report it — so each can be reviewed and reverted on its own.
65
+
66
+ ## 6. Finish
67
+
68
+ - All tests pass, the build passes, the linter and type checker are clean.
69
+ - The code is measurably simpler: fewer lines, fewer branches, smaller files,
70
+ clearer names — say which.
71
+ - Report what moved where, anything you noticed but deliberately left alone,
72
+ and any behaviour you had to pin down with new tests.
@@ -0,0 +1,110 @@
1
+ ---
2
+ name: security
3
+ description: Build and audit software so it is safe by default — secrets, authentication and authorization, input validation, injection, XSS, CSRF, SSRF, uploads, dependencies and headers, checked against how attacks actually happen.
4
+ auto: security, secure, vulnerability, vulnerabilities, auth, authentication, authorization, login, sign in, signup, sign up, password, passwords, jwt, oauth, session cookie, xss, csrf, ssrf, sql injection, injection, secrets, api key, api keys, owasp, harden, hardening, permissions, rate limit
5
+ ---
6
+
7
+ # Security
8
+
9
+ Assume every input is hostile and every secret will be looked for. Most real
10
+ breaches come from a short list of boring mistakes; this skill is that list,
11
+ with what to do about each.
12
+
13
+ ## 1. Secrets
14
+
15
+ - **Never in the browser.** Anything imported by client code ships to every
16
+ visitor — including a "temporary" hardcoded key. Keys live in server routes,
17
+ server actions or server-only modules (`import 'server-only'`).
18
+ - **Never in the repository.** Use `.env.local` / `.env` (gitignored) and commit
19
+ a `.env.example` with placeholders. If a key was ever committed or pasted
20
+ somewhere public, rotate it — deleting the line does not un-leak it.
21
+ - **Never in logs, errors or URLs.** Redact before logging; do not put tokens
22
+ in query strings.
23
+ - If the user insists on hardcoding a key for now, put it in one server-only
24
+ file, say exactly where it is, and recommend moving it to an env var.
25
+
26
+ ## 2. Authentication
27
+
28
+ - Use a proven library or provider (Auth.js/NextAuth, Clerk, Supabase Auth,
29
+ Lucia-style patterns) rather than hand-rolled sessions and hashing.
30
+ - Passwords: argon2id or bcrypt, never reversible, never logged. Rate-limit
31
+ login and reset endpoints. Same error message for "no such user" and "wrong
32
+ password".
33
+ - Sessions: `HttpOnly`, `Secure`, `SameSite=Lax` (or `Strict`) cookies. Rotate
34
+ the session on login. Expire idle sessions.
35
+ - JWTs: short expiry, verify signature and algorithm server-side, never trust
36
+ claims the client can edit.
37
+
38
+ ## 3. Authorization — the most common real hole
39
+
40
+ - **Check ownership on every request**, on the server, for every object:
41
+ `WHERE id = $1 AND user_id = $session.user`. Changing an ID in a URL or body
42
+ must never reveal someone else's data (IDOR).
43
+ - Deny by default. Every route states who may call it.
44
+ - Hiding a button is not authorization; the endpoint behind it must check too.
45
+
46
+ ## 4. Input validation and injection
47
+
48
+ - Validate every input at the boundary with a schema (zod, pydantic): type,
49
+ length, range, format. Reject, do not "clean".
50
+ - **SQL:** parameterized queries or an ORM only. Never string-build SQL with
51
+ user input.
52
+ - **Shell:** avoid it; if unavoidable, pass arguments as an array
53
+ (`execFile`, `spawn` without `shell: true`), never interpolate.
54
+ - **Paths:** resolve and verify the result stays inside the allowed directory;
55
+ reject `..` and absolute paths from users.
56
+ - **SSRF:** when fetching a user-supplied URL, allow-list hosts, block private
57
+ and link-local ranges (127.0.0.0/8, 10/8, 172.16/12, 192.168/16, 169.254/16,
58
+ ::1), and do not follow redirects blindly.
59
+
60
+ ## 5. Output: XSS
61
+
62
+ - Let the framework escape (React, templating engines). Treat
63
+ `dangerouslySetInnerHTML`, `innerHTML`, `v-html` and markdown-to-HTML as
64
+ red flags: sanitize with DOMPurify if you must render HTML.
65
+ - Never put user input into `href` without checking the scheme
66
+ (`javascript:` URLs), or into inline `<script>` or event handlers.
67
+ - Set a Content-Security-Policy where you can.
68
+
69
+ ## 6. Requests
70
+
71
+ - **CSRF:** SameSite cookies plus a CSRF token or origin check on state-changing
72
+ requests that use cookie auth. Next.js server actions check origin; custom
73
+ routes need it done.
74
+ - **CORS:** never `*` with credentials; allow-list the origins that need it.
75
+ - **Rate limiting** on login, signup, password reset, and any expensive or paid
76
+ endpoint (AI calls especially) — per IP and per user.
77
+
78
+ ## 7. File uploads
79
+
80
+ - Check type by content (magic bytes), not just the extension or MIME header.
81
+ - Enforce a size limit on the server, not only the client.
82
+ - Store outside the web root or in object storage with generated names; never
83
+ execute or serve uploads from the app's own origin as HTML.
84
+ - Strip metadata (EXIF location) from images when privacy matters.
85
+
86
+ ## 8. Errors, headers, dependencies
87
+
88
+ - Errors to users are generic; details go to server logs. No stack traces in
89
+ responses.
90
+ - Headers: `Strict-Transport-Security`, `X-Content-Type-Options: nosniff`,
91
+ `Referrer-Policy: strict-origin-when-cross-origin`, `frame-ancestors` via CSP.
92
+ - Dependencies: `npm audit` (or the ecosystem equivalent), remove unused
93
+ packages, pin versions with a lockfile, be wary of new packages with few
94
+ downloads or typo-like names.
95
+
96
+ ## 9. Auditing existing code
97
+
98
+ Search rather than read everything:
99
+
100
+ ```
101
+ grep for: api[_-]?key|secret|token|password (hardcoded secrets)
102
+ dangerouslySetInnerHTML|innerHTML|eval\(|new Function
103
+ exec\(|execSync|shell: true|child_process
104
+ \$\{.*\}.*(SELECT|INSERT|UPDATE|DELETE) (string-built SQL)
105
+ fetch\(.*req\.|axios\(.*req\. (user-controlled URLs)
106
+ ```
107
+
108
+ Then check every route for authentication and ownership checks. Report
109
+ findings ranked by severity with the file, the attack, and the fix — and fix
110
+ the critical ones first if asked to fix.