Skip to content
Back to skills

Scope Tripwire

ASecurity

Pre-push advisory gate that compares the branch's cumulative diff against the linked Linear ticket's point-estimate. Warns when LOC exceeds N times the estimate (default 2x at 200 LOC/point). Surfaces the incident-2026-1507 failure mode (1pt ticket -> +8,750 LOC) at push time without blocking by default.

  • 8 stars
  • 0 votes
  • 0 copies
  • 0 views
  • Added October 6, 2026
ai-agentsrustbashcode-reviewgitapisecurity

Works with

  • api

Security analysis

A100/100

Scanned October 6, 2026

npx -y skills add bordenet/superpowers-plus --skill scope-tripwire --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Scope Tripwire?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Scope Tripwire
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/bordenet-scope-tripwire/badge)](https://www.skillsdirectory.com/skills/bordenet-scope-tripwire)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: scope-tripwire
disable-model-invocation: true
source: superpowers-plus
augment_menu: true
auto_invoke: true
triggers:
  - "scope tripwire"
  - "scope drift"
  - "diff vs estimate"
  - "/sp-scope-tripwire"
anti_triggers:
  - "feature spec"
  - "linear estimate calibration"
description: "Pre-push advisory gate that compares the branch's cumulative diff against the linked Linear ticket's point-estimate. Warns when LOC exceeds N times the estimate (default 2x at 200 LOC/point). Surfaces the incident-2026-1507 failure mode (1pt ticket -> +8,750 LOC) at push time without blocking by default."
summary: "Advisory-by-default gate (block-mode in the dogfood repo). Reads Linear estimate via GraphQL, caches in .git/, fail-opens on every error path. Layered alongside pre-push-loc-gate (per-commit raw LOC) and hotfix-charter (per-branch budget doc)."
coordination:
  group: push-gates
  order: 0
  requires: []
  enables: []
  internal: false
composition:
  consumes: [branch-context, linear-ticket]
  produces: [scope-tripwire-finding]
  capabilities: [gates-quality]
  priority: 50
---

# Scope Tripwire

> **Wrong skill?** Per-commit raw-LOC cap -> `pre-push-loc-gate` (`tools/pre-push-loc-gate.sh`). Per-branch charter doc -> `hotfix-charter`. Branch naming -> `git-branch-conventions`. Code review -> `code-review-battery`.

A pre-push gate that asks one question: **does this push's cumulative diff blow past the linked Linear ticket's estimate?** Catches the incident-2026-1507 failure mode where a 1-point ticket grew to +8,750 / -4,195 LOC across 73 files -- the per-commit LOC gate missed it because individual commits stayed under 500 LOC; the symptom was the branch as a whole.

## Quick start

**Default behavior:** advisory only (warn mode) -- the gate prints a structured stderr line and lets the push proceed. The engineer is the gate; the script just makes the ratio visible at push time.

**To enable:**

```bash
# Option 1: sole pre-push hook
ln -sf "$(git rev-parse --show-toplevel)/tools/scope-tripwire-check.sh" .git/hooks/pre-push
chmod +x .git/hooks/pre-push

# Option 2: chained alongside the LOC gate (RECOMMENDED if you already run it)
cat > .git/hooks/pre-push <<'EOF'
#!/usr/bin/env bash
REPO_ROOT="$(git rev-parse --show-toplevel)"
"$REPO_ROOT/tools/pre-push-loc-gate.sh" "$@" || exit $?
"$REPO_ROOT/tools/scope-tripwire-check.sh" "$@" || exit $?
EOF
chmod +x .git/hooks/pre-push
```

`install.sh` does NOT auto-wire pre-push hooks in this repo today (no central wiring exists). Adoption is opt-in per the snippet above. Extending install.sh to auto-wire is filed as a follow-up TODO.

## What it does

For every push:

1. **Resolve a Linear ref from the branch name.** Regex `[A-Z]+-[0-9]+` against `git symbolic-ref --short HEAD`. Multi-ref: first match wins (override with `SCOPE_TRIPWIRE_REF=`). No match -> advisory and exit 0.
2. **Check the cache.** `.git/scope-tripwire-cache/<REF>.json` with TTL (default 1h). Within TTL: skip API.
3. **Fetch from Linear** (GraphQL). 5-second curl timeout. Fail-open: timeout, network, non-200, GraphQL errors, no estimate, ticket not found -> stderr advisory, exit 0. Every failure mode caches a `reason` (`api_down|not_found|no_estimate|ok`) to prevent re-hammering.
4. **Compute cumulative LOC** against the base branch. Base auto-resolves via `@{upstream}` -> `origin/main` -> `origin/HEAD`. Repos using `dev` as the main branch get the right base via `@{upstream}` without code changes.
5. **Compare** against `LOC_PER_POINT * estimate * SCOPE_TRIPWIRE_RATIO` (default 200 * estimate * 2.0).
6. **Mode dispatch:** `warn` -> stderr advisory + exit 0. `block` -> exit 1 unless bypassed.

## Mode dispatch (precedence)

1. `SCOPE_TRIPWIRE_MODE=warn|block` env var (highest)
2. `.scope-tripwire-mode` file at repo root (one line: `warn` or `block`; committed -- survives fork/mirror)
3. Remote URL auto-detect: if `origin` URL matches `superpowers-plus` as a path segment (not a prefix), defaults to `block` (dogfood repo). Emits `scope-tripwire: auto-detected superpowers-plus dogfood repo; mode=block.` to stderr when triggered.
4. Default -> `warn`

**Default behavior:** advisory only (warn mode) — the gate prints a structured stderr line and lets the push proceed. The engineer is the gate; the script just makes the ratio visible at push time. Enable `block` mode per-repo by committing a `.scope-tripwire-mode` file or setting `SCOPE_TRIPWIRE_MODE=block` if your workflow requires hard enforcement.

## Configuration

| Env var | Default | Purpose |
|---|---|---|
| `LOC_PER_POINT` | 200 | Starter calibration. Tune per team after observing N merged PRs. |
| `SCOPE_TRIPWIRE_RATIO` | 2.0 | Multiplier on (LOC_PER_POINT * estimate). |
| `SCOPE_TRIPWIRE_MODE` | warn ¹ | `warn` (advisory) or `block` (exit 1 on overage). |
| `SCOPE_TRIPWIRE_BYPASS` | 0 | `=1`: acknowledged bypass in block mode. Logs to evasion.log. |
| `SCOPE_TRIPWIRE_SKIP` | 0 | `=1`: skip the gate entirely. No API call, no diff scan. Logs to evasion.log. |
| `SCOPE_TRIPWIRE_REF` | auto | Override branch-name parsing (e.g., `PROJ-1234`). |
| `SCOPE_TRIPWIRE_BASE` | auto | Override base branch resolution. |
| `SCOPE_TRIPWIRE_CACHE_TTL` | 3600 | Cache TTL in seconds. `0` forces re-fetch. |
| `LINEAR_API_URL` | https://api.linear.app/graphql | Override API endpoint. |
| `LINEAR_API_KEY` | from environment | Linear API token. Missing -> fail-open advisory. |

¹ Effective default is `block` when running in the superpowers-plus dogfood repo (auto-detected via remote URL; see Mode dispatch (precedence) — item 3).

## Exit codes (stable contract)

| Mode | Result | Bypass/Skip | Exit | stderr |
|---|---|---|---|---|
| any | no ref / no key / API down / no estimate / not found | n/a | 0 | advisory line |
| any | within threshold | n/a | 0 | silent |
| `warn` | exceeds threshold | n/a | 0 | structured advisory with ticket+LOC+ratio |
| `block` | exceeds threshold | unset | 1 | refusal with remediation options |
| `block` | exceeds threshold | `BYPASS=1` | 0 | warning + evasion.log append |
| any | `SKIP=1` | yes | 0 | warning + evasion.log append |
| any | invalid env / not in git repo | n/a | 2 | error |

## Evasion log

`BYPASS` and `SKIP` both append one line to `.git/scope-tripwire-evasion.log`:

```
2026-06-10T20:15:00Z BYPASS incident-2026-1507 LOC=8750 EST=1 RATIO=43.7 BRANCH=fix/incident-2026-1507-... USER=engineer@example.com
```

The log is **`.git`-local and never pushed**. This is a *trust-the-engineer* gate, not central enforcement -- the log exists so the same engineer can grep their own history. Aligns with the INFORM-not-BLOCK framing.

## What this gate is NOT

- **Not a security gate.** Bypass is one env var away; the audit trail is local-only.
- **Not a sizing oracle.** `LOC_PER_POINT=200` is a starter; teams will calibrate. The gate is advisory by default for exactly this reason.
- **Not a replacement for code review.** Scope drift is a process signal; reviewers still need to look at what changed.
- **Not multi-ticket-aware.** A branch matching `incident-2026-1507` and `INFRA-99` compares against the first ref's estimate only (with a stderr note). Sum-of-estimates is deferred.

## Composition with peer gates

| Gate | When | What it gates |
|---|---|---|
| `hotfix-charter-check.sh` | pre-commit | `hotfix/*` and `fix/incident-*` branches need a HOTFIX-CHARTER.md doc |
| `pre-push-loc-gate.sh` | pre-push | Per-commit raw LOC ceiling (default 500) |
| **`scope-tripwire-check.sh`** | **pre-push** | **Cumulative branch LOC vs Linear ticket estimate** |

The three gates are independent signals: charter forces upfront articulation, LOC gate catches single oversize commits, scope-tripwire catches cumulative drift. All three can fire on the same push without conflict.

## Failure modes

| Mode | Symptom | Recovery |
|------|---------|----------|
| Linear API down | Push prints "Linear API unreachable" advisory, succeeds | Wait for Linear; cached as `api_down` for `SCOPE_TRIPWIRE_CACHE_TTL` to prevent re-hammering |
| Ticket re-estimated after cache | Gate uses stale estimate | `rm .git/scope-tripwire-cache/PROJ-NNNN.json` to force refresh |
| Branch has no Linear ref (chore/, doc/, exp/) | Advisory "no Linear ref" + exit 0 | Expected; use `SCOPE_TRIPWIRE_REF=` if you want the gate to compare against a specific ticket anyway |
| Engineer bypassing repeatedly | `.git/scope-tripwire-evasion.log` grows | Behavior signal; raise in 1:1 or retro. Log is local-only on purpose. |

## Why this exists

The 2026-06-10 incident-2026-1507 hotfix shipped a 1-point ticket as +8,750 / -4,195 LOC across 73 files. The per-commit LOC gate (shipped same day) didn't catch it because each individual commit stayed under 500. The scope-tripwire is the cumulative complement: it asks "is the BRANCH bigger than the TICKET said it would be?", which is the right question for scope-drift detection.

The gate is advisory by default per the 20260610-18 design pivot. We want engineers to *see* the ratio at push time and use their judgment. The incident-2026-1507 retrospective is the source-of-truth: `docs/retrospectives/incident-2026-1507-cr-battery-false-positives.md`.

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…