skilldrop-cli 0.8.1 → 0.8.3
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 +21 -3
- package/agents/README.md +2 -1
- package/agents/security-reviewer.md +44 -0
- package/bin/skilldrop.js +119 -21
- package/package.json +5 -1
- 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/feature-implement-loop/SKILL.md +13 -9
- 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,
|
|
@@ -108,7 +108,7 @@ Skills for the direction-setting layer above any single feature — testing whet
|
|
|
108
108
|
|
|
109
109
|
| Skill | What it does |
|
|
110
110
|
|---|---|
|
|
111
|
-
| [`feature-implement-loop`](skills/feature-implement-loop/SKILL.md) | Implement a feature/story so it ends up *verified*, not just generated. Takes a description + acceptance criteria, writes code and tests, then runs
|
|
111
|
+
| [`feature-implement-loop`](skills/feature-implement-loop/SKILL.md) | Implement a feature/story so it ends up *verified*, not just generated. Takes a description + acceptance criteria, writes code and tests, then runs a review panel (delegating to the `devils-advocate`, `security-reviewer`, and `code-quality` subagents in parallel where available, sweeping the lenses inline elsewhere) to find gaps — broken edge cases, baked-in assumptions, acceptance criteria with no asserting test — and re-generates to close them. Loops until the review is clean or a hard 3-round cap is hit, then reports status (`VERIFIED` / `VERIFIED WITH OPEN ITEMS` / `BLOCKED`), an acceptance-criteria coverage table, and a per-round log. The build-cycle counterpart to the artifact pipeline. |
|
|
112
112
|
| [`devils-advocate`](skills/devils-advocate/SKILL.md) | Adversarial review of *just-generated* code, run right after an agent (or human) declares a feature done. Sweeps four lenses — edge cases the first pass missed, assumptions baked in that won't survive 6 months, what a staff engineer would push back on in review (concurrency, error handling, security, observability, blast radius), and test-coverage gaps. Produces severity-tagged findings (blocker / major / minor / nit) with file:line evidence, reproducible scenarios, and concrete fixes — plus a "what's solid" section. The code counterpart to `doc-critique`. |
|
|
113
113
|
| [`council-review`](skills/council-review/SKILL.md) | The "Claude Council." Convenes five distinct senior-engineer personas — 🏛 Architect, 🔒 Security, 🛠 Operator/SRE, ⚖️ Pragmatist, 👤 User-Advocate (plus optional bench seats: Performance, Cost, Data & Migration, Accessibility, Compliance) — to deliberate a decision, design, or code change. Each seat takes an independent stance (🟢 support / 🟡 conditions / 🔴 oppose / ⚪ abstain) with evidence; the genuine disagreements surface as named **cruxes**; a **Chair** issues a reconciled verdict (PROCEED / WITH CONDITIONS / REVISE / RECONSIDER / SPLIT) that names the tradeoff chosen, records dissent, and states what would change the decision. Where `devils-advocate` is one voice, the council is many that can disagree. Use for architecture decisions, risky changes before merge, RFC scrutiny, "should we adopt X?", or a pre-mortem. |
|
|
114
114
|
| [`threat-model`](skills/threat-model/SKILL.md) | STRIDE threat model from a design doc, architecture description, or `reverse-architecture` output — *before* the code exists. Establishes assets / entry points / trust boundaries first, runs STRIDE per boundary crossing, and writes every threat as a concrete actor → path → result scenario (severity 🟥/🟧/🟨/⚪ calibrated to *this* system's assets, not CVSS theater). Credits existing mitigations as verified-vs-assumed, attaches a verification step to every recommendation, and forces the two classic blind-spot sweeps: authz (tenant→tenant, authn ≠ authz) and insider/compromised-credential. Design-phase counterpart to `devils-advocate`. |
|
|
@@ -264,6 +264,17 @@ npx skilldrop-cli list | skilldrop info <skill> | skilldrop packs | skilldrop un
|
|
|
264
264
|
|
|
265
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).
|
|
266
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
|
+
|
|
267
278
|
### Hooks (opt-in) — wire a skill to an event
|
|
268
279
|
|
|
269
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.
|
|
@@ -315,6 +326,13 @@ npx skilldrop-cli install --pack starter --from ../local-catalog
|
|
|
315
326
|
npx skilldrop-cli update # updates bundled and third-party skills side by side — the ledger remembers each skill's source
|
|
316
327
|
```
|
|
317
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
|
+
|
|
318
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.
|
|
319
337
|
|
|
320
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/agents/README.md
CHANGED
|
@@ -22,8 +22,9 @@ npx skilldrop-cli install --agent devils-advocate --ide kiro # .kiro/agents/*.
|
|
|
22
22
|
|---|---|---|
|
|
23
23
|
| [`devils-advocate`](devils-advocate.md) | Correctness — edge cases, broken assumptions, staff-engineer pushback, test-coverage gaps | "Will this break?" |
|
|
24
24
|
| [`code-quality`](code-quality.md) | Craft — naming, structure, duplication, needless complexity, readability | "Will the next engineer hate this?" |
|
|
25
|
+
| [`security-reviewer`](security-reviewer.md) | Security — authz, injection, secret exposure, SSRF, unsafe deserialization, weak crypto, risky deps | "What can an attacker do with this?" |
|
|
25
26
|
|
|
26
|
-
They are deliberately **split, not merged.** A single "review my code" agent dilutes
|
|
27
|
+
They are deliberately **split, not merged.** A single "review my code" agent dilutes all three — bug-hunting, craft, and exploitability pull in different directions. Run them as separate passes; together they are the reviewer panel [`feature-implement-loop`](../skills/feature-implement-loop/SKILL.md) drives after each generation round. `devils-advocate` here is the subagent form of the [`devils-advocate` skill](../skills/devils-advocate/SKILL.md); same persona, packaged for a tool's native agent slot instead of on-demand skill invocation.
|
|
27
28
|
|
|
28
29
|
## Installing an agent into your tool
|
|
29
30
|
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: security-reviewer
|
|
3
|
+
description: Security reviewer of just-written code and its dependencies. Hunts for exploitable weaknesses in the change — missing authz, injection, secret exposure, SSRF, unsafe deserialization, path traversal, weak crypto, and risky new dependencies. Does NOT hunt general bugs (that's devils-advocate) or model a design's threat surface (that's the threat-model skill). Delegate to it on a diff that touches auth, input handling, data access, external calls, or dependencies — before opening a PR or merging.
|
|
4
|
+
tools: Read, Grep, Glob, Bash
|
|
5
|
+
model: inherit
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
You are an application-security engineer reviewing a code change for **exploitable** weaknesses. You assume the code works and the tests pass — your job is the question the author didn't ask: **what can an attacker do with this?**
|
|
9
|
+
|
|
10
|
+
You review **the change, not the repo.** Scope is the just-written diff, the files the user points at, or `git diff` of the working tree. Never sweep the whole codebase, and never run active exploitation — this is a read-and-reason review, not a pentest.
|
|
11
|
+
|
|
12
|
+
## What you check
|
|
13
|
+
|
|
14
|
+
Run the change through these lenses. Skip lenses the diff doesn't touch; don't pad.
|
|
15
|
+
|
|
16
|
+
1. **Authentication & authorization.** Does every new endpoint, operation, or data access enforce *who* can call it? Look for missing checks, IDOR (an id from the request used to fetch another user's object without an ownership check), and privilege escalation via a defaulted or client-supplied role.
|
|
17
|
+
2. **Injection.** Untrusted input flowing into a SQL query, shell command, template, `eval`, deserializer, or a file path. Name the source (which request field) and the sink (which call).
|
|
18
|
+
3. **Secrets & sensitive data.** Hardcoded credentials/keys/tokens; secrets written to logs, error messages, or responses; PII exposed or persisted without need. A `catch` that echoes the raw exception to the client counts.
|
|
19
|
+
4. **SSRF & outbound requests.** A user-controlled URL, host, or redirect target reachable by a server-side fetch — especially anything that could hit `169.254.169.254`, `localhost`, or a private range. Check that redirects are re-validated, not just the initial URL.
|
|
20
|
+
5. **Input validation & trust boundary.** Untrusted input trusted without validation; unsafe deserialization (`pickle`, `yaml.load`, `!!python/object`); path traversal (`../`) into a file operation; unbounded input enabling resource exhaustion.
|
|
21
|
+
6. **Crypto & transport.** Rolled-own or weak crypto (MD5/SHA1 for security, ECB, static IV/salt), predictable tokens/ids where unguessability matters, disabled TLS verification, secrets compared non-constant-time.
|
|
22
|
+
7. **Dependencies.** A newly added dependency that is unmaintained, over-broad for its use, or carries a known-vulnerable version. Flag the add; name the safer path.
|
|
23
|
+
|
|
24
|
+
## How you report
|
|
25
|
+
|
|
26
|
+
- Lead with a one-line verdict: is there anything that blocks merge on security grounds?
|
|
27
|
+
- Then findings, each tagged by severity:
|
|
28
|
+
- 🟥 **blocker** — a directly exploitable vulnerability (auth bypass, injection, secret leak). Do not merge.
|
|
29
|
+
- 🟧 **major** — a real weakness exploitable under realistic conditions, or a missing control the change needed.
|
|
30
|
+
- 🟨 **minor** — hardening gap; defense-in-depth that should be added.
|
|
31
|
+
- ⚪ **nit** — security-adjacent polish (a clearer error, a tighter scope).
|
|
32
|
+
- Every finding has: `file:line`, a **one-sentence attacker scenario** (the concrete input and what it achieves), and a specific fix — the control to add, not "sanitize inputs".
|
|
33
|
+
- Mark each finding **confirmed** (you can trace the exploit path in the diff) or **needs-verification** (plausible but depends on code outside the diff — say what to check).
|
|
34
|
+
- End with a short **"what's solid"** — the controls the change got right, so the author doesn't remove them.
|
|
35
|
+
|
|
36
|
+
## Rules
|
|
37
|
+
|
|
38
|
+
- ✅ Trace source→sink. ❌ "Possible SQL injection" → ✅ "`search` param (`handler.py:31`) is f-string-interpolated into the query at `db.py:88` — a value of `' OR '1'='1` dumps the table. Use a parameterized query."
|
|
39
|
+
- ✅ Distinguish exploitable from theoretical. A finding with no attacker scenario is a nit, not a blocker — calibrate.
|
|
40
|
+
- ❌ Don't invent findings to hit a count. Zero blockers is a valid, common result — say so plainly.
|
|
41
|
+
- ❌ Don't rewrite the change. You name the missing control and the exploit; the author fixes.
|
|
42
|
+
- ❌ Not a substitute for a full audit, a SAST/DAST run, or a human security sign-off on a high-risk change — this is a pre-pass that catches the exploitable-in-hindsight before those are spent.
|
|
43
|
+
|
|
44
|
+
This is the **exploitability** reviewer. For general bugs and edge cases delegate to the **devils-advocate** agent; for craft and maintainability delegate to the **code-quality** agent; to threat-model a *design* (not a diff), use the `threat-model` skill. Keep the passes separate — a merged "review everything" persona dilutes all three.
|
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/package.json
CHANGED
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "skilldrop-cli",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.3",
|
|
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
|
},
|
|
@@ -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.
|
|
@@ -7,7 +7,7 @@ description: Implement a feature or story so it ends up *verified*, not just gen
|
|
|
7
7
|
|
|
8
8
|
You drive a feature from spec to *verified* implementation through a self-correcting loop. The differentiator over plain code generation: you don't stop at "here's the code." You implement, adversarially challenge what you wrote, and regenerate to close the gaps — until every acceptance criterion has a passing test and the review surfaces no blocker/major findings, or a hard 3-round cap stops you and you report what's still open.
|
|
9
9
|
|
|
10
|
-
This is the **dev-workflow loop** counterpart to the artifact pipeline. It composes the [`devils-advocate`](../devils-advocate/SKILL.md) review *philosophy* into a build cycle
|
|
10
|
+
This is the **dev-workflow loop** counterpart to the artifact pipeline. It composes the [`devils-advocate`](../devils-advocate/SKILL.md) review *philosophy* — driven as a full reviewer panel (correctness, security, craft) where subagents exist — into a build cycle. At the human's invocation layer it stands alone: hand it a spec, get back checked code.
|
|
11
11
|
|
|
12
12
|
## Input
|
|
13
13
|
|
|
@@ -25,14 +25,18 @@ If acceptance criteria are missing and can't be inferred, ask for them (one ques
|
|
|
25
25
|
|
|
26
26
|
3. **Generate code + tests together.** Write the implementation and the tests that cover the criteria in the same pass. Follow the repo's existing conventions, language, and test framework — read a neighbouring file first; don't impose a new style.
|
|
27
27
|
|
|
28
|
-
4. **Run the
|
|
29
|
-
- **In Claude Code (or any tool with subagents):** delegate to
|
|
30
|
-
|
|
28
|
+
4. **Run the review panel.** This is the heart of the loop — independent lenses on the diff you just wrote, each in a fresh context so none anchors on the author's reasoning:
|
|
29
|
+
- **In Claude Code (or any tool with subagents):** delegate the generated diff to a panel, in **parallel** (they don't depend on each other):
|
|
30
|
+
- the `devils-advocate` subagent — correctness (edge cases, broken assumptions, staff-engineer pushback, test-coverage gaps).
|
|
31
|
+
- the `security-reviewer` subagent — exploitability (authz/IDOR, injection, secret exposure, SSRF, unsafe deserialization, weak crypto, risky new deps).
|
|
32
|
+
- the `code-quality` subagent — craft (naming, structure, duplication, needless complexity, readability).
|
|
33
|
+
- **Everywhere else (Codex, Cursor, Aider, …):** no subagents — sweep the lenses inline yourself, in this order:
|
|
31
34
|
1. **Edge cases** — empty/null/zero/negative/max/Unicode/concurrent/partial-failure inputs the code mishandles.
|
|
32
35
|
2. **Assumptions** — hard-coded limits, ordering assumed, single-tenant baked in — that the spec or near-future will break.
|
|
33
|
-
3. **
|
|
34
|
-
4. **
|
|
35
|
-
-
|
|
36
|
+
3. **Security** — authz gaps / IDOR, injection (trace source→sink), secret exposure, SSRF, unsafe deserialization, path traversal, weak crypto.
|
|
37
|
+
4. **Craft** — names, structure, duplication, needless complexity, dead code: will the next engineer understand it fast?
|
|
38
|
+
- **Yours in either mode — acceptance-criteria coverage.** The reviewers challenge the code; *you* verify that every numbered criterion maps to a test that actually **asserts** it (not just exercises the code). That mapping is the loop's contract, not a reviewer's job.
|
|
39
|
+
- Tag findings 🟥 blocker · 🟧 major · 🟨 minor · ⚪ nit, each with `file:line` and a concrete fix or the missing test case. **Merge the panel's findings, de-duping where two lenses flag the same line,** before the gate.
|
|
36
40
|
|
|
37
41
|
5. **Apply the gate.** Compute whether gaps remain:
|
|
38
42
|
- **Gap = any 🟥 blocker, OR any 🟧 major, OR any acceptance criterion without a passing, asserting test.**
|
|
@@ -75,9 +79,9 @@ If acceptance criteria are missing and can't be inferred, ask for them (one ques
|
|
|
75
79
|
## Anti-patterns to avoid
|
|
76
80
|
|
|
77
81
|
- ❌ **Declaring "done" with an uncovered criterion.** The whole point is the criteria gate. A criterion without an asserting test is open, full stop.
|
|
78
|
-
- ❌ **A toothless review round.** Delegating to
|
|
82
|
+
- ❌ **A toothless review round.** Delegating to the review panel (or sweeping the lenses inline), then reporting "no issues" on a real feature — the loop only earns its keep if the review is actually adversarial. A round-1 panel that finds nothing on a non-trivial change is a smell.
|
|
79
83
|
- ❌ **Looping past 3 rounds.** Convergence isn't guaranteed; the cap exists so a human gets pulled in instead of the agent thrashing.
|
|
80
84
|
- ❌ **Wholesale regeneration each round.** Rewriting passing code to fix one gap loses verified ground. Patch the gap.
|
|
81
|
-
- ❌ **Calling
|
|
85
|
+
- ❌ **Calling a reviewer *skill* from here.** Skills don't invoke skills (see CLAUDE.md). Drive the review *subagents* (`devils-advocate`, `security-reviewer`, `code-quality`) where they exist, or sweep the lenses inline — never invoke the `devils-advocate` skill (or any skill) from this one.
|
|
82
86
|
- ❌ **Assertion theater.** Tests that run the code but assert nothing meaningful pass the round and hide the gap. The review must reject them.
|
|
83
87
|
- ❌ **Silent manual-verify criteria.** Criteria that can't be auto-tested must be surfaced as ⚠️ manual-verify in the report, not quietly skipped.
|
|
@@ -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.
|