diff --git a/skills/paperclip/SKILL.md b/skills/paperclip/SKILL.md index 5ef76086..82dd0c38 100644 --- a/skills/paperclip/SKILL.md +++ b/skills/paperclip/SKILL.md @@ -72,6 +72,35 @@ Use comments incrementally: Read enough ancestor/comment context to understand _why_ the task exists and what changed. Do not reflexively reload the whole thread on every heartbeat. +**Execution-policy review/approval wakes.** If the issue is in `in_review` and includes `executionState`, inspect these fields immediately: + +- `executionState.currentStageType` tells you whether you are in a `review` or `approval` stage +- `executionState.currentParticipant` tells you who is currently allowed to act +- `executionState.returnAssignee` tells you who receives the task back if changes are requested +- `executionState.lastDecisionOutcome` tells you the latest review/approval outcome + +If `currentParticipant` matches you, you are the active reviewer/approver for this heartbeat. There is **no separate execution-decision endpoint**. Submit your decision through the normal issue update route: + +```json +PATCH /api/issues/{issueId} +Headers: X-Paperclip-Run-Id: $PAPERCLIP_RUN_ID +{ "status": "done", "comment": "Approved: what you reviewed and why it passes." } +``` + +That approves the current stage. If more stages remain, Paperclip keeps the issue in `in_review`, reassigns it to the next participant, and records the decision automatically. + +To request changes, send a non-`done` status with a required comment. Prefer `in_progress`: + +```json +PATCH /api/issues/{issueId} +Headers: X-Paperclip-Run-Id: $PAPERCLIP_RUN_ID +{ "status": "in_progress", "comment": "Changes requested: exactly what must be fixed." } +``` + +Paperclip converts that into a changes-requested decision, reassigns the issue to `returnAssignee`, and routes the task back through the same stage after the executor resubmits. + +If `currentParticipant` does **not** match you, do not try to advance the stage. Only the active reviewer/approver can do that, and Paperclip will reject other actors with `422`. + **Step 7 — Do the work.** Use your tools and capabilities. **Step 8 — Update status and communicate.** Always include the run ID header. diff --git a/skills/paperclip/references/api-reference.md b/skills/paperclip/references/api-reference.md index 9ee9301d..676389b4 100644 --- a/skills/paperclip/references/api-reference.md +++ b/skills/paperclip/references/api-reference.md @@ -191,6 +191,58 @@ The response also includes `blockedBy` and `blocks` arrays showing first-class d Blocker wake semantics are strict: `issue_blockers_resolved` only fires when every blocker reaches `done`. A blocker moved to `cancelled` still requires manual re-triage or relation cleanup. +### Execution Policy Fields On An Issue + +When an issue has review or approval gates, `GET /api/issues/:issueId` can also include `executionPolicy` and `executionState`: + +```json +{ + "status": "in_review", + "executionPolicy": { + "mode": "normal", + "commentRequired": true, + "stages": [ + { + "id": "stage-review", + "type": "review", + "approvalsNeeded": 1, + "participants": [ + { "id": "participant-qa", "type": "agent", "agentId": "qa-agent-id" } + ] + }, + { + "id": "stage-approval", + "type": "approval", + "approvalsNeeded": 1, + "participants": [ + { "id": "participant-cto", "type": "user", "userId": "cto-user-id" } + ] + } + ] + }, + "executionState": { + "status": "pending", + "currentStageId": "stage-review", + "currentStageIndex": 0, + "currentStageType": "review", + "currentParticipant": { "type": "agent", "agentId": "qa-agent-id" }, + "returnAssignee": { "type": "agent", "agentId": "coder-agent-id" }, + "completedStageIds": [], + "lastDecisionId": null, + "lastDecisionOutcome": null + } +} +``` + +Interpretation: + +- `currentStageType` tells you whether the active gate is `review` or `approval` +- `currentParticipant` is the only actor allowed to advance the stage +- `returnAssignee` is who gets the task back when changes are requested +- `lastDecisionOutcome` shows the latest gate decision + +There is **no separate execution-decision endpoint**. Review and approval decisions are submitted through `PATCH /api/issues/:issueId`, and Paperclip records the decision row automatically. + --- ## Worked Example: IC Heartbeat @@ -262,6 +314,43 @@ PATCH /api/issues/issue-200 { "comment": "Your Mine inbox has 1 unread issue: [PAP-310](/PAP/issues/PAP-310)." } ``` +### Worked Example: Reviewer / Approver Heartbeat + +When you wake up on an issue in `in_review`, inspect `executionState` first: + +``` +GET /api/issues/issue-77 +-> { + id: "issue-77", + status: "in_review", + assigneeAgentId: "qa-agent-id", + executionState: { + status: "pending", + currentStageType: "review", + currentParticipant: { type: "agent", agentId: "qa-agent-id" }, + returnAssignee: { type: "agent", agentId: "coder-agent-id" } + } + } +``` + +If `currentParticipant` is you, approve the current stage by patching the issue to `done` with a required comment: + +``` +PATCH /api/issues/issue-77 +{ "status": "done", "comment": "QA signoff complete. Verified the regression and test coverage." } +``` + +Paperclip writes the execution decision automatically. If another stage remains, the issue stays in `in_review` and is reassigned to the next participant. If this was the final stage, the issue reaches actual `done`. + +To request changes, use a non-`done` status with a required comment. Prefer `in_progress`: + +``` +PATCH /api/issues/issue-77 +{ "status": "in_progress", "comment": "Changes requested: add a regression test for the empty-state path." } +``` + +Paperclip converts that into a `changes_requested` decision, reassigns the issue to `returnAssignee`, and routes it back to the same stage when the executor resubmits. + --- ## Worked Example: Manager Heartbeat