steward · git:20261103.2bdc22b · 2026-11-03 · sha256 d2b3b9ab1af6e449

steward git:20261103.2bdc22bA

Immutable. This exact content never changes and is served at /api/v1/blob/d2b3b9ab1af6e449.

---
name: steward
description: Kraft's own rules for driving a pull request on itsOmidKarami/kraft to green - which checks to run before a push, how to regenerate generated files and lockfiles, how to answer each CI job, and what the description and labels must carry. Read by Claude Code sessions that drive or babysit a Kraft pull request; not for writing the change itself.
disable-model-invocation: true
---

# Steward a Kraft pull request

These are this repository's conventions for getting a pull request mergeable.
`CONTRIBUTING.md` is the source of truth: where it disagrees with this file, it
wins. Read its "What CI checks" table before working a red check.

## Before every push

Run what the change touches, from the repo root. Run `just setup` first in a
fresh checkout.

| The diff touches | Run |
|---|---|
| anything | `just ci-checks` (CI's whole `lint` job, plus the intent check) |
| `src/`, `tests/`, `config/` | `just test` on the changed area (`just test tests/<pkg> -n auto`) |
| `frontend/` | `just test-ui` |
| `vscode/` or a config model | `just test-vscode` |
| `plugins/kraft-lite/` | CI's own command: `uv run --python 3.10 --no-project --with pytest pytest plugins/kraft-lite/tests -q` |
| `docsite/` or `frontend/src/` (the `docs` job) | `dev/build_docs_site.sh docsite/dist-site && python3 dev/check_llm_docs.py docsite/dist-site`; `just docs-site` ends in a server that never exits |

**Never run `pytest` directly.** A hook blocks it; `just test` is the door.
The kraft-lite row is the one exception: CI runs it on Python 3.10 with
nothing installed, which `just test` cannot reproduce. `just ci-test`
reproduces CI's `lint` and unit jobs on the full suite (about 14 minutes);
keep it for when a targeted run cannot reproduce a failure. A unit shard red
on one Python version only: `just test-py 3.12` (or `3.13`, `3.14`).

## Regenerate, never hand-edit

- Lockfiles: `uv lock` for `uv.lock`; `npm install` inside `frontend/`,
  `vscode/`, `vscode/dev/screenshots/` or `docsite/` for that folder's
  `package-lock.json`. In a conflict, take `main`'s lockfile, then rerun the
  command.
- `vscode/schemas/`: `just schemas`.
- `frontend/src/types/vocab.generated.ts`: `just vocab`.
- `tests/api/api_shapes.json`: `just shapes`.
- `src/kraft/templates/lucide_icons.txt`: `just icons`.
- `frontend/src/ng/theme/theme.css` and `src/kraft/console/theme.py`:
  `uv run python dev/gen_theme.py`.
- Never touched by a pull request, because the release writes them:
  `CHANGELOG.md`, `vscode/CHANGELOG.md`, `_Added in X.Y_` notes, and the
  `version` in the files `dev/stamp_plugin_versions.py` lists (the plugin
  manifests under `plugins/`, `vscode/package.json`,
  `vscode/package-lock.json`).
- The opposite rule for add-ons: a change to `addons/<name>/` raises that
  add-on's `version` in `addons/<name>/.kraft/addon.json`, in the same pull
  request (CONTRIBUTING.md, "Kraft's own collection", says by how much).
  `kraft-default` (`config/`) ships with Kraft and takes Kraft's version, so a
  `config/` change needs no raise.

## Merge conflicts

The repo merges by squash, so a merge commit on the branch costs nothing:
merge `origin/main` into the branch, never rebase or force-push a branch you
did not create. After a merge, rerun the regenerate steps for any generated
file either side changed. Never push to a branch whose pull request already
merged: cut a fresh one from `origin/main` (CONTRIBUTING.md, "Branch hygiene").

## Answering each check

- **`release impact declared`**: the pull request needs exactly one
  `release::*` label. Set it on a pull request you opened (CONTRIBUTING.md,
  "Pull requests and release labels" says which). On anyone else's, say which
  label you would pick and let the maintainer apply it. Never add
  `notes::highlight`; that is the maintainer's call.
- **`removals declared`**: the description is missing a `## Removed tests` or
  `## Removed requirements` line for something the diff deletes or renames.
  Fix the description, not the code. Check it locally with
  `uv run python dev/check_removals.py origin/main BODY.md`. Editing the body
  starts no run: re-run the failed `removals declared` job afterwards.
- **`lint`**: `just fix` covers ruff. A `dev/check_docs_*.py` failure names the
  page and the rule; CONTRIBUTING.md's "User-facing docs" explains each. A
  `check_addon_versions.py` failure means an add-on changed without its
  `version` raised (above).
- **An intent pin** (`python -m kraft.intent` in the first test shard):
  repoint it with `just intent-repoint OLD NEW`.
- **`e2e (real CLIs)`, `playwright`, `ui contract`**: real failures as often as
  not. Reproduce with the command in CONTRIBUTING.md's table before calling
  one a flake. `e2e (macOS sandbox, …)` is in the `test` gate too and cannot
  be reproduced on Linux: read its log and fix from that.
- **`docs tests`**: a docs-only pull request's stand-in for the unit tier. It
  runs the unit test files that name a docs path; CONTRIBUTING.md's table says
  how to find them.
- **`docs nudge`, `tests nudge`**: comments, never failures. A docs nudge saying
  source moved without its docs page is usually right: update the page in this
  pull request (CONTRIBUTING.md, "User-facing docs"). A tests nudge is
  information; act on it only if it names a test you deleted without declaring.
- **`test report`, `codeql`**: `test report` only reports. Treat a `codeql`
  alert like a security review finding.

## The description

Keep the template's sections. A pull request that ships something has one
user-facing line under `## Changelog`; a `release::none` one leaves it empty.
A new docs page, section or CLI verb carries `_Added in next_` under its
heading, never a version number.

## Ask the user first

- Pushing a `kraft/<branch>-screenshots` branch for a pull request that changes
  a screen. CONTRIBUTING.md, "A pull request that changes a screen", says what
  it holds.
- Any change to `.github/workflows/`, `RELEASING.md` or
  `.claude/skills/release/`, unless the pull request is about them.
- Changing a label on a pull request you did not open.