forgedock 1.6.0 → 1.7.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/README.md +13 -2
- package/bin/engine/orchestrate-canary.mjs +15 -0
- package/bin/engine/orchestrate-canary.test.mjs +28 -0
- package/bin/forgedock.mjs +70 -1
- package/bin/opencode-adapter.mjs +435 -0
- package/bin/registry.mjs +2 -1
- package/bin/runner.mjs +207 -59
- package/commands/orchestrate/phase-3-dependency.md +23 -3
- package/commands/orchestrate/phase-4-execution.md +116 -55
- package/commands/work-on/build.md +10 -1
- package/commands/work-on.md +10 -1
- package/docs/OPENCODE.md +132 -252
- package/package.json +3 -2
|
@@ -352,14 +352,39 @@ This step is the deterministic counterpart to fix #2 (atomic create-if-absent in
|
|
|
352
352
|
Before building agent prompts, run `classify-lane.sh` for every issue in the current dispatch group to compute `{LANE}` and `{PR_BASE}` deterministically. The script output is authoritative — the LLM MUST NOT override or reason around it.
|
|
353
353
|
|
|
354
354
|
```bash
|
|
355
|
-
#
|
|
356
|
-
#
|
|
357
|
-
#
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
355
|
+
# Resolve the helper once and reuse it for the initial dispatch and all finding
|
|
356
|
+
# classification loops. Claude keeps its installed path as the default, while
|
|
357
|
+
# OpenCode and Codex can use ForgeDock's repository-local scripts.
|
|
358
|
+
resolve_classify_lane() {
|
|
359
|
+
local candidates=()
|
|
360
|
+
if [ "${FORGE_RUNTIME:-}" = "opencode" ] ||
|
|
361
|
+
[ -n "${OPENCODE_SESSION_ID:-}" ] ||
|
|
362
|
+
[ -n "${OPENCODE_PID:-}" ] ||
|
|
363
|
+
[ -n "${OPENCODE:-}" ]; then
|
|
364
|
+
[ -n "${FORGE_HOME:-}" ] && candidates+=("$FORGE_HOME/scripts/classify-lane.sh")
|
|
365
|
+
[ -n "${REPO_PATH:-}" ] && candidates+=("$REPO_PATH/scripts/classify-lane.sh")
|
|
366
|
+
candidates+=("$HOME/.opencode/scripts/classify-lane.sh")
|
|
367
|
+
else
|
|
368
|
+
[ -n "${FORGE_HOME:-}" ] && candidates+=("$FORGE_HOME/scripts/classify-lane.sh")
|
|
369
|
+
candidates+=("$HOME/.claude/scripts/classify-lane.sh")
|
|
370
|
+
[ -n "${REPO_PATH:-}" ] && candidates+=("$REPO_PATH/scripts/classify-lane.sh")
|
|
371
|
+
fi
|
|
372
|
+
|
|
373
|
+
local candidate
|
|
374
|
+
for candidate in "${candidates[@]}"; do
|
|
375
|
+
if [ -f "$candidate" ]; then
|
|
376
|
+
printf '%s\n' "$candidate"
|
|
377
|
+
return 0
|
|
378
|
+
fi
|
|
379
|
+
done
|
|
380
|
+
echo "ERROR: classify-lane.sh is not installed in any configured runtime path." >&2
|
|
381
|
+
return 1
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
CLASSIFY_LANE_SCRIPT=$(resolve_classify_lane) || {
|
|
385
|
+
echo "ERROR: cannot classify lanes without classify-lane.sh" >&2
|
|
386
|
+
exit 1
|
|
387
|
+
}
|
|
363
388
|
|
|
364
389
|
declare -A ISSUE_LANE
|
|
365
390
|
declare -A ISSUE_PR_BASE
|
|
@@ -498,7 +523,7 @@ SURFACE_BATCHED_FINDINGS=() # all member issue numbers absorbed into a batch a
|
|
|
498
523
|
SURFACE_BATCH_COUNT=0 # count of batch issues created across the run
|
|
499
524
|
|
|
500
525
|
for NUM in {ready_issue_numbers}; do
|
|
501
|
-
PR_BASE=$(bash
|
|
526
|
+
PR_BASE=$(bash "$CLASSIFY_LANE_SCRIPT" "$NUM" -R {GH_REPO}) || {
|
|
502
527
|
echo "ERROR: classify-lane.sh failed for #$NUM — adding needs-human label and skipping" >&2
|
|
503
528
|
gh issue edit "$NUM" -R {GH_REPO} --add-label "needs-human" 2>/dev/null || true
|
|
504
529
|
continue
|
|
@@ -521,9 +546,34 @@ Use `${ISSUE_LANE[$NUM]}` and `${ISSUE_PR_BASE[$NUM]}` to populate `{LANE}` and
|
|
|
521
546
|
|
|
522
547
|
**Engine-first dispatch (default)**: When `forgedock` is in PATH, dispatch each ready issue via the durable engine rather than spawning prose Agent sub-agents. The engine's phase table enforces gate semantics in code — its fail-closed review gate and deterministic phase ordering are not subject to LLM interpretation.
|
|
523
548
|
|
|
549
|
+
**OpenCode dispatch (runtime-neutral adapter)**: When `FORGE_RUNTIME=opencode` or
|
|
550
|
+
an OpenCode runtime marker is present, do not invoke the engine's Claude-backed
|
|
551
|
+
runner and do not use the Claude `Agent(...)` fallback. Dispatch each ready
|
|
552
|
+
issue through OpenCode's native `task` tool, loading the nested
|
|
553
|
+
`commands/work-on.md` pipeline. Use GitHub labels and `FORGE:*` comments as the
|
|
554
|
+
resume state. After each task continuation, re-read the issue workflow label
|
|
555
|
+
and continue until it reaches
|
|
556
|
+
`workflow:merged`, `workflow:invalid`, `needs-human`, or
|
|
557
|
+
`workflow:awaiting-merge`.
|
|
558
|
+
|
|
559
|
+
```bash
|
|
560
|
+
if [ "${FORGE_RUNTIME:-}" = "opencode" ]; then
|
|
561
|
+
echo "OpenCode native task dispatch selected — load commands/work-on.md for each ready issue."
|
|
562
|
+
echo "Do not call forgedock run-issue or the Claude Agent(...) fallback in this branch."
|
|
563
|
+
echo "If native task dispatch cannot run, post FORGE:OPENCODE_BLOCKED with the missing capability and add needs-human."
|
|
564
|
+
fi
|
|
565
|
+
```
|
|
566
|
+
|
|
567
|
+
This branch is additive. When OpenCode is not selected, continue through the
|
|
568
|
+
existing engine-first and Claude Agent-spawn paths below without modification.
|
|
569
|
+
|
|
524
570
|
**CRITICAL — never background via shell `&`/`wait`** (fixed forge#2466): A single `forgedock run-issue` invocation drives an issue through investigate → build → review → close and routinely runs 30+ minutes. Backgrounding the process at the *shell* level (`cmd &` … `wait`) does not escape the Bash tool's own per-invocation ceiling — `wait` is itself the foreground command the tool watches, and it blocks for the combined duration of every process in the chunk. This made engine-first dispatch effectively dead code: any chunk running longer than the ceiling was always killed, so every run silently fell through to the Agent-spawn fallback below. The fix: dispatch each `forgedock run-issue` invocation as its **own `Bash` tool call with `run_in_background=true`** — the harness's native "start it, don't wait, notify me on completion" primitive — never with shell-level `&`/`wait`. This is exactly the same async model the Agent-spawn-fallback path already uses (and Step 4B's notification-driven completion loop already expects), so one monitoring loop now covers both dispatch styles.
|
|
525
571
|
|
|
526
572
|
```bash
|
|
573
|
+
if [ "${FORGE_RUNTIME:-}" != "opencode" ] &&
|
|
574
|
+
[ -z "${OPENCODE_SESSION_ID:-}" ] &&
|
|
575
|
+
[ -z "${OPENCODE_PID:-}" ] &&
|
|
576
|
+
[ -z "${OPENCODE:-}" ]; then
|
|
527
577
|
# Engine-first dispatch: check CLI availability, then dispatch ready issues in score order
|
|
528
578
|
# Uses SORTED_READY_SET[] from Step 3E.5 (descending value/cost) and budget gate from Step 4A-pre.0
|
|
529
579
|
FORGEDOCK_AVAILABLE=$(command -v forgedock >/dev/null 2>&1 && echo "true" || echo "false")
|
|
@@ -536,20 +586,13 @@ FORGEDOCK_AVAILABLE=$(command -v forgedock >/dev/null 2>&1 && echo "true" || ech
|
|
|
536
586
|
# `forgedock run-issue` call fails within seconds, and nothing re-routes the batch — a human has
|
|
537
587
|
# to notice and manually re-dispatch via the Agent-spawn path below.
|
|
538
588
|
#
|
|
539
|
-
#
|
|
540
|
-
#
|
|
541
|
-
#
|
|
542
|
-
#
|
|
543
|
-
# `bin/runner.mjs`'s `isClaudeCliAvailable()` for this exact reason) can find and run an
|
|
544
|
-
# npx-transient/extensionless shim that `bin/runner.mjs`'s actual backend call —
|
|
545
|
-
# `runCliBackend()`'s `spawnFn(bin, cliArgs, { shell: false, ... })` with bare `bin = "claude"`
|
|
546
|
-
# (no shell resolution) — cannot. A canary that shell-resolves `claude` reproduces the exact
|
|
547
|
-
# blind spot it exists to catch and reports the backend healthy in precisely forge#2741's failure
|
|
548
|
-
# scenario. The canary MUST reproduce `runCliBackend()`'s exact invocation shape — bare binary
|
|
549
|
-
# name, `shell: false` — via a one-off Node `spawnSync` call, not a bash-native probe:
|
|
589
|
+
# Use the runner's tested backend policy rather than duplicating it as shell/Node prose. This
|
|
590
|
+
# reuses the production resolved-CLI path check (including transient-shim refresh), honors an
|
|
591
|
+
# explicit FORGEDOCK_BACKEND, and accepts API fallback only when ANTHROPIC_API_KEY is configured.
|
|
592
|
+
# It performs no paid model request.
|
|
550
593
|
if [ "$FORGEDOCK_AVAILABLE" = "true" ]; then
|
|
551
|
-
if !
|
|
552
|
-
echo "WARNING: forgedock CLI is on PATH, but
|
|
594
|
+
if ! forgedock backend-check --quiet; then
|
|
595
|
+
echo "WARNING: forgedock CLI is on PATH, but no configured execution backend is locally usable. Downgrading to the Agent-spawn fallback path for this entire run — see forge#2743."
|
|
553
596
|
FORGEDOCK_AVAILABLE="false"
|
|
554
597
|
fi
|
|
555
598
|
fi
|
|
@@ -624,6 +667,7 @@ else
|
|
|
624
667
|
# Fall through to Agent-spawn template below. The SubagentStop hook (bin/hooks/interactive-engine.mjs)
|
|
625
668
|
# bridges these runs to the engine run-log for state persistence even on the fallback path.
|
|
626
669
|
fi
|
|
670
|
+
fi
|
|
627
671
|
```
|
|
628
672
|
|
|
629
673
|
**Dispatch each issue in `DISPATCH_NOW` via its own backgrounded `Bash` call (MANDATORY when `FORGEDOCK_AVAILABLE=true`) — never shell `&`/`wait`.** Issue one `Bash(...)` call per issue in `DISPATCH_NOW`, all in the same message, so they run concurrently within the headroom already computed above:
|
|
@@ -1132,10 +1176,6 @@ done
|
|
|
1132
1176
|
|
|
1133
1177
|
**2b. Engine-first-dispatched issue (`ENGINE_DISPATCH_MAP[{NUMBER}]` set, no `AGENT_ISSUE_MAP` entry)** — `Agent(resume=...)` has nothing to resume here; the completed task was a backgrounded `Bash(command="forgedock run-issue ...")` call, not an `Agent()`. Re-issuing the identical `forgedock run-issue` command would just reproduce the same environmental failure. Instead, fall back to the Agent-spawn path for this ONE issue — but only when it is safe to do so:
|
|
1134
1178
|
```bash
|
|
1135
|
-
# Idempotency guard: only fall back once per issue per engine-error occurrence.
|
|
1136
|
-
ALREADY_FALLEN_BACK=$(gh api repos/{GH_REPO}/issues/{NUMBER}/comments \
|
|
1137
|
-
--jq '[.[] | select(.body | contains("FORGE:ENGINE_FALLBACK"))] | length' 2>/dev/null || echo "0")
|
|
1138
|
-
|
|
1139
1179
|
# Safety gate: only fall back when the engine run committed NOTHING — no branch, no PR, no
|
|
1140
1180
|
# committed phases. Primary source: the `FORGE:STATE` HTML-comment block the engine already
|
|
1141
1181
|
# mirrors onto the issue BODY on every phase transition (`bin/engine/state.mjs`/`projector.mjs`
|
|
@@ -1149,13 +1189,22 @@ done
|
|
|
1149
1189
|
# the real multi-line shape correctly:
|
|
1150
1190
|
STATE_BLOCK=$(gh issue view {NUMBER} {GH_FLAG} --json body --jq '.body' \
|
|
1151
1191
|
| sed -n '/<!-- FORGE:STATE/,/-->/p')
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1192
|
+
STATE_JSON=$(echo "$STATE_BLOCK" | sed '1d;$d')
|
|
1193
|
+
if [ -n "$STATE_JSON" ]; then
|
|
1194
|
+
# Missing or schema-invalid fields are unverifiable, not empty. In particular, jq's
|
|
1195
|
+
# `length` is also zero for null, {}, and "", so require the committed array type first.
|
|
1196
|
+
# The persisted engine run ID and state version are also required: together they give every
|
|
1197
|
+
# orchestrator handling this same failure one stable scope while allowing a later failure
|
|
1198
|
+
# for the same issue/run name to elect a new claimant.
|
|
1199
|
+
if echo "$STATE_JSON" | jq -e \
|
|
1200
|
+
'(.run | type == "string" and test("^[A-Za-z0-9._:/-]+$")) and (.v | type == "number" and . >= 0 and floor == .) and (.committed | type == "array") and (.committed | length == 0) and (.branch == null) and (.pr == null)' \
|
|
1201
|
+
>/dev/null 2>&1; then
|
|
1202
|
+
STATE_RUN=$(echo "$STATE_JSON" | jq -r '.run')
|
|
1203
|
+
STATE_VERSION=$(echo "$STATE_JSON" | jq -r '.v')
|
|
1204
|
+
EMPTY_COMMITTED_STATE="true"
|
|
1205
|
+
else
|
|
1206
|
+
EMPTY_COMMITTED_STATE="false"
|
|
1207
|
+
fi
|
|
1159
1208
|
else
|
|
1160
1209
|
# No FORGE:STATE block was ever written to the issue body (e.g. the run failed before
|
|
1161
1210
|
# phase 1 committed anything). There is no reliable structured signal available in this
|
|
@@ -1175,32 +1224,53 @@ done
|
|
|
1175
1224
|
# dispatch could collide with or duplicate — that case is NOT auto-fallen-back; it surfaces
|
|
1176
1225
|
# via the existing stall-detection alert / needs-human path instead, exactly as before this fix.
|
|
1177
1226
|
|
|
1178
|
-
if [ "$
|
|
1227
|
+
if [ "$EMPTY_COMMITTED_STATE" = "true" ]; then
|
|
1228
|
+
# Atomic-enough cross-session claim: post first, then elect the lowest server-assigned
|
|
1229
|
+
# comment ID for this batch. Concurrent claimants may both post, but exactly one can win.
|
|
1230
|
+
# Pagination is mandatory; any POST/list/parse failure fails closed and dispatches nothing.
|
|
1231
|
+
CLAIM_SCOPE="${STATE_RUN}:${STATE_VERSION}"
|
|
1232
|
+
CLAIM_TOKEN="${CLAIM_SCOPE}-{NUMBER}-$$-${RANDOM}"
|
|
1179
1233
|
ENGINE_FALLBACK_BODY="<!-- FORGE:ENGINE_FALLBACK -->
|
|
1180
1234
|
## Engine-First Dispatch Failed — Falling Back to Agent-Spawn
|
|
1181
1235
|
|
|
1236
|
+
**Batch**: ${CLAIM_SCOPE}
|
|
1237
|
+
**Claim**: ${CLAIM_TOKEN}
|
|
1238
|
+
|
|
1182
1239
|
\`forgedock run-issue\` ended at \`workflow:engine-error\` with no committed phases, branch, or PR
|
|
1183
1240
|
(\`committed=[] branch=null pr=null\`) — this is an environmental/tool failure (forge#2261), not a
|
|
1184
1241
|
per-issue content failure, and nothing was committed that a fresh dispatch could collide with.
|
|
1185
1242
|
Auto-falling back to the Agent-spawn \`/work-on\` path for this issue. See forge#2743."
|
|
1186
|
-
gh
|
|
1243
|
+
CLAIM_ID=$(gh api "repos/{GH_REPO}/issues/{NUMBER}/comments" --method POST \
|
|
1244
|
+
--raw-field body="$ENGINE_FALLBACK_BODY" --jq '.id' 2>/dev/null) || CLAIM_ID=""
|
|
1245
|
+
CLAIM_LIST_OK="true"
|
|
1246
|
+
CLAIM_IDS=$(gh api "repos/{GH_REPO}/issues/{NUMBER}/comments" --paginate \
|
|
1247
|
+
--jq ".[] | select(.body | split(\"\\n\") | (any(. == \"<!-- FORGE:ENGINE_FALLBACK -->\") and any(. == \" **Batch**: ${CLAIM_SCOPE}\"))) | .id" \
|
|
1248
|
+
2>/dev/null) || CLAIM_LIST_OK="false"
|
|
1249
|
+
WINNER_CLAIM_ID=$(printf '%s\n' "$CLAIM_IDS" | grep -E '^[0-9]+$' | sort -n | head -1)
|
|
1250
|
+
CLAIM_WON="false"
|
|
1251
|
+
[ "$CLAIM_LIST_OK" = "true" ] && [ -n "$CLAIM_ID" ] && \
|
|
1252
|
+
[ "$CLAIM_ID" = "$WINNER_CLAIM_ID" ] && CLAIM_WON="true"
|
|
1187
1253
|
|
|
1188
1254
|
# Reuse the SAME Agent-spawn template as Step 4A's "Agent-spawn path (fallback when forgedock
|
|
1189
1255
|
# CLI unavailable)" section above (the `Agent(subagent_type="general-purpose", ...)` block
|
|
1190
1256
|
# under "Copy this template. Fill in variables. Do not modify the structure:") verbatim —
|
|
1191
1257
|
# HARD RULE 1 preserved. This is a fresh dispatch, not a resume: capture its returned agent
|
|
1192
1258
|
# ID into AGENT_ISSUE_MAP so subsequent completions for this issue route through 2a above.
|
|
1193
|
-
|
|
1259
|
+
if [ "$CLAIM_WON" = "true" ]; then
|
|
1260
|
+
Agent(
|
|
1194
1261
|
subagent_type="general-purpose",
|
|
1195
1262
|
model="{SUBAGENT_MODEL}",
|
|
1196
1263
|
description="Work on {PROJECT_PREFIX}#{NUMBER} (engine-error fallback)",
|
|
1197
1264
|
run_in_background=true,
|
|
1198
1265
|
prompt="<same template body as Step 4A's Agent-spawn path — see the 'Agent-spawn path (fallback when forgedock CLI unavailable)' section above; fill {NUMBER}/{GH_REPO}/{REPO_PATH}/{LANE}/{PR_BASE}/{ISSUE_TITLE} exactly as that template does>"
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1266
|
+
)
|
|
1267
|
+
AGENT_ISSUE_MAP[{NUMBER}] = <agent_id returned by the Agent() call above>
|
|
1268
|
+
# Re-occupies the worker slot this completion just freed (see "Concurrency slot release" above).
|
|
1269
|
+
ACTIVE_DISPATCH_COUNT=$((ACTIVE_DISPATCH_COUNT + 1))
|
|
1270
|
+
else
|
|
1271
|
+
echo "#{NUMBER}: engine fallback claim lost or could not be verified — no duplicate Agent dispatch."
|
|
1272
|
+
fi
|
|
1273
|
+
else
|
|
1204
1274
|
echo "#{NUMBER}: engine-error with non-empty committed state (partial work exists) — NOT auto-falling back. Surfaces via standard stall-detection alert; forgedock resume-stalled remains available for manual/scripted recovery."
|
|
1205
1275
|
fi
|
|
1206
1276
|
```
|
|
@@ -1501,17 +1571,9 @@ Gating predecessor #${NUM} reached \`workflow:merged\` — dispatching now. (Was
|
|
|
1501
1571
|
RERESOLVE_ENABLED=$(yq '.orchestration.reresolve.enabled // true' forge.yaml 2>/dev/null || echo "true")
|
|
1502
1572
|
RERESOLVE_MAX_ROUNDS=$(yq '.orchestration.reresolve.max_rounds // "unbounded"' forge.yaml 2>/dev/null || echo "unbounded")
|
|
1503
1573
|
|
|
1504
|
-
node -
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
const config = {
|
|
1508
|
-
enabled: process.argv[3] === "false" ? false : process.argv[3],
|
|
1509
|
-
maxRounds: process.argv[4] === "unbounded" ? undefined : Number(process.argv[4]),
|
|
1510
|
-
};
|
|
1511
|
-
const rounds = Number(process.argv[5]);
|
|
1512
|
-
console.log(JSON.stringify(shouldReResolve(classified, config, rounds)));
|
|
1513
|
-
}, () => process.exit(0));
|
|
1514
|
-
' "$ORIGINATING_QUERY_KIND" "$ORIGINATING_QUERY_PATTERN" "$RERESOLVE_ENABLED" "$RERESOLVE_MAX_ROUNDS" "$RERESOLVE_ROUNDS_SO_FAR"
|
|
1574
|
+
node "{REPO_PATH}/bin/engine/orchestrate-canary.mjs" \
|
|
1575
|
+
"$ORIGINATING_QUERY_KIND" "$ORIGINATING_QUERY_PATTERN" \
|
|
1576
|
+
"$RERESOLVE_ENABLED" "$RERESOLVE_MAX_ROUNDS" "$RERESOLVE_ROUNDS_SO_FAR"
|
|
1515
1577
|
```
|
|
1516
1578
|
|
|
1517
1579
|
If the result's `reResolve` is `false` (off switch, or `RERESOLVE_ROUNDS_SO_FAR` has reached `max_rounds` — bounded termination per the issue's acceptance criteria), skip this step for the current cycle and log the reason. `RERESOLVE_ROUNDS_SO_FAR` starts at 0 for the batch and increments once per cycle this step actually runs (declare it alongside the other Step 4A.pre batch-scope accumulators — do not re-initialize per completion).
|
|
@@ -2105,7 +2167,7 @@ Cascade-dispatched findings inherit NO lane assumption from the parent batch. St
|
|
|
2105
2167
|
|
|
2106
2168
|
```bash
|
|
2107
2169
|
for FINDING_NUM in "${QUEUED_FINDINGS[@]}"; do
|
|
2108
|
-
PR_BASE=$(bash
|
|
2170
|
+
PR_BASE=$(bash "$CLASSIFY_LANE_SCRIPT" "$FINDING_NUM" -R {GH_REPO}) || {
|
|
2109
2171
|
echo "ERROR: classify-lane.sh failed for #$FINDING_NUM — adding needs-human label and removing from QUEUED_FINDINGS" >&2
|
|
2110
2172
|
# GOVERNOR-exempt: intentional coordination side-effect (best-effort lease/board/finding post), DRY_RUN-safe — reviewed & accepted for the check-command-side-effects gate. Flagged only by the staging->main full-diff; passes on every feature PR. forge#2627
|
|
2111
2173
|
gh issue edit "$FINDING_NUM" -R {GH_REPO} --add-label "needs-human" 2>/dev/null || true
|
|
@@ -2455,7 +2517,7 @@ declare -A SWEEP_LANE
|
|
|
2455
2517
|
declare -A SWEEP_PR_BASE
|
|
2456
2518
|
|
|
2457
2519
|
for FINDING_NUM in "${SWEEP_EXECUTE[@]}"; do
|
|
2458
|
-
SWEEP_BASE=$(bash
|
|
2520
|
+
SWEEP_BASE=$(bash "$CLASSIFY_LANE_SCRIPT" "$FINDING_NUM" -R {GH_REPO}) || {
|
|
2459
2521
|
echo "ERROR: classify-lane.sh failed for #$FINDING_NUM — adding needs-human and skipping" >&2
|
|
2460
2522
|
# GOVERNOR-exempt: intentional coordination side-effect (best-effort lease/board/finding post), DRY_RUN-safe — reviewed & accepted for the check-command-side-effects gate. Flagged only by the staging->main full-diff; passes on every feature PR. forge#2627
|
|
2461
2523
|
gh issue edit "$FINDING_NUM" -R {GH_REPO} --add-label "needs-human" 2>/dev/null || true
|
|
@@ -2636,4 +2698,3 @@ fi
|
|
|
2636
2698
|
```
|
|
2637
2699
|
|
|
2638
2700
|
---
|
|
2639
|
-
|
|
@@ -125,7 +125,16 @@ Append `-{NUMBER}` to ensure uniqueness: e.g. `fix/work-on-build-landing-file-85
|
|
|
125
125
|
### B1C: Create worktree
|
|
126
126
|
|
|
127
127
|
```bash
|
|
128
|
-
|
|
128
|
+
WORKTREE_ROOT="/path/to/repo/.claude/worktrees"
|
|
129
|
+
if [ "${FORGE_RUNTIME:-}" = "opencode" ] ||
|
|
130
|
+
[ -n "${OPENCODE_SESSION_ID:-}" ] ||
|
|
131
|
+
[ -n "${OPENCODE_PID:-}" ] ||
|
|
132
|
+
[ -n "${OPENCODE:-}" ]; then
|
|
133
|
+
WORKTREE_ROOT="/path/to/repo/.opencode/worktrees"
|
|
134
|
+
elif [ "${FORGE_RUNTIME:-}" = "codex" ]; then
|
|
135
|
+
WORKTREE_ROOT="/path/to/repo/.codex/worktrees"
|
|
136
|
+
fi
|
|
137
|
+
WORKTREE_PATH="${WORKTREE_ROOT}/{BRANCH_SLUG}"
|
|
129
138
|
git worktree add {WORKTREE_PATH} -b {BRANCH} origin/{SOURCE_BRANCH}
|
|
130
139
|
```
|
|
131
140
|
|
package/commands/work-on.md
CHANGED
|
@@ -1411,7 +1411,16 @@ esac
|
|
|
1411
1411
|
cd {REPO_PATH}
|
|
1412
1412
|
git fetch origin
|
|
1413
1413
|
BRANCH="fix/{slug}-{NUMBER}"
|
|
1414
|
-
|
|
1414
|
+
WORKTREE_ROOT="{REPO_PATH}/.claude/worktrees"
|
|
1415
|
+
if [ "${FORGE_RUNTIME:-}" = "opencode" ] ||
|
|
1416
|
+
[ -n "${OPENCODE_SESSION_ID:-}" ] ||
|
|
1417
|
+
[ -n "${OPENCODE_PID:-}" ] ||
|
|
1418
|
+
[ -n "${OPENCODE:-}" ]; then
|
|
1419
|
+
WORKTREE_ROOT="{REPO_PATH}/.opencode/worktrees"
|
|
1420
|
+
elif [ "${FORGE_RUNTIME:-}" = "codex" ]; then
|
|
1421
|
+
WORKTREE_ROOT="{REPO_PATH}/.codex/worktrees"
|
|
1422
|
+
fi
|
|
1423
|
+
WORKTREE_PATH="${WORKTREE_ROOT}/{BRANCH_SLUG}"
|
|
1415
1424
|
git worktree add {WORKTREE_PATH} -b {BRANCH} origin/{PR_BASE}
|
|
1416
1425
|
```
|
|
1417
1426
|
|