🎩 You're Invited:Meet the Socket team at Black Hat in Las Vegas, August 3-6.RSVP
Sign In

opencode-plugin-zooplankton

Package Overview
Dependencies
Maintainers
1
Versions
22
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

opencode-plugin-zooplankton - npm Package Compare versions

Comparing version
0.6.0
to
0.6.1
+11
-0
guides/core-reviewer-guide.md

@@ -95,2 +95,4 @@ # Core Reviewer Guide

**Routing replies via `comment_id`:** populating the optional `comment_id` field (see schema below) is **preferred** because it gives the orchestrator direct routing for resolution replies. The `(summary_normalized, path, line)` tuple is used only as a fallback when `comment_id` is absent or null.
### Step 5: Read other reviewers' comments critically

@@ -199,2 +201,11 @@

- Empty collections are explicit: `"findings": []`, `"prior_issue_resolution": []`, `"cross_reviewer_notes": ""`.
- `prior_issue_resolution[]` entries (Round 2+ only): each entry describes the status of one of *your own* prior-round findings and supports an optional `comment_id` field — the integer GitHub PR review comment ID of the inline comment posted by the orchestrator in the prior round (when known; `null` otherwise). The orchestrator passes a prior-round `(cluster_id → comment_id)` map in your Round 2+ invocation prompt; copy the matching ID into `comment_id` so the orchestrator can route resolution replies. Set `comment_id: null` when the prior finding had no inline comment (i.e. it was file/branch-scope and rendered in the review body only) or when you cannot determine the ID. Each entry shape (note: `ephemeral_id` is **not** used in `prior_issue_resolution[]` — the orchestrator routes by `comment_id` first, then by tuple match):
```json
{
"prior_summary": "Null deref when `config.repo` is absent",
"status": "ADDRESSED",
"note": "Added null check at src/foo.ts:42",
"comment_id": 2034567
}
```
- `security_verdict` is `null` for non-security reviewers.

@@ -201,0 +212,0 @@ - Prefer `anchor_scope: "file"` over guessing line numbers when a finding has no diff-visible anchor.

@@ -98,2 +98,4 @@ # Reviewer Guide

**Routing replies via `comment_id`:** populating the optional `comment_id` field (see schema below) is **preferred** — it gives the orchestrator direct routing for resolution replies and is faster than tuple-based matching. The `(summary_normalized, path, line)` tuple is used only as a fallback when `comment_id` is absent or null.
> In Round > 1, only check **your own prior findings** (identified by your model ID prefix in GitHub mode). Do not audit other reviewers' findings — that is the core reviewers' responsibility.

@@ -180,2 +182,11 @@

- Empty collections explicit: `"findings": []`, `"prior_issue_resolution": []`, `"cross_reviewer_notes": ""`.
- `prior_issue_resolution[]` entries (Round 2+ only): each entry supports an optional `comment_id` field — the integer GitHub PR review comment ID of the inline comment posted by the orchestrator in the prior round (when known; `null` otherwise). The orchestrator passes a prior-round `(cluster_id → comment_id)` map in your Round 2+ invocation prompt; copy the matching ID into `comment_id` so the orchestrator can route resolution replies. Set `comment_id: null` when the prior finding had no inline comment (file/branch-scope) or when you cannot determine the ID. Each entry shape (note: `ephemeral_id` is **not** used in `prior_issue_resolution[]` — the orchestrator routes by `comment_id` first, then by tuple match):
```json
{
"prior_summary": "Null deref when `config.repo` is absent",
"status": "ADDRESSED",
"note": "Added null check at src/foo.ts:42",
"comment_id": 2034567
}
```
- `security_verdict` is `null` for non-security reviewers.

@@ -182,0 +193,0 @@ - You do not run verification commands — emit `"verification": {}`.

+1
-0

@@ -171,2 +171,3 @@ # Security Reviewer Guide

- Empty collections are explicit: `"findings": []`, `"prior_issue_resolution": []`, `"cross_reviewer_notes": ""`.
- `prior_issue_resolution[]` entries (Round 2+ only): each entry describes the status of one of *your own* prior-round findings and supports an optional `comment_id` field — the integer GitHub PR review comment ID of the inline comment posted by the orchestrator in the prior round (when known; `null` otherwise). The orchestrator passes a prior-round `(cluster_id → comment_id)` map in your Round 2+ invocation prompt; copy the matching ID into `comment_id` so the orchestrator can route resolution replies. Set `comment_id: null` when the prior finding had no inline comment (i.e. it was file/branch-scope and rendered in the review body only) or when you cannot determine the ID. Populating `comment_id` is preferred — the orchestrator falls back to `(summary_normalized, path, line)` matching only when `comment_id` is absent or null.

@@ -173,0 +174,0 @@ Example:

+1
-1
{
"name": "opencode-plugin-zooplankton",
"version": "0.6.0",
"version": "0.6.1",
"description": "Unified global OpenCode plugin for ZooplanktonAI — coding standards, multi-agent skills (brainstorm / plan / orchestrate), commands, guides, and the autonomous-mode permission hook.",

@@ -5,0 +5,0 @@ "type": "module",

@@ -58,2 +58,8 @@ # Invocation Templates Reference

[Round 2+ only — orchestrator passes the prior-round (cluster_id → comment_id) map for findings you raised last round, e.g.:
Prior inline comments (from round <N-1>):
- R1-C01 → comment_id 2034567 (path: src/foo.ts, line: 42)
- R1-C02 → comment_id 2034568 (path: src/bar.ts, line: 17)
Populate `comment_id` in each `prior_issue_resolution[]` entry with the matching ID so the orchestrator can route resolution replies. Use `null` when a prior finding had no inline comment (file/branch-scope).]
Return: verification results plus the structured JSON result block (schema v1).

@@ -79,2 +85,4 @@ Do NOT post to the PR yourself; the orchestrator consolidates and posts.

[Round 2+ only — orchestrator passes the prior-round (cluster_id → comment_id) map for findings you raised last round; populate `comment_id` in each `prior_issue_resolution[]` entry so the orchestrator can route resolution replies. Use `null` when a prior finding had no inline comment (file/branch-scope).]
Return: the structured JSON result block (schema v1).

@@ -81,0 +89,0 @@ Do NOT post to the PR yourself; the orchestrator consolidates and posts.

@@ -74,6 +74,24 @@ # Review Protocol Reference

"status": "IN_PROGRESS",
"timestamp": "<ISO-8601>"
"timestamp": "<ISO-8601>",
"replied_comment_ids": []
}
```
The optional `replied_comment_ids: [<comment_id>, ...]` field tracks which prior-round inline comments have already received a resolution reply in the current round (see **Round 2+ Resolution Replies** → step 6). It is appended to as each reply is successfully posted, so a re-run of the round skips comments already replied to. Round 1 entries leave it as `[]`.
In addition to per-`(pr, round, phase)` entries, the state file also stores the prior-round `(cluster_id → comment_id)` map at `(pr, round)` scope (not phase-specific) as a separate top-level entry. This lets Round N+1 look up comment IDs posted by Round N's consolidated review:
```json
{
"pr": 43,
"round": 2,
"comment_id_map": {
"R2-C01": 987654321,
"R2-C02": 987654322
}
}
```
Each `(pr, round)` map entry coexists with the `(pr, round, phase)` posting-state entries in the same JSON file; they are distinguished by the presence/absence of the `phase` and `comment_id_map` keys.
## Consolidated Review Posting

@@ -83,3 +101,3 @@

In GitHub mode, post one consolidated review per role-group per round:
In GitHub mode, post one consolidated **GitHub Pull Request Review** per role-group per round via `POST /repos/{repo}/pulls/{pull_number}/reviews`. This is **not** a plain PR comment (`gh pr comment`) — it is a structured review object whose `comments[]` array creates true inline comments anchored on the diff.

@@ -89,4 +107,38 @@ - Code reviewers: core + normal + frontend merged into one review.

Review marker format:
The review payload shape:
```json
{
"commit_id": "<headRefOid>",
"event": "COMMENT",
"body": "<marker + per-reviewer prose headers + file/branch-scope findings>",
"comments": [
{ "path": "src/foo.ts", "position": 17, "body": "[R2-C03] [blocking] …" }
]
}
```
- Each `anchor_scope: "line"` finding becomes one entry in `comments[]`. **The `POST /pulls/{pull_number}/reviews` endpoint requires `comments[].position` (the diff position offset), not the file's `line`/`side`.** To resolve a file path + line number to a `position`, fetch the PR's unified diff with `gh pr diff $PR_NUMBER` and count: `position` is the count of lines from the file's first hunk header (`@@ ... @@`) through the target line in that diff, where the line **immediately below** the `@@` line is `position` 1, the next is `position` 2, and so on. Counting includes context, addition (`+`), and deletion (`-`) lines, and continues across additional hunks within the same file (the `@@` separator lines themselves are also counted). When the same line appears across re-hunks (e.g. on a force-push), recompute against the current diff before posting.
- Each `anchor_scope: "file"` and `anchor_scope: "branch"` finding is rendered into the review `body` only — GitHub has no per-file anchor for review comments outside hunks, so file/branch-scope findings live in the summary section.
- The summary `body` always begins with the zooplankton marker followed by per-reviewer prose headers (`**[Round N] <model-id>:**`).
> **Note on `line`/`side`:** The separate per-comment endpoint `POST /repos/{owner}/{repo}/pulls/{pull_number}/comments` (used to create a single review comment outside a review object) accepts `line` + `side` directly and does not require diff-position computation. The consolidated review endpoint we use (`POST /pulls/{pull_number}/reviews`) does **not** — its `comments[]` payloads must use `position`.
Example invocation (single-quoted heredoc to prevent shell expansion):
```bash
gh api -X POST "repos/<REPO>/pulls/$PR_NUMBER/reviews" --input - <<'EOF'
{
"commit_id": "<headRefOid>",
"event": "COMMENT",
"body": "<!-- zooplankton-orchestrate: pr=123 round=2 fingerprint=abc -->\n\n**[Round 2] core-reviewer (claude-sonnet-4.6):** …",
"comments": [
{ "path": "src/foo.ts", "position": 17, "body": "[R2-C03] [blocking] Null deref when `config.repo` is absent." }
]
}
EOF
```
Review marker format (in the review `body`):
```markdown

@@ -106,6 +158,6 @@ <!-- zooplankton-orchestrate: pr=<pr_number> round=<n> fingerprint=<sha> -->

2. Build endpoint-neutral line anchors with `buildInlineCommentAnchors(findings)`.
3. Before calling GitHub, resolve anchors to the exact endpoint payload. `POST /pulls/{pull_number}/reviews` review comments require diff `position`; `POST /pulls/{pull_number}/comments` accepts `line`/`side` with `commit_id`.
3. Before calling GitHub, resolve anchors to the exact endpoint payload. `POST /pulls/{pull_number}/reviews` review comments require `path` + `position` (with `commit_id` at the top level) — `position` is the diff-position integer described above, not the file line number. The legacy `POST /pulls/{pull_number}/comments` per-comment endpoint (which does accept `path` + `line` + `side`) is **not** used by the consolidated post.
4. Tier 1: re-validate anchors against current `headRefOid`, then retry with `planReviewPostRetry(comments, 1)`.
5. Tier 2: deterministically halve inline comments and move the rest into the body with `planReviewPostRetry(comments, 2)`.
6. Tier 3: body-only review with `planReviewPostRetry(comments, 3)`.
6. Tier 3: body-only review (omit `comments[]` entirely, render every finding in `body`) with `planReviewPostRetry(comments, 3)`.

@@ -116,2 +168,104 @@ If all tiers fail, leave the local state marker as `IN_PROGRESS` and abort before evaluate/merge.

After a successful post, capture the `id` of the returned Review object. Note that `POST /pulls/{pull_number}/reviews` returns the Review object (id, body, state) but **does not include the inline comment IDs** in its response. To populate the `(cluster_id → comment_id)` map, fetch the inline comments separately and filter by the new review's id:
```bash
gh api "repos/<REPO>/pulls/$PR_NUMBER/comments" --paginate \
--jq "[.[] | select(.pull_request_review_id == <returned_review_id>)]"
```
Use the returned comment objects (each has `id`, `path`, `position`, `line`, `body`) to build the `(cluster_id → comment_id)` map — match each comment to its cluster by parsing the `[<cluster_id>]` prefix in the comment body, or by `(path, position)` against the posted findings. Persist the map in `.zooplankton-state.json` keyed by `(pr, round)` (see **State Schema** below). Subsequent rounds use this map to route resolution replies (see **Round 2+ Resolution Replies** below).
## Round 2+ Resolution Replies
> **Local mode:** skip — no PR exists.
>
> **Round 1:** skip — there are no prior inline comments to reply to.
Starting from **Round 2**, the orchestrator MUST reply (under its own account) to every inline comment posted in prior rounds, using the resolution evaluations collected from the current round's reviewer results. This step is **mandatory in GitHub mode** — every prior-round inline comment should receive a reply, but partial completion is acceptable if individual replies fail (see **Failure handling** below).
**Ordering:** This step runs as **§4d.Replies — after §4d.FP triage has completed**, because the orchestrator needs the current round's `FALSE_POSITIVE` triage map to choose between "Carried forward" and "Dismissed as false positive" reply templates (see step 3 below). It is therefore distinct from the consolidated review post in §4b.5, which has already happened by this point.
The reply *content* comes from the reviewers' `prior_issue_resolution[]` entries (aggregated across reviewers), not from the orchestrator's own analysis. The orchestrator merely routes each evaluation to the matching prior `comment_id` and synthesizes a one-sentence plain-prose reply.
### Procedure
1. **Fetch prior inline comments.** Call:
```bash
gh api "repos/<REPO>/pulls/$PR_NUMBER/comments" --paginate
```
This returns the flat list of every PR review inline comment (across all reviews) on the PR.
2. **Filter to orchestrator-posted comments from prior rounds.** Identify orchestrator comments by:
- The zooplankton marker in the parent review body (look up via the comment's `pull_request_review_id`), **or**
- A `[R<N>-C<nn>]` cluster-ID prefix at the start of the inline comment body, with `N < currentRound` (e.g. `[R1-C03] [blocking] …`). Note: the `**[Round N]**` per-reviewer header lives in the consolidated review `body`, not in individual inline comment bodies — do not use it as the filter pattern for inline comments.
Use the `(pr, round) → (cluster_id → comment_id)` map persisted by previous rounds (see **Consolidated Review Posting**) as the primary source. Fall back to API enumeration only when the map is missing or stale.
3. **Look up resolution status.** For each prior comment, find its corresponding entry in the current round's aggregated `prior_issue_resolution[]` (across all reviewer JSON results). Match priority:
1. Reviewers' populated `comment_id` field (preferred — direct routing).
2. Fallback: match by the `(summary_normalized, path, line)` tuple, where `summary_normalized` is lowercased, whitespace-collapsed, and trailing-period-stripped — the same normalization the orchestrator uses in its dedup step (see **Finding Consolidation**).
If multiple reviewers evaluated the same prior comment, prefer the most severe verdict (NOT ADDRESSED > PARTIALLY ADDRESSED > ADDRESSED) and merge their notes into a single sentence.
**No-match fallback.** If no resolution entry matches a prior comment (both `comment_id` and tuple fallback fail), log a warning and skip that comment's reply — do not abort the round.
**False-positive routing.** Reviewer vocabulary only emits `ADDRESSED` / `PARTIALLY ADDRESSED` / `NOT ADDRESSED` — there is no DISMISSED/FALSE_POSITIVE status from reviewers. To choose between the two NOT ADDRESSED templates ("Carried forward" vs. "Dismissed as false positive"), the orchestrator additionally consults its own §4d.FP triage map for the current round (see **False-Positive Triage**):
- If the matching cluster was marked `FALSE_POSITIVE` in §4d.FP → use the **Dismissed as false positive** reply.
- Otherwise → use the **Carried forward** reply.
4. **Synthesize a one-sentence plain-prose reply.** Use plain prose — **no emoji prefixes** (`✅`/`⚠️`/`❌`). Keep it concise.
| Status | Reply body |
|-----------------------|-----------------------------------------------------------------------------------------|
| ADDRESSED | `Addressed in round <N> — <one-sentence summary from reviewer resolution note>` |
| PARTIALLY ADDRESSED | `Partially addressed in round <N> — <one-sentence note>` |
| NOT ADDRESSED (carry) | `Not yet addressed. Carried forward as <new-cluster-id>.` |
| NOT ADDRESSED (FP) | `Dismissed as false positive — <one-sentence rationale>.` |
For the **carry** case, `<new-cluster-id>` is the cluster ID assigned to the equivalent finding in the current round's consolidated cluster list. Locate it by matching the prior finding's `(summary_normalized, path, line)` against the current round's clusters; if no match exists (i.e. no reviewer raised the issue this round), fall back to the **Dismissed as false positive** template if §4d.FP marked it FP, or use a literal `Not yet addressed.` (no carry-forward suffix) if the issue silently dropped out.
Examples:
- `Addressed in round 2 — replaced dynamic process.env lookup with static per-flag reads.`
- `Partially addressed in round 2 — null check added but error message still leaks internal path.`
- `Not yet addressed. Carried forward as R3-C04.`
- `Dismissed as false positive — the cited line is inside a generated file excluded from runtime.`
5. **Post one reply per prior comment.** Reply body text is reviewer-derived and may contain characters that the shell would interpret (`$(...)`, backticks, `$VAR`, etc.). Do **not** interpolate it into a double-quoted shell argument. Use one of the two safe patterns below.
**Static body (literal heredoc — preferred for orchestrator-generated text written inline):**
```bash
gh api "repos/<REPO>/pulls/$PR_NUMBER/comments/<comment_id>/replies" \
--method POST \
--input - <<'EOF'
{"body": "Addressed in round 2 — replaced dynamic process.env lookup with static per-flag reads."}
EOF
```
The single-quoted `<<'EOF'` heredoc delimiter prevents the shell from expanding `$(...)`, backticks, or `$VAR` inside the JSON payload before it is piped to `gh api`. Always use `<<'EOF'`, never `<<EOF`.
**Dynamic body (variable contents — use `jq --arg` to build the JSON safely):**
```bash
jq -n --arg body "$REPLY_BODY" '{"body": $body}' | \
gh api "repos/<REPO>/pulls/$PR_NUMBER/comments/<comment_id>/replies" \
--method POST --input -
```
`jq --arg body "$REPLY_BODY"` treats the contents of `$REPLY_BODY` as a literal string when constructing the JSON; this is safe even if `$REPLY_BODY` contains quotes, backslashes, or `$()` substitutions, because `jq` is the only consumer of the variable and it does not re-evaluate as shell.
**Do NOT use** `-f body="$REPLY_BODY"` or `--field body="$REPLY_BODY"` — even though `gh api` itself does not re-shell-evaluate the value, the shell does perform its own expansion (command substitution, parameter expansion) on the double-quoted string *before* `gh` ever receives it. A reply body containing `$(rm -rf ~)` would execute that command.
`POST /repos/{owner}/{repo}/pulls/{pull_number}/comments/{comment_id}/replies` is the dedicated review-comment reply endpoint and produces a threaded reply on the same line anchor.
6. **Update state.** Extend the existing `(pr, round, phase)` state entry in `.zooplankton-state.json` with a `replied_comment_ids: [<comment_id>, ...]` field — append each successfully replied-to comment ID. This prevents the same comment from being replied to twice across re-runs of the round. Skipped comments (no-match fallback) and failed replies (after the single retry) are **not** appended, so a re-run can attempt them again.
### Failure handling
- A 404 on a `comment_id` (e.g. the parent review was deleted) is logged as a warning; do not abort the round.
- A 422/403 on reply posting is retried once. If it still fails, log a warning and continue — partial replies are acceptable; the consolidated review for the current round is the authoritative surface.
## False-Positive Triage

@@ -118,0 +272,0 @@

@@ -147,4 +147,8 @@ ---

After FP triage, apply (post-triage blocking count):
**§4d.Replies — Round 2+ Resolution Replies** (run AFTER §4d.FP, GitHub mode + Round ≥ 2 only): post one threaded reply to each prior-round inline comment, using the current round's reviewer `prior_issue_resolution[]` evaluations together with the §4d.FP triage map to choose between "Carried forward" and "Dismissed as false positive" templates. See `references/review-protocol.md` → **Round 2+ Resolution Replies**. *(Round 1 or local mode: skip.)*
> Resolution replies for NOT ADDRESSED findings must run **after** §4d.FP triage so the orchestrator knows which findings were marked `FALSE_POSITIVE` in the current round and can render the correct reply template.
After FP triage and replies, apply (post-triage blocking count):
| Condition | Action |

@@ -151,0 +155,0 @@ |-----------|--------|