Files
octopus-workflow/core/skills/_shared/review-pipeline-phases.md
T

28 KiB
Raw Blame History

Core 中立版(Increment 6a 改写,原 deferHard verbatim)。编号与条目结构严格不变(C-2 不变量);实例术语按 core/adapters/TERMINOLOGY.md 绑定。

Review Pipeline Phases (Canonical)

This document is the single source of truth for the shared Phase A→E review/audit pipeline flow. Individual review and audit skills bind their own parameters and reference this doc rather than duplicating the shared mechanics.

Terminology: "reviewer" and "auditor" are interchangeable — audit-process legitimately uses "auditor"/"Re-audit" terminology. All generic descriptions use {REVIEWER_OR_AUDITOR}.

Output model — two findings sinks ([org-internal #1988] Tier 1 migration): raw per-dimension reviewer findings have ONE of two sinks, chosen per skill. The synthesis (human gate) and commit status are ALWAYS Tier 2 (Gitea) regardless of the findings sink.

Sink Skills Reviewer writes findings to Synthesizer reads findings from
tier1-local (migrated) review-code, review-artifact (live targets: review-dag, audit-process; the roadmap/design-space/plan targets were archived 2026-08-21, [org-internal #3072] phase 3) <runs-root>/{slug}/reviews/{stage}/round{N}/findings-{DIMENSION_CODE}.json (validated by core/schemas/reviewer-output.schema.json) the local finding files (no Gitea call)
tier2-comment (fallback) (none — all targets migrated) Gitea PR/issue comment via 工单评论 API(见 TERMINOLOGY (Pattern 5) 工单评论 API(见 TERMINOLOGY (Pattern 3)

Migration complete — every review/audit skill now outputs tier1-local; the tier2-comment row remains as the documented fallback (boundary rule: core/rules/two-tier-artifacts.md; a skill without <runs-root>/ write access falls back to comment-based output unchanged). A tier1-local reviewer additionally appends an index.json row per finding file (Tier 1 manifest, see core/templates/runs-layout.md).

Tier 2 outputs (always): synthesis is posted as a Gitea PR/issue comment with a fenced ```json block — except for the audit-process target, which has no source issue and posts the synthesis as a wiki page audit/{date}/round{N}/synthesis instead (see review-artifact/SKILL.md process target Output Artifacts); status tracking uses REST commit status (pipeline/{REVIEW_TYPE} context). The full Gitea output pattern is documented in _shared/gitea-write-patterns.md.


Phase A — Parallel {REVIEWER_OR_AUDITOR_NAME}

Orchestrator actions:

  1. Move the source issue to the review column on the Pipeline Stages board per _shared/gitea-write-patterns.md Pattern 7.5. Skip if no source issue exists. The review stage is a single board column covering all review targets (DAG single gate / code review / audit-process).

0.5. Initialize the review-status file (round 1 only). The Orchestrator creates <runs-root>/{slug}/reviews/{stage}/status.json before dispatching round 1, with the full schema-required field set (core/schemas/review-status.schema.json): slug, started_at (RFC 3339 timestamp), current_round: 1, max_rounds (per-skill binding — review-artifact = 3, review-code = 2 default / 3 high-risk — <instance-root>/workflow-routing.yaml review.max_rounds), converged: false, history: []. This step is skipped on rounds ≥ 2 — the file already exists and the Synthesizer appends to it in Phase B. Initialization is the Orchestrator's sole responsibility; the Synthesizer never creates the file. The Orchestrator also appends an index.json row for the file (type = review-status, stage = {REVIEW_TYPE}, produced_by = Orchestrator, schema = the review-status $id).

  1. Tool-availability probe (defense-in-depth, [org-internal #2115] RC1 / [org-internal #2126]). Before dispatching any {REVIEWER_OR_AUDITOR}, verify reviewers will be able to read wiki artifacts (requirements / design / plan / prior-review reports). A reviewer that cannot read its inputs aborts or hallucinates — catch this ONCE in the Orchestrator instead of letting {DIMENSION_COUNT} parallel subagents each rediscover the gap:

    • Primary path: confirm the Gitea REST API is reachable — probe GET <gitea-base-url>/api/v1/repos/{owner}/{repo}/wiki/pages with -H "Authorization: token <token>" (a 200 or 403 response means reachable; anything else — timeout, connection refused, 5xx — is unreachable). If reachable → continue to step 2.
    • Fallback path (when REST is unreachable, e.g. a cross-repo scenario [org-internal #2115]): verify the local wiki clone at <wiki-clone-root>/{repo}-wiki/ is readable — glob(pattern="*.md", path="<wiki-clone-root>/{repo}-wiki/"). If the clone is readable → continue to step 2; reviewers use the local clone (see _shared/gitea-read-patterns.md Pattern 1 fallback note and the "Tool Availability & Path Resolution" section of this skill).
    • Both unavailable → FAIL FAST: post a failure commit status (state "failure", context pipeline/{REVIEW_TYPE}, description naming the missing capability) and abort — do NOT enter parallel dispatch. Report the gap so the environment can be fixed (restore Gitea REST reachability — token validity, network route — or populate <wiki-clone-root>/{repo}-wiki/).

    This probe is belt-and-suspenders: it exists so a future environment regression (REST unreachable, stale token) fails gracefully with one clear message instead of N silent subagent aborts.

  2. Post an initial pending commit status via REST:

    POST /api/v1/repos/Octopus/octopus/statuses/{SHA}
    state: "pending"
    context: "pipeline/{REVIEW_TYPE}"
    description: "Round {N}: {DIMENSION_COUNT} reviewers dispatched"
    

    (See _shared/gitea-write-patterns.md Pattern 8.)

  3. Spawn all {DIMENSION_COUNT} {REVIEWER_OR_AUDITOR}s in parallel. Each receives:

    • The standardized prompt with dimension-specific values filled in.
    • Agent type: Explorer.
    • Permission: Explorer read-only (see core/skills/_shared/review-orchestrator-rules.md — Default Explorer Permissions).
    • Temperature: 0.1 (deterministic output).
  4. Wait for all {REVIEWER_OR_AUDITOR}s to complete. Each reviewer writes its dimension result to the skill's configured findings sink:

    • tier1-local (e.g. review-code, review-artifact, audit-process): write the JSON to <runs-root>/{slug}/reviews/{stage}/round{N}/findings-{DIMENSION_CODE}.json (validated by core/schemas/reviewer-output.schema.json). The reviewer is granted scoped write to <runs-root>/** (read-only everywhere else — this emits the reviewer's OWN output, it does not touch the artifact under review, so it does not violate rule #7 auditor independence).
    • tier2-comment (fallback): post an issue comment via 工单评论 API(见 TERMINOLOGY (Pattern 5, _shared/gitea-write-patterns.md) with a fenced ```json block on the target PR (pipeline mode) or issue (standalone mode). The comment is labeled **{DIMENSION_CODE} review — round {N}**. After all reviewers complete, for tier1-local the Orchestrator appends one index.json row per finding file (type review-findings, stage {REVIEW_TYPE}, produced_by Reviewer, schema = the reviewer-output $id).
  5. If a {REVIEWER_OR_AUDITOR} fails (crash / timeout), retry once. If still failed, mark that dimension as UNRESOLVED in the synthesis and continue. Record it for deferred retry at the start of the next round.


Phase B — Synthesis (Delegated)

Context optimization: Phase B is delegated to a Synthesizer (Worker) to prevent the Orchestrator's context from accumulating N dimension finding Read results (up to N × 50KB per round). The Synthesizer reads all dimension findings in its own context, posts the synthesis comment + commit status, and returns a compact summary (≤ 2KB). The Orchestrator never reads dimension findings directly.

Orchestrator actions:

  1. Fill the synthesis task template (core/skills/_shared/synthesis_task.md) with:

    • Round number. A round-1 dispatch carries the template's step-8 "Round-1 only — findings digest" duty (the Synthesizer writes round1/findings-summary.md, consumed verbatim at Phase E — see the digest bullet in step 3 below); verify it is present in the filled template.
    • Target PR number (pipeline mode) or issue number (standalone mode).
    • List of UNRESOLVED dimensions (reviewers that crashed/timed out).
    • Commit SHA and review type.
    • For tier1-local skills (all review/audit): write the filled template to <runs-root>/{slug}/reviews/{stage}/round{N}/task-synthesizer.md (Tier 1 synthesis prompt; append an index.json row, type synthesis-prompt). The /tmp/octopus/ embryo is NOT used — Tier 1 owns the prompt.
    • For tier2-comment skills: write the filled template to /tmp/octopus/synthesis_task_{slug}_round{N}.md for prompt_file use; the authoritative copy is published as a Gitea wiki page at {slug}/reviews/{stage}/round{N}/task-synthesizer.
  2. Dispatch the Synthesizer (Worker) via the task tool:

    • role: Synthesizer
    • subagent_type: worker
    • prompt_file: the synthesis-prompt path from step 1 (Tier 1 local path for tier1-local skills; /tmp/octopus/... for tier2-comment skills)
    • permissions: read + write + edit + bash + glob + grep (all allow)
    • temperature: 0.1
    • Foreground (the Orchestrator waits for completion).
  3. The Synthesizer:

    • Reads all dimension findings from the skill's configured sink:
      • tier1-local: read the finding files at <runs-root>/{slug}/reviews/{stage}/round{N}/findings-*.json (in its OWN context, not the Orchestrator's — no Gitea call).
      • tier2-comment: read all dimension comments via 工单评论 API(见 TERMINOLOGY (Pattern 3, _shared/gitea-read-patterns.md) on the target PR/issue.
    • Post-check finding-id uniqueness (MANDATORY): verifies that id values are unique across ALL findings files of the round (including every -r{k} slot file) — the schema's uniqueItems does NOT enforce this. On a duplicate id, keeps both findings, notes the collision in the digest and marks the affected dimension WARN (per step 1b of _shared/synthesis_task.md).
    • Computes synthesis following core/schemas/synthesis.schema.json.
    • Posts synthesis as a PR/issue comment via 工单评论 API(见 TERMINOLOGY (Pattern 5, _shared/gitea-write-patterns.md) — markdown summary + fenced ```json block matching core/schemas/synthesis.schema.json. (This is the Tier 2 human-gate output — it stays on Gitea for both sinks.) For the audit-process target (no source issue), post the synthesis as a wiki page audit/{date}/round{N}/synthesis instead of a PR/issue comment.
    • Posts a commit status via REST with context pipeline/{REVIEW_TYPE} (Pattern 8, _shared/gitea-write-patterns.md): state: "success" for PASS/WARN, "failure" for FAIL.
    • Appends one history[] entry for this round to the run's review-status file <runs-root>/{slug}/reviews/{stage}/status.json (validated by core/schemas/review-status.schema.json) — round, overall_verdict, blockers, majors, minors — the durable per-round audit history (defined by core/schemas/review-status.schema.json history[]; mirrors the audit-process checklist TRC 10.4), and updates the file's current_round to this round number. (The Orchestrator initializes status.json before round 1 — see Phase A step 0.5 — with the full required field set slug / started_at / current_round / max_rounds / converged / history. The Synthesizer only appends history[] and updates current_round; it never re-creates the file.)
    • Round-1 findings digest (round 1 only): for skills whose final report carries a Round-1 Findings Summary (review-code Phase E step 2.55 and any target adopting the template section), the round-1 Synthesizer is the PRODUCER of that table: it writes <runs-root>/{slug}/reviews/{stage}/round1/findings-summary.md — the rendered ## Round-1 Findings Summary table (columns | Dim | Finding ID | Severity | Summary |, one row per round-1 finding; Summary = one-line 摘由/root cause) — distilled from the round-1 findings it just read in its own context (append an index.json row, type review-synthesis — Synthesizer-produced synthesis artifacts; the round-verdict return summary synthesis-return.md keeps the generic worker-report type other per _shared/worker-report-persistence.md). This duty is wired into the Synthesizer's operative prompt as step 8 of _shared/synthesis_task.md ("Round 1 only — findings digest"). The Orchestrator at Phase E copies this file verbatim into the final report; it does NOT read the findings JSONs (step 4 below) — the digest file is the wired producer→consumer path.
    • Returns a compact summary (≤ 2KB) with verdict, per-dimension results, and re-audit targets.
  4. The Orchestrator receives the summary and proceeds to Phase C or Phase E based on overall_verdict. The Orchestrator does NOT read dimension comments or the synthesis comment directly — all decision-relevant data is in the Synthesizer's return summary.

  5. Present the Synthesizer's return summary to the user in chat.


Phase C — {REVISION_ROLE} Revision

If overall_verdict is PASS, skip to Phase E.

Otherwise:

Orchestrator actions:

  1. Prepare the {REVISION_ROLE}'s input as a structured task, published as a Gitea wiki page at {slug}/reviews/{stage}/round{N}/task-{ROLE} (Pattern 1, _shared/gitea-write-patterns.md). For the audit-process target, the process target's Phase Bindings override the wiki namespace to audit/{date}/ — publish to audit/{date}/round{N}/task-{ROLE} instead (see review-artifact/SKILL.md process target).

    This must contain:

    • Paths to all files that need revision.
    • Reference to the synthesis comment on the PR/issue (posted in Phase B).
    • References to the dimensions that have open BLOCKER, MAJOR, or MINOR findings, via the skill's findings sink:
      • tier1-local: paths to the Tier 1 finding files (<runs-root>/{slug}/reviews/{stage}/round{N}/findings-{DIMENSION_CODE}.json).
      • tier2-comment: references to the individual dimension issue comments. Dimensions whose only findings are INFO are excluded — INFO is dispositioned at synthesis and never revised (see the "Severity handling — INFO disposition" rule in Phase D).
    • The {REVISION_ROLE} MUST NOT receive the full text of all findings inline — use finding-file path / comment references only to avoid context explosion.
    • Convergence status: blockers/majors remaining vs. previous round.
    • Revision survival ([org-internal #2601]) — primary path: the task tool's completion output includes a task_id; passing it back resumes the SAME {REVISION_ROLE} subagent session instead of creating a fresh one. Round 1: persist the returned task_id with the round's working-notes entry (it must survive round-boundary compaction). Round ≥ 2: dispatch with task_id set to the previous revision round's — the reviser continues in-session with its prior context. {REVIEWER_OR_AUDITOR}s are NEVER resumed — per-round freshness is their independence.
    • Revision survival — fallback (task_id lost to compaction before it was persisted): for round ≥ 2, include the current <runs-root>/{slug}/working-notes.md content (run narrative memory: prior decisions, dead ends, earlier reviewer feedback) and a reference to the previous round's revision summary. State explicitly that this revises work a prior round already produced — re-derive from upstream artifacts ONLY when a finding requires it.
  2. Spawn the {REVISION_ROLE} (Worker) with revision instructions from core/skills/_shared/review-revision-prompt.md. For round ≥ 2, pass the previous reviser task_id to resume the same session (see Revision survival above); reviewer/auditor dispatches are always fresh.


Phase D — {REVIEWER_OR_AUDIT_LOOP} Loop

After the {REVISION_ROLE} completes revision:

  1. Increment the round counter, and update current_round in <runs-root>/{slug}/reviews/{stage}/status.json to match. Post the round transition as a progress update to the commit status (Pattern 8):
    state: "pending"
    context: "pipeline/{REVIEW_TYPE}"
    description: "Round {N+1}: re-reviewing after {REVISION_ROLE} revision"
    
  2. Go back to Phase A (spawn a new round against the revised files).

Convergence criteria (stop the loop when ANY of these is true):

Condition Action
overall_verdict = PASS Stop. Proceed to Phase E.
current_round > {MAX_ROUNDS} Stop. Proceed to Phase E with final status.
No improvement in 2 consecutive rounds Stop. (Same blocker count AND same major count
(compared to 2 rounds ago) without reduction). Proceed to Phase E.
User explicitly approves current state Stop. Proceed to Phase E.

{MAX_ROUNDS} binding (per skill):

  • review-artifact skill (process target): 3.
  • review-artifact skill (review-dag single-gate target): per depth from dag_metrics.review_depth — D1 ≤2, D2 ≤3, D3 ≤3, D4 ≤4 (D4 reaches round 4, exceeding the shared review-artifact default; the per-depth cap takes precedence — see review-artifact/SKILL.md review-dag Phase D and core/schemas/review-status.schema.json).
  • review-code skill: 2 default / 3 high-risk (a BLOCKER survived a round, or the diff touches a frozen cross-session contract) — <instance-root>/workflow-routing.yaml review.max_rounds ([org-internal #3567], process-diet P0b). At budget without convergence: STOP, register the remaining BLOCKER/MAJOR findings as TD registry rows, verdict FAIL if any BLOCKER remains else PASS — the debt is carried by those TD registry rows (see review-code/SKILL.md Phase D). This supersedes the 2026-08-25 [org-internal #3380] calibration (p50=2, p95=5, n=66): the runs past round 3 are the marathon tail this budget exists to cut; their residue flows to the TD registry instead of reviewer rounds. Ledger: docs/workflow-refactor/thresholds-ledger.md.

Rationale for review-artifact = 3 (evaluated in [org-internal #1969], replacing the prior blanket default of 5): (1) the Round-3 Escalation Guardrail below already detects non-convergence at the start of round 3; (2) the INFO-disposition rule below removes spurious WARN rounds previously caused by non-blocking INFO findings; (3) the "no improvement in 2 consecutive rounds" stop condition naturally fires by round 3; (4) the marginal value of rounds 4-5 is low once the guardrail has fired and they burn reviewer budget. (review-code no longer reaches round 4 — its round budget is 2/3, see the binding above.)

Provisional, not empirically validated: the four safeguards make review-artifact=3 defensible, but it has no fresh run-data yet. Treat it as a hypothesis to confirm via the retrospective signal ([org-internal #1834]; gate-trim's landing machinery was retired, [org-internal #3072] phase 3 — the check is observational, not a trim proposal): if live review-artifact runs frequently hit round 3 still improving yet are forced to stop at WARN/FAIL, bump it back to 4.

Severity handling — INFO disposition (non-blocking, non-revising): INFO findings are observations/recommendations beyond the checklist. They NEVER block convergence and NEVER drive a reviser round.

  • A dimension whose ONLY open findings are INFO has effective verdict PASS — INFO does not reduce pass_rate and does not cause WARN/FAIL (see orchestrator rule #3). Such a dimension does NOT count against overall_verdict.
  • At synthesis, the Synthesizer dispositions EVERY INFO finding as one of:
    • TD-candidate — actionable observation worth a tech-debt item; record a suggested REGISTRY ROW (one-line summary + reactivation trigger, core/rules/ticket-lifecycle.md). Actual TD-NNN registration is deferred to the verify skill's Phase 5.5 registry filing — a TD-candidate does NOT open an independent ticket at review time (consistent with how review-code Accepted Risks flow to verify).
    • DISCARDED — not worth tracking; record a one-line reason. Dispositions are listed in the synthesis comment (markdown). INFO counts carry in info_count / infos for reporting only.
  • Phase C reviser task (step 1 above) references ONLY dimensions with open BLOCKER/MAJOR/MINOR findings. INFO findings are NEVER passed to the {REVISION_ROLE}.

Round-3 Escalation Guardrail (applies to the review-artifact skill's live targets — the process audit target AND the review-dag single-gate target; the roadmap / design / plan targets were archived 2026-08-21, [org-internal #3072] phase 3). review-code does NOT use this guardrail; it uses its round budget ([org-internal #3567], see the {MAX_ROUNDS} binding above).

At the start of round 3, if the combined blocker+major count is still >50% of round 1's count, STOP the loop and surface to the user:

Review not converging: round 1 had {N1} blockers+majors, round 2 still has {N2} ({pct}% of round 1). This suggests the artifact under review needs a substantive rewrite, not incremental patching. Options: (a) Return to upstream — for the process audit target, escalate to the octopus maintainer for a process-level redesign (the legacy producer skills roadmap / requirements-elicitation / design / plan-iterations were archived 2026-08-21, [org-internal #3072] phase 3 — <instance-root>/archive/), then restart this review at round 1. (b) Continue iterating — accept that more rounds may be needed (capped by {MAX_ROUNDS}).

Choosing (a) returns control to the upstream skill (or maintainer); the review's commit status is marked with description "escalated" and converged: false.

Round-4 user-choice escalation point (shared): a multi-round review skill MUST offer the user an explicit choice before entering round 4 — (a) stop the review and escalate to a human, or (b) continue into round 4 — via the question tool. The guard means "escalate with a choice", NOT "force-stop". The choice is UNCONDITIONAL at the round-3→4 transition — instance skills may narrow when an escalation variant (e.g. a design-level decision) fires, but never narrow the choice gate itself. The call MUST be optionized ([org-internal #3378]): two options — Stop & escalate to a human (first, when recommended) / Continue into round 4 — with stage set to the running skill's stage so auto-approval applies in autonomous mode; free-text asking is a spec violation. Instance: review-dag D4 option-b (review-artifact/SKILL.md §"Round-3 Escalation Guardrail interaction") — the sole live instance, its D4 round cap (≤4) being the only budget that reaches round 4 (review-code is capped 2/3, [org-internal #3567]). Rationale (retro 2026-08-21 / [org-internal #3050]): unattended late-round loops drift into review marathons — the choice forces the design-level stall to surface to a human at round 4 instead of round 6.

Efficiency optimization: From round 2 onward ([org-internal #2598]), only spawn {REVIEWER_OR_AUDITOR}s for dimensions that had FAIL, WARN, or UNRESOLVED in the previous round. Every 2 rounds, run a full {DIMENSION_COUNT}-reviewer sweep to catch regressions. From round 2 onward reviewer input is also delta-scoped: the file list handed to each reviewer covers only the files changed since the previous round plus their convention-comparison neighbors, not the round-1 full set.


Phase E — Final Report

Orchestrator actions:

  1. Write the final report as a Gitea wiki page at {slug}/reviews/{stage}/final/report (Pattern 1, _shared/gitea-write-patterns.md), using the template at core/skills/_shared/review-final-report-template.md. For the audit-process target, write to audit/{date}/final/report instead (wiki namespace override audit/{date}/). Also set the lifecycle converged flag in <runs-root>/{slug}/reviews/{stage}/status.json: true when the final verdict is PASS (or WARN with no unresolved BLOCKERs), else false. This is the ONLY place converged is written — the Orchestrator at Phase E; the Synthesizer never sets it.

  2. Update the ## 工件索引 comment on the source issue per _shared/gitea-write-patterns.md Pattern 10: add/update the REVIEW-{stage} row (位置 = the final report page, 重读 = CORE — review conclusions are part of the compaction re-read set). Skip if no source issue exists (Pattern 10 step 1).

2.5. Inline-ization (skills that bind it) — when the review target is a PR and the skill's Phase E binds the inline-comment step (currently review-code only, as its Phase E step 2.56), execute it now: post the final round's findings as PR inline comments via <harness-package>/script/post-inline-comments.ts (consumer of frozen contract shared/review-inline-v1): noise findings (INFO / DISCARDED / WAIVED-{id}) are never inlined (contract behavior 2), multi-round posts delete the identity's previous inline comments first (behavior 5), stale lines degrade to the file's first diff hunk (behavior 4; caveat: this server build returns HTTP 500 rather than the contract's 422 for stale lines — TD-611 — so stale items currently surface under failed with fallback empty), and partial failures are accounted — never blocking the verdict (behavior 6) — in the synthesis comment and final report's inline: X posted / Y file-level / Z fallback / W failed line. Skills whose target has no PR (e.g. audit-process) skip this step. The skill-specific binding (runner invocation, findings paths, identity, cleanup) is normative in the binding skill's own Phase E section.

  1. Post final commit status via REST:

    POST /api/v1/repos/Octopus/octopus/statuses/{SHA}
    state: "success"|"failure"
    context: "pipeline/{REVIEW_TYPE}"
    description: "Final: {PASS|WARN|FAIL} after {N} rounds — converged: {true|false}"
    target_url: "{wiki page link}"
    

3.5. Gitea approval timing (mandatory when the PR needs a human approval gate): if the flow requests approval from a human reviewer on Gitea, register the reviewer FIRST via POST <gitea-base-url>/api/v1/repos/{owner}/{repo}/pulls/{index}/requested_reviewers, wait for the REQUEST_REVIEW registration to be recorded, and only THEN ask the human to approve. Approving before the reviewer is registered produces official: false on the approval — the gate treats the PR as unapproved and the approval must be re-done. ([org-internal #1931]: audit R2→R3 re-loop + re-approve, ~30min wasted on exactly this ordering.)

  1. Present the final report to the user and obtain approval via the question tool, passing stage: "{STAGE}".

    The question tool auto-approves per core/rules/auto-approve.md (stage "{STAGE}").

    Do NOT advance without approval (auto or explicit).


Orchestrator Rules (Mandatory)

See core/skills/_shared/review-orchestrator-rules.md for the mandatory Orchestrator behavior rules. Substitute {REVISION_ROLE}: {REVISION_ROLE}.