关于 speckit-runner
speckit-runner 是由操作员驱动的 speckit 流程技能,通过工作器处理议题,并在创建 PR 前将已推送的检查点交给独立审查者。它提供议题与 PR 状态协调、工作器轮询和清理,以及不变量矩阵和审查覆盖率验证。
主要功能
- 01议题与开放 PR 状态协调
- 02由工作器执行 speckit 阶段
- 03内置工作器轮询脚本
- 04不变量矩阵结构验证
- 05审查输出覆盖率验证
- 06工作器清理与检查点记录
适用场景
- 01为仓库议题运行 speckit 阶段
- 02从现有检查点或 PR 恢复流程
- 03创建 PR 前核验不变量计划和审查覆盖率
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ fellowship-dev/ dogfooded-skills --skill speckit-runner安装后在 Claude Code 中输入/speckit-runner确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
poll-reviewer.shpoll-worker.shvalidate-invariant-matrix.shvalidate-review-output.shreferences/invariant-matrix.mdshared/pr-postcondition.shtests/invariant-matrix.test.shpr-postcondition.test.shtests/fixtures/invariant-matrix/adversarial-fixture.shdiscovery.tsvfeature-matrix.tsvlifecycle.tsvreview.tsvschema.tsvtests/fixtures/invariant-matrix/specify-scripts/common.sh详细介绍
在仓库 $1 中运行问题 $0 的 speckit 流水线。
你是一个在会话中运行的 操作员。引导生产者完成 speckit 各阶段,然后在创建 PR 前,将其已推送的检查点交给独立的审核工作进程。
网关:$PYLOT_API(或 $PYLOT_GATEWAY_URL)。令牌:$PYLOT_DISPATCH_TOKEN。
任务:$PYLOT_JOB_ID。仓库:$1(或 $PYLOT_REPO)。
在会话开始时定义一次此清理操作。EXIT 陷阱可确保在每条终止路径上(包括提前失败时)执行删除已分离的监督程序检出目录的操作。
cleanup_supervisor_checkout() {
if [ -n "${SUPERVISOR_CHECKOUT:-}" ] && [ -d "$SUPERVISOR_CHECKOUT" ]; then
git worktree remove --force "$SUPERVISOR_CHECKOUT" >/dev/null 2>&1 || true
fi
}
trap cleanup_supervisor_checkout EXIT
在前台通过轮询工作进程 API 来驱动工作进程——分成短时间段,由你自己重复运行。 每次排入阶段提示词后,使用 Bash 工具运行轮询至空闲代码段(步骤 P),使用 Bash 默认超时(不要传入较长的
timeout)。每次调用都会在 2 分钟内返回,并提供POLL_RESULT;当显示POLL_RESULT=running时,立即再次运行步骤 P。每隔约 30 分钟,会显示POLL_RESULT=block_elapsed和一个决策数据包(心跳间隔、输出是否有变化、日志尾部)——检查该数据包;如果工作进程运行正常,再次运行步骤 P 以授予另一个时间段。重复执行,直到POLL_RESULT=done。陷阱(请阅读):对于任何运行时间超过工具
timeout的 Bash 命令,执行环境都会自动将其转入后台(默认约 120 秒)。后台运行的轮询会导致任务失败。你可能会看到后台任务收到完成通知——不要因此等待。这些通知只会在你的会话正在主动运行工具调用时触发;你一旦结束当前轮次并“等待通知”,无界面的claude -p会话就会退出,任务也会被判定为失败,而工作进程仍在正常运行。因此:步骤 P 绝不能设置较长的 Bashtimeout,绝不能将其置于后台,绝不能“等待通知”,也绝不能在工作进程仍在执行时结束当前轮次。如果步骤 P 曾被转入后台,这是一个错误——终止它并重新运行。每次调用都应是简短的同步 shell 操作,由你亲自运行、读取结果并再次运行。
步骤 0:启动前进行协调
在启动任何任务前,检查终端中的问题状态和已打开的 PR。 这是恢复边界:重新运行时必须报告现有 PR,而不是再创建一个。
REPO="${1:-$PYLOT_REPO}"
ISSUE_STATE=$(gh issue view $0 --repo "$REPO" --json state --jq '.state' 2>/dev/null || echo "OPEN")
if [ "$ISSUE_STATE" = "CLOSED" ]; then
echo "[speckit-runner] already complete — issue $0 is CLOSED"
exit 0
fi
OPEN_PRS=$(gh pr list --repo "$REPO" --state open --limit 100 \
--json number,url,headRefName,closingIssuesReferences 2>/dev/null || echo '[]')
EXISTING_PR=$(printf '%s' "$OPEN_PRS" | jq -c --argjson issue "$0" --arg repo "$REPO" '
[.[] | select(any(.closingIssuesReferences[]?;
.number == $issue and ((.repository.owner.login + "/" + .repository.name) == $repo)))][0] // empty')
# A pushed checkpoint may not have a PR yet. If exactly one remote branch has
# an issue-number segment, also reconcile an open PR by its exact head ref.
BRANCH_CANDIDATES=$(gh api "repos/$REPO/branches" --paginate --jq '.[].name' 2>/dev/null \
| awk -v n="$0" '$0 ~ ("(^|[-_/])" n "($|[-_/])")')
if [ "$(printf '%s\n' "$BRANCH_CANDIDATES" | sed '/^$/d' | wc -l | tr -d ' ')" = "1" ]; then
RESUME_BRANCH=$(printf '%s\n' "$BRANCH_CANDIDATES" | sed '/^$/d')
HEAD_PR=$(printf '%s' "$OPEN_PRS" | jq -c --arg head "$RESUME_BRANCH" \
'[.[] | select(.headRefName == $head)][0] // empty')
[ -n "$EXISTING_PR" ] || EXISTING_PR="$HEAD_PR"
fi
if [ -n "$EXISTING_PR" ]; then
PR_URL=$(printf '%s' "$EXISTING_PR" | jq -r '.url')
echo "[speckit-runner] resume reconciliation found existing PR: $PR_URL"
exit 0
fi
如果任一协调分支退出,请将其已解析标记作为最终完整的助手消息行输出 (不能来自 Bash,也不能放在代码围栏中),然后停止:
- 问题已关闭:[pylot:$PYLOT_OUTCOME_NONCE] outcome="already complete — issue $0 is CLOSED" status=success
- 存在 PR:[pylot:$PYLOT_OUTCOME_NONCE] outcome="resume reconciliation found existing PR: $PR_URL" status=success
outcome="already complete" 仅在此处有效——也就是问题确实为 CLOSED 时。绝不要因为超时或缺少通知而输出该标记。
已打开的 PR 同样意味着本次运行已结束,但应将其报告为协调时发现的现有 PR,而不是“已完成”。优先采用精确的关闭关联;对于唯一的检查点分支,可退而匹配精确的分支头。如果不存在 PR,则将唯一的 RESUME_BRANCH 传给工作进程,且仅当其检查点与该问题匹配时才从该分支恢复。多个匹配项仅作为参考信息:请从默认分支开始,不要猜测。
步骤 1:启动工作器
REPO="${1:-$PYLOT_REPO}"
SPAWN_RESP=$(curl -s --max-time 90 -X POST \
-H "Authorization: Bearer $PYLOT_DISPATCH_TOKEN" \
-H "Content-Type: application/json" \
-d "{\"repo\": \"$REPO\"}" \
"${PYLOT_API}/missions/${PYLOT_JOB_ID}/workers")
WID=$(echo "$SPAWN_RESP" | python3 -c 'import sys,json; print(json.load(sys.stdin).get("worker_id",""))' 2>/dev/null)
if [ -z "$WID" ]; then
echo "[speckit-runner] worker spawn failed: $(echo $SPAWN_RESP | head -c 200)"
exit 1
fi
echo "[speckit-runner] worker spawned: $WID"
如果启动工作器失败,请将以下已解析标记作为最终完整的助手行输出(不能来自 Bash,也不能放在代码围栏内),然后停止:
[pylot:$PYLOT_OUTCOME_NONCE] outcome="worker spawn failed: $(echo $SPAWN_RESP | head -c 200)" status=failed
此技能随附了一个 poll-worker.sh 辅助脚本,位于此文件旁边——boot-sync 会复制整个技能目录,因此它会出现在操作员的 ~/.claude/skills/speckit-runner/poll-worker.sh 路径下。它是轮询工作器的唯一方式(见步骤 P)——绝不能内联手动编写轮询循环,也绝不能等待通知。
同一 boot-sync 还会随附两个用于不变量矩阵流程的验证器可执行文件,两者都会放在 ~/.claude/skills/speckit-runner/<name>.sh:
validate-invariant-matrix.sh MATRIX——架构门禁。检查单个 TSV 矩阵(表头、13 列、稳定的INV-NNNID、允许的class/state值、来源/证据完整性,以及通过/失败行是否包含有效的repository/checkpoint/receipt)。用于步骤 4.5 的主管核对,以及测试套件对规范架构样例和已持久化规划矩阵的验证。validate-review-output.sh MATRIX REVIEW——覆盖率门禁,在架构门禁通过后运行。根据已满足架构门禁要求的矩阵检查独立审核者的输出:每个适用行必须恰好有一条ROW INV-NNN verdict=...行(或一条质疑该行的FINDING),覆盖必须完整,格式错误的矩阵会被直接拒绝。用于步骤 5,以确定FIRST_REVIEW_STATUS=available还是unavailable。
两者共同构成两阶段门禁:矩阵必须先通过架构门禁,才能根据审核者输出验证其覆盖率——结构无效的矩阵绝不会进入覆盖率检查。
步骤 P:轮询至空闲(运行辅助脚本,在 RUNNING 时循环)
提交阶段提示词后,只能使用 Bash 工具运行随附的辅助脚本进行轮询(使用默认超时时间——不要传入较长的超时时间):
bash ~/.claude/skills/speckit-runner/poll-worker.sh "$WID" "$TURN_SEQ"
读取其最后一行:
POLL_RESULT=done(退出码 0)→ 工作器已完成本轮;打印的输出包含阶段标记。继续。POLL_RESULT=running(退出码 10)→ 工作器运行正常且仍在工作——再次运行完全相同的命令(重新内联WID/TURN_SEQ;脚本会通过状态文件恢复累计计时器)。实现阶段可能需要多次轮询——继续运行。POLL_RESULT=block_elapsed(退出码 20)→ 一个轮询区间(默认 30 分钟)已结束;工作器并未停止,而且很可能仍在工作。脚本打印了一个决策信息包:heartbeat_age、output_changed,以及工作器输出的尾部内容。请作出判断——heartbeat_age是主要信号:- 心跳较新(< 约 5 分钟)→ 再次运行完全相同的命令。这会再授予一个轮询区间。这是工作器运行正常时的默认做法——较长的实现轮次确实可能需要多个区间;绝不能仅仅因为时间已过就判定运行正常的工作器失败。轮次进行中
output_changed=empty属于正常情况(工作器输出只会在轮次结束时写入),不能据此判定卡住。 - **心跳过期(> 约 10 分钟,且 W_STATE 仍为 running)→ 工作器已卡死。**执行步骤 C,并引用该信息包输出失败结果。连续多个区间中
output_changed=no只能作为佐证,单独不足以判定失败。
- 心跳较新(< 约 5 分钟)→ 再次运行完全相同的命令。这会再授予一个轮询区间。这是工作器运行正常时的默认做法——较长的实现轮次确实可能需要多个区间;绝不能仅仅因为时间已过就判定运行正常的工作器失败。轮次进行中
POLL_RESULT=ceiling_timeout(退出码 1)→ 已达到硬性上限(每轮默认 4 小时);脚本已停止当前工作器。执行步骤 C,然后输出失败结果。
每次调用都会按设计在 2 分钟内返回,因此绝不会转入后台。绝不能手动编写轮询循环、设置较长的 Bash timeout、将调用放到后台、等待通知,或在工作器轮次仍在进行时结束本轮——这些行为都会使运行正常的工作器被弃置,导致任务失败。
步骤 C:始终执行清理 + 恢复收据
在每种终端结果之前执行此步骤,包括成功、部分成功、受阻或失败。 它会停止所有已启动的工作进程,同时保留已推送的 Git 检查点。它还会 输出下次调用可用于协调的确切远程 HEAD。
for WORKER_ID in "${WID:-}" "${RWID:-}"; do
[ -z "$WORKER_ID" ] || curl -s --max-time 30 -X POST \
-H "Authorization: Bearer $PYLOT_DISPATCH_TOKEN" \
"${PYLOT_API}/missions/${PYLOT_JOB_ID}/workers/${WORKER_ID}/stop" >/dev/null 2>&1 || true
done
if [ -n "${BRANCH:-}" ]; then
SAVED_HEAD=$(gh api "repos/$REPO/git/ref/heads/$BRANCH" --jq '.object.sha' 2>/dev/null || true)
[ -z "$SAVED_HEAD" ] || echo "[speckit-runner] resume branch=$BRANCH head=$SAVED_HEAD"
fi
生产者失败仍按常规结果标记为失败或受阻,但必须在执行完步骤 C 之后。 审查者失败则不同:只停止审查者,将审查记录为不可用,并继续使用生产者检查点。
步骤 2:speckit.preflight — 预检 + 规范说明
将提示词加入队列,然后按 步骤 P 轮询:运行 bash ~/.claude/skills/speckit-runner/poll-worker.sh "$WID" "$TURN_SEQ",当 POLL_RESULT=running 时重复运行。
PROMPT=$(python3 -c "import json,sys; print(json.dumps('You are a worker running inside repo $REPO. Issue: #$0.\n\nPre-Flight (MANDATORY — do this FIRST):\n1. Fetch issue: gh issue view $0 --repo $REPO --json title,body,labels,comments\n2. Check if closed: if CLOSED, emit [pylot:$PYLOT_OUTCOME_NONCE] outcome=\"already complete\" status=success and exit.\n3. Verify required labels exist (create '\''in-progress'\'' if missing).\n4. Gather real data: read issue comments, fetch referenced URLs, read existing code patterns.\n\nResume or Specify:\n5. Fetch origin and inspect remote branches whose names identify issue $0. Resume only when exactly one candidate has an existing checkpoint/spec matching this issue; otherwise update the default branch and start clean. Never create or duplicate a PR in this phase.\n6. Bootstrap speckit scaffolding if absent: if [ ! -f \".specify/scripts/bash/create-new-feature.sh\" ]; then /setup-speckit; fi\n7. If the resumed branch already has a valid specification for this issue, continue it. Otherwise run: /speckit-specify $0\n8. Read the generated specification. If there are open questions, answer them from pre-flight data, then run /speckit-clarify.\n9. Set BRANCH=\$(git branch --show-current), commit any completed specification checkpoint, and push the branch.\n\nWhen done: emit [pylot] phase=preflight status=done branch=\$BRANCH head=\$(git rev-parse HEAD)'))")
PROMPT_RESP=$(curl -s --max-time 30 -X POST \
-H "Authorization: Bearer $PYLOT_DISPATCH_TOKEN" \
-H "Content-Type: application/json" \
-d "{\"prompt\": $PROMPT}" \
"${PYLOT_API}/missions/${PYLOT_JOB_ID}/workers/${WID}/prompt")
TURN_SEQ=$(echo "$PROMPT_RESP" | python3 -c 'import sys,json; print(json.load(sys.stdin).get("turn_seq",""))' 2>/dev/null)
echo "[speckit-runner] preflight prompt queued (turn_seq=$TURN_SEQ)"
现在按 步骤 P 轮询——运行 bash ~/.claude/skills/speckit-runner/poll-worker.sh "$WID" "$TURN_SEQ",并在 POLL_RESULT=running 时重复运行。当输出 POLL_RESULT=done 时,检查输出的工作进程结果中是否包含 phase=preflight status=done。如果没有,或 status=failed,则运行步骤 C 并报告失败结果。
预检成功后,获取 last_output,从其标记中设置 BRANCH,
并验证输出的 HEAD 与远程 HEAD 一致。后续每次步骤 C 收据都保留 BRANCH。
步骤 3:speckit.plan — 计划 + 任务
将提示词加入队列,然后按 步骤 P 轮询:运行 bash ~/.claude/skills/speckit-runner/poll-worker.sh "$WID" "$TURN_SEQ",当 POLL_RESULT=running 时重复运行。
PROMPT=$(python3 -c "import json; print(json.dumps('Continue on the feature branch from the previous phase.\nRun: /speckit-plan $0\nRead the generated plan and verify the approach.\nRun: /speckit-tasks $0\nRead the generated tasks and verify they are concrete.\n\nDerive the durable invariant matrix described by ~/.claude/skills/speckit-runner/references/invariant-matrix.md from issue, spec, tasks, and trusted repository evidence. Inspect every discovery class, emit every source-backed boundary and no row for an unsupported class, and persist specs/<feature>/invariant-matrix.tsv with stable IDs and initial states. Commit and push the completed planning checkpoint so a later invocation can resume it.\nWhen done: emit [pylot] phase=plan status=done branch=\$(git branch --show-current) head=\$(git rev-parse HEAD), followed by MATRIX RECEIPT path=<path> repository=$REPO checkpoint=\$(git rev-parse HEAD)'))")
PROMPT_RESP=$(curl -s --max-time 30 -X POST \
-H "Authorization: Bearer $PYLOT_DISPATCH_TOKEN" \
-H "Content-Type: application/json" \
-d "{\"prompt\": $PROMPT}" \
"${PYLOT_API}/missions/${PYLOT_JOB_ID}/workers/${WID}/prompt")
TURN_SEQ=$(echo "$PROMPT_RESP" | python3 -c 'import sys,json; print(json.load(sys.stdin).get("turn_seq",""))' 2>/dev/null)
echo "[speckit-runner] plan prompt queued (turn_seq=$TURN_SEQ)"
现在按 步骤 P 轮询——运行 bash ~/.claude/skills/speckit-runner/poll-worker.sh "$WID" "$TURN_SEQ",并在 POLL_RESULT=running 时重复运行。当输出 POLL_RESULT=done 时,检查输出的工作进程结果中是否包含 phase=plan status=done。如果受阻或失败,则在报告受阻或失败结果之前运行步骤 C。
成功后,验证计划标记中的分支和 HEAD 是否与同一远程分支一致。这可证明恢复检查点已推进,然后才能开始实施。
步骤 4:speckit.implement — 实施 + 验证 + 检查点
生产者实施并推送一个可供审查的检查点,但此时不要创建 PR。将此提示词加入队列,并按 步骤 P 轮询。
PROMPT=$(python3 -c "import json; print(json.dumps('Continue on the feature branch. Run: /speckit-implement $0\nAfter implementation:\n- Run the repository-defined verification for the affected behavior and fix failures.\n- Exercise the changed behavior through the strongest practical interface available in this repository.\n- Run: /speckit-analyze $0 && /speckit-checklist $0; resolve concrete gaps they find.\n- Commit every intended implementation and specification change, then push the current branch.\n- Do NOT create a PR. This pushed head is the independent-review checkpoint.\nWhen done, emit one marker line: [pylot] phase=implement status=done branch=\$(git branch --show-current) head=\$(git rev-parse HEAD). Then provide a concise CHECKPOINT EVIDENCE block containing the exact verification commands, pass/fail results, behavior exercised, and evidence asset ids or receipts (or explicit N/A).'))")
PROMPT_RESP=$(curl -s --max-time 30 -X POST \
-H "Authorization: Bearer $PYLOT_DISPATCH_TOKEN" -H "Content-Type: application/json" \
-d "{\"prompt\": $PROMPT}" \
"${PYLOT_API}/missions/${PYLOT_JOB_ID}/workers/${WID}/prompt")
TURN_SEQ=$(echo "$PROMPT_RESP" | python3 -c 'import sys,json; print(json.load(sys.stdin).get("turn_seq",""))' 2>/dev/null)
echo "[speckit-runner] implement prompt queued (turn_seq=$TURN_SEQ)"
轮询至 done,然后重新获取工作进程结果并提取输出的分支。
确认远程分支指向输出的 HEAD 后再继续。
PRODUCER_STATE=$(curl -s --max-time 20 -H "Authorization: Bearer $PYLOT_DISPATCH_TOKEN" \
"${PYLOT_API}/missions/${PYLOT_JOB_ID}/workers/${WID}")
PRODUCER_OUT=$(printf '%s' "$PRODUCER_STATE" | jq -r '.last_output // ""')
BRANCH=$(printf '%s' "$PRODUCER_OUT" | sed -n 's/.*phase=implement status=done branch=\([^ ]*\).*/\1/p' | tail -1)
HEAD_SHA=$(printf '%s' "$PRODUCER_OUT" | sed -n 's/.*phase=implement status=done.* head=\([0-9a-f]*\).*/\1/p' | tail -1)
REMOTE_SHA=$(gh api "repos/$REPO/git/ref/heads/$BRANCH" --jq '.object.sha')
test -n "$BRANCH" && test "$HEAD_SHA" = "$REMOTE_SHA"
如果缺少标记或远程 HEAD 不匹配,则运行步骤 C 并报告生产者失败。
CHECKPOINT EVIDENCE 可作为有用的上下文,但绝不能作为验证收据:生产者可以报告它,但它不能证明检查通过。
第 4.5 步:由监督者负责验证
由操作者而非产出者,在不可变的已推送 HEAD_SHA 上,使用产出者无法编辑的分离检出,发现并执行预期检查:
SUPERVISOR_CHECKOUT=$(mktemp -d)
git fetch origin "$BRANCH"
git worktree add --detach "$SUPERVISOR_CHECKOUT" "$REMOTE_SHA"
在该分离检出中执行仓库规定的验证检查发现。阅读任务/规范,以及仓库自身的贡献者、Agent、构建、CI 和测试说明。找出这些来源要求本次更改执行的每一项检查。不要在这个共享技能中硬编码语言、框架、惯用文件名或命令集;不要根据产出者声称“测试通过”来编造检查。必须在监督者检出中亲自执行发现的每条命令,绝不能通过提示产出者来执行。
加载检查点的 specs/<feature>/invariant-matrix.tsv,并根据 references/invariant-matrix.md 对其进行验证。只有监督者负责协调各行状态:凡是仓库或完整检查点与已审阅检查点不同的已执行收据,均标记为 stale;然后将每项新鲜的观测检查关联到相应行,并标记为 passed、failed 或 unavailable。产出者摘要、审阅者陈述或仅有叙述的结果,绝不能将状态设为 passed。在 SUPERVISOR_VERIFICATION 中包含协调后的矩阵路径和各行状态。
协调后的矩阵是由监督者持有的序列化产物,不是在分离检出中的编辑。将其完整 TSV 内容设为 SUPERVISOR_MATRIX,并将 SUPERVISOR_MATRIX_RECEIPT 设为 repository=$REPO checkpoint=$REMOTE_SHA;每次使用前都要验证这两个标识。将这两个值原样嵌入审阅、更正、最终审阅、恢复和 PR 提示词中。分支副本仍是规划记录:产出者不得协调其状态,也不得用摘要替换此产物。恢复时,在任何使用者使用先前证据之前,先在已协调的远程头提交处重建此产物。
随后编写纯 Markdown 验证摘要——每项预期检查占一行,包含检查标识、原样命令及其观测结果(仅当退出状态为 0 时标记为 passed;否则标记为 failed 并注明实际退出状态、not-run 并说明原因,或标记为 unavailable 并指出具体缺失的运行时环境/凭据):
SUPERVISOR_VERIFICATION="verified at head $REMOTE_SHA:
- <check id>: <command> — <passed|failed (exit N)|not-run: reason|unavailable: reason>
..."
确保审阅、更正和 PR 步骤都能访问此摘要。会话的 EXIT 陷阱会在任何终止路径中移除分离工作树。
第 5 步:独立的咨询性审阅
在同一仓库中启动第二个 LLM 工作进程。它会收到 issue、分支、差异、监督者验证摘要和拟提交的 PR 声明——绝不会收到产出者的理由。它从干净检出开始;其发现仅供参考。
FIRST_REVIEW_STATUS="unavailable"
REVIEW_OUT="Independent review unavailable: reviewer did not start."
REVIEW_SPAWN=$(curl -s --max-time 90 -X POST \
-H "Authorization: Bearer $PYLOT_DISPATCH_TOKEN" -H "Content-Type: application/json" \
-d "{\"repo\": \"$REPO\"}" \
"${PYLOT_API}/missions/${PYLOT_JOB_ID}/workers")
RWID=$(printf '%s' "$REVIEW_SPAWN" | jq -r '.worker_id // empty')
if [ -n "$RWID" ]; then
REVIEW_PROMPT=$(BRANCH="$BRANCH" SUPERVISOR_VERIFICATION="$SUPERVISOR_VERIFICATION" SUPERVISOR_MATRIX="$SUPERVISOR_MATRIX" SUPERVISOR_MATRIX_RECEIPT="$SUPERVISOR_MATRIX_RECEIPT" python3 -c "import json,os; print(json.dumps('Independently review issue #$0 in $REPO and pushed branch ' + os.environ['BRANCH'] + ' from a clean checkout. Read the issue, spec, tasks, trusted repository guidance, merge-base diff, and supervisor-owned matrix and receipts below. Do not request or use producer rationale. Do not edit, commit, push, or create a PR.\n\nAttempt to falsify every applicable invariant row by ID. For each surviving row emit exactly: ROW INV-NNN verdict=no-findings. For each challenge emit exactly one line: FINDING F-NNN row_id=INV-NNN priority=P0..P3 status=open evidence=<concrete evidence> suggested_action=<action>. A source-backed omission uses row_id=OMITTED. Cover every applicable row exactly once; unsupported classes need no row. Findings are advisory.\n\nSUPERVISOR MATRIX RECEIPT:\n' + os.environ['SUPERVISOR_MATRIX_RECEIPT'] + '\nSUPERVISOR MATRIX:\n' + os.environ['SUPERVISOR_MATRIX'] + '\nSUPERVISOR VERIFICATION:\n' + os.environ['SUPERVISOR_VERIFICATION'] + '\n\nEnd with [pylot] phase=independent-review status=done actionable=yes|no.'))")
REVIEW_RESP=$(curl -s --max-time 30 -X POST \
-H "Authorization: Bearer $PYLOT_DISPATCH_TOKEN" -H "Content-Type: application/json" \
-d "{\"prompt\": $REVIEW_PROMPT}" \
"${PYLOT_API}/missions/${PYLOT_JOB_ID}/workers/${RWID}/prompt")
REVIEW_SEQ=$(printf '%s' "$REVIEW_RESP" | jq -r '.turn_seq // empty')
if [ -z "$REVIEW_SEQ" ]; then
REVIEW_OUT="Independent review unavailable: reviewer prompt was not accepted."
curl -s --max-time 20 -X POST -H "Authorization: Bearer $PYLOT_DISPATCH_TOKEN" \
"${PYLOT_API}/missions/${PYLOT_JOB_ID}/workers/${RWID}/stop" >/dev/null 2>&1 || true
RWID=""
fi
fi
当存在 RWID 时,使用专用的有界辅助程序轮询——不要使用步骤 P:
bash ~/.claude/skills/speckit-runner/poll-reviewer.sh "$RWID" "$REVIEW_SEQ"
仅当其最后一行为 REVIEW_POLL_RESULT=running 时才重新运行。每次调用都很短,累计审阅时长上限为 15 分钟。done 表示可以进行下方的输出检查。unavailable 表示辅助程序已停止审阅者:将 REVIEW_OUT 设为不可用原因,保留 FIRST_REVIEW_STATUS=unavailable,将 RWID="",因为辅助程序已将其停止,然后继续执行步骤 6。绝不要通过步骤 P 的产出者失败/任务停止处理流程传递审阅者结果。
if [ -n "$RWID" ]; then
REVIEW_STATE=$(curl -s --max-time 20 -H "Authorization: Bearer $PYLOT_DISPATCH_TOKEN" \
"${PYLOT_API}/missions/${PYLOT_JOB_ID}/workers/${RWID}")
REVIEW_OUT=$(printf '%s' "$REVIEW_STATE" | jq -r '.last_output // ""')
REVIEW_FILE=$(mktemp)
MATRIX_FILE=$(mktemp)
printf '%s\n' "$REVIEW_OUT" >"$REVIEW_FILE"
printf '%s\n' "$SUPERVISOR_MATRIX" >"$MATRIX_FILE"
if bash ~/.claude/skills/speckit-runner/validate-review-output.sh "$MATRIX_FILE" "$REVIEW_FILE"; then
FIRST_REVIEW_STATUS="available"
else
FIRST_REVIEW_STATUS="unavailable"
REVIEW_OUT=$(printf 'Independent review unavailable: reviewer output was incomplete or malformed.\nPreserved raw output below is advisory only; do not treat it as complete coverage.\n\n%s\n' "$REVIEW_OUT")
curl -s --max-time 20 -X POST -H "Authorization: Bearer $PYLOT_DISPATCH_TOKEN" \
"${PYLOT_API}/missions/${PYLOT_JOB_ID}/workers/${RWID}/stop" >/dev/null 2>&1 || true
RWID=""
fi
rm -f "$REVIEW_FILE" "$MATRIX_FILE"
fi
第 6 步:产出者仅进行一轮更正
只向产出者发送一次独立建议。产出者必须记录其所做的更改及拒绝任何建议的原因;不得无限循环。
CORRECTION_PROMPT=$(REVIEW_OUT="$REVIEW_OUT" SUPERVISOR_MATRIX="$SUPERVISOR_MATRIX" SUPERVISOR_MATRIX_RECEIPT="$SUPERVISOR_MATRIX_RECEIPT" python3 -c "import json,os; print(json.dumps('''This is the one bounded correction pass before PR creation. Independently assess the review suggestions below against the issue and repository. Implement the high-value valid corrections; decline inapplicable or disproportionate suggestions with a concrete reason. Preserve every invariant row ID, evidence state, receipt, and finding ID; record each finding as resolved or declined with its disposition. The supervisor-owned matrix below is read-only producer context. If the pushed head changes, treat all unmatched executed matrix receipts as stale pending fresh supervisor verification; producer prose must not restore passed. Re-run the repository-defined verification plus /speckit-analyze $0 and /speckit-checklist $0, commit any changes, and push the branch. Do not create a PR yet. Emit [pylot] phase=correction status=done branch=\$(git branch --show-current) head=\$(git rev-parse HEAD), followed by a concise CORRECTION SUMMARY that lists each suggestion as resolved or declined with rationale and records the latest verification results.
SUPERVISOR MATRIX RECEIPT:
''' + os.environ['SUPERVISOR_MATRIX_RECEIPT'] + '''
SUPERVISOR MATRIX:
''' + os.environ['SUPERVISOR_MATRIX'] + '''
INDEPENDENT REVIEW:
''' + (os.environ.get('REVIEW_OUT') or 'Review unavailable; verify the checkpoint yourself and report that independent review evidence was unavailable.'))) ")
CORRECTION_RESP=$(curl -s --max-time 30 -X POST \
-H "Authorization: Bearer $PYLOT_DISPATCH_TOKEN" -H "Content-Type: application/json" \
-d "{\"prompt\": $CORRECTION_PROMPT}" \
"${PYLOT_API}/missions/${PYLOT_JOB_ID}/workers/${WID}/prompt")
TURN_SEQ=$(printf '%s' "$CORRECTION_RESP" | jq -r '.turn_seq // empty')
轮询产出者,直到状态为 done。即使仍有建议,这也只能是唯一的一轮更正。捕获并验证其收据:
CORRECTION_STATE=$(curl -s --max-time 20 -H "Authorization: Bearer $PYLOT_DISPATCH_TOKEN" \
"${PYLOT_API}/missions/${PYLOT_JOB_ID}/workers/${WID}")
CORRECTION_OUT=$(printf '%s' "$CORRECTION_STATE" | jq -r '.last_output // ""')
if ! printf '%s' "$CORRECTION_OUT" | grep -q 'phase=correction status=done' \
|| ! printf '%s' "$CORRECTION_OUT" | grep -q 'CORRECTION SUMMARY'; then
CORRECTION_VALID="no"
else
CORRECTION_HEAD=$(printf '%s' "$CORRECTION_OUT" | sed -n 's/.*phase=correction status=done.* head=\([0-9a-f]*\).*/\1/p' | tail -1)
CORRECTION_REMOTE_HEAD=$(gh api "repos/$REPO/git/ref/heads/$BRANCH" --jq '.object.sha' 2>/dev/null || true)
if [ -n "$CORRECTION_HEAD" ] && [ "$CORRECTION_HEAD" = "$CORRECTION_REMOTE_HEAD" ]; then
CORRECTION_VALID="yes"
else
CORRECTION_VALID="no"
fi
fi
如果 CORRECTION_VALID=no,运行步骤 C,然后使用已保存的分支收据发出产出者失败通知。不要继续审阅或创建 PR。
步骤 6.5:修正后重新验证
修正可能会推送新的 HEAD。切勿将 passed 结果沿用到该节点之后。如果 CORRECTION_HEAD 与步骤 4.5 验证过的 HEAD 不同,请丢弃旧摘要,并在 CORRECTION_HEAD 重新执行步骤 4.5 中由仓库负责的发现和监督器执行:
if [ "$CORRECTION_HEAD" != "$REMOTE_SHA" ]; then
echo '[speckit-runner] correction moved HEAD; re-verifying at the new head'
cleanup_supervisor_checkout
SUPERVISOR_CHECKOUT=$(mktemp -d)
git fetch origin "$BRANCH"
git worktree add --detach "$SUPERVISOR_CHECKOUT" "$CORRECTION_HEAD"
cd "$SUPERVISOR_CHECKOUT"
# Repeat Step 4.5 discovery and execution here, then rebuild
# SUPERVISOR_VERIFICATION with "verified at head $CORRECTION_HEAD:".
# Rebuild SUPERVISOR_MATRIX from those observations and set
# SUPERVISOR_MATRIX_RECEIPT="repository=$REPO checkpoint=$CORRECTION_HEAD".
fi
如果新检查失败、未运行或不可用,请继续。这些检查的确切状态必须保留在最终摘要中;它们都不会阻止创建 PR。
步骤 7:可选的最终建议审查
如果首次审查报告 actionable=yes,或者修正更改了已推送的 HEAD,请再次提示审查者检查更新后的远程 HEAD。只询问最初提出的问题中哪些已解决,以及哪些建议仍未解决。不要再启动一轮修正。如果首次审查没有可执行的建议,请设置 RESIDUAL_REVIEW="No residual suggestions." 并跳过此轮。
最终审查仍仅供参考。请保留未解决的建议以便披露;切勿仅因建议仍未解决就将其转为任务阻碍。
RUN_FINAL_REVIEW="no"
if printf '%s' "$REVIEW_OUT" | grep -q 'actionable=yes' \
|| { [ -n "${CORRECTION_HEAD:-}" ] && [ "$CORRECTION_HEAD" != "$HEAD_SHA" ]; }; then
RUN_FINAL_REVIEW="yes"
fi
if [ "$FIRST_REVIEW_STATUS" = "available" ] && [ -n "$RWID" ] \
&& [ "$RUN_FINAL_REVIEW" = "yes" ]; then
FINAL_REVIEW_PROMPT=$(CORRECTION_OUT="$CORRECTION_OUT" SUPERVISOR_VERIFICATION="$SUPERVISOR_VERIFICATION" SUPERVISOR_MATRIX="$SUPERVISOR_MATRIX" SUPERVISOR_MATRIX_RECEIPT="$SUPERVISOR_MATRIX_RECEIPT" python3 -c "import json,os; print(json.dumps('Re-fetch branch $BRANCH and review its updated diff for issue #$0. Reassess only your original F-NNN findings against the correction summary and the supervisor-owned matrix at its exact receipt. Preserve finding IDs and report each as resolved, declined, or still open with evidence; do not erase unavailable review or non-passing row states. Do not edit, push, or create a PR. Remaining suggestions are advisory. End with [pylot] phase=final-review status=done.\n\nSUPERVISOR MATRIX RECEIPT:\n' + os.environ['SUPERVISOR_MATRIX_RECEIPT'] + '\nSUPERVISOR MATRIX:\n' + os.environ['SUPERVISOR_MATRIX'] + '\nPRODUCER CORRECTION SUMMARY:\n' + os.environ['CORRECTION_OUT'] + '\n\nCURRENT SUPERVISOR VERIFICATION:\n' + os.environ['SUPERVISOR_VERIFICATION']))")
FINAL_REVIEW_RESP=$(curl -s --max-time 30 -X POST \
-H "Authorization: Bearer $PYLOT_DISPATCH_TOKEN" -H "Content-Type: application/json" \
-d "{\"prompt\": $FINAL_REVIEW_PROMPT}" \
"${PYLOT_API}/missions/${PYLOT_JOB_ID}/workers/${RWID}/prompt")
REVIEW_SEQ=$(printf '%s' "$FINAL_REVIEW_RESP" | jq -r '.turn_seq // empty')
if [ -z "$REVIEW_SEQ" ]; then
RESIDUAL_REVIEW="Final advisory review unavailable: prompt was not accepted."
curl -s --max-time 20 -X POST -H "Authorization: Bearer $PYLOT_DISPATCH_TOKEN" \
"${PYLOT_API}/missions/${PYLOT_JOB_ID}/workers/${RWID}/stop" >/dev/null 2>&1 || true
RWID=""
fi
else
RESIDUAL_REVIEW="No final review run: first review unavailable or had no actionable suggestions."
if [ -n "${RWID:-}" ]; then
curl -s --max-time 20 -X POST -H "Authorization: Bearer $PYLOT_DISPATCH_TOKEN" \
"${PYLOT_API}/missions/${PYLOT_JOB_ID}/workers/${RWID}/stop" >/dev/null 2>&1 || true
RWID=""
fi
fi
当 REVIEW_SEQ 存在时,再次使用 poll-reviewer.sh,且仅在 REVIEW_POLL_RESULT=running 时重新运行。若结果为 unavailable,请设置简洁的不可用原因,清除 RWID,然后继续。若结果为 done,请获取输出,且仅当输出包含 phase=final-review status=done 时才接受;否则将其标记为不可用。捕获有效的最终输出后,立即停止审查者。此等待的累计上限同样为 15 分钟,且绝不会导致任务失败。
if [ -n "${RWID:-}" ] && [ -n "${REVIEW_SEQ:-}" ]; then
FINAL_REVIEW_STATE=$(curl -s --max-time 20 -H "Authorization: Bearer $PYLOT_DISPATCH_TOKEN" \
"${PYLOT_API}/missions/${PYLOT_JOB_ID}/workers/${RWID}")
FINAL_REVIEW_OUT=$(printf '%s' "$FINAL_REVIEW_STATE" | jq -r '.last_output // ""')
if printf '%s' "$FINAL_REVIEW_OUT" | grep -q 'phase=final-review status=done'; then
RESIDUAL_REVIEW="$FINAL_REVIEW_OUT"
else
RESIDUAL_REVIEW="Final advisory review unavailable: no valid completion marker."
fi
curl -s --max-time 20 -X POST -H "Authorization: Bearer $PYLOT_DISPATCH_TOKEN" \
"${PYLOT_API}/missions/${PYLOT_JOB_ID}/workers/${RWID}/stop" >/dev/null 2>&1 || true
RWID=""
fi
步骤 8:创建唯一的 PR
将最终建议记录发送给执行者。这是整个流程中唯一的 PR 创建节点。
PR_PROMPT=$(SUPERVISOR_VERIFICATION="$SUPERVISOR_VERIFICATION" SUPERVISOR_MATRIX="$SUPERVISOR_MATRIX" SUPERVISOR_MATRIX_RECEIPT="$SUPERVISOR_MATRIX_RECEIPT" FIRST_REVIEW_STATUS="$FIRST_REVIEW_STATUS" REVIEW_OUT="$REVIEW_OUT" CORRECTION_OUT="$CORRECTION_OUT" RESIDUAL_REVIEW="$RESIDUAL_REVIEW" python3 -c "import json,os; print(json.dumps('''Create the single PR for issue #$0 from the already-pushed branch. First confirm the worktree is clean and the remote head matches local HEAD. Invoke /create-compelling-prs and use that skill to compose and open the PR — do not substitute a placeholder template.
Base the PR's Verification section on the supervisor verification summary below. It is authoritative over any producer claim. Consume the supervisor-owned matrix only when its receipt matches the exact current repository/head. Never reconcile or restore execution states yourself. If that identity does not match, disclose executed rows as stale or the matrix as unavailable; do not claim passed evidence for the current head. Disclose every applicable row whose state is failed, not-run, unavailable, or stale; preserve not-applicable rows without presenting them as gaps. Never state or imply readiness when any current check is non-passing. Include an Independent review section summarizing first-review availability, every unresolved or declined F-NNN finding, correction decisions, and residual or unavailable final review. Review suggestions and verification failures are transparent advisory context, not a reason to suppress the PR or create another PR boundary. Emit [pylot] phase=pr status=done pr=<PR_URL>.
CURRENT SUPERVISOR VERIFICATION:
''' + os.environ['SUPERVISOR_VERIFICATION'] + '''
SUPERVISOR MATRIX RECEIPT:
''' + os.environ['SUPERVISOR_MATRIX_RECEIPT'] + '''
SUPERVISOR MATRIX:
''' + os.environ['SUPERVISOR_MATRIX'] + '''
FIRST REVIEW STATUS:
''' + os.environ['FIRST_REVIEW_STATUS'] + '''
FIRST REVIEW:
''' + os.environ['REVIEW_OUT'] + '''
PRODUCER CORRECTION SUMMARY:
''' + os.environ['CORRECTION_OUT'] + '''
FINAL ADVISORY REVIEW:
''' + (os.environ.get('RESIDUAL_REVIEW') or 'Final advisory review unavailable.'))) ")
PR_RESP=$(curl -s --max-time 30 -X POST \
-H "Authorization: Bearer $PYLOT_DISPATCH_TOKEN" -H "Content-Type: application/json" \
-d "{\"prompt\": $PR_PROMPT}" \
"${PYLOT_API}/missions/${PYLOT_JOB_ID}/workers/${WID}/prompt")
TURN_SEQ=$(printf '%s' "$PR_RESP" | jq -r '.turn_seq // empty')
轮询执行者,直到其状态为 done;从其输出中找到 PR URL,并使用 gh pr view 确认其头部分支和问题关联与本次运行相符。
任何执行者提示、轮询、标记或协调失败,都必须在输出最终结果前执行步骤 C。
ISSUE_NUMBER="$0"
source ~/.claude/skills/speckit-runner/shared/pr-postcondition.sh || {
echo '[speckit-runner] fatal: pr-postcondition.sh not found — boot sync may be incomplete' >&2
exit 1
}
verify_pr_postcondition || { echo '[speckit-runner] PR postcondition failed' >&2; exit 1; }
步骤 9:清理并输出结果
无条件执行步骤 C,然后报告 PR 和独立审查的可用性。如果 PR 已存在,未解决或不可用的建议应产生 status=success,而不是 failed 或 blocked。
# Run the Step C snippet first. The session EXIT trap removes
# SUPERVISOR_CHECKOUT on this and every other terminal path.
if [ -n "$PR_NUM" ]; then
echo "[speckit-runner] complete: PR #$PR_NUM opened; independent_review=$FIRST_REVIEW_STATUS"
else
echo "[speckit-runner] verified checkpoint exists but no PR URL was confirmed — check worker output"
fi
报告命令执行后,输出一个且仅一个已解析标记,作为助手最后一整行(不能来自 Bash,也不能放在代码围栏内):
- 已确认 PR:[pylot:$PYLOT_OUTCOME_NONCE] outcome="speckit complete: PR #$PR_NUM opened; independent_review=$FIRST_REVIEW_STATUS" status=success
- 没有 PR URL:[pylot:$PYLOT_OUTCOME_NONCE] outcome="verified checkpoint exists but no PR URL was confirmed — check worker output" status=partial
硬性规则
- 必须进行预检——工作者必须先收集真实数据,然后才能进入 speckit 阶段
- 仅通过脚本轮询——生产者轮次使用
poll-worker.sh;审查者轮次使用有界的poll-reviewer.sh。每次调用均会在 2 分钟内返回。切勿自行编写循环、传入较长的 Bash 超时、在后台轮询、等待通知,或在工作者轮次仍在执行时结束操作员轮次。 - 切勿因计时器而停止正常工作的工作者——
block_elapsed是检查点,不代表失败。仅当工作者的心跳已过期(> 约 10 分钟)或工作者报告失败时,才停止它;绝不能仅因经过的时间或轮次中途没有输出而停止。只有脚本会强制执行硬性上限。 - 始终执行清理——所有终止路径都必须运行 Step C;审查者不可用时,立即停止该审查者并继续
- 外部派发仍采用发后不管模式——前台轮询属于内部任务执行,并不意味着派发器要看管任务
- 在干净的上下文中审查——生产者不得审查自己的检查点;审查者不得编辑该检查点
- 验证由监督器负责——只能从任务和仓库自身的说明中发现命令;在分离的监督器检出环境中,针对准确推送的提交头执行这些命令,并如实报告观察到的结果。生产者的文字描述绝不能凭空生成通过结果。
- 仓库信任边界——
/speckit-runner只能用于贡献者、Agent 和 CI 说明文件与派发令牌具有相同可信级别的仓库。监督器会在自己的会话中执行从仓库发现的命令(持有$PYLOT_DISPATCH_TOKEN、$PYLOT_API和gh凭据);目标仓库中恶意或遭入侵的说明文件可能会窃取这些凭据。 - 建议绝不构成阻断条件——允许生产者进行一轮修正,披露所有残留问题,然后继续到 PR 阶段
- PR 只能在最后创建一次——验证、分析/检查清单、推送检查点和咨询式审查都必须先于
/create-compelling-prs - 输出结果标记——退出前,
[pylot:$PYLOT_OUTCOME_NONCE] outcome=... status=必须是助手最后一行完整内容 - “已完成”仅用于去重关卡——只有当问题确实为 CLOSED 时(Step 0)才输出此结果;绝不能用于超时或缺少通知的情况
- 一项任务对应一个 PR——不要将范围扩展到相邻问题