Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 19 additions & 18 deletions .claude/skills/pr-flow/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,17 +67,19 @@ This repo adopted the [Developer Certificate of
Origin](https://developercertificate.org/) (maintainer decision on #4861).
Commit with **`git commit -s`**, every time.

⚠️ **The [probot DCO app](https://probot.github.io/apps/dco/) is not installed on
this repo yet**, so today nothing fails an unsigned commit. Installing it is an
org-admin step (#4867). Sign off anyway: once the app is on, it checks
every commit in a PR, so an unsigned commit pushed today becomes a red check
that can only be cleared by rewriting history. Once the app enforces the check,
`AGENTS.md` gains the signoff rule and this paragraph goes.

What the app checks: each commit carries a `Signed-off-by: Name <email>` trailer
The rule is checked twice, by the same script (`scripts/verify-dco.mjs`):

- **`npm run verify:dco`**, a stage of the pre-push gate (step 4), over
`origin/v2/main..HEAD`: the commits you are about to push. Name another
range with `-- --base <rev> --head <rev>` (a stacked branch's base, say).
- **The `DCO signoff` job** (`.github/workflows/dco.yml`), on every pull
request except into `main`, over the PR's `base.sha..head.sha`.

What it checks: each commit carries a `Signed-off-by: Name <email>` trailer
whose name **and** email match the commit's author or its committer. Merge
commits and bot-authored commits are exempt. There is no partial credit: one
unsigned commit out of six fails the whole check.
unsigned commit out of six fails the whole check. The failure names each
unsigned commit and prints the repair below.

Two things that look like automation and are not:

Expand All @@ -90,20 +92,19 @@ Two things that look like automation and are not:
`git var GIT_AUTHOR_IDENT` returns your config identity rather than the
preserved author, so it cannot even tell it is signing for someone else.

**Repairing commits already pushed** means rewriting them:
**Repairing commits already made** means rewriting them, onto the merge base
the failure prints:

```sh
git rebase HEAD~<n> --signoff
git rebase --signoff <merge-base>
git push --force-with-lease
```

Use `--force-with-lease` rather than `--force`, and rewrite only when you are the
sole author and nobody has based work on the branch (a stacked PR above yours
has). The two apparent alternatives are not alternatives: the app's empty
"remediation commit" flow needs `allowRemediationCommits.individual`, and this
repo ships no `.github/dco.yml`, so it is disabled; and the override button that
anyone with write access sees only silences the check, with nobody certifying
anything.
`--signoff` signs each commit as you, the committer. Use `--force-with-lease`
rather than `--force`, and rewrite only when you are the sole author and
nobody has based work on the branch (a stacked PR above yours has). Nothing
else clears the check: an empty "remediation" commit signs nothing that came
before it, and the check reads every commit in the range.

The signoff is a DCO assertion made in **your own name**. It does not claim you
wrote the code, so signing off a cherry-pick is legitimate. Fabricating
Expand Down
14 changes: 14 additions & 0 deletions .claude/skills/pre-push-gate/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,20 @@ repo root**, commit the lockfile if it changed, and re-run. It is the first stag
passes every static check and fails later as a test reporting the _old_
dependency's behavior. Do not "fix" that test.

### `verify:dco`

A commit in `origin/v2/main..HEAD` has no `Signed-off-by:` trailer, or one
whose name and email match neither its author nor its committer. The output
names each commit and the repair: `git rebase --signoff <merge-base>`, then
`git push --force-with-lease` if it was already pushed. Commit with
`git commit -s` from then on. The rule, and when a rewrite is safe, is
`pr-flow` step 3.

"cannot resolve origin/v2/main" means the ref is missing from this clone:
`git fetch origin v2/main`. On a stacked branch the gate also checks the lower
branch's commits; `npm run verify:dco -- --base <lower branch>` checks only
yours.

### `format:check` / `format:check:root`

Run `npm run format` at the **root**. It covers `scripts/`, the root configs
Expand Down
52 changes: 52 additions & 0 deletions .github/workflows/dco.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
name: DCO

# Every commit a pull request adds carries a DCO signoff (#4919). It replaces
# the probot DCO app, which never ran on this repo. The rule and its two
# exemptions (merge commits, bot-authored commits) are in
# `scripts/verify-dco.mjs`; `local:gate` runs the same script over
# `origin/v2/main..HEAD` before a push.
#
# On `pull_request`, unlike typescript.yml and python.yml: only a PR knows its
# base, and the range to check is exactly `base.sha..head.sha`. `edited` is
# there so retargeting a stacked PR re-checks its new range. PRs into `main`
# are skipped: the only one is a milestone merge, whose commits were each
# checked on their way into `v2/main`, and whose range reaches back past the
# adoption of the rule.
on:
pull_request:
types: [opened, synchronize, reopened, edited]
branches-ignore: [main]

# Read-only, and handed no secret, so this job holds no credential and stays
# outside the SHA-pin rule (`verify:action-pins`).
permissions:
contents: read

concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number }}
cancel-in-progress: true

jobs:
dco:
timeout-minutes: 5
name: DCO signoff
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
with:
ref: ${{ github.event.pull_request.head.sha }}
# The whole history, so both ends of the range and their merge base
# are present.
fetch-depth: 0
persist-credentials: false

- uses: actions/setup-node@v7
with:
node-version: 22

# Node built-ins and git only, so no `npm ci`.
- name: Check every commit is signed off
env:
BASE_SHA: ${{ github.event.pull_request.base.sha }}
HEAD_SHA: ${{ github.event.pull_request.head.sha }}
run: npm run verify:dco -- --base "$BASE_SHA" --head "$HEAD_SHA"
Comment thread
cliffhall marked this conversation as resolved.
49 changes: 30 additions & 19 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,19 +19,19 @@ not in advance.

## Skills index

| Skill | Covers | How it loads |
| ------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------- |
| [`board-ops`](.claude/skills/board-ops/SKILL.md) | `gh project` recipes and the IDs for the Servers V2 board (#43); resolving option IDs by name; the option-deletion hazard and its recovery | Model-invoked, or `/board-ops` |
| [`issue-create`](.claude/skills/issue-create/SKILL.md) | The create flow: duplicate check, `v2` + type + server-scope labels, milestone, board card, Status + Priority, and the query that verifies them | Model-invoked, or `/issue-create` |
| [`pr-flow`](.claude/skills/pr-flow/SKILL.md) | Issue to PR: branch, DCO signoff and repair, the gate, client evidence, `addCloseIssueReferences`, the Copilot loop and its exits, close-out on merge | Model-invoked, or `/pr-flow` |
| [`issue-triage`](.claude/skills/issue-triage/SKILL.md) | Inflow: the class check and canned responses (listings, new servers, duplicates, outside PRs), pass 1 onto the board as Incoming, the priority rubric and its score comment, the board audit | Model-invoked, or `/issue-triage` |
| [`pre-push-gate`](.claude/skills/pre-push-gate/SKILL.md) | Running `npm run local:gate` and reading its result; what each stage checks and how to fix it when it fails; waiting on the gate lease | Model-invoked, or `/pre-push-gate` |
| [`security-advisory`](.claude/skills/security-advisory/SKILL.md) | A privately reported vulnerability end to end: the `[GHSA-…]` draft card, server or SDK ownership, the reach classes, accepting, the private fork, publishing, public tracking | Model-invoked, or `/security-advisory` |
| [`project-structure`](.claude/skills/project-structure/SKILL.md) | What is inside each server: the TypeScript and Python layouts, where each server registers its features, and where a new file goes | Model-invoked, or `/project-structure` |
| [`local-dev`](.claude/skills/local-dev/SKILL.md) | Install, build and run each server from the checkout over the transports it implements; local `npx`/`uvx` and client-config runs; stale builds and fresh worktrees; the `overrides`, lockstep and `uv.lock` procedures | Model-invoked, or `/local-dev` |
| [`testing`](.claude/skills/testing/SKILL.md) | The in-process protocol-level harness, test placement, the commands per suite, `test` versus `coverage`, and clearing the per-file coverage gate | Model-invoked, or `/testing` |
| [`release`](.claude/skills/release/SKILL.md) | A milestone release end to end: the release issue, the preparation PRs on `v2/main` (audit, Version Packages, Python CalVer), the pure `v2/main` → `main` merge PR, the release ledger, and what a maintainer publishes | **Name-only**: `/release` |
| [`client-smoke`](.claude/skills/client-smoke/SKILL.md) | Driving a built server with the Inspector CLI (the scripted path), the Inspector web UI (by hand) and an LLM client; the CLI's argument split and exit codes; protocol eras and what can be exercised today | Model-invoked, or `/client-smoke` |
| Skill | Covers | How it loads |
| ---------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------- |
| [`board-ops`](.claude/skills/board-ops/SKILL.md) | `gh project` recipes and the IDs for the Servers V2 board (#43); resolving option IDs by name; the option-deletion hazard and its recovery | Model-invoked, or `/board-ops` |
| [`issue-create`](.claude/skills/issue-create/SKILL.md) | The create flow: duplicate check, `v2` + type + server-scope labels, milestone, board card, Status + Priority, and the query that verifies them | Model-invoked, or `/issue-create` |
| [`pr-flow`](.claude/skills/pr-flow/SKILL.md) | Issue to PR: branch, DCO signoff and repair, the gate, client evidence, `addCloseIssueReferences`, the Copilot loop and its exits, close-out on merge | Model-invoked, or `/pr-flow` |
| [`issue-triage`](.claude/skills/issue-triage/SKILL.md) | Inflow: the class check and canned responses (listings, new servers, duplicates, outside PRs), pass 1 onto the board as Incoming, the priority rubric and its score comment, the board audit | Model-invoked, or `/issue-triage` |
| [`pre-push-gate`](.claude/skills/pre-push-gate/SKILL.md) | Running `npm run local:gate` and reading its result; what each stage checks and how to fix it when it fails; waiting on the gate lease | Model-invoked, or `/pre-push-gate` |
| [`security-advisory`](.claude/skills/security-advisory/SKILL.md) | A privately reported vulnerability end to end: the `[GHSA-…]` draft card, server or SDK ownership, the reach classes, accepting, the private fork, publishing, public tracking | Model-invoked, or `/security-advisory` |
| [`project-structure`](.claude/skills/project-structure/SKILL.md) | What is inside each server: the TypeScript and Python layouts, where each server registers its features, and where a new file goes | Model-invoked, or `/project-structure` |
| [`local-dev`](.claude/skills/local-dev/SKILL.md) | Install, build and run each server from the checkout over the transports it implements; local `npx`/`uvx` and client-config runs; stale builds and fresh worktrees; the `overrides`, lockstep and `uv.lock` procedures | Model-invoked, or `/local-dev` |
| [`testing`](.claude/skills/testing/SKILL.md) | The in-process protocol-level harness, test placement, the commands per suite, `test` versus `coverage`, and clearing the per-file coverage gate | Model-invoked, or `/testing` |
| [`release`](.claude/skills/release/SKILL.md) | A milestone release end to end: the release issue, the preparation PRs on `v2/main` (audit, Version Packages, Python CalVer), the pure `v2/main` → `main` merge PR, the release ledger, and what a maintainer publishes | **Name-only**: `/release` |
| [`client-smoke`](.claude/skills/client-smoke/SKILL.md) | Driving a built server with the Inspector CLI (the scripted path), the Inspector web UI (by hand) and an LLM client; the CLI's argument split and exit codes; protocol eras and what can be exercised today | Model-invoked, or `/client-smoke` |

A PR that adds a skill under `.claude/skills/<name>/SKILL.md` adds its row to
this table in the same change, and the table never lists a skill that does not
Expand Down Expand Up @@ -120,7 +120,8 @@ npm run local:gate

**Run `npm run local:gate` before every push, and push only when it exits 0.**
It runs every check CI runs, for both languages, in one command:
`verify:install-fresh`, the root `validate` (the guards, then each TypeScript
`verify:install-fresh`, `verify:dco` (every commit since `origin/v2/main` is
signed off), the root `validate` (the guards, then each TypeScript
workspace's format check, lint, typecheck, build and tests), `coverage` (each
TypeScript workspace's per-file coverage gate), `validate:py`
(each Python server's locked sync, `ruff check`, `ruff format --check`,
Expand Down Expand Up @@ -251,6 +252,15 @@ together.
PR merges to `v2/main`. The server dropdown in each form does not label the
issue; triage applies the `server-<name>` scope label from it.

**Every commit is signed off** under the [Developer Certificate of
Origin](https://developercertificate.org/) (adopted on #4861): commit with
`git commit -s`, so the message ends in a `Signed-off-by: Name <email>`
trailer whose name and email match the commit's author or committer. Merge
commits and bot-authored commits are exempt. `verify:dco` checks it in the
pre-push gate, and the **DCO signoff** job (`.github/workflows/dco.yml`) checks
every commit a pull request adds; one unsigned commit fails it. The repair
(`git rebase --signoff`) is in the `pr-flow` skill.

**Every PR references an issue.** The PR body's first line is
`Closes #<ISSUE_NUMBER>`. A PR with no linked issue has no board card, so the
work is invisible to the board. If there is no issue yet, create it first. This
Expand Down Expand Up @@ -327,11 +337,12 @@ from happening. How to write a description that fires, and eval cases that
measure it, is [`docs/skill-authoring.md`](./docs/skill-authoring.md).

1. **`npm run verify:skills` must pass.** It runs inside `validate:guards`, and
CI runs it with `verify:skills:cli` (the authoritative `claude plugin
validate`, at a pinned CLI version) on every push and pull request. It parses
each `SKILL.md`'s frontmatter the way Claude Code does. Malformed YAML loads
the body with an _empty_ description, so `/name` still works while the skill
can never auto-fire; an unquoted `#` truncates the description silently.
CI runs it with `verify:skills:cli` (the authoritative
`claude plugin validate`, at a pinned CLI version) on every push and pull
request. It parses each `SKILL.md`'s frontmatter the way Claude Code does.
Malformed YAML loads the body with an _empty_ description, so `/name` still
works while the skill can never auto-fire; an unquoted `#` truncates the
description silently.
**Quote any description containing `#` or `:`**, and keep the opening `---`
on the file's first line.
2. **Every skill declares `disable-model-invocation` explicitly. Default it to
Expand Down
Loading
Loading