Skip to content
Back to skills

Preflight

ASecurity

Scaffold and grow a project's executable gate file — the checks that run before every deploy and fail the build on the bug families this project actually ships.

  • 6 stars
  • 0 votes
  • 0 copies
  • 1 view
  • Added September 3, 2026
developmentjavascripttypescriptgojavabashnodeawstestinggitsecurity

Security analysis

A100/100

Scanned September 20, 2026

npx -y skills add djnsty23/claude-auto-dev --skill preflight --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Preflight?

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

Security grade badge for Preflight
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/djnsty23-preflight/badge)](https://www.skillsdirectory.com/skills/djnsty23-preflight)

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: preflight
description: Scaffold and grow a project's executable gate file — the checks that run before every deploy and fail the build on the bug families this project actually ships.
when_to_use: "Invoked when the user says \"preflight\", \"add a gate\", \"set up preflight\", \"gate this\", or after /learn-from-fixes identifies a class worth enforcing mechanically."
allowed-tools: Bash, Read, Write, Edit, Grep, Glob
model: opus
user-invocable: true
argument-hint: "[init | add <class> | verify]"
---

# Preflight

A checklist a human runs sometimes is not a gate. This builds the executable
one: `scripts/preflight.js`, wired into the project’s actual local completion/
release gate and existing CI when used, failing on relevant bug families.

A rule’s presence does not prove it changes outcomes. Measure whether the
actual gate rejects a reproduced defect while preserving a known-good control.

## `preflight init`

1. **Find out what to gate.** Run `/learn-from-fixes` first. Gate the top two or
   three classes for *this* repo, not a generic list. If the user insists on
   starting without that analysis, start with applicable template checks and
   known acceptance risks. Record missing history; a new repo can still have
   concrete requirements worth testing.

   The template defaults are `syntax` (JavaScript parse checks), `gates-ran`
   (wiring hints) and `workflow-valid` (a narrow workflow shape check). They
   need project-specific applicability and behavioral controls. A
   workflow file GitHub refuses fails in **0 seconds, with no jobs and no log**,
   so nothing readable tells you it happened. Measured in one repo on
   2026-08-20 — a duplicate top-level `concurrency:` key left a workflow dead
   for three days while marking every open PR `UNSTABLE`, past sixty other
   gates. The template’s line scan targets that class; it is not a full workflow
   validator. Some parser configurations accept duplicate keys, so verify that
   the chosen validator rejects the actual malformed workflow.

2. **Copy the template** to `scripts/preflight.js`:

   ```bash
   cp "${CLAUDE_PLUGIN_ROOT}/templates/preflight.js" scripts/preflight.js
   ```

3. **Wire it into the command that actually guards completion:**

   ```json
   { "scripts": { "preflight": "node scripts/preflight.js" } }
   ```

   Add it to the actual local completion/release command and existing CI if
   applicable. Inspect what the copied template requires before claiming it is
   wired; a package-script name alone does not show a consumer runs it.

4. **Run it and its consumer.** Inspect failures and warnings. The template’s
   `gates-ran` checks a script substring and can pass on `echo preflight`; absent
   package/CI configuration can produce only a warning. Prove propagation by
   making an actual check fail in an isolated fixture and running the real
   completion/release command. Do not infer enforcement from this template alone.

## `preflight add <class>` — first, prove the gate does not already exist

**Before writing anything**, in this order:

1. **Is it already gated?** List the gate ids in the existing file and read the
   tests the build already runs. A duplicate gate reports the same finding under
   two names and doubles the noise.
2. **Was it already rejected?** Search the gate file for a recorded decision not
   to build it. Mature gate files carry these, and they usually contain a reason
   better than the one you arrived with.
3. **Measure the population before writing the check.** Count what the gate would
   fire on today, then **read every finding**. If they are false positives, the
   gate is wrong — not the codebase. A gate that cries wolf is one people learn
   to skip, and the skipping generalises to the gates that were right.

A measurement of zero is a fine result: the gate becomes a regression guard.
A measurement of sixty requires triage: it can reflect real debt, false
positives or both. The count alone does not decide which.

### When the population is large: ratchet, don't flood

A measurement in the hundreds does not mean "write a gate that fails 400 times".
After triage confirms real debt, a **ratchet** can prevent new violations
without declaring the existing ones fixed: record today’s verified violations
as a baseline, fail on **new** ones,
and let the baseline shrink. Do not baseline an unresolved critical release
risk merely to ship; keep its acceptance criterion and remediation visible.

Measured example: `@typescript-eslint/no-floating-promises` on one repo returned
**417 findings across 183 files**. As `error` it breaks the build immediately; as
`warn` it gates nothing and is ignored within a week. As a ratchet it stops the
418th on the day it is written.

The shape:

1. Enable the rule and dump today's violations to a checked-in baseline file.
2. The gate fails when a violation appears that is not in the baseline.
3. The gate **also fails when a baseline entry no longer violates** — the same
   stale-excuse rule as `KNOWN_RED`. Otherwise the baseline never shrinks.
4. Never regenerate the baseline to make a build pass. Regenerating is how a
   ratchet silently becomes a rubber stamp.

Prefer an existing, battle-tested rule over a hand-written check every time. A
config line plus a baseline beats a custom detector you will have to debug — and
this project's own history is four hand-written detectors that were wrong on
first contact with a real repo.

### Record the gates you decide NOT to build

When you conclude a gate should not exist, write that into the gate file as a
comment block in the same format as a real gate, ending with why. Something like:

```js
/* [thing] NOT BUILT, ON PURPOSE — <what already covers it>.
   Written down here because "we should gate <thing>" is a thought that recurs,
   and the next person to have it should find the answer instead of building the
   duplicate.
   <the specific reason a naive version would be WRONG — e.g. four controls are
   deliberately under the floor, measured in a real browser, so a static px gate
   fires on all four.>
   WHAT IS STILL NOT COVERED, so nobody assumes it is: <the honest gap>. */
```

This convention is worth more than most gates. A rejected-gate record answers a
recurring question permanently, and it is the only thing that stops each new
contributor — human or agent — from rebuilding the same wrong check.

## Gate shapes

One gate per bug family. Name the gate after **the family it prevents**, not the
mechanism — a future reader needs to know why it exists.

Write the comment above each gate as the incident: what shipped, what it cost,
what the gate now prevents. That comment is the reason nobody deletes the gate
in six months.

Shapes that work, by class:

| Class | Gate shape |
|---|---|
| Reachability / dead path | Parse the dispatch site; assert every handler is registered at the depth that actually runs |
| Duplicated derivation | Assert only one module computes the value; every other reference imports it |
| Cross-surface consistency | Exercise the relevant surfaces on the same inputs; shared imports are a structural guard, not proof of equal output |
| Cache / key scoping | Test account-scoped data across account changes; exempt intentionally global caches with evidence |
| Copy / i18n drift | Hash the source string per key; fail when the source changed and a locale's hash did not |
| Lifecycle | Exercise creation/disposal and verify no unintended live handlers/timers remain; document intentionally process-long resources |
| Config targeting | Assert the env var or project id resolves to the environment the build targets |
| **Gate satisfied by a comment** | Strip comments with a real lexer before the gate's own regex runs — see below |

## The gate that a comment satisfies

The nastiest failure a gate file has, because the gate reports PASS forever and
the thing it guards is gone.

Two real instances in one repo, same week:

- An owner-only exemption stripped `//` comments before testing for an owner
  check. A **block** comment describing a check that had been *deleted three
  months earlier* kept granting the exemption.
- An image-consent gate ran `/consentV/` against raw source. Three of the files
  it checked mention `consentV` in explanatory prose. Delete the real guard,
  leave the comment twelve lines above it, and the gate stayed green over
  Art. 9 special-category health data.

Both were proven by injection — remove the guard, confirm the gate still passes —
which is the only way to know a gate is not decoration.

**Do not ship this as a scanning gate.** Measured on those two files: a detector
for "regex tested against raw file contents" found **54 hits, of which 2 were
bugs.** Most raw-source tests are correct — a check looking for `readFileSync`
calls, or matching a version label, genuinely wants the literal text. A gate at
that precision is one people learn to skip.

For named security-critical checks, run the actual gate on controlled copies
of its production input. Remove the executable guard while leaving line and
block comments naming it; require failure for the intended reason. Preserve a
valid control (including valid string-literal forms) and require it to pass.
A regex checking whether the gate body mentions `decomment` or `codeOnly` is
not evidence it uses a lexer: a comment can satisfy that regex too.

Use a real lexer, not two regexes. `src.replace(/\/\*[^]*?\*\//g,'').replace(/\/\/.*$/gm,'')`
is not a scanner: a `//` inside a string (every URL) eats the rest of the line,
and a `/*` inside a string or line comment opens a block that runs to the next
close marker. Measured on one repo, that idiom deleted **128,599 characters of
live code** across 5 files — whole functions — from the views assertions ran
against. Those assertions did not fail; they looked at a hole and passed.

**Pick the right variant.** A comments-only strip keeps string literals; a
strip that also blanks literal *contents* is stronger but blinds any gate whose
pattern matches inside a string. One of the two gates above needed each:

```
sample                          comments-only   +literals
real gate, identifier form           true         true
real gate, string-literal form       true         FALSE   <- blinded
only a line comment                  false        false
only a block comment                 false        false
```

**Then prove it.** Reintroduce the original defect, run preflight, and watch the
gate go red. A gate never seen to fail is not known to work — say explicitly in
your report that you did this, or that you could not.

## `preflight verify`

Audit the gate file itself:

- Does every gate still run? A gate whose target file was renamed reports
  "skipped", and in the template that is a hard failure — confirm none are.
- Is every `KNOWN_RED` entry still red, and still tied to an open work item?
- Does the actual completion/release command execute preflight and preserve
  its failure? Check existing CI wiring too when applicable. Run a controlled
  failure through that consumer; matching a script name is not enough.

## The four laws

These are not style preferences. Each cost a production repo a shipped bug.

**1. A gate that could not run is not a pass.** Gates sit in try/catch so one
broken gate cannot take out the run — but routing that catch to a warning lets a
gate switch *itself* off while the run still exits 0. That shipped: renaming one
file turned a parity gate into "check skipped" and preflight printed PASS. In
this template a skip is a **hard** failure.

**2. Snapshot before you regenerate.** If a gate compares a generated artifact
against its source, read the artifact from disk *before* any step regenerates
it — otherwise it compares the generator against its own output and is green
forever. That shipped two consecutive stale releases.

**3. A known-red excuse that now passes is a failure.** Track deliberate
failures in `KNOWN_RED`, keyed by bare gate id, each naming an open work item —
and fail the run when a tracked gate starts passing. A stale excuse is how a
real failure gets waved through.

**4. A gate never seen to fail is not known to work.** Prove every new gate by
reintroducing the defect.

## What not to do

- Do not add six gates at once. An unwanted gate gets disabled, and a disabled
  gate teaches the team that gates are noise.
- Do not gate what a typecheck already catches. Gate what survives it.
- Do not let preflight become slow enough to skip. Keep it offline and parallel;
  anything touching the network belongs behind an explicit flag.
- Do not silence a red gate by loosening it. Track it in `KNOWN_RED` with the
  work item, or fix it.

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…