SuperAPI AI Directory

preflight

General & other

View repository

Updated 10-16 04:28

About this skill

preflight

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

  1. Make sure the base branch is up to date: git fetch origin <base> (the base is auto-detected — see Configuration).
  2. Run the preflight: node skills/preflight/scripts/preflight.mjs (append --dry-run to report categories and scoped file lists without classifying violations). --dry-run is a true preview — every linter reports would-run and nothing is written, including .preflight-summary.json. Before anything else, the script checks that the active Node major matches the repo's pin — an exact engines.node pin (24.x, 24.18.0) or, failing that, the .nvmrc major — 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.
  3. Read .preflight-summary.json for the categories run, the violation counts (passed, deferred, blocking), and lintSurface (see Lint surfaces). Written only on a real run, not under --dry-run. It is a transient scratch artefact, never committed — consumer repos gitignore it (the rheged-skills-setup 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. Fix the code: never clear a violation by editing lint config or adding an ignore / disable directive. When that is the only fix, stop the loop and report it as a developer decision (see Lint surfaces). (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 pnpm lint / CI (which exit 0 on warnings). Set blockOnWarnings: true 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 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.

Lint surfaces are a developer decision

Changing how a linter is configured, or telling it to look away, is the developer's call, never the agent's. The full rule (the surfaces covered, the preference order, what to report, and the carve-out for repairing a developer's own change) is in references/lint-surfaces.md, shared with triage-pr and commit. In preflight it means:

  • In the exit-1 loop, fix the offending code. If the only way to clear an introduced violation is a lint-config edit or an ignore / disable directive, don't make it. Stop and report the four points from the reference (file and rule, the change you would have made, why no code fix was available, the preferred alternative) at the end of the run.
  • lintSurface in the summary lists the lint config files the branch changed (configFiles) and the ignore / disable directives it adds (directives, each with file, line, directive and text), with a count. Preflight can't tell who made them, so it is informational and never blocks: the exit code is unchanged. Only changed files that can carry a directive are diffed, and if that scan fails, lintSurface.error says so (config files are still listed) instead of the failure stopping preflight. When count > 0, list them in the report as developer decisions for the developer to confirm. Don't revert them, and don't make more.

The report is the natural stopping point. Never stop mid-loop to ask about a lint surface.

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 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 /send-it 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 pnpm-workspace.yaml plus each package's package.json: a workspace is included only if it declares a lint script. 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 to main when 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 (default false) — 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 pnpm lint / CI semantics. Set true 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.
  • 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:

  1. If either key is empty, or the Linear MCP server is unavailable → do not call save_issue. Tell the user to set linearTeamName + debtProject (or choose Fix now). Skip silently only when MCP is unavailable and they still want to continue without tracking.
  2. Resolve the team by name (linearTeamName) and the project via list_projects (debtProject as name, id, or slug). On a miss → do not call save_issue; fail loudly with the unresolved value.
  3. On a hit → call save_issue with team, project (always), a title/description covering the pre-existing violations, assignee = the maintainer, and a link to the branch/PR context. Never omit project.

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 — scoped eslint --fix / markdownlint-cli2 --fix 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).
  • scripts/lib/lint-surface.mjs — the lintSurface detector: changed lint config files and added ignore / disable directives (R-1203).
  • references/lint-surfaces.md — the shared lint-surface rule, vendored from the repo's lib/lint-surfaces.md (ADR-0006). Edit the source, not the copy.

They have no external npm dependencies (Node built-ins only).

Arguments

$ARGUMENTS