verify
Skill6 starsChanged 2 months agoMITView on GitHub
Tier-aware evidence-gated pre-deploy gate — Pass/Partial/Fail verdict per AC; writes the sha-matched verify-complete.md that pk ship requires. Use when /work is finished, before pk ship.
188 other projects use a version of this.
Adopted by
Most recently taken up by
- Doist/todoist-aitheir own version, first seen Nov 9, 2026
- udecode/better-convextheir own version, first seen Nov 9, 2026
- pytorch/heliontheir own version, first seen Nov 8, 2026
- amir20/dozzletheir own version, first seen Nov 8, 2026
- Doist/todoist-clitheir own version, first seen Nov 8, 2026
- codewithmukesh/dotnet-claude-kittheir own version, first seen Nov 8, 2026
Used with
Projects with it also use instinct-export, instinct-import and 10 more.
Other files people use alongside this one.
Files found in the same public projects as a version of this one, more often than they turn up anywhere else. Each project counts once, large collections count for less, and this file's own repository is left out.
TheKREATORSSkillinstinct-export
Export instincts from project/global scope to a file
Also in 3 projects that use this
TheKREATORSSkillinstinct-import
Import instincts from file or URL into project/global scope
Also in 3 projects that use this
AviadPSkillrisk-assess
Assess the risk and blast radius of a proposed code change before implementing it. Evaluates CI impact, framework-wide vs localized scope, rollback path, and identifies blindspots. Use when user says "what's the risk", "is this safe", "assess the impact", "risk assess", "blast radius", or before applying framework-wide or CI-affecting changes. Do NOT use for code quality review (use code-quality) or plan validation (use design-validator).
Also in 3 projects that use this
diego-tobalinaSkillbuild-fix
Fix build and compilation errors systematically. Run when build fails.
Also in 6 projects that use this
AaravChadhaSkillsecure
Security review with a confidence gate - a finding exists only with a concrete exploit scenario and a high/medium/low confidence rating; everything else goes to a worth-hardening list, never inflated. Sweeps five surfaces: auth gates, secrets hygiene, injection and unsafe sinks, unsafe deserialization/crypto/transport, and LLM tool-use trust boundaries. Reports only, never fixes. Use when the user asks for a security review or to check vulnerabilities, secrets, or auth.
Also in 3 projects that use this
ArvorCoSkillspecify
Create feature specification from natural language description. Use when starting a new feature or creating a spec. Triggers on: create spec, specify feature, new feature spec.
Also in 10 projects that use this
- +1 more from TheKREATORS
Other versions188 projects use a version of this.
Word for word · 0
None found.
Edited · 0
None found.
Wrote their own version · 188
- react/react★ 252ksame name
- facebook/react★ 252ksame name
- garrytan/gstack★ 141ksame name
- oven-sh/bun★ 97ksame name
- anthropics/financial-services★ 41ksame name
- anthropics/financial-services-plugins★ 41ksame name
- Yeachan-Heo/oh-my-claude-sisyphus★ 41ksame name
- Yeachan-Heo/oh-my-claudecode★ 41ksame name
- anthropics/claude-quickstarts★ 19ksame name
- NVIDIA-NeMo/Speech★ 19ksame name
178 more not listed
How matches are found. Word for word: the exact same text. Edited: shares passages with this file (“99% same” means 99% of its five-word phrases also appear here; 10% or more counts). Their own version: similar meaning, or, weakest, the same name. We can’t tell which project had it first. Projects are counted among the 200 most-starred candidates, and mentions are links to the repository we found, up to 50 per site, so both counts are a floor.
In this repository32 other files it gives its agents.
- CLAUDE.mdCLAUDE.md
- brainstorm-reviewSkill
- brainstormSkill
- conceptSkill
- defineSkill
- financial-reviewSkill
- light-spec-reviseSkill
- light-specSkill
- linear-hygieneSkill
- linearSkill
- phase-planSkill
- pipekit-helpSkill
- pipekit-updateSkill
- pk-bugSkill
- pk-exitSkill
- pk-expressSkill
- pr-fixSkill
- prod-readySkill
- pr-security-reviewSkill
- release-changelogSkill
- repo-security-reviewSkill
- review-planSkill
- roadmap-createSkill
- roadmap-reviewSkill
- security-gateSkill
- spec-preflightSkill
- startupSkill
- strategy-createSkill
- strategy-syncSkill
- sync-linearSkill
- tpmSkill
- workSkill
The file
Show its rules and text
We split this skill into the rules it gives an agent
30 rules. 0 are common, 6 are distinctive, and 24 are very specific to this project.
0
Common
Most of the 167 edited versions we read keep these.
6
Distinctive
This author's own choices. Worth a look.
24
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Distinctive: line 183
Only tests you actually cite need this.
- Very specific: line 201
Run this block once per /verify run, in its own Bash call, with GATE_RC set to Step 3's result and a timeout long enough for the harness.
- Distinctive: line 258
Anchor — a consumer project, 2026-10: Every quote upload failed with a 422 because the extraction schema carried 17 union parameters …
- Very specific: line 266
Read the spec via the project's Linear route — pk issue show "$ISSUE" --fields id,title,description (verb table: sop/Linear_SOP.md § Linear …
- Distinctive: line 282
Use Task tool with:
- Very specific: line 347
After the subagent returns, read $VERIFY_DIR/qa-verdict.md and grep the Verdict: line to set QA_VERDICT (Pass | Partial | Fail) for use in …
- Very specific: line 349
If the subagent fails to write the file (Write permission denied, disk full), surface the error and treat the QA verdict as Fail — better …
- Very specific: line 399
If RUN_ADVERSARIAL=false, skip to Step 6.
- Very specific: line 411
Use subagent_type: "general-purpose" (same agent as QA — only the prompt differs).
- Very specific: line 465
After the subagent returns, read $VERIFY_DIR/adversarial.md and count findings to set ADVERSARIAL_FINDING_COUNT.
- Very specific: line 487
Run these checks in order.
- Very specific: line 506
prefer subagent_type: "pr-review-toolkit:code-reviewer" (the same specialist /pr-security-review uses)
- Very specific: line 557
After the subagent returns, read <VERIFY_DIR>/migration-review.md (or the inline block on tier:quick), grep the Verdict: line into …
- Distinctive: line 568
When QA ran and returned Pass, also parse the verdict block for non-"none" content in:
- Very specific: line 578
If the user invoked /verify --qa (regardless of Require QA review config), they signaled they want eyes on it.
- Very specific: line 601
If Step 5 ran and ADVERSARIAL_FINDING_COUNT > 0, surface one flag per finding (or one aggregate flag, your choice — the count matters more …
- Very specific: line 630
If SECCATS is empty (no categories file), skip this check silently — the gate is opt-in per project, exactly like /prod-ready and …
- Very specific: line 659
Never on FAIL / WARNINGS-to-block / indeterminate — the missing sentinel is what makes pk ship refuse.
- Distinctive: line 679
After all seven checks, count flags:
- Very specific: line 685
If FLAG_COUNT > 0, the verdict is treated as Pass-with-flags in Step 9's auto-ship decision, even if the QA verdict block says Pass.
- Very specific: line 777
Use the Write tool with file_path = "$VERIFY_DIR/reality-check.md" to land the file.
- Very specific: line 826
If the status is NEEDS WORK and a previous PASS run left a stale verify-complete.md on disk, delete it explicitly so the next pk ship …
- Very specific: line 869
If pk ship succeeds: print its output (PR URL + Linear transition) and exit.
- Very specific: line 870
If pk ship fails (push rejected, gh CLI error, branch protection): surface the error verbatim and STOP.
- Very specific: line 876
When the verdict is Pass but FLAG_COUNT > 0, do not auto-ship under any circumstance, even with --auto-ship.
- Very specific: line 941
No session-log writes — /pk-exit owns the session log.
- Very specific: line 942
No Linear status updates — pk ship and pk done post the necessary ones.
- Very specific: line 943
No PR creation — pk ship is separate.
- Distinctive: line 944
No re-running of failed tests — fix and re-invoke (idempotent).
- Very specific: line 946
No source modifications. Subagents have Read, Bash, Write — Write is scoped to $VERIFY_DIR.
“Common” is measured: kept by at least 6 in 10 of the 167 edited versions of this file we could read. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
/verify
North star: safe and frictionless. Helps, never adds work.
You verify that a feature branch's work is shippable. You run the project's pre-deploy gate (tests, lint, types), enumerate human-decision flags, and — when configured or invoked with --qa — spawn a QA review subagent that checks the diff against the issue's acceptance criteria.
v2.7 evidence layer. For tier:standard and tier:heavy, every run produces three files under Logs/Verify/<YYYYMMDD>/<ISSUE>/:
evidence.txt— raw gate output with per-command exit codes and durations, plus appendedFLAG: ...linesreality-check.md— structured verdict with gate results table, QA block, flags, status reasoningverify-complete.md— minimal sentinel written on PASS;tier:quickwrites only this file (a virtual sentinel), the other two stay stdout-only
pk ship reads verify-complete.md as a hard, sha-matched gate: it accepts a sentinel under any date dir whose sha: line matches the commit being shipped (git rev-parse HEAD). Without a HEAD-matching sentinel, ship aborts — bypass via --force (Linear audit comment) or PK_VERIFY_BYPASS=1 (emergency, logs to bypass.log). Every tier writes the sentinel on PASS, so quick is no longer a warn-but-proceed special case.
Triggers
/verify— primary; uses current branch's issue ID/verify <ISSUE-ID>— explicit/verify --qa— force QA review even ifRequire QA review: false/verify --review— force antagonistic review (mandatory anyway for tier:heavy)- "verify this" / "is this ready to ship?"
Required preconditions
- You are inside a worktree on a feature branch (or pass an explicit issue ID).
- There are committed changes on the branch (else: nothing to verify).
method.config.md§ Pre-Deploy Gate is configured (else/verifyexits with a clear "configure your gate" message).
Step 0 — Resolve issue, tier, and evidence dir
CURRENT=$(git branch --show-current)
ISSUE="${1:-}"
[ -z "$ISSUE" ] && ISSUE=$(echo "$CURRENT" | grep -oE '[A-Z]+-[0-9]+' | head -1)
[ -z "$ISSUE" ] && { echo "ERROR: pass an issue ID or run on a feature branch." >&2; exit 1; }
# Need at least one commit ahead of integration
INTEGRATION=$(pk config "Integration branch" "dev")
COMMITS_AHEAD=$(git rev-list --count "origin/$INTEGRATION..HEAD" 2>/dev/null || echo 0)
[ "$COMMITS_AHEAD" = "0" ] && { echo "ERROR: no commits ahead of $INTEGRATION — nothing to verify." >&2; exit 1; }
# Tier resolution — v2.7. pk_linear_tier returns empty (never a silent default)
# when there's no tier: label or Linear is unreachable — visibly warn here so
# a guessed tier can't pass as an explicit one.
TIER=$(pk_linear_tier "$ISSUE" 2>/dev/null)
if [ -z "$TIER" ]; then
TIER="standard"
echo "⚠ No tier: label resolved for $ISSUE (missing label, or Linear unreachable) — defaulting to tier:standard. /verify is guessing; confirm this matches the change's actual risk."
fi
# Stable per-run stamp; one directory per issue per day. Re-runs overwrite.
STAMP=$(date -u +%Y%m%dT%H%MZ)
TODAY=$(date -u +%Y%m%d)
VERIFY_DIR="Logs/Verify/${TODAY}/${ISSUE}"Step 0.5 — Tier-gated dispatch (one-screen contract)
| Tier | Evidence layer | reality-check.md | verify-complete.md | QA subagent | Antagonistic review | pk ship gate |
| quick | no | virtual (printed only) | minimal, on PASS | only if --qa | never | hard, sha-matched |
| standard | yes | written | written on PASS | per config + --qa | auto on sensitive-path diffs; opt-in via --review otherwise | hard, sha-matched |
| heavy | yes | written | written on PASS | per config + --qa | mandatory | hard, sha-matched |
Sensitive-path patterns are documented in Step 5.
For tier:quick, the rest of this skill executes against stdout only — Steps 5 and 7 are skipped and no evidence.txt / reality-check.md is written — except Step 8, which on PASS writes a single minimal verify-complete.md so pk ship's sha-matched gate can confirm this commit was verified. For tier:standard and tier:heavy, every step below appends to or writes the files under $VERIFY_DIR. Step 5 (antagonistic) is also skipped for tier:standard unless --review is passed.
Step 1 — Read configuration
Read from method.config.md:
Require QA review(defaultfalse) — controls whether QA subagent runs by default## Pre-Deploy Gate— bash code block of test/lint/type commands
Resolve --qa:
- Explicit
--qaflag → run QA Require QA review: truein config → run QA- Else → skip QA
Print one line:
Verify: <ISSUE-ID> · Tier: <TIER> · Gate: yes · QA: <yes|no> · Evidence: <VERIFY_DIR | (quick, virtual)>Step 2 — Initialize evidence layer (skip for tier:quick)
For TIER != quick, create the evidence directory and write the header:
if [ "$TIER" != "quick" ]; then
mkdir -p "$VERIFY_DIR"
cat > "$VERIFY_DIR/evidence.txt" <<EOF
# /verify evidence — $ISSUE
# stamp: $STAMP
# tier: $TIER
# integration: $INTEGRATION
# commits-ahead: $COMMITS_AHEAD
EOF
fiRe-runs overwrite evidence.txt rather than appending — each /verify invocation is a fresh snapshot of "what would ship from this HEAD."
Step 3 — Run the pre-deploy gate
Extract the bash block from the config's § Pre-Deploy Gate section. Run each command through a writer that captures the exit code and duration.
Implementation:
# Read the bash block between ```bash and ``` after the "## Pre-Deploy Gate" heading.
# The leading awk uses `next` after matching the start line so the end-pattern
# check (/^## /) does not also match the start line and self-terminate the range.
awk '/^## Pre-Deploy Gate[[:space:]]*$/{flag=1; next} /^## /{flag=0} flag' method.config.md \
| awk '/^```bash/,/^```$/' \
| grep -v '^```' > /tmp/pk-gate.sh
set -o pipefail
# tier:quick — bulk runner, stdout only.
if [ "$TIER" = "quick" ]; then
bash /tmp/pk-gate.sh
GATE_RC=$?
else
# tier:standard / heavy — per-command writer with anchors.
run_gate_command() {
local cmd="$1"
printf '==> $ %s\n' "$cmd" >> "$VERIFY_DIR/evidence.txt"
local start; start=$(date +%s)
bash -c "$cmd" 2>&1 | tee -a "$VERIFY_DIR/evidence.txt"
# `$?` with the `set -o pipefail` above — NOT ${PIPESTATUS[0]}. zsh has no
# PIPESTATUS (it spells it `pipestatus`, and 1-indexed), so under the zsh that
# Claude Code's Bash tool runs, ${PIPESTATUS[0]} expanded to empty: every gate
# command recorded `exit: 0` and `return $rc` fell through to the printf's
# status. A red gate was indistinguishable from a green one, and the evidence
# file preserved the false 0. Observed on a consumer project, 2026-07.
local rc=$?
printf 'exit: %d\nduration: %ds\n\n' "$rc" "$(($(date +%s) - start))" >> "$VERIFY_DIR/evidence.txt"
return $rc
}
GATE_RC=0
while IFS= read -r line; do
case "$line" in
''|'#'*) continue ;;
esac
run_gate_command "$line" || GATE_RC=$?
done < /tmp/pk-gate.sh
fiIf GATE_RC != 0:
✗ Pre-deploy gate FAILED.
The first failing command's last 30 lines:
<output>
Next: fix the gate failure, then re-run /verify (idempotent).For TIER != quick, still write reality-check.md (Step 7) with status NEEDS WORK. Do not write verify-complete.md. Do not run QA review on a failed gate. Do not run antagonistic review on a failed gate.
If GATE_RC == 0, print:
✓ Pre-deploy gate PASSED (<command list>).Step 3.5 — A green assertion is not coverage until something made it red (v4.26.0+)
A passing gate proves the suite ran, not that any of it can fail. Before citing any test as evidence that this change works — in the Linear comment, the PR body, evidence.txt, or a security-gate report — mutate the thing it guards and confirm the test goes red on its named assertion, not on a crash.
Only tests you actually cite need this. It is a claim-time obligation, not a whole-suite audit.
Three vacuous shapes, each observed shipping:
- The tautology.
expect(true).toBe(true), or an assertion decided by an early return before the guard is reached. - The fixture that cannot violate. A cross-tenant leak test seeded only with in-scope rows; a scope test whose
clients: []. It passes identically against the unfixed code. - The crash that reads as a failure. The test reddens on an unread
TypeErrorrather than the expectation, so it will keep "passing" once the crash is fixed and the real assertion is never exercised.
The check is one question: would this test still pass against the code before the fix? If yes, it is not evidence. Re-point it, or say plainly that the change is unverified.
Anchor — a consumer project, 2026-07. One issue cited two assertions as security evidence that could not fail (one fixture seeded only in-scope projects; one compared two identical computations through a seam the fix had deleted) — and then made the same vacuous-fixture mistake again on clientInfo in the very pass writing that lesson up. A second issue shipped four separate vacuous assertions before mutation checks caught them. Where the discipline was applied first, it paid immediately: a third issue's guard, authored red-first, failed on exactly its 5 divergent sites and exposed a resolver hole that would have silently exempted a CORS-granting function.
Step 3.6 — A mocked model cannot violate the provider's limits
A suite that mocks the model is Step 3.5's fixture that cannot violate, at the API boundary: the provider's schema limits, and its refusals of a schema it will not accept, live only on the real API. So a new or changed AI extraction call needs a real-API harness run recorded in this run's evidence. /verify is the last automated gate before pk promote carries the diff toward beta, so the record is required here.
Trigger. The diff (committed, origin/$INTEGRATION...HEAD) changes a file that holds a structured-output or tool-schema marker, or a path matching the project's AI extraction paths. The marker grep reads the whole changed file, not only its changed lines, because the anchor's defect was a union added inside an existing schema. Markdown, tests and Logs/ / Reports/ are excluded from it, since a test that mocks the call is not the call. An extraction prompt, a schema file kept apart from the call, or a call that parses free-text JSON with no schema carries no marker; a project covers those by declaring the path. A project with no AI extraction call never matches and pays nothing.
Run this block once per /verify run, in its own Bash call, with GATE_RC set to Step 3's result and a timeout long enough for the harness.
AI_MARKERS='input_schema|inputSchema|tool_choice|toolChoice|response_format|responseFormat|json_schema|jsonSchema|output_format|output_config|responseSchema|response_schema|outputFormat|outputConfig|responseJsonSchema|functionDeclarations|with_structured_output|response_model|generateObject|streamObject|Output\.object'
AI_PATHS=$(pk config "AI extraction paths" "") # ERE: prompts, schema files, schema-less calls
HARNESS=$(pk config "Real-API harness" "") # the project's command; none declared → none run
DIFF_FILES=$(git -c core.quotePath=false diff --name-only --diff-filter=d "origin/$INTEGRATION...HEAD" 2>/dev/null)
AI_CHANGED=$(printf '%s\n' "$DIFF_FILES" \
| grep -vE '\.md$|(^|/)(tests?|__tests__|__mocks__|spec|fixtures|e2e)/|\.(test|spec)\.|(^|/)test_[^/]*$|_test\.[a-z]+$|^(Logs|Reports)/' \
| while IFS= read -r f; do grep -qE "$AI_MARKERS" "$f" 2>/dev/null && echo "$f"; done)
if [ -n "$AI_PATHS" ]; then
AI_CHANGED=$(printf '%s\n%s\n' "$AI_CHANGED" "$(printf '%s\n' "$DIFF_FILES" | grep -E "$AI_PATHS")" | grep -v '^$' | sort -u)
fi
REAL_API_MARK=".pk-work/${ISSUE}.real-api-flag" # read by Flag check G in a later shell
mkdir -p .pk-work && rm -f "$REAL_API_MARK"; REASON=""
if [ -n "$AI_CHANGED" ] && [ "${GATE_RC:-0}" = "0" ]; then
if [ -z "$HARNESS" ]; then
printf 'AI extraction call changed (%s) and no Real-API harness is declared in method.config.md\n' "$(printf '%s\n' "$AI_CHANGED" | tr '\n' ' ')" > "$REAL_API_MARK"
else
HLOG=$(mktemp "${TMPDIR:-/tmp}/pk-real-api.XXXXXX") # 0600; real payloads stay out of every committed file
echo "real-API harness was killed before it recorded an exit code: counts as failed (NEEDS WORK); re-run with a longer timeout" > "$REAL_API_MARK"
start=$(date +%s)
PK_AI_CHANGED="$AI_CHANGED" bash -o pipefail -c "$HARNESS" > "$HLOG" 2>&1
HARNESS_RC=$?
rm -f "$REAL_API_MARK"
CAUSE=$(tr -d '\r' < "$HLOG" | grep -a '^real-api-unrun: ' | tail -1 | sed 's/^real-api-unrun: *//; s/[[:space:]]*$//')
case "$HARNESS_RC:$CAUSE" in
0:*) RESULT=pass ;;
75:server-error|75:rate-limit|75:auth|75:credits|75:network|75:timeout)
RESULT=unrun; REASON=$CAUSE # EX_TEMPFAIL with a listed cause → Flag check G
echo "real-API harness could not run (exit 75, $CAUSE)" > "$REAL_API_MARK" ;;
*) RESULT=fail ;; # ran and failed, broke the contract, or 75 with no listed cause
esac
RECORD="real-api-run: harness=$HARNESS · date=$(date -u +%Y-%m-%dT%H:%MZ) · result=$RESULT (exit $HARNESS_RC)${REASON:+ · reason=$REASON} · sha=$(git rev-parse HEAD)"
printf '%s (output: %s)\n' "$RECORD" "$HLOG"
if [ "$TIER" != "quick" ]; then
printf '==> $ %s\n(output withheld: %s)\nexit: %d\nduration: %ds\n%s\n\n' \
"$HARNESS" "$HLOG" "$HARNESS_RC" "$(($(date +%s) - start))" "$RECORD" >> "$VERIFY_DIR/evidence.txt"
fi
if [ "$RESULT" = "fail" ]; then GATE_RC=$HARNESS_RC; fi
fi
fiThe real-api-run: line is the evidence format (harness, run date, result, sha, and the cause on a could-not-run); cite it in reality-check.md § Measurements. The harness's output stays in $HLOG and is never quoted into evidence.txt, reality-check.md, Linear or the PR (a real run carries real documents); the record carries only the log's location and, on exit 75, the listed cause. Read it locally, then delete it. The harness receives the changed files as PK_AI_CHANGED and must cover every extraction call it could name there. Its exit code decides the outcome:
| Exit | Meaning | /verify |
0 | ran and passed | result=pass, no flag |
75 (sysexits EX_TEMPFAIL) plus a real-api-unrun: <cause> line, cause one of server-error, rate-limit, auth, credits, network, timeout | could not run: 5xx, rate limit, expired key, exhausted credits, no network, a timeout the harness caught before any verdict | result=unrun, Flag check G |
| any other | ran and failed: schema refusal, parse failure, a 422 | result=fail, red leg, NEEDS WORK, no accept path |
A harness that does not follow this convention is treated as failed on any non-zero exit (fail-closed), and so is exit 75 without a listed cause. Exit 75 is for infrastructure causes only, never to dodge a real failure; before accepting the flag, read $HLOG and confirm the cause. A timeout or OOM kill imposed from outside, a crash or a signal (124, 137, 139), and a missing or unrunnable harness (126, 127) count as failed: none is a verdict the harness vouched for, and a broken harness is fixed in the repo, not waited out. A harness that wants its own timeout counted as could-not-run catches it and exits 75 with timeout. A block killed by the Bash tool leaves its provisional marker behind, so Flag check G reports it and it counts as failed. The command runs under pipefail, so a pipeline cannot mask a failure. The listed cause is copied into the record as reason=; it is the only harness output that reaches committed evidence, and the fixed list is what keeps data out of it.
A failed run gets status NEEDS WORK, as for a gate failure above, except that the hand-off prints the real-api-run: record and the log path instead of the output's last lines. Fix the code, or the harness if the harness is wrong, and re-run. A flaky run is re-run, not accepted. With no harness declared, /verify does not invent one: Flag check G surfaces the gap. Like every leg, the harness re-runs at each new sha (Step 9).
Anchor — a consumer project, 2026-10. Every quote upload failed with a 422 because the extraction schema carried 17 union parameters against Anthropic's limit of 16. Every test mocked the model, so nothing counted unions until the first real-API harness run, after the feature had shipped.
Continue to step 4 only if QA is enabled.
Step 4 — QA review subagent (if QA enabled)
Step 4a — Fetch the spec for AC reference
Read the spec via the project's Linear route — pk issue show "$ISSUE" --fields id,title,description (verb table: sop/Linear_SOP.md § Linear transport; the mcp route's equivalent is listed there). Capture the full description.
Step 4b — Compute the diff
INTEGRATION=$(pk config "Integration branch" "dev")
git diff --stat "origin/$INTEGRATION...HEAD"
git diff "origin/$INTEGRATION...HEAD"Step 4c — Spawn the QA subagent
Use subagent_type: "general-purpose".
The subagent is configured with allowed-tools: Read, Bash, Write (the Write permission is what lets it land the verdict file). Run it on the verification tier per method.config.md § Model Policy (default sonnet, effort high).
Use Task tool with:
description: "QA review of <ISSUE-ID>"- Prompt template:
You are a QA reviewer doing goal-backward verification. Linear issue: <ISSUE-ID> Branch: <CURRENT> Tier: <TIER> Write target: <VERIFY_DIR>/qa-verdict.md Use the Write tool to create this file with the verdict block below. Return only a one-line confirmation: "QA verdict written: <path>". Spec (from Linear): <full description from step 4a> Gate evidence (for anchor cites): <VERIFY_DIR>/evidence.txt Cite gate output as `evidence.txt:L<line>` — point at the `==> $ ...` header row for each command, not the body. Diff (relative to origin/<INTEGRATION>): <output of git diff --stat> <output of git diff> Your job: 1. Read the spec's Acceptance Criteria. 2. Walk each AC. For each, find the diff line(s) that fulfills it. If you cannot find one, the AC is unmet. 3. Look for things the spec REQUIRED but the diff DOESN'T touch (omissions). 4. Look for things the diff DID that the spec didn't authorize (scope creep). 5. Note any obvious bugs you can see (logic errors, missing null checks in financial math, RLS bypasses). Anchor-emit discipline: - Cite source lines as `<file>:<line>` (e.g., `app/lib/budget.ts:88`) - Cite gate output as `evidence.txt:L<line>` (the `==> $` header row) - Vague refs ("somewhere in the auth module") are invalid — re-grep until you have a line. Write to <VERIFY_DIR>/qa-verdict.md exactly this shape: ## QA verdict **Verdict:** Pass | Partial | Fail **Reasoning:** <2–4 sentences> **Per-AC table:** | # | AC summary | Status | Evidence | |---|---|---|---| | 1 | <text> | Met / Unmet / Partial | `<file>:<line>` or "no change found" | **Omissions:** <list of spec requirements not addressed in diff, or "none"> **Scope creep:** <list of diff changes not authorized by spec, or "none"> **Bugs noticed:** <list of obvious bugs with file:line, or "none observed"> Verdict rules: - Pass — every AC has Met evidence; no omissions; no significant scope creep; no bugs noticed - Partial — most ACs Met but specific gaps exist (Partial ACs allowed; minor scope creep allowed if defensible) - Fail — fundamental ACs Unmet, OR scope creep significant enough to block ship, OR bugs noticed
The subagent returns only a one-line confirmation; the verdict block lives in the file. Parent-side parsing is removed — Step 7 reads qa-verdict.md and inlines it into reality-check.md.
After the subagent returns, read $VERIFY_DIR/qa-verdict.md and grep the Verdict: line to set QA_VERDICT (Pass | Partial | Fail) for use in Step 6 flag check B and Step 7 status reasoning.
If the subagent fails to write the file (Write permission denied, disk full), surface the error and treat the QA verdict as Fail — better to block ship than to ship on a silently-missing verdict.
Step 5 — Antagonistic review (mandatory tier:heavy; auto for sensitive-path diffs on tier:standard; opt-in via --review on tier:standard; skipped tier:quick)
Antagonistic review is a separate, adversarial subagent with the same inputs as QA (diff + AC list) but a different lens: find what is wrong, not whether it shipped. The prompt is the verbatim DOUBT prompt from sop/Completion_Claims_SOP.md § 3. DOUBT (pipekit/sop/ in consuming projects) — never paraphrase it, the wording is load-bearing. pipekit-discipline.md § Completion Claims only summarizes the loop.
Run conditions:
RUN_ADVERSARIAL=false
TRIGGER_REASON=""
# tier:heavy — always fires
if [ "$TIER" = "heavy" ]; then
RUN_ADVERSARIAL=true
TRIGGER_REASON="tier:heavy mandatory"
fi
# tier:standard — explicit --review flag fires
if [ "$TIER" = "standard" ] && echo "$@" | grep -q -- '--review'; then
RUN_ADVERSARIAL=true
TRIGGER_REASON="--review flag"
fi
# tier:standard — sensitive-path auto-trigger (mirrors /pr-fix patterns)
if [ "$TIER" = "standard" ] && [ "$RUN_ADVERSARIAL" = "false" ]; then
SENSITIVE_PATHS=$(git diff --name-only "origin/$INTEGRATION...HEAD" 2>/dev/null \
| grep -E '(supabase/migrations/|/auth/|/middleware\.|/rls\.|/policies\.)' || true)
SENSITIVE_CONTENT=""
if git diff "origin/$INTEGRATION...HEAD" 2>/dev/null \
| grep '^+' \
| grep -qE 'SECURITY DEFINER|GRANT EXECUTE|REVOKE EXECUTE|CREATE POLICY|ALTER POLICY'; then
SENSITIVE_CONTENT="yes"
fi
if [ -n "$SENSITIVE_PATHS" ] || [ -n "$SENSITIVE_CONTENT" ]; then
RUN_ADVERSARIAL=true
TRIGGER_REASON="sensitive-path auto-trigger"
echo "⚠ Antagonistic review auto-triggered on tier:standard — diff touches sensitive surface."
[ -n "$SENSITIVE_PATHS" ] && echo " Paths: $(echo "$SENSITIVE_PATHS" | tr '\n' ' ')"
[ -n "$SENSITIVE_CONTENT" ] && echo " Content: RLS/SECURITY DEFINER/GRANT-EXECUTE pattern in diff"
fi
fi
if [ "$RUN_ADVERSARIAL" = "false" ]; then
: # skip to Step 6
else
echo "Antagonistic review: $TRIGGER_REASON"
fiIf RUN_ADVERSARIAL=false, skip to Step 6.
Sensitive-path patterns mirror /pr-fix's plan (which lands Week 4). Defaults:
- Paths:
supabase/migrations/,/auth/,/middleware\.,/rls\.,/policies\. - Content (added lines only —
^+, never context or removed lines):SECURITY DEFINER,GRANT EXECUTE,REVOKE EXECUTE,CREATE POLICY,ALTER POLICY. A file that merely lists one of these terms in an unchanged context line doesn't trigger — only a line the diff actually adds does (a consumer project).
The reasoning: these are the exact surfaces where shipping without adversarial review historically caused incidents — RLS bypasses, missing search_path on SECURITY DEFINER functions, GRANT scope creep. Trivial-but-not-quick standard issues that don't touch these surfaces stay opt-in (no token blowup on tier:standard typo fixes). Project-specific path/content overrides can be added to method.config.md in a future [verify.sensitive_paths] block (deferred to v2.7.1+).
This "sensitive" definition is not, and isn't meant to be, the same one /security-gate's six categories use or the same one a project's own CI enforces. Three separate classifiers can legitimately disagree, because they do different jobs: this one decides whether to spawn an adversarial review subagent; /security-gate's decides which checklist to run against a feature diff; a project's own CI check (e.g. a doc-update requirement keyed on its own path/keyword tiers) enforces something specific to that project's process. Raised as a gap on a consumer project and left out of scope there — it isn't one; each classifier is scoped to what it gates, and a project is free to define a narrower or broader one in its own CI without pipekit needing to match it.
Step 5a — Spawn the adversarial subagent
Use subagent_type: "general-purpose" (same agent as QA — only the prompt differs).
Configure with allowed-tools: Read, Bash, Write. Run it on the plan-review / adversarial tier per method.config.md § Model Policy (default opus, effort xhigh) — adversarial review benefits from deeper reasoning. Set ADVERSARIAL_TIER to the model that role resolves to before spawning, and ADVERSARIAL_RAN_ON to the model that actually produced adversarial.md afterwards (empty if nothing did); Steps 6 and 7 read both.
The tier holds (v4.36.0). If the subagent on that tier dies before writing its file — API rate limit, timeout, killed — retry it on the same tier (twice, a minute apart) before doing anything else. A run on a lower tier is not the adversarial review: if you fall back to one, or exhaust the retries and skip it, that is a flag in its own right regardless of how many findings come back (Flag check E), and the reality-check's Antagonistic section names the tier that actually ran. Anchor: a consumer project, 2026-09 — the opus run was rate-limited, the sonnet fallback returned three findings, and the two that decided the ship (the new cells never ROUND while every cached value is cent-rounded; a multiplier the spec excludes) were both missed. The identical diff reviewed on opus surfaced them in cycle one.
Use Task tool with:
description: "Antagonistic review of <ISSUE-ID>"- Prompt template (the verbatim DOUBT prompt wrapped with Write target + inputs):
Write target: <VERIFY_DIR>/adversarial.md Use the Write tool to create this file with your findings. Return only a one-line confirmation: "Adversarial findings written: <path>". Anchor-emit discipline: - Cite source lines as `<file>:<line>` - Cite gate output as `evidence.txt:L<line>` - No vague refs. Write to <VERIFY_DIR>/adversarial.md exactly this shape: ## Antagonistic review Findings (or "No issues found after thorough examination."): | # | Finding | File:line | Class | |---|---|---|---| | 1 | <text> | `<file>:<line>` | unstated_assumption \| edge_case \| coupling \| contract_violation \| convention_break \| failure_mode | --- Adversarial review. Find what is wrong with this artifact. Assume the author is overconfident. Look for: - Unstated assumptions - Edge cases not handled - Hidden coupling or shared state - Ways the contract could be violated - Existing conventions this might break - Failure modes under unexpected input Do NOT validate. Do NOT summarize. Find issues, or state explicitly that you cannot find any after thorough examination. ARTIFACT: <output of git diff --stat> <output of git diff> CONTRACT: <full Linear issue description from Step 4a — the AC list is the contract>
The adversarial subagent's job is to find issues — it does NOT classify them. Classification happens in the parent's RECONCILE step (per pipekit-discipline.md § Completion Claims): AC misread → AC wrong (amend the AC) → Valid actionable → Valid trade-off → Noise. That step is the user's, not the subagent's.
After the subagent returns, read $VERIFY_DIR/adversarial.md and count findings to set ADVERSARIAL_FINDING_COUNT. Used in Step 7 status reasoning and Step 6 flag check E (added below).
Doubt theater check
Per the discipline rule: "2+ cycles with substantive findings, zero classifications that changed the artifact or the AC = you're validating, not doubting." /verify only runs the loop once by design (subagent finds issues; the user classifies during the ship decision), so this check is not enforced here. It belongs to a future multi-cycle iteration-loop skill. Day-4 deliverable surfaces the findings; user judgment closes the loop.
Scope-drift signals across repeated /verify runs (v4.26.0+)
The check above governs review cycles within one run. The runaway this section covers is the other axis: the same issue re-verified round after round, each round fixing the last round's findings. Nothing counts those rounds — VERIFY_DIR is one directory per issue per day and re-runs overwrite, so ls -d Logs/Verify/*/$ISSUE tells you how many days an issue has been in verification, not how many rounds. Treat 3+ days as its own signal and read these three by hand:
- A fix commit contains a new High. Once is noise. Twice means the unit is too large to review as one — the diff is big enough that fixing one part breaks a part already cleared. This is the specific trigger, not "another round happened."
- Object-count drift. Compare the spec's Migration Plan "schema objects touched" against what the diff actually touches. A spec naming 3 objects against a diff touching 23 has outgrown its contract. Compare mid-flight, not at ship.
- Reductions review clean while additions do not. Once that asymmetry appears, stop patching and start cutting.
When two or more fire, the move is to cut the branch, not to run another round — ship the converged half and move the rest to a follow-up issue filed to sop/Linear_SOP.md § Filing a follow-up issue (Source: <ISSUE-ID>, an Area: label for the follow-up's own work, a Type label, priority from the floor table, state by that priority, relates to the parent — one create call, placement named in the hand-off). /light-spec Phase 3.75 is the spec-time gate that prevents the common case (a schema change bundled with a rewrite of the client that consumes it).
Anchor — a consumer project, 2026-07. Two CRITICALs, a 1,816-line migration across 23 schema objects, and a rewrite of an admin tab inside an 8,000-line page on one branch. Rounds 1–5 each found a High inside the previous round's fix commit; the one clean round (6, zero findings, three reviewers) was the one that reviewed a reduction; round 7's additions produced 22 findings. The branch was cut at round 7 and shipped at round 8. Every signal above was present by round 2 and none was named until round 7.
Step 6 — Human-decision flag enumeration
After the gate, QA, and antagonistic review (if run), enumerate any human-decision flags present. A flag is anything that should pause the auto-ship chain even when the headline verdict is Pass. The principle: the auto-chain is a feature, but it must not bypass a human eye when there is any signal worth a human eye.
Run these checks in order. Surface every match. For TIER != quick, also append each surfaced flag as a FLAG: ... line to $VERIFY_DIR/evidence.txt.
Flag check A — Migration files in diff (auto-reviewed)
MIGRATION_DIR=$(pk config "Migration dir" "")
MIGRATION_FILES=""
if [ -n "$MIGRATION_DIR" ]; then
MIGRATION_FILES=$(git diff --name-only "origin/$INTEGRATION...HEAD" -- "$MIGRATION_DIR" 2>/dev/null)
fiIf MIGRATION_FILES is non-empty, never surface a bare pointer. Migrations are high-stakes and always warrant a human eye regardless of QA verdict — but the human's job is to approve a verdict, not to perform the review. A "trivial column add" can ship a destructive default backfill or a missing search_path on a SECURITY DEFINER function, and handing the user a raw git show asks them to catch exactly the class of issue this skill exists to catch. So when migration files are present, spawn a review subagent and attach its verdict to the flag.
Spawn the migration-review subagent
Fires whenever MIGRATION_FILES is non-empty, on every tier (migrations are high-stakes regardless of tier). Complementary to Step 5's antagonistic review — that pass is a generic "find what's wrong" lens; this one applies the structured migration rubric and returns a Hold/Approve verdict against named rubric IDs.
Backend-pluggable:
- prefer
subagent_type: "pr-review-toolkit:code-reviewer"(the same specialist/pr-security-reviewuses) - fall back to
general-purposeif the toolkit isn't installed (warn, mirroring the QA fallback in the Failure model)
Configure allowed-tools: Read, Bash, Write, on the plan-review / adversarial tier per method.config.md § Model Policy (default opus, effort xhigh — migrations are high-stakes on every tier). Use the Task tool with:
description: "Migration review of <ISSUE-ID>"- Prompt:
Write target: <VERIFY_DIR>/migration-review.md (if $VERIFY_DIR is unset — tier:quick — skip the file and return the verdict block inline). Return only a one-line confirmation: "Migration review written: <verdict>". Apply the migration rubric from the /pr-security-review skill. Read it first — it lives at `.claude/skills/pr-security-review/SKILL.md` (consuming projects) or `skills/pr-security-review/SKILL.md` (Pipekit itself) — read § "Migration rubric (M1–M8)" and apply every item. ALSO apply, only when the diff body contains the triggering pattern: - RLS rubric (R1–R6) — diff contains CREATE POLICY / ALTER POLICY / ENABLE ROW LEVEL SECURITY - SECURITY DEFINER rubric (S1–S8) — diff contains SECURITY DEFINER - GRANT/REVOKE rubric (G1–G3) — diff contains GRANT / REVOKE If you cannot read the skill file, fall back to checking these categories directly: idempotency; destructive DDL without a data-migration plan; NOT NULL columns without default/backfill; FK ON DELETE behavior; RLS enablement + at least one policy; search_path on SECURITY DEFINER functions; type regeneration present in the diff; timestamp ordering of the migration filename. Anchor-emit discipline: cite every finding as `<file>:<line>`. No vague refs. Write exactly this shape: ## Migration review — <ISSUE> **Verdict:** Hold | Approve with notes | Approve **Files:** <migration files reviewed> **Findings:** N Critical · N High · N Medium · N Low · N Nit | # | Sev | Rubric | File:line | Finding | Remediation | |---|---|---|---|---|---| | 1 | High | M3 | `<file>:<line>` | <issue> | <action> | (or, if clean: "No issues found after thorough examination — Verdict: Approve.") Verdict rule: any Critical or High finding → Hold. Medium/Low/Nit only → Approve with notes. No findings → Approve. ARTIFACT: <output of: git diff "origin/$INTEGRATION...HEAD" -- "$MIGRATION_DIR"> CONTRACT: <full Linear issue description — the AC list>
After the subagent returns, read <VERIFY_DIR>/migration-review.md (or the inline block on tier:quick), grep the Verdict: line into MIGRATION_VERDICT, then surface the flag carrying the verdict — never a bare pointer:
echo "FLAG: migration review — ${MIGRATION_VERDICT} — see migration-review.md ($(echo "$MIGRATION_FILES" | tr '\n' ' '))"
[ "$TIER" != "quick" ] && printf 'FLAG: migration review: %s — files: %s\n' "$MIGRATION_VERDICT" "$(echo "$MIGRATION_FILES" | tr '\n' ' ')" >> "$VERIFY_DIR/evidence.txt"The verdict — Hold included — is surfaced for the user to RECONCILE; it does not auto-downgrade the /verify status, consistent with how antagonistic findings and every other flag behave (Step 8). The migration flag still pauses auto-ship (Step 9). The only thing that changed: the user now approves Hold: M3 missing backfill on line 14 or Approve — no findings, instead of a git show they were never positioned to act on.
Flag check B — QA Pass with non-empty sub-sections
When QA ran and returned Pass, also parse the verdict block for non-"none" content in:
- Omissions line: anything other than
none(case-insensitive) - Scope creep line: anything other than
none - Bugs noticed line: anything other than
none observed/none
A Pass with non-empty sub-sections means the subagent classified the work as shippable in aggregate but still flagged items worth surfacing. Treat each non-empty sub-section as one flag.
Flag check C — --qa was forced by user
If the user invoked /verify --qa (regardless of Require QA review config), they signaled they want eyes on it. The Pass verdict from QA is necessary but not sufficient — auto-ship would short-circuit the intent of the explicit flag.
Surface as: FLAG: --qa forced — user explicitly requested QA, do not auto-ship without confirm
Flag check D — /work cross-skill marker
/work's Step 6.5 writes .pk-work/<ISSUE-ID>.flags (one line per flag) when self-reference grep surfaces matches, behavioral self-check finds gaps, or a documented Risk-fallback was invoked during execution. Read it here.
MARKER=".pk-work/${ISSUE}.flags"
if [ -f "$MARKER" ]; then
echo "FLAGS from /work (Step 6.5):"
sed 's/^/ FLAG: /' "$MARKER"
if [ "$TIER" != "quick" ]; then
sed 's/^/FLAG: \/work marker — /' "$MARKER" >> "$VERIFY_DIR/evidence.txt"
fi
fiThe marker is .pk-work/ per repo convention; gitignored.
Flag check E — Antagonistic review surfaced findings
If Step 5 ran and ADVERSARIAL_FINDING_COUNT > 0, surface one flag per finding (or one aggregate flag, your choice — the count matters more than the granularity):
if [ -n "$ADVERSARIAL_FINDING_COUNT" ] && [ "$ADVERSARIAL_FINDING_COUNT" -gt 0 ]; then
echo "FLAG: antagonistic review surfaced $ADVERSARIAL_FINDING_COUNT finding(s) — see adversarial.md"
[ "$TIER" != "quick" ] && printf 'FLAG: antagonistic review: %d finding(s)\n' "$ADVERSARIAL_FINDING_COUNT" >> "$VERIFY_DIR/evidence.txt"
fiA run below the configured tier — or none at all after the retries in Step 5a — is a flag on its own, even with zero findings (v4.36.0):
if [ "$RUN_ADVERSARIAL" = "true" ] && [ "${ADVERSARIAL_RAN_ON:-}" != "$ADVERSARIAL_TIER" ]; then
echo "FLAG: antagonistic review ran on ${ADVERSARIAL_RAN_ON:-nothing}, below the configured tier ($ADVERSARIAL_TIER) — not a substitute; re-run /verify --review when the tier is available"
[ "$TIER" != "quick" ] && printf 'FLAG: antagonistic review below tier: ran on %s, configured %s\n' "${ADVERSARIAL_RAN_ON:-none}" "$ADVERSARIAL_TIER" >> "$VERIFY_DIR/evidence.txt"
fiAdversarial findings are advisory — they do not auto-downgrade the QA verdict — but they always warrant a human eye before ship. The RECONCILE step (per pipekit-discipline.md § Completion Claims) is the user's: AC misread → AC wrong (amend the AC) → Valid actionable → Valid trade-off → Noise.
Flag check F — Security-sensitive change (auto-gated; v4.4.0)
This is where the feature-scoped security gate (gap #3) actually fires. /verify is the only point in the /work → /verify → pk ship auto-rollover with a human-decision seam, so the gate runs here as a flag rather than as a standalone step the auto-chain would skip.
SECCATS=$(pk config "Security categories" "")
[ -n "$SECCATS" ] && [ ! -f "$SECCATS" ] && SECCATS="" # configured but absent → treat as not-configuredIf SECCATS is empty (no categories file), skip this check silently — the gate is opt-in per project, exactly like /prod-ready and /financial-review. If it points at a real file, run the gate:
- Determine the changed surface (committed work on this branch, never the working tree):
Ifgit fetch -q origin "$INTEGRATION" 2>/dev/null || true # best-effort; worktrees share the object store CHANGED=$(git diff --name-only "origin/$INTEGRATION...HEAD" 2>/dev/null) [ -z "$CHANGED" ] && CHANGED=$(git diff --name-only "$INTEGRATION...HEAD" 2>/dev/null) CHANGED=$(printf '%s\n' "$CHANGED" | grep -v '^Reports/' ) # don't classify the gate's own past reportsCHANGEDis still empty after both committed-diff attempts, do not emit a clean pass — the surface is indeterminate (stale/missingorigin/$INTEGRATION, detached HEAD). SurfaceFLAG: security gate — could not determine changed surface; classify manuallyand treat it as a flag (fail-safe, mirroring/prod-ready's blank-build handling — a miss here would ship an unreviewed sensitive change). - Classify + review by following the
/security-gateskill (skills/security-gate/SKILL.md, or.claude/skills/in consuming projects) againstCHANGED, using the project's$SECCATSdefinitions and thesop/Security_Gate_SOP.mdper-category checklists. Spawn it as the gate's own read-only sub-agents (general-purpose, execution tier permethod.config.md § Model Policy— defaultsonnet, effortmedium—allowed-tools: Read, Bash, Grep, Glob). The verdict isPASS(no category matched, or all matched checklists clean) /FAIL(any confirmed Critical or High) /WARNINGS(Medium/Low only).
Surface the flag carrying the verdict — never a bare pointer (same discipline as the migration flag):
echo "FLAG: security gate — ${SECGATE_VERDICT} — categories: ${SECGATE_MATCHED:-none} — see Security_Gate_*.md"
[ "$TIER" != "quick" ] && printf 'FLAG: security gate: %s — categories: %s\n' "$SECGATE_VERDICT" "${SECGATE_MATCHED:-none}" >> "$VERIFY_DIR/evidence.txt"- On PASS, write the secgate sentinel (v4.17.0 —
pk shiphard-requires it on any categories-armed project, so the embedded gate run must leave the same artifact a standalone/security-gaterun would; without this, the auto-ship rollover would block right after its own gate passed):
Never on FAIL / WARNINGS-to-block / indeterminate — the missing sentinel is what makesif [ "$SECGATE_VERDICT" = "PASS" ]; then _sg_dir="Logs/SecurityGate/$(date +%Y%m%d)/${ISSUE}" mkdir -p "$_sg_dir" printf '# secgate-complete\n\nissue: %s\nstatus: PASS\nsha: %s\ncategories: %s\n' \ "$ISSUE" "$(git rev-parse HEAD)" "${SECGATE_MATCHED:-none}" > "$_sg_dir/secgate-complete.md" fipk shiprefuse.
A PASS with no category matched is the common, cheap case — emit no flag (the gate ran and found nothing sensitive), but still write the sentinel. Only a category match (whether the verdict is FAIL, WARNINGS, or even a clean PASS the user should see) surfaces a flag. Like every other flag, a security-gate FAIL pauses auto-ship (Step 9) for the user to RECONCILE; it does not auto-downgrade the /verify status. As of v4.17.0 the gate is hard at the pk ship seam: no HEAD-matching PASS sentinel on an armed project → ship refuses (--force / PK_SECGATE_BYPASS=1 escape).
Flag check G — AI extraction call with no passing real-API run
Step 3.6 fired and either no harness is declared, or the harness could not run (exit 75 with a listed cause), or the block was killed before it recorded an exit code (which counts as failed: NEEDS WORK). Step 3.6 keeps at most one marker, so the flag is raised once:
REAL_API_MARK=".pk-work/${ISSUE}.real-api-flag"
if [ -s "$REAL_API_MARK" ]; then
echo "FLAG: no passing real-API run — $(cat "$REAL_API_MARK")"
[ "$TIER" != "quick" ] && printf 'FLAG: no passing real-API run: %s\n' "$(cat "$REAL_API_MARK")" >> "$VERIFY_DIR/evidence.txt"
fiOnly a recorded result=pass clears it: declare the harness (Real-API harness), or wait out the could-not-run cause, and re-run /verify. Accepting it instead means shipping a call that has not met the real API at this sha. The accept note must name the reason (no harness declared, or the could-not-run cause), plus the harness, date and result of any hand run. The note does not clear the flag: every later /verify without a passing run raises it again. A marker match that is not an extraction call (an unrelated response_format) is accepted in one line.
Tally
After all seven checks, count flags:
FLAG_COUNT=<sum of flags surfaced above>If FLAG_COUNT > 0, the verdict is treated as Pass-with-flags in Step 9's auto-ship decision, even if the QA verdict block says Pass. This is the load-bearing rule for F6.
Print one summary line:
Flags: <count> · Auto-ship: <will-fire | paused>Step 7 — Write reality-check.md (skip for tier:quick)
For TIER != quick, write $VERIFY_DIR/reality-check.md.
Write the header block with the heredoc below, not by rendering it as markdown. The sha: line is load-bearing beyond this file: when verify-complete.md is absent — it is written only on PASS, and is sometimes never committed — reality-check.md is what pk ready / pk done fall back to when deciding whether the recorded verification still covers HEAD (pk_verify_sentinel_latest, v4.27.1+). A missing sha: line silently downgrades that gate to "no evidence found".
Model-rendered markdown drifts; a heredoc does not. Measured on a consumer project, 2026-08: verify-complete.md, written by the Step 8 heredoc, carried a sha: line in 191 of 192 committed files, while reality-check.md, previously rendered as prose, carried one in only 167 of 195 — and the miss rate was rising (8% in June, 24% in July, 33% in August), because nothing in a prose template fails when a line goes missing.
# STATUS is the verdict from the status-logic table in Step 8 (gate red → NEEDS
# WORK; QA ran and Verdict == Fail → NEEDS WORK; otherwise PASS). Decide it now
# and assign it as a shell variable — Step 8 consumes this same `$STATUS` to
# decide whether to write the sentinel, and nothing in this skill assigns it for
# you. Flags do NOT change it; they pause auto-ship in Step 9.
STATUS="PASS" # or: STATUS="NEEDS WORK"
SHA=$(git rev-parse HEAD)
cat > "$VERIFY_DIR/reality-check.md" <<EOF
# reality-check — $ISSUE
- stamp: $STAMP
- tier: $TIER
- integration: $INTEGRATION
- commits-ahead: $COMMITS_AHEAD
- sha: $SHA
- status: $STATUS
EOFThen append the sections below to that file (these are yours to render — they are prose, and no gate parses them):
## Gate results
| Command | Exit | Duration | Anchor |
|---|---|---|---|
| <cmd 1> | <rc> | <s> | evidence.txt:L<line> |
| <cmd 2> | <rc> | <s> | evidence.txt:L<line> |
(Reconstruct rows from `==> $ ...` / `exit:` / `duration:` triplets in evidence.txt. Line anchors point to the `==>` row of each command.)
## Measurements
<One record per AC verdict that rests on a NUMBER — a count, a timing, a query
result, a diff size — or "none". A number quoted without its query is a claim;
with it, the claim is re-runnable by anyone, later, verbatim.>
- claim: <plain English, one sentence — "the seeded fixture has 5 out-of-scope projects and the RPC returns 0 of them">
command: `<the exact command or query that produced it>`
result: <the number(s), verbatim>
caveats: <what would make this number lie — fixture-dependent, wall-clock, env-specific — or "none">
anchor: evidence.txt:L<line>
## QA verdict
<inline contents of `$VERIFY_DIR/qa-verdict.md`, or "Not run.">
## Antagonistic review
Tier: <ADVERSARIAL_TIER / effort> — ran on: <ADVERSARIAL_RAN_ON, or "not run">
<inline contents of `$VERIFY_DIR/adversarial.md`, or "Not run (tier:<TIER>, no --review).">
## Migration review
<inline contents of `$VERIFY_DIR/migration-review.md`, or "No migration files in diff.">
## Flags surfaced
<bulleted list of all flags from Step 6, or "none">
## Status reasoning
<2–4 sentences. Examples:
- "Gate green, QA Pass, 0 flags → PASS."
- "Gate green, QA Pass, 2 flags (migration files; --qa forced) → PASS (auto-ship paused for human decision)."
- "Gate green, antagonistic review 3 findings → PASS (auto-ship paused; user reconciles findings before ship)."
- "Gate red on `pnpm turbo run lint` (exit 1) → NEEDS WORK.">
## Next actions
<hint copy from Step 9's verdict table, scoped to this run's verdict + flag count>Use the Write tool with file_path = "$VERIFY_DIR/reality-check.md" to land the file. Re-runs overwrite. The QA and adversarial sections inline the fragment files written by their respective subagents — assembly happens here, not at subagent boundaries (parent-side parsing is for inclusion, not for verdict computation).
Step 8 — Write verify-complete.md on PASS (every tier, quick included)
On PASS, write $VERIFY_DIR/verify-complete.md — for every tier. pk ship's gate accepts a sentinel under any date dir whose sha: matches the commit being shipped (git rev-parse HEAD), so the sha line is load-bearing: write the full 40-char HEAD sha, exactly sha: <40-hex> on its own line.
if [ "$STATUS" = "PASS" ]; then
mkdir -p "$VERIFY_DIR" # tier:quick skipped dir creation in Step 2; create it now
SHA=$(git rev-parse HEAD)
if [ "$TIER" = "quick" ]; then
cat > "$VERIFY_DIR/verify-complete.md" <<EOF
# verify-complete
issue: $ISSUE
stamp: $STAMP
tier: quick
status: PASS
mode: virtual
sha: $SHA
EOF
else
cat > "$VERIFY_DIR/verify-complete.md" <<EOF
# verify-complete
issue: $ISSUE
stamp: $STAMP
tier: $TIER
status: PASS
evidence: evidence.txt
reality-check: reality-check.md
sha: $SHA
EOF
fi
fiFor tier:quick this minimal sentinel is the only file written — evidence.txt and reality-check.md stay virtual (stdout). It exists solely so pk ship's gate can confirm "verify passed at this exact commit" without re-deriving tier or guessing the date. (Before v4 the gate special-cased tier:quick as warn-but-proceed and re-derived tier from Linear at ship time; a Linear flake or a midnight UTC rollover then caused false aborts. The sentinel-per-tier + sha match removes both.)
Status logic:
- Gate red, including a Step 3.6 real-API run that ran and failed (any exit but 0, or 75 with a listed cause) or was killed before recording one → NEEDS WORK (no
verify-complete.md) - QA ran and Verdict == Fail → NEEDS WORK (no
verify-complete.md) - Otherwise (gate green AND no QA-Fail) → PASS (write
verify-complete.md)
Antagonistic findings do not downgrade the status — they pause auto-ship in Step 9 via Flag check E, but the file still gets written. The reasoning: adversarial review surfaces issues for the user to RECONCILE; classification is the user's, not the subagent's (the class list is canonical in pipekit-discipline.md § Completion Claims — do not re-enumerate it here). Pre-classifying as Fail would short-circuit the discipline.
Flags do not downgrade the status — they pause auto-ship in Step 9 but the file still gets written. pk ship consults verify-complete.md (Day 3); the Step 9 flag-pause is the additional human-gate layer.
If the status is NEEDS WORK and a previous PASS run left a stale verify-complete.md on disk, delete it explicitly so the next pk ship cannot trust a stale sentinel:
[ -f "$VERIFY_DIR/verify-complete.md" ] && [ "$STATUS" != "PASS" ] && rm "$VERIFY_DIR/verify-complete.md"Step 9 — Hand off (with auto-ship for the /work rollover path)
Based on verdict + flag tally, print the next-action:
| Verdict | Flags | Next |
| Pass (gate only) | 0 | pk ship (auto-shipped if invoked with --auto-ship; otherwise hint only) |
| Pass (gate + QA) | 0 | pk ship (auto-shipped if invoked with --auto-ship; otherwise hint only) |
| Pass | ≥1 | Pause. List flags. Auto-ship is forbidden. User runs pk ship manually after reviewing — or /work --resume to address a flag, or amends the commit to document the accept decision. |
| Partial | any | Read the per-AC table. Decide: amend with /work (if gap is real), or ship anyway with git commit --amend documenting the gap. Then pk ship. |
| Fail (gate) | any | Fix the failing command, then re-run /verify (idempotent). |
| Fail (QA) | any | Stop. Either: expand /work <ID> to address Unmet ACs; OR pk delegate <ID> "the spec needs <X>" to refine spec; OR override consciously with documented decision. |
For TIER != quick, also print the artifact paths:
Evidence: <VERIFY_DIR>/evidence.txt
Reality check: <VERIFY_DIR>/reality-check.md
Verify complete: <VERIFY_DIR>/verify-complete.md (present only on PASS)Every cited check is in this run's evidence.txt (v4.36.0)
The hand-off, the reality-check, and any Linear or PR text written from them may cite only checks that appear as a ==> $ block in this run's evidence.txt — the configured gate rows and any extra leg you ran (an artifact build, an E2E suite, a checker on fresh exports). evidence.txt is overwritten per run (Step 2), so a check that was not re-run at this sha is absent by construction. A result from an earlier run, from before a session resume, or from a scratchpad that no longer exists is not evidence at this sha: re-run it, or say it was not run. Anchor: a consumer project, 2026-09 — a session resumed after a machine pause closed with "fresh artifacts pass the checker, 1,940 checks", a claim from the pre-pause /work step; the artifacts had been wiped with the scratchpad, and the run's evidence.txt had no such leg.
Auto-ship rollover (Pass + zero flags only, only when invoked with --auto-ship)
Auto-ship fires when all three conditions hold:
- The verdict is Pass (gate green; QA Pass if QA ran).
- The flag tally from Step 6 is zero (no migration files, no QA Pass-with-non-empty sub-sections, no
--qaforce, no/workadvisory marker, no antagonistic findings, no matched security category, no AI extraction change without a passing real-API run). - This skill was invoked with the
--auto-shipargument (passed via the Skill tool'sargsparameter by/work's Step 7 rollover).
Behavior:
- Print:
✓ /verify Pass + 0 flags — auto-running pk ship - Run
pk shipvia bash. Use no flags —pk shipreadsIntegration branchfrommethod.config.mdto pick the destination, which givesdevfor multi-env projects andmainfor single-env projects (correct in both cases). v2.6.0+: opens the PR as Draft; outside reviewers (Semgrep + claude-review pertemplates/ci/) do not fire yet. - If
pk shipsucceeds: print its output (PR URL + Linear transition) and exit. The hand-off should remind the user that outside reviewers will fire when they runpk ready <ID>— that's the merge-moment gesture, not part of the auto-flow. - If
pk shipfails (push rejected, gh CLI error, branch protection): surface the error verbatim and STOP. Do NOT auto-retry — push failures usually mean branch protection, lockfile drift, or remote conflicts that need human eyes.
--review (antagonistic review) stays opt-in; the user runs pk ship --review separately if they want it. The Draft default is also opt-out-able via pk ship --ready for one-shot tiny issues where iteration won't happen.
Pass-with-flags pause (the F6 gate — load-bearing)
When the verdict is Pass but FLAG_COUNT > 0, do not auto-ship under any circumstance, even with --auto-ship. Print:
✓ Pass with <N> flag(s) — auto-ship paused for human decision.
Flags surfaced:
<flag 1>
<flag 2>
...
To proceed:
• Address the flag(s), then re-run /verify.
• OR accept the flag(s): document the accept decision in the commit
(git commit --amend --no-edit -m '<existing>\n\nAccepted flags: <reason>')
and run `pk ship` manually.
• OR /work --resume <ISSUE-ID> if execution gaps remain.Then stop. The chain does not advance to pk ship without explicit human action. This rule is non-negotiable — it is the entire reason Step 6 exists. Skipping it on agent judgment reintroduces the F6 failure mode the canary 2026-05-14 surfaced.
Standalone /verify (no --auto-ship)
If --auto-ship is not in this skill's args (standalone /verify invocation), do NOT auto-ship regardless of verdict or flag count. Print the hint and let the user pace. The flag list still gets surfaced — it just informs the user's manual pk ship decision instead of gating it.
Why an arg, not an env var: env vars set in one Bash tool call don't propagate to subsequent Bash calls — each invocation is a fresh subshell. The only reliable cross-skill signal is the Skill tool's args parameter.
Partial / Fail with --auto-ship: still no auto-ship. Auto-ship is gated on Pass + zero flags, not on the arg alone. The arg only authorizes the clean Pass branch to ship; flags or failures always pause for human attention.
Failure model
| Failure | Behavior |
| No commits ahead of integration | Refuse with clear message. |
| Pre-Deploy Gate not configured | Print: "Configure § Pre-Deploy Gate in method.config.md, then re-run." |
| Gate command fails | Stop. Print failing command + last 30 lines. Write reality-check.md with status NEEDS WORK. Don't run QA. Don't write verify-complete.md. |
| QA subagent type unavailable | Warn and skip QA rather than blocking the gate. |
| Spec has no AC | QA subagent should report "AC missing" as Fail; user gets clear next-action. |
$VERIFY_DIR unwritable (perms, disk full) | Print error, fall through to stdout-only mode for this run (tier downgraded to virtual). Day 3 gate will block ship in this case since verify-complete.md cannot be written. |
No tier: label, or Linear unreachable | Step 0 defaults to tier:standard and prints a visible warning that it's guessing — evidence layer still written. |
| Session resumed mid-run (context compacted, scratchpad gone) | Re-run from Step 2. Nothing from before the resume is evidence at this sha — see Step 9. |
| Adversarial subagent dies (rate limit, timeout) | Retry on the same tier twice; a lower-tier or skipped run is flagged as such (Step 5a, Flag check E). |
AI extraction call changed, no Real-API harness declared | Flag check G, never an invented command. |
| Harness exits 75 with a listed cause (could not run) | result=unrun in the record, never a pass; Flag check G. |
| Harness exits anything else non-zero (124/126/127/137/139 included), 75 with no listed cause, or is killed by the Bash tool | Red leg, NEEDS WORK, no accept path (Step 3.6). |
When NOT to use
- Design / code-quality review of a PR — that's
/pr-fix(interactive triage) orpk ship --review(antagonistic)./verifychecks spec adherence, not what the spec forgot to ask. - Security audit —
/pr-security-review(PR-scoped) or/repo-security-review(repo-wide)./verify's Step 6 security gate is a classifier pass, not a review. - Interactive UAT —
/verifyproves the code passes its gates; it is not a substitute for the human exercising the feature (RUNBOOK step [5e], the non-skippable Stage 3 gate). - Production-readiness — operational preconditions (monitoring, backups, flags) are
/prod-ready, run once per feature at the production boundary, not per task.
Common Rationalizations
| You're about to say… | The rebuttal |
| "It's a small change — skip the gate" | The gate is the gate. Simple-feeling changes are where silent regressions live (pipekit-discipline.md Red Flags, row 1 — it's row 1 because this excuse keeps recurring). |
| "The artifacts passed before the pause / the checker was green earlier" | Not at this sha, and not in this run's evidence.txt. Re-run the leg or report it as not run (Step 9). |
| "I already ran the tests earlier this session" | The evidence layer exists because "Done" kept overstating reality — issues marked shipped that git never built (v2.7.0-rc5 log-mining, both consumers). Exit codes in evidence.txt at this SHA, or it didn't happen. |
| "CI will catch it on the PR anyway" | CI is the backstop, not the gate. A red PR wastes the reviewer pass and blocks the merge lane — /verify is cheaper than a bounced PR. |
| "The preview/dashboard looks right" | A vendor-UI affirmative state is a claim, not evidence of effect (Red Flags). Closing on a green checkbox you never exercised is the false-ship pattern. |
"The gate fails on something unrelated — --no-verify past it" | Never route around a failing gate; fix the cause, or if the gate itself has drifted from CI, that's its own bug — raise it (pipekit-tooling.md § Pre-Deploy Gate). |
What this skill does NOT do
- No session-log writes —
/pk-exitowns the session log. - No Linear status updates —
pk shipandpk donepost the necessary ones. - No PR creation —
pk shipis separate. - No re-running of failed tests — fix and re-invoke (idempotent).
- No
Logs/Verify/pruning — artifacts are intentionally committed as the per-PR audit trail (v2.7.0-rc1 decision). Consuming projects own retention via periodic cleanup commits (e.g., quarterlygit rm -r Logs/Verify/<old-date>/). Do NOT gitignore — the verify-complete.md sentinel travels with the merge as evidence that the pre-deploy gate passed at that SHA. - No source modifications. Subagents have
Read, Bash, Write—Writeis scoped to$VERIFY_DIR. They do not edit project source files.
Comparison with v2.6
| Concern | v2.6 (/verify) | v2.7 (/verify) | ||
| Tier awareness | Implicit | Explicit `tier:quick | standard | heavyviapk_linear_tier` |
| Evidence artifact | None | Logs/Verify/<date>/<id>/evidence.txt with per-command exit + duration | ||
| Reality check | Printed to stdout | Written to reality-check.md for standard/heavy | ||
| Verify-complete sentinel | None | verify-complete.md on PASS (read by pk ship Day 3) | ||
| Pass-with-flags pause | Yes (Step 3.5) | Yes (Step 6) — flag check E added for antagonistic findings | ||
| Auto-ship rollover | Yes (Step 4) | Yes (Step 9) — unchanged behavior | ||
| Antagonistic review | Not present | Mandatory tier:heavy; auto on sensitive-path diffs for tier:standard; opt-in --review for tier:standard otherwise | ||
| QA verdict capture | Parent-side parse | Subagent writes $VERIFY_DIR/qa-verdict.md; parent inlines into reality-check.md |
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

