About preflight
preflight is a repository skill for change-scoped linting. It checks relevant categories and paths in a branch diff, and distinguishes new violations from pre-existing ones for development teams.
Key features
- 01Run lint on branch-changed paths
- 02Separate new from pre-existing violations
- 03Support ESLint, markdownlint, and actionlint
- 04Provide scoped lint-fix scripts
- 05Drive checks through exit codes
Use cases
- 01Check branch lint before committing
- 02Add a lint gate to a ship flow
- 03Identify new issues versus existing debt
Install
Copy the command and run it in a terminal.
npx skills add https:/ / github.com/ rheged-studio/ agent-skills --skill preflightAfter installing, type/preflightin Claude Code to confirm it loaded.
Bundled files
References and scripts installed along with SKILL.md.
config.example.jsonpackage.jsonREADME.mdscripts/classify-lint.mjslint-fix.mjspreflight.mjsscripts/lib/diff-lines.mjspaths.mjsscope.mjsscripts/lib/vendor/base-branch.mjsDetails
From the project READMEpreflight
Change-gated, branch-scoped lint preflight. It lints only the categories relevant
to origin/<base>...HEAD, on changed paths only — not a whole-repo pnpm lint —
then classifies each violation as introduced (on a line this branch added or
changed) or pre-existing (already there, in a file the branch happens to
touch).
This skill is the single source of truth for the preflight loop. It is invoked two ways:
- Standalone (
/preflight) — a quick "will my branch pass scoped lint?" check, leaving any fixes in the working tree. - Inside a ship flow (e.g.
/send-it) — the lint gate that runs after commits and before the changelog/push steps.
All bundled scripts use only Node built-ins — no npm install, no build step.
They operate on the consumer repo's root (run them from the repo root, where
git resolves the branch diff).
Running it
- Make sure the base branch is up to date:
git fetch origin <base>(the base is auto-detected — see Configuration). - Run the preflight:
node skills/preflight/scripts/preflight.mjs(append--dry-runto report categories and scoped file lists without classifying violations).--dry-runis a true preview — every linter reportswould-runand nothing is written, including.preflight-summary.json. Before anything else, the script checks that the active Node major matches the repo's pin — an exactengines.nodepin (24.x,24.18.0) or, failing that, the.nvmrcmajor — and exits 1 with a switch hint (nvm use/fnm use/mise use) if it does not, so ESLint never runs under the wrong Node. A>=/^/~engines range is not a pin; a repo with neither is unchecked. - Read
.preflight-summary.jsonfor the categories run and the violation counts (passed,deferred,blocking). Written only on a real run, not under--dry-run. It is a transient scratch artefact, never committed — consumer repos gitignore it (therheged-skills-setupskill adds the entry when it reconciles a repo).
The script's exit code drives the loop:
- Exit 0 — pass. No introduced violations and every linter ran cleanly. Continue.
- Exit 1 — introduced violations (blocking). Run
node skills/preflight/scripts/lint-fix.mjson the branch-scoped paths, then re-run preflight. Repeat until introduced violations clear or the user aborts. (Inside a ship flow, commit the fixes; standalone, leave them in the working tree for the user to review and commit.) Only introduced errors block by default — introduced ESLint warnings are reported as a non-blocking notice and don't fail the gate, matchingpnpm lint/ CI (which exit 0 on warnings). SetblockOnWarnings: trueto gate on them too (see Configuration). - Exit 2 — pre-existing violations only. Show the list and ask the user to
choose:
- Fix now — apply the fixes, (commit if shipping), re-run preflight.
- Defer — open a debt issue in Linear (see Debt-issue create below). After creating (or refusing), decide whether to continue or abort.
Exit 1 can also signal a linter that failed to run (non-zero exit with no parseable violations) — inspect its stderr; this is blocking too — or a Node major mismatch (see step 2): switch Node and re-run, don't treat it as a lint failure.
Categories
Each category is gated on what the branch changed (mirrors CI path triggers, narrower scope):
| Category | Runs when | Skipped when |
|---|---|---|
| ESLint | Branch diff includes lintable code or eslint/tsconfig config paths | Markdown-only or non-lintable changes |
| markdownlint | Branch diff includes .md / .mdx (respecting repo ignores) | No markdown changes; warns and skips if the markdownlint-cli2 binary is missing |
| actionlint | Branch diff includes .github/workflows/*.yml or .github/actionlint.yaml | No workflow changes; config-only changes lint all tracked workflows; warns and skips if actionlint binary missing |
ESLint runs per workspace (via pnpm --filter), plus a root/scripts bucket.
Typecheck, tests, and framework checks (e.g. astro check) are not part of
preflight — they stay in CI.
Standalone vs inside a ship flow
- Standalone (
/preflight) does the lint preflight and the exit-code loop, then reports. On introduced violations it may runnode skills/preflight/scripts/lint-fix.mjsand re-run, but it leaves fixes in the working tree — it never commits, writes a changelog, pushes, or opens a PR. - Inside
/send-itthe same loop runs as the lint gate (after commits, before changelog work); fixes are committed so the branch is clean before the changelog is written. The changelog and its validation are separate gates owned by the ship flow — they are not part of this skill.
Configuration
The two repo-specific inputs are auto-detected — a consuming repo edits nothing in the common case:
- Linted workspaces are derived from
pnpm-workspace.yamlplus each package'spackage.json: a workspace is included only if it declares alintscript. This auto-excludes intentionally-unlinted workspaces and non-package directories without a hand-maintained list. - Base branch is detected from
origin/HEAD(e.g.main,master,develop), falling back tomainwhen that symbolic ref is absent.
To override either, add a preflight.config.json at the consumer repo root
(a config.example.json ships beside this file as a
template):
{
"baseBranch": "main",
"blockOnWarnings": false,
"workspaces": {
"web": { "filter": "@acme/web", "prefix": "apps/web/" }
},
"linearTeamName": "",
"debtProject": ""
}
Any key may be supplied on its own; the others are still auto-detected/defaulted. Use the override for non-pnpm repos, deliberate exclusions, or nested workspace globs the detector does not expand.
blockOnWarnings(defaultfalse) — whether introduced ESLint warning-severity findings block the gate. Off by default, preflight blocks only on introduced errors (and linters that fail to run); introduced warnings are surfaced non-blockingly, matchingpnpm lint/ CI semantics. Settruefor repos that want warn-level findings the branch adds to gate as well. markdownlint/actionlint findings always block — the warn/error split is ESLint-only.linearTeamName/debtProject— agent-facing keys for the Exit 2 Defer path (the lint scripts ignore them). Both must be non-empty to mint a debt issue; see Debt-issue create. Empty disables debt-create (tell the user to set them, or choose Fix now) — never file with no project.
Debt-issue create
When the user chooses Defer on Exit 2, mint a Linear debt issue only when
both linearTeamName and debtProject are set in the repo-root
preflight.config.json (read that file; empty / missing keys mean debt-create
is disabled). Fail closed:
- If either key is empty, or the Linear MCP server is unavailable → do not
call
save_issue. Tell the user to setlinearTeamName+debtProject(or choose Fix now). Skip silently only when MCP is unavailable and they still want to continue without tracking. - Resolve the team by name (
linearTeamName) and the project vialist_projects(debtProjectas name, id, or slug). On a miss → do not callsave_issue; fail loudly with the unresolved value. - On a hit → call
save_issuewithteam,project(always), a title/description covering the pre-existing violations, assignee = the maintainer, and a link to the branch/PR context. Never omitproject.
Implementation
The bundled scripts live beside this file under
scripts/ and are invoked directly with node — no pnpm aliases,
no npm install:
scripts/preflight.mjs— the change-gated preflight and exit-code contract.scripts/lint-fix.mjs— scopedeslint --fix/markdownlint-cli2 --fixon the branch-changed paths.scripts/classify-lint.mjs— parse + classify violations as introduced vs pre-existing.scripts/lib/{scope,diff-lines,paths}.mjs— shared helpers (workspace/base-branch detection, diff-line mapping, path normalisation).
They have no external npm dependencies (Node built-ins only).
Arguments
$ARGUMENTS