@webpresso/codex-plugin 3.1.10 → 3.1.12

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.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agent-kit",
3
- "version": "3.1.10",
3
+ "version": "3.1.12",
4
4
  "description": "Webpresso agent-kit: blueprints, skills, hooks, and MCP server",
5
5
  "author": {
6
6
  "name": "Webpresso",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@webpresso/codex-plugin",
3
- "version": "3.1.10",
3
+ "version": "3.1.12",
4
4
  "private": false,
5
5
  "description": "Codex plugin adapter for Webpresso agent-kit skills, hooks, and MCP runtime.",
6
6
  "homepage": "https://github.com/webpresso/agent-kit#readme",
@@ -15,6 +15,7 @@
15
15
  },
16
16
  "files": [
17
17
  ".codex-plugin",
18
+ "plugin-skill-ownership.json",
18
19
  ".mcp.json",
19
20
  "skills",
20
21
  "hooks",
@@ -0,0 +1,78 @@
1
+ {
2
+ "schemaVersion": 1,
3
+ "host": "codex",
4
+ "packageName": "@webpresso/codex-plugin",
5
+ "packageVersion": "3.1.12",
6
+ "runtimeDirs": [
7
+ ".codex/skills",
8
+ ".agents/skills"
9
+ ],
10
+ "skills": {
11
+ "autopilot": {
12
+ "digest": "sha256:7fc1fe9f5fb5b8a3e755f8e3ac4b807e4f140288544ffcf39e4cbfdea984014d"
13
+ },
14
+ "best-practice-research": {
15
+ "digest": "sha256:b0d355c7f4afe9c38be83e293788bf582bea068c1db2fa5c45400441e315735d"
16
+ },
17
+ "browse": {
18
+ "digest": "sha256:21fd24862e7f7c8a1feadea6bc376492b136b99fc381cf1b8927c9db80615431"
19
+ },
20
+ "claude": {
21
+ "digest": "sha256:b75a472594d78bd9452c07fd791a426864848a2c9c726454dc72daf3813ae22b"
22
+ },
23
+ "codex": {
24
+ "digest": "sha256:803cea9c1b37406d7bdc8e44e9d5e9f3988218277500d8d6cb0d955c933c3302"
25
+ },
26
+ "deep-research": {
27
+ "digest": "sha256:01dafd194066a2936db0b437fd5c3beef6d7ff82478d0d995393093d89496f18"
28
+ },
29
+ "design-review": {
30
+ "digest": "sha256:5b213735e2842568774572f36b198bccf03c03a54b2732484aadd023f04acb23"
31
+ },
32
+ "devex-review": {
33
+ "digest": "sha256:306355888da0827e3523022077f731845726bbaaf595ca12515cc91d8605b5e6"
34
+ },
35
+ "fix": {
36
+ "digest": "sha256:e43dfad198f7279ebc2dca9e62f43fd960c783ca0ab25f133668fca13aed96b2"
37
+ },
38
+ "goal": {
39
+ "digest": "sha256:85380b633cfaaff8ec1639dd3a88412a41920aae3e8531de95e2b79411926a18"
40
+ },
41
+ "hooks-doctor": {
42
+ "digest": "sha256:3bfb0af64ff6796c56d51e4cd2bede7fb24c869f89b292546ea13ad2f5d3bb73"
43
+ },
44
+ "lore-protocol": {
45
+ "digest": "sha256:a80fbacc765e8b886437b9fdf7dd71e1ceb1b98b939078ec90fc88d06a8b4c69"
46
+ },
47
+ "opencode-go": {
48
+ "digest": "sha256:ccb2bcf09cc1c5c0d54027b84f580e688f5f5cf624e6cdc19870bd3ce99940fc"
49
+ },
50
+ "plan-ceo-review": {
51
+ "digest": "sha256:4d426158518dd71f8998770e8d4037bcbfa3c65d7265a93a4a78a9b015ba881e"
52
+ },
53
+ "plan-design-review": {
54
+ "digest": "sha256:a8a33efe67844d0602ff595c0b7e8f5529e52f51a781112952636c91963ce19e"
55
+ },
56
+ "plan-devex-review": {
57
+ "digest": "sha256:a49b4f61f1c54a7d89111c270616684dcbf2a7236bc817fafb5919366a7cda31"
58
+ },
59
+ "plan-eng-review": {
60
+ "digest": "sha256:b728dad90254d4c9f81b3818321d34ab53519b79ce20ff3bfe08bfacb2aa3e86"
61
+ },
62
+ "plan-refine": {
63
+ "digest": "sha256:891e84ff8592b8489b59e8642f6a602c9558fd57edd171af98499c94e2f62f13"
64
+ },
65
+ "tech-debt": {
66
+ "digest": "sha256:8b1f30e55ec6e5ab185ff19ecf15384438dbd619f675c2c65f2e53cedecbae5c"
67
+ },
68
+ "testing-philosophy": {
69
+ "digest": "sha256:43fa05bcb7eaf8efd4a8dcc0f9fb9c30ae9f80079d9b3dbfdc926734a64a2973"
70
+ },
71
+ "tph": {
72
+ "digest": "sha256:bed3684a850651534f315d355cdeae50d951c429817df0f105cccdb7f4e1561b"
73
+ },
74
+ "verify": {
75
+ "digest": "sha256:b088b138a985f94e29970edac1bcae70b29b15b5b3573787652ad590b789c9c7"
76
+ }
77
+ }
78
+ }
@@ -38,6 +38,19 @@ PROMPT_FILE=$(mktemp -t wp-claude-review.XXXXXX)
38
38
  trap 'rm -f "$PROMPT_FILE"' EXIT
39
39
  ```
40
40
 
41
+ ## Dashboard helper relation
42
+
43
+ When this skill is invoked from `wp dash`, create explicit lifecycle records around the outside-voice call. The shared `wp dash-helper-*` service owns the event JSON and report shape; do not duplicate it in a skill. Outside the dashboard the block is a no-op, preserving standalone calls.
44
+
45
+ ```bash
46
+ DASH_HELPER_RUN_ID=""
47
+ if [ "${WP_DASH:-}" = "1" ] && [ -n "${WP_DASH_RUN_ID:-}" ]; then
48
+ DASH_HELPER_RUN_ID=$(wp dash-helper-start --provider claude --role reviewer)
49
+ fi
50
+ ```
51
+
52
+ Claude's managed native subagent lifecycle does not provide a reliable parent-linked completion event. It remains explicitly unsupported rather than being inferred from transcript text or timing.
53
+
41
54
  ## Modes
42
55
 
43
56
  ### Review
@@ -77,116 +90,55 @@ TARGET_FILE=${TARGET_FILE:?set TARGET_FILE to one changed file}
77
90
  } >"$PROMPT_FILE"
78
91
  ```
79
92
 
80
- #### Timed Claude artifact wrapper
93
+ #### Progress-aware review runtime
81
94
 
82
- Run Claude through the bounded Python wrapper. Use `claude --print --model "$CLAUDE_REVIEW_MODEL"`; do not recommend `--bare` for this path. Do not add artificial budget caps unless the user requested one. The wrapper is deterministic:
83
-
84
- - it defaults to `CLAUDE_REVIEW_MODEL=sonnet` and allows only `opus` or `sonnet`
85
- - it never passes `--tools ""`, because Claude's variadic `--tools` option can swallow the prompt
86
- - it captures stdout and stderr for preflight and the full review
87
- - it writes the artifact on success, non-zero exit, or timeout
88
- - it runs one full review attempt only; retry only if you change the command shape for a specific diagnosed reason
89
-
90
- Preflight must be only the tiny `claude-ok` prompt with the selected model. Do not use the real review prompt for preflight.
95
+ Run both the availability probe and the full review through the typed `wp review run` owner. Skills must not embed subprocess supervision. The runtime consumes Claude's streaming JSON events, advances its idle clock only on monotonic semantic progress, and has no total wall-clock cutoff.
91
96
 
92
97
  ```bash
93
98
  CLAUDE_REVIEW_MODEL=${CLAUDE_REVIEW_MODEL:-sonnet}
94
99
  CLAUDE_REVIEW_EFFORT=${CLAUDE_REVIEW_EFFORT:-medium}
95
- CLAUDE_REVIEW_PREFLIGHT_TIMEOUT_SECONDS=${CLAUDE_REVIEW_PREFLIGHT_TIMEOUT_SECONDS:-30}
96
- CLAUDE_REVIEW_IDLE_TIMEOUT_SECONDS=${CLAUDE_REVIEW_IDLE_TIMEOUT_SECONDS:-180}
97
- CLAUDE_REVIEW_ARTIFACT=${CLAUDE_REVIEW_ARTIFACT:-"$(pwd)/.webpresso/claude-review-$(date +%Y%m%dT%H%M%S).json"}
98
- export CLAUDE_REVIEW_MODEL CLAUDE_REVIEW_EFFORT CLAUDE_REVIEW_PREFLIGHT_TIMEOUT_SECONDS CLAUDE_REVIEW_IDLE_TIMEOUT_SECONDS CLAUDE_REVIEW_ARTIFACT
99
- mkdir -p "$(dirname "$CLAUDE_REVIEW_ARTIFACT")"
100
-
101
- python3 - "$PROMPT_FILE" "$CLAUDE_REVIEW_ARTIFACT" <<'PY'
102
- from __future__ import annotations
103
-
104
- import json, os, subprocess, sys, time
105
- from pathlib import Path
106
-
107
- prompt_path = Path(sys.argv[1])
108
- artifact_path = Path(sys.argv[2])
109
- model = os.environ.get("CLAUDE_REVIEW_MODEL", "sonnet")
110
- effort = os.environ.get("CLAUDE_REVIEW_EFFORT", "medium")
111
- preflight_timeout = float(os.environ.get("CLAUDE_REVIEW_PREFLIGHT_TIMEOUT_SECONDS", "30"))
112
- review_idle_timeout = float(os.environ.get("CLAUDE_REVIEW_IDLE_TIMEOUT_SECONDS", "180"))
113
- if model not in {"opus", "sonnet"}: raise SystemExit("CLAUDE_REVIEW_MODEL must be one of: opus, sonnet")
114
- if effort not in {"medium", "high"}: raise SystemExit("CLAUDE_REVIEW_EFFORT must be one of: medium, high")
115
- if preflight_timeout <= 0 or review_idle_timeout <= 0: raise SystemExit("Claude timeout values must be positive numbers")
116
-
117
-
118
- def utc_now() -> str: return time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime())
119
-
120
- artifact: dict[str, object] = {"tool": "claude-outside-voice", "model": model, "effort": effort, "startedAt": utc_now(), "artifactPath": str(artifact_path), "runs": []}
121
-
122
-
123
- def write_artifact(status: str, exit_code: int) -> None:
124
- artifact.update({"finishedAt": utc_now(), "status": status, "exitCode": exit_code})
125
- artifact_path.parent.mkdir(parents=True, exist_ok=True)
126
- artifact_path.write_text(json.dumps(artifact, indent=2) + "\n", encoding="utf-8")
127
-
128
-
129
- def as_text(value: str | bytes | None) -> str:
130
- return value.decode(errors="replace") if isinstance(value, bytes) else (value or "")
131
-
132
-
133
- def run_claude(stage: str, prompt: str, timeout_seconds: float, timeout_kind: str) -> int:
134
- command = ["claude", "--print", "--model", model, "--effort", effort, prompt]
135
- started = last_progress = time.monotonic()
136
- last_progress_at = utc_now()
137
- out = err = ""
138
- record: dict[str, object] = {"stage": stage, "command": [*command[:-1], "<prompt>"], "timeoutKind": timeout_kind, "timeoutSeconds": timeout_seconds, "idleTimeoutSeconds": timeout_seconds if timeout_kind == "idle" else None, "startedAt": last_progress_at}
139
- try:
140
- process = subprocess.Popen(command, text=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
141
- while True:
142
- try:
143
- new_out, new_err = process.communicate(timeout=0.5)
144
- out, err = as_text(new_out), as_text(new_err)
145
- code = int(process.returncode)
146
- timed_out = False
147
- break
148
- except subprocess.TimeoutExpired as exc:
149
- new_out, new_err = as_text(exc.stdout), as_text(exc.stderr)
150
- if new_out != out or new_err != err:
151
- out, err, last_progress, last_progress_at = new_out, new_err, time.monotonic(), utc_now()
152
- reference = last_progress if timeout_kind == "idle" else started
153
- if time.monotonic() - reference > timeout_seconds:
154
- process.kill()
155
- final_out, final_err = process.communicate()
156
- out += as_text(final_out)[len(out):]
157
- err += as_text(final_err)[len(err):]
158
- code, timed_out = 124, True
159
- break
160
- except OSError as exc:
161
- out, err, code, timed_out = "", str(exc), 1, False
162
- record.update({"exitCode": code, "timedOut": timed_out, "stdout": out, "stderr": err, "finishedAt": utc_now(), "lastProgressAt": last_progress_at, "elapsedSeconds": round(time.monotonic() - started, 3)})
163
- artifact["runs"].append(record)
164
- return code
165
-
166
- preflight_code = run_claude("preflight", "claude-ok", preflight_timeout, "wall-clock")
167
- if preflight_code != 0:
168
- write_artifact("preflight-timeout" if preflight_code == 124 else "preflight-failed", preflight_code)
169
- print(f"CLAUDE_REVIEW_PREFLIGHT_EXIT={preflight_code}", file=sys.stderr)
170
- print(f"CLAUDE_REVIEW_ARTIFACT={artifact_path}", file=sys.stderr)
171
- raise SystemExit(preflight_code)
172
- review_code = run_claude("review", prompt_path.read_text(encoding="utf-8"), review_idle_timeout, "idle")
173
- write_artifact("idle-timeout" if review_code == 124 else ("success" if review_code == 0 else "failed"), review_code)
174
- print(f"CLAUDE_REVIEW_EXIT={review_code}", file=sys.stderr)
175
- print(f"CLAUDE_REVIEW_ARTIFACT={artifact_path}", file=sys.stderr)
176
- raise SystemExit(review_code)
177
- PY
100
+ CLAUDE_REVIEW_PROBE_IDLE_SECONDS=${CLAUDE_REVIEW_PROBE_IDLE_SECONDS:-180}
101
+ CLAUDE_REVIEW_IDLE_SECONDS=${CLAUDE_REVIEW_IDLE_SECONDS:-180}
102
+ CLAUDE_REVIEW_ARTIFACT_ROOT=${CLAUDE_REVIEW_ARTIFACT_ROOT:-"$(pwd)/.webpresso/reviews"}
103
+ PROBE_FILE=$(mktemp -t wp-claude-probe.XXXXXX)
104
+ trap 'rm -f "$PROMPT_FILE" "$PROBE_FILE"' EXIT
105
+ printf 'Reply exactly CLAUDE_OK.\n' >"$PROBE_FILE"
106
+
107
+ wp review run \
108
+ --provider claude \
109
+ --prompt-file "$PROBE_FILE" \
110
+ --model "$CLAUDE_REVIEW_MODEL" \
111
+ --effort "$CLAUDE_REVIEW_EFFORT" \
112
+ --stage probe \
113
+ --expected-marker CLAUDE_OK \
114
+ --artifact-root "$CLAUDE_REVIEW_ARTIFACT_ROOT" \
115
+ --idle-seconds "$CLAUDE_REVIEW_PROBE_IDLE_SECONDS"
116
+
117
+ wp review run \
118
+ --provider claude \
119
+ --prompt-file "$PROMPT_FILE" \
120
+ --model "$CLAUDE_REVIEW_MODEL" \
121
+ --effort "$CLAUDE_REVIEW_EFFORT" \
122
+ --stage review \
123
+ --artifact-root "$CLAUDE_REVIEW_ARTIFACT_ROOT" \
124
+ --idle-seconds "$CLAUDE_REVIEW_IDLE_SECONDS"
125
+ CLAUDE_REVIEW_CODE=$?
178
126
  ```
179
127
 
180
- After the wrapper exits, verify both the exit code and artifact path before summarizing Claude's raw output:
128
+ After the review runtime exits, emit its terminal state using the same artifact path:
181
129
 
182
130
  ```bash
183
- test -f "$CLAUDE_REVIEW_ARTIFACT" || {
184
- echo "CLAUDE_REVIEW_ARTIFACT=missing: $CLAUDE_REVIEW_ARTIFACT" >&2
185
- exit 1
186
- }
187
- echo "CLAUDE_REVIEW_ARTIFACT=$CLAUDE_REVIEW_ARTIFACT"
131
+ if [ -n "$DASH_HELPER_RUN_ID" ]; then
132
+ if [ "$CLAUDE_REVIEW_CODE" -eq 0 ]; then
133
+ wp dash-helper-complete --provider claude --run-id "$DASH_HELPER_RUN_ID" --role reviewer --outcome completed --artifact "${CLAUDE_REVIEW_ARTIFACT:-}"
134
+ else
135
+ wp dash-helper-complete --provider claude --run-id "$DASH_HELPER_RUN_ID" --role reviewer --outcome failed --artifact "${CLAUDE_REVIEW_ARTIFACT:-}"
136
+ fi
137
+ fi
188
138
  ```
189
139
 
140
+ Treat `true-idle`, `protocol-unsupported`, provider failure, abort, spawn failure, or artifact failure as unavailable review evidence. Never replace them with a static timeout, arbitrary byte-growth heartbeat, or buffered-output fallback.
141
+
190
142
  #### Retry policy
191
143
 
192
144
  Do not perform blind retries. Run the full review once. Retry only after diagnosing and changing a command-shape problem; record the reason and artifact path. Do not fall back to an unbounded whole-PR prompt.
@@ -25,6 +25,31 @@ PROMPT_FILE=$(mktemp -t wp-codex-review.XXXXXX)
25
25
  trap 'rm -f "$PROMPT_FILE"' EXIT
26
26
  ```
27
27
 
28
+ ## Dashboard helper relation
29
+
30
+ When called by `wp dash`, register the outside-voice helper through the shared service rather than writing relationship JSON in this skill. This is intentionally a no-op for standalone use.
31
+
32
+ ```bash
33
+ DASH_HELPER_RUN_ID=""
34
+ if [ "${WP_DASH:-}" = "1" ] && [ -n "${WP_DASH_RUN_ID:-}" ]; then
35
+ DASH_HELPER_RUN_ID=$(wp dash-helper-start --provider codex --role reviewer)
36
+ fi
37
+ ```
38
+
39
+ After the review command completes, report the exact terminal outcome. Supply an artifact path when the caller created one.
40
+
41
+ ```bash
42
+ if [ -n "$DASH_HELPER_RUN_ID" ]; then
43
+ if [ "${CODEX_REVIEW_CODE:-0}" -eq 0 ]; then
44
+ wp dash-helper-complete --provider codex --run-id "$DASH_HELPER_RUN_ID" --role reviewer --outcome completed --artifact "${CODEX_REVIEW_ARTIFACT:-}"
45
+ else
46
+ wp dash-helper-complete --provider codex --run-id "$DASH_HELPER_RUN_ID" --role reviewer --outcome failed --artifact "${CODEX_REVIEW_ARTIFACT:-}"
47
+ fi
48
+ fi
49
+ ```
50
+
51
+ Codex native subagent events are not currently parent-linked in the managed lifecycle. They are shown as unsupported, never inferred from text, PIDs, or timestamps.
52
+
28
53
  ## Modes
29
54
 
30
55
  ### Review
@@ -39,7 +64,8 @@ case "$CODEX_REVIEW_EFFORT" in
39
64
  medium|high) ;;
40
65
  *) echo "CODEX_REVIEW_EFFORT must be one of: medium, high" >&2; exit 2 ;;
41
66
  esac
42
- codex exec -c model_reasoning_effort="\"$CODEX_REVIEW_EFFORT\"" --sandbox read-only --cd "$PWD" - <"$PROMPT_FILE"
67
+ CODEX_REVIEW_CODE=0
68
+ codex exec -c model_reasoning_effort="\"$CODEX_REVIEW_EFFORT\"" --sandbox read-only --cd "$PWD" - <"$PROMPT_FILE" || CODEX_REVIEW_CODE=$?
43
69
  ```
44
70
 
45
71
  4. Summarize findings with severity, evidence, and whether you independently verified them.
@@ -28,6 +28,32 @@ PROMPT_FILE=$(mktemp -t wp-opencode-go-review.XXXXXX)
28
28
  trap 'rm -f "$PROMPT_FILE"' EXIT
29
29
  ```
30
30
 
31
+ ## Dashboard helper relation
32
+
33
+ When `wp dash` provides dashboard context, use the shared helper emitter around this outside-voice review. The commands leave standalone OpenCode Go reviews unchanged.
34
+
35
+ ```bash
36
+ DASH_HELPER_RUN_ID=""
37
+ if [ "${WP_DASH:-}" = "1" ] && [ -n "${WP_DASH_RUN_ID:-}" ]; then
38
+ DASH_HELPER_RUN_ID=$(wp dash-helper-start --provider opencode-go --role reviewer --artifact "${OPENCODE_GO_REVIEW_ARTIFACT:-}")
39
+ fi
40
+ ```
41
+
42
+ After the review runtime exits, record its actual result and optional artifact:
43
+
44
+ ```bash
45
+ OPENCODE_GO_REVIEW_CODE=$?
46
+ if [ -n "$DASH_HELPER_RUN_ID" ]; then
47
+ if [ "$OPENCODE_GO_REVIEW_CODE" -eq 0 ]; then
48
+ wp dash-helper-complete --provider opencode-go --run-id "$DASH_HELPER_RUN_ID" --role reviewer --outcome completed --artifact "${OPENCODE_GO_REVIEW_ARTIFACT:-}"
49
+ else
50
+ wp dash-helper-complete --provider opencode-go --run-id "$DASH_HELPER_RUN_ID" --role reviewer --outcome failed --artifact "${OPENCODE_GO_REVIEW_ARTIFACT:-}"
51
+ fi
52
+ fi
53
+ ```
54
+
55
+ The managed OpenCode plugin currently has no parent-linked native subagent completion lifecycle. This capability is explicit unsupported coverage, not an inferred dashboard relation.
56
+
31
57
  ## Review command
32
58
 
33
59
  Use read-only prompts. Run from the repo directory; do NOT pass `--dir "$PWD"` because opencode already uses the current directory and the extra index can stall reviews.
@@ -44,64 +70,37 @@ MODEL=$(echo "$CATALOG" | grep '^opencode-go/kimi' | grep -- '-code$' | sort -V
44
70
  [ -z "$MODEL" ] && MODEL=$(echo "$CATALOG" | grep '^opencode-go/mimo' | grep -- '-pro$' | sort -V | tail -1)
45
71
  [ -z "$MODEL" ] && MODEL=$(echo "$CATALOG" | sort -V | tail -1)
46
72
 
47
- # Fail fast on blocked quota before launching a long review. The probe uses
48
- # an idle timeout, not a wall-clock timeout, so active output is allowed to continue.
49
- # The probe succeeds as soon as it observes the expected success marker instead of
50
- # waiting for the CLI to finish teardown after the reply has already arrived.
73
+ # Fail fast on blocked quota before launching a long review. The typed runtime
74
+ # consumes provider JSON events and resets its idle clock only on semantic progress.
51
75
  [ -n "$MODEL" ] || { echo "No OpenCode Go model resolved for this reviewer." >&2; exit 2; }
52
76
  OPENCODE_GO_REVIEW_EFFORT=${OPENCODE_GO_REVIEW_EFFORT:-medium}
53
77
  [ "$OPENCODE_GO_REVIEW_EFFORT" = medium ] || [ "$OPENCODE_GO_REVIEW_EFFORT" = high ] || { echo "OPENCODE_GO_REVIEW_EFFORT must be one of: medium, high" >&2; exit 2; }
54
- OPENCODE_GO_PROBE_TIMEOUT=${OPENCODE_GO_PROBE_TIMEOUT:-20}
55
- case "$OPENCODE_GO_PROBE_TIMEOUT" in
56
- ""|*[!0-9]*) echo "OPENCODE_GO_PROBE_TIMEOUT must be a positive integer number of seconds" >&2; exit 2 ;;
57
- esac
58
- [ "$OPENCODE_GO_PROBE_TIMEOUT" -gt 0 ] || { echo "OPENCODE_GO_PROBE_TIMEOUT must be a positive integer number of seconds" >&2; exit 2; }
59
- PROBE_LOG=$(mktemp -t wp-opencode-go-probe.XXXXXX)
60
- unset PROBE_RC
61
- opencode run --format json --variant "$OPENCODE_GO_REVIEW_EFFORT" --model "$MODEL" --title opencode-go-credit-probe 'Reply exactly OPENCODE_GO_OK.' >"$PROBE_LOG" 2>&1 &
62
- PROBE_PID=$!
63
- LAST_PROBE_SIZE=$(wc -c <"$PROBE_LOG" | tr -d " ")
64
- LAST_PROBE_PROGRESS_AT=$(date +%s)
65
- while kill -0 "$PROBE_PID" 2>/dev/null; do
66
- if grep -q "OPENCODE_GO_OK" "$PROBE_LOG"; then
67
- kill "$PROBE_PID" 2>/dev/null || true
68
- wait "$PROBE_PID" 2>/dev/null || true
69
- PROBE_RC=0
70
- break
71
- fi
72
- CURRENT_PROBE_SIZE=$(wc -c <"$PROBE_LOG" | tr -d " ")
73
- NOW=$(date +%s)
74
- if [ "$CURRENT_PROBE_SIZE" -gt "$LAST_PROBE_SIZE" ]; then
75
- LAST_PROBE_SIZE=$CURRENT_PROBE_SIZE
76
- LAST_PROBE_PROGRESS_AT=$NOW
77
- elif [ $((NOW - LAST_PROBE_PROGRESS_AT)) -gt "$OPENCODE_GO_PROBE_TIMEOUT" ]; then
78
- printf '\nOPENCODE_GO_PROBE_TIMEOUT after %ss without progress\n' "$OPENCODE_GO_PROBE_TIMEOUT" >>"$PROBE_LOG"
79
- kill -KILL "$PROBE_PID" 2>/dev/null || true
80
- wait "$PROBE_PID" 2>/dev/null || true
81
- PROBE_RC=124
82
- break
83
- fi
84
- sleep 1
85
- done
86
- if [ -z "${PROBE_RC+x}" ]; then
87
- if grep -q "OPENCODE_GO_OK" "$PROBE_LOG"; then
88
- PROBE_RC=0
89
- else
90
- wait "$PROBE_PID" || PROBE_RC=$?
91
- fi
92
- fi
93
- if [ "$PROBE_RC" -eq 0 ] && ! grep -q "OPENCODE_GO_OK" "$PROBE_LOG"; then
94
- echo "OpenCode Go probe finished without the expected success marker." >>"$PROBE_LOG"
95
- PROBE_RC=1
96
- fi
97
- if [ "$PROBE_RC" -ne 0 ]; then
98
- echo "OpenCode Go probe failed or timed out for $MODEL after ${OPENCODE_GO_PROBE_TIMEOUT}s; skipping this reviewer instead of starting the full review." >&2
99
- sed -n '1,80p' "$PROBE_LOG" >&2
100
- rm -f "$PROBE_LOG"
101
- exit "$PROBE_RC"
102
- fi
103
- rm -f "$PROBE_LOG"
104
- opencode run --variant "$OPENCODE_GO_REVIEW_EFFORT" --model "$MODEL" "$(cat "$PROMPT_FILE")"
78
+ OPENCODE_GO_PROBE_IDLE_SECONDS=${OPENCODE_GO_PROBE_IDLE_SECONDS:-180}
79
+ OPENCODE_GO_REVIEW_IDLE_SECONDS=${OPENCODE_GO_REVIEW_IDLE_SECONDS:-180}
80
+ OPENCODE_GO_REVIEW_ARTIFACT_ROOT=${OPENCODE_GO_REVIEW_ARTIFACT_ROOT:-"$(pwd)/.webpresso/reviews"}
81
+ PROBE_FILE=$(mktemp -t wp-opencode-go-probe.XXXXXX)
82
+ trap 'rm -f "$PROMPT_FILE" "$PROBE_FILE"' EXIT
83
+ printf 'Reply exactly OPENCODE_GO_OK.\n' >"$PROBE_FILE"
84
+
85
+ wp review run \
86
+ --provider opencode \
87
+ --prompt-file "$PROBE_FILE" \
88
+ --model "$MODEL" \
89
+ --effort "$OPENCODE_GO_REVIEW_EFFORT" \
90
+ --stage probe \
91
+ --expected-marker OPENCODE_GO_OK \
92
+ --artifact-root "$OPENCODE_GO_REVIEW_ARTIFACT_ROOT" \
93
+ --idle-seconds "$OPENCODE_GO_PROBE_IDLE_SECONDS"
94
+
95
+ wp review run \
96
+ --provider opencode \
97
+ --prompt-file "$PROMPT_FILE" \
98
+ --model "$MODEL" \
99
+ --effort "$OPENCODE_GO_REVIEW_EFFORT" \
100
+ --stage review \
101
+ --artifact-root "$OPENCODE_GO_REVIEW_ARTIFACT_ROOT" \
102
+ --idle-seconds "$OPENCODE_GO_REVIEW_IDLE_SECONDS"
105
103
  ```
106
104
 
107
105
  If `$MODEL` is empty, run `opencode models opencode-go` and choose a present reviewer.
106
+ Treat `true-idle`, `protocol-unsupported`, provider failure, abort, spawn failure, or artifact failure as unavailable review evidence. Never replace them with a static timeout, arbitrary byte-growth heartbeat, or buffered-output fallback.
@@ -182,6 +182,10 @@ Reviewer preference:
182
182
 
183
183
  Approval evidence requirements:
184
184
 
185
+ - For blueprint lifecycle approval workflows, run `wp review gate <blueprint-slug> --target HEAD --json`
186
+ first so exact-SHA reusable evidence and approval frontmatter are handled
187
+ transactionally. Use `wp review log` / `wp review read` for focused ledger
188
+ inspection or manual evidence recording.
185
189
  - Each outside reviewer must return a clear `APPROVED`/`BLOCKED` (or equivalent)
186
190
  verdict with enough evidence to identify what it reviewed.
187
191
  - Record outside approval evidence as PR comments when a PR exists.