forgedock 1.1.11 → 1.2.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 +1 -1
- package/bin/cli-spawn-shared.mjs +25 -0
- package/bin/engine-cli.mjs +22 -1
- package/bin/engine.mjs +46 -4
- package/bin/forgedock.mjs +87 -16
- package/bin/hooks/pre-tool-use.mjs +331 -10
- package/bin/init-enrich-api.mjs +12 -4
- package/bin/init-enrich-cli.mjs +328 -0
- package/bin/init-enrich.mjs +137 -0
- package/bin/journey.mjs +13 -4
- package/bin/runner.mjs +653 -10
- package/commands/adopt.md +1 -1
- package/commands/analytics.md +10 -7
- package/commands/audit-agents.md +29 -6
- package/commands/audit.md +9 -20
- package/commands/autopilot.md +52 -78
- package/commands/changelog.md +1 -1
- package/commands/ci-audit.md +19 -10
- package/commands/cleanup.md +1 -1
- package/commands/compat-audit.md +1 -1
- package/commands/deploy-info.md +1 -1
- package/commands/deploy-pr.md +1 -1
- package/commands/design-bench.md +1 -1
- package/commands/design-render-critique-loop.md +1 -1
- package/commands/diagnose.md +1 -1
- package/commands/forge-stats.md +1 -1
- package/commands/forgedock-init.md +1 -1
- package/commands/geo-audit.md +9 -6
- package/commands/incident-response.md +15 -5
- package/commands/issue.md +298 -15
- package/commands/milestone.md +45 -18
- package/commands/optimize.md +1 -1
- package/commands/orchestrate/phase-1-resolve.md +20 -5
- package/commands/orchestrate/phase-4-execution.md +6 -1
- package/commands/orchestrate/phase-5-cleanup.md +43 -2
- package/commands/orchestrate/phase-6-report.md +1 -0
- package/commands/orchestrate.md +1 -1
- package/commands/pipeline-health/phase-4-proposals.md +40 -10
- package/commands/pipeline-health/phase-5-report.md +140 -13
- package/commands/pipeline-health.md +1 -1
- package/commands/pipeline-resume.md +1 -1
- package/commands/pipeline-status.md +1 -1
- package/commands/qa-sweep.md +10 -4
- package/commands/recover-orphans.md +1 -1
- package/commands/review-pr-agents/protocols.md +4 -0
- package/commands/review-pr-agents.md +7 -4
- package/commands/review-pr-staging.md +44 -15
- package/commands/review-pr.md +73 -28
- package/commands/rollback.md +15 -5
- package/commands/security-audit.md +14 -11
- package/commands/signal-planner.md +32 -12
- package/commands/spec-doctor.md +1 -1
- package/commands/sync-ecosystem.md +54 -11
- package/commands/test-gate.md +49 -11
- package/commands/validate.md +16 -5
- package/commands/work-on/build/architect.md +1 -1
- package/commands/work-on/build/context.md +1 -1
- package/commands/work-on/build/implement.md +2 -2
- package/commands/work-on/build/validate.md +3 -1
- package/commands/work-on/build.md +1 -1
- package/commands/work-on/close.md +1 -1
- package/commands/work-on/decompose.md +27 -24
- package/commands/work-on/investigate.md +2 -2
- package/commands/work-on/remediate.md +1 -1
- package/commands/work-on/review.md +5 -3
- package/commands/work-on-monolithic.md +1 -1
- package/commands/work-on.md +28 -7
- package/docs/CI.md +18 -0
- package/docs/CONFIG.md +5 -2
- package/docs/site/annotations-explained.md +2 -2
- package/docs/site/command-reference.md +21 -0
- package/docs/spec/review-protocol.md +6 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -250,7 +250,7 @@ Then just open Claude Code and run `/work-on <issue>`.
|
|
|
250
250
|
|
|
251
251
|
Commands then appear as `/forgedock:work-on`, etc. You still run `npx forgedock init` to generate `forge.yaml`.
|
|
252
252
|
|
|
253
|
-
**Headless / CI:** the pipeline also runs outside Claude Code. `npx forgedock run work-on <issue> --dry-run` previews the assembled prompt and tool plan
|
|
253
|
+
**Headless / CI:** the pipeline also runs outside Claude Code. `npx forgedock run work-on <issue> --dry-run` previews the assembled prompt and tool plan. `npx forgedock run` picks an execution backend automatically (`--backend auto`, the default): if the Claude Code CLI (`claude`) is installed and already authenticated (Pro/Max subscription or a CLI-managed key), it drives the command through that — **no separate `ANTHROPIC_API_KEY` required**. Otherwise it falls back to the Anthropic API directly (`ANTHROPIC_API_KEY` required). Force either path explicitly with `--backend cli` / `--backend api` (or `FORGEDOCK_BACKEND=cli|api`) — the API backend is what CI environments without an interactive `claude` login should use. `npx forgedock run-issue <issue>` executes the same command specs on the durable engine (event-sourced run log, leases, crash-safe resume).
|
|
254
254
|
|
|
255
255
|
**Explicit install command:**
|
|
256
256
|
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* cli-spawn-shared.mjs — shared constants for `claude` CLI `spawnSync`
|
|
3
|
+
* invocations across the codebase.
|
|
4
|
+
*
|
|
5
|
+
* Extracted (issue #2032) to stop `maxBuffer: 50 * 1024 * 1024` from
|
|
6
|
+
* drifting independently at each spawn call site (bin/runner.mjs's
|
|
7
|
+
* `runCliBackend` and `run_bash`, bin/init-enrich-cli.mjs's `enrich()`) —
|
|
8
|
+
* all three invoke the same local `claude` CLI and should share one
|
|
9
|
+
* default rather than three copies of the same magic number.
|
|
10
|
+
*
|
|
11
|
+
* Isolated: imports no external modules, so it is safe to import from any
|
|
12
|
+
* module without creating a cycle.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Default `maxBuffer` (bytes) for a `spawnSync` call against the local
|
|
17
|
+
* `claude` CLI. 50 MB comfortably covers a full headless response (stdout)
|
|
18
|
+
* plus any diagnostic stderr without risking silent truncation.
|
|
19
|
+
*
|
|
20
|
+
* `bin/runner.mjs`'s `run_bash` tool handler additionally allows this
|
|
21
|
+
* default to be overridden per-call via `FORGEDOCK_MAX_BUFFER_BYTES` — that
|
|
22
|
+
* override logic lives in `runner.mjs` itself; this constant is only the
|
|
23
|
+
* shared *default*.
|
|
24
|
+
*/
|
|
25
|
+
export const DEFAULT_SPAWN_MAX_BUFFER_BYTES = 50 * 1024 * 1024;
|
package/bin/engine-cli.mjs
CHANGED
|
@@ -224,6 +224,21 @@ export function lastLocalRun(dir) {
|
|
|
224
224
|
* @param {{io?: {gh: Function}, runIssue?: Function}} [deps]
|
|
225
225
|
* Injectable for tests — defaults to real `gh`/`git` (makeIo()) and the real
|
|
226
226
|
* `runIssue` engine driver.
|
|
227
|
+
*
|
|
228
|
+
* Flags:
|
|
229
|
+
* --lane <lane> Required — e.g. `main` or `staging`.
|
|
230
|
+
* --repo <owner/repo> Optional — must match the cwd-resolved repo (forge#1593).
|
|
231
|
+
* --backend <cli|api|auto> Optional (forge#2028). Forwarded to every phase's
|
|
232
|
+
* `runCommand()` call. Omit to keep runner.mjs's own
|
|
233
|
+
* default ("auto" ladder — probes the `claude` CLI, falls
|
|
234
|
+
* back to the API). Mirrors `bin/forgedock.mjs`'s `run()`
|
|
235
|
+
* `--backend` semantics — validation (rejecting invalid
|
|
236
|
+
* values) is left to `runCommand()`'s own `resolveBackend()`
|
|
237
|
+
* rather than duplicated here.
|
|
238
|
+
* --model <id> Optional (forge#2028). Forwarded to every phase's
|
|
239
|
+
* `runCommand()` call; only applies on the "api" backend.
|
|
240
|
+
* Omit to keep runner.mjs's default (`FORGEDOCK_MODEL` env
|
|
241
|
+
* or its built-in default).
|
|
227
242
|
*/
|
|
228
243
|
export async function runFromCli(argv, deps = {}) {
|
|
229
244
|
const issue = parseInt(argv[0], 10);
|
|
@@ -231,12 +246,18 @@ export async function runFromCli(argv, deps = {}) {
|
|
|
231
246
|
const lane = flag(argv, "--lane");
|
|
232
247
|
if (!lane) throw new Error("--lane is required: e.g. --lane main or --lane staging. No default to prevent accidental production targeting.");
|
|
233
248
|
const repo = flag(argv, "--repo");
|
|
249
|
+
const backend = flag(argv, "--backend");
|
|
250
|
+
const model = flag(argv, "--model");
|
|
234
251
|
const io = deps.io ?? makeIo();
|
|
235
252
|
await assertRepoMatchesCwd(io, repo);
|
|
236
253
|
const runIssueFn = deps.runIssue ?? runIssue;
|
|
237
254
|
const agentId = `cli_${process.pid}`;
|
|
238
255
|
const res = await runIssueFn({ issue, dir: runDir(), agentId, lane, io,
|
|
239
|
-
runner: (await import("./runner.mjs")).runCommand, now: () => Date.now()
|
|
256
|
+
runner: (await import("./runner.mjs")).runCommand, now: () => Date.now(),
|
|
257
|
+
// Only forwarded when explicitly provided — omitting them preserves
|
|
258
|
+
// runIssue's/runner.mjs's existing defaults (forge#2028).
|
|
259
|
+
...(backend ? { backend } : {}),
|
|
260
|
+
...(model ? { model } : {}) });
|
|
240
261
|
console.log(`issue #${issue} → ${res.terminalReason}`);
|
|
241
262
|
return res;
|
|
242
263
|
}
|
package/bin/engine.mjs
CHANGED
|
@@ -8,13 +8,49 @@ import { appendEvent, readLog, deriveState, rewriteLog } from "./engine/runlog.m
|
|
|
8
8
|
import { pickPhase, TERMINAL_REASONS } from "./engine/phases.mjs";
|
|
9
9
|
import { reconcileState } from "./engine/reconcile.mjs";
|
|
10
10
|
import { makeProjector } from "./engine/projector.mjs";
|
|
11
|
+
import { VALID_BACKENDS } from "./runner.mjs";
|
|
11
12
|
|
|
12
13
|
const DEFAULT_MAX_ATTEMPTS = 3;
|
|
13
14
|
|
|
15
|
+
/**
|
|
16
|
+
* @param {object} opts
|
|
17
|
+
* @param {string} [opts.backend] - "cli" | "api" | "auto" (forge#2028). Forwarded
|
|
18
|
+
* to every phase's `runner()` call when supplied. Omit to keep runner.mjs's own
|
|
19
|
+
* default ("auto" ladder — probes the `claude` CLI, falls back to the API).
|
|
20
|
+
* An invalid value throws synchronously (see below) rather than being forwarded.
|
|
21
|
+
* @param {string} [opts.model] - Model id (forge#2028). Forwarded to every phase's
|
|
22
|
+
* `runner()` call when supplied; only applies on the "api" backend. Omit to keep
|
|
23
|
+
* runner.mjs's default (`FORGEDOCK_MODEL` env or its built-in default).
|
|
24
|
+
*/
|
|
14
25
|
export async function runIssue(opts) {
|
|
15
26
|
const { issue, dir, agentId, lane = "staging", io, runner,
|
|
16
27
|
now = () => Date.now(), maxAttempts = DEFAULT_MAX_ATTEMPTS,
|
|
17
|
-
commandsDir = fileURLToPath(new URL("../commands", import.meta.url))
|
|
28
|
+
commandsDir = fileURLToPath(new URL("../commands", import.meta.url)),
|
|
29
|
+
// Optional execution-backend override for every phase's `runner()`
|
|
30
|
+
// call (forge#2028 / MAT-3). Left undefined by default so existing
|
|
31
|
+
// callers keep runner.mjs's own "auto" ladder default unchanged —
|
|
32
|
+
// this is purely additive pass-through, not a new default.
|
|
33
|
+
backend, model } = opts;
|
|
34
|
+
|
|
35
|
+
// forge#2054: validate `backend` before anything else — before state is
|
|
36
|
+
// read/written and before the phase/retry loop begins below. An invalid
|
|
37
|
+
// value must fail fast and non-retryably. Without this check, an invalid
|
|
38
|
+
// backend instead reaches runner.mjs's resolveBackend() deep inside
|
|
39
|
+
// runPhaseWithRetry()'s per-attempt try/catch, throws an uncoded Error, and
|
|
40
|
+
// is silently retried up to `maxAttempts` times (the catch there only
|
|
41
|
+
// fast-fails on `.code === "NO_API_KEY"/"NO_SDK"`) before escalating to
|
|
42
|
+
// needs-human — burning retries on what is actually a config error, not a
|
|
43
|
+
// transient phase failure. runIssue() is the single production choke point
|
|
44
|
+
// (its only caller is bin/engine-cli.mjs's runFromCli()), so validating
|
|
45
|
+
// here protects every current and future caller without requiring each one
|
|
46
|
+
// to remember to validate independently.
|
|
47
|
+
if (backend !== undefined && !VALID_BACKENDS.has(backend)) {
|
|
48
|
+
throw Object.assign(
|
|
49
|
+
new Error(`Invalid backend "${backend}". Must be one of: ${[...VALID_BACKENDS].join(", ")}.`),
|
|
50
|
+
{ code: "INVALID_BACKEND" },
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
|
|
18
54
|
const projector = makeProjector(io);
|
|
19
55
|
|
|
20
56
|
// 1. Load + reconcile (GitHub wins).
|
|
@@ -67,7 +103,7 @@ export async function runIssue(opts) {
|
|
|
67
103
|
outcome = { status: "committed", outputs: reconciled.outputs || {} };
|
|
68
104
|
} else {
|
|
69
105
|
if (reconciled.outputs?.pr) state.pr = reconciled.outputs.pr;
|
|
70
|
-
outcome = await runPhaseWithRetry(phase, state, { io, runner, dir, issue, commandsDir, maxAttempts });
|
|
106
|
+
outcome = await runPhaseWithRetry(phase, state, { io, runner, dir, issue, commandsDir, maxAttempts, backend, model });
|
|
71
107
|
}
|
|
72
108
|
|
|
73
109
|
if (outcome.status === "blocked") return await terminate(state, "needs-human", outcome.detail);
|
|
@@ -95,11 +131,17 @@ export async function runIssue(opts) {
|
|
|
95
131
|
}
|
|
96
132
|
|
|
97
133
|
async function runPhaseWithRetry(phase, state, ctx) {
|
|
98
|
-
const { io, runner, dir, issue, commandsDir, maxAttempts } = ctx;
|
|
134
|
+
const { io, runner, dir, issue, commandsDir, maxAttempts, backend, model } = ctx;
|
|
99
135
|
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
|
100
136
|
appendEvent(dir, issue, { event: "PHASE_START", phase: phase.id, attempt });
|
|
101
137
|
try {
|
|
102
|
-
await runner({
|
|
138
|
+
await runner({
|
|
139
|
+
commandsDir, commandName: phase.command, args: [String(issue)],
|
|
140
|
+
// Only forwarded when explicitly provided — omitting them preserves
|
|
141
|
+
// runner.mjs's existing default ("auto" backend / DEFAULT_MODEL).
|
|
142
|
+
...(backend ? { backend } : {}),
|
|
143
|
+
...(model ? { model } : {}),
|
|
144
|
+
});
|
|
103
145
|
} catch (e) {
|
|
104
146
|
// A missing API key / SDK is a config error, not a transient phase
|
|
105
147
|
// failure — surface it distinctly instead of burning retries and
|
package/bin/forgedock.mjs
CHANGED
|
@@ -951,6 +951,21 @@ async function statusScreen(c) {
|
|
|
951
951
|
c.stdout.write("\n");
|
|
952
952
|
}
|
|
953
953
|
|
|
954
|
+
/**
|
|
955
|
+
* `npx forgedock init` — detect + AI-enrich + review `forge.yaml`.
|
|
956
|
+
*
|
|
957
|
+
* AI enrichment backend selection (bin/init-enrich.mjs `resolveEnrichBackend`,
|
|
958
|
+
* issue #2004): prefers a local, authenticated `claude` CLI when present,
|
|
959
|
+
* otherwise falls back to the Anthropic API when ANTHROPIC_API_KEY is set,
|
|
960
|
+
* otherwise skips enrichment.
|
|
961
|
+
*
|
|
962
|
+
* Env:
|
|
963
|
+
* FORGEDOCK_INIT_BACKEND Override ("cli"|"api"|"none"|"auto"; default
|
|
964
|
+
* "auto") — pins the enrichment backend instead of
|
|
965
|
+
* the auto-detect ladder above (issue #2023).
|
|
966
|
+
* Independent of FORGEDOCK_BACKEND, which controls
|
|
967
|
+
* the separate `forgedock run` engine backend.
|
|
968
|
+
*/
|
|
954
969
|
async function initFlow(c) {
|
|
955
970
|
const outputPath = join(c.cwd, "forge.yaml");
|
|
956
971
|
const hasExisting = existsSync(outputPath);
|
|
@@ -2721,7 +2736,7 @@ function help() {
|
|
|
2721
2736
|
["npx forgedock enable [dir]", "Activate ForgeDock in a directory"],
|
|
2722
2737
|
["npx forgedock disable [dir]", "Opt a directory out of ForgeDock"],
|
|
2723
2738
|
["npx forgedock status [dir]", "Show ForgeDock state for a directory"],
|
|
2724
|
-
["npx forgedock run <cmd> [args]", "Run a command headlessly
|
|
2739
|
+
["npx forgedock run <cmd> [args]", "Run a command headlessly (local claude CLI or Anthropic API)"],
|
|
2725
2740
|
["npx forgedock run-issue <issue>", "Drive one issue through the durable engine"],
|
|
2726
2741
|
["npx forgedock resume-stalled [--dry-run]", "Fleet stall recovery — re-dispatch expired-lease issues"],
|
|
2727
2742
|
["npx forgedock demo", "Set up a risk-free demo repo and print next steps"],
|
|
@@ -2755,46 +2770,88 @@ function help() {
|
|
|
2755
2770
|
|
|
2756
2771
|
/**
|
|
2757
2772
|
* `forgedock run <command> [args...]` — execute a ForgeDock command spec
|
|
2758
|
-
*
|
|
2773
|
+
* headlessly, outside of an interactive Claude Code session (CI or local).
|
|
2759
2774
|
*
|
|
2760
2775
|
* Flags:
|
|
2761
|
-
* --dry-run
|
|
2762
|
-
* --model <id>
|
|
2763
|
-
* --max-iterations <n>
|
|
2776
|
+
* --dry-run Preview the assembled prompt + tool plan; no call made.
|
|
2777
|
+
* --model <id> Override the model (see resolution order below; api backend only).
|
|
2778
|
+
* --max-iterations <n> Bound the tool-use loop (default: 50; api backend only).
|
|
2779
|
+
* --backend <cli|api|auto> Execution backend (see below; default: auto).
|
|
2764
2780
|
*
|
|
2765
|
-
*
|
|
2766
|
-
*
|
|
2781
|
+
* Two execution backends (issue #2003):
|
|
2782
|
+
* - "cli": shells out to the local Claude Code CLI (`claude --print ...`),
|
|
2783
|
+
* reusing whatever credentials it already has (Pro/Max OAuth or a
|
|
2784
|
+
* CLI-managed key). No ANTHROPIC_API_KEY needed. Requires `claude` on PATH.
|
|
2785
|
+
* - "api": drives the Anthropic SDK directly. Requires ANTHROPIC_API_KEY and
|
|
2786
|
+
* the optional @anthropic-ai/sdk dependency. Needed for CI/headless
|
|
2787
|
+
* environments without an interactively-authenticated `claude` CLI.
|
|
2788
|
+
* - "auto" (default): prefers "cli" when a working `claude` install is
|
|
2789
|
+
* detected, otherwise falls back to "api" unchanged — existing callers
|
|
2790
|
+
* that never pass --backend see no behavior change when `claude` isn't
|
|
2791
|
+
* installed.
|
|
2767
2792
|
*
|
|
2768
|
-
*
|
|
2793
|
+
* The runtime itself lives in bin/runner.mjs.
|
|
2794
|
+
*
|
|
2795
|
+
* Model resolution order (highest precedence first; api backend only — the
|
|
2796
|
+
* cli backend uses whatever model the `claude` CLI itself is configured for):
|
|
2769
2797
|
* 1. --model <id> This flag.
|
|
2770
2798
|
* 2. $FORGEDOCK_MODEL Env var, below.
|
|
2771
2799
|
* 3. forge.yaml `agents.default_model` Read from the run's cwd, if present.
|
|
2772
2800
|
* 4. Hardcoded default "claude-sonnet-5" (bin/runner.mjs DEFAULT_MODEL).
|
|
2773
2801
|
*
|
|
2774
2802
|
* Env:
|
|
2775
|
-
*
|
|
2776
|
-
*
|
|
2777
|
-
*
|
|
2778
|
-
*
|
|
2803
|
+
* FORGEDOCK_BACKEND Default backend ("cli"|"api"|"auto") when --backend is omitted.
|
|
2804
|
+
* FORGEDOCK_MODEL Default model id when --model is omitted.
|
|
2805
|
+
* FORGEDOCK_CLI_TIMEOUT_MS Wall-clock timeout (ms) for the cli backend invocation.
|
|
2806
|
+
* FORGEDOCK_SHELL Override the shell used by run_bash / the cli backend.
|
|
2807
|
+
* Defaults to bash when found (Git Bash / WSL on
|
|
2808
|
+
* Windows, /bin/bash on POSIX), falling back to the
|
|
2809
|
+
* platform default shell otherwise.
|
|
2779
2810
|
*/
|
|
2780
2811
|
async function run() {
|
|
2781
2812
|
const runArgs = restArgs;
|
|
2782
2813
|
let dryRun = false;
|
|
2783
2814
|
let model;
|
|
2784
2815
|
let maxIterations;
|
|
2816
|
+
let backend;
|
|
2785
2817
|
const positional = [];
|
|
2818
|
+
// Consume the value for a `--flag <value>` pair at index `idx` (the flag
|
|
2819
|
+
// itself). Errors loudly instead of silently returning `undefined` when
|
|
2820
|
+
// the flag is the last token or is immediately followed by another flag —
|
|
2821
|
+
// both cases previously left the corresponding variable `undefined`,
|
|
2822
|
+
// which downstream validation treats identically to "flag omitted",
|
|
2823
|
+
// silently falling back to the default instead of erroring. Returns the
|
|
2824
|
+
// consumed value; the caller is responsible for advancing its own loop
|
|
2825
|
+
// index past the consumed token.
|
|
2826
|
+
const requireFlagValue = (flagName, idx) => {
|
|
2827
|
+
const value = runArgs[idx + 1];
|
|
2828
|
+
if (value === undefined || value.startsWith("--")) {
|
|
2829
|
+
process.stderr.write(
|
|
2830
|
+
`${RED}Missing value for ${flagName}. Usage: ${flagName} <value>${RESET}\n`,
|
|
2831
|
+
);
|
|
2832
|
+
process.exit(1);
|
|
2833
|
+
}
|
|
2834
|
+
return value;
|
|
2835
|
+
};
|
|
2786
2836
|
for (let i = 0; i < runArgs.length; i++) {
|
|
2787
2837
|
const a = runArgs[i];
|
|
2788
2838
|
if (a === "--dry-run") {
|
|
2789
2839
|
dryRun = true;
|
|
2790
2840
|
} else if (a === "--model") {
|
|
2791
|
-
model =
|
|
2841
|
+
model = requireFlagValue("--model", i);
|
|
2842
|
+
i++;
|
|
2792
2843
|
} else if (a.startsWith("--model=")) {
|
|
2793
2844
|
model = a.slice("--model=".length);
|
|
2794
2845
|
} else if (a === "--max-iterations") {
|
|
2795
|
-
maxIterations = parseInt(
|
|
2846
|
+
maxIterations = parseInt(requireFlagValue("--max-iterations", i), 10);
|
|
2847
|
+
i++;
|
|
2796
2848
|
} else if (a.startsWith("--max-iterations=")) {
|
|
2797
2849
|
maxIterations = parseInt(a.slice("--max-iterations=".length), 10);
|
|
2850
|
+
} else if (a === "--backend") {
|
|
2851
|
+
backend = requireFlagValue("--backend", i);
|
|
2852
|
+
i++;
|
|
2853
|
+
} else if (a.startsWith("--backend=")) {
|
|
2854
|
+
backend = a.slice("--backend=".length);
|
|
2798
2855
|
} else {
|
|
2799
2856
|
positional.push(a);
|
|
2800
2857
|
}
|
|
@@ -2805,12 +2862,25 @@ async function run() {
|
|
|
2805
2862
|
|
|
2806
2863
|
if (!commandName) {
|
|
2807
2864
|
process.stderr.write(
|
|
2808
|
-
`${RED}Usage: forgedock run <command> [args...] [--dry-run] [--model <id>] [--max-iterations <n>]${RESET}\n`,
|
|
2865
|
+
`${RED}Usage: forgedock run <command> [args...] [--dry-run] [--model <id>] [--max-iterations <n>] [--backend <cli|api|auto>]${RESET}\n`,
|
|
2866
|
+
);
|
|
2867
|
+
process.exit(1);
|
|
2868
|
+
}
|
|
2869
|
+
|
|
2870
|
+
// VALID_BACKENDS is imported from bin/runner.mjs (not re-hardcoded here) so
|
|
2871
|
+
// this CLI-layer flag check and runner.mjs's own library-layer
|
|
2872
|
+
// resolveBackend() validation can never structurally diverge on the set of
|
|
2873
|
+
// accepted values or the wording of the resulting error message (issue
|
|
2874
|
+
// #2013 — the two were previously independently-hardcoded Sets with
|
|
2875
|
+
// near-identical but not-quite-matching error text).
|
|
2876
|
+
const { runCommand, VALID_BACKENDS } = await import("./runner.mjs");
|
|
2877
|
+
if (backend !== undefined && !VALID_BACKENDS.has(backend)) {
|
|
2878
|
+
process.stderr.write(
|
|
2879
|
+
`${RED}Invalid --backend "${backend}". Must be one of: ${[...VALID_BACKENDS].join(", ")}.${RESET}\n`,
|
|
2809
2880
|
);
|
|
2810
2881
|
process.exit(1);
|
|
2811
2882
|
}
|
|
2812
2883
|
|
|
2813
|
-
const { runCommand } = await import("./runner.mjs");
|
|
2814
2884
|
try {
|
|
2815
2885
|
const result = await runCommand({
|
|
2816
2886
|
commandsDir: COMMANDS_DIR,
|
|
@@ -2822,6 +2892,7 @@ async function run() {
|
|
|
2822
2892
|
...(Number.isInteger(maxIterations) && maxIterations > 0
|
|
2823
2893
|
? { maxIterations }
|
|
2824
2894
|
: {}),
|
|
2895
|
+
...(backend ? { backend } : {}),
|
|
2825
2896
|
});
|
|
2826
2897
|
// Treat a non-clean stop (iteration cap hit, or a max_tokens-truncated
|
|
2827
2898
|
// turn) as a failed run so CI/headless callers notice.
|