forgedock 1.12.0 → 1.12.1
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/bin/engine/phases-args.test.mjs +2 -1
- package/bin/engine/phases.mjs +295 -78
- package/bin/engine/runlog.mjs +13 -2
- package/bin/engine-cli.mjs +1 -0
- package/bin/engine.mjs +8 -3
- package/bin/hooks/interactive-engine.mjs +3 -0
- package/bin/labels.json +5 -0
- package/bin/orchestrate-preflight.mjs +1 -1
- package/bin/runner.mjs +24 -2
- package/bin/watch-utils.mjs +1 -0
- package/commands/cleanup.md +1 -1
- package/commands/orchestrate/phase-4-execution.md +32 -17
- package/commands/recover-orphans.md +5 -5
- package/commands/review-pr-agents/protocols.md +1 -1
- package/commands/review-pr-agents.md +11 -11
- package/commands/review-pr.md +155 -24
- package/commands/work-on/build/implement.md +1 -1
- package/commands/work-on/build.md +11 -4
- package/commands/work-on/decompose.md +2 -2
- package/commands/work-on/investigate.md +14 -4
- package/commands/work-on/remediate.md +22 -12
- package/commands/work-on/review.md +25 -9
- package/commands/work-on.md +13 -13
- package/docs/spec/label-state-machine.md +6 -1
- package/docs/spec/review-protocol.md +1 -1
- package/package.json +1 -1
- package/packages/protocol/src/phases.js +21 -0
- package/scripts/forge-root.test.sh +35 -14
- package/scripts/review-pr-domain-map.test.sh +87 -0
- package/scripts/transition-label.sh +3 -1
package/bin/labels.json
CHANGED
|
@@ -39,6 +39,11 @@
|
|
|
39
39
|
"color": "5319E7",
|
|
40
40
|
"description": "Pipeline: PR created, under review. Managed by ForgeDock."
|
|
41
41
|
},
|
|
42
|
+
{
|
|
43
|
+
"name": "workflow:remediating",
|
|
44
|
+
"color": "C5DEF5",
|
|
45
|
+
"description": "Pipeline: autonomous remediation pending (no human action needed). Managed by ForgeDock."
|
|
46
|
+
},
|
|
42
47
|
{
|
|
43
48
|
"name": "workflow:awaiting-merge",
|
|
44
49
|
"color": "FF8C00",
|
|
@@ -16,7 +16,7 @@ import { fileURLToPath } from "node:url";
|
|
|
16
16
|
import { classifyInputPattern } from "./engine/resolve.mjs";
|
|
17
17
|
|
|
18
18
|
const VERSION = "opencode-preflight-v1";
|
|
19
|
-
const IN_FLIGHT_LABELS = new Set(["workflow:building", "workflow:in-review"]);
|
|
19
|
+
const IN_FLIGHT_LABELS = new Set(["workflow:building", "workflow:in-review", "workflow:remediating"]);
|
|
20
20
|
const EXCLUDED_LABELS = new Set([
|
|
21
21
|
"needs-human",
|
|
22
22
|
"workflow:decomposed",
|
package/bin/runner.mjs
CHANGED
|
@@ -942,6 +942,24 @@ const USAGE_LIMIT_RESET_PAREN_RE = new RegExp(
|
|
|
942
942
|
// Zone-less fallback: capture to end of line, but never across a quote.
|
|
943
943
|
const USAGE_LIMIT_RESET_LINE_RE = new RegExp(
|
|
944
944
|
`${USAGE_LIMIT_PHRASE}[^\\n"]*?resets?\\s+([^\\n"]+?)\\s*$`, "im");
|
|
945
|
+
// The two regexes above are polynomial on long no-match lines, so they only
|
|
946
|
+
// ever run on a short window per phrase hit (untrusted input can be tens of MB).
|
|
947
|
+
const USAGE_LIMIT_SCAN_WINDOW_CHARS = 512;
|
|
948
|
+
const USAGE_LIMIT_MAX_PHRASE_HITS = 32;
|
|
949
|
+
|
|
950
|
+
/** Linear scan for phrase hits; returns at most MAX_PHRASE_HITS newline-cut windows. */
|
|
951
|
+
function collectUsageLimitWindows(source) {
|
|
952
|
+
const windows = [];
|
|
953
|
+
const phraseRe = new RegExp(USAGE_LIMIT_PHRASE, "gi");
|
|
954
|
+
let hit;
|
|
955
|
+
while (windows.length < USAGE_LIMIT_MAX_PHRASE_HITS && (hit = phraseRe.exec(source)) !== null) {
|
|
956
|
+
let window = source.slice(hit.index, hit.index + USAGE_LIMIT_SCAN_WINDOW_CHARS);
|
|
957
|
+
const nl = window.indexOf("\n");
|
|
958
|
+
if (nl !== -1) window = window.slice(0, nl);
|
|
959
|
+
windows.push(window);
|
|
960
|
+
}
|
|
961
|
+
return windows;
|
|
962
|
+
}
|
|
945
963
|
|
|
946
964
|
/**
|
|
947
965
|
* Parses a `claude --output-format json` single-result envelope from `text`.
|
|
@@ -1006,8 +1024,12 @@ export function extractSessionLimitResetTime(output) {
|
|
|
1006
1024
|
// JSON-wrapped message cannot swallow the trailing `","type":...` fields.
|
|
1007
1025
|
const text = parseCliEnvelope(output)?.result ?? output;
|
|
1008
1026
|
const source = typeof text === "string" ? text : output;
|
|
1009
|
-
const
|
|
1010
|
-
|
|
1027
|
+
const windows = collectUsageLimitWindows(source);
|
|
1028
|
+
let match = null;
|
|
1029
|
+
for (const w of windows) { if ((match = USAGE_LIMIT_RESET_PAREN_RE.exec(w))) break; }
|
|
1030
|
+
if (!match) {
|
|
1031
|
+
for (const w of windows) { if ((match = USAGE_LIMIT_RESET_LINE_RE.exec(w))) break; }
|
|
1032
|
+
}
|
|
1011
1033
|
if (!match) return undefined;
|
|
1012
1034
|
const resetAt = match[1].trim();
|
|
1013
1035
|
if (resetAt.length === 0) return undefined;
|
package/bin/watch-utils.mjs
CHANGED
package/commands/cleanup.md
CHANGED
|
@@ -226,7 +226,7 @@ For each orphaned issue found:
|
|
|
226
226
|
```bash
|
|
227
227
|
gh issue close $NUM {GH_FLAG} --comment "Closed by cleanup — PR #$MERGED_PR was already merged."
|
|
228
228
|
gh issue edit $NUM {GH_FLAG} --add-label "workflow:merged"
|
|
229
|
-
gh issue edit $NUM {GH_FLAG} --remove-label "workflow:in-review,workflow:awaiting-merge" 2>/dev/null || true
|
|
229
|
+
gh issue edit $NUM {GH_FLAG} --remove-label "workflow:in-review,workflow:remediating,workflow:awaiting-merge" 2>/dev/null || true # allowlist:check-command-side-effects
|
|
230
230
|
```
|
|
231
231
|
|
|
232
232
|
Also check open issues with `workflow:building` — same pattern (search for merged PRs referencing them).
|
|
@@ -1691,6 +1691,11 @@ classify_predecessor_state() {
|
|
|
1691
1691
|
esac
|
|
1692
1692
|
elif echo "$PRED_LABELS" | grep -qxE "needs-human|workflow:awaiting-merge"; then
|
|
1693
1693
|
echo "GATED"
|
|
1694
|
+
elif echo "$PRED_LABELS" | grep -qx "workflow:remediating"; then
|
|
1695
|
+
# forge#3541: autonomous remediation (ci-gate / in-pr-fix / base-sync) is pending or running. No human decision is
|
|
1696
|
+
# needed, so this is NOT GATED: dependents keep waiting exactly as for any mid-pipeline predecessor, and item 6.4
|
|
1697
|
+
# (or the engine handoff) drives the fix. A concurrent `needs-human` is matched by the GATED arm above first.
|
|
1698
|
+
echo "IN_PROGRESS"
|
|
1694
1699
|
elif echo "$PRED_LABELS" | grep -qx "workflow:engine-error"; then
|
|
1695
1700
|
# forge#2261: the engine/tool itself broke on this run (e.g. a fail-fast
|
|
1696
1701
|
# CLI_BACKEND_FAILED, or an exhausted retry loop where the runner never
|
|
@@ -1721,7 +1726,7 @@ classify_predecessor_state() {
|
|
|
1721
1726
|
- **DONE** — predecessor's code is in the base branch (`workflow:merged`, unless gated by a Step 4B item 0 trail escalation), or the predecessor is closed with no pending code. Safe for dependents to dispatch.
|
|
1722
1727
|
- **GATED** — predecessor is paused pending a human decision (`needs-human`) or pending only a human merge click (`workflow:awaiting-merge`). Its code is NOT yet in the base branch — except for a `workflow:merged` issue that also carries `needs-human` and the Step 4B item 0 trail-escalation comment (merged with an incomplete phase trail), whose code is merged but whose trail needs human review. Dependents are neither dispatched nor skipped — they move to the `blocked-on-human-merge` tracked state (item 6.5 below).
|
|
1723
1728
|
- **FAILED** — predecessor was closed as `workflow:invalid`, or the agent explicitly reported a build/test error. Dependents are marked "skipped — dependency failed" (item 6 below) — unchanged from prior behavior.
|
|
1724
|
-
- **IN_PROGRESS** — predecessor is still mid-pipeline (`investigating`/`ready-to-build`/`building`/`in-review`), or terminated `workflow:engine-error` (forge#2261 — an engine/tool failure, not a human-judgment block or a genuine content failure; treated as still-in-flight since stall-detection auto-resumes it). Dependent simply continues waiting; no special tracking needed.
|
|
1729
|
+
- **IN_PROGRESS** — predecessor is still mid-pipeline (`investigating`/`ready-to-build`/`building`/`in-review`), or autonomous remediation is pending (`workflow:remediating`, forge#3541 — the pipeline is fixing a base conflict, red CI or an in-PR finding itself; no human gate, so dependents are not held as GATED), or terminated `workflow:engine-error` (forge#2261 — an engine/tool failure, not a human-judgment block or a genuine content failure; treated as still-in-flight since stall-detection auto-resumes it). Dependent simply continues waiting; no special tracking needed.
|
|
1725
1730
|
|
|
1726
1731
|
A GATED predecessor whose PR later merges reclassifies to DONE the next time `classify_predecessor_state` runs (its label flips to `workflow:merged`; a merged predecessor gated on its phase trail reclassifies to DONE once a human removes `needs-human`) — this is exactly what the merge-triggered wake check (item 6.6 below) relies on.
|
|
1727
1732
|
|
|
@@ -2284,7 +2289,7 @@ done
|
|
|
2284
2289
|
|
|
2285
2290
|
Report every dependent whose skip was avoided this way — e.g. "#{DEP} — predecessor #{PRED} failed but never touched the shared file(s); edge dropped, #{DEP} not skipped." For all remaining dependents (real `EDGE_KIND` overlap confirmed, or a non-`EDGE_KIND` edge type), mark them "skipped — dependency #{X} failed" and report them. Do NOT dispatch them.
|
|
2286
2291
|
|
|
2287
|
-
6.4. **Auto-dispatch remediation against a `needs-human`-gated issue's own PR — runs unconditionally per completion, with or without dependents** <!-- Added: forge#1813, fixed: forge#2243 --> — item 6.5 below tracks the *dependents* of a `GATED` predecessor; this item handles the gated issue's own PR, which item 6.5/6.6 never re-drive on their own. **This check is bound directly to the issue that just completed — `PRED="$NUM"` (the same issue number carried from items 1-4 of this Step 4B sequence) — NOT to any `$PRED` produced by walking a dependent's predecessor list (item 5's readiness loop) or by item 6's FAILED-specific dependent-walk.** Run it for every completed agent, every cycle, regardless of whether that issue has any dependents in the DAG at all — a leaf issue (no dependents) is just as eligible as an issue that blocks others. Trigger condition: the completed issue classifies `GATED` **
|
|
2292
|
+
6.4. **Auto-dispatch remediation against a `needs-human`-gated or `workflow:remediating` issue's own PR — runs unconditionally per completion, with or without dependents** <!-- Added: forge#1813, fixed: forge#2243 --> — item 6.5 below tracks the *dependents* of a `GATED` predecessor; this item handles the gated issue's own PR, which item 6.5/6.6 never re-drive on their own. **This check is bound directly to the issue that just completed — `PRED="$NUM"` (the same issue number carried from items 1-4 of this Step 4B sequence) — NOT to any `$PRED` produced by walking a dependent's predecessor list (item 5's readiness loop) or by item 6's FAILED-specific dependent-walk.** Run it for every completed agent, every cycle, regardless of whether that issue has any dependents in the DAG at all — a leaf issue (no dependents) is just as eligible as an issue that blocks others. Trigger condition: the completed issue carries `needs-human` (classifies `GATED`) **or `workflow:remediating`** (classifies `IN_PROGRESS`, forge#3541 — the non-human state review sets for autonomous ci-gate / in-pr-fix / base-sync handoffs) — NOT `workflow:awaiting-merge`. That second state already means "remediated and re-reviewed to a clean verdict" (see forge#1810's guard) — dispatching remediation again would be redundant, not just wasteful, since there is nothing left to fix. A PR that reaches `workflow:awaiting-merge` now does so only when it targets `main` / the deploy gate (where `staging → main` is the genuine human gate and a human's merge click is required); a remediated-clean PR targeting a non-`main` base (`staging`, `milestone/*`) auto-lands to `workflow:merged` via remediate.md Phase M7's base-scoped auto-land bar (forge#2570) and never parks here. Either way this item's skip of `workflow:awaiting-merge` is unchanged.
|
|
2288
2293
|
|
|
2289
2294
|
```bash
|
|
2290
2295
|
# Bind PRED to the issue that just completed — independent of item 5's dependent-walk loop
|
|
@@ -2294,9 +2299,9 @@ done
|
|
|
2294
2299
|
PRED="$NUM"
|
|
2295
2300
|
|
|
2296
2301
|
PRED_CURRENT_LABEL=$(gh issue view "$PRED" -R {GH_REPO} --json labels \
|
|
2297
|
-
--jq '[.labels[].name | select(. == "needs-human" or . == "workflow:awaiting-merge")] | .[0] // empty' 2>/dev/null)
|
|
2302
|
+
--jq '[.labels[].name | select(. == "needs-human" or . == "workflow:remediating" or . == "workflow:awaiting-merge")] | .[0] // empty' 2>/dev/null)
|
|
2298
2303
|
|
|
2299
|
-
if [ "$PRED_CURRENT_LABEL" = "needs-human" ]; then
|
|
2304
|
+
if [ "$PRED_CURRENT_LABEL" = "needs-human" ] || [ "$PRED_CURRENT_LABEL" = "workflow:remediating" ]; then
|
|
2300
2305
|
# Resolve PRED's open PR using the anchored search (forge#1634/#1646 precedent —
|
|
2301
2306
|
# never a bare-number search, which would misattribute an unrelated PR).
|
|
2302
2307
|
GATING_PR=$(gh pr list -R {GH_REPO} --state open --search "\"Closes #${PRED}\" in:body" \
|
|
@@ -2308,18 +2313,23 @@ done
|
|
|
2308
2313
|
# avoids spawning a redundant agent that would immediately no-op on entry).
|
|
2309
2314
|
# Leading-marker + trusted-author count (forge#3412): a comment that merely quotes FORGE:REMEDIATION
|
|
2310
2315
|
# (review findings, INPR_FIX work order) must not suppress dispatch. Unreadable => skip dispatch (fail closed).
|
|
2316
|
+
# TRUSTED_SCRIPT resolver (canonical; byte-identical across specs, guarded by scripts/forge-root.test.sh): plugin root, FORGE_ROOT, FORGEDOCK_HOME, FORGE_HOME, install symlink, marketplaces, newest pinned plugin cache under CLAUDE_CONFIG_DIR then ~/.claude. Never the working directory (author-controlled, #3400). The cache scan matters because the plugin-root placeholder is not always substituted in forked runs.
|
|
2311
2317
|
_l="$(readlink -f "$HOME/.claude/commands/work-on.md" 2>/dev/null || true)"; _l="${_l%/commands/work-on.md}"
|
|
2318
|
+
_tc="$(printf '%s\n' '${CLAUDE_PLUGIN_ROOT}' "${FORGE_ROOT:-}" "${FORGEDOCK_HOME:-}" "${FORGE_HOME:-}" "$_l" "$HOME/.claude/plugins/marketplaces/forgedock")"
|
|
2319
|
+
for _cfg in "${CLAUDE_CONFIG_DIR:-}" "$HOME/.claude"; do
|
|
2320
|
+
[ -n "$_cfg" ] && _tc="$_tc"$'\n'"$(find -L "$_cfg/plugins/cache/forgedock/forgedock" -mindepth 1 -maxdepth 1 -type d 2>/dev/null | awk -F/ '$NF ~ /^[0-9]+\.[0-9]+\.[0-9]+$/{split($NF,a,".");printf "%d %d %d %s\n",a[1],a[2],a[3],$(0)}' | sort -k1,1nr -k2,2nr -k3,3nr | cut -d' ' -f4- || true)"
|
|
2321
|
+
done
|
|
2312
2322
|
TRUSTED_SCRIPT=""
|
|
2313
|
-
|
|
2323
|
+
while IFS= read -r _c; do
|
|
2314
2324
|
case "$_c" in /*) [ -z "$TRUSTED_SCRIPT" ] && [ -f "$_c/scripts/trusted-comments.sh" ] && TRUSTED_SCRIPT="$_c/scripts/trusted-comments.sh" ;; esac
|
|
2315
|
-
done
|
|
2325
|
+
done <<< "$_tc"
|
|
2316
2326
|
ALREADY_REMEDIATED=1
|
|
2317
2327
|
if [ -n "$TRUSTED_SCRIPT" ] && GATING_COMMENTS=$(gh api --paginate repos/{GH_REPO}/issues/${GATING_PR}/comments 2>/dev/null); then
|
|
2318
2328
|
ALREADY_REMEDIATED=$(printf '%s' "$GATING_COMMENTS" | bash "$TRUSTED_SCRIPT" count '^<!-- FORGE:REMEDIATION -->' 2>/dev/null || echo "1")
|
|
2319
2329
|
fi
|
|
2320
2330
|
|
|
2321
2331
|
if [ "$ALREADY_REMEDIATED" -eq 0 ]; then
|
|
2322
|
-
echo "Dispatching remediation for #{PRED}'s gating PR #{GATING_PR} (
|
|
2332
|
+
echo "Dispatching remediation for #{PRED}'s gating PR #{GATING_PR} (${PRED_CURRENT_LABEL})"
|
|
2323
2333
|
# Same Agent-spawn-fallback style as Step 4A's template — one background agent,
|
|
2324
2334
|
# whose sole job is to invoke /work-on in remediation mode and let it run to
|
|
2325
2335
|
# completion (AUTO-LANDED, HELD-AWAITING-MERGE, RE-ESCALATED, or UNFIXABLE — all
|
|
@@ -2329,9 +2339,9 @@ done
|
|
|
2329
2339
|
model="{SUBAGENT_MODEL}",
|
|
2330
2340
|
description="Remediate PR #{GATING_PR} (needs-human, blocks #{PRED})",
|
|
2331
2341
|
run_in_background=true,
|
|
2332
|
-
prompt="You are remediating GitHub PR #{GATING_PR} for the {PROJECT_NAME} project (repo: {GH_REPO}), which is currently held at `needs-human` on its linked issue #{PRED}.
|
|
2342
|
+
prompt="You are remediating GitHub PR #{GATING_PR} for the {PROJECT_NAME} project (repo: {GH_REPO}), which is currently held at `needs-human` or `workflow:remediating` (autonomous remediation pending) on its linked issue #{PRED}.
|
|
2333
2343
|
|
|
2334
|
-
**YOUR MISSION**: Invoke `Skill(skill='{FORGE_SKILL_PREFIX}work-on', args='{GATING_PR} --remediate --issue {PRED} --repo {GH_REPO} --gh-flag {GH_FLAG} --under-orchestration')` and let it run to completion. This is a self-contained flow: it classifies the gate first, replaces `needs-human` with `workflow:in-review` only for fixable remediation, checks out the PR branch, fixes any fixable review findings, re-reviews, and either auto-lands the PR, holds it at `workflow:awaiting-merge` for a human, re-escalates back to `needs-human`, or reports the block as policy-level and unfixable. Do NOT intervene manually — do not run raw git/gh commands yourself.
|
|
2344
|
+
**YOUR MISSION**: Invoke `Skill(skill='{FORGE_SKILL_PREFIX}work-on', args='{GATING_PR} --remediate --issue {PRED} --repo {GH_REPO} --gh-flag {GH_FLAG} --under-orchestration')` and let it run to completion. This is a self-contained flow: it classifies the gate first, replaces `needs-human` / `workflow:remediating` with `workflow:in-review` only for fixable remediation, checks out the PR branch, fixes any fixable review findings, re-reviews, and either auto-lands the PR, holds it at `workflow:awaiting-merge` for a human, re-escalates back to `needs-human`, or reports the block as policy-level and unfixable. Do NOT intervene manually — do not run raw git/gh commands yourself.
|
|
2335
2345
|
|
|
2336
2346
|
**DO NOT STOP EARLY**: if the Skill call returns without a terminal `REMEDIATE_RESULT.status`, invoke it again — it re-reads GitHub state and resumes. Terminal statuses are: `COMPLETE`, `ALREADY_DONE`, `UNFIXABLE`, `BLOCKED`, `REREVIEW_REQUIRED`. `REREVIEW_REQUIRED` is terminal for the worker only: the orchestrator then runs the re-review handoff (item 6.4) or the terminal fallback, so it is never a silent end state.
|
|
2337
2347
|
|
|
@@ -2378,8 +2388,9 @@ Do not ask the user questions — you are running autonomously in the background
|
|
|
2378
2388
|
# Lease age reads the NEWEST row carrying the same `Claim:` id as the earliest claim (.[0]); ownership/LOST stays on .[0].
|
|
2379
2389
|
REREVIEW_LEASE_FILTER='(.[0].body | capture("Claim: (?<c>[^\\n]+)").c) as $c | [.[] | select(.body | split("\n") | any(. == ("Claim: " + $c)))] | .[-1].body | capture("Lease: (?<t>[0-9T:Z-]+)").t'
|
|
2380
2390
|
REREVIEW_FIRST_LEASE_FILTER='.[0].body | capture("Lease: (?<t>[0-9T:Z-]+)").t'
|
|
2381
|
-
# Explicit release: a trusted leading `<!-- FORGE:REREVIEW_RELEASED -->` row for this head (posted by the claimant after
|
|
2382
|
-
|
|
2391
|
+
# Explicit release: a trusted leading `<!-- FORGE:REREVIEW_RELEASED -->` row for this head (posted by the claimant only after the outcome is settled, forge#3486).
|
|
2392
|
+
# Only a row carrying `Outcome: SETTLED` counts; a legacy or unsettled release (no Outcome line) is ignored so it can never short-circuit staleness detection.
|
|
2393
|
+
REREVIEW_RELEASED_FILTER='[.[] | select((.body | startswith("<!-- FORGE:REREVIEW_RELEASED -->")) and (.body | contains("Head: " + $sha)) and (.body | split("\n") | any(. == "Outcome: SETTLED")))] | length'
|
|
2383
2394
|
|
|
2384
2395
|
PRED="$NUM"
|
|
2385
2396
|
REREVIEW_STATE="NONE"; REREVIEW_PR=""; REREVIEW_SHA=""; REREVIEW_BASE=""
|
|
@@ -2414,7 +2425,8 @@ Do not ask the user questions — you are running autonomously in the background
|
|
|
2414
2425
|
REREVIEW_STATE="PENDING" # DRY_RUN is handled in Step 2 (no `DRY_RUN != true` term here, so its branch is reachable)
|
|
2415
2426
|
elif [ "$NEEDS_REREVIEW" -ge 1 ]; then
|
|
2416
2427
|
# A claim for this head already exists (orchestrator or router). forge#3428: age alone never makes it stale.
|
|
2417
|
-
# released ->
|
|
2428
|
+
# released -> STALE (forge#3486: Step 1 only reaches here with the issue still at workflow:in-review, so a SETTLED release
|
|
2429
|
+
# is not terminal; the outcome never landed (crash or label reset) and the fallback must re-apply)
|
|
2418
2430
|
# lease still fresh -> CLAIMED (the lease is the newest row of the owner's claim; a renewal extends it)
|
|
2419
2431
|
# lease expired -> LIVENESS probe: PR not open -> NONE; recent trusted FORGE:REVIEW* activity inside the lease
|
|
2420
2432
|
# window or running checks -> CLAIMED; no sign of life -> STALE. A lookup error is UNREADABLE.
|
|
@@ -2426,7 +2438,7 @@ Do not ask the user questions — you are running autonomously in the background
|
|
|
2426
2438
|
FIRST_EPOCH=$(printf '%s' "$FIRST_T" | jq -R 'fromdateiso8601' 2>/dev/null || true)
|
|
2427
2439
|
NOW=$(date -u +%s)
|
|
2428
2440
|
if [ -z "$RELEASED" ]; then REREVIEW_STATE="UNREADABLE"
|
|
2429
|
-
elif [ "$RELEASED" -ge 1 ]; then REREVIEW_STATE="
|
|
2441
|
+
elif [ "$RELEASED" -ge 1 ]; then REREVIEW_STATE="STALE" # released but the issue is still workflow:in-review with an open PR: not terminal, re-apply the fallback
|
|
2430
2442
|
elif [ -z "$CLAIM_EPOCH" ] || [ -z "$FIRST_EPOCH" ]; then REREVIEW_STATE="STALE" # malformed claim (no parseable Lease)
|
|
2431
2443
|
elif [ $(( NOW - CLAIM_EPOCH )) -le "$REREVIEW_LEASE_SECS" ]; then REREVIEW_STATE="CLAIMED"
|
|
2432
2444
|
elif ! LIVE_META=$(gh pr view "$REREVIEW_PR" -R {GH_REPO} --json state,statusCheckRollup \
|
|
@@ -2454,7 +2466,7 @@ Do not ask the user questions — you are running autonomously in the background
|
|
|
2454
2466
|
echo "REREVIEW_STATE=$REREVIEW_STATE PR=$REREVIEW_PR SHA=$REREVIEW_SHA BASE=$REREVIEW_BASE FIRST_CLAIM_ID=$FIRST_CLAIM_ID"
|
|
2455
2467
|
```
|
|
2456
2468
|
|
|
2457
|
-
`REREVIEW_STATE=NONE` means nothing to do (including an issue already at `needs-human`, closed, or not at `workflow:in-review`). `CLAIMED` means another actor holds a live claim for this head (an unexpired or renewed lease, or an expired lease with recent liveness such as a trusted `FORGE:REVIEW*` comment inside the lease window or running checks): do nothing, re-run Step 1 next cycle. A `FORGE:REREVIEW_RELEASED` marker for the head
|
|
2469
|
+
`REREVIEW_STATE=NONE` means nothing to do (including an issue already at `needs-human`, closed, or not at `workflow:in-review`). `CLAIMED` means another actor holds a live claim for this head (an unexpired or renewed lease, or an expired lease with recent liveness such as a trusted `FORGE:REVIEW*` comment inside the lease window or running checks): do nothing, re-run Step 1 next cycle. A merged/closed PR is `NONE`. A `FORGE:REREVIEW_RELEASED` marker (`Outcome: SETTLED`) for the head whose issue is still at `workflow:in-review` is `STALE`, not `NONE` (forge#3486): the release was posted but the terminal outcome never landed (a crash between the release and the fallback write, or a human label reset on the same head), so the release is not terminal and the fallback re-applies. `UNREADABLE` fails closed: classify the issue `GATED` this cycle without writing anything, and re-run Step 1 next cycle. `STALE` (an expired lease with no liveness and the PR still open, a lease past the `REREVIEW_MAX_SECS` ceiling, an unparseable claim, or a trail that does not match the current head) goes straight to the Step 3 terminal fallback with the reason `stale re-review claim or trail`, without dispatching. Only `PENDING` continues.
|
|
2458
2470
|
|
|
2459
2471
|
**Step 2 — dispatch (claim first, forge#3413).** The claim is written and read back BEFORE `review-pr` is invoked. A failed post or read-back means NO dispatch (fail closed, terminal fallback), so there is never a wasted duplicate dispatch, and a second actor sees the claim and stands down. Bash blocks are independent executions: run the claim block with the **SHARED HELPERS** (`rereview_trusted_rows`, `REREVIEW_CLAIMS`, `REREVIEW_DONE_FILTER`, the lease/released filters and `REREVIEW_LEASE_SECS` / `REREVIEW_MAX_SECS`, plus its transitive deps `resolve_orch_login` and `trail_cache_dir`, pasted if not already defined in the block) from Step 1 pasted verbatim at its top, and `REREVIEW_PR` / `REREVIEW_SHA` set to the values Step 1 printed. The read-back lists the PR's comments by number and applies the same trusted predicate. The EARLIEST trusted claim for the head wins, defined as the LOWEST comment id (`.[0]` of the id-sorted `REREVIEW_CLAIMS`, unchanged by renewals; forge#3429): if the first matching row does not carry this actor's `Claim:` id, another actor claimed first (`LOST`) and this one stands down. The read-back waits a short settle delay before the first read and re-reads once after `VERIFIED` (a lower-id claim that appears late demotes it to `LOST`). Any API failure, zero matching rows, or a failed confirming re-read is `UNVERIFIED` (fail closed, never `VERIFIED`). The block has no `exit`, so it is safe in a shared or sourced shell:
|
|
2460
2472
|
|
|
@@ -2519,7 +2531,7 @@ Do not ask the user questions — you are running autonomously in the background
|
|
|
2519
2531
|
|
|
2520
2532
|
If the call returns no `REVIEW_RESULT` (interrupted or no result), invoke it once more; this retry is the only one (bounded, independent of the Phase 4R `github-unavailable` waits) and does not post a second claim: the claim for this head is already recorded.
|
|
2521
2533
|
|
|
2522
|
-
**Renew and release (forge#3428).** After `review-pr` returns (any result, including none), the claimant posts a checked, DRY_RUN-guarded release
|
|
2534
|
+
**Renew and release (forge#3428).** After `review-pr` returns (any result, including none), the claimant posts a checked, DRY_RUN-guarded release, but only AFTER the outcome is settled (forge#3486): `review-pr` produced a labelled terminal state (merged, or `needs-human`), or the Step 3 terminal fallback's label write was verified. Never post it before Step 3 has run: a release that precedes an unfinished outcome would hide a crash from Step 1. Because Step 1 only classifies issues still at `workflow:in-review`, a release found there means the outcome never landed and reads `STALE`. Run it with the SHARED HELPERS (the same set, including `resolve_orch_login` and `trail_cache_dir`) pasted at the top and `REREVIEW_PR` / `REREVIEW_SHA` / `CLAIM_ID` from Step 2. A failed post is reported (never ignored) but is not fatal: liveness in Step 1 still protects a live claimant. Renewal is best-effort: between review phases the claimant may re-post `<!-- FORGE:REREVIEW_DISPATCHED -->` with the SAME `Claim:` id and a fresh `Lease:` (Step 1 reads the newest row of the earliest claim's id for lease age; the earliest-claim-wins read-back stays on `.[0]`):
|
|
2523
2535
|
|
|
2524
2536
|
```bash
|
|
2525
2537
|
if [ "${DRY_RUN:-false}" = "true" ]; then
|
|
@@ -2529,13 +2541,16 @@ Do not ask the user questions — you are running autonomously in the background
|
|
|
2529
2541
|
Head: ${REREVIEW_SHA}
|
|
2530
2542
|
Actor: orchestrator
|
|
2531
2543
|
Claim: ${CLAIM_ID}
|
|
2544
|
+
Outcome: SETTLED
|
|
2532
2545
|
|
|
2533
|
-
Re-review finished by the orchestrator: claim released."; }
|
|
2546
|
+
Re-review finished by the orchestrator and the outcome is settled: claim released."; }
|
|
2534
2547
|
post_release || { sleep 2; post_release || echo "REREVIEW release post failed twice (liveness still protects the claim)" >&2; }
|
|
2535
2548
|
fi
|
|
2536
2549
|
```
|
|
2537
2550
|
|
|
2538
|
-
|
|
2551
|
+
Run the release block only at the end of Step 3 below, once the outcome is settled (merged or labelled terminal state, or the fallback's label write verified), not right after `review-pr` returns.
|
|
2552
|
+
|
|
2553
|
+
**Step 3 — outcome.** On `REVIEW_RESULT: status: COMPLETE` (merged), drive `work-on:close` and let item 6.6 wake dependents as normal. If `review-pr` returns another result with its own labelled terminal state (CHANGES REQUESTED, or `needs-human` for genuine blockers), that verdict applies and the issue is `GATED` or failed per the existing rules. `REREVIEW_MARKER=LOST` (another actor claimed this head first) and `REREVIEW_STATE=CLAIMED` take no action: leave the issue as it is and re-check next cycle. Before applying the terminal fallback for `STALE`, re-read the PR state and the issue labels once more: a merged or closed PR, or an issue that already advanced, means skip the fallback and report the state (forge#3428). If dispatch failed, the skill did not resolve, the issue is still at `workflow:in-review` with no `needs-human` after the retry, Step 1 printed `STALE`, or Step 2 printed `REREVIEW_MARKER=UNVERIFIED` and the re-check below found no live claim, apply the **terminal fallback** defined in `commands/work-on.md` Phase 0A.1 (labels re-read first, comment with the reason, add `needs-human` and remove `workflow:in-review` in one edit, verify the write), classify the issue `GATED`, and surface it to the operator in the run summary as `GATED: re-review could not run (#{PRED}, PR #{REREVIEW_PR}): <reason>`. For `REREVIEW_MARKER=UNVERIFIED`, first re-check for another actor's fresh claim (forge#3429): re-run the Step 1 CLAIMED/STALE/DONE classification for this head (with the SHARED HELPERS pasted at the top of that block). Map the result, reading the printed `FIRST_CLAIM_ID` (the lowest-id, winning claim, the same `.[0]` row that decides ownership everywhere else): if `FIRST_CLAIM_ID` equals this actor's own `CLAIM_ID`, the winning claim is our own unverified one, so go to the fallback (do not stand down on it). If `FIRST_CLAIM_ID` is another actor's id, the winning claim is not ours: classify it with the mapping below, exactly as the router's Claim pre-check step 4 does for `LOST` (forge#3431), so a `STALE` foreign winner (a crashed earlier claimant) goes to the fallback and never strands the issue, even though our own row still exists. Otherwise (and for a foreign winner) `CLAIMED` (another actor's live claim) and `NONE` (merged, closed, or advanced past in-review) stand down like `LOST` (no dispatch, no fallback, re-check next cycle); `UNREADABLE` writes nothing (fail closed, classify `GATED` this cycle without the fallback, as in Step 1); `STALE` and `PENDING` go to the fallback. The reason is `re-review claim could not be recorded` and the report line is `GATED: re-review could not run (#{PRED}, PR #{REREVIEW_PR}): marker write failed`; because the claim is written first, `UNVERIFIED` now means `review-pr` was NOT dispatched. Do not leave the issue `PENDING`. A failed label write is reported as such, not as done. The verified per-head claim bounds this to one re-review per PR head across the orchestrator and the router (no loop). Never review inline.
|
|
2539
2554
|
|
|
2540
2555
|
This satisfies #1809 Q2 (the orchestrator auto-dispatches remediation against the gated issue itself — the exact gap forge#1812's item 6.5/6.6 left open, since those items only ever track and wake *dependents*, never the gated PR's own remediation) — and closes forge#2243 (the gap that #1812's fix left open for *leaf* issues with no dependents: because this item's `$PRED` binding was never made explicit and self-contained, it was only ever reached while walking a dependent's predecessor list, so a `needs-human` issue that has no dependents never got remediation dispatched and its CHANGES-REQUESTED PR re-reviewed the same unchanged commit forever). The remediation agent's outcome is picked up on the **next** completion-monitoring cycle of this same Step 4B loop: if it lands (`workflow:merged`), item 6.6 below fires normally and wakes any `blocked-on-human-merge` dependents (if any exist — a leaf issue simply has none to wake); if it holds/re-escalates, the issue simply remains `GATED` and item 6.5 continues tracking its dependents (if any) unchanged.
|
|
2541
2556
|
|
|
@@ -100,7 +100,7 @@ if [ -n "$TARGET_ISSUE" ]; then
|
|
|
100
100
|
else
|
|
101
101
|
# Fleet scan: query each workflow label separately, merge, deduplicate
|
|
102
102
|
ORPHAN_JSON=$(
|
|
103
|
-
for LABEL in "workflow:investigating" "workflow:ready-to-build" "workflow:building" "workflow:in-review"; do
|
|
103
|
+
for LABEL in "workflow:investigating" "workflow:ready-to-build" "workflow:building" "workflow:in-review" "workflow:remediating"; do
|
|
104
104
|
gh issue list ${GH_FLAG} \
|
|
105
105
|
--state open \
|
|
106
106
|
--label "$LABEL" \
|
|
@@ -457,7 +457,7 @@ for NUM in $ORPHAN_LIST; do
|
|
|
457
457
|
else
|
|
458
458
|
gh issue edit "$NUM" ${GH_FLAG} \
|
|
459
459
|
--add-label "workflow:merged" \
|
|
460
|
-
--remove-label "workflow:investigating,workflow:ready-to-build,workflow:building,workflow:in-review,workflow:awaiting-merge,workflow:invalid,workflow:decomposed" \
|
|
460
|
+
--remove-label "workflow:investigating,workflow:ready-to-build,workflow:building,workflow:in-review,workflow:remediating,workflow:awaiting-merge,workflow:invalid,workflow:decomposed" \
|
|
461
461
|
2>/dev/null || true
|
|
462
462
|
gh issue close "$NUM" ${GH_FLAG} \
|
|
463
463
|
--comment "Closed by /recover-orphans: PR #${PR_NUM} was already merged. Labels corrected." \
|
|
@@ -506,7 +506,7 @@ ${CI_GATE_OUT}
|
|
|
506
506
|
2>/dev/null || true
|
|
507
507
|
gh issue edit "$NUM" ${GH_FLAG} \
|
|
508
508
|
--add-label "workflow:merged" \
|
|
509
|
-
--remove-label "workflow:investigating,workflow:ready-to-build,workflow:building,workflow:in-review,workflow:awaiting-merge,workflow:invalid,workflow:decomposed" \
|
|
509
|
+
--remove-label "workflow:investigating,workflow:ready-to-build,workflow:building,workflow:in-review,workflow:remediating,workflow:awaiting-merge,workflow:invalid,workflow:decomposed" \
|
|
510
510
|
2>/dev/null || true
|
|
511
511
|
fi
|
|
512
512
|
fi
|
|
@@ -579,7 +579,7 @@ See the latest FORGE:PHASE_TRAIL_FAILED comment for the missing phases. Re-run e
|
|
|
579
579
|
else
|
|
580
580
|
# After review: update label
|
|
581
581
|
gh issue edit "$NUM" ${GH_FLAG} --add-label "workflow:in-review" \
|
|
582
|
-
--remove-label "workflow:building,workflow:awaiting-merge" 2>/dev/null || true
|
|
582
|
+
--remove-label "workflow:building,workflow:remediating,workflow:awaiting-merge" 2>/dev/null || true
|
|
583
583
|
RECOVERY_RESULTS="${RECOVERY_RESULTS}| #${NUM} | review-pr | PR #${PR_NUM} submitted for review |\n"
|
|
584
584
|
fi
|
|
585
585
|
fi
|
|
@@ -607,7 +607,7 @@ See the latest FORGE:PHASE_TRAIL_FAILED comment for the missing phases. Re-run e
|
|
|
607
607
|
echo " [DRY-RUN] Would: remove workflow:investigating, workflow:ready-to-build, workflow:building, workflow:in-review"
|
|
608
608
|
else
|
|
609
609
|
gh issue edit "$NUM" ${GH_FLAG} \
|
|
610
|
-
--remove-label "workflow:investigating,workflow:ready-to-build,workflow:building,workflow:in-review,workflow:awaiting-merge" \
|
|
610
|
+
--remove-label "workflow:investigating,workflow:ready-to-build,workflow:building,workflow:in-review,workflow:remediating,workflow:awaiting-merge" \
|
|
611
611
|
2>/dev/null || true
|
|
612
612
|
gh issue comment "$NUM" ${GH_FLAG} \
|
|
613
613
|
--body "<!-- FORGE:ORPHAN_RECOVERED -->
|
|
@@ -176,7 +176,7 @@ Append this block at the very end of your comment (after the `---` footer line,
|
|
|
176
176
|
6. **Summary**: Concise one-line description (no pipe `|` characters in summary)
|
|
177
177
|
7. **Empty block**: If no findings at all, include just the START/END markers
|
|
178
178
|
8. **HTML comments**: The block is invisible in rendered markdown but parseable by the review system
|
|
179
|
-
9. **Agent marker**: Include exactly one `<!-- FORGE:REVIEW-AGENT:{domain} -->` marker in the persisted body, where `{domain}` is the
|
|
179
|
+
9. **Agent marker**: Include exactly one `<!-- FORGE:REVIEW-AGENT:{domain} -->` marker in the persisted body, where `{domain}` is the marker domain the orchestrator passes you (the canonical value from the roster-to-marker table in `review-pr.md`, e.g. `infra` and `scraper`; never a lowercased display name such as `infrastructure` or `scraping`).
|
|
180
180
|
10. **Reviewed SHA**: Include exactly one line `Reviewed-SHA: [REVIEW_SHA]` (the full 40-hex head you were pinned to) in the persisted body, outside the marker. `/review-pr` scopes its completion wait and idempotent re-entry to this line, so a comment without the current head does not count as a completed review. Do NOT put the SHA inside the `<!-- FORGE:REVIEW-AGENT:{domain} -->` marker.
|
|
181
181
|
|
|
182
182
|
### Domain Prefixes
|
|
@@ -46,17 +46,17 @@ For each agent selected in Phase 3B, read **only** the files relevant to that ag
|
|
|
46
46
|
|
|
47
47
|
2. **Then read the persona file for each selected agent**:
|
|
48
48
|
|
|
49
|
-
| Agent | Trigger | File |
|
|
50
|
-
|
|
51
|
-
| General Security & Quality Scan | ALWAYS RUNS | `review-pr-agents/security.md` |
|
|
52
|
-
| Auth Conventions Auditor | AUTH domain | `review-pr-agents/auth.md` |
|
|
53
|
-
| Billing Integrity Auditor | BILLING domain | `review-pr-agents/billing.md` |
|
|
54
|
-
| Concurrency & Race Condition Auditor | CONCURRENCY or BILLING domain | `review-pr-agents/concurrency.md` |
|
|
55
|
-
| Domain Logic Auditor | SCRAPING domain (requires `review.domains.scraping` in forge.yaml) | `review-pr-agents/scraper.md` |
|
|
56
|
-
| Frontend Quality Auditor | WEB service touched | `review-pr-agents/frontend.md` |
|
|
57
|
-
| API Design & Consistency Auditor | New/modified routers or SDK/OpenAPI files | `review-pr-agents/api.md` |
|
|
58
|
-
| Database & Migration Auditor | DATABASE domain | `review-pr-agents/database.md` |
|
|
59
|
-
| Infrastructure & Deploy Safety Auditor | INFRA service touched | `review-pr-agents/infra.md` |
|
|
49
|
+
| Agent | Trigger | Marker domain | File |
|
|
50
|
+
|-------|---------|---------------|------|
|
|
51
|
+
| General Security & Quality Scan | ALWAYS RUNS | `security` | `review-pr-agents/security.md` |
|
|
52
|
+
| Auth Conventions Auditor | AUTH domain | `auth` | `review-pr-agents/auth.md` |
|
|
53
|
+
| Billing Integrity Auditor | BILLING domain | `billing` | `review-pr-agents/billing.md` |
|
|
54
|
+
| Concurrency & Race Condition Auditor | CONCURRENCY or BILLING domain | `concurrency` | `review-pr-agents/concurrency.md` |
|
|
55
|
+
| Domain Logic Auditor | SCRAPING domain (requires `review.domains.scraping` in forge.yaml) | `scraper` | `review-pr-agents/scraper.md` |
|
|
56
|
+
| Frontend Quality Auditor | WEB service touched | `frontend` | `review-pr-agents/frontend.md` |
|
|
57
|
+
| API Design & Consistency Auditor | New/modified routers or SDK/OpenAPI files | `api` | `review-pr-agents/api.md` |
|
|
58
|
+
| Database & Migration Auditor | DATABASE domain | `database` | `review-pr-agents/database.md` |
|
|
59
|
+
| Infrastructure & Deploy Safety Auditor | INFRA service touched | `infra` | `review-pr-agents/infra.md` |
|
|
60
60
|
|
|
61
61
|
## Domain Prefixes (for Structured Findings)
|
|
62
62
|
|