reviewer-harness 1.4.2 → 1.6.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 +9 -6
- package/bin/cli.js +33 -8
- package/lib/adapters/antigravity.js +15 -0
- package/lib/adapters/copilot.js +15 -0
- package/lib/adapters/freebuff.js +16 -0
- package/lib/adapters/index.js +12 -1
- package/lib/init.js +1 -1
- package/lib/skills.js +14 -0
- package/lib/sync.js +21 -4
- package/package.json +7 -4
- package/skills/api-security.md +37 -0
- package/skills/ux-edge-states.md +37 -0
package/README.md
CHANGED
|
@@ -47,20 +47,23 @@ The harness is **not the AI model** — it is what prepares everything the AI re
|
|
|
47
47
|
harness.config.json ← your master spec (ONE file)
|
|
48
48
|
│ validate() ← quality checks + secret scanning
|
|
49
49
|
▼
|
|
50
|
-
lib/adapters/*.js ← adapters: opencode, claude, gemini, cline
|
|
50
|
+
lib/adapters/*.js ← adapters: opencode, claude, gemini, cline,
|
|
51
|
+
freebuff, copilot, antigravity
|
|
51
52
|
│ renderInstructions()
|
|
52
53
|
▼
|
|
53
54
|
.opencode/instructions.md ← OpenCode
|
|
54
55
|
CLAUDE.md ← Claude Code
|
|
55
56
|
GEMINI.md ← Gemini CLI
|
|
56
57
|
.clinerules ← Cline
|
|
58
|
+
.agents/skills/<skill>/SKILL.md ← Freebuff, Antigravity (project skills)
|
|
59
|
+
.github/skills/<skill>/SKILL.md ← GitHub Copilot CLI (project skills)
|
|
57
60
|
```
|
|
58
61
|
|
|
59
62
|
Change the rules in **one** place (`harness.config.json`), and `sync` propagates them to every runtime.
|
|
60
63
|
|
|
61
64
|
## Quickstart
|
|
62
65
|
|
|
63
|
-
You can use reviewer-harness in two ways: **Modular Mode** (zero-install: add individual skills to any project, no setup) or **Full Harness Mode** (one master config synced across all
|
|
66
|
+
You can use reviewer-harness in two ways: **Modular Mode** (zero-install: add individual skills to any project, no setup) or **Full Harness Mode** (one master config synced across all 7 runtimes).
|
|
64
67
|
|
|
65
68
|
### 1. Modular Skills (zero-install via npx)
|
|
66
69
|
|
|
@@ -77,9 +80,9 @@ npx reviewer-harness skills add no-hallucination
|
|
|
77
80
|
|
|
78
81
|
> Prefer a global CLI? Install once with `npm install -g reviewer-harness` and run `reviewer-harness skills add <name>` anywhere.
|
|
79
82
|
|
|
80
|
-
### 2. Full Harness Synchronization (
|
|
83
|
+
### 2. Full Harness Synchronization (7 runtimes)
|
|
81
84
|
|
|
82
|
-
`init` creates your master config, `validate` checks it (including secret scanning), and `sync` generates the native instruction file for every runtime you enable: `.opencode/instructions.md`, `CLAUDE.md`, `GEMINI.md`, and `.clinerules`.
|
|
85
|
+
`init` creates your master config, `validate` checks it (including secret scanning), and `sync` generates the native instruction file for every runtime you enable: `.opencode/instructions.md`, `CLAUDE.md`, `GEMINI.md`, and `.clinerules` — plus Agent Skills bundles (`.agents/skills/<skill>/SKILL.md` for Freebuff and Antigravity, `.github/skills/<skill>/SKILL.md` for GitHub Copilot CLI), so your skills follow you even when you switch CLIs.
|
|
83
86
|
|
|
84
87
|
```bash
|
|
85
88
|
# Using npx (zero-install, recommended)
|
|
@@ -152,7 +155,7 @@ Guardrails that run before any file is written:
|
|
|
152
155
|
|
|
153
156
|
## This repo governs itself
|
|
154
157
|
|
|
155
|
-
reviewer-harness eats its own cooking: the instruction files at this repo's root — `.opencode/instructions.md`, `CLAUDE.md`, `GEMINI.md`, `.clinerules` — plus the agents and commands in `.opencode/` and `.claude/` are **generated by reviewer-harness itself** from its own `harness.config.json`. Open this repo in any AI coding tool and the tool follows the harness's own rules while working on the harness. To refresh them after editing the config, just run `node bin/cli.js sync` here.
|
|
158
|
+
reviewer-harness eats its own cooking: the instruction files at this repo's root — `.opencode/instructions.md`, `CLAUDE.md`, `GEMINI.md`, `.clinerules` — plus the agents and commands in `.opencode/` and `.claude/` and the skill bundles in `.agents/skills/` and `.github/skills/` are **generated by reviewer-harness itself** from its own `harness.config.json`. Open this repo in any AI coding tool and the tool follows the harness's own rules while working on the harness. To refresh them after editing the config, just run `node bin/cli.js sync` here.
|
|
156
159
|
|
|
157
160
|
## Local Memory (graph-style, Obsidian-ready)
|
|
158
161
|
|
|
@@ -238,7 +241,7 @@ Running from a source checkout instead? The same commands work as `node bin/cli.
|
|
|
238
241
|
- `lib/memory.js` — local memory: notes vault, graph builder, index generator
|
|
239
242
|
- `lib/skills.js` — skills system: bundled store, install/enable, section rendering
|
|
240
243
|
- `lib/agents.js` / `lib/commands.js` — one source → OpenCode + Claude formats
|
|
241
|
-
- `lib/adapters/` — registry + one adapter per runtime (opencode, claude, gemini, cline)
|
|
244
|
+
- `lib/adapters/` — registry + one adapter per runtime (opencode, claude, gemini, cline, freebuff, copilot, antigravity)
|
|
242
245
|
- `skills/` — bundled skills · `agents/` — bundled sub-agents · `commands/` — bundled slash commands
|
|
243
246
|
|
|
244
247
|
## How it was built (the lessons)
|
package/bin/cli.js
CHANGED
|
@@ -13,7 +13,7 @@ import { sync, writeSafe } from "../lib/sync.js";
|
|
|
13
13
|
import { initProject } from "../lib/init.js";
|
|
14
14
|
import { initMemory, buildIndex, readNotes, memoryDir } from "../lib/memory.js";
|
|
15
15
|
import { validateConfig } from "../lib/validate.js";
|
|
16
|
-
import { listBundledSkills, addSkill, removeSkill, loadBundledSkills, skillCommandContent } from "../lib/skills.js";
|
|
16
|
+
import { listBundledSkills, addSkill, removeSkill, loadBundledSkills, skillCommandContent, skillBundleContent } from "../lib/skills.js";
|
|
17
17
|
import { listBundledAgents, addAgent } from "../lib/agents.js";
|
|
18
18
|
import { listBundledCommands, addCommand } from "../lib/commands.js";
|
|
19
19
|
|
|
@@ -45,7 +45,7 @@ Usage (run inside your project):
|
|
|
45
45
|
reviewer-harness commands list List bundled slash commands
|
|
46
46
|
reviewer-harness commands add <name> Install a slash command (OpenCode + Claude)
|
|
47
47
|
|
|
48
|
-
reviewer-harness global [--dry-run] Install EVERY bundled skill
|
|
48
|
+
reviewer-harness global [--dry-run] Install EVERY bundled skill for ALL projects (OpenCode + Claude "/" menus; Freebuff, Copilot CLI, Antigravity skill bundles)
|
|
49
49
|
|
|
50
50
|
reviewer-harness memory init Start the local memory (memory/ folder)
|
|
51
51
|
reviewer-harness memory index Rebuild the memory INDEX.md + graph.json
|
|
@@ -62,7 +62,11 @@ function cmdStatus() {
|
|
|
62
62
|
console.log(`Runtimes:`);
|
|
63
63
|
for (const name of config.runtimes) {
|
|
64
64
|
const adapter = adapters[name];
|
|
65
|
-
|
|
65
|
+
const target = !adapter ? "(no adapter!)"
|
|
66
|
+
: typeof adapter.targetFile === "function" ? adapter.targetFile()
|
|
67
|
+
: typeof adapter.skillBundleDir === "function" ? `${adapter.skillBundleDir()}/<skill>/SKILL.md`
|
|
68
|
+
: "(unknown)";
|
|
69
|
+
console.log(` - ${name} → ${target}`);
|
|
66
70
|
}
|
|
67
71
|
console.log(`Skills : ${config.skills?.length ? config.skills.join(", ") : "(none)"}`);
|
|
68
72
|
}
|
|
@@ -82,6 +86,17 @@ function cmdSync() {
|
|
|
82
86
|
}
|
|
83
87
|
} else if (r.skipped) {
|
|
84
88
|
console.log(`- ${r.runtime}: SKIP — ${r.skipped}`);
|
|
89
|
+
} else if (r.skillBundles) {
|
|
90
|
+
const files = r.skillBundles;
|
|
91
|
+
const written = files.filter((f) => f.written).length;
|
|
92
|
+
const pending = files.filter((f) => f.dryRun && f.changed).length;
|
|
93
|
+
const unchanged = files.filter((f) => f.unchanged || (f.dryRun && !f.changed)).length;
|
|
94
|
+
const where = `${r.bundleDir}/<skill>/SKILL.md`;
|
|
95
|
+
if (dryRun) {
|
|
96
|
+
console.log(`- ${r.runtime} → ${where}: ${pending} would be written/updated, ${unchanged} unchanged (Agent Skills bundles)`);
|
|
97
|
+
} else {
|
|
98
|
+
console.log(`- ${r.runtime} → ${where}: ${written} written ✓, ${unchanged} unchanged`);
|
|
99
|
+
}
|
|
85
100
|
} else if (r.dryRun) {
|
|
86
101
|
console.log(`- ${r.runtime} → ${r.file}: ${r.changed ? "WOULD WRITE (changes pending)" : "no changes"}`);
|
|
87
102
|
} else if (r.unchanged) {
|
|
@@ -213,21 +228,31 @@ function cmdGlobal() {
|
|
|
213
228
|
const targets = [
|
|
214
229
|
{ runtime: "OpenCode", dir: join(homedir(), ".config", "opencode", "commands") },
|
|
215
230
|
{ runtime: "Claude", dir: join(homedir(), ".claude", "commands") },
|
|
231
|
+
// Skill-bundle runtimes (Agent Skills open standard: <skill>/SKILL.md)
|
|
232
|
+
{ runtime: "Freebuff", dir: join(homedir(), ".agents", "skills"), bundle: true },
|
|
233
|
+
{ runtime: "Copilot CLI", dir: join(homedir(), ".copilot", "skills"), bundle: true },
|
|
234
|
+
{ runtime: "Antigravity", dir: join(homedir(), ".gemini", "config", "skills"), bundle: true },
|
|
216
235
|
];
|
|
217
236
|
if (dryRun) console.log("(dry-run: nothing was written)\n");
|
|
218
|
-
for (const { runtime, dir } of targets) {
|
|
237
|
+
for (const { runtime, dir, bundle } of targets) {
|
|
219
238
|
let written = 0, unchanged = 0, pending = 0;
|
|
220
239
|
for (const skill of skills) {
|
|
221
|
-
const
|
|
240
|
+
const rel = bundle ? join(skill.name, "SKILL.md") : `${skill.name}.md`;
|
|
241
|
+
const content = bundle
|
|
242
|
+
? skillBundleContent(skill, skills)
|
|
243
|
+
: skillCommandContent(skill, skills);
|
|
244
|
+
const outcome = writeSafe(join(dir, rel), content, dryRun);
|
|
222
245
|
if (outcome.written) written++;
|
|
223
246
|
else if (outcome.dryRun && outcome.changed) pending++;
|
|
224
247
|
else unchanged++;
|
|
225
248
|
}
|
|
226
|
-
|
|
227
|
-
|
|
249
|
+
const where = bundle ? `${dir} (<skill>/SKILL.md)` : dir;
|
|
250
|
+
if (dryRun) console.log(`- ${runtime} (global): ${pending} would be written/updated, ${unchanged} unchanged → ${where}`);
|
|
251
|
+
else console.log(`- ${runtime} (global): ${written} written ✓, ${unchanged} unchanged → ${where}`);
|
|
228
252
|
}
|
|
229
253
|
console.log(
|
|
230
|
-
`\nDone: ${skills.map((s) => "/" + s.name).join(", ")} are now in
|
|
254
|
+
`\nDone: ${skills.map((s) => "/" + s.name).join(", ")} are now available in EVERY project.` +
|
|
255
|
+
`\n(OpenCode + Claude: "/" menu commands. Freebuff, Copilot CLI, Antigravity: Agent Skills bundles.)` +
|
|
231
256
|
`\n(Per-project rules still come from "sync" inside a project.)`
|
|
232
257
|
);
|
|
233
258
|
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
// lib/adapters/antigravity.js — ADAPTER for Google Antigravity.
|
|
2
|
+
//
|
|
3
|
+
// Antigravity follows the Agent Skills open standard (agentskills.io):
|
|
4
|
+
// workspace skills at `<workspace>/.agents/skills/<name>/SKILL.md`
|
|
5
|
+
// (with backward compatibility for `.agent/skills/`), and global
|
|
6
|
+
// skills at `~/.gemini/config/skills/<name>/SKILL.md`. This adapter
|
|
7
|
+
// covers the workspace side (used by `sync`); the global side is
|
|
8
|
+
// covered by the `global` command.
|
|
9
|
+
//
|
|
10
|
+
// Like Freebuff and Copilot CLI, Antigravity is skills-only here:
|
|
11
|
+
// it exports skillBundleDir() instead of targetFile()/renderInstructions().
|
|
12
|
+
|
|
13
|
+
export function skillBundleDir() {
|
|
14
|
+
return ".agents/skills";
|
|
15
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
// lib/adapters/copilot.js — ADAPTER for GitHub Copilot CLI.
|
|
2
|
+
//
|
|
3
|
+
// GitHub Copilot CLI reads Agent Skills (agentskills.io) from skill
|
|
4
|
+
// folders: project skills at `<project>/.github/skills/<name>/SKILL.md`
|
|
5
|
+
// (it also recognizes `.claude/skills/` and `.agents/skills/`), and
|
|
6
|
+
// personal skills at `~/.copilot/skills/<name>/SKILL.md` (or
|
|
7
|
+
// `~/.agents/skills/`). This adapter covers the project side (used by
|
|
8
|
+
// `sync`); the personal side is covered by the `global` command.
|
|
9
|
+
//
|
|
10
|
+
// Like Freebuff and Antigravity, Copilot CLI is skills-only here:
|
|
11
|
+
// it exports skillBundleDir() instead of targetFile()/renderInstructions().
|
|
12
|
+
|
|
13
|
+
export function skillBundleDir() {
|
|
14
|
+
return ".github/skills";
|
|
15
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
// lib/adapters/freebuff.js — ADAPTER for Freebuff (Codebuff platform).
|
|
2
|
+
//
|
|
3
|
+
// Freebuff follows the Agent Skills open standard (agentskills.io):
|
|
4
|
+
// skills are folders containing a SKILL.md with `name` + `description`
|
|
5
|
+
// frontmatter. Personal skills live at `~/.agents/skills/<name>/SKILL.md`;
|
|
6
|
+
// project skills live at `<project>/.agents/skills/<name>/SKILL.md`.
|
|
7
|
+
// This adapter covers the project side (used by `sync`); the personal
|
|
8
|
+
// side is covered by the `global` command.
|
|
9
|
+
//
|
|
10
|
+
// Freebuff has no single instruction file like CLAUDE.md, so this
|
|
11
|
+
// adapter is skills-only: it exports skillBundleDir() instead of
|
|
12
|
+
// targetFile()/renderInstructions().
|
|
13
|
+
|
|
14
|
+
export function skillBundleDir() {
|
|
15
|
+
return ".agents/skills";
|
|
16
|
+
}
|
package/lib/adapters/index.js
CHANGED
|
@@ -9,7 +9,18 @@ import * as opencode from "./opencode.js";
|
|
|
9
9
|
import * as claude from "./claude.js";
|
|
10
10
|
import * as gemini from "./gemini.js";
|
|
11
11
|
import * as cline from "./cline.js";
|
|
12
|
+
import * as freebuff from "./freebuff.js";
|
|
13
|
+
import * as copilot from "./copilot.js";
|
|
14
|
+
import * as antigravity from "./antigravity.js";
|
|
12
15
|
|
|
13
|
-
export const adapters = {
|
|
16
|
+
export const adapters = {
|
|
17
|
+
opencode,
|
|
18
|
+
claude,
|
|
19
|
+
gemini,
|
|
20
|
+
cline,
|
|
21
|
+
freebuff,
|
|
22
|
+
copilot,
|
|
23
|
+
antigravity,
|
|
24
|
+
};
|
|
14
25
|
|
|
15
26
|
export const supportedRuntimes = Object.keys(adapters);
|
package/lib/init.js
CHANGED
|
@@ -11,7 +11,7 @@ const TEMPLATE = {
|
|
|
11
11
|
version: "1.0.0",
|
|
12
12
|
problem:
|
|
13
13
|
"The problem — an AI with no guardrails: it sometimes breaks existing code it was never asked to touch, and hallucinates — inventing functions, libraries, or output that do not actually exist. These rules are the guardrails against that.",
|
|
14
|
-
runtimes: ["opencode", "claude", "gemini", "cline"],
|
|
14
|
+
runtimes: ["opencode", "claude", "gemini", "cline", "freebuff", "copilot", "antigravity"],
|
|
15
15
|
skills: [],
|
|
16
16
|
rules: [
|
|
17
17
|
"Edit surgically: change only the code the task requires — never rewrite a whole file.",
|
package/lib/skills.js
CHANGED
|
@@ -168,3 +168,17 @@ export function skillCommandContent(skill, allSkills = []) {
|
|
|
168
168
|
content += `\nApply this playbook to the following: $ARGUMENTS\n`;
|
|
169
169
|
return content;
|
|
170
170
|
}
|
|
171
|
+
|
|
172
|
+
// Skill BUNDLE content — the Agent Skills open standard (agentskills.io):
|
|
173
|
+
// one folder per skill, containing a SKILL.md with `name` + `description`
|
|
174
|
+
// frontmatter. Freebuff, GitHub Copilot CLI, and Antigravity all read
|
|
175
|
+
// this format. The body is identical to the slash-command version
|
|
176
|
+
// (companions included) so every runtime enforces the same disciplines;
|
|
177
|
+
// only the frontmatter gains the required `name` field.
|
|
178
|
+
export function skillBundleContent(skill, allSkills = []) {
|
|
179
|
+
const command = skillCommandContent(skill, allSkills);
|
|
180
|
+
return command.replace(
|
|
181
|
+
`---\ndescription: `,
|
|
182
|
+
`---\nname: ${skill.name}\ndescription: `
|
|
183
|
+
);
|
|
184
|
+
}
|
package/lib/sync.js
CHANGED
|
@@ -27,7 +27,7 @@ import { join, dirname } from "node:path";
|
|
|
27
27
|
import { loadConfig } from "./config.js";
|
|
28
28
|
import { adapters, supportedRuntimes } from "./adapters/index.js";
|
|
29
29
|
import { validateConfig } from "./validate.js";
|
|
30
|
-
import { loadSkills, skillCommandContent } from "./skills.js";
|
|
30
|
+
import { loadSkills, skillCommandContent, skillBundleContent } from "./skills.js";
|
|
31
31
|
|
|
32
32
|
// Write one file the safe way. Returns what happened:
|
|
33
33
|
// dry-run → { dryRun: true, changed } · no change → { unchanged: true }
|
|
@@ -86,9 +86,26 @@ export function sync({ targetDir, dryRun = false }) {
|
|
|
86
86
|
continue;
|
|
87
87
|
}
|
|
88
88
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
89
|
+
// Instruction-file runtimes (OpenCode, Claude, Gemini, Cline):
|
|
90
|
+
// one rendered rules file per runtime.
|
|
91
|
+
if (typeof adapter.targetFile === "function") {
|
|
92
|
+
const rel = adapter.targetFile();
|
|
93
|
+
const outcome = writeSafe(join(targetDir, rel), adapter.renderInstructions(config), dryRun);
|
|
94
|
+
results.push({ runtime: name, file: rel, ...outcome });
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Skill-bundle runtimes (Freebuff, Copilot CLI, Antigravity):
|
|
98
|
+
// one <skill>/SKILL.md per enabled skill, following the Agent
|
|
99
|
+
// Skills open standard (agentskills.io).
|
|
100
|
+
if (typeof adapter.skillBundleDir === "function") {
|
|
101
|
+
const files = [];
|
|
102
|
+
for (const skill of skills) {
|
|
103
|
+
const rel = `${adapter.skillBundleDir()}/${skill.name}/SKILL.md`;
|
|
104
|
+
const content = skillBundleContent(skill, skills);
|
|
105
|
+
files.push({ file: rel, ...writeSafe(join(targetDir, rel), content, dryRun) });
|
|
106
|
+
}
|
|
107
|
+
results.push({ runtime: name, bundleDir: adapter.skillBundleDir(), skillBundles: files });
|
|
108
|
+
}
|
|
92
109
|
}
|
|
93
110
|
|
|
94
111
|
// SLASH COMMANDS: one per enabled skill, for the two runtimes whose
|
package/package.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"bin": {
|
|
4
4
|
"reviewer-harness": "./bin/cli.js"
|
|
5
5
|
},
|
|
6
|
-
"description": "Your own AI harness \u2014 one master config that syncs guardrail rules, skills, and a local memory to Claude, OpenCode, Gemini, and
|
|
6
|
+
"description": "Your own AI harness \u2014 one master config that syncs guardrail rules, skills, and a local memory to Claude, OpenCode, Gemini, Cline, Freebuff, Copilot CLI, and Antigravity. No API key, no dependencies.",
|
|
7
7
|
"engines": {
|
|
8
8
|
"node": ">=18"
|
|
9
9
|
},
|
|
@@ -14,7 +14,10 @@
|
|
|
14
14
|
"opencode",
|
|
15
15
|
"claude-code",
|
|
16
16
|
"gemini",
|
|
17
|
-
"cline"
|
|
17
|
+
"cline",
|
|
18
|
+
"freebuff",
|
|
19
|
+
"copilot-cli",
|
|
20
|
+
"antigravity"
|
|
18
21
|
],
|
|
19
22
|
"license": "MIT",
|
|
20
23
|
"name": "reviewer-harness",
|
|
@@ -23,7 +26,7 @@
|
|
|
23
26
|
"url": "https://github.com/xcell321/reviewer-harness.git"
|
|
24
27
|
},
|
|
25
28
|
"type": "module",
|
|
26
|
-
"version": "1.
|
|
29
|
+
"version": "1.6.0",
|
|
27
30
|
"files": [
|
|
28
31
|
"bin",
|
|
29
32
|
"lib",
|
|
@@ -31,4 +34,4 @@
|
|
|
31
34
|
"agents",
|
|
32
35
|
"commands"
|
|
33
36
|
]
|
|
34
|
-
}
|
|
37
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: api-security
|
|
3
|
+
description: 12 API security rules — apply to every backend endpoint you build or review. No anonymous backdoors, no leaked secrets.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
## Skill: api-security
|
|
7
|
+
|
|
8
|
+
When to use: building or reviewing any API — REST, GraphQL, or internal endpoints.
|
|
9
|
+
|
|
10
|
+
Apply these 12 rules to every endpoint. If a rule doesn't apply, say why.
|
|
11
|
+
|
|
12
|
+
### Authentication & Access
|
|
13
|
+
1. **Authenticate every call.** No anonymous backdoors. Every endpoint requires a valid credential — no "internal" or "debug" routes left open.
|
|
14
|
+
2. **Check object access.** Verify the caller owns the resource: *your* ID, not anyone's ID. Never trust an ID from the request without checking ownership.
|
|
15
|
+
3. **Use least-privilege keys.** Read-only keys where possible. Separate keys per environment. Never reuse an admin key for a client.
|
|
16
|
+
|
|
17
|
+
### Input & Transport
|
|
18
|
+
4. **Validate input.** Reject what you didn't expect. Whitelist shapes with a schema (e.g. Zod); never trust raw request bodies, query params, or headers.
|
|
19
|
+
5. **HTTPS only.** No plain-text tokens, ever. Redirect HTTP to HTTPS. Set Secure cookies.
|
|
20
|
+
6. **No secrets in URLs.** URLs end up in logs, browser history, and referrer headers. Put tokens in headers, never in query strings or paths.
|
|
21
|
+
|
|
22
|
+
### Tokens & Sessions
|
|
23
|
+
7. **Short-lived tokens.** Leaks expire fast. Access tokens in minutes, refresh tokens in days — never permanent. Rotate on sensitive actions.
|
|
24
|
+
8. **Rate limit.** Slow down brute force. Throttle auth endpoints aggressively (e.g. 5 attempts/minute); throttle the rest by reasonable use.
|
|
25
|
+
|
|
26
|
+
### Observability & Hygiene
|
|
27
|
+
9. **Log auth failures.** Spot attacks early. Log failed logins, invalid tokens, and permission denials with IP and timestamp — but never log the secret itself.
|
|
28
|
+
10. **Version your API.** Retire old weak endpoints. Keep `/v1`, `/v2` explicit; deprecate with a sunset date, don't silently change behavior.
|
|
29
|
+
11. **Limit response data.** Send only what's needed. Don't return full user objects, internal IDs, or stack traces to clients. Filter fields per role.
|
|
30
|
+
12. **Inventory endpoints.** You can't guard what you forgot. Keep a list of every route; audit it when adding features. Remove dead endpoints.
|
|
31
|
+
|
|
32
|
+
### Quick check before shipping
|
|
33
|
+
- [ ] Every route has auth (or a documented reason it doesn't)
|
|
34
|
+
- [ ] IDs in requests are ownership-checked
|
|
35
|
+
- [ ] Inputs are schema-validated
|
|
36
|
+
- [ ] No secrets in URLs or logs
|
|
37
|
+
- [ ] Auth endpoints are rate-limited
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: ux-edge-states
|
|
3
|
+
description: 10 UX states beyond the happy path — design every screen for empty, loading, error, offline, and success. The real test is what the UI does when nothing goes right.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
## Skill: ux-edge-states
|
|
7
|
+
|
|
8
|
+
When to use: building or reviewing any screen, page, or component.
|
|
9
|
+
|
|
10
|
+
Your product works when everything goes right. The real test is what it does when nothing does. Run every screen through these 10 states before calling it done.
|
|
11
|
+
|
|
12
|
+
### States to design
|
|
13
|
+
1. **Empty state.** What does the screen say before there is any data? Never show a blank page. Explain what's missing and give a clear next action ("No sessions yet — create one").
|
|
14
|
+
2. **Loading state.** Make the wait feel short. Show a skeleton or spinner immediately; never freeze the UI with no feedback. For long waits, show progress.
|
|
15
|
+
3. **Error state.** Say what went wrong *and how to fix it*. "Something went wrong" is not enough. Include the cause and the recovery step ("Connection failed — check the backend is running, then Retry").
|
|
16
|
+
4. **No internet.** Never leave the user staring at a blank screen. Detect offline, show a clear banner, and queue actions for retry when back online.
|
|
17
|
+
5. **Slow network.** Show progress, not a frozen spinner. Use timeouts with friendly messages; allow cancel. Distinguish "loading" from "stuck".
|
|
18
|
+
6. **No search results.** Suggest something instead of a dead end. Show "No results for X" plus alternatives: clear filters, related items, or a create action.
|
|
19
|
+
7. **Permission denied.** Explain why and give a way back. Don't just hide the button — tell the user what permission is missing and how to get it (or who to ask).
|
|
20
|
+
8. **Session expired.** Save their work before logging out. Warn before expiry when possible; preserve form input and drafts across re-auth.
|
|
21
|
+
9. **Form validation.** Flag the mistake the moment it happens, not after submit. Inline errors next to the field; explain the rule ("Password needs 8+ characters").
|
|
22
|
+
10. **Success state.** Confirm it, celebrate it, then show what is next. A toast or checkmark plus the logical next action ("Session created — add your first terminal").
|
|
23
|
+
|
|
24
|
+
### Quick check before shipping
|
|
25
|
+
- [ ] Empty: screen explains what's missing + next action
|
|
26
|
+
- [ ] Loading: immediate feedback, no frozen UI
|
|
27
|
+
- [ ] Error: cause + recovery step, not just "failed"
|
|
28
|
+
- [ ] Offline/slow: detected and communicated
|
|
29
|
+
- [ ] Validation: inline, immediate, explanatory
|
|
30
|
+
- [ ] Success: confirmed + next step shown
|
|
31
|
+
|
|
32
|
+
### Anti-patterns
|
|
33
|
+
- Blank screens with no explanation.
|
|
34
|
+
- Generic "Something went wrong" with no recovery path.
|
|
35
|
+
- Spinners that never resolve and can't be cancelled.
|
|
36
|
+
- Losing user input on error, expiry, or navigation.
|
|
37
|
+
- Dead-end "no results" with no suggestions.
|