Use when auditing, reviewing or tightening an existing CI pipeline (e.g. Woodpecker, GitHub Actions, GitLab CI), when a secret scan or other gate logs 0 items or has no self-test or must-fail proof, when guarding the pipeline file itself with a test, when pipeline or gate files are protected or operator-signed, or when asked to make CI "stricter", "more reliable" or "trustworthy" — checks that every gate really verifies something (non-zero counts in the log, a must-fail proof), covers every file, runs at a strict level, and is pinned and ratcheted. Also use when a gate has been green for a long time without ever catching anything. Not for setting up a new pipeline or closing local/CI drift — that is ci-local-parity.
77
97%
Does it follow best practices?
Run evals on this skill
Adds up to 20 points to the overall score
Passed
No findings from the security scan
The core rule: a green step proves nothing until its log shows what it checked (a count above zero) and it has failed at least once on a deliberate violation.
ci-local-parity asks "does local check the same as CI?". This skill asks
the other question: "does each gate really check anything, and as strictly
as is reasonable?". A secret scanner that was green for weeks while logging
"0 commits scanned" passes every parity check. For setting up a pipeline or
closing local/CI drift, use ci-local-parity first; hand parity findings
(a local gate CI lacks, or the reverse) to it instead of fixing them here.
Default to audit-only (steps 0-2, report, stop) unless the user asked for changes. Pushing to the default branch needs the owner's go-ahead. Audit-only still delivers each fix as a ready-to-apply patch (a diff or a file), never as prose; "you may edit" is permission, not a request.
Highest yield first when time is short: how many items each scan step
logged (0 or 1 means vacuous); whether the secret scan has a self-test and
full history; whether hooks are active; which files the lint steps skip
versus git ls-files; which images are unpinned. Then walk the checklist.
Orientation (cheap). Fetch, then git log origin/<default>..HEAD
(unpushed commits of others) and git log HEAD..origin/<default>
(someone may have pushed the same fix). Is the default branch green now,
and how does the last green run relate to HEAD? Did a scheduled run ever
succeed? A default branch that is already red is its own finding at the
top of the report; triage it from the failed step
(log-evidence.md). Are local hooks really
active (git config --show-origin --get-all core.hooksPath; /dev/null
means every local gate is off, so CI is the only reliable gate until
fixed; test hook logic by running the scripts directly)? Findings go
stale while an audit stays open (a scheduled run fires, someone pushes):
re-check the schedule and HEAD..origin/<default> right before fixing.
Also read the role and commit rules in the agent rules (for example
"the orchestrator commits, executing agents never do"); when in doubt
have the owner confirm who may commit.
Repository governance wins over this skill: when the pipeline, hooks or
gate scripts are protected, a blocked commit is an outcome ("operator-gated"), not
an obstacle: protected-pipelines.md.
Inventory. Read the scripts the pipeline calls, not only the pipeline
file: a wrapper decides what really runs, and a non-strict mode can make a
skipped tool exit 0. For each step write down what it claims, what its
log shows it checked (files, commits, tests, rules) and which files it ran
on versus git ls-files. Evidence per tool class and a loop to pull it
from every step log: log-evidence.md.
Gap analysis. Walk the checklist. Mark an item "n/a" with a reason when it does not fit (proportionality: a ratchet or parity test is heavy for a small content repo) or cannot be audited read-only (needs history or a quality server); a historical red run is must-fail proof only when it failed for that gate's reason, otherwise the gate is "unproven". Record each gap with a severity and one concrete failure scenario ("a leaked key in commit N would pass because the scan sees 0 commits").
| Level | Meaning | Examples |
|---|---|---|
| high | the gate cannot fail | 0 items scanned and exit 0 (always high, even with a pinned tool); silently skipped; a tool crash passes |
| medium | partial or unproven | 1 of N commits scanned (high when no full-history scan exists anywhere); no must-fail proof; unpinned image; half the files linted |
| low | strictness and hygiene | default rule set; advisory step with a written reason that is not the only check of a critical property |
Edge cases: a local-only gate (e.g. a live-site test) is "n/a" with a reason; when the last green run is a scheduled run, compare with the last green push run on the default branch; without a checkout, audit from a read-only clone in a scratch directory.
Tighten in small commits, one gate per commit, only when changes were asked for. After each push, fetch the CI log for that exact commit and quote the evidence line. Order of work, copies across repositories, baselines, pre-flight, debt a new gate exposes, worktrees and commit hygiene: gate-rollout.md. Worked example of a gate script with its self-test: secret-scan.sh.
Evidence report in one place: pushed hash, CI run number, the quoted log lines, before/after numbers (see Report form), never "improved". Tie the run to the commit by its sha and take the evidence from the run that contains the hash and exactly that gate (log-evidence.md). Take every number in a commit message or report from the run, never an estimate: a wrong figure in a pushed commit cannot be corrected.
Effectiveness
|| true, 2>/dev/null || other-tool (in CI the
fallback may check an empty input and exit 0, so a crash and a real
finding both pass), continue-on-error, an early exit 0, and
install-and-ignore (<install tool> || true, which can hide that the
image lacks the installer or the tool). Assert what the step needs
(e.g. git --version) instead. A scanner that logs an error line must
fail even when its exit code is 0. Run the same hunt over your own
patch before reporting: a fix that adds 2>/dev/null || echo 1 has
only moved the gap.command -v tool || exit 0)? Acceptable locally only
with a blocking CI counterpart (the boundary is whether one exists);
in CI it must fail, and a hook that is the only barrier should fail
too. Enabling, testing and degrading hooks:
local-hooks.md.git init or commit isolated
from a git hook environment (GIT_DIR exported)? That includes the
repository's own test fixtures
(grep -rnE 'git.*(init|commit)' tests/). Proof and damage check:
selftest-patterns.md.failure: ignore, allow_failure) listed
with a written reason? Otherwise it is a gate that cannot fail.Coverage
git ls-files per file type (extensionless files such as git
hooks escape an *.sh glob). Hand-kept lists inside scripts (a compile
list, a test file list) drift when a file is added.files list covers
1 of 18 source files. Log the input count and compare with git ls-files.Strictness
Clone and history
${PREV:-}..HEAD)
guarded for first and forced pushes? Log range: A..B (N files changed): begin equal to end (a restart) with 0 changed files is
fine, an invalid range must mean "run everything", and a gate that
processed 0 items may only be green when N changed files is also 0.
For a range scan the logged count is at least the commits with added
non-binary lines in that range.Supply chain
>= ranges without a lockfile, npx --yes <tool>, binaries fetched by curl without a checksum, git-URL
dependencies? Does the dependency audit find at least one manifest,
and is the audit gate in the deploy step's depends_on? Is every
npx pinned to an exact version at least two weeks old (from the
lockfile or the last green run), including tool configs
(dependency-audit.md)?Pipeline shape
killed on the time-out is no run, and
a scanner without a failing exit code on findings is a report, not a
gate (woodpecker.md,
dependency-audit.md).Regression
cmp (a wrapper may rewrite diff output); silent
divergence means one repository runs an older, weaker gate.The pipeline file is code without a safety net; lint only checks syntax. A guard test in the normal test suite (dependency graph, image pins, producers before consumers, ignore allowlist with deadline) catches what otherwise shows up only after a push: pipeline-definition-guard.md.
Start with a summary table: images pinned n/m, files linted vs git ls-files
per language (shell, config, container), commits scanned n vs the scan's own
expected lower bound m (never vs git rev-list --all), gates with a
must-fail proof n/m, floors present.
Per gate: claim, evidence line from the log (quoted), status (proven, unproven, n/a or not verified, always one of these words), must-fail proof (commit or run number), gap, fix commit. A finding that needs an operator carries both labels: unproven and operator-gated. End with before/after numbers and what remains open. A gate with no must-fail proof is "unproven", not passing.
A reference shape to compare with: every image pinned by digest; a gate script that fails when a tool is missing; coverage floors per glob; the quality gate proven red once on a deliberate violation; a scan that logs its input count and exits non-zero on an error line.
Each tightening is committed, the CI run for that exact commit is green (checked from the CI system, never made green by weakening a gate), and the evidence line is quoted. What could not be verified is reported as "not verified" with the reason.
CI-specific pitfalls (clone depth, credentials, variables, log fetching): woodpecker.md.
vannifr/agent-skills
0e43fd4
If you maintain this skill, you can claim it as your own. Once claimed, you can manage eval scenarios, bundle related skills, attach documentation or rules, and ensure cross-agent compatibility.