preflight
Change-gated, branch-scoped lint preflight. It lints only the categories relevant
to
, on changed paths only — not a whole-repo
—
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 () — a quick "will my branch pass scoped lint?"
check, leaving any fixes in the working tree.
- Inside a ship flow (e.g. ) — the lint gate that runs after commits
and before the changelog/push steps.
All bundled scripts use only Node built-ins — no
, no build step.
They operate on the
consumer repo's root (run them from the repo root, where
resolves the branch diff).
Running it
- Make sure the base branch is up to date: (the base is
auto-detected — see Configuration).
- Run the preflight:
node skills/preflight/scripts/preflight.mjs
(append
to report categories and scoped file lists without classifying
violations). is a true preview — every linter reports
and nothing is written, including .
- Read for the categories run and the violation counts
(, , ). Written only on a real run, not under
. It is a transient scratch artefact, never committed — consumer
repos gitignore it (the skill 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.mjs
on 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, matching / CI (which exit 0 on warnings).
Set to 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 the project's tracker (assign the maintainer;
link the branch/PR context), then 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.
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 / (respecting repo ignores) | No markdown changes; warns and skips if the binary is missing |
| actionlint | Branch diff includes or | No workflow changes; config-only changes lint all tracked workflows; warns and skips if binary missing |
ESLint runs per workspace (via
), plus a root/scripts bucket.
Typecheck, tests, and framework checks (e.g.
) are
not part of
preflight — they stay in CI.
Standalone vs inside a ship flow
- Standalone () does the lint preflight and the exit-code loop,
then reports. On introduced violations it may run
node skills/preflight/scripts/lint-fix.mjs
and re-run, but it leaves fixes
in the working tree — it never commits, writes a changelog, pushes, or opens
a PR.
- Inside the 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 plus each package's
: a workspace is included only if it declares a script. This
auto-excludes intentionally-unlinted workspaces and non-package directories
without a hand-maintained list.
- Base branch is detected from (e.g. , ,
), falling back to when that symbolic ref is absent.
To override either, add a
at the
consumer repo root
(a
ships beside this file as a
template):
json
{
"baseBranch": "main",
"blockOnWarnings": false,
"workspaces": {
"web": { "filter": "@acme/web", "prefix": "apps/web/" }
}
}
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.
- (default ) — 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, matching / CI semantics. Set for
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.
Implementation
The bundled scripts live beside this file under
and are invoked directly with
— no
aliases,
no
:
- — the change-gated preflight and exit-code contract.
- — scoped / on 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