mcpField
S
speckit-runner

speckit-runner

github.com/fellowship-dev开发3221 次安装2 周前更新
代码仓库

关于 speckit-runner

speckit-runner 是由操作员驱动的 speckit 流程技能,通过工作器处理议题,并在创建 PR 前将已推送的检查点交给独立审查者。它提供议题与 PR 状态协调、工作器轮询和清理,以及不变量矩阵和审查覆盖率验证。

主要功能

  1. 01议题与开放 PR 状态协调
  2. 02由工作器执行 speckit 阶段
  3. 03内置工作器轮询脚本
  4. 04不变量矩阵结构验证
  5. 05审查输出覆盖率验证
  6. 06工作器清理与检查点记录

适用场景

  1. 01为仓库议题运行 speckit 阶段
  2. 02从现有检查点或 PR 恢复流程
  3. 03创建 PR 前核验不变量计划和审查覆盖率

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/fellowship-dev/dogfooded-skills --skill speckit-runner

安装后在 Claude Code 中输入/speckit-runner确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

poll-reviewer.sh
poll-worker.sh
validate-invariant-matrix.sh
validate-review-output.sh
references/
invariant-matrix.md
shared/
pr-postcondition.sh
tests/
invariant-matrix.test.sh
pr-postcondition.test.sh
tests/fixtures/invariant-matrix/
adversarial-fixture.sh
discovery.tsv
feature-matrix.tsv
lifecycle.tsv
review.tsv
schema.tsv
tests/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 绝不能设置较长的 Bash timeout,绝不能将其置于后台,绝不能“等待通知”,也绝不能在工作进程仍在执行时结束当前轮次。如果步骤 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-NNN ID、允许的 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 只能作为佐证,单独不足以判定失败。
  • 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——不要将范围扩展到相邻问题

评论

支持 Markdown