ai-foreman 1.1.0 → 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 +14 -6
- package/dist/cli/runtimeAuthPrompt.d.ts +21 -2
- package/dist/cli/runtimeAuthPrompt.js +65 -13
- package/dist/cli/runtimeSelection.d.ts +9 -0
- package/dist/cli/runtimeSelection.js +48 -0
- package/dist/cli/start.js +29 -11
- package/dist/cli/tickets.js +18 -11
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -85,7 +85,7 @@ What each part does:
|
|
|
85
85
|
| --- | --- | --- | --- |
|
|
86
86
|
| `<project>` | `./my-project` / `../repo` | required | Target repo the agent works in. |
|
|
87
87
|
| `-s, --steps <n>` | `1` / `5` / `10` | required | Max tickets or implementation steps to drive. |
|
|
88
|
-
| `-a, --agent <agent>` | `claude` / `codex` | `claude` | Builder agent. |
|
|
88
|
+
| `-a, --agent <agent>` | `claude` / `codex` | single `rafi-config.yaml` target, otherwise `claude` | Builder agent. Explicit `--agent` always wins. |
|
|
89
89
|
| `-m, --model <model>` | `gpt-5.5` / any supported agent model | agent default | Overrides the builder model. |
|
|
90
90
|
| `--effort <level>` | Claude Code: `low` / `medium` / `high` / `xhigh`<br>Codex: `low` / `medium` / `high` / `xhigh` | agent default | Reasoning level. |
|
|
91
91
|
| `--fast` | flag | off | Lower latency. For Codex, maps to `effort=low` when `--effort` is not set. |
|
|
@@ -156,7 +156,7 @@ gh repo view <host>/<owner>/<repo>
|
|
|
156
156
|
| Option | Common values | Default | Notes |
|
|
157
157
|
| --- | --- | --- | --- |
|
|
158
158
|
| `-p, --project <dir>` | `./my-project` / `../repo` | current directory | Target repo with `.tickets/`. |
|
|
159
|
-
| `-a, --agent <agent>` | `claude` / `codex` | `claude` | Builder agent. |
|
|
159
|
+
| `-a, --agent <agent>` | `claude` / `codex` | single `rafi-config.yaml` target, otherwise `claude` | Builder agent. Explicit `--agent` always wins. |
|
|
160
160
|
| `-m, --model <model>` | `gpt-5.5` / any supported agent model | agent default | Overrides the builder model. |
|
|
161
161
|
| `--effort <level>` | Claude Code: `low` / `medium` / `high` / `xhigh`<br>Codex: `low` / `medium` / `high` / `xhigh` | agent default | Reasoning level. |
|
|
162
162
|
| `--sources <paths...>` | `docs/tickets.md docs/plans/**` | scans relevant docs automatically | Optional files, folders, or globs for the agent to check first. Any reasonable planning format is OK. |
|
|
@@ -188,17 +188,25 @@ Without tickets, Foreman still works with:
|
|
|
188
188
|
|
|
189
189
|
Claude Code:
|
|
190
190
|
|
|
191
|
-
- Default agent.
|
|
191
|
+
- Default agent when no single runtime target is configured.
|
|
192
192
|
- Uses your existing Claude Code credentials.
|
|
193
193
|
- Runs through the Claude Agent SDK.
|
|
194
|
-
- `start` and `tickets populate` check Claude auth before launching the builder and prompt you to repair it
|
|
194
|
+
- `start` and `tickets populate` check Claude auth before launching the builder and prompt you to repair it, switch to Codex for the current run, or cancel when interactive.
|
|
195
195
|
|
|
196
196
|
Codex:
|
|
197
197
|
|
|
198
|
-
- Use `--agent codex`.
|
|
198
|
+
- Use `--agent codex`, or set `harness.targets: ["codex"]` in `rafi-config.yaml` and omit `--agent`.
|
|
199
199
|
- Shells out to `codex exec`.
|
|
200
200
|
- Requires the `codex` CLI on your `PATH`.
|
|
201
|
-
- `start` and `tickets populate --agent codex` check Codex auth before launching the builder and prompt you to repair it
|
|
201
|
+
- `start` and `tickets populate --agent codex` check Codex auth before launching the builder and prompt you to repair it, switch to Claude for the current run, or cancel when interactive.
|
|
202
|
+
|
|
203
|
+
Runtime defaults and recovery:
|
|
204
|
+
|
|
205
|
+
- If `--agent` is omitted, Foreman reads `rafi-config.yaml`. A single `harness.targets` value selects that runtime; missing config or both targets default to Claude.
|
|
206
|
+
- Non-interactive and `--yes` runs fail clearly instead of switching runtimes automatically.
|
|
207
|
+
- Resume and continue flows do not offer runtime switching because session IDs are runtime-specific.
|
|
208
|
+
- If an interactive command switches providers, a provider-specific `--model` override is ignored for that run. `--effort` and `--fast` remain in effect.
|
|
209
|
+
- Cancel stops the command and keeps project files, generated files, and installed packages in place.
|
|
202
210
|
|
|
203
211
|
Task file:
|
|
204
212
|
|
|
@@ -1,2 +1,21 @@
|
|
|
1
|
-
import { type AgentRuntime } from "../runtimeAuth.js";
|
|
2
|
-
export
|
|
1
|
+
import { RuntimeAuthError, type AgentRuntime } from "../runtimeAuth.js";
|
|
2
|
+
export type RuntimeCommandRecoveryChoice = "retry" | "switch" | "cancel";
|
|
3
|
+
export interface RuntimeCommandRecoveryContext {
|
|
4
|
+
otherRuntime: AgentRuntime;
|
|
5
|
+
allowSwitch: boolean;
|
|
6
|
+
}
|
|
7
|
+
export interface RuntimeReadyForCommandOptions {
|
|
8
|
+
label: string;
|
|
9
|
+
yes?: boolean;
|
|
10
|
+
allowSwitch?: boolean;
|
|
11
|
+
model?: string | undefined;
|
|
12
|
+
check?: (projectDir: string, runtime: AgentRuntime) => void;
|
|
13
|
+
checkClaudeSdk?: () => Promise<void>;
|
|
14
|
+
choose?: (err: RuntimeAuthError, context: RuntimeCommandRecoveryContext) => Promise<RuntimeCommandRecoveryChoice>;
|
|
15
|
+
}
|
|
16
|
+
export interface RuntimeReadyForCommandResult {
|
|
17
|
+
runtime: AgentRuntime;
|
|
18
|
+
model?: string;
|
|
19
|
+
fellBack: boolean;
|
|
20
|
+
}
|
|
21
|
+
export declare function ensureRuntimeReadyForCommand(projectDir: string, runtime: AgentRuntime, labelOrOptions: string | RuntimeReadyForCommandOptions): Promise<RuntimeReadyForCommandResult>;
|
|
@@ -1,27 +1,79 @@
|
|
|
1
1
|
import { select, isCancel, log } from "@clack/prompts";
|
|
2
|
+
import { requireClaudeSDK } from "../adapters/claude.js";
|
|
2
3
|
import { checkRuntimeReady, RuntimeAuthError, runtimeCommandLabel, } from "../runtimeAuth.js";
|
|
3
|
-
|
|
4
|
+
import { otherRuntime, runtimeDisplayName } from "./runtimeSelection.js";
|
|
5
|
+
export async function ensureRuntimeReadyForCommand(projectDir, runtime, labelOrOptions) {
|
|
6
|
+
const opts = typeof labelOrOptions === "string"
|
|
7
|
+
? { label: labelOrOptions }
|
|
8
|
+
: labelOrOptions;
|
|
9
|
+
const check = opts.check ?? checkRuntimeReady;
|
|
10
|
+
const checkClaudeSdk = opts.checkClaudeSdk ?? requireClaudeSDK;
|
|
11
|
+
const allowSwitch = opts.allowSwitch !== false;
|
|
12
|
+
const nonInteractive = !opts.choose && (Boolean(opts.yes) || !process.stdin.isTTY || !process.stdout.isTTY);
|
|
4
13
|
while (true) {
|
|
5
14
|
try {
|
|
6
|
-
|
|
7
|
-
return;
|
|
15
|
+
check(projectDir, runtime);
|
|
16
|
+
return { runtime, model: opts.model, fellBack: false };
|
|
8
17
|
}
|
|
9
18
|
catch (err) {
|
|
10
19
|
const failure = err instanceof RuntimeAuthError
|
|
11
20
|
? err
|
|
12
|
-
: new RuntimeAuthError({ runtime, context: label, cause: err });
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
21
|
+
: new RuntimeAuthError({ runtime, context: opts.label, cause: err });
|
|
22
|
+
if (nonInteractive) {
|
|
23
|
+
throw failure;
|
|
24
|
+
}
|
|
25
|
+
const fallbackRuntime = otherRuntime(runtime);
|
|
26
|
+
const choice = opts.choose
|
|
27
|
+
? await opts.choose(failure, { otherRuntime: fallbackRuntime, allowSwitch })
|
|
28
|
+
: await promptRuntimeRecovery(failure, opts.label, fallbackRuntime, allowSwitch);
|
|
29
|
+
if (choice === "retry")
|
|
30
|
+
continue;
|
|
31
|
+
if (choice === "switch" && allowSwitch) {
|
|
32
|
+
try {
|
|
33
|
+
check(projectDir, fallbackRuntime);
|
|
34
|
+
if (fallbackRuntime === "claude") {
|
|
35
|
+
await checkClaudeSdk();
|
|
36
|
+
}
|
|
37
|
+
if (opts.model) {
|
|
38
|
+
console.log(`foreman: ignored --model ${opts.model} after switching to ${fallbackRuntime}; model names are provider-specific.`);
|
|
39
|
+
}
|
|
40
|
+
console.log(`foreman: using ${runtimeDisplayName(fallbackRuntime)} for this run.`);
|
|
41
|
+
return { runtime: fallbackRuntime, model: undefined, fellBack: true };
|
|
42
|
+
}
|
|
43
|
+
catch (switchErr) {
|
|
44
|
+
const switchFailure = switchErr instanceof RuntimeAuthError
|
|
45
|
+
? switchErr
|
|
46
|
+
: new RuntimeAuthError({ runtime: fallbackRuntime, context: opts.label, cause: switchErr });
|
|
47
|
+
log.error(`Fallback runtime is not ready:\n${switchFailure.message}`);
|
|
48
|
+
continue;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
if (choice === "cancel") {
|
|
22
52
|
console.log("foreman: cancelled");
|
|
23
53
|
process.exit(0);
|
|
24
54
|
}
|
|
55
|
+
throw failure;
|
|
25
56
|
}
|
|
26
57
|
}
|
|
27
58
|
}
|
|
59
|
+
async function promptRuntimeRecovery(err, label, fallbackRuntime, allowSwitch) {
|
|
60
|
+
log.error(err.message);
|
|
61
|
+
log.info("Cancel stops this command and keeps project files in place. It does not uninstall packages, delete generated files, or change configuration.");
|
|
62
|
+
if (!allowSwitch) {
|
|
63
|
+
log.info("Switching runtimes is disabled while resuming because session IDs are runtime-specific.");
|
|
64
|
+
}
|
|
65
|
+
const options = [
|
|
66
|
+
{ value: "retry", label: "Fix manually and retry check" },
|
|
67
|
+
...(allowSwitch
|
|
68
|
+
? [{ value: "switch", label: `Use ${runtimeDisplayName(fallbackRuntime)} for now` }]
|
|
69
|
+
: []),
|
|
70
|
+
{ value: "cancel", label: "Cancel - stop here; keep generated files" },
|
|
71
|
+
];
|
|
72
|
+
const choice = await select({
|
|
73
|
+
message: `${runtimeCommandLabel(err.runtime)} is not ready for ${label}. What should Foreman do?`,
|
|
74
|
+
options,
|
|
75
|
+
});
|
|
76
|
+
if (isCancel(choice))
|
|
77
|
+
return "cancel";
|
|
78
|
+
return choice;
|
|
79
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { AgentRuntime } from "../runtimeAuth.js";
|
|
2
|
+
export declare const VALID_AGENTS: readonly ["claude", "codex"];
|
|
3
|
+
export declare function isAgentRuntime(value: string): value is AgentRuntime;
|
|
4
|
+
export declare function validateAgentRuntime(value: string): AgentRuntime;
|
|
5
|
+
export declare function otherRuntime(runtime: AgentRuntime): AgentRuntime;
|
|
6
|
+
export declare function runtimeDisplayName(runtime: AgentRuntime): string;
|
|
7
|
+
export declare function defaultAgentForProject(projectDir: string): AgentRuntime;
|
|
8
|
+
export declare function resolveAgentForProject(projectDir: string, explicitAgent?: string): AgentRuntime;
|
|
9
|
+
export declare function readRafiRuntimeTargets(projectDir: string): AgentRuntime[] | undefined;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { parse as parseYaml } from "yaml";
|
|
4
|
+
export const VALID_AGENTS = ["claude", "codex"];
|
|
5
|
+
export function isAgentRuntime(value) {
|
|
6
|
+
return VALID_AGENTS.includes(value);
|
|
7
|
+
}
|
|
8
|
+
export function validateAgentRuntime(value) {
|
|
9
|
+
if (isAgentRuntime(value))
|
|
10
|
+
return value;
|
|
11
|
+
throw new Error(`unknown agent "${value}" - choose: ${VALID_AGENTS.join(" | ")}`);
|
|
12
|
+
}
|
|
13
|
+
export function otherRuntime(runtime) {
|
|
14
|
+
return runtime === "claude" ? "codex" : "claude";
|
|
15
|
+
}
|
|
16
|
+
export function runtimeDisplayName(runtime) {
|
|
17
|
+
return runtime === "claude" ? "Claude" : "Codex";
|
|
18
|
+
}
|
|
19
|
+
export function defaultAgentForProject(projectDir) {
|
|
20
|
+
const targets = readRafiRuntimeTargets(projectDir);
|
|
21
|
+
return targets?.length === 1 ? targets[0] : "claude";
|
|
22
|
+
}
|
|
23
|
+
export function resolveAgentForProject(projectDir, explicitAgent) {
|
|
24
|
+
if (explicitAgent !== undefined)
|
|
25
|
+
return validateAgentRuntime(explicitAgent);
|
|
26
|
+
return defaultAgentForProject(projectDir);
|
|
27
|
+
}
|
|
28
|
+
export function readRafiRuntimeTargets(projectDir) {
|
|
29
|
+
const configPath = join(projectDir, "rafi-config.yaml");
|
|
30
|
+
if (!existsSync(configPath))
|
|
31
|
+
return undefined;
|
|
32
|
+
try {
|
|
33
|
+
const raw = parseYaml(readFileSync(configPath, "utf8"));
|
|
34
|
+
const targets = raw?.harness?.targets;
|
|
35
|
+
if (!Array.isArray(targets))
|
|
36
|
+
return undefined;
|
|
37
|
+
const out = [];
|
|
38
|
+
for (const target of targets) {
|
|
39
|
+
if ((target === "claude" || target === "codex") && !out.includes(target)) {
|
|
40
|
+
out.push(target);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
return out.length > 0 ? out : undefined;
|
|
44
|
+
}
|
|
45
|
+
catch {
|
|
46
|
+
return undefined;
|
|
47
|
+
}
|
|
48
|
+
}
|
package/dist/cli/start.js
CHANGED
|
@@ -11,6 +11,7 @@ import { Foreman, createPermissionHandler } from "../foreman.js";
|
|
|
11
11
|
import { printEvents } from "./events.js";
|
|
12
12
|
import { loadRoleBundle } from "../roles.js";
|
|
13
13
|
import { ensureRuntimeReadyForCommand } from "./runtimeAuthPrompt.js";
|
|
14
|
+
import { resolveAgentForProject } from "./runtimeSelection.js";
|
|
14
15
|
import { isTicketsInitialized, loadTicketsConfig, resolveTicketPaths } from "../tickets/config.js";
|
|
15
16
|
import { loadTickets } from "../tickets/ticketLoader.js";
|
|
16
17
|
import { StateDb } from "../tickets/stateDb.js";
|
|
@@ -97,7 +98,7 @@ export function buildStartCommand() {
|
|
|
97
98
|
.description("Enlist a builder and drive it through a batch of N steps.")
|
|
98
99
|
.argument("<project>", "path to the project directory the builder works in")
|
|
99
100
|
.requiredOption("-s, --steps <n>", "number of steps to drive")
|
|
100
|
-
.option("-a, --agent <agent>", "builder agent (claude | codex)"
|
|
101
|
+
.option("-a, --agent <agent>", "builder agent (claude | codex)")
|
|
101
102
|
.option("-m, --model <model>", "override the builder's model")
|
|
102
103
|
.option("-r, --resume <sessionId>", "resume a prior builder session")
|
|
103
104
|
.option("--continue", "resume the most recent logged session for this project")
|
|
@@ -119,10 +120,6 @@ export function buildStartCommand() {
|
|
|
119
120
|
if (!Number.isInteger(steps) || steps < 1) {
|
|
120
121
|
fail("--steps must be a positive integer");
|
|
121
122
|
}
|
|
122
|
-
const VALID_AGENTS = ["claude", "codex"];
|
|
123
|
-
if (!VALID_AGENTS.includes(opts.agent)) {
|
|
124
|
-
fail(`unknown agent "${opts.agent}" — choose: ${VALID_AGENTS.join(" | ")}`);
|
|
125
|
-
}
|
|
126
123
|
const VALID_EFFORT = ["low", "medium", "high", "xhigh"];
|
|
127
124
|
if (opts.effort && !VALID_EFFORT.includes(opts.effort)) {
|
|
128
125
|
fail(`unknown effort "${opts.effort}" — choose: ${VALID_EFFORT.join(" | ")}`);
|
|
@@ -130,6 +127,14 @@ export function buildStartCommand() {
|
|
|
130
127
|
const cwd = resolve(project);
|
|
131
128
|
if (!existsSync(cwd))
|
|
132
129
|
fail(`project directory not found: ${cwd}`);
|
|
130
|
+
let agent;
|
|
131
|
+
try {
|
|
132
|
+
agent = resolveAgentForProject(cwd, opts.agent);
|
|
133
|
+
}
|
|
134
|
+
catch (err) {
|
|
135
|
+
fail(err instanceof Error ? err.message : String(err));
|
|
136
|
+
}
|
|
137
|
+
let model = opts.model;
|
|
133
138
|
const configuredTrackerPath = isTicketsInitialized(cwd)
|
|
134
139
|
? loadTicketsConfig(cwd).paths.progressDoc
|
|
135
140
|
: undefined;
|
|
@@ -193,14 +198,13 @@ export function buildStartCommand() {
|
|
|
193
198
|
const stamp = new Date().toISOString().replace(/[:.]/g, "-");
|
|
194
199
|
const logPath = join(cwd, ".foreman", `${stamp}.jsonl`);
|
|
195
200
|
const log = new Log(logPath);
|
|
196
|
-
const agent = opts.agent;
|
|
197
201
|
const qaEnabled = opts.qa !== false && config.qa.enabled !== false;
|
|
198
202
|
const createBuilder = async (builderCwd, sessionId) => {
|
|
199
203
|
const builderPolicy = new PermissionPolicy(config.permissions, builderCwd);
|
|
200
204
|
const roleBundle = loadRoleBundle("builder", { projectDir: builderCwd });
|
|
201
205
|
const adapterOpts = {
|
|
202
206
|
cwd: builderCwd,
|
|
203
|
-
model
|
|
207
|
+
model,
|
|
204
208
|
resumeSessionId: sessionId,
|
|
205
209
|
permission: createPermissionHandler(builderPolicy, log),
|
|
206
210
|
effort: opts.effort,
|
|
@@ -224,7 +228,14 @@ export function buildStartCommand() {
|
|
|
224
228
|
if (opts.createPr) {
|
|
225
229
|
await ensureGitHubReadyForCreatePr(cwd, log, Boolean(opts.yes));
|
|
226
230
|
}
|
|
227
|
-
await ensureRuntimeReadyForCommand(cwd, agent,
|
|
231
|
+
const ready = await ensureRuntimeReadyForCommand(cwd, agent, {
|
|
232
|
+
label: "start",
|
|
233
|
+
yes: Boolean(opts.yes),
|
|
234
|
+
allowSwitch: !(opts.resume || opts.continue),
|
|
235
|
+
model,
|
|
236
|
+
});
|
|
237
|
+
agent = ready.runtime;
|
|
238
|
+
model = ready.model;
|
|
228
239
|
const ticketPaths = resolveTicketPaths(ticketsConfig, cwd);
|
|
229
240
|
const tickets = loadTickets(ticketPaths.tickets);
|
|
230
241
|
const db = new StateDb(ticketPaths.stateDb);
|
|
@@ -337,7 +348,7 @@ export function buildStartCommand() {
|
|
|
337
348
|
plan,
|
|
338
349
|
log,
|
|
339
350
|
agent,
|
|
340
|
-
model
|
|
351
|
+
model,
|
|
341
352
|
effort: opts.effort,
|
|
342
353
|
fast: opts.fast,
|
|
343
354
|
notificationsEnabled: config.notifications.enabled,
|
|
@@ -378,11 +389,18 @@ export function buildStartCommand() {
|
|
|
378
389
|
const failed = summaries.some((row) => row.buildStatus === "blocked" || row.buildStatus === "needs-human");
|
|
379
390
|
process.exit(failed ? 2 : 0);
|
|
380
391
|
}
|
|
381
|
-
await ensureRuntimeReadyForCommand(cwd, agent,
|
|
392
|
+
const ready = await ensureRuntimeReadyForCommand(cwd, agent, {
|
|
393
|
+
label: "start",
|
|
394
|
+
yes: Boolean(opts.yes),
|
|
395
|
+
allowSwitch: !(opts.resume || opts.continue),
|
|
396
|
+
model,
|
|
397
|
+
});
|
|
398
|
+
agent = ready.runtime;
|
|
399
|
+
model = ready.model;
|
|
382
400
|
const builder = await createBuilder(cwd, resumeSessionId);
|
|
383
401
|
const foreman = new Foreman(builder, log, config.notifications.enabled, qaEnabled, 3, cwd);
|
|
384
402
|
const modifiers = [
|
|
385
|
-
|
|
403
|
+
model ? `model=${model}` : null,
|
|
386
404
|
opts.effort ? `effort=${opts.effort}` : null,
|
|
387
405
|
opts.fast ? "fast" : null,
|
|
388
406
|
qaEnabled ? null : "qa=off",
|
package/dist/cli/tickets.js
CHANGED
|
@@ -14,6 +14,7 @@ import { isTicketsInitialized, loadTicketsConfig } from "../tickets/config.js";
|
|
|
14
14
|
import { importFromMarkdown } from "../tickets/importer.js";
|
|
15
15
|
import { formatValidationIssues } from "../tickets/validate.js";
|
|
16
16
|
import { ensureRuntimeReadyForCommand } from "./runtimeAuthPrompt.js";
|
|
17
|
+
import { resolveAgentForProject } from "./runtimeSelection.js";
|
|
17
18
|
function fail(msg) {
|
|
18
19
|
console.error(`foreman tickets: ${msg}`);
|
|
19
20
|
process.exit(1);
|
|
@@ -21,13 +22,7 @@ function fail(msg) {
|
|
|
21
22
|
function cwd(opts) {
|
|
22
23
|
return resolve(opts.project ?? ".");
|
|
23
24
|
}
|
|
24
|
-
const VALID_AGENTS = ["claude", "codex"];
|
|
25
25
|
const VALID_EFFORT = ["low", "medium", "high", "xhigh"];
|
|
26
|
-
function validateAgent(agent) {
|
|
27
|
-
if (!VALID_AGENTS.includes(agent)) {
|
|
28
|
-
fail(`unknown agent "${agent}" — choose: ${VALID_AGENTS.join(" | ")}`);
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
26
|
function validateEffort(effort) {
|
|
32
27
|
if (effort && !VALID_EFFORT.includes(effort)) {
|
|
33
28
|
fail(`unknown effort "${effort}" — choose: ${VALID_EFFORT.join(" | ")}`);
|
|
@@ -118,7 +113,7 @@ export function buildTicketsCommand() {
|
|
|
118
113
|
.command("populate")
|
|
119
114
|
.description("Ask a builder to populate .tickets/tickets.yaml from existing project ticket/backlog docs.")
|
|
120
115
|
.option("-p, --project <dir>", "project directory (default: cwd)")
|
|
121
|
-
.option("-a, --agent <agent>", "builder agent (claude | codex)"
|
|
116
|
+
.option("-a, --agent <agent>", "builder agent (claude | codex)")
|
|
122
117
|
.option("-m, --model <model>", "override the builder's model")
|
|
123
118
|
.option("--effort <level>", "reasoning effort level (low|medium|high|xhigh)")
|
|
124
119
|
.option("--sources <paths...>", "source hint files, folders, or globs to check first")
|
|
@@ -126,11 +121,17 @@ export function buildTicketsCommand() {
|
|
|
126
121
|
.option("-y, --yes", "skip confirmation prompt before letting the builder edit tickets")
|
|
127
122
|
.action(async (opts) => {
|
|
128
123
|
const dir = cwd(opts);
|
|
129
|
-
const agent = opts.agent;
|
|
130
|
-
validateAgent(agent);
|
|
131
124
|
validateEffort(opts.effort);
|
|
132
125
|
if (!existsSync(dir))
|
|
133
126
|
fail(`project directory not found: ${dir}`);
|
|
127
|
+
let agent;
|
|
128
|
+
try {
|
|
129
|
+
agent = resolveAgentForProject(dir, opts.agent);
|
|
130
|
+
}
|
|
131
|
+
catch (err) {
|
|
132
|
+
fail(err instanceof Error ? err.message : String(err));
|
|
133
|
+
}
|
|
134
|
+
let model = opts.model;
|
|
134
135
|
if (!isTicketsInitialized(dir)) {
|
|
135
136
|
fail(`ticket tracker is not initialized in ${dir}; run \`foreman tickets init --project ${dir}\` first`);
|
|
136
137
|
}
|
|
@@ -151,10 +152,16 @@ export function buildTicketsCommand() {
|
|
|
151
152
|
const logPath = makeLogPath(dir, "tickets-populate");
|
|
152
153
|
const log = new Log(logPath);
|
|
153
154
|
const policy = new PermissionPolicy(config.permissions, dir);
|
|
154
|
-
await ensureRuntimeReadyForCommand(dir, agent,
|
|
155
|
+
const ready = await ensureRuntimeReadyForCommand(dir, agent, {
|
|
156
|
+
label: "tickets populate",
|
|
157
|
+
yes: Boolean(opts.yes),
|
|
158
|
+
model,
|
|
159
|
+
});
|
|
160
|
+
agent = ready.runtime;
|
|
161
|
+
model = ready.model;
|
|
155
162
|
const adapterOpts = {
|
|
156
163
|
cwd: dir,
|
|
157
|
-
model
|
|
164
|
+
model,
|
|
158
165
|
permission: createPermissionHandler(policy, log),
|
|
159
166
|
effort: opts.effort,
|
|
160
167
|
fast: opts.fast,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ai-foreman",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"description": "An orchestrator that keeps Codex CLI / Claude Code builders moving through their step list unattended. Part of Rafi.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"type": "module",
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
},
|
|
15
15
|
"homepage": "https://github.com/ttante/rafi/tree/main/packages/ai-foreman#readme",
|
|
16
16
|
"bin": {
|
|
17
|
-
"ai-foreman": "
|
|
17
|
+
"ai-foreman": "dist/index.js"
|
|
18
18
|
},
|
|
19
19
|
"exports": {
|
|
20
20
|
"./cli/tickets.js": {
|