@toolu/opencode 6.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/generated/GENERATED-NOTES.md +16 -0
- package/generated/agents/toolu--architect.md +52 -0
- package/generated/agents/toolu--deep-explore.md +65 -0
- package/generated/agents/toolu--implementer.md +50 -0
- package/generated/agents/toolu--quick-task.md +54 -0
- package/generated/agents/toolu--research-agent.md +101 -0
- package/generated/commands/toolu--commit.md +8 -0
- package/generated/commands/toolu--review-and-commit.md +8 -0
- package/generated/opencode.toolu.json +116 -0
- package/generated/skills/toolu--brainstorm/SKILL.md +57 -0
- package/generated/skills/toolu--brainstorm/references/design-questions.md +36 -0
- package/generated/skills/toolu--commit--skill/SKILL.md +11 -0
- package/generated/skills/toolu--debug/SKILL.md +54 -0
- package/generated/skills/toolu--deep-research/SKILL.md +51 -0
- package/generated/skills/toolu--execution/SKILL.md +77 -0
- package/generated/skills/toolu--orchestrator/SKILL.md +145 -0
- package/generated/skills/toolu--orchestrator/references/delegation-cost.md +104 -0
- package/generated/skills/toolu--orchestrator/references/model-routing.md +112 -0
- package/generated/skills/toolu--plan/SKILL.md +56 -0
- package/generated/skills/toolu--plan/references/ledger.md +18 -0
- package/generated/skills/toolu--plan-review/SKILL.md +48 -0
- package/generated/skills/toolu--review-and-commit--skill/SKILL.md +10 -0
- package/generated/skills/toolu--setup/SKILL.md +24 -0
- package/generated/skills/toolu--setup/scripts/setup.sh +206 -0
- package/generated/skills/toolu--spec/SKILL.md +67 -0
- package/generated/skills/toolu--spec-review/SKILL.md +50 -0
- package/generated/skills/toolu--test/SKILL.md +49 -0
- package/package.json +51 -0
- package/plugins/agent-browser/.claude-plugin/plugin.json +18 -0
- package/plugins/agent-browser/.codex-plugin/plugin.json +7 -0
- package/plugins/agent-browser/README.md +18 -0
- package/plugins/agent-browser/hooks/hooks.json +15 -0
- package/plugins/agent-browser/hooks/session-start.sh +42 -0
- package/plugins/agent-browser/skills/agent-browser/SKILL.md +77 -0
- package/plugins/agent-browser/skills/agent-browser/scripts/agent-browser.sh +80 -0
- package/plugins/ast-grep/.claude-plugin/plugin.json +18 -0
- package/plugins/ast-grep/.codex-plugin/plugin.json +7 -0
- package/plugins/ast-grep/README.md +21 -0
- package/plugins/ast-grep/hooks/hooks.json +15 -0
- package/plugins/ast-grep/hooks/post-tools.d/byte-savings.sh +67 -0
- package/plugins/ast-grep/hooks/pre-tools.d/search-nudge.sh +170 -0
- package/plugins/ast-grep/hooks/register.sh +68 -0
- package/plugins/ast-grep/scripts/byte-savings-report.sh +28 -0
- package/plugins/ast-grep/skills/ast-grep/SKILL.md +127 -0
- package/plugins/ast-grep/skills/ast-grep/references/ast-grep-advanced.md +44 -0
- package/plugins/ast-grep/skills/ast-grep/scripts/ast-grep.sh +103 -0
- package/plugins/context7/.claude-plugin/plugin.json +18 -0
- package/plugins/context7/.codex-plugin/plugin.json +7 -0
- package/plugins/context7/README.md +19 -0
- package/plugins/context7/hooks/hooks.json +15 -0
- package/plugins/context7/hooks/session-start.sh +42 -0
- package/plugins/context7/skills/context7/SKILL.md +64 -0
- package/plugins/context7/skills/context7/scripts/search.sh +151 -0
- package/plugins/exa-search/.claude-plugin/plugin.json +18 -0
- package/plugins/exa-search/.codex-plugin/plugin.json +7 -0
- package/plugins/exa-search/README.md +19 -0
- package/plugins/exa-search/hooks/hooks.json +15 -0
- package/plugins/exa-search/hooks/session-start.sh +34 -0
- package/plugins/exa-search/skills/exa-search/SKILL.md +91 -0
- package/plugins/exa-search/skills/exa-search/scripts/search.sh +221 -0
- package/plugins/jev/.claude-plugin/plugin.json +19 -0
- package/plugins/jev/.codex-plugin/plugin.json +7 -0
- package/plugins/jev/README.md +61 -0
- package/plugins/jev/hooks/hooks.json +25 -0
- package/plugins/jev/hooks/lib/common.sh +49 -0
- package/plugins/jev/hooks/session-start.sh +45 -0
- package/plugins/jev/hooks/user-prompt-submit.sh +41 -0
- package/plugins/jev/skills/jev/SKILL.md +81 -0
- package/plugins/jev/skills/jev/evals/README.md +98 -0
- package/plugins/jev/skills/jev/references/problem-solving.md +198 -0
- package/plugins/jev/skills/jev/scripts/jev.sh +405 -0
- package/plugins/jira/.claude-plugin/plugin.json +18 -0
- package/plugins/jira/.codex-plugin/plugin.json +7 -0
- package/plugins/jira/README.md +42 -0
- package/plugins/jira/hooks/hooks.json +15 -0
- package/plugins/jira/hooks/session-start.sh +34 -0
- package/plugins/jira/skills/jira/SKILL.md +123 -0
- package/plugins/jira/skills/jira/scripts/jira.sh +70 -0
- package/plugins/jira/skills/jira/scripts/lib/adf.sh +17 -0
- package/plugins/jira/skills/jira/scripts/lib/attachment.sh +94 -0
- package/plugins/jira/skills/jira/scripts/lib/board.sh +58 -0
- package/plugins/jira/skills/jira/scripts/lib/http.sh +184 -0
- package/plugins/jira/skills/jira/scripts/lib/issue.sh +159 -0
- package/plugins/jira/skills/jira/scripts/lib/paginate.sh +45 -0
- package/plugins/jira/skills/jira/scripts/lib/plan-parse.sh +70 -0
- package/plugins/jira/skills/jira/scripts/lib/plan-run.sh +115 -0
- package/plugins/jira/skills/jira/scripts/lib/plan-store.sh +168 -0
- package/plugins/jira/skills/jira/scripts/lib/plan.sh +108 -0
- package/plugins/jira/skills/jira/scripts/lib/project.sh +42 -0
- package/plugins/jira/skills/jira/scripts/lib/raw.sh +15 -0
- package/plugins/jira/skills/jira/scripts/lib/search.sh +41 -0
- package/plugins/jira/skills/jira/scripts/lib/sprint.sh +88 -0
- package/plugins/jira/skills/jira/scripts/lib/user.sh +45 -0
- package/plugins/jira/skills/jira/scripts/lib/worklog.sh +56 -0
- package/plugins/pr-babysit/.claude-plugin/plugin.json +25 -0
- package/plugins/pr-babysit/.codex-plugin/plugin.json +7 -0
- package/plugins/pr-babysit/README.md +38 -0
- package/plugins/pr-babysit/commands/babysit.md +13 -0
- package/plugins/pr-babysit/hooks/check-toolu.sh +20 -0
- package/plugins/pr-babysit/hooks/hooks.json +15 -0
- package/plugins/pr-babysit/scripts/babysit-tick.sh +133 -0
- package/plugins/pr-babysit/scripts/collect-pr.sh +224 -0
- package/plugins/pr-babysit/scripts/lib/common.sh +146 -0
- package/plugins/pr-babysit/scripts/lib/gh.sh +150 -0
- package/plugins/pr-babysit/scripts/lib/lock.sh +84 -0
- package/plugins/pr-babysit/scripts/lib/normalize.sh +98 -0
- package/plugins/pr-babysit/scripts/lib/state.sh +37 -0
- package/plugins/pr-babysit/scripts/parse-verdict.sh +171 -0
- package/plugins/pr-babysit/scripts/record.sh +69 -0
- package/plugins/pr-babysit/scripts/reduce-state.sh +245 -0
- package/plugins/pr-babysit/scripts/reply-thread.sh +90 -0
- package/plugins/pr-babysit/scripts/resolve-thread.sh +71 -0
- package/plugins/pr-babysit/skills/babysit/SKILL.md +31 -0
- package/plugins/pr-babysit/skills/babysit/references/helper.md +209 -0
- package/plugins/pr-babysit/workflows/babysit.md +722 -0
- package/plugins/python-quality/.claude-plugin/plugin.json +24 -0
- package/plugins/python-quality/.codex-plugin/plugin.json +6 -0
- package/plugins/python-quality/README.md +23 -0
- package/plugins/python-quality/hooks/check-toolu.sh +20 -0
- package/plugins/python-quality/hooks/concerns/00-preamble.sh +85 -0
- package/plugins/python-quality/hooks/concerns/10-size-file.sh +6 -0
- package/plugins/python-quality/hooks/concerns/20-tests.sh +37 -0
- package/plugins/python-quality/hooks/concerns/30-suppression.sh +33 -0
- package/plugins/python-quality/hooks/concerns/50-size-fn.sh +49 -0
- package/plugins/python-quality/hooks/concerns/70-no-mocks.sh +76 -0
- package/plugins/python-quality/hooks/concerns/90-docs.sh +52 -0
- package/plugins/python-quality/hooks/concerns/99-finalize.sh +36 -0
- package/plugins/python-quality/hooks/hooks.json +19 -0
- package/plugins/python-quality/hooks/register.sh +65 -0
- package/plugins/rust-quality/.claude-plugin/plugin.json +24 -0
- package/plugins/rust-quality/.codex-plugin/plugin.json +6 -0
- package/plugins/rust-quality/README.md +24 -0
- package/plugins/rust-quality/hooks/check-toolu.sh +20 -0
- package/plugins/rust-quality/hooks/concerns/00-preamble.sh +93 -0
- package/plugins/rust-quality/hooks/concerns/10-size-file.sh +10 -0
- package/plugins/rust-quality/hooks/concerns/20-tests.sh +84 -0
- package/plugins/rust-quality/hooks/concerns/30-suppression.sh +26 -0
- package/plugins/rust-quality/hooks/concerns/40-unsafe.sh +39 -0
- package/plugins/rust-quality/hooks/concerns/50-size-fn.sh +38 -0
- package/plugins/rust-quality/hooks/concerns/55-size-impl.sh +28 -0
- package/plugins/rust-quality/hooks/concerns/60-error-handling.sh +149 -0
- package/plugins/rust-quality/hooks/concerns/70-no-mocks.sh +139 -0
- package/plugins/rust-quality/hooks/concerns/90-docs.sh +32 -0
- package/plugins/rust-quality/hooks/concerns/99-finalize.sh +36 -0
- package/plugins/rust-quality/hooks/hooks.json +19 -0
- package/plugins/rust-quality/hooks/register.sh +65 -0
- package/plugins/statusline/.claude-plugin/plugin.json +19 -0
- package/plugins/statusline/.codex-plugin/plugin.json +7 -0
- package/plugins/statusline/README.md +108 -0
- package/plugins/statusline/commands/setup.md +30 -0
- package/plugins/statusline/hooks/hooks.json +15 -0
- package/plugins/statusline/hooks/session-start.sh +45 -0
- package/plugins/statusline/scripts/collect-status.sh +147 -0
- package/plugins/statusline/scripts/setup.sh +93 -0
- package/plugins/statusline/scripts/status.sh +55 -0
- package/plugins/statusline/skills/status/SKILL.md +13 -0
- package/plugins/statusline/statusline.sh +177 -0
- package/plugins/toolu/.claude-plugin/plugin.json +19 -0
- package/plugins/toolu/.codex-plugin/plugin.json +7 -0
- package/plugins/toolu/README.md +35 -0
- package/plugins/toolu/agents/.gitkeep +0 -0
- package/plugins/toolu/agents/architect.md +52 -0
- package/plugins/toolu/agents/deep-explore.md +65 -0
- package/plugins/toolu/agents/implementer.md +50 -0
- package/plugins/toolu/agents/quick-task.md +54 -0
- package/plugins/toolu/agents/research-agent.md +107 -0
- package/plugins/toolu/assets/agents/architect.toml +9 -0
- package/plugins/toolu/assets/agents/deep-explore.toml +9 -0
- package/plugins/toolu/assets/agents/implementer.toml +9 -0
- package/plugins/toolu/assets/agents/quick-task.toml +9 -0
- package/plugins/toolu/assets/agents/research-agent.toml +9 -0
- package/plugins/toolu/commands/.gitkeep +0 -0
- package/plugins/toolu/commands/commit.md +5 -0
- package/plugins/toolu/commands/review-and-commit.md +5 -0
- package/plugins/toolu/hooks/docs/gates.md +157 -0
- package/plugins/toolu/hooks/docs/model-routing.md +14 -0
- package/plugins/toolu/hooks/docs/post-compaction.md +3 -0
- package/plugins/toolu/hooks/docs/push-review.md +123 -0
- package/plugins/toolu/hooks/docs/session-start-python.md +3 -0
- package/plugins/toolu/hooks/docs/session-start-rust.md +4 -0
- package/plugins/toolu/hooks/docs/session-start-ts.md +3 -0
- package/plugins/toolu/hooks/docs/session-start.md +16 -0
- package/plugins/toolu/hooks/hooks.json +76 -0
- package/plugins/toolu/hooks/lib/config.sh +355 -0
- package/plugins/toolu/hooks/lib/detect.sh +929 -0
- package/plugins/toolu/hooks/lib/diff-sha.sh +32 -0
- package/plugins/toolu/hooks/lib/dispatch.sh +365 -0
- package/plugins/toolu/hooks/lib/docs-sync-config.sh +81 -0
- package/plugins/toolu/hooks/lib/edit-records.sh +157 -0
- package/plugins/toolu/hooks/lib/gate-file.sh +201 -0
- package/plugins/toolu/hooks/lib/gate-mode.sh +244 -0
- package/plugins/toolu/hooks/lib/host.sh +205 -0
- package/plugins/toolu/hooks/lib/permissions.sh +125 -0
- package/plugins/toolu/hooks/lib/plan-ledger-parse.sh +219 -0
- package/plugins/toolu/hooks/lib/plan-ledger-preflight.sh +137 -0
- package/plugins/toolu/hooks/lib/plan-ledger.sh +749 -0
- package/plugins/toolu/hooks/lib/push-waiver.sh +116 -0
- package/plugins/toolu/hooks/lib/quality-config.sh +184 -0
- package/plugins/toolu/hooks/lib/registry.sh +72 -0
- package/plugins/toolu/hooks/lib/state-sweeper.sh +194 -0
- package/plugins/toolu/hooks/lib/telemetry.sh +94 -0
- package/plugins/toolu/hooks/lib/verdict.sh +477 -0
- package/plugins/toolu/hooks/post-tools/mod.sh +42 -0
- package/plugins/toolu/hooks/post-tools/modules/gate-status.sh +140 -0
- package/plugins/toolu/hooks/post-tools/modules/push-waiver.sh +97 -0
- package/plugins/toolu/hooks/pre-compact.sh +18 -0
- package/plugins/toolu/hooks/pre-tools/agent-tier.sh +111 -0
- package/plugins/toolu/hooks/pre-tools/mod.sh +37 -0
- package/plugins/toolu/hooks/pre-tools/modules/bash-commands.sh +187 -0
- package/plugins/toolu/hooks/pre-tools/modules/code-edit-rules.sh +78 -0
- package/plugins/toolu/hooks/pre-tools/modules/commit-gate.sh +70 -0
- package/plugins/toolu/hooks/pre-tools/modules/docs-sync.sh +137 -0
- package/plugins/toolu/hooks/pre-tools/modules/mcp-blocker.sh +151 -0
- package/plugins/toolu/hooks/pre-tools/modules/plan-ledger.sh +201 -0
- package/plugins/toolu/hooks/pre-tools/modules/protected-files.sh +177 -0
- package/plugins/toolu/hooks/pre-tools/modules/push-review.sh +273 -0
- package/plugins/toolu/hooks/pre-tools/modules/quality-gate.sh +87 -0
- package/plugins/toolu/hooks/session-start.sh +379 -0
- package/plugins/toolu/hooks/user-prompt-submit.sh +197 -0
- package/plugins/toolu/scripts/context-budget.sh +111 -0
- package/plugins/toolu/scripts/debug-log.sh +98 -0
- package/plugins/toolu/scripts/debug-stack.sh +108 -0
- package/plugins/toolu/scripts/debug-testfail.sh +97 -0
- package/plugins/toolu/settings/.gitkeep +0 -0
- package/plugins/toolu/settings/README.md +127 -0
- package/plugins/toolu/settings/bash-allowlist.txt +12 -0
- package/plugins/toolu/settings/bash-denylist.txt +25 -0
- package/plugins/toolu/settings/code-edit-rules.json +67 -0
- package/plugins/toolu/settings/commit-prefixes.txt +12 -0
- package/plugins/toolu/settings/mcp-blocklist.txt +8 -0
- package/plugins/toolu/settings/permissions.fragment.json +36 -0
- package/plugins/toolu/settings/protected-files.txt +30 -0
- package/plugins/toolu/settings/rust-unsafe-exemptions.txt +5 -0
- package/plugins/toolu/settings/toolu.config.example.json +130 -0
- package/plugins/toolu/skills/.gitkeep +0 -0
- package/plugins/toolu/skills/brainstorm/SKILL.md +57 -0
- package/plugins/toolu/skills/brainstorm/references/design-questions.md +36 -0
- package/plugins/toolu/skills/commit/SKILL.md +11 -0
- package/plugins/toolu/skills/debug/SKILL.md +54 -0
- package/plugins/toolu/skills/debug/evals/README.md +17 -0
- package/plugins/toolu/skills/debug/evals/cases.json +24 -0
- package/plugins/toolu/skills/debug/evals/results.json +34 -0
- package/plugins/toolu/skills/deep-research/SKILL.md +51 -0
- package/plugins/toolu/skills/execution/SKILL.md +77 -0
- package/plugins/toolu/skills/orchestrator/SKILL.md +145 -0
- package/plugins/toolu/skills/orchestrator/references/delegation-cost.md +104 -0
- package/plugins/toolu/skills/orchestrator/references/model-routing.md +112 -0
- package/plugins/toolu/skills/plan/SKILL.md +56 -0
- package/plugins/toolu/skills/plan/references/ledger.md +18 -0
- package/plugins/toolu/skills/plan-review/SKILL.md +48 -0
- package/plugins/toolu/skills/review-and-commit/SKILL.md +10 -0
- package/plugins/toolu/skills/setup/SKILL.md +24 -0
- package/plugins/toolu/skills/setup/scripts/setup.sh +206 -0
- package/plugins/toolu/skills/spec/SKILL.md +67 -0
- package/plugins/toolu/skills/spec-review/SKILL.md +50 -0
- package/plugins/toolu/skills/test/SKILL.md +49 -0
- package/plugins/toolu/workflows/commit.md +21 -0
- package/plugins/toolu/workflows/host-mapping.md +22 -0
- package/plugins/toolu/workflows/review-and-commit.md +54 -0
- package/plugins/toolu/workflows/semantic-judgments.md +35 -0
- package/plugins/toolu-review/.claude-plugin/plugin.json +18 -0
- package/plugins/toolu-review/.codex-plugin/plugin.json +7 -0
- package/plugins/toolu-review/README.md +17 -0
- package/plugins/toolu-review/hooks/hooks.json +15 -0
- package/plugins/toolu-review/hooks/session-start.sh +34 -0
- package/plugins/toolu-review/skills/review/SKILL.md +90 -0
- package/plugins/toolu-review/skills/review/scripts/write-state.sh +189 -0
- package/plugins/ts-quality/.claude-plugin/plugin.json +24 -0
- package/plugins/ts-quality/.codex-plugin/plugin.json +6 -0
- package/plugins/ts-quality/README.md +24 -0
- package/plugins/ts-quality/hooks/check-toolu.sh +20 -0
- package/plugins/ts-quality/hooks/concerns/00-preamble.sh +123 -0
- package/plugins/ts-quality/hooks/concerns/10-imports.sh +33 -0
- package/plugins/ts-quality/hooks/concerns/15-type-as.sh +9 -0
- package/plugins/ts-quality/hooks/concerns/20-tests.sh +26 -0
- package/plugins/ts-quality/hooks/concerns/25-size-file.sh +40 -0
- package/plugins/ts-quality/hooks/concerns/30-size-fn.sh +53 -0
- package/plugins/ts-quality/hooks/concerns/35-react-hooks.sh +7 -0
- package/plugins/ts-quality/hooks/concerns/40-factory.sh +5 -0
- package/plugins/ts-quality/hooks/concerns/45-typeguard.sh +6 -0
- package/plugins/ts-quality/hooks/concerns/50-type-dup.sh +16 -0
- package/plugins/ts-quality/hooks/concerns/55-naming.sh +11 -0
- package/plugins/ts-quality/hooks/concerns/60-console.sh +6 -0
- package/plugins/ts-quality/hooks/concerns/65-suppression.sh +12 -0
- package/plugins/ts-quality/hooks/concerns/70-ui-confirm.sh +8 -0
- package/plugins/ts-quality/hooks/concerns/72-ui-radix.sh +12 -0
- package/plugins/ts-quality/hooks/concerns/74-react-props.sh +6 -0
- package/plugins/ts-quality/hooks/concerns/76-toast.sh +10 -0
- package/plugins/ts-quality/hooks/concerns/78-error-ast.sh +242 -0
- package/plugins/ts-quality/hooks/concerns/80-throw-literal.sh +23 -0
- package/plugins/ts-quality/hooks/concerns/85-no-mocks.sh +109 -0
- package/plugins/ts-quality/hooks/concerns/90-duplication.sh +30 -0
- package/plugins/ts-quality/hooks/concerns/92-docs.sh +36 -0
- package/plugins/ts-quality/hooks/concerns/94-handler.sh +15 -0
- package/plugins/ts-quality/hooks/concerns/99-finalize.sh +46 -0
- package/plugins/ts-quality/hooks/hooks.json +19 -0
- package/plugins/ts-quality/hooks/register.sh +65 -0
- package/src/adapter/__tests__/evaluate.test.ts +82 -0
- package/src/adapter/__tests__/permission-map.test.ts +91 -0
- package/src/adapter/evaluate.ts +54 -0
- package/src/adapter/permission-map.ts +177 -0
- package/src/bootstrap/__tests__/bootstrap.test.ts +148 -0
- package/src/bootstrap/entrypoint.ts +18 -0
- package/src/bootstrap/index.ts +5 -0
- package/src/bootstrap/readiness.ts +59 -0
- package/src/bootstrap/result.ts +21 -0
- package/src/bootstrap/runtime.ts +99 -0
- package/src/bootstrap/test-helpers.ts +25 -0
- package/src/host/__tests__/host.test.ts +37 -0
- package/src/host/detect.ts +46 -0
- package/src/host/index.ts +10 -0
- package/src/host/roots.ts +59 -0
- package/src/host/types.ts +14 -0
- package/src/inventory/__tests__/inventory.test.ts +69 -0
- package/src/inventory/index.ts +9 -0
- package/src/inventory/manifest.ts +46 -0
- package/src/inventory/scan.ts +66 -0
- package/src/inventory/selection.ts +92 -0
- package/src/inventory/types.ts +22 -0
- package/src/lifecycle/__tests__/lifecycle.test.ts +7 -0
- package/src/lifecycle/index.ts +2 -0
- package/src/lifecycle/table.ts +42 -0
- package/src/plugin/__tests__/plugin-stub.test.ts +6 -0
- package/src/plugin/plugin-stub.ts +7 -0
- package/src/plugin/toolu.ts +142 -0
- package/src/preflight/__tests__/preflight.test.ts +20 -0
- package/src/preflight/check.ts +75 -0
- package/src/preflight/index.ts +2 -0
- package/src/select/__tests__/select.test.ts +52 -0
- package/src/select/index.ts +2 -0
- package/src/select/resolve.ts +87 -0
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# reply-thread.sh — post one reply (review thread, conversation, or review-
|
|
3
|
+
# level) and record it in slot state so it is never posted twice.
|
|
4
|
+
#
|
|
5
|
+
# Usage: reply-thread.sh --state-file <path> --kind thread|conversation|review
|
|
6
|
+
# (--thread <graphqlId> --root-comment <databaseId> --in-reply-to <databaseId>
|
|
7
|
+
# | --comment-id <id> | --review-id <id>)
|
|
8
|
+
# --body-file <path> [--timeout SECONDS]
|
|
9
|
+
#
|
|
10
|
+
# Idempotency key = the reviewer comment being answered:
|
|
11
|
+
# thread:<threadId>@<inReplyTo> · conversation:<id> · review:<id>
|
|
12
|
+
# A key already present in actions.replied → exit 4 duplicate_reply, nothing
|
|
13
|
+
# posted. A later reviewer follow-up carries a new inReplyTo, so it is
|
|
14
|
+
# answerable. The body comes from a file so untrusted text never enters argv.
|
|
15
|
+
# The agent writes the words; this script makes the call land or says why not.
|
|
16
|
+
# Exit: 0 posted (stdout {ok,key,commentId,url}) · 2 usage · 3 api_error |
|
|
17
|
+
# state_malformed · 4 duplicate_reply · 75 locked.
|
|
18
|
+
set -euo pipefail
|
|
19
|
+
|
|
20
|
+
PB_SCRIPT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd -P)
|
|
21
|
+
# shellcheck source=lib/common.sh
|
|
22
|
+
. "$PB_SCRIPT_DIR/lib/common.sh"
|
|
23
|
+
# shellcheck source=lib/lock.sh
|
|
24
|
+
. "$PB_SCRIPT_DIR/lib/lock.sh"
|
|
25
|
+
# shellcheck source=lib/gh.sh
|
|
26
|
+
. "$PB_SCRIPT_DIR/lib/gh.sh"
|
|
27
|
+
# shellcheck source=lib/state.sh
|
|
28
|
+
. "$PB_SCRIPT_DIR/lib/state.sh"
|
|
29
|
+
|
|
30
|
+
state_file=""; kind=""; thread=""; root=""; in_reply_to=""; comment_id=""; review_id=""; body_file=""
|
|
31
|
+
while [ $# -gt 0 ]; do
|
|
32
|
+
case "$1" in
|
|
33
|
+
--state-file) state_file="${2:-}"; shift 2 ;;
|
|
34
|
+
--kind) kind="${2:-}"; shift 2 ;;
|
|
35
|
+
--thread) thread="${2:-}"; shift 2 ;;
|
|
36
|
+
--root-comment) root="${2:-}"; shift 2 ;;
|
|
37
|
+
--in-reply-to) in_reply_to="${2:-}"; shift 2 ;;
|
|
38
|
+
--comment-id) comment_id="${2:-}"; shift 2 ;;
|
|
39
|
+
--review-id) review_id="${2:-}"; shift 2 ;;
|
|
40
|
+
--body-file) body_file="${2:-}"; shift 2 ;;
|
|
41
|
+
--timeout) PB_GH_TIMEOUT="${2:-}"; shift 2 ;;
|
|
42
|
+
*) pb_fail usage "reply-thread.sh: unknown argument: $1" ;;
|
|
43
|
+
esac
|
|
44
|
+
done
|
|
45
|
+
[ -n "$state_file" ] || pb_fail usage "reply-thread.sh: --state-file required"
|
|
46
|
+
[ -n "$body_file" ] && [ -f "$body_file" ] || pb_fail usage "reply-thread.sh: --body-file <existing file> required"
|
|
47
|
+
[ -s "$body_file" ] || pb_fail usage "reply-thread.sh: reply body is empty"
|
|
48
|
+
# GitHub caps comment bodies at 65536 characters; refuse before the request.
|
|
49
|
+
[ "$(wc -c <"$body_file" | tr -d ' ')" -le 65536 ] || pb_fail usage "reply-thread.sh: reply body exceeds GitHub's 65536-character limit"
|
|
50
|
+
case "$kind" in
|
|
51
|
+
thread)
|
|
52
|
+
[[ "$thread" =~ ^[A-Za-z0-9_=-]+$ ]] || pb_fail usage "reply-thread.sh: --thread <graphqlId> required for --kind thread"
|
|
53
|
+
[[ "$root" =~ ^[0-9]+$ ]] || pb_fail usage "reply-thread.sh: --root-comment <databaseId> required for --kind thread"
|
|
54
|
+
[[ "$in_reply_to" =~ ^[0-9]+$ ]] || pb_fail usage "reply-thread.sh: --in-reply-to <databaseId> required for --kind thread"
|
|
55
|
+
key="thread:${thread}@${in_reply_to}" ;;
|
|
56
|
+
conversation)
|
|
57
|
+
[[ "$comment_id" =~ ^[0-9]+$ ]] || pb_fail usage "reply-thread.sh: --comment-id <id> required for --kind conversation"
|
|
58
|
+
key="conversation:${comment_id}" ;;
|
|
59
|
+
review)
|
|
60
|
+
[[ "$review_id" =~ ^[0-9]+$ ]] || pb_fail usage "reply-thread.sh: --review-id <id> required for --kind review"
|
|
61
|
+
key="review:${review_id}" ;;
|
|
62
|
+
*) pb_fail usage "reply-thread.sh: --kind must be thread, conversation or review" ;;
|
|
63
|
+
esac
|
|
64
|
+
|
|
65
|
+
pb_require jq gh
|
|
66
|
+
pb_init
|
|
67
|
+
pb_mktmpdir
|
|
68
|
+
pb_lock_acquire "$state_file" || pb_lock_fail "$state_file"
|
|
69
|
+
pb_state_load "$state_file"
|
|
70
|
+
|
|
71
|
+
# Idempotency: refuse before any request.
|
|
72
|
+
if jq -e --arg k "$key" '.actions.replied[$k] != null' "$state_file" >/dev/null 2>&1; then
|
|
73
|
+
pb_fail duplicate_reply "reply-thread.sh: a reply to $key is already recorded; not posting again" \
|
|
74
|
+
"$(jq -c --arg k "$key" '{key:$k, recorded:.actions.replied[$k]}' "$state_file")"
|
|
75
|
+
fi
|
|
76
|
+
|
|
77
|
+
case "$kind" in
|
|
78
|
+
thread) endpoint="repos/$PB_STATE_REPO/pulls/$PB_STATE_NUMBER/comments/$root/replies" ;;
|
|
79
|
+
conversation|review) endpoint="repos/$PB_STATE_REPO/issues/$PB_STATE_NUMBER/comments" ;;
|
|
80
|
+
esac
|
|
81
|
+
jq -n --rawfile body "$body_file" '{body:$body}' >"$PB_TMPDIR/payload.json"
|
|
82
|
+
pb_gh "$PB_TMPDIR/reply.json" api --method POST "$endpoint" --input "$PB_TMPDIR/payload.json" || pb_gh_fail reply
|
|
83
|
+
jq -e '.id | type == "number"' "$PB_TMPDIR/reply.json" >/dev/null 2>&1 \
|
|
84
|
+
|| pb_fail invalid_json "reply-thread.sh: reply response carried no comment id" '{"source":"reply"}'
|
|
85
|
+
|
|
86
|
+
pb_state_update "$state_file" \
|
|
87
|
+
'.actions.replied[$k] = {commentId: $r.id, url: $r.html_url, at: $now, headSha: $head, kind: $kind}' \
|
|
88
|
+
--arg k "$key" --arg now "$(pb_now)" --arg head "$PB_STATE_HEAD" --arg kind "$kind" \
|
|
89
|
+
--argjson r "$(jq -c '{id, html_url}' "$PB_TMPDIR/reply.json")"
|
|
90
|
+
jq -c --arg k "$key" '{ok:true, key:$k, commentId:.id, url:.html_url}' "$PB_TMPDIR/reply.json"
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# resolve-thread.sh — resolve one review thread and CONFIRM it from the
|
|
3
|
+
# mutation response before recording it. A reply is never a resolve.
|
|
4
|
+
#
|
|
5
|
+
# Usage: resolve-thread.sh --state-file <path> --thread <graphqlId> [--timeout SECONDS]
|
|
6
|
+
#
|
|
7
|
+
# The mutation runs up to 3 times until the response shows
|
|
8
|
+
# thread.isResolved == true. Still false → exit 5 resolve_unconfirmed and the
|
|
9
|
+
# thread stays out of actions.resolved, so the next tick's resolution audit
|
|
10
|
+
# picks it up again. A thread already recorded as confirmed → exit 0 without
|
|
11
|
+
# any request. Transport failure after lib/gh.sh's bounded retry → exit 3.
|
|
12
|
+
# Exit: 0 confirmed (stdout {ok,thread,confirmed,attempts}) · 2 usage ·
|
|
13
|
+
# 3 api_error | state_malformed · 5 resolve_unconfirmed · 75 locked.
|
|
14
|
+
set -euo pipefail
|
|
15
|
+
|
|
16
|
+
PB_SCRIPT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd -P)
|
|
17
|
+
# shellcheck source=lib/common.sh
|
|
18
|
+
. "$PB_SCRIPT_DIR/lib/common.sh"
|
|
19
|
+
# shellcheck source=lib/lock.sh
|
|
20
|
+
. "$PB_SCRIPT_DIR/lib/lock.sh"
|
|
21
|
+
# shellcheck source=lib/gh.sh
|
|
22
|
+
. "$PB_SCRIPT_DIR/lib/gh.sh"
|
|
23
|
+
# shellcheck source=lib/state.sh
|
|
24
|
+
. "$PB_SCRIPT_DIR/lib/state.sh"
|
|
25
|
+
|
|
26
|
+
state_file=""; thread=""
|
|
27
|
+
while [ $# -gt 0 ]; do
|
|
28
|
+
case "$1" in
|
|
29
|
+
--state-file) state_file="${2:-}"; shift 2 ;;
|
|
30
|
+
--thread) thread="${2:-}"; shift 2 ;;
|
|
31
|
+
--timeout) PB_GH_TIMEOUT="${2:-}"; shift 2 ;;
|
|
32
|
+
*) pb_fail usage "resolve-thread.sh: unknown argument: $1" ;;
|
|
33
|
+
esac
|
|
34
|
+
done
|
|
35
|
+
[ -n "$state_file" ] || pb_fail usage "resolve-thread.sh: --state-file required"
|
|
36
|
+
# GraphQL node ids are base64url-ish; anything else is a caller bug, not a request.
|
|
37
|
+
[[ "$thread" =~ ^[A-Za-z0-9_=-]+$ ]] || pb_fail usage "resolve-thread.sh: --thread <graphqlId> required (got '${thread:-}')"
|
|
38
|
+
|
|
39
|
+
pb_require jq gh
|
|
40
|
+
pb_init
|
|
41
|
+
pb_mktmpdir
|
|
42
|
+
pb_lock_acquire "$state_file" || pb_lock_fail "$state_file"
|
|
43
|
+
pb_state_load "$state_file"
|
|
44
|
+
|
|
45
|
+
if jq -e --arg t "$thread" '.actions.resolved[$t].confirmed == true' "$state_file" >/dev/null 2>&1; then
|
|
46
|
+
jq -c --arg t "$thread" '{ok:true, thread:$t, confirmed:true, attempts:0, alreadyResolved:true, at:.actions.resolved[$t].at}' "$state_file"
|
|
47
|
+
exit 0
|
|
48
|
+
fi
|
|
49
|
+
|
|
50
|
+
mutation='mutation($threadId:ID!){ resolveReviewThread(input:{threadId:$threadId}){ thread{ id isResolved } } }'
|
|
51
|
+
attempt=0; confirmed=false; last=""
|
|
52
|
+
while [ "$attempt" -lt 3 ]; do
|
|
53
|
+
attempt=$((attempt + 1))
|
|
54
|
+
pb_gh "$PB_TMPDIR/resolve.json" api graphql -f threadId="$thread" -f query="$mutation" || pb_gh_fail resolve
|
|
55
|
+
pb_gh_json_ok "$PB_TMPDIR/resolve.json" || pb_fail invalid_json "resolve-thread.sh: mutation response carried errors[]" \
|
|
56
|
+
"$(jq -c '{source:"resolve", errors:(.errors // [])}' "$PB_TMPDIR/resolve.json")"
|
|
57
|
+
last=$(jq -c '.data.resolveReviewThread.thread // null' "$PB_TMPDIR/resolve.json")
|
|
58
|
+
if [ "$(jq -r '.isResolved // false' <<<"$last")" = true ]; then confirmed=true; break; fi
|
|
59
|
+
echo "resolve-thread.sh: attempt $attempt returned isResolved=false for $thread; retrying" >&2
|
|
60
|
+
sleep 1
|
|
61
|
+
done
|
|
62
|
+
|
|
63
|
+
if [ "$confirmed" != true ]; then
|
|
64
|
+
pb_fail resolve_unconfirmed "resolve-thread.sh: $thread still unresolved after $attempt attempt(s)" \
|
|
65
|
+
"$(jq -nc --arg t "$thread" --argjson attempts "$attempt" --argjson last "${last:-null}" '{thread:$t, attempts:$attempts, lastResponse:$last}')"
|
|
66
|
+
fi
|
|
67
|
+
|
|
68
|
+
pb_state_update "$state_file" \
|
|
69
|
+
'.actions.resolved[$t] = {confirmed: true, at: $now, attempts: $attempts, headSha: $head}' \
|
|
70
|
+
--arg t "$thread" --arg now "$(pb_now)" --argjson attempts "$attempt" --arg head "$PB_STATE_HEAD"
|
|
71
|
+
jq -nc --arg t "$thread" --argjson attempts "$attempt" '{ok:true, thread:$t, confirmed:true, attempts:$attempts}'
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: babysit
|
|
3
|
+
description: Use when the user explicitly asks Codex, or an authorized verified execution handoff invokes it, to monitor and autonomously clear the current branch's pull request until CI, review threads, and the review-bot verdict are all green.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Babysit a PR
|
|
7
|
+
|
|
8
|
+
This no-argument invocation explicitly authorizes one durable babysitting goal
|
|
9
|
+
for the current repository and PR. A verified execution handoff is sufficient authorization
|
|
10
|
+
when execution already confirmed delivery authorization, GitHub
|
|
11
|
+
auth, a non-default branch, and this installed plugin; do not ask again or
|
|
12
|
+
introduce handoff arguments. Read [the canonical workflow](../../workflows/babysit.md)
|
|
13
|
+
completely and follow only its Codex controller branches plus every shared
|
|
14
|
+
strict-clearance step. Each tick is one command — the shipped
|
|
15
|
+
`scripts/babysit-tick.sh` under this plugin's root (`../../scripts/babysit-tick.sh`
|
|
16
|
+
relative to this file), with `--state-file` set to the Codex slot path; its output contract is [references/helper.md](references/helper.md).
|
|
17
|
+
Trust that result: never write a polling script or controller of your own,
|
|
18
|
+
never re-fetch with ad-hoc `gh` calls what the result already reports, and act
|
|
19
|
+
through `reply-thread.sh`, `resolve-thread.sh` and `record.sh`.
|
|
20
|
+
|
|
21
|
+
Use `get_goal` before `create_goal`; keep one active goal for the resolved
|
|
22
|
+
repository/PR. Continue with bounded cycles: use the native `wait` mechanism for
|
|
23
|
+
at most 60 seconds, persist the exact slot state, and allow later goal
|
|
24
|
+
continuations to resume. Pending checks are not completion or blockage.
|
|
25
|
+
|
|
26
|
+
Call `update_goal` with `complete` only after the same-cycle success audit proves
|
|
27
|
+
CI green, zero unresolved threads, and an approved zero-finding bot verdict.
|
|
28
|
+
Call it with `blocked` only for a genuine human-only escalation after the same
|
|
29
|
+
blocker has met Codex's consecutive-goal-turn threshold. For `stop` or `cancel`,
|
|
30
|
+
perform the canonical local cleanup and tell the user to cancel through Codex's
|
|
31
|
+
goal control; cancellation is not a completion status.
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
# pr-babysit helper contract
|
|
2
|
+
|
|
3
|
+
The tick helper is the deterministic half of babysitting. It lives under the
|
|
4
|
+
plugin's `scripts/` directory, is plain bash (`gh` and `jq` only; bash 3.2 — `git`
|
|
5
|
+
is used by the workflow's worktree steps, not by these scripts), and
|
|
6
|
+
is the same on Claude Code and Codex — only `--state-file` differs by host.
|
|
7
|
+
Read this file instead of the script sources: every field the agent may act
|
|
8
|
+
on is listed here. Fields not listed are not part of the contract.
|
|
9
|
+
|
|
10
|
+
## Scripts
|
|
11
|
+
|
|
12
|
+
| Script | Role | Exit codes |
|
|
13
|
+
|---|---|---|
|
|
14
|
+
| `babysit-tick.sh` | **The tick.** Lock the slot → collect → reduce → persist → print the result. The only read-side command the workflow runs. | `0` result · `2` usage · `3` structured error (state preserved, `pr.lastError` stamped) · `75` slot locked |
|
|
15
|
+
| `collect-pr.sh` | Read side: PR metadata, review threads (paginated, nested comment pages), issue comments, reviews; head re-checked after the fan-out; bot verdict via `parse-verdict.sh`. Writes one snapshot. | `0` · `2` · `3` |
|
|
16
|
+
| `reduce-state.sh` | Pure decision layer: snapshot + prior state + `--now` → next state + result. No network, no clock. | `0` · `2` · `3` |
|
|
17
|
+
| `reply-thread.sh` | Write side: post one reply (thread / conversation / review-level), idempotent per reviewer comment. | `0` · `2` · `3` · `4` duplicate_reply · `75` |
|
|
18
|
+
| `resolve-thread.sh` | Write side: `resolveReviewThread`, confirmed from the response, retried, recorded. | `0` · `2` · `3` · `5` resolve_unconfirmed · `75` |
|
|
19
|
+
| `record.sh` | Hand agent decisions to the reducer: `round`, `flag-injection`, `status`. | `0` · `2` · `3` · `75` |
|
|
20
|
+
| `parse-verdict.sh` | Existing verdict parser; unchanged. | — |
|
|
21
|
+
|
|
22
|
+
Structured errors are one JSON document on stdout:
|
|
23
|
+
`{"version":1,"errors":[{"code":"…","message":"…", …}]}`. `code` is a closed set:
|
|
24
|
+
`usage`, `gh_unavailable`, `jq_required`, `api_error`, `invalid_json`,
|
|
25
|
+
`head_moved`, `state_malformed`, `slot_mismatch`, `locked`, `duplicate_reply`,
|
|
26
|
+
`resolve_unconfirmed`.
|
|
27
|
+
|
|
28
|
+
## `babysit-tick.sh`
|
|
29
|
+
|
|
30
|
+
```
|
|
31
|
+
babysit-tick.sh --repo <owner/repo> --pr <n> --state-file <path>
|
|
32
|
+
[--snapshot-out <path>] [--snapshot-in <path>]
|
|
33
|
+
[--page-size N] [--timeout SECONDS] [--now <iso8601>]
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
- `--state-file` — the host's slot path, verbatim: `/tmp/pr-babysit-<slot>.json`
|
|
37
|
+
(Claude) or `<repo>/.codex/tmp/pr-babysit/<slot>.json` (Codex). Created on the
|
|
38
|
+
first tick, resumed after.
|
|
39
|
+
- `--snapshot-out` — full snapshot (default `<state-file minus .json>.snapshot.json`).
|
|
40
|
+
- `--snapshot-in` — replay a captured snapshot instead of collecting (tests,
|
|
41
|
+
debugging). The workflow never passes it.
|
|
42
|
+
- `--timeout` — per `gh` call (default 60 s); every call retries 3× with 2 s /
|
|
43
|
+
4 s waits on timeouts, 5xx, 429, rate-limit 403 and connection errors, and
|
|
44
|
+
never on other 4xx.
|
|
45
|
+
- `--page-size` — GraphQL/REST page size (default 100). Exists so pagination is
|
|
46
|
+
testable on small real PRs.
|
|
47
|
+
|
|
48
|
+
The PR head is read before and after the fan-out; if it moved the collection
|
|
49
|
+
runs once more, then fails with `head_moved`. A tick never writes a partial
|
|
50
|
+
state: state and snapshot are written atomically (temp file + rename) and a
|
|
51
|
+
crash leaves the previous state and no temp file.
|
|
52
|
+
|
|
53
|
+
## Result (`version: 1`)
|
|
54
|
+
|
|
55
|
+
What the agent acts on. Printed on stdout by `babysit-tick.sh`.
|
|
56
|
+
|
|
57
|
+
| Field | Meaning |
|
|
58
|
+
|---|---|
|
|
59
|
+
| `slot` | `<owner>-<repo>-<number>`, lowercase. |
|
|
60
|
+
| `changed` | Any of `ciStatus, reviewDecision, mergeable, unresolvedThreads, headSha, botVerdict, botState, botFindingKeys` differs from the previous tick. `false` → silent tick. |
|
|
61
|
+
| `decision` | `keep_going` · `success` · `escalate` — the Step 6 rules, already applied. |
|
|
62
|
+
| `reasons[]` | `{code, detail}`; escalation codes first. Closed set below. |
|
|
63
|
+
| `pr` | `number, url, head, branch, base, author, state, mergeable, reviewDecision`. |
|
|
64
|
+
| `ci.status` | `pass` · `pending` · `fail` over `statusCheckRollup` (`CheckRun` and legacy `StatusContext`); an empty rollup is `pending`. |
|
|
65
|
+
| `ci.checks[]` | `{name, status, url}` per check. |
|
|
66
|
+
| `verdict.state` | `absent` · `unknown` · `in_progress` · `complete` · `provider_error` (parse-verdict.sh states, plus `absent` when no CI-reviewer comment exists). |
|
|
67
|
+
| `verdict.verdict` | `approved` · `changes` · `none`. |
|
|
68
|
+
| `verdict.findingsCount`, `findingKeys[]` | Round-level recurrence keys (`path:line:hash`). Never the finding source — the inline threads are. |
|
|
69
|
+
| `verdict.mustFix[]` | Top-N must-fix prose lines. Surface to the human; not resolvable threads. |
|
|
70
|
+
| `verdict.commentUrl`, `commentId` | The bot comment read. |
|
|
71
|
+
| `verdict.degraded`, `degradedReason` | `true` with `review_absent` or `review_unknown_format` when the verdict cannot be read; success is then the documented manual-verify fallback, never silent. |
|
|
72
|
+
| `verdict.sameRunAsLastTick` | The same bot comment id and `updatedAt` as last tick — a sticky verdict, not a new rejection. |
|
|
73
|
+
| `threads.total`, `unresolved` | All threads; the Resolution-audit set (`!isResolved && !isOutdated && !flagged`). `unresolved` must be `0` for success. |
|
|
74
|
+
| `threads.actionable[]` | Threads needing a NEW disposition this tick: `{id, path, line, isOutdated, rootCommentId, inReplyTo, authorClass, lastCommentAuthor, lastCommentAt, injectionSuspect, injectionPattern, comments[]}`. `authorClass` ∈ `ci_reviewer` (exact login set `github-actions`, `github-actions[bot]`, `claude`, `claude[bot]` — current Toolu Code Review `@v8` posts as `github-actions[bot]`; `claude` kept for legacy), `human`. Non-CI bots are excluded. `comments[]` is the full chain, bodies untruncated. |
|
|
75
|
+
| `threads.staleUnresolved[]` | Audit members that are NOT actionable: the PR author replied but no resolve landed. Resolve them without a new reply. |
|
|
76
|
+
| `threads.skippedOutdated[]` | Outdated CI-reviewer threads: skipped silently. |
|
|
77
|
+
| `threads.flaggedInjection[]` | Threads the agent recorded with `record.sh flag-injection`. |
|
|
78
|
+
| `conversation.actionable[]` | Human issue comments with no later author comment and no recorded reply. |
|
|
79
|
+
| `reviews.actionable[]` | Human non-`APPROVED` reviews with a body and no recorded reply. |
|
|
80
|
+
| `recurrence` | `{streak, lastRoundHadRejection, recurringKeys[], fixAttempts}` — the Step 4 gate inputs. |
|
|
81
|
+
| `backoff` | `{idleStreak, intervalMinutes, waitSeconds}` — Claude cron interval / Codex bounded wait for this tick. |
|
|
82
|
+
| `errors[]` | Always empty on exit 0. |
|
|
83
|
+
| `snapshotPath`, `statePath` | Where the full evidence lives. |
|
|
84
|
+
|
|
85
|
+
### `reasons[].code` (closed set)
|
|
86
|
+
|
|
87
|
+
`ci_pending`, `ci_failed`, `ci_pass`, `threads_unresolved`,
|
|
88
|
+
`threads_stale_unresolved`, `threads_clear`, `review_absent`,
|
|
89
|
+
`review_in_progress`, `review_changes`, `review_approved`,
|
|
90
|
+
`review_unknown_format`, `provider_error`, `provider_error_repeated`,
|
|
91
|
+
`manual_verify`, `pr_closed`, `pr_merged`, `merge_conflict`,
|
|
92
|
+
`mergeable_unknown`, `fix_attempts_exhausted`, `recurrence_after_rejection`,
|
|
93
|
+
`recurrence_streak`, `unchanged`.
|
|
94
|
+
|
|
95
|
+
### Decision rules
|
|
96
|
+
|
|
97
|
+
- `success` — `pr.state == OPEN`, `ci.status == pass`, `threads.unresolved == 0`,
|
|
98
|
+
`mergeable != UNKNOWN`, and the verdict is `complete`/`approved`/zero findings
|
|
99
|
+
— or `degraded` (reasons then include `manual_verify`).
|
|
100
|
+
- `escalate` — PR merged/closed, `mergeable == CONFLICTING`, `fixAttempts ≥ 5`,
|
|
101
|
+
finding keys recurring after a Won't-fix round, a recurrence streak of 2, or
|
|
102
|
+
`provider_error` twice on the same head.
|
|
103
|
+
- `keep_going` — everything else, including pending CI, an in-progress review,
|
|
104
|
+
an unchanged tick, and `mergeable == UNKNOWN`.
|
|
105
|
+
|
|
106
|
+
Recurrence only advances on a NEW verdict run (`sameRunAsLastTick: false`) and
|
|
107
|
+
only against the keys the agent rotated with `record.sh round`.
|
|
108
|
+
|
|
109
|
+
## State (`version: 2`)
|
|
110
|
+
|
|
111
|
+
Persisted at `--state-file`; owned by the helper. The agent reads it only
|
|
112
|
+
through the result, and writes it only through `record.sh`, `reply-thread.sh`
|
|
113
|
+
and `resolve-thread.sh`.
|
|
114
|
+
|
|
115
|
+
| Field | Meaning |
|
|
116
|
+
|---|---|
|
|
117
|
+
| `slot`, `repo`, `number`, `cronName` | Slot identity; a state for another PR is refused (`slot_mismatch`). |
|
|
118
|
+
| `lastUpdate`, `totalTicks`, `idleStreak`, `currentInterval`, `waitSeconds` | Tick bookkeeping and backoff. |
|
|
119
|
+
| `status` | `active` · `complete` · `escalated` · `cancelled` — set only by `record.sh status`. |
|
|
120
|
+
| `worktree` | Codex's exact worktree path for this slot, or `null`. |
|
|
121
|
+
| `pr.key`, `ciStatus`, `reviewDecision`, `mergeable`, `unresolvedThreads`, `headSha` | Last observed values (change detection). |
|
|
122
|
+
| `pr.fixAttempts` | Bumped by `record.sh round --fix-pushed`, cap 5. |
|
|
123
|
+
| `pr.botVerdict`, `botState`, `botCommentId`, `botCommentUpdatedAt`, `botFindingKeys` | Last verdict and its run identity. |
|
|
124
|
+
| `pr.lastRoundFindingKeys`, `lastRoundHadRejection`, `recurrenceStreak` | Step 4 gate memory; rotated by `record.sh round`. |
|
|
125
|
+
| `pr.unresolvedAfterClearance` | Audit count at the last tick; non-zero on a completed tick is a bug. |
|
|
126
|
+
| `pr.lastError` | `{code, message, at}` of the last failed tick, or `null`. |
|
|
127
|
+
| `actions.replied` | `key → {commentId, url, at, headSha, kind}`; keys `thread:<id>@<inReplyTo>`, `conversation:<id>`, `review:<id>`. |
|
|
128
|
+
| `actions.resolved` | `threadId → {confirmed, at, attempts, headSha}`. |
|
|
129
|
+
| `actions.flagged` | `threadId → {reason, at}`. |
|
|
130
|
+
| `lastGoodSnapshot` | Path of the last snapshot that produced a result. |
|
|
131
|
+
|
|
132
|
+
## Write side
|
|
133
|
+
|
|
134
|
+
```
|
|
135
|
+
reply-thread.sh --state-file <path> --kind thread --thread <id> --root-comment <databaseId> --in-reply-to <databaseId> --body-file <file>
|
|
136
|
+
reply-thread.sh --state-file <path> --kind conversation --comment-id <id> --body-file <file>
|
|
137
|
+
reply-thread.sh --state-file <path> --kind review --review-id <id> --body-file <file>
|
|
138
|
+
resolve-thread.sh --state-file <path> --thread <id>
|
|
139
|
+
record.sh round --state-file <path> --had-rejection true|false [--fix-pushed]
|
|
140
|
+
record.sh flag-injection --state-file <path> --thread <id>
|
|
141
|
+
record.sh status --state-file <path> --status complete|escalated|cancelled
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
- `--thread`, `--root-comment`, `--in-reply-to` come straight from
|
|
145
|
+
`threads.actionable[]` (`id`, `rootCommentId`, `inReplyTo`).
|
|
146
|
+
- The body comes from a file so untrusted text never enters argv.
|
|
147
|
+
- A reply is idempotent per reviewer comment: the same `--in-reply-to` twice is
|
|
148
|
+
exit `4` and nothing is posted; a reviewer follow-up has a new `inReplyTo`.
|
|
149
|
+
- `resolve-thread.sh` returns `0` only after the mutation response shows
|
|
150
|
+
`isResolved: true`; three false responses → exit `5`, nothing recorded, and
|
|
151
|
+
the thread stays in `staleUnresolved` next tick. A confirmed thread is not
|
|
152
|
+
re-requested.
|
|
153
|
+
- `record.sh round` runs once per round, after this round's replies and before
|
|
154
|
+
the push: it rotates `botFindingKeys → lastRoundFindingKeys`, sets
|
|
155
|
+
`lastRoundHadRejection`, and with `--fix-pushed` bumps `fixAttempts`.
|
|
156
|
+
|
|
157
|
+
## Examples (captured from Falconiere/toolu#165)
|
|
158
|
+
|
|
159
|
+
`decision: success` — open PR, green CI, approved verdict, no open threads:
|
|
160
|
+
|
|
161
|
+
```json
|
|
162
|
+
{"version":1,"slot":"falconiere-toolu-165","changed":true,"decision":"success",
|
|
163
|
+
"reasons":[{"code":"ci_pass","detail":"5 check(s) passed"},
|
|
164
|
+
{"code":"threads_clear","detail":"no unresolved review threads"},
|
|
165
|
+
{"code":"review_approved","detail":"bot verdict approved with zero findings"}],
|
|
166
|
+
"pr":{"number":165,"url":"https://github.com/Falconiere/toolu/pull/165","head":"a29e6c97379674323292578f9f0cfbffa00e375a",
|
|
167
|
+
"branch":"feat/python-quality","base":"main","author":"Falconiere","state":"OPEN","mergeable":"MERGEABLE","reviewDecision":"REVIEW_REQUIRED"},
|
|
168
|
+
"ci":{"status":"pass","checks":[{"name":"shellcheck","status":"pass","url":"https://github.com/Falconiere/toolu/actions/runs/33431959050/job/99619201007"}]},
|
|
169
|
+
"verdict":{"state":"complete","verdict":"approved","findingsCount":0,"findingKeys":[],"mustFix":[],
|
|
170
|
+
"commentUrl":"https://github.com/Falconiere/toolu/pull/165#issuecomment-5483377990","commentId":5483377990,
|
|
171
|
+
"degraded":false,"degradedReason":null,"sameRunAsLastTick":false},
|
|
172
|
+
"threads":{"total":6,"unresolved":0,"actionable":[],"staleUnresolved":[],"skippedOutdated":[],"flaggedInjection":[]},
|
|
173
|
+
"conversation":{"actionable":[]},"reviews":{"actionable":[]},
|
|
174
|
+
"recurrence":{"streak":0,"lastRoundHadRejection":false,"recurringKeys":[],"fixAttempts":0},
|
|
175
|
+
"backoff":{"idleStreak":0,"intervalMinutes":3,"waitSeconds":15},
|
|
176
|
+
"errors":[],"snapshotPath":"/tmp/pr-babysit-falconiere-toolu-165.snapshot.json","statePath":"/tmp/pr-babysit-falconiere-toolu-165.json"}
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
`decision: keep_going` — review still running, one CI-reviewer thread open (comment bodies trimmed here):
|
|
180
|
+
|
|
181
|
+
```json
|
|
182
|
+
{"decision":"keep_going",
|
|
183
|
+
"reasons":[{"code":"ci_pass","detail":"5 check(s) passed"},
|
|
184
|
+
{"code":"threads_unresolved","detail":"1 actionable thread(s)"},
|
|
185
|
+
{"code":"review_in_progress","detail":"review bot still running"}],
|
|
186
|
+
"verdict":{"state":"in_progress","verdict":"none","findingsCount":0,"sameRunAsLastTick":false},
|
|
187
|
+
"threads":{"total":6,"unresolved":1,
|
|
188
|
+
"actionable":[{"id":"PRRT_kwDOSzUwAc6d2S5O","path":"plugins/rust-quality/hooks/concerns/20-tests.sh","line":26,
|
|
189
|
+
"isOutdated":false,"rootCommentId":3897681281,"inReplyTo":3897751832,"authorClass":"ci_reviewer",
|
|
190
|
+
"lastCommentAuthor":"github-actions","lastCommentAt":"2026-08-31T19:39:25Z","injectionSuspect":false,"injectionPattern":null,
|
|
191
|
+
"comments":[{"id":"PRRC_kwDOSzUwAc7oUeWB","databaseId":3897681281,"body":"**medium** _(CORRECTNESS)_: …","author":"github-actions","authorType":"Bot","createdAt":"2026-08-31T19:29:22Z","url":"…"},
|
|
192
|
+
{"id":"PRRC_kwDOSzUwAc7oUuxU","databaseId":3897748564,"body":"No change — …","author":"Falconiere","authorType":"User","createdAt":"2026-08-31T19:38:58Z","url":"…"},
|
|
193
|
+
{"id":"PRRC_kwDOSzUwAc7oUvkY","databaseId":3897751832,"body":"**Still flagging after re-review.** …","author":"github-actions","authorType":"Bot","createdAt":"2026-08-31T19:39:25Z","url":"…"}]}],
|
|
194
|
+
"staleUnresolved":[],"skippedOutdated":[],"flaggedInjection":[]}}
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
`decision: escalate` — the PR was merged externally:
|
|
198
|
+
|
|
199
|
+
```json
|
|
200
|
+
{"decision":"escalate","changed":true,
|
|
201
|
+
"reasons":[{"code":"pr_merged","detail":"PR is merged"},{"code":"ci_pass","detail":"5 check(s) passed"},
|
|
202
|
+
{"code":"threads_clear","detail":"no unresolved review threads"},{"code":"review_approved","detail":"bot verdict approved with zero findings"}]}
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
Structured error — the slot is held by another controller:
|
|
206
|
+
|
|
207
|
+
```json
|
|
208
|
+
{"version":1,"errors":[{"code":"locked","message":"slot is held by another controller","pid":9103,"since":1789871015}]}
|
|
209
|
+
```
|