Phase 15 — Skip api/harness CI jobs for doc-only PRs + markdown lint gate
Doc-only PRs currently pay the full slow CI bill (api + harness). This restructures CI so doc-only PRs skip the heavy jobs without deadlocking branch protection, and adds a real markdown lint gate.
What changed
15-01 — Markdown lint gate: add markdownlint-cli2@0.22.1 + root .markdownlint-cli2.jsonc (Prettier-compatible preset, .planning/** excluded), md:lint script, and a "Markdown lint" step in fast-checks. Fixed 13 baseline doc violations so the gate starts green.
15-02 — Doc-only skip: add a changes job (dorny/paths-filter@v4) emitting a code output; gate api/harness on needs.changes.outputs.code == 'true'; add an always-running gate aggregate (if: always(), per-job needs.X.result checks for Gitea bug #31007) that accepts skipped heavy jobs.
15-03 — Gating surface (partial): update publish.yml safety-gate comment to name the new required checks.
⚠ Required operator follow-up AFTER merge (Plan 15-03 Task 2)
Once this merges and CI / gate has reported on main, update branch protection on main:
Required checks → exactly CI / fast-checks + CI / gate
Remove CI / api + CI / harness (now conditionally skipped, gated via CI / gate)
Do NOT drop api/harness before CI / gate exists on main (ordering hazard — would leave main ungated).
This PR is itself a code PR (touches package.json/ci.yml), so fast-checks, api, harness, and gate all run — it is gated by the current protection and is the run that first emits CI / gate.
## Phase 15 — Skip api/harness CI jobs for doc-only PRs + markdown lint gate
Doc-only PRs currently pay the full slow CI bill (api + harness). This restructures CI so doc-only PRs skip the heavy jobs without deadlocking branch protection, and adds a real markdown lint gate.
### What changed
- **15-01 — Markdown lint gate**: add `markdownlint-cli2@0.22.1` + root `.markdownlint-cli2.jsonc` (Prettier-compatible preset, `.planning/**` excluded), `md:lint` script, and a "Markdown lint" step in `fast-checks`. Fixed 13 baseline doc violations so the gate starts green.
- **15-02 — Doc-only skip**: add a `changes` job (`dorny/paths-filter@v4`) emitting a `code` output; gate `api`/`harness` on `needs.changes.outputs.code == 'true'`; add an always-running `gate` aggregate (`if: always()`, per-job `needs.X.result` checks for Gitea bug #31007) that accepts skipped heavy jobs.
- **15-03 — Gating surface (partial)**: update `publish.yml` safety-gate comment to name the new required checks.
### ⚠ Required operator follow-up AFTER merge (Plan 15-03 Task 2)
Once this merges and `CI / gate` has reported on `main`, update branch protection on `main`:
- Required checks → exactly `CI / fast-checks` + `CI / gate`
- Remove `CI / api` + `CI / harness` (now conditionally skipped, gated via `CI / gate`)
Do NOT drop api/harness before `CI / gate` exists on main (ordering hazard — would leave main ungated).
This PR is itself a code PR (touches `package.json`/`ci.yml`), so `fast-checks`, `api`, `harness`, and `gate` all run — it is gated by the current protection and is the run that first emits `CI / gate`.
- Fix MD040 (11 bare fences): add language tags (text/bash) across 7 files
- Fix MD031 (2 violations): add blank lines around fence in GETTING-STARTED.md
- Wire 'Markdown lint' step to fast-checks job (after Format check, before Typecheck)
- Reformat .markdownlint-cli2.jsonc per Prettier (trailing commas in JSONC)
- pnpm md:lint exits 0; pnpm format:check exits 0; gate can fail on bare fence (verified)
- insert changes job (dorny/paths-filter@v4) before fast-checks
- changes job: permissions pull-requests:read, outputs code, no checkout
- code filter lists positive patterns: **/*.ts, apps/**, pnpm-lock.yaml, Dockerfile, etc.
- api job: needs [changes] + if combined with needs.changes.outputs.code == 'true'
- harness job: same needs/if pattern as api
- services, env, and step bodies unchanged in both heavy jobs
- gate job needs [fast-checks, changes, api, harness] with if: always()
- gate fails (exit 1) when fast-checks != success
- gate accepts success OR skipped for api and harness, fails on any other result
- uses individual needs.X.result checks (not wildcard) — Gitea 1.26.2 bug #31007
- once merged, emits CI / gate commit-status required by Plan 03 branch-protection update
- Replace "three required checks (CI / fast-checks, CI / api, CI / harness)"
with the new gating surface: CI / fast-checks + CI / gate
- Note that CI / api and CI / harness are conditionally skipped on doc-only PRs
and gated via the always-running CI / gate aggregate
- Comment-only change; no job/step/env/trigger modified
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Phase 15 — Skip api/harness CI jobs for doc-only PRs + markdown lint gate
Doc-only PRs currently pay the full slow CI bill (api + harness). This restructures CI so doc-only PRs skip the heavy jobs without deadlocking branch protection, and adds a real markdown lint gate.
What changed
markdownlint-cli2@0.22.1+ root.markdownlint-cli2.jsonc(Prettier-compatible preset,.planning/**excluded),md:lintscript, and a "Markdown lint" step infast-checks. Fixed 13 baseline doc violations so the gate starts green.changesjob (dorny/paths-filter@v4) emitting acodeoutput; gateapi/harnessonneeds.changes.outputs.code == 'true'; add an always-runninggateaggregate (if: always(), per-jobneeds.X.resultchecks for Gitea bug #31007) that accepts skipped heavy jobs.publish.ymlsafety-gate comment to name the new required checks.⚠ Required operator follow-up AFTER merge (Plan 15-03 Task 2)
Once this merges and
CI / gatehas reported onmain, update branch protection onmain:CI / fast-checks+CI / gateCI / api+CI / harness(now conditionally skipped, gated viaCI / gate)Do NOT drop api/harness before
CI / gateexists on main (ordering hazard — would leave main ungated).This PR is itself a code PR (touches
package.json/ci.yml), sofast-checks,api,harness, andgateall run — it is gated by the current protection and is the run that first emitsCI / gate.