Give a project its first real CI gate, built only from commands the repo already declares, and ratcheted so it goes green on day one. Use when a repo has no CI, has CI that has never passed, or has CI nobody trusts.
Installs into .claude/skills of the current project.
Are you the author of Ci Bootstrap?
Add the live security badge to your README. It updates with every re-scan.
[](https://www.skillsdirectory.com/skills/xkazm04-ci-bootstrap)
---
name: ci-bootstrap
description: "Give a project its first real CI gate, built only from commands the repo already declares, and ratcheted so it goes green on day one. Use when a repo has no CI, has CI that has never passed, or has CI nobody trusts."
category: ci-cd
memory: project
version: 0.4.1
tags: bootstrap, gate, ratchet, workflow, first-ci
argument-hint: "[project-root]"
---
# CI bootstrap
Most projects get CI at the wrong moment and in the wrong shape. Too early, it is a
copied template running commands the repo does not have. Too late, it is added in one
push, fails on forty pre-existing problems, and is switched off within a week.
This skill installs a gate that is **true on the day it lands**: it runs only commands
this repository actually declares, it blocks only on stages that pass right now, and
everything else is reported until someone fixes it. A gate that is green on day one is a
gate that stays.
## When to use
- The repo has no CI at all.
- The repo has CI that has never passed, or whose failures nobody reads.
- An agent is about to start making changes at volume and there is nothing catching them.
## Step 1: measure before you gate
Do this first and do not skip it. You cannot ratchet what you have not measured.
Discover the real commands, in this order of authority. **Never invent a command.**
1. `.ai/manifest.yaml` -> `capabilities` (`lint`, `typecheck`, `test`, `build`)
2. The project's own task definitions: `package.json` scripts, `Makefile` targets,
`justfile` recipes, `pyproject.toml` tool config, `Cargo.toml`
3. Any existing CI config - whatever it runs IS the current gate, and it wins ties
Then run each one locally and record the result. Report the table before writing anything:
```
format ok 0.8s npm run format:check
lint FAIL 4.1s npm run lint 37 problems
typecheck ok 9.2s npx tsc --noEmit
test ok 22.0s npm test 104 passed
build - - not configured
```
`not configured` is a real outcome and is reported as itself. A stage with no command in
this repo does not get one made up for it.
## Step 2: place each stage on a rung
Three rungs, and the assignment is decided by the measurement, not by preference.
| rung | goes here when | behaviour |
| --- | --- | --- |
| blocking | the stage passes today | fails the build |
| reporting | the stage fails today | runs, prints, exits 0 |
| absent | no command exists | not in the workflow at all |
The reporting rung is the ratchet. It exists so a pre-existing problem does not block an
unrelated change - which is how gates get deleted - while still being visible on every
run. Each reporting stage gets a one-line comment in the workflow naming what is wrong
and roughly how much: `# 37 lint problems as of <date>; promote to blocking when 0`.
Never put a stage on the reporting rung because it is slow or annoying. Reporting is for
"currently failing", nothing else.
## Step 3: write one workflow, with these five properties
1. **One job per stage, named for the stage.** Not one job running five commands in a
chain - a chain aborts at the first failure and hides every stage after it, so a
single early breakage blinds the whole gate.
2. **Path filters that include the workflow itself** and the dependency lockfile. A gate
that can be edited without running is not a gate. If in doubt, filter coarsely: over-
triggering costs seconds, under-triggering costs a whole class of unchecked change.
3. **A schedule trigger as well as the change trigger.** A change-only trigger means a
repo that goes quiet never re-reports, and the last green result stays on the page
while everything underneath it drifts. Weekly is enough. The scheduled run reports and
does not block.
4. **No install step the gate does not need.** Keep the gate dependency-light so it is
fast and so it can be run by anyone debugging it.
5. **Fail on a stage that did not run.** If a command is missing or a glob matched
nothing, that is a failure of the gate, not a pass. Zero files checked is never green.
## Step 4: wire the same commands locally
The gate's commands and the local pre-push commands are the same commands. Add them to
whatever hook system the repo already has - `lefthook`, `husky`, `pre-commit`. **Never
add a second hook system.** If there is no hook system, do not install one; point the
author at `ci-gate-check` instead, which reads the same declarations.
## Step 5: report
```
CI bootstrap complete.
blocking format, typecheck, test
reporting lint (37 problems, 2026-08-21)
absent build (no command declared)
triggers push+PR on <paths>, weekly schedule
local 3 stages wired into existing lefthook pre-push
next fix lint to 0, then promote it to blocking
```
## Rules
- Never invent a command. `not configured` is an honest result; a fabricated passing
command is a false green with a plausible name on it.
- Never land a gate that is red on the day it lands. Ratchet instead.
- Never chain stages into one job.
- Never let a stage be silently skipped. Not-run, not-configured and passed are three
different outcomes and must render as three.
- Do not add a second hook system, a second lint config, or a second version of any tool
the repo already has. One authority per rule.
- Do not raise thresholds or add exemptions to make a stage pass. That is a change to
what the gate means, and it needs its own commit and a reason.
## Related
- `ci-gate-check` - runs this same gate locally before a push.
- `ci-triage` - what to do when the gate this skill installed goes red.
- Knowledge: `quality-gates` (gate-laddering, ratchet-design, gate-liveness),
`pipeline-authoring` (change-scoped-work-selection),
`machine-paced-delivery` (pre-authorship-verification).
---
<!-- clause: skill-reflection v6 - stamped by scripts/apply-skill-clauses.mjs from docs/skill-clauses/skill-reflection.md; edit the template, then re-stamp -->
## Skill Reflection
After the work, record only useful observations supported by this run. No lesson is
a valid result. Reflection inherits the task's authorization; it grants no additional
permission to edit another repository, send data, commit, or publish.
**Run log.** Unlike a lesson, this is written on every run that started work - failed and
aborted runs included; skip read-only info modes and runs cancelled before any work. Append
ONE line to `.ai/skill-runs.local.jsonl` at the root of the task's MAIN checkout: local,
gitignored run output inside the task's own repository, never a write into the registry.
A run that worked in a linked worktree logs in the main checkout that worktree belongs to
(`git rev-parse --git-common-dir`, minus `/.git`) - a disposable worktree takes the line
with it when it is removed. `log-run.mjs` resolves this on its own.
The registry pulls it later (`/librarian skills` on the same machine). When a registry
checkout is reachable (`registry.local` in `.ai/manifest.yaml`), prefer its writer, which
stamps project, device and version for you:
```sh
node <registry>/scripts/log-run.mjs --skill ci-bootstrap --outcome <o> --difficulty <1-5> \
--provider <claude|openai|xai|qwen|google|other> --model <your model id> [--effort <level>] \
[--tokens-est <n>] --result "<one sentence>" --comment "<self-reflection>"
```
Otherwise write the line yourself: `{"ts":"<ISO, UTC Z>","skill":"ci-bootstrap","outcome":…,
"difficulty":…,"provider":…,"model":…,"effort":…|null,"tokensEst":…|null,"result":…,"comment":…}`.
- `outcome`: `shipped` (the goal landed) / `partial` / `no-op` (ran correctly, nothing to
do) / `parked` (designed or staged, deliberately not landed) / `failed` / `aborted`.
- `difficulty` rates the task as this run met it: 1 trivial - mechanical; 2 routine - the
method as written; 3 demanding - real judgment calls or one detour; 4 hard - dead ends,
rework or an operator course-correction; 5 at the edge - partial or failed on the merits.
- `model`/`effort` as your harness states them (`null` effort when you cannot see it).
`tokensEst` is the drop in the harness's remaining-token counter since this skill was
invoked, or `null` (with `log-run.mjs`, omit `--tokens-est`); exact figures are measured
later from transcripts - never guess one.
- `result` is one line (max 240 chars). `comment` (max 2000) is the self-reflection a
reviewer reads: what worked, what the method made harder, where its instructions were
wrong, missing or ignored. No filesystem paths or email addresses.
- Never read run logs during a run. They are evidence ABOUT this skill for its reviewer;
an executor that reads its own diagnosis contaminates the next measurement.
**Project learning.** Only when this run produced an observation that would change how a
future run behaves. A run that went as the method describes writes nothing: an entry that
restates the procedure, records "no issues", or repeats the task is a defect, not a
deliverable. When there is such an observation and local edits are within scope, put one
dated line in the overlay this skill's `## Project overlay` section names, under
`## Skill improvement log`. **Write only into an overlay that already exists.** If the
project has none, put the observation in the response instead - creating a new tracked
file for a reflection is scope the task did not ask for, and a reader who never asked for
the skill has to review it. If the overlay is a structured config (YAML, TOML, JSON),
record the note as comments so the file keeps parsing, or use the response.
Use a supplied memory contract only when its destination and writes are authorized.
Keep project details out of the shared method.
**Method learning.** Identify the installation before editing anything. A local
`.ai/registry-installation.local.json` receipt can identify development versus release,
the registry revision, and selected skill versions. Verify any link's actual target;
do not assume a skill directory is a writable registry link.
- For a pinned release, marketplace cache, ordinary copy, or unknown installation,
keep a proposal in the project overlay or response. Do not edit the installed method
or silently relink it. Adoption and rollback are explicit installation operations.
- For a development link, edit the registry only when that checkout is already within
the accepted task scope. Otherwise report a proposal. Authorized changes belong in
the source checkout, followed by its gates; commit only when the task authorizes it.
- Record an actual lesson in `LESSONS.md` against the version **used**:
`## <version-used> - <YYYY-MM-DD> - <project-name>` and concise bullets. A proposal
must be labeled as such; structural checks are not evidence of field effectiveness.
- Applied skill changes require a version bump: patch for wording, minor for a step
refinement, major for method redesign. A lesson alone needs no bump. Shared stamped
clauses are edited in the registry's `docs/skill-clauses/` and regenerated with
`scripts/apply-skill-clauses.mjs`, never patched in individual installed skills.
**Domain learning.** Follow `## Knowledge sync` when present, within the same scope
and privacy boundaries. A method lesson and a domain knowledge lead are different
artifacts; do not fabricate either to fill a reflection quota.
<!-- /clause: skill-reflection -->