forgedock 1.7.0 → 1.7.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/README.md +2 -1
- package/bin/engine-cli.mjs +2 -0
- package/bin/forgedock.mjs +109 -46
- package/bin/opencode-adapter.mjs +618 -92
- package/bin/runner.mjs +47 -5
- package/commands/orchestrate/phase-3-dependency.md +26 -9
- package/commands/orchestrate/phase-4-execution.md +42 -3
- package/commands/review-pr-agents.md +8 -0
- package/commands/review-pr-staging.md +18 -0
- package/commands/review-pr.md +20 -2
- package/docs/CONFIG.md +6 -5
- package/docs/OPENCODE.md +113 -25
- package/package.json +1 -1
package/bin/runner.mjs
CHANGED
|
@@ -127,6 +127,35 @@ function readOnlySet(values) {
|
|
|
127
127
|
* simultaneously. Treat it as immutable; mutation attempts throw.
|
|
128
128
|
*/
|
|
129
129
|
export const VALID_BACKENDS = readOnlySet(["cli", "api", "auto"]);
|
|
130
|
+
|
|
131
|
+
export const FORGE_OPENCODE_CAPABILITY_ERROR = "FORGE_OPENCODE_CAPABILITY_ERROR";
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Whether the caller is an OpenCode session. The adapter sets this marker on
|
|
135
|
+
* every shell environment; keeping the predicate small makes all Claude
|
|
136
|
+
* backend entrypoints agree on the same runtime boundary.
|
|
137
|
+
*/
|
|
138
|
+
export function isOpenCodeRuntime(env = process.env) {
|
|
139
|
+
return String(env?.FORGE_RUNTIME || "").toLowerCase() === "opencode";
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Refuse Claude-backed execution from an OpenCode workflow. OpenCode has its
|
|
144
|
+
* own Skill/Task execution path; falling through to this runner would create a
|
|
145
|
+
* competing controller and may consume Claude credentials or quota.
|
|
146
|
+
*/
|
|
147
|
+
export function assertOpenCodeNativeRuntime({
|
|
148
|
+
env = process.env,
|
|
149
|
+
operation = "Claude-backed ForgeDock execution",
|
|
150
|
+
} = {}) {
|
|
151
|
+
if (!isOpenCodeRuntime(env)) return;
|
|
152
|
+
const error = new Error(
|
|
153
|
+
`${FORGE_OPENCODE_CAPABILITY_ERROR}: ${operation} is unavailable when FORGE_RUNTIME=opencode. Use native ForgeDock Skill/Task dispatch instead.`,
|
|
154
|
+
);
|
|
155
|
+
error.code = FORGE_OPENCODE_CAPABILITY_ERROR;
|
|
156
|
+
throw error;
|
|
157
|
+
}
|
|
158
|
+
|
|
130
159
|
// Per-process memoization for isClaudeCliAvailable(), keyed by `cwd` (issue
|
|
131
160
|
// #2011). `runCommand()` calls resolveBackend() — and therefore, for the
|
|
132
161
|
// default "auto" backend, isClaudeCliAvailable() — unconditionally on every
|
|
@@ -491,9 +520,10 @@ export function runCliWithStalePathRecovery({
|
|
|
491
520
|
* API checks verify that a key is configured, not that the key is accepted.
|
|
492
521
|
*/
|
|
493
522
|
export function checkExecutionBackend({
|
|
494
|
-
|
|
523
|
+
env = process.env,
|
|
524
|
+
requested = env.FORGEDOCK_BACKEND || "auto",
|
|
495
525
|
cwd = process.cwd(),
|
|
496
|
-
apiKey =
|
|
526
|
+
apiKey = env.ANTHROPIC_API_KEY,
|
|
497
527
|
resolveCliFn = resolveClaudeCliBinary,
|
|
498
528
|
spawnImpl = spawnSync,
|
|
499
529
|
sdkAvailableFn = () => {
|
|
@@ -509,6 +539,10 @@ export function checkExecutionBackend({
|
|
|
509
539
|
return { ready: false, backend: requested, reason: "invalid-backend" };
|
|
510
540
|
}
|
|
511
541
|
|
|
542
|
+
if (isOpenCodeRuntime(env)) {
|
|
543
|
+
return { ready: false, backend: "opencode", reason: "native-opencode-required" };
|
|
544
|
+
}
|
|
545
|
+
|
|
512
546
|
if (requested !== "api") {
|
|
513
547
|
const cliPath = resolveCliFn(cwd);
|
|
514
548
|
if (cliPath) {
|
|
@@ -606,14 +640,16 @@ export function resolveBackendLadder({
|
|
|
606
640
|
* @param {object} [opts]
|
|
607
641
|
* @param {string} [opts.requested] - "cli" | "api" | "auto" (default "auto").
|
|
608
642
|
* @param {string} [opts.cwd] - Working directory for the CLI probe.
|
|
643
|
+
* @param {object} [opts.env] - Runtime environment used for capability checks.
|
|
609
644
|
* @returns {"cli"|"api"}
|
|
610
645
|
*/
|
|
611
|
-
export function resolveBackend({ requested = "auto", cwd = process.cwd() } = {}) {
|
|
646
|
+
export function resolveBackend({ requested = "auto", cwd = process.cwd(), env = process.env } = {}) {
|
|
612
647
|
if (!VALID_BACKENDS.has(requested)) {
|
|
613
648
|
throw new Error(
|
|
614
649
|
`Invalid backend "${requested}". Must be one of: ${[...VALID_BACKENDS].join(", ")}.`,
|
|
615
650
|
);
|
|
616
651
|
}
|
|
652
|
+
assertOpenCodeNativeRuntime({ env, operation: "Claude-backed backend resolution" });
|
|
617
653
|
return resolveBackendLadder({
|
|
618
654
|
override: requested === "auto" ? undefined : requested,
|
|
619
655
|
validOverrides: new Set(["cli", "api"]),
|
|
@@ -2086,9 +2122,15 @@ export async function runCommand(opts = {}) {
|
|
|
2086
2122
|
maxIterations = DEFAULT_MAX_ITERATIONS,
|
|
2087
2123
|
dryRun = false,
|
|
2088
2124
|
backend = process.env.FORGEDOCK_BACKEND || "auto",
|
|
2125
|
+
runtimeEnv = process.env,
|
|
2089
2126
|
logger = console,
|
|
2090
2127
|
} = opts;
|
|
2091
2128
|
|
|
2129
|
+
assertOpenCodeNativeRuntime({
|
|
2130
|
+
env: runtimeEnv,
|
|
2131
|
+
operation: "Claude-backed ForgeDock runner",
|
|
2132
|
+
});
|
|
2133
|
+
|
|
2092
2134
|
const spec = loadCommandSpec(commandsDir, commandName);
|
|
2093
2135
|
const systemPrompt = buildSystemPrompt(spec, { repoRoot: cwd });
|
|
2094
2136
|
// CLI-backend-specific prompt (issue #2019) — see buildCliSystemPrompt's
|
|
@@ -2104,10 +2146,10 @@ export async function runCommand(opts = {}) {
|
|
|
2104
2146
|
// misbehaving. An explicitly invalid `backend` value DOES throw here —
|
|
2105
2147
|
// surfacing a bad --backend/FORGEDOCK_BACKEND value immediately, before any
|
|
2106
2148
|
// work is attempted, rather than silently ignoring it.
|
|
2107
|
-
let resolvedBackend = resolveBackend({ requested: backend, cwd });
|
|
2149
|
+
let resolvedBackend = resolveBackend({ requested: backend, cwd, env: runtimeEnv });
|
|
2108
2150
|
|
|
2109
2151
|
if (resolvedBackend === "cli" && backend === "auto") {
|
|
2110
|
-
const readiness = checkExecutionBackend({ requested: "auto", cwd, apiKey });
|
|
2152
|
+
const readiness = checkExecutionBackend({ requested: "auto", cwd, apiKey, env: runtimeEnv });
|
|
2111
2153
|
if (readiness.backend === "api") resolvedBackend = "api";
|
|
2112
2154
|
}
|
|
2113
2155
|
|
|
@@ -1145,8 +1145,15 @@ if [ -n "${OPENCODE_SESSION_ID:-}" ] || [ -n "${OPENCODE_PID:-}" ] || [ -n "${OP
|
|
|
1145
1145
|
fi
|
|
1146
1146
|
|
|
1147
1147
|
if [ "$FORGE_RUNTIME" = "opencode" ]; then
|
|
1148
|
-
echo "OpenCode runtime detected: native task dispatch selected
|
|
1149
|
-
|
|
1148
|
+
echo "OpenCode runtime detected: native task dispatch selected."
|
|
1149
|
+
# ForgeDock's OpenCode plugin opts into background task events by default so
|
|
1150
|
+
# each completed DAG node can wake this parent without a wave barrier.
|
|
1151
|
+
if [ "${OPENCODE_EXPERIMENTAL_BACKGROUND_SUBAGENTS:-true}" = "false" ]; then
|
|
1152
|
+
echo "OpenCode background subagents explicitly disabled: orchestration is degraded to foreground task dispatch."
|
|
1153
|
+
BACKGROUND_DISPATCH_ENABLED=false
|
|
1154
|
+
else
|
|
1155
|
+
BACKGROUND_DISPATCH_ENABLED=true
|
|
1156
|
+
fi
|
|
1150
1157
|
fi
|
|
1151
1158
|
|
|
1152
1159
|
# Feature gate check — run once before Phase 4 dispatch begins
|
|
@@ -1176,13 +1183,23 @@ fi
|
|
|
1176
1183
|
|
|
1177
1184
|
When `FORGE_RUNTIME=opencode` (or an OpenCode runtime marker is present), do not
|
|
1178
1185
|
interpret a missing `claude` executable as an environment failure. The Phase 4
|
|
1179
|
-
dispatcher must use OpenCode's
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
**When `BACKGROUND_DISPATCH_ENABLED=
|
|
1186
|
+
dispatcher must use OpenCode's native `task` contract instead of the Claude
|
|
1187
|
+
`Agent(...)` fallback. With background subagents enabled, each task is launched
|
|
1188
|
+
with `background=true` and its injected task-result event immediately drives
|
|
1189
|
+
predecessor classification and newly-ready dispatch. Claude remains the default
|
|
1190
|
+
when no runtime marker is present, preserving the existing Claude behavior.
|
|
1191
|
+
|
|
1192
|
+
**When `BACKGROUND_DISPATCH_ENABLED=false`**: Claude falls back to its existing
|
|
1193
|
+
synchronous/polling behavior. OpenCode reaches this branch only after an
|
|
1194
|
+
explicit `OPENCODE_EXPERIMENTAL_BACKGROUND_SUBAGENTS=false`; use independent
|
|
1195
|
+
foreground `task` calls where the host can execute them concurrently, and
|
|
1196
|
+
report that per-completion streaming is unavailable. Do not use Claude's
|
|
1197
|
+
`Agent(...)` fallback and do not claim the event-driven DAG guarantee.
|
|
1198
|
+
|
|
1199
|
+
**When `BACKGROUND_DISPATCH_ENABLED=true`**: Claude uses
|
|
1200
|
+
`run_in_background=true` and reacts to `agent_completed` notifications. OpenCode
|
|
1201
|
+
uses `task(..., background=true)` and reacts to each injected task-result event.
|
|
1202
|
+
Do NOT poll or wait for a whole wave in either enabled path.
|
|
1186
1203
|
|
|
1187
1204
|
### Orchestrator state reconstruction on wake / after compaction
|
|
1188
1205
|
|
|
@@ -416,6 +416,9 @@ declare -A AGENT_ISSUE_MAP
|
|
|
416
416
|
# this map to identify which issue a background-Bash completion notification belongs to,
|
|
417
417
|
# the same role AGENT_ISSUE_MAP plays for Agent-tool `agent_completed` notifications.
|
|
418
418
|
declare -A ENGINE_DISPATCH_MAP
|
|
419
|
+
# OpenCode native task equivalent of ENGINE_DISPATCH_MAP. Keys are issue numbers;
|
|
420
|
+
# values are the session ids returned by task(background=true).
|
|
421
|
+
declare -A OPENCODE_DISPATCH_MAP
|
|
419
422
|
|
|
420
423
|
# Same-file current-state brief forwarding (forge#1860). Populated by the core streaming
|
|
421
424
|
# dispatch loop below (Step 4B) whenever a Layer 1/2/3 structural predecessor edge (see
|
|
@@ -556,10 +559,33 @@ and continue until it reaches
|
|
|
556
559
|
`workflow:merged`, `workflow:invalid`, `needs-human`, or
|
|
557
560
|
`workflow:awaiting-merge`.
|
|
558
561
|
|
|
562
|
+
The native call is asynchronous and must be issued once per issue in
|
|
563
|
+
`DISPATCH_NOW`, in the same assistant message where the batch is dispatched:
|
|
564
|
+
|
|
565
|
+
```
|
|
566
|
+
task(
|
|
567
|
+
description="Work on {PROJECT_PREFIX}#{NUMBER}",
|
|
568
|
+
subagent_type="general",
|
|
569
|
+
background=true,
|
|
570
|
+
prompt="Use the same Phase 4A work-on template below. Invoke Skill(skill='work-on', args='{PROJECT_PREFIX}{NUMBER} --under-orchestration') and continue until a terminal workflow state."
|
|
571
|
+
)
|
|
572
|
+
```
|
|
573
|
+
|
|
574
|
+
Capture the returned `<task id="..." state="running">` id in
|
|
575
|
+
`OPENCODE_DISPATCH_MAP[{NUMBER}]`. `background=true` is required for streaming
|
|
576
|
+
DAG behavior; a foreground task makes the orchestrator wait for that issue and
|
|
577
|
+
reintroduces a wave barrier. The ForgeDock OpenCode plugin opts into
|
|
578
|
+
`OPENCODE_EXPERIMENTAL_BACKGROUND_SUBAGENTS=true` by default. An explicit
|
|
579
|
+
`OPENCODE_EXPERIMENTAL_BACKGROUND_SUBAGENTS=false` is a supported degraded mode,
|
|
580
|
+
but it must be reported as non-streaming rather than treated as equivalent to
|
|
581
|
+
Claude background dispatch. Do not wait for the slowest sibling before handling
|
|
582
|
+
an earlier task-result event.
|
|
583
|
+
|
|
559
584
|
```bash
|
|
560
585
|
if [ "${FORGE_RUNTIME:-}" = "opencode" ]; then
|
|
561
586
|
echo "OpenCode native task dispatch selected — load commands/work-on.md for each ready issue."
|
|
562
587
|
echo "Do not call forgedock run-issue or the Claude Agent(...) fallback in this branch."
|
|
588
|
+
echo "Use task(subagent_type=general, background=true) once per ready issue and process each task-result event immediately."
|
|
563
589
|
echo "If native task dispatch cannot run, post FORGE:OPENCODE_BLOCKED with the missing capability and add needs-human."
|
|
564
590
|
fi
|
|
565
591
|
```
|
|
@@ -567,6 +593,13 @@ fi
|
|
|
567
593
|
This branch is additive. When OpenCode is not selected, continue through the
|
|
568
594
|
existing engine-first and Claude Agent-spawn paths below without modification.
|
|
569
595
|
|
|
596
|
+
The generated OpenCode plugin is an executable boundary, not just prompt
|
|
597
|
+
guidance: its `tool.execute.before` hook rejects `claude`, `forgedock
|
|
598
|
+
run-issue`, `npx forgedock run-issue`, and recursive `opencode run` shell
|
|
599
|
+
commands with `FORGE_OPENCODE_CAPABILITY_ERROR`. If a native Skill or Task is
|
|
600
|
+
unavailable, stop with that bounded error and preserve the GitHub label state;
|
|
601
|
+
never compensate by starting a second controller or the Claude-backed engine.
|
|
602
|
+
|
|
570
603
|
**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.
|
|
571
604
|
|
|
572
605
|
```bash
|
|
@@ -687,7 +720,13 @@ ENGINE_DISPATCH_MAP[{NUM}] = <task_id returned by Bash(run_in_background=true, .
|
|
|
687
720
|
|
|
688
721
|
If `DISPATCH_NOW` is empty (headroom is 0), do not dispatch any issues this cycle — wait for the next completion notification, exactly like the Agent-spawn path.
|
|
689
722
|
|
|
690
|
-
**Agent-spawn path (fallback when forgedock CLI unavailable)**: When `FORGEDOCK_AVAILABLE=false
|
|
723
|
+
**Agent-spawn path (Claude fallback when forgedock CLI unavailable)**: When `FORGEDOCK_AVAILABLE=false` and the runtime is not OpenCode, spawn Agent sub-agents per issue using the template below. This preserves engine state via the SubagentStop hook even without the CLI.
|
|
724
|
+
|
|
725
|
+
When the runtime is OpenCode, skip this Claude-only `Agent(...)` path entirely.
|
|
726
|
+
Use the native `task` call above for initial, newly-ready, remediation, and
|
|
727
|
+
stall-resume dispatches. Translate every later `Agent(...)` reference in this
|
|
728
|
+
phase to `task(...)` with `subagent_type="general"`; preserve `background=true`
|
|
729
|
+
and use `task_id=OPENCODE_DISPATCH_MAP[{NUMBER}]` when resuming a child.
|
|
691
730
|
|
|
692
731
|
**REMINDER: You MUST use the template below verbatim when on the Agent-spawn fallback path. Only fill in `{VARIABLES}`. Do NOT rewrite the agent prompt. Do NOT write custom implementation instructions. The agent MUST invoke `/work-on` via the Skill tool — this is the HARD RULE from the top of this file.**
|
|
693
732
|
|
|
@@ -917,9 +956,9 @@ If `DISPATCH_NOW` is empty (headroom is 0), do not spawn any agents this cycle
|
|
|
917
956
|
|
|
918
957
|
### Step 4B: Monitor completions and dispatch newly ready issues
|
|
919
958
|
|
|
920
|
-
You will be automatically notified when each background agent completes — or, for an engine-first dispatch (Step 4A, `FORGEDOCK_AVAILABLE=true`), when a backgrounded `Bash(run_in_background=true, command="forgedock run-issue ...")` call completes.
|
|
959
|
+
You will be automatically notified when each background agent completes — or, for an engine-first dispatch (Step 4A, `FORGEDOCK_AVAILABLE=true`), when a backgrounded `Bash(run_in_background=true, command="forgedock run-issue ...")` call completes. In OpenCode, a native background `task` first returns a `state="running"` result and later injects a synthetic `<task id="..." state="completed">` or `state="error"` result into this same parent session. The later result is the completion event; the initial running result is not completion. Both Claude notifications and OpenCode task-result events must trigger the same per-issue handling immediately. **Do NOT use `sleep` loops, wait for the slowest sibling, or poll before processing an event.** When one arrives, look up which issue it belongs to — `AGENT_ISSUE_MAP` for a Claude `agent_completed` notification, `ENGINE_DISPATCH_MAP` for a background-Bash notification, or `OPENCODE_DISPATCH_MAP` for an OpenCode task result — and immediately process it exactly the same way regardless of which map resolved it; every check below (`classify_predecessor_state()`, dependent dispatch, stall/staging checks) keys off GitHub labels/state, not which dispatch mechanism produced them. **Fallback**: if a runtime ever fails to deliver a background completion notification, use the documented label-state recovery path, but do not convert the normal OpenCode path to foreground tasks or a wave barrier.
|
|
921
960
|
|
|
922
|
-
**Concurrency slot release (MANDATORY — first action on every completion)** <!-- Added: forge#1912 -->: The instant a completion notification arrives — `agent_completed`,
|
|
961
|
+
**Concurrency slot release (MANDATORY — first action on every completion)** <!-- Added: forge#1912 -->: The instant a completion notification arrives — `agent_completed`, a background-Bash completion, or an OpenCode `task` result with `state="completed"`/`state="error"` — decrement `ACTIVE_DISPATCH_COUNT` by 1 — a worker slot has just freed, regardless of what terminal state the issue ended up in. Do this before any stall-recovery/resume logic below. If that agent is then resumed or fallback-dispatched (item 2 below, or Step 4B.5's stall recovery) because it stalled mid-pipeline rather than truly finishing, re-increment `ACTIVE_DISPATCH_COUNT` when the `Agent(resume=...)` or OpenCode `task(task_id=..., background=true)` call is issued — either re-occupies a worker slot exactly like a fresh dispatch does. (Step 4B.5's TIME-BASED hang detector — an agent that never completes at all, as opposed to one that completes at `workflow:engine-error` — remains resume-specific and out of scope for this fix: an engine-dispatched issue that silently hangs still surfaces only via the standard stall-detection alert, and `forgedock resume-stalled` remains available as a separate manual/scripted recovery path for that case. The completion-triggered case — an engine-dispatched issue that DOES complete at `workflow:engine-error` with empty committed state — is still auto-fallen-back by item 2b below, fixed forge#2743.)
|
|
923
962
|
|
|
924
963
|
**Ordering requirement (MANDATORY — prevents transient over-cap)**: For every agent that completed in this notification batch, finish its terminal-state check and, if applicable, its resume re-increment (items 1-2 below) BEFORE computing `dispatch_headroom` for item 5's newly-ready-issue dispatch. Computing headroom before a to-be-resumed agent's re-increment would let a genuinely-still-running agent's freed slot be double-booked — once by a fresh dispatch, once by the resume itself — transiently exceeding `MAX_CONCURRENT`. Process every completed agent's items 1-2 to a decision (terminal vs. resumed) first; only then compute headroom once for the batch's item 5 dispatch.
|
|
925
964
|
|
|
@@ -19,6 +19,14 @@ the `prompt` argument to whichever tool the orchestrator resolved (`Task(...)` o
|
|
|
19
19
|
same per-agent isolation and the same requirement that each agent posts structured findings to the PR
|
|
20
20
|
directly (`gh pr comment`) rather than relaying them through the orchestrator's own context.
|
|
21
21
|
|
|
22
|
+
**OpenCode override**: When `FORGE_RUNTIME=opencode` or an equivalent OpenCode marker is present, the
|
|
23
|
+
orchestrator must set `DISPATCH_TOOL=task` before checking literal Claude tool names. A native task
|
|
24
|
+
argument is shaped as `{ description: "...", prompt: "...", subagent_type: "general"|"explore", background: true }`:
|
|
25
|
+
use `general` for implementation/review and `explore` for read-only discovery. The `Neither tool is
|
|
26
|
+
available` hard stop applies only when lowercase native `task` is genuinely absent; it must not be
|
|
27
|
+
triggered by the absence of `Task` and `Agent` names in an OpenCode session. If native `task` is absent,
|
|
28
|
+
post `FORGE:REVIEW_BLOCKED` and never replace the isolated review with inline work.
|
|
29
|
+
|
|
22
30
|
<!-- FORGE:PROTOCOL_SOURCE — canonical definition lives in docs/spec/review-protocol.md -->
|
|
23
31
|
|
|
24
32
|
---
|
|
@@ -23,6 +23,24 @@ Performs comprehensive review of `staging` before merging to `main`. Handles lar
|
|
|
23
23
|
|
|
24
24
|
This spec dispatches Bug Hunter and domain review agents via a sub-agent-spawning tool. Different runtimes expose different tools for this — resolve deterministically, once per invocation, and use the same tool for every dispatch call in this run:
|
|
25
25
|
|
|
26
|
+
### OpenCode Runtime Override
|
|
27
|
+
|
|
28
|
+
When the workflow is running under OpenCode, resolve the native tool before applying the Claude-specific availability order above:
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
IS_OPENCODE_RUNTIME=false
|
|
32
|
+
if [ "${FORGE_RUNTIME:-}" = "opencode" ] ||
|
|
33
|
+
[ -n "${OPENCODE_SESSION_ID:-}" ] ||
|
|
34
|
+
[ -n "${OPENCODE_PID:-}" ] ||
|
|
35
|
+
[ -n "${OPENCODE:-}" ]; then
|
|
36
|
+
IS_OPENCODE_RUNTIME=true
|
|
37
|
+
DISPATCH_TOOL=task
|
|
38
|
+
DISPATCH_SUBAGENT_TYPE=general
|
|
39
|
+
fi
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
When `IS_OPENCODE_RUNTIME=true`, lowercase native `task` is the preferred isolated dispatch tool. Do not enter the `Neither tool is available` branch merely because Claude's literal `Task` and `Agent` names are absent. Every native task call must use a top-level `subagent_type`: `general` for implementation/review work and `explore` for read-only discovery (Claude `general-purpose` and `codebase-explorer` map to those values). Use `background: true` for independent reviewers so the parent can process each completion event without a wave barrier. If the native `task` capability itself is absent from the current tool registry, use the existing hard-stop path and post `FORGE:REVIEW_BLOCKED`; never substitute inline review.
|
|
43
|
+
|
|
26
44
|
1. **If `Task` is available in the current environment**: set `{DISPATCH_TOOL} = Task`. This is the preferred tool — tightest `allowed-tools` scoping. (Identical resolution logic to `/review-pr` Phase 3C — do not diverge.)
|
|
27
45
|
2. **Else if `Agent` is available**: set `{DISPATCH_TOOL} = Agent`. This is the documented fallback, not a degraded path — use it exactly as you would `Task`: one call per selected agent (Bug Hunters in Phase 3, Code Quality in Phase 4, domain agents in Phase 5), same prompt template, `subagent_type: "general-purpose"` (or the closest equivalent the environment offers), same requirement that each agent posts its own findings directly to the PR via `gh pr comment`. Isolation and fresh-context review are preserved either way.
|
|
28
46
|
3. **Neither tool is available**: this is a genuine setup defect, not a routing decision — HARD STOP, post a PR/issue comment explaining that no sub-agent dispatch tool is available, add `needs-human`, and exit without posting a verdict. Do NOT fall back to reviewing inline in the orchestrator's own context.
|
package/commands/review-pr.md
CHANGED
|
@@ -33,6 +33,24 @@ allowed-tools: Task, Agent, Bash, Read, Grep, Glob, WebFetch, Skill
|
|
|
33
33
|
|
|
34
34
|
This spec dispatches domain review agents via a sub-agent-spawning tool. Different runtimes expose different tools for this — resolve deterministically, once per invocation, and use the same tool for every dispatch call in this run:
|
|
35
35
|
|
|
36
|
+
### OpenCode Runtime Override
|
|
37
|
+
|
|
38
|
+
When the workflow is running under OpenCode, resolve the native tool before applying the Claude-specific availability order above:
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
IS_OPENCODE_RUNTIME=false
|
|
42
|
+
if [ "${FORGE_RUNTIME:-}" = "opencode" ] ||
|
|
43
|
+
[ -n "${OPENCODE_SESSION_ID:-}" ] ||
|
|
44
|
+
[ -n "${OPENCODE_PID:-}" ] ||
|
|
45
|
+
[ -n "${OPENCODE:-}" ]; then
|
|
46
|
+
IS_OPENCODE_RUNTIME=true
|
|
47
|
+
DISPATCH_TOOL=task
|
|
48
|
+
DISPATCH_SUBAGENT_TYPE=general
|
|
49
|
+
fi
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
When `IS_OPENCODE_RUNTIME=true`, lowercase native `task` is the preferred isolated dispatch tool. Do not enter the `Neither tool is available` branch merely because Claude's literal `Task` and `Agent` names are absent. Every native task call must use a top-level `subagent_type`: `general` for implementation/review work and `explore` for read-only discovery (Claude `general-purpose` and `codebase-explorer` map to those values). Use `background: true` for independent reviewers so the parent can process each completion event without a wave barrier. If the native `task` capability itself is absent from the current tool registry, use the existing hard-stop path and post `FORGE:REVIEW_BLOCKED`; never substitute inline review.
|
|
53
|
+
|
|
36
54
|
1. **If `Task` is available in the current environment**: set `{DISPATCH_TOOL} = Task`. This is the preferred tool — tightest `allowed-tools` scoping.
|
|
37
55
|
2. **Else if `Agent` is available**: set `{DISPATCH_TOOL} = Agent`. This is the documented fallback, not a degraded path — use it exactly as you would `Task`: one call per selected domain agent, same prompt template, `subagent_type: "general-purpose"` (or the closest equivalent the environment offers), same requirement that each agent posts its own findings directly to the PR via `gh pr comment`. Isolation and fresh-context review are preserved either way.
|
|
38
56
|
3. **Neither tool is available**: this is a genuine setup defect, not a routing decision — HARD STOP, post a PR/issue comment explaining that no sub-agent dispatch tool is available, add `needs-human`, and exit without posting a verdict. Do NOT fall back to reviewing inline in the orchestrator's own context — inline self-review is strictly weaker than an isolated fresh-context reviewer and is never a substitute for a missing dispatch tool.
|
|
@@ -1520,7 +1538,7 @@ The `protocols.md` file contains the Evidence-Based Review Protocol, Structured
|
|
|
1520
1538
|
6. Substitute code index slice: `[INDEX_SLICE]` → the matching `$INDEX_SLICE_{DOMAIN}` variable for this agent (e.g., `$INDEX_SLICE_AUTH` for the auth agent). Agents MUST query index data first; fall back to grep only when index slice is empty or unavailable.
|
|
1521
1539
|
7. Substitute per-agent diff slice: `[DOMAIN_DIFF_SLICE]` → the matching `$DIFF_SLICE_*` variable (e.g., `$DIFF_SLICE_AUTH` for the auth agent, `$DIFF_SLICE_SECURITY` for the security agent). This replaces any `gh pr diff [PR_NUMBER]` call inside the agent template — the agent works from the pre-computed slice, not the full changeset.
|
|
1522
1540
|
8. If Phase 2.5 found broken assumptions, append them to the agent's prompt as "Pre-found integration issues to verify"
|
|
1523
|
-
9. Launch via the resolved `{DISPATCH_TOOL}` (see Sub-Agent Dispatch Tool Resolution above) with `model: "{SUBAGENT_MODEL}"` (forge.yaml `agents.subagent_model`, else `agents.default_model`, else `"sonnet"`; fallback `"opus"` if rate-limited)
|
|
1541
|
+
9. Launch via the resolved `{DISPATCH_TOOL}` (see Sub-Agent Dispatch Tool Resolution above) with `model: "{SUBAGENT_MODEL}"` (forge.yaml `agents.subagent_model`, else `agents.default_model`, else `"sonnet"`; fallback `"opus"` if rate-limited). Under OpenCode, emit a top-level `subagent_type: "general"` or `"explore"` in the native `task` argument object and use `background: true` for each independent reviewer.
|
|
1524
1542
|
|
|
1525
1543
|
**CRITICAL**: Launch ALL selected agents in a SINGLE message using multiple `{DISPATCH_TOOL}` calls. Each agent posts findings directly to the PR via `gh pr comment`.
|
|
1526
1544
|
|
|
@@ -1553,7 +1571,7 @@ gh pr view $ARGUMENTS --json comments --jq '.comments | length'
|
|
|
1553
1571
|
gh api repos/{owner}/{repo}/issues/$ARGUMENTS/comments --jq '.[-10:] | .[].body[:100]'
|
|
1554
1572
|
```
|
|
1555
1573
|
|
|
1556
|
-
**Do NOT proceed until ALL launched agent comments are visible on the PR.**
|
|
1574
|
+
**Do NOT proceed until ALL launched agent comments are visible on the PR.** Under OpenCode, the initial `task(..., background: true)` result with `state="running"` is not completion; wait for and process each injected `task` result event, then verify the corresponding structured PR comment before continuing.
|
|
1557
1575
|
|
|
1558
1576
|
---
|
|
1559
1577
|
|
package/docs/CONFIG.md
CHANGED
|
@@ -927,7 +927,7 @@ The hook entry is registered idempotently: re-installing (`npx forgedock update`
|
|
|
927
927
|
| Command | Action |
|
|
928
928
|
|---------|--------|
|
|
929
929
|
| `npx forgedock` / `npx forgedock install` | Registers the SessionStart hook (along with installing commands and writing `forge.yaml`) |
|
|
930
|
-
| `npx forgedock update` | Re-registers the hook (relink only — does not touch `forge.yaml`) |
|
|
930
|
+
| `npx forgedock update` | Re-registers the hook when the resolved source can be safely refreshed (relink only — does not touch `forge.yaml`) |
|
|
931
931
|
| `npx forgedock uninstall` | Removes the hook entry from `settings.json` |
|
|
932
932
|
|
|
933
933
|
### Opting Out
|
|
@@ -948,7 +948,7 @@ See [Per-Directory State Registry](#per-directory-state-registry) below for how
|
|
|
948
948
|
|
|
949
949
|
`npx`/`npm` never installs `forgedock` to a stable location on your machine — it resolves to wherever `npx`'s cache (or a global npm install, or a git clone) happens to physically live. Historically, `~/.claude/commands/` and the SessionStart hook's script path were symlinked **directly** from that ephemeral location. When npm/npx (or the OS) later evicted its cache, those symlinks silently dangled — Claude Code sessions stopped getting ForgeDock context with no error shown.
|
|
950
950
|
|
|
951
|
-
|
|
951
|
+
For npm/npx sources, every `npx forgedock` (or `npx forgedock update`) run copies ForgeDock's own installable payload into a stable, version-independent home:
|
|
952
952
|
|
|
953
953
|
```
|
|
954
954
|
~/.forge/
|
|
@@ -968,8 +968,8 @@ As of this feature, every `npx forgedock` (or `npx forgedock update`) run copies
|
|
|
968
968
|
| `npx forgedock` / `npx forgedock install` | Copies the resolved package's payload into `~/.forge/` (content-compared — unchanged files are never rewritten) before linking commands, then links from `~/.forge/`, not the original source |
|
|
969
969
|
| `npx forgedock init` | Does not touch `~/.forge/` — `init` only writes `forge.yaml`, it never installs commands |
|
|
970
970
|
| `npx forgedock update` (npm/npx install) | Refreshes `~/.forge/` from whatever the currently-resolved package looks like, then relinks |
|
|
971
|
-
| `npx forgedock update` (git-clone install) |
|
|
972
|
-
| `npx forgedock doctor` | Reports `~/.forge/version` and confirms `~/.forge/{bin,commands,scripts,templates}`
|
|
971
|
+
| `npx forgedock update` (git-clone install) | Updates and relinks a `main` checkout; a non-main checkout is left untouched and gets actionable guidance because the current source must remain the source used after the command returns |
|
|
972
|
+
| `npx forgedock doctor` | Reports the effective source path, version, and git branch, then reports `~/.forge/version` and confirms `~/.forge/{bin,commands,scripts,templates}` for npm installs |
|
|
973
973
|
|
|
974
974
|
The copy is idempotent: files whose content is already byte-identical are never rewritten, so steady-state re-runs are fast and don't generate false "updated" noise.
|
|
975
975
|
|
|
@@ -979,6 +979,7 @@ If you run ForgeDock from a git clone (or a git worktree) of the repo itself —
|
|
|
979
979
|
|
|
980
980
|
- `~/.claude/commands/` continues to symlink directly from the clone, exactly as before this feature.
|
|
981
981
|
- `~/.forge/{bin,commands,scripts,templates}` correctly does **not** exist for this install mode — `npx forgedock doctor` treats that absence as healthy, not a warning.
|
|
982
|
+
- `npx forgedock update` never switches a non-main checkout to `main` and then restores it. That transient update would leave the next invocation running the old branch source, so the command reports the effective path, branch, and version and tells you to run from a main checkout instead.
|
|
982
983
|
|
|
983
984
|
### Not the Same as `~/.forge/{runs,index}`
|
|
984
985
|
|
|
@@ -1086,7 +1087,7 @@ The directory (`~/.forge/`) is created automatically on first write with `mkdir(
|
|
|
1086
1087
|
### When It's Written
|
|
1087
1088
|
|
|
1088
1089
|
- After every successful `npx forgedock install` (end of the onboarding journey, right after the "Forged." celebration screen).
|
|
1089
|
-
- After every `npx forgedock update` —
|
|
1090
|
+
- After every successful `npx forgedock update` — the main git-clone branch and npm branch route through the same repair step (`relinkAndHint()`), so the receipt is refreshed either way. A non-main git-clone advisory intentionally does not relink or rewrite the receipt.
|
|
1090
1091
|
|
|
1091
1092
|
The write is best-effort and never blocks or fails the install/update it records: any error (permission denied, disk full, unusual `FORGE_HOME` layout) degrades silently to a no-op, matching the fail-open contract already used by the command-manifest and hook-registration steps.
|
|
1092
1093
|
|
package/docs/OPENCODE.md
CHANGED
|
@@ -19,12 +19,12 @@ For the optional command tier:
|
|
|
19
19
|
npx forgedock opencode install --extras
|
|
20
20
|
```
|
|
21
21
|
|
|
22
|
-
Restart OpenCode after install or update. OpenCode loads commands and
|
|
23
|
-
at startup.
|
|
22
|
+
Restart OpenCode after install or update. OpenCode loads commands, skills, and
|
|
23
|
+
plugins at startup.
|
|
24
24
|
|
|
25
|
-
The
|
|
26
|
-
ForgeDock-owned files under OpenCode's config directory and records
|
|
27
|
-
`forgedock/manifest.json` for safe updates and removal.
|
|
25
|
+
The native adapter does not add or modify user-owned OpenCode settings. It
|
|
26
|
+
writes only ForgeDock-owned files under OpenCode's config directory and records
|
|
27
|
+
them in `forgedock/manifest.json` for safe updates and removal.
|
|
28
28
|
|
|
29
29
|
## Usage
|
|
30
30
|
|
|
@@ -62,9 +62,29 @@ The generated plugin has no prompt text. It:
|
|
|
62
62
|
- injects `FORGE_HOME` into OpenCode shell environments;
|
|
63
63
|
- defaults `subagent_depth` to 2 when the user has not configured it, while
|
|
64
64
|
preserving explicit lower limits;
|
|
65
|
+
- grants the built-in `general` subagent permission to invoke native `task`
|
|
66
|
+
unless the user explicitly configured a task permission;
|
|
67
|
+
- opts into OpenCode background subagents by default so each completed issue can
|
|
68
|
+
wake the parent orchestrator independently; set
|
|
69
|
+
`OPENCODE_EXPERIMENTAL_BACKGROUND_SUBAGENTS=false` to opt out;
|
|
65
70
|
- selects Git Bash on Windows only when the user has not explicitly configured
|
|
66
71
|
an OpenCode shell, because the shared workflows and helper scripts use Bash.
|
|
67
72
|
|
|
73
|
+
The adapter also registers every eligible ForgeDock workflow as a native
|
|
74
|
+
OpenCode skill. Top-level and nested source paths are normalized to valid
|
|
75
|
+
hyphenated skill names while the wrapper continues to point at the authoritative
|
|
76
|
+
source file:
|
|
77
|
+
|
|
78
|
+
```text
|
|
79
|
+
commands/work-on.md -> skills/work-on/SKILL.md
|
|
80
|
+
commands/work-on/investigate.md -> skills/work-on-investigate/SKILL.md
|
|
81
|
+
commands/review-pr.md -> skills/review-pr/SKILL.md
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
The wrapper is intentionally thin. It carries the workflow's `name` and
|
|
85
|
+
`description` frontmatter, preserves the current-context arguments, and loads
|
|
86
|
+
only the referenced `commands/**/*.md` spec.
|
|
87
|
+
|
|
68
88
|
OpenCode's provider configuration remains entirely user-owned. ForgeDock does
|
|
69
89
|
not require Anthropic when invoked through OpenCode; any provider and model
|
|
70
90
|
supported by the user's OpenCode configuration can execute the commands.
|
|
@@ -74,19 +94,45 @@ supported by the user's OpenCode configuration can execute the commands.
|
|
|
74
94
|
The adapter follows these rules:
|
|
75
95
|
|
|
76
96
|
- No global ForgeDock `instructions` entry.
|
|
77
|
-
-
|
|
78
|
-
|
|
79
|
-
|
|
97
|
+
- Top-level user entry commands are registered under `/forge/*`.
|
|
98
|
+
- Every eligible workflow is registered as a native skill so nested
|
|
99
|
+
`Skill(...)` dispatch resolves through OpenCode's `skill` tool instead of
|
|
100
|
+
filesystem guessing.
|
|
80
101
|
- Nested phase specs are loaded only when their dispatcher reaches them.
|
|
81
102
|
- Task/Agent work uses OpenCode subagents only for the parallelism, isolation,
|
|
82
103
|
or context-pressure cases required by the shared workflow.
|
|
104
|
+
- Orchestration dispatches independent issues with `task(background=true)` and
|
|
105
|
+
processes each injected task-result event immediately; it does not wait for a
|
|
106
|
+
wave or the slowest sibling.
|
|
83
107
|
- GitHub state and the durable engine remain the recovery source instead of
|
|
84
108
|
replaying prior prompt context.
|
|
85
109
|
|
|
86
110
|
The generated adapter preamble is intentionally small. It maps Claude Code's
|
|
87
|
-
in-conversation `Skill(...)` loading to
|
|
88
|
-
spec. It maps isolated `Task(...)` and permitted
|
|
89
|
-
OpenCode's native `task` tool.
|
|
111
|
+
in-conversation `Skill(...)` loading to the normalized native skill name and
|
|
112
|
+
authoritative shared spec. It maps isolated `Task(...)` and permitted
|
|
113
|
+
`Agent(...)` calls to OpenCode's native `task` tool. Before the shared review
|
|
114
|
+
specs evaluate Claude's literal `Task`/`Agent` names, an OpenCode runtime marker
|
|
115
|
+
selects `DISPATCH_TOOL=task`; the absence of those Claude names must not produce
|
|
116
|
+
a false `FORGE:REVIEW_BLOCKED` result.
|
|
117
|
+
|
|
118
|
+
Every isolated review dispatch uses the explicit native argument shape. Every
|
|
119
|
+
native task call must include the schema-required `subagent_type` at the top
|
|
120
|
+
level alongside its `description` and `prompt`:
|
|
121
|
+
|
|
122
|
+
```js
|
|
123
|
+
{ description: "...", prompt: "...", subagent_type: "general", background: true }
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
Implementation and review work uses `general`; read-only discovery uses
|
|
127
|
+
`explore`. Claude `general-purpose` maps to `general` and
|
|
128
|
+
`codebase-explorer` maps to `explore`. When a source task omits its type, the
|
|
129
|
+
generated adapter safely defaults it to `general`; unsupported types stop with
|
|
130
|
+
`FORGE_OPENCODE_CAPABILITY_ERROR` instead of reaching schema validation.
|
|
131
|
+
If the native `task` capability itself is unavailable, the workflow posts
|
|
132
|
+
`FORGE:REVIEW_BLOCKED` and stops rather than falling back to inline review or
|
|
133
|
+
another pipeline controller.
|
|
134
|
+
The generated plugin adds `background: true` unless
|
|
135
|
+
`OPENCODE_EXPERIMENTAL_BACKGROUND_SUBAGENTS=false` is explicitly set.
|
|
90
136
|
|
|
91
137
|
`commands/work-on.md` is still a large entry dispatcher and is loaded in full,
|
|
92
138
|
matching the Claude Code path. The adapter prevents additional eager loading,
|
|
@@ -102,13 +148,27 @@ npx forgedock opencode install --extras
|
|
|
102
148
|
npx forgedock opencode uninstall
|
|
103
149
|
```
|
|
104
150
|
|
|
151
|
+
`npx forgedock update` also refreshes an existing managed OpenCode adapter,
|
|
152
|
+
including its plugin and generated skills. It preserves the installed core vs.
|
|
153
|
+
`--extras` tier, migrates the sentinel-marked legacy adapter, and does not
|
|
154
|
+
install OpenCode files when no ForgeDock adapter is already registered.
|
|
155
|
+
|
|
105
156
|
Updates are deterministic and prune stale ForgeDock-owned command files.
|
|
106
157
|
Uninstall removes only files listed in the ownership manifest and still marked
|
|
107
158
|
with a ForgeDock sentinel. User-owned commands, plugins, and files placed in
|
|
108
159
|
the `forgedock/` namespace are never removed; that namespace is pruned only
|
|
109
160
|
when it is empty.
|
|
110
|
-
|
|
111
|
-
|
|
161
|
+
For backward compatibility, install and uninstall may rewrite `opencode.json`
|
|
162
|
+
only to remove legacy entries whose two-key `description` and `template` still
|
|
163
|
+
exactly match one of the definitions emitted by the retired adapter and point to
|
|
164
|
+
the active ForgeDock home (or the home recorded in the ownership manifest during
|
|
165
|
+
uninstall), and the sentinel-marked legacy instructions file is present. It also
|
|
166
|
+
removes references to that managed `~/.opencode-forge.md` instructions file. The
|
|
167
|
+
migration does not rewrite `opencode.jsonc`. User-owned settings and customized commands are preserved;
|
|
168
|
+
definitions with extra keys, edited fields, different
|
|
169
|
+
paths, a missing ownership marker, or ambiguous ownership are left in place. If
|
|
170
|
+
a legacy config cannot be parsed or written, the migration leaves the legacy
|
|
171
|
+
artifacts in place.
|
|
112
172
|
|
|
113
173
|
## Locations
|
|
114
174
|
|
|
@@ -117,6 +177,7 @@ Default global location:
|
|
|
117
177
|
```text
|
|
118
178
|
~/.config/opencode/
|
|
119
179
|
commands/forge/*.md
|
|
180
|
+
skills/<workflow-name>/SKILL.md
|
|
120
181
|
plugins/forgedock.js
|
|
121
182
|
forgedock/manifest.json
|
|
122
183
|
```
|
|
@@ -133,16 +194,25 @@ The separate `forgedock run` and `forgedock run-issue` backend still supports
|
|
|
133
194
|
only Claude CLI and the Anthropic API. An OpenCode engine backend must be added
|
|
134
195
|
and validated before ForgeDock can claim provider-neutral headless parity.
|
|
135
196
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
197
|
+
The ForgeDock plugin provides the OpenCode-specific runtime boundary for shell
|
|
198
|
+
and task execution. Its `tool.execute.before` hook rejects Claude-backed and
|
|
199
|
+
recursive ForgeDock controller commands, and normalizes native task arguments
|
|
200
|
+
before execution; the shared workflow rules and deterministic scripts remain
|
|
201
|
+
the source of truth for all other behavior.
|
|
202
|
+
|
|
203
|
+
OpenCode 1.18.4 keeps background subagents experimental. ForgeDock opts into
|
|
204
|
+
that feature by default because the streaming DAG depends on the parent session
|
|
205
|
+
receiving one completion event per child. A native background task returns a
|
|
206
|
+
`<task id="..." state="running">` marker immediately and later injects a
|
|
207
|
+
`state="completed"` or `state="error"` result into the parent session. The
|
|
208
|
+
orchestrator maps that id to the issue, re-reads GitHub state, and dispatches
|
|
209
|
+
newly unlocked successors in the same response.
|
|
210
|
+
|
|
211
|
+
If `OPENCODE_EXPERIMENTAL_BACKGROUND_SUBAGENTS=false` is explicitly set,
|
|
212
|
+
ForgeDock uses independent foreground tasks where OpenCode can execute them
|
|
213
|
+
concurrently and labels the run as degraded. Foreground tasks cannot provide
|
|
214
|
+
the same per-completion wake behavior, so this opt-out intentionally restores a
|
|
215
|
+
wave-like fallback rather than silently claiming Claude-equivalent throughput.
|
|
146
216
|
|
|
147
217
|
Commands that inspect Claude-specific transcripts or Claude installation state
|
|
148
218
|
remain runtime-specific and should not be represented as portable until they
|
|
@@ -151,7 +221,10 @@ receive dedicated implementations.
|
|
|
151
221
|
## Orchestration Runtime
|
|
152
222
|
|
|
153
223
|
OpenCode orchestration uses the native OpenCode `task` tool for isolated issue
|
|
154
|
-
work and follows the shared `commands/work-on.md` state machine.
|
|
224
|
+
work and follows the shared `commands/work-on.md` state machine. Each ready
|
|
225
|
+
issue is launched as `task(subagent_type="general", background=true)` and its
|
|
226
|
+
task-result event is treated as the completion notification for that issue. It
|
|
227
|
+
must not
|
|
155
228
|
route through `forgedock run-issue` when that would select the Claude CLI or
|
|
156
229
|
Anthropic API backend. If the host does not expose an OpenCode runtime marker,
|
|
157
230
|
set the runtime explicitly before launching a headless command:
|
|
@@ -162,10 +235,25 @@ FORGE_RUNTIME=opencode opencode run --command forge/orchestrate "fast-lane"
|
|
|
162
235
|
|
|
163
236
|
Each task re-reads GitHub labels and `FORGE:*` comments after every phase and
|
|
164
237
|
continues until `workflow:merged`, `workflow:invalid`, `needs-human`, or
|
|
165
|
-
`workflow:awaiting-merge`.
|
|
238
|
+
`workflow:awaiting-merge`. The parent orchestrator processes every child
|
|
239
|
+
completion independently, so a completed predecessor can unlock and dispatch
|
|
240
|
+
its successors while unrelated issues continue running. If native task
|
|
241
|
+
dispatch is unavailable, post a
|
|
166
242
|
`FORGE:OPENCODE_BLOCKED` diagnostic naming the missing capability and add
|
|
167
243
|
`needs-human`; do not leave an issue stranded at `workflow:engine-error`.
|
|
168
244
|
|
|
245
|
+
If a generated command or skill cannot load its authoritative workflow, it must
|
|
246
|
+
stop with an actionable `FORGE_OPENCODE_CAPABILITY_ERROR`. It must not recover
|
|
247
|
+
by invoking `forgedock run-issue`, `npx forgedock run-issue`, or a recursive
|
|
248
|
+
`opencode run`; those paths select a competing controller or Claude-backed
|
|
249
|
+
backend.
|
|
250
|
+
|
|
251
|
+
The same boundary is enforced below the plugin for direct CLI callers. With
|
|
252
|
+
`FORGE_RUNTIME=opencode`, `forgedock run`, `forgedock run-issue`, and Claude
|
|
253
|
+
backend preflight fail before selecting a provider. The stable error is
|
|
254
|
+
`FORGE_OPENCODE_CAPABILITY_ERROR`; continue by using the registered native
|
|
255
|
+
Skill and Task workflow instead.
|
|
256
|
+
|
|
169
257
|
This runtime branch is additive. Claude keeps its existing engine and
|
|
170
258
|
background-agent paths, and Codex keeps its installed namespaced skills and
|
|
171
259
|
repo-local adapters.
|
package/package.json
CHANGED