---
name: validate-before-pr
description: REQUIRED before opening any pull request. Always invoke when about to open a PR, create a merge request, push for review, or run pre-PR checks. Runs lint and format checks as a gate before PR creation.
TRIGGER: user asks to open a PR or merge request, asks to push for review, mentions pre-PR validation, or calls the open-pull-request skill.
DO NOT SKIP: applies to all PR creation regardless of repository or context.
---

# Validate Before PR

This skill is a mandatory gate that runs lint and format checks before any pull request is opened. It must be invoked before the `open-pull-request` skill. If either check fails, the PR must not be opened until the failures are resolved.

## IMPORTANT: When This Skill Applies

This skill is **mandatory** for:
- Any request to open, create, or draft a pull request or merge request
- Any workflow that leads to invoking the `open-pull-request` skill
- Requests to "push for review" or "push and open a PR"
- Pre-PR validation checks requested explicitly

**Do not** open a PR through any means without running this skill first.

## Steps

Each check must be run as a **separate, standalone Bash call**. Do not chain commands with `&&` or `;` — this is required by the bash-style rule.

### Step 0 — Detect stack

Run the following as a standalone Bash call (no args — uses the current directory):

```
~/.claude/scripts/detect-stack.sh
```

The script emits a single-line JSON object to stdout:

```json
{"stack": "...", "lint_cmd": "...", "format_check_cmd": "...", "format_warning": "..."}
```

Branch on `.stack`:

- If `.stack == "unknown"`: print the value of `.format_warning` (which will be "No recognized lint/format toolchain was found. Skipping validation."), then jump directly to Step 3. Note in the handoff that pre-PR checks were not run.
- Otherwise: store `.lint_cmd` as `<lint_cmd>` and `.format_check_cmd` as `<format_check_cmd>` for use in Steps 1–2. If `.format_warning` is non-empty, print it now (this surfaces the Makefile `fmt`-only warning to the user). Proceed to Step 1.

### Step 1 — Run lint

Run the following as a standalone Bash call, using the `<lint_cmd>` value captured in Step 0:

```
<lint_cmd>
```

- If lint **passes** (exit code 0): proceed to Step 1b.
- If lint **fails** (non-zero exit): stop immediately. Do not proceed to format check or PR creation. Report the full lint output to the user and request that the errors be fixed before retrying.

### Step 1b — Run shell lint

Run the following probe as a standalone Bash call:

```
node -e "process.exit(require('./package.json').scripts?.['lint:sh'] ? 0 : 1)" 2>/dev/null && npm run lint:sh
```

- If the `node -e` probe exits non-zero (script not declared in package.json) or if `package.json` does not exist: skip with note "No `lint:sh` script declared in package.json — skipping shell lint." This is not a failure.
- If the probe passes and `npm run lint:sh` **passes** (exit code 0): proceed to Step 1c.
- If the probe passes and `npm run lint:sh` **fails** (non-zero exit): stop immediately. Do not proceed to format check or PR creation. Report the full lint output to the user and request that the errors be fixed before retrying.

### Step 1c — Run YAML lint

Run the following probe as a standalone Bash call:

```
node -e "process.exit(require('./package.json').scripts?.['lint:yaml'] ? 0 : 1)" 2>/dev/null && npm run lint:yaml
```

- If the `node -e` probe exits non-zero (script not declared in package.json) or if `package.json` does not exist: skip with note "No `lint:yaml` script declared in package.json — skipping YAML lint." This is not a failure.
- If the probe passes and `npm run lint:yaml` **passes** (exit code 0): proceed to Step 2.
- If the probe passes and `npm run lint:yaml` **fails** (non-zero exit): stop immediately. Do not proceed to format check or PR creation. Report the full lint output to the user and request that the errors be fixed before retrying.

### Step 2 — Run format check

If `<format_check_cmd>` is empty (e.g., a Makefile repo with only a `fmt` target that modifies files), skip this step with the note: "No format check is configured for this stack." Proceed directly to Step 2b.

Otherwise, run the following as a standalone Bash call, using the `<format_check_cmd>` value captured in Step 0:

```
<format_check_cmd>
```

- If format check **passes** (exit code 0): proceed to Step 2b.
- If format check **fails** (non-zero exit): stop immediately. Do not open the PR. Report the full output to the user and request that the formatting issues be fixed before retrying.

### Step 2b — Run shell format check

Run the following probe as a standalone Bash call:

```
node -e "process.exit(require('./package.json').scripts?.['format:check:sh'] ? 0 : 1)" 2>/dev/null && npm run format:check:sh
```

- If the `node -e` probe exits non-zero (script not declared in package.json) or if `package.json` does not exist: skip with note "No `format:check:sh` script declared in package.json — skipping shell format check." This is not a failure.
- If the probe passes and `npm run format:check:sh` **passes** (exit code 0): proceed to Step 2c.
- If the probe passes and `npm run format:check:sh` **fails** (non-zero exit): stop immediately. Do not open the PR. Report the full output to the user and request that the formatting issues be fixed before retrying.

### Step 2c — Run spellcheck

Run the following probe as a standalone Bash call:

```
node -e "process.exit(require('./package.json').scripts?.['spellcheck'] ? 0 : 1)" 2>/dev/null && npm run spellcheck
```

- If the `node -e` probe exits non-zero (script not declared in package.json) or if `package.json` does not exist: skip with note "No `spellcheck` script declared in package.json — skipping spell check." This is not a failure.
- If the probe passes and `npm run spellcheck` **passes** (exit code 0): proceed to Step 3.
- If the probe passes and `npm run spellcheck` **fails** (non-zero exit): stop immediately. Do not open the PR. Report the full output to the user and request that the spelling issues be fixed before retrying.

### Step 3 — Proceed to open-pull-request

Only after all of Steps 1, 1b, 1c, 2, 2b, and 2c pass (or are legitimately skipped) may you proceed to the `open-pull-request` skill to create the pull request.

## Fail-Fast Behavior

- Stop on the first failure. Do not run Step 1b if Step 1 failed; do not run Step 1c if Step 1b failed; do not run Step 2 if Step 1c failed; do not run Step 2b if Step 2 failed.
- Report which check failed and include its full output so the user can act on it.
- Do not attempt to open the PR until all checks pass.
- The presence-check skip in Steps 1b, 1c, 2b, and 2c (when `lint:sh`, `lint:yaml`, `format:check:sh`, or `spellcheck` is absent from `package.json`) is NOT a failure — it is a graceful no-op.

## Note on Optional Sub-Steps

Optional sub-steps (Steps 1b, 1c, 2b, and 2c) use a `node -e` probe against `package.json.scripts` to detect whether the relevant script is declared. This covers shell lint (`lint:sh`), YAML lint (`lint:yaml`), shell format check (`format:check:sh`), and spell check (`spellcheck`). This skill installs to `~/.claude/skills/` and runs in any repository; the probe ensures the gate degrades cleanly in repos without these scripts. `npm run` is used (not `pnpm run`) for package-manager-agnostic compatibility.

## Relationship to Other Skills

- **open-pull-request**: This skill gates entry to `open-pull-request`. It must always be invoked first. After both lint and format:check pass, hand off to `open-pull-request` to complete the PR workflow.
- **commit-message-guide**: Commits must follow conventional commit format. This skill does not replace that requirement — it adds a validation layer on top of it.
