skilldrop-cli 0.8.0 → 0.8.2
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 +22 -2
- package/bin/skilldrop.js +119 -21
- package/model-routing.json +1 -0
- package/package.json +5 -1
- package/packs.json +3 -1
- package/skills/agents-md-generator/SKILL.md +80 -0
- package/skills/agents-md-generator/evals/eval_queries.json +15 -0
- package/skills/agents-md-generator/evals/evals.json +50 -0
- package/skills/agents-md-generator/manifest.json +11 -0
- package/skills/agents-md-generator/reference.md +89 -0
- package/skills/agents-md-generator/templates/agents-md.md +48 -0
- package/skills/business-case/examples/build-vs-buy-flags.md +42 -0
- package/skills/council-review/SKILL.md +1 -1
- package/skills/devils-advocate/examples/rate-limiter-review.md +46 -0
- package/skills/doc-critique/examples/design-doc-critique.md +34 -0
- package/skills/strategy-analysis/examples/market-entry-tows.md +42 -0
- package/skills/tech-comparison-matrix/examples/queue-selection.md +41 -0
package/README.md
CHANGED
|
@@ -3,9 +3,9 @@
|
|
|
3
3
|
[](https://www.npmjs.com/package/skilldrop-cli)
|
|
4
4
|
[](LICENSE)
|
|
5
5
|
|
|
6
|
-
**[Browse the catalogue →](https://sananthanarayan.github.io/skilldrop/)** — all
|
|
6
|
+
**[Browse the catalogue →](https://sananthanarayan.github.io/skilldrop/)** — all 50 skills, filterable by pack, tag, and model tier.
|
|
7
7
|
|
|
8
|
-
**
|
|
8
|
+
**50 portable AI-agent skills for the deliverables knowledge workers actually ship** — diagrams, design docs, ADRs, PRDs, runbooks, decks, decision logs, threat models, adversarial reviews — installable into **Claude Code**, **Cursor**, **Kiro**, **Codex**, **GitHub Copilot**, and any AI tool that accepts custom instructions, one skill or one role-based pack at a time:
|
|
9
9
|
|
|
10
10
|
```bash
|
|
11
11
|
npx skilldrop-cli install --pack product-manager # or: solution-architect, dev-team,
|
|
@@ -151,6 +151,8 @@ Designing the agentic systems themselves — the loops, orchestrations, and budg
|
|
|
151
151
|
| [`agent-budget`](skills/agent-budget/SKILL.md) | The spend spec for an agentic workflow: cheapest-adequate **tier per stage** (light/standard/heavy — heavy verification is never downgraded to save money), three numbers per stage (expected, hard cap, on-cap action — "warn and continue" banned), a run-level cap *below* the sum of stage caps, a **degradation ladder where verification is last and never cut**, and **cost-per-outcome** as the governing metric with a comparison line against the manual alternative — so "$900/week" becomes answerable. Estimates tagged `[assumption]` until calibrated on real runs. |
|
|
152
152
|
| [`agent-threat-model`](skills/agent-threat-model/SKILL.md) | Security review of an agent's **capability surface** against the lethal trifecta — private data, untrusted content, an exfiltration vector. Inventories data reach *transitively* (the filesystem tool reaches every secret in `.env`), applies a writer-authorization test that classifies almost every content source untrusted, and sweeps egress past the obvious network tool (rendered markdown images, CI logs, error text, DNS). Scores **paths, not tools** — `fetch` is fine alone and lethal beside a secrets file — then names the architectural fix that breaks a leg, with a verification step. Structurally refuses the two answers teams reach for first: a system prompt telling the model to ignore injected instructions is not a control, and an injection classifier is defense in depth, never a broken leg. |
|
|
153
153
|
|
|
154
|
+
| [`agents-md-generator`](skills/agents-md-generator/SKILL.md) | Generate or audit the `AGENTS.md` that Claude Code, Codex, Cursor, Copilot, Kiro and Antigravity all read before your prompt. Built around one rule: **every command traces to evidence in the repo** — a CI step, a Makefile target, a `package.json` script — or it appears as `[missing: no test command in CI or manifests]` rather than a guess, because an invented command is a fault the agent will trust over the filesystem. Convention lines must pass a **counterfactual test** (without this line, would a competent agent do the wrong thing?); virtue instructions — "clean", "best practice", "thorough" — are *stripped, not softened*, since they spend context and change no behaviour. Names forbidden actions explicitly, the section most files omit. Hard ~150-line cap when generating, a warning when auditing, because an existing file may be long for good reason. Audit mode emits a diff, never a silent rewrite. Satellites (`CLAUDE.md`, `.github/copilot-instructions.md`) are opt-in and **delta-only** — a satellite that repeats `AGENTS.md` is guaranteed drift. |
|
|
155
|
+
|
|
154
156
|
### AI adoption & observability
|
|
155
157
|
|
|
156
158
|
| Skill | What it does |
|
|
@@ -262,6 +264,17 @@ npx skilldrop-cli list | skilldrop info <skill> | skilldrop packs | skilldrop un
|
|
|
262
264
|
|
|
263
265
|
`--with-related` also pulls each skill's companions. From a clone (or before the package is published): `node bin/skilldrop.js <same args>`. Scope and design: [RFC-0002](docs/rfcs/0002-skilldrop-cli.md), full command surface in [`docs/designs/skilldrop-cli-design.md`](docs/designs/skilldrop-cli-design.md).
|
|
264
266
|
|
|
267
|
+
### Or: the Claude Code plugin marketplace
|
|
268
|
+
|
|
269
|
+
skilldrop is also a **Claude Code plugin marketplace** — install the whole catalogue as one plugin, no npm step:
|
|
270
|
+
|
|
271
|
+
```text
|
|
272
|
+
/plugin marketplace add sananthanarayan/skilldrop
|
|
273
|
+
/plugin install skilldrop@skilldrop
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
Every skill then invokes as `/skilldrop:<name>` (e.g. `/skilldrop:prfaq`), and the reviewer subagents come with it. The marketplace ships the flat `skills/` and `agents/` trees at the repo root unchanged — same copy-install premise, expressed in Claude's own plugin format. `.claude-plugin/{marketplace,plugin}.json` are generated from `package.json` by [`build_marketplace.py`](build_marketplace.py) (`--check` guards drift in CI). Use the CLI above when you want per-pack or per-skill granularity, another IDE, or hooks; use the marketplace when you're in Claude Code and want everything in two lines. Rationale and the deferred per-pack-plugin step: [RFC-0014](docs/rfcs/0014-agentbundle-interop.md).
|
|
277
|
+
|
|
265
278
|
### Hooks (opt-in) — wire a skill to an event
|
|
266
279
|
|
|
267
280
|
Some loop-shaped skills declare **hooks** — event-triggered nudges the CLI wires into your environment when you pass `--with-hooks` ([RFC-0006](docs/rfcs/0006-per-ide-hooks.md)). It's off by default, so a plain install never touches your git repo or editor settings.
|
|
@@ -313,6 +326,13 @@ npx skilldrop-cli install --pack starter --from ../local-catalog
|
|
|
313
326
|
npx skilldrop-cli update # updates bundled and third-party skills side by side — the ledger remembers each skill's source
|
|
314
327
|
```
|
|
315
328
|
|
|
329
|
+
The CLI also reads **agentbundle-shaped catalogs** ([agent-ready-repo](https://github.com/eugenelim/agent-ready-repo)) — `packs/<pack>/.apm/skills/<name>/SKILL.md` with a `pack.toml` per pack — so you can install *its* packs through the same command ([RFC-0014](docs/rfcs/0014-agentbundle-interop.md)). Both shapes share the agentskills.io `SKILL.md`, so the reader just maps his packs onto the accessors above:
|
|
330
|
+
|
|
331
|
+
```bash
|
|
332
|
+
npx skilldrop-cli packs --from https://github.com/eugenelim/agent-ready-repo
|
|
333
|
+
npx skilldrop-cli install --pack contracts --from https://github.com/eugenelim/agent-ready-repo --dest .agents/skills
|
|
334
|
+
```
|
|
335
|
+
|
|
316
336
|
Safety model: installs **copy files only — nothing from a catalog is ever executed**; every skill passes a structural check before copying (broken folders are refused with reasons); and third-party installs print a review-before-use warning, because skills are instructions your AI agent will follow — read a stranger's `SKILL.md` before letting your agent obey it.
|
|
317
337
|
|
|
318
338
|
**Authoring a catalog:** mirror the layout above, then check it with `npx skilldrop-cli validate --from <your-repo-or-path>` before publishing. `related`, `packs.json`, and `requirements.txt` all work in third-party catalogs exactly as they do here.
|
package/bin/skilldrop.js
CHANGED
|
@@ -3,8 +3,10 @@
|
|
|
3
3
|
* or any directory, from the bundled catalog or any third-party catalog
|
|
4
4
|
* (--from <path | git-url[#ref]>). Zero dependencies; copy-only, never executes
|
|
5
5
|
* catalog content at install time.
|
|
6
|
+
* Reads two catalog shapes: skilldrop (flat skills/) and agentbundle (packs/<p>/.apm/skills).
|
|
6
7
|
* Design: skilldrop-cli-design/skilldrop-cli-design.md
|
|
7
|
-
* Scope: docs/rfcs/0002-skilldrop-cli.md, docs/rfcs/0003-third-party-catalogs.md
|
|
8
|
+
* Scope: docs/rfcs/0002-skilldrop-cli.md, docs/rfcs/0003-third-party-catalogs.md,
|
|
9
|
+
* docs/rfcs/0014-agentbundle-interop.md (the agentbundle reader)
|
|
8
10
|
*/
|
|
9
11
|
"use strict";
|
|
10
12
|
const fs = require("fs");
|
|
@@ -37,8 +39,9 @@ Usage:
|
|
|
37
39
|
|
|
38
40
|
Catalogs:
|
|
39
41
|
(default) the catalog bundled with this package
|
|
40
|
-
--from <dir>
|
|
41
|
-
|
|
42
|
+
--from <dir|url> a skilldrop catalog (skills/<name>/{SKILL.md,manifest.json}) OR an
|
|
43
|
+
agentbundle one (packs/<pack>/.apm/skills/<name>/SKILL.md); a git URL
|
|
44
|
+
works for either — append #<branch-or-tag> to pin
|
|
42
45
|
|
|
43
46
|
Install/update/uninstall targets (pick one):
|
|
44
47
|
(default) ~/.claude/skills Claude Code, user scope
|
|
@@ -78,7 +81,16 @@ function parseArgs(argv) {
|
|
|
78
81
|
return out;
|
|
79
82
|
}
|
|
80
83
|
|
|
81
|
-
/* ---------- catalogs ----------
|
|
84
|
+
/* ---------- catalogs ----------
|
|
85
|
+
Two on-disk shapes are read (RFC-0003, RFC-0014):
|
|
86
|
+
"skilldrop" — flat skills/<name>/{SKILL.md,manifest.json} + optional packs.json
|
|
87
|
+
"apm" — agentbundle (agent-ready-repo): packs/<pack>/{pack.toml,
|
|
88
|
+
.apm/skills/<name>/SKILL.md, .apm/agents/<name>.md}
|
|
89
|
+
The apm reader normalizes into the same accessors the native path uses — skills keyed by
|
|
90
|
+
folder name, a per-skill manifest synthesized from the SKILL.md frontmatter + the pack.toml
|
|
91
|
+
version, each pack.toml's [pack] table a virtual pack — so `--from git+https://…/agent-ready-repo`
|
|
92
|
+
installs his packs through this same CLI. Skill accessors: skillsIn / skillDir / skillExists /
|
|
93
|
+
manifestOf; never touch cat.skillsDir directly (it exists only on the skilldrop shape). */
|
|
82
94
|
|
|
83
95
|
const catalogCache = {};
|
|
84
96
|
function resolveCatalog(source) {
|
|
@@ -96,23 +108,101 @@ function resolveCatalog(source) {
|
|
|
96
108
|
die(`could not fetch catalog '${source}' — not a local path, and git clone failed`);
|
|
97
109
|
}
|
|
98
110
|
}
|
|
99
|
-
const
|
|
100
|
-
if (
|
|
101
|
-
|
|
111
|
+
const src = source || BUNDLED;
|
|
112
|
+
if (fs.existsSync(path.join(dir, "skills")))
|
|
113
|
+
return (catalogCache[key] = { dir, source: src, shape: "skilldrop", skillsDir: path.join(dir, "skills") });
|
|
114
|
+
if (fs.existsSync(path.join(dir, "packs")))
|
|
115
|
+
return (catalogCache[key] = readApmCatalog(dir, src));
|
|
116
|
+
die(`'${src}' is not a catalog — expected a skills/ (skilldrop) or packs/ (agentbundle) directory`);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/* Minimal TOML read — the [pack] table's basic-string scalars (name, version, description).
|
|
120
|
+
pack.toml is small and regular; a full TOML parser would be dead weight in a zero-dep CLI. */
|
|
121
|
+
function packMeta(tomlText) {
|
|
122
|
+
const out = {};
|
|
123
|
+
let inPack = false;
|
|
124
|
+
for (const raw of tomlText.split(/\r?\n/)) {
|
|
125
|
+
const t = raw.trim();
|
|
126
|
+
if (t.startsWith("[")) { inPack = t === "[pack]"; continue; }
|
|
127
|
+
if (!inPack || !t || t.startsWith("#")) continue;
|
|
128
|
+
const m = t.match(/^([A-Za-z0-9_.-]+)\s*=\s*"((?:[^"\\]|\\.)*)"/);
|
|
129
|
+
if (m) out[m[1]] = m[2].replace(/\\"/g, '"').replace(/\\\\/g, "\\");
|
|
130
|
+
}
|
|
131
|
+
return out;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/* SKILL.md frontmatter (agentskills.io: name optional, description carries the trigger). */
|
|
135
|
+
function frontmatter(mdPath) {
|
|
136
|
+
let fm = "";
|
|
137
|
+
try { fm = fs.readFileSync(mdPath, "utf8").split("---")[1] || ""; } catch (e) { return {}; }
|
|
138
|
+
const get = (k) => { const m = fm.match(new RegExp("^" + k + ":\\s*(.+)$", "m")); return m ? m[1].trim() : ""; };
|
|
139
|
+
return { name: get("name"), description: get("description") };
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/* Read an agentbundle catalog into the native accessor shape. First-wins on a skill name
|
|
143
|
+
(skilldrop's own generated catalogue duplicates a multi-pack skill into each pack — the
|
|
144
|
+
flat skill list dedups, while packsData still lists it under every pack it belongs to). */
|
|
145
|
+
function readApmCatalog(dir, source) {
|
|
146
|
+
const packsDir = path.join(dir, "packs");
|
|
147
|
+
const skillIndex = {}, agentIndex = {}, packsData = {};
|
|
148
|
+
for (const pack of fs.readdirSync(packsDir)) {
|
|
149
|
+
const pdir = path.join(packsDir, pack);
|
|
150
|
+
if (!fs.statSync(pdir).isDirectory()) continue;
|
|
151
|
+
const ptoml = path.join(pdir, "pack.toml");
|
|
152
|
+
const pm = fs.existsSync(ptoml) ? packMeta(fs.readFileSync(ptoml, "utf8")) : {};
|
|
153
|
+
const version = pm.version || "0.0.0";
|
|
154
|
+
const skills = [];
|
|
155
|
+
const skillsRoot = path.join(pdir, ".apm", "skills");
|
|
156
|
+
if (fs.existsSync(skillsRoot))
|
|
157
|
+
for (const s of fs.readdirSync(skillsRoot)) {
|
|
158
|
+
const sdir = path.join(skillsRoot, s);
|
|
159
|
+
if (!fs.statSync(sdir).isDirectory()) continue;
|
|
160
|
+
skills.push(s);
|
|
161
|
+
if (!skillIndex[s])
|
|
162
|
+
skillIndex[s] = { dir: sdir, manifest: {
|
|
163
|
+
name: s, description: frontmatter(path.join(sdir, "SKILL.md")).description || "",
|
|
164
|
+
version, related: [], tags: [],
|
|
165
|
+
deps: { pip: [], npm: [] }, env: { required: [], optional: [] },
|
|
166
|
+
} };
|
|
167
|
+
}
|
|
168
|
+
const agentsRoot = path.join(pdir, ".apm", "agents");
|
|
169
|
+
if (fs.existsSync(agentsRoot))
|
|
170
|
+
for (const f of fs.readdirSync(agentsRoot))
|
|
171
|
+
if (f.endsWith(".md") && f !== "README.md") agentIndex[f.slice(0, -3)] = path.join(agentsRoot, f);
|
|
172
|
+
packsData[pack] = { description: pm.description || "", skills };
|
|
173
|
+
}
|
|
174
|
+
return { dir, source, shape: "apm", skillIndex, agentIndex, packsData };
|
|
102
175
|
}
|
|
176
|
+
|
|
103
177
|
function skillsIn(cat) {
|
|
178
|
+
if (cat.shape === "apm") return Object.keys(cat.skillIndex).sort();
|
|
104
179
|
return fs.readdirSync(cat.skillsDir).filter((d) => fs.statSync(path.join(cat.skillsDir, d)).isDirectory()).sort();
|
|
105
180
|
}
|
|
106
|
-
function
|
|
181
|
+
function skillDir(cat, s) {
|
|
182
|
+
if (cat.shape === "apm") return cat.skillIndex[s] && cat.skillIndex[s].dir;
|
|
183
|
+
return path.join(cat.skillsDir, s);
|
|
184
|
+
}
|
|
185
|
+
function skillExists(cat, s) { const d = skillDir(cat, s); return !!d && fs.existsSync(d); }
|
|
186
|
+
function manifestOf(cat, s) {
|
|
187
|
+
if (cat.shape === "apm") {
|
|
188
|
+
if (!cat.skillIndex[s]) throw new Error(`no such skill '${s}'`);
|
|
189
|
+
return cat.skillIndex[s].manifest;
|
|
190
|
+
}
|
|
191
|
+
return readJSON(path.join(cat.skillsDir, s, "manifest.json"));
|
|
192
|
+
}
|
|
107
193
|
|
|
108
194
|
/* Agents (RFC-0012): single markdown files, frontmatter is already Claude Code's format.
|
|
109
|
-
Optional in a catalog — a third-party catalog with no agents
|
|
195
|
+
Optional in a catalog — a third-party catalog with no agents is still valid. */
|
|
110
196
|
function agentsIn(cat) {
|
|
197
|
+
if (cat.shape === "apm") return Object.keys(cat.agentIndex).sort();
|
|
111
198
|
const d = path.join(cat.dir, "agents");
|
|
112
199
|
if (!fs.existsSync(d)) return [];
|
|
113
200
|
return fs.readdirSync(d).filter((f) => f.endsWith(".md") && f !== "README.md").map((f) => f.slice(0, -3)).sort();
|
|
114
201
|
}
|
|
115
|
-
function agentPath(cat, a) {
|
|
202
|
+
function agentPath(cat, a) {
|
|
203
|
+
if (cat.shape === "apm") return cat.agentIndex[a];
|
|
204
|
+
return path.join(cat.dir, "agents", `${a}.md`);
|
|
205
|
+
}
|
|
116
206
|
function agentMeta(cat, a) {
|
|
117
207
|
const fm = (fs.readFileSync(agentPath(cat, a), "utf8").split("---")[1] || "");
|
|
118
208
|
const get = (k) => { const m = fm.match(new RegExp("^" + k + ":\\s*(.+)$", "m")); return m ? m[1].trim() : ""; };
|
|
@@ -122,13 +212,15 @@ function agentMeta(cat, a) {
|
|
|
122
212
|
/* Same gate the skills get, applied to agents before anything is copied. */
|
|
123
213
|
function checkAgent(cat, a) {
|
|
124
214
|
const problems = [];
|
|
125
|
-
|
|
215
|
+
const p = agentPath(cat, a);
|
|
216
|
+
if (!p || !fs.existsSync(p)) return [`agent '${a}' missing`];
|
|
126
217
|
const m = agentMeta(cat, a);
|
|
127
218
|
if (m.name !== a) problems.push(`frontmatter name '${m.name}' != filename '${a}'`);
|
|
128
219
|
if (!m.description) problems.push("frontmatter missing description");
|
|
129
220
|
return problems;
|
|
130
221
|
}
|
|
131
222
|
function packsOf(cat) {
|
|
223
|
+
if (cat.shape === "apm") return Object.keys(cat.packsData).length ? cat.packsData : null;
|
|
132
224
|
const p = path.join(cat.dir, "packs.json");
|
|
133
225
|
return fs.existsSync(p) ? readJSON(p).packs : null;
|
|
134
226
|
}
|
|
@@ -136,8 +228,14 @@ function packsOf(cat) {
|
|
|
136
228
|
/* Structural gate (RFC-0003): a skill must pass before it is copied anywhere. */
|
|
137
229
|
function checkSkill(cat, s) {
|
|
138
230
|
const problems = [];
|
|
139
|
-
const dir =
|
|
140
|
-
if (!fs.existsSync(path.join(dir, "SKILL.md"))) problems.push("SKILL.md missing");
|
|
231
|
+
const dir = skillDir(cat, s);
|
|
232
|
+
if (!dir || !fs.existsSync(path.join(dir, "SKILL.md"))) problems.push("SKILL.md missing");
|
|
233
|
+
if (cat.shape === "apm") {
|
|
234
|
+
// agentbundle skills carry name/description in SKILL.md frontmatter (agentskills.io);
|
|
235
|
+
// version lives in pack.toml and there is no model tier — gate only the portable fields.
|
|
236
|
+
if (!problems.length && !manifestOf(cat, s).description) problems.push("SKILL.md frontmatter missing description");
|
|
237
|
+
return problems;
|
|
238
|
+
}
|
|
141
239
|
let m = null;
|
|
142
240
|
try { m = manifestOf(cat, s); } catch (e) { problems.push("manifest.json missing or invalid JSON"); }
|
|
143
241
|
if (m) {
|
|
@@ -456,13 +554,13 @@ function expandNames(args, cat) {
|
|
|
456
554
|
return p.skills.slice();
|
|
457
555
|
}
|
|
458
556
|
if (!args._.length) die("nothing to install — pass skill names, --pack <name>, or --all");
|
|
459
|
-
for (const s of args._) if (!
|
|
557
|
+
for (const s of args._) if (!skillExists(cat, s)) die(`unknown skill '${s}' in catalog '${cat.source}'`);
|
|
460
558
|
return args._.slice();
|
|
461
559
|
}
|
|
462
560
|
|
|
463
561
|
function copyOne(cat, s, dest, ide, l, notes) {
|
|
464
562
|
const m = manifestOf(cat, s);
|
|
465
|
-
fs.cpSync(
|
|
563
|
+
fs.cpSync(skillDir(cat, s), path.join(dest, s), { recursive: true });
|
|
466
564
|
const note = writeWiring(ide, dest, s, m.description);
|
|
467
565
|
if (note && notes) notes.push(note);
|
|
468
566
|
l.data[s] = { version: m.version, source: cat.source };
|
|
@@ -478,7 +576,7 @@ function install(args) {
|
|
|
478
576
|
for (const s of names.slice()) {
|
|
479
577
|
let related = [];
|
|
480
578
|
try { related = manifestOf(cat, s).related || []; } catch (e) { /* gated below */ }
|
|
481
|
-
for (const r of related) if (!seen.has(r) &&
|
|
579
|
+
for (const r of related) if (!seen.has(r) && skillExists(cat, r)) { seen.add(r); names.push(r); }
|
|
482
580
|
}
|
|
483
581
|
}
|
|
484
582
|
gate(cat, names);
|
|
@@ -488,7 +586,7 @@ function install(args) {
|
|
|
488
586
|
const pipDeps = [], suggestions = new Set(), notes = [];
|
|
489
587
|
for (const s of names) {
|
|
490
588
|
const m = copyOne(cat, s, dest, ide, l, notes);
|
|
491
|
-
if (fs.existsSync(path.join(cat
|
|
589
|
+
if (fs.existsSync(path.join(skillDir(cat, s), "requirements.txt"))) pipDeps.push(s);
|
|
492
590
|
for (const r of m.related || []) if (!names.includes(r) && !l.data[r]) suggestions.add(r);
|
|
493
591
|
console.log(`installed ${s}@${m.version} -> ${dest}`);
|
|
494
592
|
}
|
|
@@ -525,7 +623,7 @@ function installedRows(flags) {
|
|
|
525
623
|
let current = null, cat = null;
|
|
526
624
|
try {
|
|
527
625
|
cat = resolveCatalog(src === BUNDLED ? undefined : src);
|
|
528
|
-
if (
|
|
626
|
+
if (skillExists(cat, s)) current = manifestOf(cat, s).version;
|
|
529
627
|
} catch (e) { /* unreachable source: current stays null */ }
|
|
530
628
|
return { s, src, cat, installed: lver(l.data[s]), current };
|
|
531
629
|
});
|
|
@@ -651,15 +749,15 @@ function list(args) {
|
|
|
651
749
|
function info(args) {
|
|
652
750
|
const cat = resolveCatalog(args.flags.from);
|
|
653
751
|
const s = args._[0] || die("pass a skill name");
|
|
654
|
-
if (!
|
|
752
|
+
if (!skillExists(cat, s)) die(`unknown skill '${s}' in catalog '${cat.source}'`);
|
|
655
753
|
const m = manifestOf(cat, s);
|
|
656
754
|
const ps = packsOf(cat) || {};
|
|
657
755
|
const inPacks = Object.entries(ps).filter(([, p]) => p.skills.includes(s)).map(([n]) => n);
|
|
658
|
-
console.log(`${m.name}@${m.version} (tier: ${m.model && m.model.tier})\n\n${m.description}\n`);
|
|
756
|
+
console.log(`${m.name}@${m.version} (tier: ${(m.model && m.model.tier) || "n/a"})\n\n${m.description}\n`);
|
|
659
757
|
console.log(`catalog: ${cat.source}`);
|
|
660
758
|
console.log(`packs: ${inPacks.join(", ") || "-"}`);
|
|
661
759
|
console.log(`related: ${(m.related || []).join(", ") || "-"}`);
|
|
662
|
-
if (((m.deps || {}).pip || []).length || fs.existsSync(path.join(cat
|
|
760
|
+
if (((m.deps || {}).pip || []).length || fs.existsSync(path.join(skillDir(cat, s), "requirements.txt")))
|
|
663
761
|
console.log("deps: python (requirements.txt)");
|
|
664
762
|
if (((m.env || {}).required || []).length) console.log(`env: ${m.env.required.join(", ")} (required)`);
|
|
665
763
|
}
|
package/model-routing.json
CHANGED
|
@@ -146,6 +146,7 @@
|
|
|
146
146
|
"agent-loop-design": { "tier": "standard", "rationale": "Design synthesis against a fixed state-machine pattern and exit-criteria catalog. The gate-placement judgment is bounded by the blast-radius rule; escalate via ambiguous-input when the task itself is contested." },
|
|
147
147
|
"subagent-design": { "tier": "standard", "rationale": "Design synthesis against a fixed topology catalog and role-card template. The fan-out-or-refuse judgment is bounded by the three-reasons rule; escalate via ambiguous-input on genuinely novel workflow shapes." },
|
|
148
148
|
"agent-budget": { "tier": "standard", "rationale": "Quantitative spec synthesis against a fixed tier vocabulary and degradation ladder. Cap-setting judgment is bounded by the 3-5x rule; escalate via ambiguous-input when the workflow itself is undefined." },
|
|
149
|
+
"agents-md-generator": { "tier": "standard", "rationale": "Repo inspection plus synthesis against a fixed section order and line budget — the same shape as reverse-architecture. The judgment is the counterfactual test on each convention line, which is bounded; escalate via large-input on monorepos." },
|
|
149
150
|
"agent-threat-model": { "tier": "heavy", "rationale": "Adversarial reasoning over a capability graph — the judgment is which mitigations are architectural versus theatrical, and that call is exactly where a weaker model produces a confident wrong answer. Never downgraded." }
|
|
150
151
|
}
|
|
151
152
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "skilldrop-cli",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.2",
|
|
4
4
|
"description": "Portable AI-agent skills for the deliverables knowledge workers ship \u2014 diagrams, ADRs, PRDs, runbooks, decks, reviews \u2014 installable into Claude Code, Cursor, Kiro, and any IDE that takes custom instructions. One command per skill or per role-based pack.",
|
|
5
|
+
"author": {
|
|
6
|
+
"name": "Sanjay Ananthanarayan",
|
|
7
|
+
"url": "https://github.com/sananthanarayan"
|
|
8
|
+
},
|
|
5
9
|
"bin": {
|
|
6
10
|
"skilldrop": "bin/skilldrop.js"
|
|
7
11
|
},
|
package/packs.json
CHANGED
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
"tech-comparison-matrix",
|
|
15
15
|
"threat-model",
|
|
16
16
|
"agent-threat-model",
|
|
17
|
+
"agents-md-generator",
|
|
17
18
|
"api-contract-draft",
|
|
18
19
|
"data-contract",
|
|
19
20
|
"db-schema-design",
|
|
@@ -41,8 +42,9 @@
|
|
|
41
42
|
]
|
|
42
43
|
},
|
|
43
44
|
"dev-team": {
|
|
44
|
-
"description": "Build-and-ship loop: story implementation with adversarial review, test plans, triage, migrations, release notes, and code-quality gates.",
|
|
45
|
+
"description": "Build-and-ship loop: story implementation with adversarial review, test plans, triage, migrations, release notes, agent-policy files, and code-quality gates.",
|
|
45
46
|
"skills": [
|
|
47
|
+
"agents-md-generator",
|
|
46
48
|
"user-story-splitter",
|
|
47
49
|
"feature-implement-loop",
|
|
48
50
|
"devils-advocate",
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: agents-md-generator
|
|
3
|
+
description: Generate or audit a repository's AGENTS.md — the agent-policy file Claude Code, Codex, Cursor, Copilot, Kiro and Antigravity read — with every command traced to real evidence in the repo, generic virtue instructions stripped, and forbidden actions named. Use when a repo has no AGENTS.md, when an existing one is stale or ignored, when onboarding a codebase to agentic coding, or when an agent keeps running commands that don't exist.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# agents-md-generator
|
|
7
|
+
|
|
8
|
+
Produces the file agents read *before* the user's prompt. An `AGENTS.md` is not documentation — it is the context an agent spends on every turn, so a line that changes no decision is a line stolen from the task.
|
|
9
|
+
|
|
10
|
+
The failure that makes these files worse than useless is an **invented command**. `npm test` in a repo with no test script sends every agent down a path that fails, and the agent trusts the file over the filesystem. So the rule this skill is built around: **every command traces to evidence in the repo, or it does not appear.**
|
|
11
|
+
|
|
12
|
+
Two modes, chosen by what's already there:
|
|
13
|
+
- **Generate** — no `AGENTS.md` exists. Produce one under the line cap.
|
|
14
|
+
- **Audit** — one exists. Score it, name what can't be verified, emit a diff. Never silently rewrite a hand-tuned file.
|
|
15
|
+
|
|
16
|
+
## How to respond
|
|
17
|
+
|
|
18
|
+
1. **Inventory the real command surface before writing anything.** Read, in this order: `package.json` (`scripts`), `Makefile`, `pyproject.toml` / `setup.cfg`, `Cargo.toml`, `go.mod`, `composer.json`, `.github/workflows/*.yml`, `justfile`, `Taskfile.yml`, and any `CONTRIBUTING.md`. CI workflows are the highest-value source: they contain the commands that actually have to pass.
|
|
19
|
+
|
|
20
|
+
For each command record its **provenance** — the file and key it came from. Provenance is what makes audit mode possible later.
|
|
21
|
+
|
|
22
|
+
2. **Apply the evidence rule without exception.** A command appears only if it traces to a file in the repo. Never infer from ecosystem convention: a Python repo does not necessarily run `pytest`, and a repo with `jest` in `devDependencies` may still have no test script.
|
|
23
|
+
|
|
24
|
+
When a section needs a command that doesn't exist, write the marker, not a guess: ✅ *"Test: `[missing: no test script in package.json or CI]`"* — ❌ *"Test: `npm test`"*. The marker is useful; the guess is a trap.
|
|
25
|
+
|
|
26
|
+
3. **Derive file placement from the actual tree**, not from what the framework usually does. Name the directories that exist and what belongs in each. Two or three rows beat a full tree dump.
|
|
27
|
+
|
|
28
|
+
4. **Extract only conventions an agent would otherwise get wrong.** The test is counterfactual: *would a competent agent, without this line, do the wrong thing?* Naming rules that are enforced somewhere, invariants that look arbitrary but aren't, a generated file that must never be hand-edited. If the answer is no, cut the line.
|
|
29
|
+
|
|
30
|
+
Generic virtue instructions — "write clean code", "be thorough", "follow best practices", "use good naming" — are **stripped, not softened.** They consume context and change no behaviour.
|
|
31
|
+
|
|
32
|
+
5. **Name forbidden actions explicitly.** This is the section most files omit and the one that prevents the most damage: paths that must not be touched, commands that must never run unattended, files that are generated, secrets that must never be committed. Concrete and few.
|
|
33
|
+
|
|
34
|
+
6. **Assemble under ~150 lines.** In generate mode this is a **hard cap** — go over and cut, lowest value first (background prose, then examples, then convention lines that failed the counterfactual test). In audit mode it is a **warning**: report the overage and name the sections you would cut, because an existing file may be long for good reason.
|
|
35
|
+
|
|
36
|
+
7. **Satellites only on request, and only as a delta.** If asked to wire a specific tool, emit a satellite (`CLAUDE.md`, `.github/copilot-instructions.md`, `GEMINI.md`) that contains **only what is specific to that tool**, plus one line pointing at `AGENTS.md` for everything shared. **Refuse to copy shared content into a satellite** — four copies of a command list is four chances to be wrong, and the drift is invisible. See [`reference.md`](reference.md) for the per-tool shapes.
|
|
37
|
+
|
|
38
|
+
8. **Emit.** Generate mode: the file, plus a provenance table showing where each command came from and what was marked `[missing]`. Audit mode: verdict, the unverifiable lines quoted, the virtue instructions to cut, the line-count position, and a concrete diff.
|
|
39
|
+
|
|
40
|
+
**Non-interactive runs:** unstated conventions are simply omitted — an `AGENTS.md` is allowed to be short. If the repo cannot be read at all, emit `BLOCKED: need repo access — every command must trace to a file`. Never produce an `AGENTS.md` from a description of a repo; the whole artifact is a claim about what is in the tree.
|
|
41
|
+
|
|
42
|
+
## Useful references in this skill
|
|
43
|
+
|
|
44
|
+
- [`reference.md`](reference.md) — where each tool looks, the evidence-source table, the satellite shapes, and the strip-list of virtue phrasings
|
|
45
|
+
- [`templates/agents-md.md`](templates/agents-md.md) — the output skeleton with the section order and the line budget per section
|
|
46
|
+
|
|
47
|
+
## Quality bar
|
|
48
|
+
|
|
49
|
+
- **Zero unverified commands.** Every one traces to a file and key, or carries a `[missing: …]` marker. This is the bar; a file that fails it is worse than no file.
|
|
50
|
+
- **A newcomer agent could build and test from this file alone** — or can see plainly that the repo has no such command.
|
|
51
|
+
- **Forbidden actions are named**, not implied.
|
|
52
|
+
- **Every convention line passes the counterfactual test.** If an agent would do the right thing without it, it is padding.
|
|
53
|
+
- **≤150 lines generated.** Over the cap means something was kept that should have been cut.
|
|
54
|
+
- **No virtue instructions survive.** Search the output for "clean", "best practice", "thorough", "appropriate", "as needed" — each hit is a line that changes nothing.
|
|
55
|
+
- **It does not restate the README.** Different reader, different job: the README sells and explains, `AGENTS.md` constrains.
|
|
56
|
+
|
|
57
|
+
## When to use this skill
|
|
58
|
+
|
|
59
|
+
- ✅ A repo is adopting agentic coding and has no `AGENTS.md`
|
|
60
|
+
- ✅ An existing `AGENTS.md` is stale, ignored, or full of advice rather than facts
|
|
61
|
+
- ✅ Agents keep running commands that don't exist in the repo
|
|
62
|
+
- ✅ Onboarding a legacy codebase where the real commands live only in CI
|
|
63
|
+
- ✅ After `reverse-architecture` — its extraction is good input for the conventions section
|
|
64
|
+
|
|
65
|
+
## When NOT to use this skill
|
|
66
|
+
|
|
67
|
+
- ❌ Writing human contributor docs — that's `CONTRIBUTING.md`, a different reader
|
|
68
|
+
- ❌ Explaining what the project *is* — that's the README
|
|
69
|
+
- ❌ Operating a running service — that's `runbook-generator`
|
|
70
|
+
- ❌ Reviewing an existing document's prose quality — that's `doc-critique`, which takes documents; this one takes a repository
|
|
71
|
+
|
|
72
|
+
## Anti-patterns to avoid
|
|
73
|
+
|
|
74
|
+
- ❌ **Inventing a command that "should" exist.** The single most damaging thing this artifact can do. A `[missing]` marker is a finding; a guess is a fault the agent will trust.
|
|
75
|
+
- ❌ **Virtue instructions.** "Write clean, maintainable code" is context spent on nothing. Agents already try.
|
|
76
|
+
- ❌ **Dumping the file tree.** Placement rules earn their space; a directory listing does not — the agent can run `ls`.
|
|
77
|
+
- ❌ **Restating the README.** If it's already there, link it.
|
|
78
|
+
- ❌ **A satellite that duplicates `AGENTS.md`.** Guaranteed drift, invisible until an agent acts on the stale copy.
|
|
79
|
+
- ❌ **Silently rewriting a hand-tuned file.** Audit mode emits a diff and lets a human choose; the file may encode decisions the repo can't show you.
|
|
80
|
+
- ❌ **Writing to the cap.** 150 lines is a ceiling, not a target. Most repos need far less.
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
[
|
|
2
|
+
{ "query": "Write an AGENTS.md for this repo", "should_trigger": true },
|
|
3
|
+
{ "query": "Our AGENTS.md is stale — review it", "should_trigger": true },
|
|
4
|
+
{ "query": "The agent keeps running commands that don't exist in our project", "should_trigger": true },
|
|
5
|
+
{ "query": "Set this codebase up for agentic coding", "should_trigger": true },
|
|
6
|
+
{ "query": "Create a CLAUDE.md for this project", "should_trigger": true },
|
|
7
|
+
{ "query": "What should go in an agent instructions file?", "should_trigger": true },
|
|
8
|
+
|
|
9
|
+
{ "query": "Write a CONTRIBUTING.md for this repo", "should_trigger": false },
|
|
10
|
+
{ "query": "Document what this service does for new hires", "should_trigger": false },
|
|
11
|
+
{ "query": "Reverse-engineer the architecture of this codebase", "should_trigger": false },
|
|
12
|
+
{ "query": "Write a runbook for this service", "should_trigger": false },
|
|
13
|
+
{ "query": "Review this design doc for gaps", "should_trigger": false },
|
|
14
|
+
{ "query": "Design a supervised agent loop", "should_trigger": false }
|
|
15
|
+
]
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
{
|
|
2
|
+
"skill_name": "agents-md-generator",
|
|
3
|
+
"evals": [
|
|
4
|
+
{
|
|
5
|
+
"id": 1,
|
|
6
|
+
"prompt": "Write an AGENTS.md for this repo. It's a Python service: pyproject.toml declares pytest and ruff in dev dependencies, there's a Makefile with `run` and `migrate` targets, and .github/workflows/ci.yml runs `ruff check .` then `pytest -q`. No test script anywhere else. Migrations are in alembic/versions/.",
|
|
7
|
+
"assertions": [
|
|
8
|
+
"Commands are traced to their source — CI for ruff/pytest, Makefile for run/migrate — not inferred from the dependency list",
|
|
9
|
+
"The test command is `pytest -q` as CI runs it, not a guessed `pytest` or `make test` that does not exist",
|
|
10
|
+
"No command appears that cannot be traced to pyproject.toml, the Makefile, or the workflow",
|
|
11
|
+
"Alembic migrations produce a convention or forbidden-action line (append-only / never edit a committed migration)",
|
|
12
|
+
"A `Do not` section exists with concrete entries, not implied caution",
|
|
13
|
+
"Output is at or under ~150 lines",
|
|
14
|
+
"No virtue instructions appear — no 'clean', 'best practice', 'thorough', 'maintainable'",
|
|
15
|
+
"Conventions included would each change agent behaviour if removed"
|
|
16
|
+
]
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
"id": 2,
|
|
20
|
+
"prompt": "Here's our AGENTS.md — audit it. It says: 'Run `npm test` to test. Write clean, maintainable code and follow best practices. Be thorough in reviews.' Our package.json has scripts for build and lint only, no test.",
|
|
21
|
+
"assertions": [
|
|
22
|
+
"Audit mode is used — a verdict and diff, not a silent rewrite",
|
|
23
|
+
"`npm test` is flagged as unverifiable: package.json has no test script",
|
|
24
|
+
"The three virtue instructions are named for removal, with the reason that they change no behaviour",
|
|
25
|
+
"What the file is missing is stated — real commands (build, lint), file placement, forbidden actions",
|
|
26
|
+
"The line-count position is reported rather than the file being failed for length",
|
|
27
|
+
"A concrete replacement or diff is offered, not just criticism"
|
|
28
|
+
]
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
"id": 3,
|
|
32
|
+
"prompt": "Also wire it up for Claude Code and Copilot.",
|
|
33
|
+
"assertions": [
|
|
34
|
+
"Satellites are emitted as deltas only — each points at AGENTS.md for shared content",
|
|
35
|
+
"No satellite repeats the command table, file placement, or conventions",
|
|
36
|
+
"CLAUDE.md carries only Claude-Code-specific content; .github/copilot-instructions.md only Copilot-specific content",
|
|
37
|
+
"If there is no genuinely tool-specific content, that is said rather than a file padded to justify itself"
|
|
38
|
+
]
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
"id": 4,
|
|
42
|
+
"prompt": "[Non-interactive run — no repo access] Generate an AGENTS.md for a typical Node microservice.",
|
|
43
|
+
"assertions": [
|
|
44
|
+
"No AGENTS.md is produced — the output is BLOCKED: need repo access",
|
|
45
|
+
"No commands are invented from ecosystem convention",
|
|
46
|
+
"The reason given is that every command must trace to a file in the tree"
|
|
47
|
+
]
|
|
48
|
+
}
|
|
49
|
+
]
|
|
50
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "agents-md-generator",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Generate or audit a repository's AGENTS.md — the agent-policy file Claude Code, Codex, Cursor, Copilot, Kiro and Antigravity read — with every command traced to real evidence in the repo, generic virtue instructions stripped, and forbidden actions named. Use when a repo has no AGENTS.md, when an existing one is stale or ignored, when onboarding a codebase to agentic coding, or when an agent keeps running commands that don't exist.",
|
|
5
|
+
"entrypoint": "SKILL.md",
|
|
6
|
+
"deps": { "npm": [], "pip": [] },
|
|
7
|
+
"env": { "required": [], "optional": [] },
|
|
8
|
+
"related": ["doc-critique", "reverse-architecture", "runbook-generator"],
|
|
9
|
+
"tags": ["agents-md", "agent-policy", "repo-onboarding", "context-engineering", "agentic", "conventions"],
|
|
10
|
+
"model": { "tier": "standard", "rationale": "Repo inspection plus synthesis against a fixed section order and line budget — the same shape as reverse-architecture. The judgment is the counterfactual test on each convention line, which is bounded; escalate via large-input on monorepos." }
|
|
11
|
+
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
# agents-md-generator — reference
|
|
2
|
+
|
|
3
|
+
## Who reads what
|
|
4
|
+
|
|
5
|
+
Confirmed in the July 2026 survey ([`docs/designs/ide-primitive-coverage.md`](../../docs/designs/ide-primitive-coverage.md)). A root `AGENTS.md` is the broadest single file; satellites are a reach fix for specific tools, not the norm.
|
|
6
|
+
|
|
7
|
+
| Tool | Reads |
|
|
8
|
+
|---|---|
|
|
9
|
+
| Codex | root `AGENTS.md` |
|
|
10
|
+
| GitHub Copilot | root `AGENTS.md`, `.github/copilot-instructions.md`, `.github/instructions/*.instructions.md`, and also root `CLAUDE.md` / `GEMINI.md` |
|
|
11
|
+
| Claude Code | `CLAUDE.md` (auto-loaded) |
|
|
12
|
+
| Cursor | root `AGENTS.md`, plus `.cursor/rules/*.mdc` for rules |
|
|
13
|
+
| Kiro | root `AGENTS.md`, plus `.kiro/steering/` for always-on context |
|
|
14
|
+
| Antigravity | root `AGENTS.md` |
|
|
15
|
+
|
|
16
|
+
**Precedence matters more than reach.** Several of these load on every turn regardless of relevance. A file that is always in context should carry only what earns its place there — the same reason an always-included steering file is a poor home for a persona.
|
|
17
|
+
|
|
18
|
+
## Evidence sources, in priority order
|
|
19
|
+
|
|
20
|
+
Every command must trace to one of these. Record the file and key as provenance.
|
|
21
|
+
|
|
22
|
+
| Source | What it proves |
|
|
23
|
+
|---|---|
|
|
24
|
+
| `.github/workflows/*.yml` (and other CI) | **Strongest.** These commands must pass or the build fails |
|
|
25
|
+
| `Makefile` / `justfile` / `Taskfile.yml` | Named entry points the team actually uses |
|
|
26
|
+
| `package.json` `scripts` | Node entry points; check the script body, not just the key |
|
|
27
|
+
| `pyproject.toml` / `setup.cfg` / `tox.ini` | Python entry points and runners |
|
|
28
|
+
| `Cargo.toml`, `go.mod`, `composer.json`, `Gemfile` | Ecosystem defaults *only if* the file confirms them |
|
|
29
|
+
| `CONTRIBUTING.md`, `README.md` | Human-facing; **verify against the above before trusting** — docs go stale faster than CI |
|
|
30
|
+
| Dev container / Dockerfile | Setup steps and version pins |
|
|
31
|
+
|
|
32
|
+
**A dependency is not a command.** `jest` in `devDependencies` does not mean `npm test` exists. Check for the script.
|
|
33
|
+
|
|
34
|
+
## What earns a line — the counterfactual test
|
|
35
|
+
|
|
36
|
+
Ask: *without this line, would a competent agent do the wrong thing?*
|
|
37
|
+
|
|
38
|
+
| ✅ Earns its place | ❌ Cut |
|
|
39
|
+
|---|---|
|
|
40
|
+
| "Generated — never hand-edit `src/api/types.ts`; run `make codegen`" | "Keep the codebase clean and well organised" |
|
|
41
|
+
| "Migrations are append-only; never edit a committed migration" | "Follow database best practices" |
|
|
42
|
+
| "Tests live beside the source as `*_test.go`, not in `tests/`" | "Write comprehensive tests" |
|
|
43
|
+
| "`main` is protected; open a PR" | "Follow good git hygiene" |
|
|
44
|
+
| "Do not run `terraform apply` — plan only, a human applies" | "Be careful with infrastructure" |
|
|
45
|
+
|
|
46
|
+
## Strip list
|
|
47
|
+
|
|
48
|
+
Search generated output for these; each hit is a line that changes no behaviour:
|
|
49
|
+
|
|
50
|
+
`clean` · `best practice` · `thorough` · `appropriate` · `as needed` · `where possible` · `make sure to` · `try to` · `robust` · `maintainable` · `readable` · `well-structured` · `properly` · `high-quality` · `idiomatic` (unless a specific idiom is named)
|
|
51
|
+
|
|
52
|
+
## Satellite shapes — delta only
|
|
53
|
+
|
|
54
|
+
A satellite carries what is specific to its tool **and nothing else**. Every shared fact stays in `AGENTS.md` and is pointed at.
|
|
55
|
+
|
|
56
|
+
**`CLAUDE.md`** — Claude Code auto-loads it:
|
|
57
|
+
|
|
58
|
+
```markdown
|
|
59
|
+
# CLAUDE.md
|
|
60
|
+
|
|
61
|
+
The substantive guidance lives in [AGENTS.md](AGENTS.md). Read it first.
|
|
62
|
+
|
|
63
|
+
This file exists because Claude Code auto-loads `CLAUDE.md`. Everything below is
|
|
64
|
+
Claude-Code-specific and does not belong in the cross-IDE file.
|
|
65
|
+
|
|
66
|
+
## <tool-specific section>
|
|
67
|
+
…e.g. `${CLAUDE_SKILL_DIR}` semantics, `.claude/settings.json`, subagent delegation…
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
**`.github/copilot-instructions.md`** — same shape, Copilot-specific content only (custom agents in `.github/agents/`, `.github/hooks/*.json`).
|
|
71
|
+
|
|
72
|
+
**`GEMINI.md`** — same shape, for Gemini/Antigravity specifics.
|
|
73
|
+
|
|
74
|
+
**Refuse** to emit a satellite containing the command table, file placement, or conventions that already live in `AGENTS.md`. If a tool needs those, it reads `AGENTS.md` — and if it genuinely cannot, say so plainly rather than duplicating.
|
|
75
|
+
|
|
76
|
+
## Line budget
|
|
77
|
+
|
|
78
|
+
150 lines total. A workable split:
|
|
79
|
+
|
|
80
|
+
| Section | Lines |
|
|
81
|
+
|---|---|
|
|
82
|
+
| Repo in one paragraph | 3–5 |
|
|
83
|
+
| Commands (with provenance verified) | 15–30 |
|
|
84
|
+
| File placement | 10–20 |
|
|
85
|
+
| Conventions that pass the counterfactual test | 20–40 |
|
|
86
|
+
| Forbidden actions | 5–15 |
|
|
87
|
+
| Pointers to deeper docs | 3–5 |
|
|
88
|
+
|
|
89
|
+
Over budget: cut background prose first, then examples, then convention lines that only *probably* pass the test.
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# AGENTS.md — {project name}
|
|
2
|
+
|
|
3
|
+
> {One line: what an agent needs to know it is working on.}
|
|
4
|
+
|
|
5
|
+
## Repo in one paragraph
|
|
6
|
+
|
|
7
|
+
{What this is, what it produces, and the one structural fact an agent must hold. 3–5 lines.}
|
|
8
|
+
|
|
9
|
+
## Commands
|
|
10
|
+
|
|
11
|
+
<!-- Every row traces to a file. No row may be inferred from ecosystem convention. -->
|
|
12
|
+
|
|
13
|
+
| Task | Command | Source |
|
|
14
|
+
|---|---|---|
|
|
15
|
+
| Install | `{cmd}` | `{file}` → `{key}` |
|
|
16
|
+
| Build | `{cmd}` | `{file}` → `{key}` |
|
|
17
|
+
| Test | `{cmd}` *or* `[missing: no test command in CI or manifests]` | `{file}` → `{key}` |
|
|
18
|
+
| Lint | `{cmd}` | `{file}` → `{key}` |
|
|
19
|
+
| Run locally | `{cmd}` | `{file}` → `{key}` |
|
|
20
|
+
|
|
21
|
+
{Drop the Source column from the final file if it crowds the budget — but only after every row has been verified with it present.}
|
|
22
|
+
|
|
23
|
+
## File placement
|
|
24
|
+
|
|
25
|
+
| Kind | Where |
|
|
26
|
+
|---|---|
|
|
27
|
+
| {thing} | `{path}` |
|
|
28
|
+
|
|
29
|
+
## Conventions
|
|
30
|
+
|
|
31
|
+
<!-- Each line must pass: without it, would a competent agent do the wrong thing? -->
|
|
32
|
+
|
|
33
|
+
- **{Rule stated as a rule.}** {One clause of why, only if the why changes how it is applied.}
|
|
34
|
+
|
|
35
|
+
## Do not
|
|
36
|
+
|
|
37
|
+
- {Path, command, or action that must never happen unattended — concrete.}
|
|
38
|
+
- {Generated artifacts that must not be hand-edited, and the command that regenerates them.}
|
|
39
|
+
|
|
40
|
+
## Pointers
|
|
41
|
+
|
|
42
|
+
- {Deeper doc}: `{path}`
|
|
43
|
+
- {Architecture / design}: `{path}`
|
|
44
|
+
|
|
45
|
+
<!--
|
|
46
|
+
Line budget: 150. Provenance for every command was verified at generation time.
|
|
47
|
+
Commands marked [missing] are findings, not omissions — the repo has no such command.
|
|
48
|
+
-->
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# Worked example — build vs. buy a feature-flag system
|
|
2
|
+
|
|
3
|
+
Input → output oracle for `business-case`. Shows the ask first (< 40 words), Option 0
|
|
4
|
+
costed, benefits as re-runnable calculations with confidence tags, all three cost
|
|
5
|
+
layers, ranges, the flip-assumption, and a singular recommendation that states the
|
|
6
|
+
runner-up's best argument fairly.
|
|
7
|
+
|
|
8
|
+
## Input
|
|
9
|
+
|
|
10
|
+
> "Should [Acme] build an in-house feature-flag system or buy a SaaS one? ~30
|
|
11
|
+
> engineers, currently gating releases with config files and redeploys."
|
|
12
|
+
|
|
13
|
+
## Output
|
|
14
|
+
|
|
15
|
+
### The ask
|
|
16
|
+
**Buy the SaaS feature-flag service (~$18–30k/yr).** It pays back in < 3 months against ~$140k of first-year build cost and recurring toil, and frees roadmap capacity now. Decision needed before next quarter's release-velocity commitments.
|
|
17
|
+
|
|
18
|
+
### Options (Option 0 is costed)
|
|
19
|
+
|
|
20
|
+
| Option | Build (one-time) | Run (yr 1) | Opportunity cost |
|
|
21
|
+
|---|--:|--:|--:|
|
|
22
|
+
| **0 — Status quo** (config + redeploy) | $0 | ~$60–90k/yr toil | Slow releases → est. 1–2 delayed launches/yr |
|
|
23
|
+
| **1 — Build in-house** | ~$110–160k | ~$25–40k/yr (on-call, maint.) | 2 eng × 3–4 mo off roadmap |
|
|
24
|
+
| **2 — Buy SaaS** | ~$5k (integrate) | ~$18–30k/yr license | ~1 eng-week integration |
|
|
25
|
+
|
|
26
|
+
### Benefits (re-runnable calculations, sourced, confidence-tagged)
|
|
27
|
+
|
|
28
|
+
- **Toil removed vs. Option 0:** each gated release currently costs ~1 eng-hr of redeploy coordination × ~15 releases/mo × $120 loaded/hr = **~$22k/yr** recovered. *(confidence: medium — release count from deploy logs; hourly is loaded-cost estimate.)*
|
|
29
|
+
- **Incident reduction:** flag-based instant rollback vs. redeploy shortens mean remediation from ~40 min to ~5 min; at ~6 rollback-worthy incidents/yr that's ~3.5 hrs × team = **~$8–15k/yr** avoided. *(confidence: low — incident count is small-sample; range reflects it.)*
|
|
30
|
+
- **Release velocity:** decoupling deploy from release plausibly removes 1–2 launch slips/yr; value is real but **not costed here** (depends on launch revenue) — flagged for the decision owner.
|
|
31
|
+
|
|
32
|
+
Totals are ranges; the **lower end is likelier** for the hard-dollar items (toil is the solid number; incident/velocity are directional).
|
|
33
|
+
|
|
34
|
+
### The flip-assumption
|
|
35
|
+
The case flips to **Build** only if **flag evaluation must run in a data plane the SaaS can't reach** (e.g. strict data-residency or sub-ms in-process evaluation at scale). Confidence that this *doesn't* apply is medium. **De-risking step before committing to Buy:** a 1-week spike confirming the SaaS SDK meets latency + residency requirements — cheap insurance against the one assumption that would reverse the decision.
|
|
36
|
+
|
|
37
|
+
### Recommendation
|
|
38
|
+
**Buy (Option 2).** Lowest first-year cost, fastest payback, and it returns ~2 engineers to the roadmap that Build would consume. **The runner-up's best argument, stated fairly:** Build is genuinely better *if* flag evaluation becomes a core differentiator or a hard data-residency constraint emerges — in that world the SaaS boundary bites and owning the system is worth the cost. The spike above is what tells us which world we're in.
|
|
39
|
+
|
|
40
|
+
## Why this passes the quality bar
|
|
41
|
+
|
|
42
|
+
The ask leads in under 40 words; Option 0 is present and costed; every benefit is a calculation with sourced inputs and a confidence tag; outputs are ranges naming the likelier end; all three cost layers appear per option; the flip-assumption is identified with a de-risking step; and the recommendation is singular while stating the runner-up's strongest case honestly.
|
|
@@ -33,7 +33,7 @@ Five **standing seats** — each a distinct mandate with a lane it stays in. Rea
|
|
|
33
33
|
| ⚖️ **Pragmatist** | Is this the simplest thing that works? Over-engineered? Do we even need it? | [`seats/pragmatist.md`](seats/pragmatist.md) |
|
|
34
34
|
| 👤 **User-Advocate** | Is it correct for the people who use it? Edge cases, errors, contracts, compat? | [`seats/user-advocate.md`](seats/user-advocate.md) |
|
|
35
35
|
|
|
36
|
-
Plus a **bench** of optional seats (Performance, Cost/FinOps, Data & Migration, Accessibility, Compliance) — seat them only when the change warrants. See [`seats/bench.md`](seats/bench.md).
|
|
36
|
+
Plus a **bench** of optional seats (Performance, Cost/FinOps, Data & Migration, Accessibility, Compliance) — seat them only when the change warrants. See [`seats/bench.md`](seats/bench.md). Chairing and roster-choice guidance that doesn't fit here lives in [`reference.md`](reference.md).
|
|
37
37
|
|
|
38
38
|
The **Chair** is not a sixth opinion. The Chair facilitates, finds the cruxes, and decides.
|
|
39
39
|
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# Worked example — adversarial review of a just-written rate limiter
|
|
2
|
+
|
|
3
|
+
Input → output oracle for `devils-advocate`. Shows the four-lens sweep producing
|
|
4
|
+
severity-calibrated findings with `file:line` evidence and paste-able fixes — not a
|
|
5
|
+
generic "looks good, consider tests" pass.
|
|
6
|
+
|
|
7
|
+
## Input (the "done" implementation)
|
|
8
|
+
|
|
9
|
+
> "Implemented the per-user rate limiter, tests pass, ready for review."
|
|
10
|
+
|
|
11
|
+
```python
|
|
12
|
+
# ratelimit.py
|
|
13
|
+
_buckets: dict[str, tuple[float, float]] = {} # user_id -> (tokens, last_refill)
|
|
14
|
+
|
|
15
|
+
def allow(user_id: str, rate: float, burst: float) -> bool:
|
|
16
|
+
now = time.time()
|
|
17
|
+
tokens, last = _buckets.get(user_id, (burst, now))
|
|
18
|
+
tokens = min(burst, tokens + (now - last) * rate)
|
|
19
|
+
if tokens >= 1:
|
|
20
|
+
_buckets[user_id] = (tokens - 1, now)
|
|
21
|
+
return True
|
|
22
|
+
_buckets[user_id] = (tokens, now)
|
|
23
|
+
return False
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Output (the review)
|
|
27
|
+
|
|
28
|
+
**Verdict: not ready — 1 blocker, 1 major.** Four lenses swept; happy path is fine, the concurrency and growth paths are not.
|
|
29
|
+
|
|
30
|
+
🟥 **Blocker — race on the shared dict (assumptions lens).** `ratelimit.py:6-11` reads then writes `_buckets[user_id]` with no lock. Two concurrent requests for the same user both read `tokens=1.0`, both pass, both write back `0.0` — the limiter leaks one token per race, so a burst of N parallel requests admits ~2× the intended rate. The "tests pass" because they're single-threaded. *Fix:* guard the read-modify-write with a per-user lock, or move to an atomic backend (Redis `INCR`+TTL). A dict-of-locks keyed by `user_id` is the minimal change.
|
|
31
|
+
|
|
32
|
+
🟧 **Major — unbounded memory growth (edge-case lens).** `_buckets` never evicts (`:1`). Every distinct `user_id` ever seen stays forever; a service facing the open internet (or spoofable user ids) OOMs over time. *Fix:* cap with an LRU (`functools.lru_cache` won't fit the mutable value — use `cachetools.TTLCache(maxsize=…, ttl=…)`), evicting idle buckets.
|
|
33
|
+
|
|
34
|
+
🟨 **Minor — first call gets a free full burst (edge-case lens).** A never-seen `user_id` starts at `(burst, now)` (`:6`), so a brand-new user can immediately spend the whole burst. If the intent is "warm up", seed at `min(burst, rate*window)` instead — flag for product, don't silently change.
|
|
35
|
+
|
|
36
|
+
⚪ **Nit — `rate`/`burst` unvalidated (staff-engineer lens).** `rate<=0` freezes every user out; `burst<1` makes `allow` always false. Assert `rate>0 and burst>=1` at the boundary.
|
|
37
|
+
|
|
38
|
+
**Test-coverage gaps (the "tests pass" were happy-path only):**
|
|
39
|
+
- Concurrency: two threads hammering one `user_id` must not exceed `burst + rate*elapsed` admits.
|
|
40
|
+
- Refill: after exhausting tokens, `allow` returns True again only after `1/rate` seconds.
|
|
41
|
+
- Eviction: memory stays bounded under 10k distinct ids.
|
|
42
|
+
- Boundary: `tokens` sitting at exactly `1.0` admits; at `0.999…` denies.
|
|
43
|
+
|
|
44
|
+
## Why this passes the quality bar
|
|
45
|
+
|
|
46
|
+
Severity is calibrated (one blocker, demoted the rest); every finding cites `file:line`, names the *lens* it came from, and gives a concrete fix or a specific missing test — not "add more tests."
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# Worked example — critique of a design-doc excerpt
|
|
2
|
+
|
|
3
|
+
Input → output oracle for `doc-critique`. Shows local, doc-quoting findings with
|
|
4
|
+
paste-able fixes — reviewing whether the doc *made its case*, not re-debating the topic.
|
|
5
|
+
|
|
6
|
+
## Input (excerpt of a design doc under review)
|
|
7
|
+
|
|
8
|
+
> ## TL;DR
|
|
9
|
+
> We will build an innovative, scalable notification service to leverage our
|
|
10
|
+
> existing infrastructure and deliver best-in-class user engagement.
|
|
11
|
+
>
|
|
12
|
+
> ## Approach
|
|
13
|
+
> The service consumes events and sends notifications. It will be highly
|
|
14
|
+
> available and performant. We considered several options and chose a
|
|
15
|
+
> queue-based design because it is the industry standard.
|
|
16
|
+
>
|
|
17
|
+
> ## Rollout
|
|
18
|
+
> We will roll it out to all users.
|
|
19
|
+
|
|
20
|
+
## Output (the critique)
|
|
21
|
+
|
|
22
|
+
**Verdict: not ready to circulate — 1 blocker, 2 major, 1 minor.** The doc asserts conclusions it never earns; a reader can't tell what's being built or why this shape.
|
|
23
|
+
|
|
24
|
+
🟥 **Blocker — the decision has no rationale (§ Approach).** Quoted: *"chose a queue-based design because it is the industry standard."* "Industry standard" is not a reason; it hides the actual tradeoff. *Fix:* replace with the decision's real driver — e.g. "chose a queue so a downstream outage buffers events instead of dropping them; the alternative (synchronous fan-out) loses notifications when a provider is down." Name the alternative and why it lost.
|
|
25
|
+
|
|
26
|
+
🟧 **Major — TL;DR is adjectives, not content (§ TL;DR).** Quoted: *"innovative, scalable … leverage … best-in-class user engagement."* Five value-words, zero facts — a reader learns nothing about scope. *Fix:* state what it does and the one number that matters: "A queue-backed service that delivers email/push notifications from domain events, targeting < 5s p95 delivery for 1M events/day."
|
|
27
|
+
|
|
28
|
+
🟧 **Major — unfalsifiable quality claims (§ Approach).** Quoted: *"highly available and performant."* No SLO, so nothing can be reviewed or later checked. *Fix:* commit numbers — availability target (e.g. 99.9%), p95 latency, throughput — or move them to an explicit "Open questions" list if unknown.
|
|
29
|
+
|
|
30
|
+
🟨 **Minor — rollout has no stages or guardrail (§ Rollout).** Quoted: *"roll it out to all users."* No canary, no rollback trigger. *Fix:* a 2–3 line staged plan (1% → 10% → 100%) and the metric that halts it (delivery-failure rate > X%).
|
|
31
|
+
|
|
32
|
+
## Why this passes the quality bar
|
|
33
|
+
|
|
34
|
+
Severity is calibrated (one blocker). Every finding is **local** and **quotes the doc verbatim** (the audit trail that it was actually read), the fixes are paste-able, and it critiques the *doc's* case-making — it does not re-argue whether a queue is the right architecture.
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# Worked example — SWOT arriving as TOWS for a market-entry decision
|
|
2
|
+
|
|
3
|
+
Input → output oracle for `strategy-analysis`. Shows the framework justified in one
|
|
4
|
+
line, evidenced/consequential cells, the mandatory SWOT→TOWS crossings, ranked
|
|
5
|
+
action-shaped implications tied to the decision, and a verdict that takes a side.
|
|
6
|
+
|
|
7
|
+
## Input
|
|
8
|
+
|
|
9
|
+
> "Should [Acme], a mid-market B2B analytics vendor, enter the SMB self-serve
|
|
10
|
+
> segment next year? Decision owner wants a go / no-go with conditions."
|
|
11
|
+
|
|
12
|
+
## Framework fit (one line)
|
|
13
|
+
|
|
14
|
+
**SWOT→TOWS**, not Five Forces: the question is about *our* fit to a new segment (internal capabilities vs. external openings), not the structural attractiveness of the industry.
|
|
15
|
+
|
|
16
|
+
## SWOT (every cell specific · evidenced · consequential)
|
|
17
|
+
|
|
18
|
+
| | Helpful | Harmful |
|
|
19
|
+
|---|---|---|
|
|
20
|
+
| **Internal** | **S1** Self-serve onboarding already built for mid-market trials (evidence: 40% of trials activate with zero sales touch) → *marginal cost to serve SMB is low.* | **W1** Pricing starts at $2k/mo (evidence: current floor plan) → *5–10× above SMB willingness-to-pay; no viable entry SKU.* |
|
|
21
|
+
| **External** | **O1** No incumbent owns SMB self-serve analytics in this niche (evidence: top 3 competitors are all sales-led, $10k+ ACV) → *an unserved segment with a wedge.* | **T1** Two horizontal BI tools added SMB free tiers this year (evidence: their changelogs) → *the window is closing; a free tier resets price expectations.* |
|
|
22
|
+
|
|
23
|
+
## TOWS crossings (the strategy — inventory becomes moves)
|
|
24
|
+
|
|
25
|
+
- **S1 × O1 (invest):** Package the existing zero-touch onboarding as a low-price self-serve SKU to claim the unserved niche before it's contested.
|
|
26
|
+
- **W1 × T1 (defend/decide):** The $2k floor collides head-on with free tiers — a new SMB SKU is a *prerequisite*, not an option; without it, don't enter.
|
|
27
|
+
- **S1 × T1 (hedge):** Low cost-to-serve means an SMB tier can be near-free to run, so a defensive free/low tier is affordable if a competitor forces it.
|
|
28
|
+
- **W1 × O1 (fix-first):** The opportunity is real but unreachable until pricing is solved — so pricing is the gating workstream, not marketing.
|
|
29
|
+
|
|
30
|
+
## Ranked implications (action-shaped, tied to the go/no-go)
|
|
31
|
+
|
|
32
|
+
1. **Gate the decision on a validated SMB SKU** (target ≤ $99/mo, positive unit economics at self-serve volume). No SKU → no-go. *(from W1×T1, W1×O1)*
|
|
33
|
+
2. **Run a 60-day self-serve pilot** on the existing onboarding to test activation without sales. *(S1×O1)*
|
|
34
|
+
3. **Pre-commit a free-tier response** so a competitor's move doesn't catch us flat. *(S1×T1)*
|
|
35
|
+
|
|
36
|
+
## Verdict (takes a side + what would change it)
|
|
37
|
+
|
|
38
|
+
**Conditional GO** — enter *only* after a sub-$100 SKU clears a unit-economics test in a 60-day pilot; the low cost-to-serve and the open niche make the upside real, but the pricing gap is disqualifying until fixed. **What flips it to no-go:** the pilot shows self-serve activation < 20% (the zero-touch advantage doesn't transfer), or a horizontal BI tool ships a *vertical* SMB tier first (the wedge closes).
|
|
39
|
+
|
|
40
|
+
## Why this passes the quality bar
|
|
41
|
+
|
|
42
|
+
The framework choice is justified; every cell is specific, evidenced, and carries a "→ so what"; SWOT is delivered *as TOWS* with real crossings; implications are ranked and tied to the named decision; and the verdict commits with a falsifiable flip-condition instead of "it depends."
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# Worked example — weighted comparison of message queues
|
|
2
|
+
|
|
3
|
+
Input → output oracle for `tech-comparison-matrix`. Shows shown weights, a one-line
|
|
4
|
+
justification per score, honest "rough" markers, and a one-sentence recommendation.
|
|
5
|
+
|
|
6
|
+
## Input
|
|
7
|
+
|
|
8
|
+
> "We need a message queue for an order-processing pipeline: ~5k msg/s, must not
|
|
9
|
+
> lose messages, small team, already on AWS. Compare [managed-broker-A],
|
|
10
|
+
> [self-hosted-broker-B], and [cloud-queue-C]."
|
|
11
|
+
|
|
12
|
+
## Output
|
|
13
|
+
|
|
14
|
+
**Weights** (sum to 100, set by the stated constraints — durability and ops-load dominate because the team is small and messages can't be lost):
|
|
15
|
+
|
|
16
|
+
| Criterion | Weight | Why this weight |
|
|
17
|
+
|---|--:|---|
|
|
18
|
+
| Durability / no message loss | 30 | Hard requirement — order data. |
|
|
19
|
+
| Operational load | 25 | Small team; running a broker is a tax. |
|
|
20
|
+
| Throughput headroom | 15 | 5k/s is modest; all three clear it, so it's not decisive. |
|
|
21
|
+
| Delivery semantics / ordering | 15 | Orders need per-key ordering. |
|
|
22
|
+
| Ecosystem fit (AWS) | 15 | Already on AWS; native integration saves glue. |
|
|
23
|
+
|
|
24
|
+
**Scores** (1–5, each justified; ties kept where real):
|
|
25
|
+
|
|
26
|
+
| Criterion | Wt | [managed-broker-A] | [self-hosted-broker-B] | [cloud-queue-C] |
|
|
27
|
+
|---|--:|--:|--:|--:|
|
|
28
|
+
| Durability | 30 | 5 — replicated, managed | 5 — replicated *if* configured right | 5 — replicated, managed |
|
|
29
|
+
| Ops load | 25 | 4 — managed, some tuning | 2 — you patch/scale/monitor it | 5 — fully serverless |
|
|
30
|
+
| Throughput (rough) | 15 | 4 — ~tens of k/s | 5 — highest ceiling | 4 — ample for 5k/s |
|
|
31
|
+
| Semantics/ordering | 15 | 4 — ordered per partition | 5 — most flexible | 3 — FIFO variant, lower quotas |
|
|
32
|
+
| AWS fit | 15 | 3 — runs on AWS, not native | 2 — self-run on EC2/EKS | 5 — native service |
|
|
33
|
+
| **Weighted total** | | **4.15** | **3.55** | **4.45** |
|
|
34
|
+
|
|
35
|
+
**Recommendation:** Pick **[cloud-queue-C]** — its serverless ops profile decisively fits a small team with a modest, durability-critical load, and the only real gap (ordering quotas) is manageable at 5k/s.
|
|
36
|
+
|
|
37
|
+
*Directional note:* throughput scores are rough (order-of-magnitude, not benchmarked); if load is likely to 10× within a year, re-weight throughput up and re-score — [self-hosted-broker-B]'s ceiling starts to matter.
|
|
38
|
+
|
|
39
|
+
## Why this passes the quality bar
|
|
40
|
+
|
|
41
|
+
Weights are shown and tied to the question; every score carries a one-line justification; ties (durability 5/5/5) are left as ties; the guessed throughput numbers are flagged "rough"/"directional"; and the recommendation is one decisive sentence, not hedged.
|