skilldrop-cli 0.8.2 → 0.8.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -3,9 +3,9 @@
3
3
  [![npm](https://img.shields.io/npm/v/skilldrop-cli)](https://www.npmjs.com/package/skilldrop-cli)
4
4
  [![license](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
5
5
 
6
- **[Browse the catalogue →](https://sananthanarayan.github.io/skilldrop/)** — all 50 skills, filterable by pack, tag, and model tier.
6
+ **[Browse the catalogue →](https://sananthanarayan.github.io/skilldrop/)** — all 51 skills, filterable by pack, tag, and model tier.
7
7
 
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:
8
+ **51 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,8 @@ 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 an adversarial review pass (delegating to the `devils-advocate` subagent where available, sweeping the four 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. |
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
+ | [`pre-merge-review`](skills/pre-merge-review/SKILL.md) | Gate an existing change before merge with **one** command that fires the whole review fleet: a deterministic **mechanical gate** (`scripts/gate.py` runs the project's lint + typecheck + tests; its exit code decides — un-bypassable) **and** the three-reviewer panel — `devils-advocate` (correctness), `security-reviewer` (exploitability), `code-quality` (craft) — dispatched in parallel, then a single **READY / NOT READY** verdict (READY only when the gate is green *and* no blocker/major stands). Where `feature-implement-loop` *generates* and loops, this *judges* a diff you already have; design disagreements escalate to `council-review`. |
112
113
  | [`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
114
  | [`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
115
  | [`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`. |
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 both jobs — bug-hunting and craft pull in different directions. Run them as two passes. `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
+ 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.
@@ -115,6 +115,7 @@
115
115
  "sonar-review": { "tier": "standard", "rationale": "Formats Sonar API findings into a report. The hard analysis is Sonar's; this is synthesis. Escalate on very large diffs." },
116
116
  "ai-usage-report": { "tier": "standard", "rationale": "Tabulates telemetry into a report. Mostly mechanical, but the 'AI theater' effectiveness call needs judgment." },
117
117
  "feature-implement-loop": { "tier": "standard", "rationale": "Code + test generation against acceptance criteria. The adversarial pass delegates to devils-advocate (heavy) where available; the loop itself is generation." },
118
+ "pre-merge-review": { "tier": "standard", "rationale": "Orchestration: runs a deterministic gate script, dispatches the (heavy) reviewer subagents, and renders a rule-based go/no-go. The adversarial reasoning is delegated to the panel, so the skill itself is coordination." },
118
119
  "user-story-splitter": { "tier": "standard", "rationale": "Decomposes an epic into INVEST slices with testable ACs. Structured synthesis; escalation rules cover large or contradictory PRDs." },
119
120
  "test-plan-generator": { "tier": "standard", "rationale": "Synthesizes a structured plan from a change description. Risk ranking needs judgment but is bounded; escalate via ambiguous-input on contested or high-stakes releases." },
120
121
  "release-notes": { "tier": "standard", "rationale": "Classifies commits and rewrites them for a reader. The audience translation needs judgment; escalate via large-input on very long ranges." },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "skilldrop-cli",
3
- "version": "0.8.2",
3
+ "version": "0.8.4",
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
5
  "author": {
6
6
  "name": "Sanjay Ananthanarayan",
package/packs.json CHANGED
@@ -47,6 +47,7 @@
47
47
  "agents-md-generator",
48
48
  "user-story-splitter",
49
49
  "feature-implement-loop",
50
+ "pre-merge-review",
50
51
  "devils-advocate",
51
52
  "council-review",
52
53
  "test-plan-generator",
@@ -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 — but at the human's invocation layer it stands alone: hand it a spec, get back checked code.
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,17 +25,22 @@ 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 adversarial review pass.** This is the heart of the loop. Challenge what you just wrote:
29
- - **In Claude Code (or any tool with subagents):** delegate to the `devils-advocate` subagent on the generated diff.
30
- - **Everywhere else (Codex, Cursor, Aider, …):** run the review inline, sweeping the four lenses yourself:
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. **Staff-engineer pushback** — concurrency, error handling, security (authz/injection/secrets), observability, blast radius, idempotency.
34
- 4. **Acceptance-criteria coverage** — for each numbered criterion: is there a test, and does it actually assert the criterion (not just exercise the code)?
35
- - Tag findings 🟥 blocker · 🟧 major · 🟨 minor · ⚪ nit, each with `file:line` and a concrete fix or the missing test case.
36
-
37
- 5. **Apply the gate.** Compute whether gaps remain:
38
- - **Gap = any 🟥 blocker, OR any 🟧 major, OR any acceptance criterion without a passing, asserting test.**
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.
40
+
41
+ 5. **Apply the gate — mechanical first, then findings.**
42
+ - **Mechanical (un-bypassable):** *run* the project's verify — the tests you wrote **plus** its lint/typecheck — and honor the exit code. A non-zero result is an automatic gap, not a judgment call; you may not report `VERIFIED` while any command is red. The pass/fail is the command's exit code, not your read of the diff. (This is the deterministic half; the review is the judgment half. For a standalone gate on an existing change, `pre-merge-review` wraps this in a script.)
43
+ - **Findings gap = any 🟥 blocker, OR any 🟧 major, OR any acceptance criterion without a passing, asserting test.**
39
44
  - 🟨 minor / ⚪ nit findings do **not** force another round — list them, don't loop on them.
40
45
 
41
46
  6. **Loop or stop.**
@@ -75,9 +80,9 @@ If acceptance criteria are missing and can't be inferred, ask for them (one ques
75
80
  ## Anti-patterns to avoid
76
81
 
77
82
  - ❌ **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 devils-advocate or sweeping the lenses, then reporting "no issues" on a real feature — the loop only earns its keep if the review actually adversarial.
83
+ - ❌ **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
84
  - ❌ **Looping past 3 rounds.** Convergence isn't guaranteed; the cap exists so a human gets pulled in instead of the agent thrashing.
80
85
  - ❌ **Wholesale regeneration each round.** Rewriting passing code to fix one gap loses verified ground. Patch the gap.
81
- - ❌ **Calling the `devils-advocate` *skill* from here.** Skills don't invoke skills (see CLAUDE.md). Drive the *subagent* where one exists, or inline the four lenses. Same applies to any other skill.
86
+ - ❌ **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
87
  - ❌ **Assertion theater.** Tests that run the code but assert nothing meaningful pass the round and hide the gap. The review must reject them.
83
88
  - ❌ **Silent manual-verify criteria.** Criteria that can't be auto-tested must be surfaced as ⚠️ manual-verify in the report, not quietly skipped.
@@ -5,7 +5,7 @@
5
5
  "entrypoint": "SKILL.md",
6
6
  "deps": { "npm": [], "pip": [] },
7
7
  "env": { "required": [], "optional": [] },
8
- "related": ["design-doc", "devils-advocate", "doc-critique"],
8
+ "related": ["design-doc", "devils-advocate", "doc-critique", "pre-merge-review"],
9
9
  "tags": ["dev-workflow", "implementation", "tdd", "acceptance-criteria", "adversarial", "self-correcting", "code-generation"],
10
10
  "model": { "tier": "standard", "rationale": "Code + test generation against acceptance criteria. The adversarial pass delegates to devils-advocate (heavy) where available; the loop itself is generation." }
11
11
  }
@@ -0,0 +1,83 @@
1
+ ---
2
+ name: pre-merge-review
3
+ description: Gate an existing change for merge — run one skill that fires the whole production-readiness pass. First a deterministic mechanical gate (lint + typecheck + tests, via a script whose exit code decides), then the three-reviewer panel dispatched in parallel — devils-advocate (correctness), security-reviewer (exploitability), code-quality (craft) — then a single READY / NOT READY verdict. Use before opening a PR or merging, when you want the full review fleet run on a diff you already have. Do NOT use to implement a feature from a spec (that's feature-implement-loop) or to review a document (that's doc-critique).
4
+ ---
5
+
6
+ # pre-merge-review
7
+
8
+ One command that runs the full production-readiness pass on a change you already have: the **mechanical gate** (deterministic — real commands, real exit codes) and the **reviewer panel** (three cold-read subagents), then a single **READY / NOT READY** verdict. This is the "is this safe to merge?" orchestrator — where [`feature-implement-loop`](../feature-implement-loop/SKILL.md) *generates* code and loops, this one *judges* a diff that already exists and gates it.
9
+
10
+ Two gates, and they are different kinds. The mechanical gate is **not a judgment call** — a script runs the project's checks and its exit code decides, so a red gate can't be talked past. The review panel *is* judgment — three lenses that a deterministic check can't cover.
11
+
12
+ ## How to respond
13
+
14
+ 1. **Scope the change.** The diff, the branch vs. its base, or the files the user names. Everything below reviews *that change*, never the whole repo.
15
+
16
+ 2. **Run the mechanical gate first — it is un-bypassable.** Run the bundled gate script:
17
+ - Claude Code: `python3 ${CLAUDE_SKILL_DIR}/scripts/gate.py`
18
+ - Other IDEs: `python3 skills/pre-merge-review/scripts/gate.py`
19
+
20
+ It auto-detects the project's lint / typecheck / test commands (or takes `--cmd "…"` / `--config gate.json`), runs each, and **exits non-zero if any fail**. A **RED gate is an automatic `NOT READY`** — do not proceed to a positive verdict, no matter how clean the review looks. The pass/fail is the script's exit code, not your assessment of the code. If the gate can't determine commands, surface `BLOCKED: need verify commands` and ask for them once — never fabricate a green gate.
21
+
22
+ 3. **Fire the review panel — in parallel.** Independent lenses on the diff, each in a fresh context so none anchors on the author's reasoning:
23
+ - **Subagent tools (Claude Code, …):** dispatch the `devils-advocate` subagent (correctness — edge cases, broken assumptions, staff-engineer pushback, test gaps), the `security-reviewer` subagent (exploitability — authz/IDOR, injection, secret exposure, SSRF, unsafe deserialization, weak crypto, risky deps), and the `code-quality` subagent (craft — naming, structure, duplication, readability). Run them together; they don't depend on each other.
24
+ - **No-subagent tools (Codex, Cursor, Aider, …):** sweep the three lenses inline yourself — correctness, then security, then craft.
25
+ - Tag findings 🟥 blocker · 🟧 major · 🟨 minor · ⚪ nit, each with `file:line` and a concrete fix. **Merge and de-dupe** across the three lenses — when two flag the same line, report it once with both angles.
26
+
27
+ 4. **Render the verdict.** One word, then the reasons:
28
+ - **`READY`** — the mechanical gate is GREEN **and** there is no 🟥 blocker and no 🟧 major.
29
+ - **`NOT READY`** — the gate is RED, **or** any blocker/major stands. List every blocking item (gate failures first, then blocker/major findings) and what must change. 🟨 minor / ⚪ nit are listed but never block.
30
+ - **`BLOCKED`** — the gate couldn't run (unknown commands, no test setup). Name what's missing.
31
+
32
+ 5. **Escalate design disagreements — don't adjudicate them.** If the panel surfaces a genuine *tradeoff* dispute (not a bug — "should this be a queue or a cron?"), the verdict still gates on gate+blocker/major, but **offer [`council-review`](../council-review/SKILL.md)** for the open decision rather than settling a design debate here.
33
+
34
+ 6. **Report.** Lead with the verdict. Then: the gate results table (command → PASS/FAIL), the merged panel findings by severity, and a **must-fix** list for `NOT READY`.
35
+
36
+ ## Useful references in this skill
37
+
38
+ - [`scripts/gate.py`](scripts/gate.py) — the deterministic mechanical gate (`--list` to preview, `--cmd`/`--config` to override detection, `--install-hook` to enforce it — see below).
39
+
40
+ ## Enforce as a harness (opt-in)
41
+
42
+ The verdict above is *advisory* — an agent can ignore prose. To make the gate **un-bypassable**, move enforcement into the substrate every tool shares (git and CI), not this skill's text:
43
+
44
+ - **Layer 1 — local git block (portable across every tool).** Install a blocking pre-commit hook:
45
+ - Claude Code: `python3 ${CLAUDE_SKILL_DIR}/scripts/gate.py --install-hook --cmd "npm test" --cmd "npm run lint"`
46
+ - other IDEs: `python3 skills/pre-merge-review/scripts/gate.py --install-hook --cmd "…"`
47
+
48
+ Git runs it on every `git commit` no matter which tool (Claude, Cursor, Codex, Aider) drove the change; a RED gate blocks the commit. Bypassable with `git commit --no-verify`, so it stops honest mistakes, not a determined override. `--uninstall-hook` removes it.
49
+ - **Layer 2 — CI required checks (un-bypassable).** Run the same commands as a required status check on the branch; the git host refuses the merge when they're red, outside any local tool — the layer that can't be `--no-verify`'d. Pair Layer 1 (fast local feedback) with Layer 2 (the backstop).
50
+ - **Layer 3 — per-tool native (richer where it exists).** Where a tool can block its own turn, point it at the gate: Claude Code — a `Stop`/`PreToolUse` hook running `gate.py`; Aider — `--auto-test --test-cmd "python3 …/gate.py"`. Cursor and Codex lean on Layers 1–2.
51
+
52
+ The honest limit: a portable *skill* can't physically block an agent — enforcement lives in git/CI (universal) or per-tool hooks (uneven). Rationale and the distinction from RFC-0006's reminder-only hooks: RFC-0019.
53
+
54
+ ## Quality bar
55
+
56
+ - **The mechanical gate is run, not assumed.** A verdict with no real gate result is invalid — you ran the script (or reported `BLOCKED`), you didn't eyeball the tests.
57
+ - **The gate is un-bypassable.** A RED gate is `NOT READY` regardless of how good the review looks. The review can add blockers; it can never clear a failing gate.
58
+ - **Findings are merged and de-duped** across the three lenses — the same line is not reported three times.
59
+ - **The verdict is one word plus the blocking reasons**, not a hedged paragraph. A reader sees READY/NOT READY and exactly what stands in the way.
60
+ - **Design disagreements go to `council-review`**, not adjudicated here — this skill gates a change, it doesn't decide an architecture.
61
+
62
+ ## When to use this skill
63
+
64
+ - ✅ Before opening a PR or merging — the full production-readiness pass on a diff you already have.
65
+ - ✅ "Run all the reviewers on this change and tell me if it's safe to merge."
66
+ - ✅ Reviewing a change you (or another agent) generated elsewhere, or a teammate's branch.
67
+
68
+ ## When NOT to use this skill
69
+
70
+ - ❌ Implementing a feature from a spec — that's [`feature-implement-loop`](../feature-implement-loop/SKILL.md), which generates and loops; this skill only judges an existing change.
71
+ - ❌ One lens is enough — a pure bug pass is [`devils-advocate`](../devils-advocate/SKILL.md) standalone; a pure doc pass is `doc-critique`.
72
+ - ❌ An open design decision ("Postgres or Dynamo?") — that's `council-review`.
73
+
74
+ ## Anti-patterns to avoid
75
+
76
+ - ❌ **Declaring `READY` on a RED gate.** The whole point is that the mechanical gate is un-bypassable. A failing test or lint is `NOT READY`, full stop — the review does not get a vote on it.
77
+ - ❌ **Skipping the gate and "just reviewing".** Without the deterministic pass this is only half the check — the half that can be fooled. Run `scripts/gate.py`.
78
+ - ❌ **A toothless panel.** Firing the three subagents (or sweeping the lenses) and reporting "no issues" on a real change — the panel earns its keep only if it's actually adversarial.
79
+ - ❌ **Triple-reporting.** The same `file:line` flagged by all three lenses as three findings. Merge them.
80
+ - ❌ **Adjudicating a design debate.** Settling "queue vs cron" inside the verdict instead of gating on gate+severity and offering `council-review`.
81
+ - ❌ **Calling a reviewer *skill* from here.** Skills don't invoke skills (see CLAUDE.md). Drive the review *subagents* where they exist, or sweep the lenses inline.
82
+
83
+ **Non-interactive:** if the verify commands can't be detected and none were supplied, emit `BLOCKED: need verify commands (--cmd/--config)` — never fabricate a green gate. If the change scope is ambiguous, default to the working-tree diff and tag it `[assumption]` at the top of the report.
@@ -0,0 +1,11 @@
1
+ [
2
+ { "query": "is this change ready to merge?", "should_trigger": true },
3
+ { "query": "run all the reviewers on my branch before I open the PR", "should_trigger": true },
4
+ { "query": "production-readiness review of this diff", "should_trigger": true },
5
+ { "query": "gate my PR — lint, tests, and a full review", "should_trigger": true },
6
+ { "query": "review this branch and tell me if it's safe to ship", "should_trigger": true },
7
+ { "query": "implement this feature from the ticket's acceptance criteria", "should_trigger": false },
8
+ { "query": "just find the bugs in this function", "should_trigger": false },
9
+ { "query": "critique this design doc", "should_trigger": false },
10
+ { "query": "should we use Postgres or DynamoDB for this?", "should_trigger": false }
11
+ ]
@@ -0,0 +1,26 @@
1
+ {
2
+ "skill_name": "pre-merge-review",
3
+ "evals": [
4
+ {
5
+ "id": "red-gate-is-not-ready",
6
+ "prompt": "Run a pre-merge review on my branch that adds a /export endpoint. The project is Python; `pytest` currently fails on one new test, and the endpoint reads user_id from the query string.",
7
+ "assertions": [
8
+ "Runs the mechanical gate (scripts/gate.py or the project's lint/typecheck/test commands) before or alongside the review — does not skip it",
9
+ "Treats the failing pytest as a RED gate that forces NOT READY on its own, independent of the review findings",
10
+ "Dispatches the three-reviewer panel (devils-advocate, security-reviewer, code-quality) or sweeps the three lenses inline",
11
+ "Flags the query-string user_id as an authorization/IDOR blocker with a concrete attacker path and a fix",
12
+ "Ends with a one-word verdict (NOT READY) plus an ordered must-fix list, gate failures first",
13
+ "Does not report the same file:line three times — findings are merged across lenses"
14
+ ]
15
+ },
16
+ {
17
+ "id": "clean-change-ready",
18
+ "prompt": "Pre-merge review this small, well-tested refactor branch — tests and lint pass locally.",
19
+ "assertions": [
20
+ "Runs the gate and reports it GREEN before rendering a positive verdict",
21
+ "Only returns READY when the gate is green AND there is no blocker and no major finding",
22
+ "Lists any minor/nit findings without letting them block the verdict"
23
+ ]
24
+ }
25
+ ]
26
+ }
@@ -0,0 +1,43 @@
1
+ # Worked example — a NOT READY verdict where the mechanical gate is decisive
2
+
3
+ Input → output oracle for `pre-merge-review`. Shows the un-bypassable gate: a clean-looking
4
+ review does not clear a red test run, and the verdict leads with one word plus the blocking reasons.
5
+
6
+ ## Input
7
+
8
+ > "Review my branch before I open the PR — it adds a `/export` endpoint that streams a user's
9
+ > records as CSV."
10
+
11
+ The change: a new `export_handler` + one new test.
12
+
13
+ ## Output
14
+
15
+ **Verdict: `NOT READY`** — the mechanical gate is RED, and the panel adds one blocker.
16
+
17
+ ### Mechanical gate (`scripts/gate.py`, auto-detected)
18
+
19
+ | Command | Result |
20
+ |---|---|
21
+ | `ruff check .` | PASS |
22
+ | `mypy .` | PASS |
23
+ | `pytest -q` | **FAIL** — `test_export_empty` errors: `export_handler` raises `TypeError` on a user with zero records |
24
+
25
+ `GATE: RED — 1/3 command(s) failed.` A red gate is `NOT READY` on its own; the review below can only *add* blockers, never clear this.
26
+
27
+ ### Review panel (merged, de-duped)
28
+
29
+ 🟥 **blocker — missing authorization (security-reviewer).** `export_handler` reads `user_id` from the **query string** (`handler.py:12`) and returns that user's records with no check that the caller *is* that user — any authenticated user can export anyone's data (IDOR). *Fix:* derive `user_id` from the session, not the request; if an admin export is intended, gate it behind an explicit role check.
30
+
31
+ 🟧 **major — unbounded response (devils-advocate).** The CSV is built in memory for the full record set (`handler.py:20`); a user with millions of rows OOMs the process. *Fix:* stream rows, or paginate/cap.
32
+
33
+ 🟨 minor — **craft (code-quality):** `export_handler` mixes fetch, format, and HTTP concerns in one 60-line function (`handler.py:8-68`); extract `rows_to_csv()`. *(listed, does not block.)*
34
+
35
+ ### Must-fix before merge
36
+
37
+ 1. **Gate:** fix `export_handler` on the empty-records case so `pytest` is green.
38
+ 2. **Blocker:** derive `user_id` from the session — close the IDOR.
39
+ 3. **Major:** stream/cap the CSV to bound memory.
40
+
41
+ ## Why this passes the quality bar
42
+
43
+ The gate was *run*, not assumed, and its RED result decides the verdict regardless of the review; findings are merged across the three lenses (security, correctness, craft) with no triple-reporting; the verdict is one word plus an ordered must-fix list — gate failures first, then blocker/major. A no-victim security note wasn't inflated to a blocker; the IDOR — with a concrete attacker path — was.
@@ -0,0 +1,11 @@
1
+ {
2
+ "name": "pre-merge-review",
3
+ "version": "0.1.0",
4
+ "description": "Gate an existing change for merge — run one skill that fires the whole production-readiness pass. First a deterministic mechanical gate (lint + typecheck + tests, via a script whose exit code decides), then the three-reviewer panel dispatched in parallel — devils-advocate (correctness), security-reviewer (exploitability), code-quality (craft) — then a single READY / NOT READY verdict. Use before opening a PR or merging, when you want the full review fleet run on a diff you already have. Do NOT use to implement a feature from a spec (that's feature-implement-loop) or to review a document (that's doc-critique).",
5
+ "entrypoint": "SKILL.md",
6
+ "deps": { "npm": [], "pip": [] },
7
+ "env": { "required": [], "optional": [] },
8
+ "related": ["feature-implement-loop", "devils-advocate", "council-review", "doc-critique"],
9
+ "tags": ["dev-workflow", "code-review", "pre-merge", "quality-gate", "subagents", "production-readiness"],
10
+ "model": { "tier": "standard", "rationale": "Coordination: runs a deterministic gate script, dispatches the (heavy) reviewer subagents, and renders a rule-based go/no-go. The adversarial reasoning is delegated to the panel, so the skill itself is orchestration, not deep judgment." }
11
+ }
@@ -0,0 +1,186 @@
1
+ #!/usr/bin/env python3
2
+ """Mechanical merge gate — run a project's verify commands and exit non-zero if any fail.
3
+
4
+ The deterministic half of `pre-merge-review`: the pass/fail verdict comes from *running real
5
+ commands*, not from a judgment call, so an agent can't talk its way past a red gate. The commands
6
+ are the operator's own (auto-detected, or supplied) — this is a local dev tool, not a sandbox.
7
+
8
+ python3 gate.py # auto-detect lint / typecheck / test for the project
9
+ python3 gate.py --cmd "npm test" --cmd "npm run lint" # explicit commands (repeatable)
10
+ python3 gate.py --config gate.json # {"commands": ["pytest -q", "ruff check ."]}
11
+ python3 gate.py --list # print what it would run, don't run
12
+ python3 gate.py --root path/to/repo # run against another project root
13
+
14
+ Precedence: --cmd (explicit) > --config > auto-detect. Runs each command in order, streams its
15
+ output, prints a PASS/FAIL line per command and an overall verdict. Exit 0 iff every command
16
+ passed; 1 if any failed; 2 if there was nothing to run. Stdlib only; no network.
17
+ """
18
+ import argparse
19
+ import json
20
+ import os
21
+ import re
22
+ import shlex
23
+ import shutil
24
+ import subprocess
25
+ import sys
26
+
27
+ HOOK_MARKER = "skilldrop-gate"
28
+
29
+
30
+ def detect(root):
31
+ """Best-effort verify commands for common stacks. Modest on purpose — an operator with a
32
+ non-standard setup passes --cmd/--config; auto-detect covers the usual cases."""
33
+ cmds = []
34
+ has = lambda f: os.path.exists(os.path.join(root, f))
35
+
36
+ if any(has(f) for f in ("pyproject.toml", "setup.py", "pytest.ini", "tox.ini")):
37
+ if shutil.which("ruff"):
38
+ cmds.append("ruff check .")
39
+ if shutil.which("mypy") and has("mypy.ini") or (shutil.which("mypy") and has("pyproject.toml")):
40
+ cmds.append("mypy .")
41
+ if shutil.which("pytest"):
42
+ cmds.append("pytest -q")
43
+
44
+ if has("package.json"):
45
+ try:
46
+ scripts = json.load(open(os.path.join(root, "package.json"))).get("scripts", {})
47
+ except (OSError, json.JSONDecodeError):
48
+ scripts = {}
49
+ if "lint" in scripts:
50
+ cmds.append("npm run lint")
51
+ if "typecheck" in scripts:
52
+ cmds.append("npm run typecheck")
53
+ if "test" in scripts:
54
+ cmds.append("npm test")
55
+
56
+ if has("go.mod"):
57
+ cmds += ["go vet ./...", "go test ./..."]
58
+
59
+ if not cmds and has("Makefile"):
60
+ mk = "\n" + open(os.path.join(root, "Makefile"), encoding="utf-8", errors="replace").read()
61
+ cmds += [f"make {t}" for t in ("lint", "typecheck", "test") if f"\n{t}:" in mk]
62
+
63
+ return cmds
64
+
65
+
66
+ def load_config(path):
67
+ data = json.load(open(path, encoding="utf-8"))
68
+ cmds = data.get("commands") if isinstance(data, dict) else data
69
+ if not isinstance(cmds, list) or not all(isinstance(c, str) for c in cmds):
70
+ raise SystemExit("gate: --config must hold a list of command strings (or {\"commands\": [...]})")
71
+ return cmds
72
+
73
+
74
+ # --- opt-in harness: a blocking git pre-commit hook (Layer 1) ---------------------------
75
+ # The portable enforcement point — git runs it regardless of which agent/tool wrote the code.
76
+ # Bypassable with `git commit --no-verify`; CI required-checks are the un-bypassable backstop.
77
+
78
+ def _hooks_dir(root):
79
+ r = subprocess.run(["git", "rev-parse", "--git-path", "hooks"], cwd=root, capture_output=True, text=True)
80
+ if r.returncode != 0:
81
+ raise SystemExit("gate: not a git repository — run --install-hook from inside your repo")
82
+ hd = r.stdout.strip()
83
+ return hd if os.path.isabs(hd) else os.path.join(root, hd)
84
+
85
+
86
+ def _hook_block(cmd, config):
87
+ inv = f"python3 {shlex.quote(os.path.abspath(__file__))}"
88
+ for c in cmd:
89
+ inv += f" --cmd {shlex.quote(c)}"
90
+ if config:
91
+ inv += f" --config {shlex.quote(os.path.abspath(config))}"
92
+ return (
93
+ f"# >>> {HOOK_MARKER} >>> pre-merge-review gate — run `gate.py --uninstall-hook` to remove\n"
94
+ f"{inv} || {{ echo 'skilldrop-gate: commit blocked (gate RED). Fix it, or override once with "
95
+ f"--no-verify — CI required-checks still gate the merge.' >&2; exit 1; }}\n"
96
+ f"# <<< {HOOK_MARKER} <<<\n"
97
+ )
98
+
99
+
100
+ def install_hook(root, cmd, config):
101
+ hooks = _hooks_dir(root)
102
+ os.makedirs(hooks, exist_ok=True)
103
+ path = os.path.join(hooks, "pre-commit")
104
+ body = open(path, encoding="utf-8").read() if os.path.exists(path) else ""
105
+ if HOOK_MARKER in body:
106
+ print(f"gate: hook already installed at {path} — --uninstall-hook then --install-hook to change its commands")
107
+ return 0
108
+ if not body.startswith("#!"):
109
+ body = "#!/bin/sh\n" + body
110
+ if body and not body.endswith("\n"):
111
+ body += "\n"
112
+ body += "\n" + _hook_block(cmd, config)
113
+ with open(path, "w", encoding="utf-8") as fh:
114
+ fh.write(body)
115
+ os.chmod(path, 0o755)
116
+ print(f"gate: installed a blocking pre-commit hook -> {path}")
117
+ print(" a RED gate now blocks `git commit` (any tool). Bypass once with --no-verify; "
118
+ "wire CI required-checks for the un-bypassable backstop.")
119
+ return 0
120
+
121
+
122
+ def uninstall_hook(root):
123
+ path = os.path.join(_hooks_dir(root), "pre-commit")
124
+ if not os.path.exists(path):
125
+ print("gate: no pre-commit hook to remove")
126
+ return 0
127
+ body = open(path, encoding="utf-8").read()
128
+ new = re.sub(rf"\n?# >>> {HOOK_MARKER} >>>.*?# <<< {HOOK_MARKER} <<<\n?", "\n", body, flags=re.S)
129
+ if new == body:
130
+ print(f"gate: no {HOOK_MARKER} block in {path} — left untouched")
131
+ return 0
132
+ open(path, "w", encoding="utf-8").write(new)
133
+ print(f"gate: removed the {HOOK_MARKER} block from {path}")
134
+ return 0
135
+
136
+
137
+ def main():
138
+ ap = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
139
+ ap.add_argument("--cmd", action="append", default=[], help="a verify command (repeatable)")
140
+ ap.add_argument("--config", help='JSON file: {"commands": [...]}')
141
+ ap.add_argument("--root", default=".", help="project root (default: cwd)")
142
+ ap.add_argument("--list", action="store_true", help="print the commands the gate would run, don't run them")
143
+ ap.add_argument("--install-hook", action="store_true",
144
+ help="install a blocking git pre-commit hook that runs this gate (with the given --cmd/--config)")
145
+ ap.add_argument("--uninstall-hook", action="store_true", help="remove the skilldrop-gate pre-commit hook block")
146
+ args = ap.parse_args()
147
+
148
+ root = os.path.abspath(args.root)
149
+ if args.uninstall_hook:
150
+ return uninstall_hook(root)
151
+ if args.install_hook:
152
+ return install_hook(root, args.cmd, args.config)
153
+ cmds = args.cmd or (load_config(args.config) if args.config else detect(root))
154
+ if not cmds:
155
+ print("gate: no verify commands — pass --cmd/--config, or run inside a project the gate can "
156
+ "detect (Python / Node / Go / Makefile).", file=sys.stderr)
157
+ return 2
158
+
159
+ if args.list:
160
+ print("gate would run, in order:")
161
+ for c in cmds:
162
+ print(" -", c)
163
+ return 0
164
+
165
+ results = []
166
+ for c in cmds:
167
+ print(f"\n=== gate: {c} ===", flush=True)
168
+ rc = subprocess.run(c, shell=True, cwd=root).returncode # operator-supplied commands, by design
169
+ results.append((c, rc))
170
+ print(f"--- {'PASS' if rc == 0 else 'FAIL'} ({c}) rc={rc} ---", flush=True)
171
+
172
+ print("\n" + "=" * 48)
173
+ for c, rc in results:
174
+ print(f" {'PASS' if rc == 0 else 'FAIL'} {c}")
175
+ print("=" * 48)
176
+
177
+ failed = [c for c, rc in results if rc != 0]
178
+ if failed:
179
+ print(f"GATE: RED — {len(failed)}/{len(results)} command(s) failed. Change is NOT READY to merge.")
180
+ return 1
181
+ print(f"GATE: GREEN — all {len(results)} command(s) passed.")
182
+ return 0
183
+
184
+
185
+ if __name__ == "__main__":
186
+ sys.exit(main())