execute — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited execute (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
Execute a complete unit of work: plan it, build it, verify the actual outcomes, commit it.
This is a primary pipeline skill used after /prd-to-issues has produced a concrete slice, or when the user already has a clearly scoped implementation task.
Use /execute when the work is ready to build, verify, and commit.
Use HITL /execute when the slice still needs active user judgment, supervision, or acceptance decisions during implementation. Use AFK /execute only when the next slice is already durable in GitHub, unblocked, and legible from its issue, boundary map, and any linked research artifact (archive file or spike issue) or docs/solutions/ context.
Legible to a reader holding none of it — which is what an AFK iteration is. /prd-to-issues §4's context completeness check is the gate behind that condition: it requires every AFK slice to carry a ### Context block, or explicit empty declarations, before the issue is filed. A slice marked AFK with no such block was never checked against this condition, only assumed to meet it. Treat that as a reason to run the slice HITL, or to send it back through /prd-to-issues, rather than as a formality to wave through.
See Step 0: Prerequisites below for the mandatory Ralph auto-detection and TDD marker gates.
Do not use it to replace /shape, /research, or /write-a-prd when the problem or shape is still unresolved. Do not use it as a substitute for /pre-merge once implementation is complete and ready for review.
Branch isolation gate. Before any implementation work, ensure you are working on a clean branch created for this specific task — not a leftover feature branch from previous work.
Isolation already provided — stand down (check this first, before the numbered rules). Some environments hand the session a dedicated, isolated worktree+branch before /execute runs — Conductor workspaces, GitHub Codespaces, devcontainers, and similar hosts each provision "one workspace = one branch, auto-forked from the base," with setup scripts already run. When that is the case the pipeline does not own provisioning: creating a worktree here would nest one inside the host's, and the host's branch (named for the workspace, not the task) is not "stale." Read the environment's signal and cede the worktree to whoever already owns it (Norman — read the world's signifier instead of carrying an in-head assumption that the pipeline always provisions; Meadows — one actor per stock, so the pipeline defers rather than adding a competing manager).
Resolve the provisioning mode from .claude/settings.json worktree.provisioning — "host" | "pipeline" | "auto", default "auto" when the key is absent (mirroring the existing research.storage precedent):
host_owned=yes. This is the cheapest, primary discriminator: the pipeline's only detection mechanism is Bash, and these vars are visible in the agent's shell. Presence alone is not enough: CONDUCTOR_WORKSPACE_PATH is inherited by any shell started under Conductor and can name a workspace of a different repository, so it counts only when it contains the current toplevel. (Do not detect via a .conductor directory in the cwd — Conductor keeps it under $CONDUCTOR_ROOT_PATH, not the workspace.)git rev-parse --show-toplevel differs from the first path in git worktree list --porcelain.# host-signal: does a host env var describe THIS tree, not just this shell?
host_owned=no
top=$(cd "$(git rev-parse --show-toplevel)" && pwd -P)
if [ -n "${CONDUCTOR_WORKSPACE_PATH:-}" ]; then
ws=$(cd "$CONDUCTOR_WORKSPACE_PATH" 2>/dev/null && pwd -P) || ws=
if [ -n "$ws" ]; then
case "$top/" in "$ws"/*) host_owned=yes ;; esac
fi
fi
if [ -n "${CODESPACES:-}" ] || [ -n "${REMOTE_CONTAINERS:-}" ]; then host_owned=yes; fi
echo "host_owned=$host_owned"CODESPACES and REMOTE_CONTAINERS carry no path to compare and are read for presence; they are container-scoped, so a shell that sees them is inside the container whose repos they describe. The same block appears in /closeout, /lfg, and /init-pipeline, byte-identical, pinned by scripts/test-host-signal-containment.sh in the pack's own repo.
When standing down: skip worktree creation and `EnterWorktree`, and work in place on the current branch. The numbered rules below are already satisfied — in particular rule 3 does not apply (a host-provisioned branch is neither base nor task-named, but it is not stale; do not nest a worktree and do not stop). The host has already seeded git-ignored config and dependencies, so most of the "Worktree setup checklist" is informational only — spot-check .env.local/deps if a command fails, but do not re-provision. Its git-hooks item is the exception and still applies. Hosts provision tracked files plus dependencies; git hooks live in .git/hooks, which is per-worktree and untracked, so a host-provisioned workspace characteristically has none. Check that item even when standing down. Continue to the issue-shape gate.
This stand-down is deliberately asymmetric with /closeout's teardown check. Inflow only needs to answer "am I already isolated?" — generic detection (toplevel ≠ primary) and the env-var hint each settle that. The outflow question — "who owns teardown?" — is stricter and cannot rely on the generic heuristic alone, because a pipeline-made worktree also satisfies toplevel ≠ primary; /closeout keys off the explicit setting or host env var only.
git branch --show-currentmain, prod, master), create a new feature branch for this task.Consumes from #N declaration, you are intentionally about to fork from it for a stacked-PR slice — proceed. Exception (host-provisioned): if the stand-down check above fired, this branch is the host's isolated workspace branch — it is not stale; work in place.To create an isolated branch, use one of these approaches (in order of preference):
wt is available): wt switch --create <branch-name> — creates a new worktree + branch from the appropriate base and switches to it, giving full filesystem isolation. Use the /worktrunk skill for guidance.git checkout <base> && git checkout -b <branch-name> — creates a new branch from the appropriate base in the current working directory.The appropriate base is the repo's own base branch by default — whatever the repo declares (git symbolic-ref refs/remotes/origin/HEAD --short | sed 's@^origin/@@'). Do not assume main. For a slice with an unmerged Consumes from #N dependency that produces symbols this slice imports, branch from that sibling slice's branch instead so the stacked PR can target the sibling's PR (Hammant Trunk-Based Development Ch. 13: multiple PRs per story; the sibling's PR must still merge to the repo's base branch within 2 days).
Derive the branch name from the task: e.g., issue-5-landing-page, landing-page, or the issue slug. Do not reuse branch names from previous work.
Enter the worktree as a session (when you created a worktree). Creating the worktree is not the same as the session running inside it — wt switch --create resets the shell cwd back to the project root after each command, so without a further step the session stays anchored at the original checkout and the reported cwd lies about where work is happening. After creating the worktree, enter it with the harness EnterWorktree tool so the session's working directory genuinely is the worktree:
EnterWorktree { path: <absolute-worktree-path> }. The path must already appear in git worktree list for this repo (it will, because you just created it). This persistently switches the session into the worktree.wt switch --create / git worktree add runs the worktrunk pre-start hooks that seed .env.local and dependencies. EnterWorktree only switches the cwd; it does not run those hooks. Create first (to seed env/deps), then enter.cd prefix to forget — eliminating the slip class where one un-prefixed command writes to the wrong tree.git checkout -b, no worktree) creates the branch in the current checkout — there is no worktree to enter, so skip EnterWorktree and work in place.AFK / headless fallback. If EnterWorktree is unavailable (headless runs, AFK Ralph, cron), fall back to cwd-prefix discipline: the shell cwd resets to the project root after every Bash command, so prefix every Bash call in this session with cd <absolute-worktree-path> &&. Use this only when the harness tool is genuinely unavailable — it is the old workaround, retained for environments without the native mechanism.
This worktree's teardown is owned by /closeout at the pipeline tail — after the PR merges, /closeout re-anchors the shell to the base checkout, removes the worktree, and prunes the merged branch. /execute is the inflow side of the worktree lifecycle; /closeout is the outflow. Step 6 cleanup below removes only the .tdd-* markers — it deliberately does not remove the worktree.
After creating the worktree, set it up. A new worktree inherits tracked files but not git-ignored ones (.env.local, per-worktree deps, build caches). Two paths:
Preferred — configure once via worktrunk hooks (.config/wt.toml in the project):
[pre-start]
copy = "wt step copy-ignored"
install = "pnpm install"pre-start hooks are blocking — the worktree is not reported ready until they finish. Use pre-start (not post-start) for both, because post-start runs in the background and subsequent commands that need .env.local or node_modules will race the hook. See /worktrunk for the full recipe. One-time per project.
Fallback — manual setup for plain `git worktree add` (no worktrunk):
cp <source-repo>/.env.local <worktree>/.env.local (and any other git-ignored config the project uses).pnpm install, npm ci, pip install -r requirements.txt, etc.) from the worktree.Worktree setup checklist (DO-CONFIRM — perform each step, then verify before proceeding). Applies regardless of how the worktree was created:
.env.local (and any other .env.*, *.local, or project-specific ignored config) exists in the worktreepnpm install, npm ci, etc.) ran without error in the worktreepwd reports the worktree path because you entered it via EnterWorktree { path } (not the project root). In the AFK/headless fallback only, this item instead means the cd <absolute-worktree-path> && prefix is being applied to every Bash call$CLAUDE_PROJECT_DIR scoping correct — if the project references this env var in scripts, verify it resolves to the worktree path, not the primary repols "$(git rev-parse --git-dir)/hooks/" | grep -v '\.sample$' lists something, and the manager the repo declares (lefthook, husky, pre-commit, …) resolves. Hooks live in .git/hooks, which is per-worktree and untracked, so a fresh worktree inherits none of them — and the failure is silent in the worst direction: every commit succeeds, and every guarantee the repo documents at commit time simply did not run. If the manager is absent, either install it (lefthook install) or record in the Step 6 review notes that local gates were inactive for this branch, so nobody reads a green local run as the merge gate.claude/.tdd-active, .claude/.tdd-skipped, and .claude/.lfg-active do not exist in the worktree (fresh slate; Step 3 creates one).claude/.review-stamped and .claude/.fix-findings-active do not exist. Both belong to the previous branch's review: /pre-merge Phase 4 writes the first, /fix-findings writes the second, and /closeout and /fix-findings respectively remove them at the end of that branch. A leaked .review-stamped refuses every implementation write on this slice before it starts; a leaked .fix-findings-active holds the lock open for the whole slice, which is the silent direction. Delete either one you find — you are at the start of a branch that has had no review, so neither can be describing this oneIssue-shape and blocker gate. If the task is a GitHub issue, confirm it is a takeable slice before implementing it: not an undecomposed PRD, not blocked, and not already being closed by someone else's open PR. One GraphQL read answers all three, and the block decides — do not judge any of it by eye:
N=<n>
VERDICT_JQ='
def lines: split("\n")[];
[inputs][0] | if (.errors // [] | length) > 0 then "unknown" else
.data.repository.issue as $i
| if $i == null or $i.number != $n or (($i.title // "") == "")
or ($i.body | type) != "string"
or ($i.comments.nodes | type) != "array"
or ($i.blockedBy.nodes | type) != "array"
or ($i.closedByPullRequestsReferences.nodes | type) != "array"
or ($i.comments.nodes | length) != ([$i.comments.totalCount, 100] | min)
or ($i.blockedBy.nodes | length) != ([$i.blockedBy.totalCount, 50] | min)
or ($i.closedByPullRequestsReferences.nodes | length) != ([$i.closedByPullRequestsReferences.totalCount, 20] | min)
then "unknown"
else
([$i.comments.nodes[].body | lines | select(test("^Decomposed into: #[0-9]+"))] | length > 0) as $decomposed
| ([$i.body | lines | select(test("^#+ *(Appetite|Rabbit Holes|No-gos|User Stories|Implementation Decisions)\\s*$"; "i"))] | length > 0) as $prd
| [$i.blockedBy.nodes[] | select(.state == "OPEN") | {number, title}] as $open_blockers
| [$i.closedByPullRequestsReferences.nodes[] | select(.state == "OPEN" and .headRefName != $branch) | {number, headRefName}] as $in_flight
| if $prd and ($decomposed | not) then "undecomposed-prd"
elif ($open_blockers | length) > 0 then "blocked \($open_blockers | tojson)"
elif ($in_flight | length) > 0 then "in-flight \($in_flight | tojson)"
else "takeable ever_wired=\($i.blockedBy.totalCount) comments=\($i.comments.totalCount)"
end
end
end'
RESP=$(gh api graphql -F owner='{owner}' -F repo='{repo}' -F n="$N" -f query='
query($owner: String!, $repo: String!, $n: Int!) {
repository(owner: $owner, name: $repo) {
issue(number: $n) {
number title body
comments(last: 100) { totalCount nodes { body } }
blockedBy(first: 50) { totalCount nodes { number title state } }
closedByPullRequestsReferences(first: 20) { totalCount nodes { number state headRefName } }
}
}
}') || RESP=null
jq -rn --argjson n "$N" --arg branch "$(git branch --show-current)" "$VERDICT_JQ" <<<"${RESP:-null}" || echo unknownIt prints exactly one verdict, and each one names its action:
gh failed, the response carried GraphQL errors, the issue's own number or title did not come back, a field came back missing, or a returned list is shorter than its own totalCount says it should be. Stop. Re-run once; if it is still unknown, tell the user the read failed. Never proceed on it: an empty answer looks exactly like an issue with no comments, no blockers, and no PRs, and every check below fails open on it. A session once built a duplicate implementation of an issue an open PR already closed, because its read came back without the comment that said so.Appetite, Rabbit Holes, No-gos, User Stories, or Implementation Decisions) and no comment matches ^Decomposed into: #\d+. Stop. Invoke /prd-to-issues <this-issue-number> to produce implementation-ready slices, then restart /execute against one of the child slice issues. A decomposed PRD falls through to the checks below; the operator should still have supplied the child slice's number rather than the PRD's. /prd-to-issues is responsible for keeping only one Decomposed into: comment authoritative. A heading added to the shaped-pitch template is added to the block's list in the same change.comments=<m> for Step 1's thread read.Read `ever_wired` before trusting an empty blocker list. Empty has two causes: the slice genuinely has no open blockers, or the repo predates the edge wiring in /prd-to-issues §7 and has no edges at all. totalCount counts blockers in every state, so it separates them — ever_wired > 0 means this slice was wired and its blockers have since closed, so the empty list is real. ever_wired == 0 means no edge was ever written and the empty list proves nothing; if the issue body carries a prose Blocked by #N line, trust the prose and check those blockers' state by hand.
The in-flight check sees only linked PRs. closedByPullRequestsReferences lists PRs that close the issue through a Closes/Fixes/Resolves keyword or a manual link in the sidebar. A PR that only says Refs #N, or names the issue nowhere, is invisible to it, so takeable means no linked PR is open, not that nobody is working on the issue.
Note state here is the GraphQL enum — uppercase OPEN, not the REST endpoint's lowercase open.
On a sidecar work item. A task named W-<n> is a work item in the project's sidecar, not a GitHub issue (references/state-backend.md). No gh command runs for it, here or in any later step: on a guest repo the tracker is the client's. This block reads it with work view --json, reads each blocked_by id the same way, and prints the same verdicts as the GraphQL gate:
SC="<this skill's directory>/references/sidecar.sh"
W="W-<n>"
SIDECAR_VERDICT_JQ='
[inputs][0] as $i
| if ($i | type) != "object" or $i.id != $w
or ($i.body | type) != "string"
or ($i.comments | type) != "array"
or ($i.blocked_by | type) != "array"
then "unknown"
else
((($i.decomposed_into // []) | length) > 0) as $decomposed
| ($i.kind == "prd") as $prd
| [$i.blocked_by[] | select($st[.] != "closed") | {id: ., state: $st[.]}] as $open_blockers
| if $prd and ($decomposed | not) then "undecomposed-prd"
elif ($open_blockers | length) > 0 then "blocked \($open_blockers | tojson)"
elif ($i.pr // "") != "" then "in-flight \([{pr: $i.pr}] | tojson)"
else "takeable ever_wired=\($i.blocked_by | length) comments=\($i.comments | length)"
end
end'
J="$(bash "$SC" work view "$W" --json)" || J=null
ST='{}'
while IFS= read -r b; do
[ -n "$b" ] || continue
s="$(bash "$SC" work view "$b" --json | jq -r '.state // empty')"
ST="$(jq -c --arg b "$b" --arg s "${s:-unreadable}" '. + {($b): $s}' <<<"$ST")"
done <<<"$(jq -r '(.blocked_by // [])[]?' <<<"$J")"
jq -rn --arg w "$W" --argjson st "$ST" "$SIDECAR_VERDICT_JQ" <<<"$J" || echo unknownEach verdict takes the action listed above, with these differences:
work view failed, or the item's id, body, comments, or blocked_by did not come back. Same rule: re-run once, never proceed on it.kind is prd and decomposed_into is empty. The kind field decides, not the body's headings: a PRD whose body lacks the shaped-pitch headings is still undecomposed, and a slice never is (work set refuses decomposed_into on one). /prd-to-issues §8 writes that field, and work set replaces it on every write, so there is no "most recent comment" to pick.unreadable and gates like an open one, the same rule work list --unblocked applies to an id that does not exist.pr field is set. A sidecar has no closedByPullRequestsReferences, so the field is the only signal, and it carries no branch to compare with the current one. When the PR it names is the one this branch opened, continue; otherwise the GraphQL rule applies.ever_wired counts every blocked_by id. Unlike on GitHub, 0 is a real empty list: /prd-to-issues writes blocked_by with the slice, so there is no pre-wiring repo for it to be confused with.Skip this gate for one-off tasks not tied to a GitHub issue or a work item.
Ralph auto-detection gate. Evaluate all three conditions:
ralph-once.sh or ralph.sh exists in the repo rootIf all three are true, invoke /setup-ralph-loop now. Do not proceed to Step 1 until Ralph setup is complete or the conditions are not met.
Pipeline hooks gate. The hook file existing is not the same as the hook carrying the post-review edit lock, and this gate tests both. A project that ran /init-pipeline before the post-review edit lock shipped carries a hook that never reads .claude/.review-stamped: the lock is inert there, and a /fix-findings fixer holding .claude/.fix-findings-active is refused anyway — by the classification clause, under a message that names /tdd and never names the route the fixer is standing on. Both halves fail silently, which is why an existence-only check is not enough: an inert lock looks from the outside exactly like a branch nobody edited after review.
HOOK="${CLAUDE_PROJECT_DIR:-$(git rev-parse --show-toplevel)}/.claude/hooks/enforce-classification.sh"
# Every path the hook's own clause 1 tests, one per line. A path missing from
# this list is a boundary this gate cannot see, so an install predating it
# reports current forever. scripts/test-lfg-marker.sh section 7 pins this list
# against that clause; add a path here in the commit that adds it there, or the
# suite fails.
GATE_TERMS=".claude/.review-stamped
.claude/.tdd-active
.claude/.tdd-skipped
.claude/.lfg-active"
# Every `# gate: <name>` sentinel in the hook body, one name per line: a clause
# that adds no path above, so GATE_TERMS cannot see it. scripts/test-lfg-marker.sh
# section 10 pins this list against those sentinel lines.
GATE_SENTINELS="project-scope"
if [ ! -f "$HOOK" ]; then
echo "hooks-absent"
else
missing=""
while IFS= read -r term; do
[ -n "$term" ] || continue
grep -qF -- "$term" "$HOOK" || missing="$missing $term"
done <<EOF
$GATE_TERMS
EOF
while IFS= read -r s; do
[ -n "$s" ] || continue
grep -qxF -- "# gate: $s" "$HOOK" || missing="$missing gate:$s"
done <<EOF
$GATE_SENTINELS
EOF
if [ -n "$missing" ]; then
echo "hooks-stale"
echo "missing:$missing" >&2
else
echo "hooks-lock-present"
fi
fihooks-absent and hooks-stale both mean invoke `/init-pipeline` now, and do not proceed to Step 1 until it reports. The first scaffolds enforcement hooks into a project that has none. The second re-scaffolds § 2's hook body over an existing install and re-runs § 6's .gitignore append, which that install never ran for the lock's two flags — without it both land as untracked files that can be committed, holding the lock shut or open across every future branch in that repo. /init-pipeline § 2 carries an existing IMPL_PATTERNS line through a re-scaffold rather than re-asking, so an upgrade does not reset a trigger surface the project customized. The sidecar is /init-pipeline § 8's concern, and its gate there decides whether it is created. hooks-lock-present proceeds.
What this gate cannot see, since its third verdict would otherwise read as a claim it does not make. It greps for every path the hook's own clause 1 tests, so it recognizes one version boundary per path and reports which side of each an install is on. That is sound in one direction only. A hook missing any of those terms predates it; but once a project has been upgraded the terms are there forever, so a later change to /init-pipeline § 2's hook body that adds no new path — a reordering, a rewritten refusal message, a fix to a clause's logic — still reports hooks-lock-present and is never distributed by this gate. The third verdict is therefore named for the terms it found rather than for currency, which the check cannot establish. A change that adds a capability rather than a path, like the clause that keeps the hook from gating writes outside $CLAUDE_PROJECT_DIR, gets its own boundary by carrying a # gate: <name> sentinel line in the hook body and its name in GATE_SENTINELS; an install without the line reports hooks-stale and names gate:<name> on the missing: line.
GATE_TERMS and GATE_SENTINELS are the whole of that claim. scripts/test-lfg-marker.sh section 7 pins GATE_TERMS against the hook's clause 1 and section 10 pins GATE_SENTINELS against the hook's sentinel lines, so a marker or a sentinel added to the hook and not to its list (or the reverse, for a sentinel) fails the suite in the commit that adds it. That replaces the rule this paragraph used to state in prose — give the boundary its own term, in the commit that introduces it — which chrislacey89/skills#370 read and did not follow, shipping .claude/.lfg-active with no distribution term and leaving it inert in every project installed before it (chrislacey89/skills#385). A distribution boundary that is neither a new path nor a new sentinel still has no mechanism: a change that should reach existing installs and adds neither is invisible to this gate, as the paragraph above says.
Guard resolver check. /git-guardrails-claude-code installs block-dangerous-git.sh with a copy of sidecar.sh beside it, and the guard reads a sidecar review stamp through that copy. The copy updates only when someone re-runs that skill, so a guard installed before the resolver existed has none, and one whose copy has fallen behind the stamp format finds no stamp. Both fail open: the merge goes through unchecked, on exactly the guest repos whose PR body never carries a stamp. This check compares the project's copy with this skill's bundled one.
GUARD_DIR="${CLAUDE_PROJECT_DIR:-$(git rev-parse --show-toplevel)}/.claude/hooks"
BUNDLED="<this skill's directory>/references/sidecar.sh"
if [ ! -f "$GUARD_DIR/block-dangerous-git.sh" ]; then
echo "guard-absent"
elif cmp -s "$BUNDLED" "$GUARD_DIR/sidecar.sh"; then
echo "guard-sidecar-current"
else
echo "guard-sidecar-stale"
figuard-sidecar-stale means tell the user and offer to re-run /git-guardrails-claude-code at project scope, which copies the pair afresh; in an AFK iteration, record it in the Step 6 review notes instead. It does not hold Step 1. guard-absent and guard-sidecar-current proceed. A guard installed at global scope is not inspected here. scripts/test-git-guardrails.sh runs this block against all three cases.
TDD classification gate. Step 3 requires classifying the work before writing any code. /tdd creates .claude/.tdd-active with a ! command the harness runs when the skill loads, without the model, except in auto mode for a command that would need approval, where the skill loads with an instruction for the model to run it instead (Claude Code skills docs), so count the marker as created once its TDD marker created line has printed; visual frontend creates .claude/.tdd-skipped; /lfg creates .claude/.lfg-active. A PreToolUse hook blocks all .ts file writes unless one of these markers exists. Step 6 removes every one of them after commit.
Trivial-task exception. A single-commit cleanup unrelated to active feature work may skip classification by creating .claude/.tdd-skipped directly. The four conditions that all have to hold, and what to do when one does not, are stated once in references/trivial-task-rule.md.
Assumptions validation gate. If the task is a GitHub issue with an "Assumptions from Parent PRD" section, spend 60 seconds checking each listed assumption against current reality before proceeding. For each:
pkg → pkg/http, or any pkg/<sub> → pkg/<other-sub>) for a multi-runtime package is a runtime-affecting change disguised as a type-only diff — treat it as an assumption shift, not a free-pass type-equivalent edit.If all assumptions still hold, proceed to Step 1. If any assumption has changed, stop and flag it to the user — this slice needs a targeted /research + mini-PRD cycle before execution, not a patch during implementation. Do not proceed with stale assumptions and attempt to work around them mid-execution.
Skip this gate entirely for one-off tasks without an "Assumptions from Parent PRD" section.
Un-discharged feasibility check (advisory). If the task's research artifact (archive file or spike issue) still carries Uncertain or Speculative assumptions whose verdicts are cheaply settled by 10 lines of throwaway code — does the library actually expose this, does the streaming path emit partial tags, does this format render where we need it — surface them now and suggest /prototype FEASIBILITY before main implementation. This is advisory, not blocking (per XP's slack principle); the user may have a reason to proceed and discover the answer mid-implementation. But the cost asymmetry is real (XP's Defect Cost Increase): a spike at this moment costs minutes, while the same assumption discovered mid-implementation costs hours of pivot. State the un-discharged assumptions explicitly and let the user decide. Skip this check for one-off tasks without a research artifact or when every assumption is already tagged Verified / Refuted.
Consumes verification gate. Only for issue-based slice work. If the task comes from a GitHub issue created by /prd-to-issues, and its ## Boundary Map / ### Consumes section references an already-closed upstream slice, spend 60 seconds verifying each listed symbol exists at the declared path in the current tree. This catches upstream boundary-map drift before implementation starts.
For each Consumes entry:
/prd-to-issues Boundary Map guidance), confirm the consumer code derives its input type from the producer via import type (or the language's equivalent) rather than re-declaring the shape. A local re-declaration that happens to match the producer is a DRY violation that will silently drift when the producer evolves; the typed import is the structural fix that makes that drift class impossible.If any Consumes symbol is missing or wrong-shaped, stop. The upstream boundary map is stale. Choose one of:
Do not silently absorb the gap — leave a breadcrumb for the next slice.
Skip this gate for one-off tasks, sibling slices still being planned, or issues without upstream Consumes entries.
This gate is scoped to intra-repo symbols (paths, exports, shapes). The mirror check for externally-resolvable declarations — package names, public API symbols, and pinned versions against the research snapshot — runs at /pre-merge's Boundary Map Contracts dimension under "Spec-reality check." Step 0 sees the registry at slice-start; /pre-merge sees it at merge time. Both windows are intentional; do not widen this gate to duplicate the review-time check.
Read any referenced plan, PRD, or GitHub issue. Explore the codebase to understand the relevant files, patterns, and conventions. If the task is ambiguous, ask the user to clarify scope before proceeding.
Read the issue comment thread (issue-based work only). When the task is a GitHub issue, read its comment thread before implementing — do not stop at the body. Step 0's issue-shape and blocker gate read the thread to decide its verdict but printed only the verdict, so fetch it now with gh issue view <n> --json comments --jq '.comments[].body'. The gate's takeable line carries comments=<m>; if this read returns fewer than <m> comments, it came back thin — re-run it, and never read a short or empty result as an empty thread. The comments are where the pipeline's continuation state lives: prior-iteration handoff notes (what was done, what remains, the exact error output a previous context window hit), plateau-stop notes naming what did not advance, post-hoc correction comments filed against this issue when an upstream boundary map drifted, and explicit human scope changes added after the issue was authored. In AFK Ralph loops this is load-bearing — iteration N+1 is designed to continue from what iteration N wrote into the thread, so skipping it re-derives or repeats work the previous iteration already explained.
On a sidecar work item, the body, the comment thread, and the parent PRD come from work view --json (references/state-backend.md). The ### Context block below is in the body, and the parent PRD is the parent field, not a #N:
SC="<this skill's directory>/references/sidecar.sh"
W="W-<n>"
J="$(bash "$SC" work view "$W" --json)" || exit 1
jq -r '.body' <<<"$J"
jq -r '.comments[] | "## Comment \(.at)\n\(.body)"' <<<"$J"
P="$(jq -r '.parent // empty' <<<"$J")"
[ -z "$P" ] || bash "$SC" work view "$P" --json | jq -r '.body'The comments=<m> check above applies unchanged, and so do the precedence and disposal rules below. A comment that qualifies for its own item is filed with work create --kind slice --parent <prd>, and the item it blocks gets no edge, because no verb rewrites blocked_by: name the new id in a work comment on the blocked item instead.
Precedence rule. The issue body remains the durable contract. Comments augment it; they do not silently override it. A comment is an authoritative addition only when it is (a) a pipeline-authored continuation or correction comment, or (b) an explicit human scope change. Freeform discussion is context, never an override. If a comment appears to contradict the body on scope, and it is not a clear pipeline-authored correction or human scope change, flag the conflict to the user rather than acting on the comment.
Disposal rule. Classifying a comment is not the whole job — decide what happens to the part of the thread the body does not cover. This applies when this slice's PR will close the issue (Closes #N). The body is this pipeline's baseline: acceptance criteria live there, /prd-to-issues decomposes bodies, Closes #N closes on a body's terms. A capability that stays in a comment is outside every one of those mechanisms, so the close silently discards it — and the loss emits no signal, because the issue closes green and the PR merges clean.
The discriminator is a single test, and it is not "is the idea good": can you name the consumer? If you can point at the issue, slice, PRD, or shipped code that would use the capability, it qualifies. If you cannot — a design musing, a "we might want," an approach the author was thinking aloud about with no identified caller — it does not qualify and must not be promoted; promoting it manufactures scope and invents dependencies. When in doubt, it does not qualify.
For a comment that does qualify, surface it to the user before finishing the slice and offer the two disposals:
Blocks #N link naming the consumer you identified — when it is real work that belongs elsewhere.Either one gives the capability an owner that survives the close. Do not silently absorb it, do not widen scope unilaterally, and do not implement it just because you noticed it — the choice is the user's. On AFK runs there is no user to ask, so default to filing the follow-up issue with the Blocks #N link; promotion into the body widens the slice without approval.
Skip this read — and the disposal rule with it — for one-off tasks not tied to a GitHub issue (the same scope guard Step 0 uses).
Resolve the sidecar first. The research archive and the project's lessons are found through this skill's bundled resolver, references/sidecar.sh, at its absolute installed path; never compute a sidecar path yourself.
SC="<this skill's directory>/references/sidecar.sh"
bash "$SC" resolveExit 0 proceeds. On exit 1 in an AFK iteration, stop: print the resolver's stderr as printed — on SIDECAR MISS that is the init command — and leave it for a human, because creating a sidecar is a human act and an iteration that reads half its context reads as complete when it is not. In a HITL run, show the same line to the user and continue read-only: the solutions grep below falls back to docs/solutions/, and a research path the PRD cites is still checked as written.
Read the research artifact for this feature. The PRD's "Research Reference" section names where it lives — one of two locations depending on the project's research.storage mode:
research-labeled GitHub issue (Refs #<spike-issue-number>). Read it with: gh issue view <spike-issue-number>This works on any machine — fresh clones, CI sandboxes, recovered laptops, or contributor environments.
SC="<this skill's directory>/references/sidecar.sh"
bash "$SC" require "<path the PRD cites>" --from "PRD #<n>"On a miss the resolver prints MISSING: <path> (referenced by PRD #<n>) and exits 1 — typically a different machine from the one that produced the research. In an AFK iteration, stop. In a HITL run, show the line to the user and either re-run /research or proceed with their explicit acknowledgment of the missing context.
Some legacy PRDs may still reference research.md in the repo root or plans/ — read it if present. Whatever the location, the research artifact contains cached technical research that should inform your approach. Do not re-research what has already been decided.
Read the slice's `### Context` block before the grep below (issue-based work only). When the slice issue's ## Boundary Map carries a ### Context subsection, read its anchors, gotchas, and research pointer first. /prd-to-issues wrote that block so this session would not have to re-derive it: the anchors name the existing files to read or imitate, and the research pointer goes straight to the artifact instead of walking the parent PRD to find it. Reading it is not optional — a block that is written and never preferentially read is not neutral overhead, it is a section readers learn to skip, which taxes the Boundary Map around it.
Treat it as pointers, not truth. The anchors were written at decomposition time and siblings may have merged since; the code and the research artifact still win on any conflict, per the precedence rule below. A stale anchor is a correction to file on the issue, not a reason to skip the block.
Consult docs/solutions/ and the sidecar for relevant past solutions before starting implementation. sidecar.sh solutions-dirs prints the directories to search and owns that policy (its header states it). Set KW to the keyword to look for:
SC="<this skill's directory>/references/sidecar.sh"
KW="relevant-keyword"
bash "$SC" solutions-dirs | while IFS= read -r DIR; do
[ -d "$DIR" ] && grep -rl -- "$KW" "$DIR"
done
trueList matches from both locations; one does not shadow the other. A match under an in_tree_path other than docs/solutions is that repo's own convention: read it as a plain document. When an entry you read cites another path (a Related: pointer, a research entry, a dossier), check it with SC="<this skill's directory>/references/sidecar.sh"; bash "$SC" require "<cited path>" --from "<the citing entry's file path>" (a fresh shell has no SC) before relying on it, and open the path it prints, not one you derive from the citing entry; on MISSING:, stop in an AFK iteration and flag it to the user in a HITL run.
If past solutions exist for this problem domain, incorporate their lessons and avoid their documented pitfalls.
Artifact precedence: When the research artifact and a solutions entry give conflicting guidance, follow the research artifact — it was verified against the current installed versions. Storage location does not affect trust: a spike issue and an archive entry carry equivalent authority. If the conflict is significant enough that you are uncertain, flag it to the user before proceeding. Load solutions entries selectively: grep for relevant keywords first, then read only matching files.
If the task has not already been planned, create a plan for it. If the GitHub issue includes boundary maps (Produces/Consumes sections), use them to understand the interfaces you need to implement or code against.
Before implementing, check the project's stack and load relevant best practices.
package.json includes next or react, load these skills before writing code:/vercel-react-best-practices — performance optimization and React Server Component guidance/vercel-composition-patterns — component composition patterns that scale without prop sprawl/next-best-practices — file conventions, data patterns, metadata, and error handling/next-cache-components — cache components, use cache, cacheLife, and cacheTagSTOP — classify before writing any code:
/tdd now (creates .claude/.tdd-active)/tdd now (creates .claude/.tdd-active)mkdir -p .claude && touch .claude/.tdd-skippedA PreToolUse hook blocks all .ts file writes unless one of these markers exists. Do not write implementation code until you have classified the work.
If /tdd is not available, follow this minimum discipline:
Do not write all tests upfront — write one, make it pass, then move to the next.
[TypeScript projects] Library callback returns. When a logical unit implements a callback the library asks the application to provide (agent hooks, middleware, proxy, tool handlers, render props, lifecycle methods), anchor the returned value to the library's declared return type with satisfies LibraryReturnType, a fresh object literal, or a derived type (ReturnType<typeof …>). Never return a typed local variable — TypeScript's excess-property check does not run on returns of typed values, so fields the library's signature does not declare are silently dropped at runtime. See /tdd Refactor step for the full rationale; if the research artifact (archive file or spike issue) carries a Library Callback Contracts snapshot (/research Phase 1.25), use its accepted-fields list as the pinned source.
Comment the code, not the incident. A comment you write here carries the current why — what this code does, and why it is shaped this way. The incident that led you here goes to the commit message and the PR body instead: what the prior behavior was, which earlier attempt this supersedes, what you rejected on the way. Before committing a unit, reread the comments you added to it and move any sentence that explains history rather than code. Write the comment to the next person who edits this file, never to the reviewer of this diff. A sentence that answers an objection the review might raise — why this and not the adapter, that the component is the real shipped one, that the parallel path is deliberate — is addressed to the reviewer, and it goes in the PR body with the rest of the review-facing account. The tell is emphasis: a capitalized REAL, SAME, or DELIBERATE is a comment arguing with someone who is not the file's next editor. Say each thing once: a comment that repeats a sentence already in the file, or already on the declaration beside it, is the copy to delete.
#### Commit after each logical unit
Do not accumulate all changes into one commit. Commit after each self-contained unit of progress. A logical unit is the smallest change that leaves the codebase in a working state — typecheck passes, tests pass, nothing is half-wired. Examples:
After completing each logical unit:
pnpm run typecheck and pnpm run test (or the project's equivalent). Fix any failures before committing./closeout treats squash as the common convention, so the commit message alone is not a durable destination. The PR body is the copy that survives intact for a reader running git blame months later.If a unit touches both a test and its implementation, they belong in the same commit. If a refactor was triggered by the unit but is conceptually separate, commit the refactor separately.
"All steps done" is NOT verification. Check the actual outcomes.
By this point, each logical unit has already been committed with passing typecheck and tests. Step 4 is the full-slice verification pass — confirming the whole feature works end-to-end, not just that individual units pass.
Run the full feedback loops one final time:
pnpm run typecheck
pnpm run testFix any issues. If fixes are needed, commit them as a separate commit (e.g., "fix integration between X and Y").
Then apply the verification ladder — use the strongest tier you can reach:
#### Tier 1: Static Verification
Deletion Completeness (when this slice's diff deletes or renames a module). Read the trigger off the diff, which you already have, rather than off a section header nothing in the pipeline writes:
BASE_BRANCH=$(git symbolic-ref refs/remotes/origin/HEAD --short 2>/dev/null | sed 's@^origin/@@')
if [ -z "$BASE_BRANCH" ]; then
for candidate in main master prod develop trunk; do
if git rev-parse --verify "$candidate" >/dev/null 2>&1; then BASE_BRANCH=$candidate; break; fi
done
fi
# A name is not a ref. $BASE_BRANCH names the branch (for `--base`, `git switch`);
# $BASE_REF points at it, and is the only thing safe as a range endpoint.
if git rev-parse --verify "origin/$BASE_BRANCH" >/dev/null 2>&1; then
BASE_REF="origin/$BASE_BRANCH"
else
BASE_REF="$BASE_BRANCH"
echo "note: origin/$BASE_BRANCH does not resolve — measuring against the local branch, which may be stale" >&2
fi
git diff --diff-filter=DR --name-status "$BASE_REF...HEAD"Residual: $BASE_REF is only as fresh as the last git fetch, and on a triangular fork (or a remote not named origin) origin/$BASE_BRANCH may be absent or track your fork rather than upstream — the else branch then falls back to the local branch, which is the stale-ref behavior this guard exists to avoid. It says so on stderr rather than falling back silently, because a plausible wrong answer with no signal is what let this defect live for five months. If the counts look wrong, git fetch and re-run, or set BASE_REF by hand.
Deleted paths print as D<TAB><path>, renames as R<score><TAB><old><TAB><new>. Any output at all fires the rung; empty output skips it. Gate on the output, not the exit status — the command exits 0 either way. A ### Deletes section in the slice body is an optional hint that names which surfaces to sweep; it is not the trigger.
For each deleted or renamed module, enumerate its external consumer surfaces — the symbolic names callers were taught to emit for it to consume, beyond its exports. Typical surfaces:
data-*)addEventListener('foo-bar'), dispatchEvent(new CustomEvent('foo-bar')))window, localStorage, or sessionStorage keysInfer surfaces from the module body as it existed before deletion (git show <base>:<path>, or the Deletes hint notes when the slice body happens to carry them). Grep the merged tree for each surface across every source-text file type the project uses — templates, source code, styles, config, docs. Do not restrict to a fixed extension list; the relevant surfaces depend on the stack (.py/.rb/.go/.rs for imports, .vue/.svelte/.astro/.tsx for templates, .css/.scss/.sass/.less/.styl for styles, .yml/.toml/.json for config, .md/.mdx for docs that ship). Zero matches required to pass. Non-zero matches: restore the module, migrate the consumers, or declare them as intentionally inert and track the cleanup as a follow-up slice. Imports alone are the narrowest possible definition of "consumer"; the surface may be wider.
Upstream shape sweep. After the consumer-surface sweep above, list each export touched on a shared module in this slice (context fields, builder return-type fields, interface or type members, schema fields, exported map or record entries). For each such export, grep the post-delete tree for non-self-reference reads. Zero matches required to pass. Non-zero matches: confirm the readers are live and intentional. Zero matches: drop the export in the same PR — the migration window closes the moment the legacy consumer is deleted, and a retained-but-unread export widens the import contract so a future cleanup becomes a breaking change rather than a silent removal. Dead-export linters (knip, ts-prune, TypeScript noUnusedLocals) cover many shapes of this but not exported context, interface, or schema fields — those are read by the type itself and look live to the tool, so the targeted post-delete grep is doing work the linter cannot.
Removed-guard sweep (whenever the diff removes lines, including inside files that survive). The module rung above fires on deleted files. It does not see a guard removed from a block that was edited in place: an exit, return, throw, a refusal, an || … fallback, or a case or if arm that sends some inputs elsewhere. That happens most often in a dedup. A restated parse is replaced by a call to its one owner, and the parse turns out to have been the only check that a nonzero exit was the expected one. The value moved to the owner, but the guard did not move with it. List the candidates off the diff. Each block runs in its own shell, so paste the BASE_REF block above in front of this one:
: "${BASE_REF:?paste the BASE_REF block above in front of this one}"
git diff -U0 "$BASE_REF...HEAD" | grep -E '^-[^-]' | grep -E '(^|[^[:alnum:]_])(exit|return|throw|raise|die|case|if|elif|else)([^[:alnum:]_]|$)|\|\||&&'It is a candidate list, not a gate: a match is a line to read, and an empty list skips the sweep. For each removed guard, write down the condition it enforced, then either name where that condition is enforced now or say it is retired on purpose. Back each "enforced now" with a test that sends the guarded input, such as the nonzero exit that is not a miss. "The new owner is the single source" does not answer it.
#### Tier 2: Command Verification
/tdd § Timing-coupled primitives. Fix via injection, not testTimeout bumps.#### Tier 2.5: Runtime Startup Verification Mandatory when the slice touches schema, migrations, environment config, server initialization, or new routes. Skip only for pure-logic changes to existing modules where nothing about app startup changed.
db:push — do not assume the dev database has the latest schema just because tests passed (tests often use in-memory databases that run their own migrations)pnpm run dev (or equivalent) boots and responds, not just buildscurl -s -o /dev/null -w '%{http_code}' http://localhost:<port>/ returns 200.env.local or equivalent)If you cannot start the dev server (e.g., missing external services), note which checks you skipped and why in both the Step 5 checklist and the Step 6 review notes. The checklist reaches the user; the review notes reach the reviewer. Sending the note only to the checklist means it dies there on any run that never presents one — which is every AFK run — and skipped verification invisible to review is the exact gap this tier exists to close.
#### Tier 2.6: Non-Dry Path Sanity Check (CLI + orchestration slices only)
Mandatory when the slice ships a CLI, scheduled job, cron worker, or orchestration entrypoint that has a dry-run or preview mode.
Dry-run success does not imply real-run success. A dry-run can short-circuit before storage or side-effects and hide placeholder functions wired into the production path.
For each function wired as a default in the production code path (layer construction, DI container, config object, CLI flag handler), check:
If any placeholder is wired as the default for a non-dry path without a fail-fast guard, flag it now. Options:
if (!process.env.ALLOW_PLACEHOLDER) throw new Error(...) or equivalentdryRun === true and require a real implementation for the non-dry pathThis is a silent-degradation check: if an operator ran this without --dry-run, would the output be real, or would placeholder data flow through the production path?
#### Tier 2.7: Production-Runtime Parity (deploy-runtime ≠ test-runtime slices only)
Conditional — never universal. This rung fires only when at least one of the following holds, and is skipped entirely otherwise. A slice whose tests run in the same runtime it deploys to (most Node-to-Node and Node-to-Vercel work) never sees it.
<link>/<script>/<img> references, a publish/copy step, a CDN or worker public root.Why this rung exists — and why it is a feedback loop, not an inspection layer. Green tests certify behavior in the environment the tests model, not the one the code deploys to. When the test runtime is more permissive than the deploy runtime, or a seam is mocked away from the real artifact, "tests pass" is not evidence of "works in production." This is the dev/prod parity gap (Twelve-Factor Factor X: keep the dev↔prod gap small along the tools dimension) and the missing automated feedback loop that Continuous Delivery closes with a smoke test against a production-like environment. The job here is not to add ceremony — it is to make the manual "someone ran the deployed thing and it worked" step structural, so the safety net is the pipeline rather than a person's diligence.
Run it against the real artifact, not the test build:
wrangler dev / the platform's emulator that uses the production runtime, a preview deploy, or the production build served the way it will actually be served — not just pnpm run dev./_astro/*, /assets/*, fonts, favicon) each return 200, invoke the platform API the test mocked. A 200 on the HTML is not enough if its sub-resources 404.The deeper fix usually lives upstream in /tdd: a seam that had to be mocked to stay green (a platform crypto API, the filesystem, the deploy copy) is exactly the seam GOOS says you should not mock — wrap external types you don't own in a thin adapter and integration-test the adapter. This rung catches the parity gap; owned adapters plus a faithful test runtime keep it from recurring.
Review-cadence note. Added on convergent grounds (Continuous Delivery's smoke-against-production-like-environment, Twelve-Factor Factor X, Release It! "Design for Deployment", the GOOS walking skeleton) plus one triggering incident (a downstream Cloudflare Workers repo's audit-publish slices, 2026-06-25 — both green under the test suite, both broke only in the workerd/deployed runtime). If after a reasonable sample of slices this rung fires <10% of the time on diffs that had no other issue, demote it to advisory or remove it rather than leave it as ceremony.
#### Tier 3: Behavioral Verification
#### Tier 4: Human Verification
If verification reveals gaps, fix them and commit the fix as its own commit. Do not amend a prior commit — the history should show what was built and what was corrected.
Scope check on set-claims. Before writing any sentence that quantifies over a set, name the set, the enumeration that produced it, and the members not covered. If every member found shares one wording or one kind of file, the enumeration stopped early — see references/restated-claims.md.
Then mutate at the point of consumption (only when this slice ships a test that claims to hold a property). Skip it when the slice ships no such test — prose, config, and styling work have nothing for it to act on. The rung itself, both readings of a green result, and the rule that the mutating edit must be drawn from the corpus rather than composed are stated once in references/mutation-at-consumption.md.
#### Bug-Fix Verification (when the task is a fix, not a feature)
If this unit of work is fixing a bug, apply these additional checks before committing:
~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.