skilldrop-cli 0.8.3 → 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,
@@ -109,6 +109,7 @@ Skills for the direction-setting layer above any single feature — testing whet
109
109
  | Skill | What it does |
110
110
  |---|---|
111
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`. |
@@ -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.3",
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",
@@ -38,8 +38,9 @@ If acceptance criteria are missing and can't be inferred, ask for them (one ques
38
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
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
40
 
41
- 5. **Apply the gate.** Compute whether gaps remain:
42
- - **Gap = any 🟥 blocker, OR any 🟧 major, OR any acceptance criterion without a passing, asserting test.**
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.**
43
44
  - 🟨 minor / ⚪ nit findings do **not** force another round — list them, don't loop on them.
44
45
 
45
46
  6. **Loop or stop.**
@@ -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())