Skip to content
This repository was archived by the owner on Jul 20, 2026. It is now read-only.

srobroek/speckit-gate

Repository files navigation

speckit-gate

Harness-agnostic workflow gates for spec-kit projects.

Prevents out-of-order spec-kit command invocations by evaluating prerequisite artefacts at hook time. Supports hard-deny enforcement on Claude Code and Codex; advisory-only enforcement on all other spec-kit harnesses.

Install

Option 1 — uvx (one-shot, no install required)

# Scan and initialise gates in the current project
uvx speckit-gate init --defaults
uvx speckit-gate compile

# Install Claude Code hooks
uvx speckit-gate install --harness claude

Option 2 — Claude Code plugin (persistent, recommended for Claude Code)

# From within Claude Code, or via the CLI:
claude plugin install srobroek/speckit-gate

The .claude-plugin/plugin.json in adapters/claude/ wires UserPromptExpansion, PreToolUse:Skill, and PreToolUse:Agent hooks.

Option 3 — spec-kit bundle (all harnesses, advisory enforcement)

# Add the community bundle catalog and install
specify bundle catalog add https://github.com/srobroek/speckit-gate/releases/latest/download/bundles.json --id speckit-gate
specify bundle install speckit-gate

See Bundle adapter rationale below.

Option 4 — APM external source

# In apm.yml:
dependencies:
  apm:
    - repo: srobroek/speckit-gate
      ref: ">=0.1.0"
apm install

Quick start

# gates.yaml — 10-line example
config:
  prefix: speckit.
  feature_root: specs
  resolve: [git-branch, newest-dir]

gates:
  plan:
    requires: [specify]
    produces: [specs/<feat>/plan.md]
    context: Decomposes spec into implementation plan.
  tasks:
    requires: [plan]
    produces: [specs/<feat>/tasks.md]
    context: Decomposes plan into discrete tasks.

Compile to nodes.json:

speckit-gate compile
# → Compiled 2 gates → .specify/gates/nodes.json

Agent-driven interview

Use the speckit-gate skill (in skills/speckit-gate/SKILL.md) with Claude Code or any skill-capable harness. The skill:

  1. Scans the project for spec-kit commands.
  2. Shows a full proposal table (known prerequisites pre-filled).
  3. Asks ONE question per unknown command.
  4. Writes gates.yaml via init --answers and runs compile.

For no-agent setup run speckit-gate init --interactive or speckit-gate init --defaults.

Harness enforcement matrix

A gate is only as strong as its blindest channel, and every harness differs by invocation channel: what happens when the user types a command vs. when the model (a workflow engine, a subagent) evokes one mid-run. A gate that covers only the user channel is silently bypassed by agent-driven runs — so the matrix is per-channel. All deny rows below are verified against harness source (mid-2026); adapters currently ship for Claude Code and Codex.

Harness User invocation Model/agent evocation Adapter
Claude Code denyUserPromptExpansion, sees command name denyPreToolUse: Skill|Agent shipped
Qwen Code denyUserPromptExpansion, sees command_name (+ UserPromptSubmit post-expansion) denyPreToolUse on skill/agent tools (full prompt); model-invoked commands re-fire UserPromptExpansion. SubagentStart cannot block planned
Codex CLI denyUserPromptSubmit (raw human text; text-match, no command structure; requires [features] hooks = true) none — skills inject as context with no event; SubagentStart is metadata-only (continue:false at best). Artifact gates are the backstop shipped
Gemini CLI denyBeforeAgent (expanded template text only — no command name, no slash-origin marker; content-match required) n/a — model cannot invoke .toml commands by design planned
Mistral Vibe none — slash/skill input expands in the UI layer, invisible to hooks denybefore_tool on the skill tool (sees skill name; flag: enable_experimental_hooks). Note: skill loads bypass Vibe's user-approval prompt, so this hook is the only programmatic gate planned
GitHub Copilot hooks exist; deny semantics unverified unverified
Cursor, Zed, Cline, Goose, Devin, Trae, Lingma, Kimi, ZCode, Firebender, Junie, Auggie, + ~14 more static context only — no hook system static context only spec-kit bundle (advisory text)

Where a channel shows none/static, enforcement falls back to artifact gates: a downstream gated command hard-requires the report file the skipped step should have produced, so out-of-order runs still fail at the next enforceable point.

deny = the hook emits permissionDecision: deny or decision: block, preventing the tool from running.

advisory = the gate injects prerequisite context into the model's context window (via spec-kit's before_* hook infrastructure) but does not block execution.

Bundle adapter rationale

spec-kit supports 36+ harnesses. Of these, only Claude Code and Codex expose a real hook system (PreToolUse / UserPromptExpansion / UserPromptSubmit) that can deny tool calls before they run. All other harnesses receive spec-kit's advisory prompt injection as their only integration path — there is no hook event to intercept.

The spec-kit bundle is therefore the only distribution path that reaches advisory harnesses. It ships an extension that registers /speckit.gate.init and emits prerequisite context before each spec-kit command via spec-kit's built-in before_* advisory hooks.

The bundle does not duplicate Claude Code or Codex enforcement — those harnesses should use the plugin or install --harness path for real deny gates.

CLI reference

Verb Description
scan Scan project for spec-kit commands
propose Show prerequisite proposal table
init Write gates.yaml (--defaults / --answers / --interactive)
compile Compile gates.yaml.specify/gates/nodes.json
compile --check Drift check only; exit 1 if stale
install --harness Install hook adapters (claude/codex/speckit/all)
explain <cmd> Show prerequisites for a command
dry-run <cmd> Simulate a hook event
`dispatch pre post`

gates.yaml schema

config:
  prefix: speckit.          # command prefix; default "speckit."
  feature_root: specs       # feature artefact root; default "specs"
  resolve: [git-branch, newest-dir]
  messages:
    no_feature: "..."       # block reason when no feature resolved

gates:
  <command>:
    requires: [cmd-or-artefact, ...]
    produces: [artefact, ...]
    deprecated: true|false
    spawn_agent: true|false  # adds PreToolUse:Agent gate (Claude only)
    context: "..."           # advisory context injected into the model

compile derives edges by matching producesrequires and writes .specify/gates/nodes.json in the flat dispatcher format the hook reads.

Presets

Preset Description
presets/core.gates.yaml Spec-kit built-in commands only (analyze, checklist, clarify, constitution, converge, implement, plan, specify, tasks, taskstoissues). Safe to use on any spec-kit project.
presets/extensions.example.gates.yaml Example file showing how to gate community and custom extensions. Not an installable preset — copy individual entries into your gates.yaml.

Copy the core preset as your starting gates.yaml:

curl -sL https://raw.githubusercontent.com/srobroek/speckit-gate/main/presets/core.gates.yaml \
  > gates.yaml
speckit-gate compile

Extending for custom extensions

If your project uses community extensions (e.g. speckit.verify-tasks, speckit.agent-assign.*, speckit.refine.*), add gates for them on top of the core preset. See presets/extensions.example.gates.yaml for documented patterns covering:

  • requires-chain — sequencing a community command after a built-in artefact
  • spawn_agent — gating commands that spawn sub-agents via PreToolUse:Agent
  • deprecated — emitting an upgrade hint instead of a prerequisite block
  • produces-artefact — requiring a specific artefact path rather than a command name
# Copy entries you need from the example into your gates.yaml:
curl -sL https://raw.githubusercontent.com/srobroek/speckit-gate/main/presets/extensions.example.gates.yaml

Plain-python fallback

For exec-tax-sensitive environments (slow filesystem, EDR overhead) where uvx startup latency matters even with caching, use the installed console script directly:

pip install speckit-gate  # or uv pip install speckit-gate
# then in hooks.json:
# "command": "speckit-gate dispatch pre"

The dispatcher (dispatch.py) is stdlib-only with no import overhead. uvx caches after first run and is the default wiring; switch to the installed path only when profiling shows hook latency is a problem.

Development

uv sync --dev
uv run pytest
uv run speckit-gate --help

License

Apache-2.0

About

Generated, harness-agnostic workflow gates for spec-kit projects

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages