aiwcli 0.13.7 → 0.14.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.
Files changed (39) hide show
  1. package/README.md +11 -1
  2. package/dist/commands/launch.d.ts +8 -0
  3. package/dist/commands/launch.js +96 -5
  4. package/dist/templates/_shared/.claude/skills/codex/SKILL.md +42 -0
  5. package/dist/templates/_shared/.claude/skills/codex/prompt.md +10 -0
  6. package/dist/templates/_shared/lib-ts/agent-exec/backends/headless.ts +33 -0
  7. package/dist/templates/_shared/lib-ts/agent-exec/backends/index.ts +6 -0
  8. package/dist/templates/_shared/lib-ts/agent-exec/backends/tmux.ts +145 -0
  9. package/dist/templates/_shared/lib-ts/agent-exec/base-agent.ts +229 -0
  10. package/dist/templates/_shared/lib-ts/agent-exec/execution-backend.ts +50 -0
  11. package/dist/templates/_shared/lib-ts/agent-exec/index.ts +4 -0
  12. package/dist/templates/_shared/lib-ts/base/cli-args.ts +283 -0
  13. package/dist/templates/_shared/lib-ts/base/inference.ts +53 -47
  14. package/dist/templates/_shared/lib-ts/base/models.ts +16 -0
  15. package/dist/templates/_shared/lib-ts/base/preflight.ts +98 -0
  16. package/dist/templates/_shared/lib-ts/base/tmux-driver.ts +381 -0
  17. package/dist/templates/_shared/lib-ts/base/utils.ts +8 -0
  18. package/dist/templates/_shared/lib-ts/context/context-formatter.ts +35 -11
  19. package/dist/templates/_shared/lib-ts/types.ts +17 -0
  20. package/dist/templates/_shared/scripts/status_line.ts +57 -28
  21. package/dist/templates/_shared/skills/prompt-codex/CLAUDE.md +46 -0
  22. package/dist/templates/_shared/skills/prompt-codex/scripts/launch-codex.ts +254 -0
  23. package/dist/templates/cc-native/.claude/settings.json +121 -1
  24. package/dist/templates/cc-native/_cc-native/CLAUDE.md +73 -0
  25. package/dist/templates/cc-native/_cc-native/cc-native.config.json +2 -1
  26. package/dist/templates/cc-native/_cc-native/lib-ts/CLAUDE.md +70 -0
  27. package/dist/templates/cc-native/_cc-native/lib-ts/settings.ts +3 -1
  28. package/dist/templates/cc-native/_cc-native/lib-ts/types.ts +5 -10
  29. package/dist/templates/cc-native/_cc-native/plan-review/lib/agent-selection.ts +2 -2
  30. package/dist/templates/cc-native/_cc-native/plan-review/lib/preflight.ts +14 -80
  31. package/dist/templates/cc-native/_cc-native/plan-review/lib/reviewers/agent.ts +19 -7
  32. package/dist/templates/cc-native/_cc-native/plan-review/lib/reviewers/base/base-agent.ts +4 -215
  33. package/dist/templates/cc-native/_cc-native/plan-review/lib/reviewers/index.ts +1 -1
  34. package/dist/templates/cc-native/_cc-native/plan-review/lib/reviewers/providers/claude-agent.ts +9 -39
  35. package/dist/templates/cc-native/_cc-native/plan-review/lib/reviewers/providers/codex-agent.ts +19 -21
  36. package/dist/templates/cc-native/_cc-native/plan-review/lib/reviewers/providers/gemini-agent.ts +2 -1
  37. package/dist/templates/cc-native/_cc-native/plan-review/lib/reviewers/providers/orchestrator-claude-agent.ts +13 -15
  38. package/oclif.manifest.json +21 -3
  39. package/package.json +1 -1
@@ -2,97 +2,29 @@
2
2
  * Preflight health checks for plan review agents.
3
3
  * Validates provider+model combos work before committing agents to them.
4
4
  * Runs minimal "ping" requests in parallel per unique provider:model combo.
5
+ *
6
+ * Uses shared checkProviderModel() from _shared/lib-ts/base/preflight.ts.
7
+ * This module provides the batch orchestrator and provider registry specific
8
+ * to the plan review pipeline.
5
9
  */
6
10
 
7
- import { logInfo, logWarn, logDebug } from "../../../_shared/lib-ts/base/logger.js";
8
- import { findExecutable, execFileAsync, getInternalSubprocessEnv } from "../../../_shared/lib-ts/base/subprocess-utils.js";
11
+ import { preflightCommandConfig } from "../../../_shared/lib-ts/base/cli-args.js";
12
+ import { logInfo, logWarn } from "../../../_shared/lib-ts/base/logger.js";
13
+ import { checkProviderModel, type PreflightCommandConfig } from "../../../_shared/lib-ts/base/preflight.js";
9
14
  import type { ModelsConfig, PreflightCheckResult, PreflightReport } from "../../lib-ts/types.js";
10
- import { claudePreflightArgs, CLAUDE_PREFLIGHT_INPUT } from "./reviewers/providers/claude-agent.js";
11
- import { codexPreflightArgs, CODEX_PREFLIGHT_INPUT } from "./reviewers/providers/codex-agent.js";
12
15
 
13
16
  const HOOK = "preflight";
14
17
  const DEFAULT_TIMEOUT_MS = 15000;
15
18
 
16
19
  // ---------------------------------------------------------------------------
17
- // Provider Registry
20
+ // Provider Registry (built from centralized cli-args)
18
21
  // ---------------------------------------------------------------------------
19
22
 
20
- interface PreflightCommandConfig {
21
- cliName: string;
22
- buildArgs: (model: string) => string[];
23
- input: string;
24
- }
25
-
26
23
  const PREFLIGHT_COMMANDS: Record<string, PreflightCommandConfig> = {
27
- claude: { cliName: "claude", buildArgs: claudePreflightArgs, input: CLAUDE_PREFLIGHT_INPUT },
28
- codex: { cliName: "codex", buildArgs: codexPreflightArgs, input: CODEX_PREFLIGHT_INPUT },
24
+ claude: preflightCommandConfig("claude"),
25
+ codex: preflightCommandConfig("codex"),
29
26
  };
30
27
 
31
- // ---------------------------------------------------------------------------
32
- // Error Classification
33
- // ---------------------------------------------------------------------------
34
-
35
- function classifyError(stderr: string, exitCode: number | null, killed: boolean, signal: string | null): string {
36
- if (killed || signal === "SIGTERM") return "Preflight timed out";
37
- if (/model.*not found|not available/i.test(stderr)) return "Model not available for this account";
38
- if (/rate limit|429/i.test(stderr)) return "Rate limited";
39
- if (/auth|api key|401/i.test(stderr)) return "Authentication failed";
40
- if (/quota|billing/i.test(stderr)) return "Quota/billing issue";
41
- return `Exit code ${exitCode}`;
42
- }
43
-
44
- // ---------------------------------------------------------------------------
45
- // Single Check
46
- // ---------------------------------------------------------------------------
47
-
48
- async function checkProviderModel(
49
- provider: string,
50
- model: string,
51
- timeoutMs: number,
52
- ): Promise<PreflightCheckResult> {
53
- const config = PREFLIGHT_COMMANDS[provider];
54
- if (!config) {
55
- return { provider, model, available: false, latencyMs: 0, error: `Unknown provider: ${provider}` };
56
- }
57
-
58
- const cliPath = findExecutable(config.cliName);
59
- if (!cliPath) {
60
- return { provider, model, available: false, latencyMs: 0, error: `CLI '${config.cliName}' not found on PATH` };
61
- }
62
-
63
- const start = Date.now();
64
- try {
65
- const env = getInternalSubprocessEnv();
66
- const result = await execFileAsync(cliPath, config.buildArgs(model), {
67
- input: config.input,
68
- timeout: timeoutMs,
69
- env: env as Record<string, string>,
70
- maxBuffer: 1 * 1024 * 1024,
71
- shell: process.platform === "win32",
72
- });
73
-
74
- const latencyMs = Date.now() - start;
75
-
76
- if (result.killed || result.signal === "SIGTERM") {
77
- return { provider, model, available: false, latencyMs, error: "Preflight timed out" };
78
- }
79
-
80
- if (result.exitCode !== 0) {
81
- const error = classifyError(result.stderr, result.exitCode, result.killed, result.signal);
82
- logWarn(HOOK, `${provider}:${model} failed: ${error} (stderr: ${result.stderr.slice(-200)})`);
83
- return { provider, model, available: false, latencyMs, error };
84
- }
85
-
86
- logDebug(HOOK, `${provider}:${model} passed (${latencyMs}ms)`);
87
- return { provider, model, available: true, latencyMs };
88
- } catch (err) {
89
- const latencyMs = Date.now() - start;
90
- const error = err instanceof Error ? err.message : String(err);
91
- logWarn(HOOK, `${provider}:${model} exception: ${error}`);
92
- return { provider, model, available: false, latencyMs, error };
93
- }
94
- }
95
-
96
28
  // ---------------------------------------------------------------------------
97
29
  // Run All Checks
98
30
  // ---------------------------------------------------------------------------
@@ -130,9 +62,11 @@ export async function runPreflight(
130
62
 
131
63
  logInfo(HOOK, `Checking ${checks.length} provider:model combo(s): ${checks.map(c => `${c.provider}:${c.model}`).join(", ")}`);
132
64
 
133
- // Run all checks in parallel
65
+ // Run all checks in parallel (pass provider-specific config from registry)
134
66
  const results = await Promise.all(
135
- checks.map(({ provider, model }) => checkProviderModel(provider, model, effectiveTimeout)),
67
+ checks.map(({ provider, model }) =>
68
+ checkProviderModel(provider, model, PREFLIGHT_COMMANDS[provider]!, effectiveTimeout, HOOK),
69
+ ),
136
70
  );
137
71
 
138
72
  // Build available map
@@ -4,19 +4,24 @@
4
4
  * See cc-native-plan-review-spec.md §4.10
5
5
  */
6
6
 
7
+ import type { ExecutionBackend } from "../../../../_shared/lib-ts/agent-exec/execution-backend.js";
8
+ import { logWarn } from "../../../../_shared/lib-ts/base/logger.js";
9
+ import { debugLog, debugRaw } from "../../../lib-ts/debug.js";
10
+ import type { AgentConfig, ReviewerResult, ReviewOptions } from "../../../lib-ts/types.js";
7
11
  import { ClaudeAgent } from "./providers/claude-agent.js";
8
12
  import { CodexAgent } from "./providers/codex-agent.js";
9
13
  import { GeminiAgent } from "./providers/gemini-agent.js";
10
14
  import type { Reviewer } from "./types.js";
11
15
  import { makeResult } from "./types.js";
12
- import { logWarn } from "../../../../_shared/lib-ts/base/logger.js";
13
- import type { AgentConfig, ReviewerResult, ReviewOptions } from "../../../lib-ts/types.js";
14
16
 
15
17
  /**
16
18
  * Agent reviewer — runs a CLI instance with a custom persona.
17
19
  */
18
20
  export class AgentReviewer implements Reviewer {
19
- constructor(private agent: AgentConfig) {}
21
+ constructor(
22
+ private agent: AgentConfig,
23
+ private backend?: ExecutionBackend,
24
+ ) {}
20
25
 
21
26
  async review(
22
27
  plan: string,
@@ -30,6 +35,7 @@ export class AgentReviewer implements Reviewer {
30
35
  options.timeout,
31
36
  options.context_path,
32
37
  options.session_name ?? "unknown",
38
+ this.backend,
33
39
  );
34
40
  }
35
41
  }
@@ -46,21 +52,27 @@ export async function runAgentReview(
46
52
  timeout: number,
47
53
  contextPath?: string,
48
54
  sessionName = "unknown",
55
+ backend?: ExecutionBackend,
49
56
  ): Promise<ReviewerResult> {
50
57
  try {
51
- let reviewer;
58
+ const config = {
59
+ agent, schema, timeout, contextPath, sessionName,
60
+ debugLogger: { log: debugLog, raw: debugRaw },
61
+ };
62
+
63
+ let reviewer: ClaudeAgent | CodexAgent | GeminiAgent;
52
64
 
53
65
  switch (agent.provider) {
54
66
  case "codex": {
55
- reviewer = new CodexAgent(agent, schema, timeout, contextPath, sessionName);
67
+ reviewer = new CodexAgent(config, backend);
56
68
  break;
57
69
  }
58
70
  case "gemini": {
59
- reviewer = new GeminiAgent(agent, schema, timeout, contextPath, sessionName);
71
+ reviewer = new GeminiAgent(config, backend);
60
72
  break;
61
73
  }
62
74
  default: {
63
- reviewer = new ClaudeAgent(agent, schema, timeout, contextPath, sessionName);
75
+ reviewer = new ClaudeAgent(config, backend);
64
76
  break;
65
77
  }
66
78
  }
@@ -1,218 +1,7 @@
1
1
  /**
2
- * Abstract base class for CLI-based agent reviewers.
3
- * Implements template method pattern for subprocess execution flow.
4
- * Provider-specific implementations (Claude, Codex, Gemini) extend this class.
2
+ * Re-export shim BaseCliAgent now lives in _shared/lib-ts/agent-exec/base-agent.ts.
3
+ * This file preserves all existing import paths for provider implementations.
5
4
  */
6
5
 
7
- import { logDebug, logInfo, logWarn, logError } from "../../../../../_shared/lib-ts/base/logger.js";
8
- import { getInternalSubprocessEnv, findExecutable, execFileAsync, normalizePathForCli } from "../../../../../_shared/lib-ts/base/subprocess-utils.js";
9
- import { debugLog, debugRaw } from "../../../../lib-ts/debug.js";
10
- import type { AgentConfig } from "../../../../lib-ts/types.js";
11
-
12
- /** Result from execFileAsync */
13
- export interface ExecResult {
14
- stdout: string;
15
- stderr: string;
16
- exitCode: number | null;
17
- signal: string | null;
18
- killed: boolean;
19
- }
20
-
21
- /**
22
- * Abstract base class for all CLI agent subprocess invocations.
23
- * Parameterized over return type T — ReviewerResult for reviewers,
24
- * OrchestratorResult for the orchestrator.
25
- * Subclasses implement provider-specific details.
26
- */
27
- export abstract class BaseCliAgent<T> {
28
- protected agent: AgentConfig;
29
- protected contextPath?: string;
30
- protected schema: Record<string, unknown>;
31
- protected sessionName: string;
32
- protected timeout: number;
33
-
34
- constructor(
35
- agent: AgentConfig,
36
- schema: Record<string, unknown>,
37
- timeout: number,
38
- contextPath?: string,
39
- sessionName = "unknown",
40
- ) {
41
- this.agent = agent;
42
- this.schema = schema;
43
- this.timeout = timeout;
44
- this.contextPath = contextPath;
45
- this.sessionName = sessionName;
46
- }
47
-
48
- /** Build the command-line arguments for the CLI */
49
- protected abstract buildCliArgs(): string[];
50
-
51
- // ─── Abstract Methods (Subclass Implements) ────────────────────────────
52
-
53
- /** Build the stdin prompt for the CLI */
54
- protected abstract buildPrompt(plan: string): string;
55
-
56
- /** Optional cleanup after subprocess execution */
57
- protected async cleanup(): Promise<void> {
58
- // Default: no-op. Subclasses override if needed (e.g., Codex temp files).
59
- }
60
-
61
- /** Coerce parsed JSON into the result type T */
62
- protected abstract coerceResult(obj: Record<string, unknown> | null, raw: string, err: string): T;
63
-
64
- /** Extract stdout/stderr from subprocess result. Override for file-based output (Codex). */
65
- protected extractOutput(result: ExecResult): { raw: string; err: string } {
66
- return {
67
- raw: result.stdout.trim(),
68
- err: result.stderr.trim(),
69
- };
70
- }
71
-
72
- /** Find the CLI executable. Override for custom search logic. */
73
- protected findCli(): string | null {
74
- return findExecutable(this.getCliName());
75
- }
76
-
77
- // ─── Template Methods (Subclass Can Override) ──────────────────────────
78
-
79
- /** Get the CLI executable name (e.g., "claude", "codex") */
80
- protected abstract getCliName(): string;
81
-
82
- /** Get default error message for coerceToReview */
83
- protected getDefaultErrorMessage(): string {
84
- return `Retry or check ${this.getCliName()} configuration.`;
85
- }
86
-
87
- /** Handle non-zero exit with no output */
88
- protected handleExitError(result: ExecResult): T {
89
- const msg = `${this.agent.name} failed to run (exit ${result.exitCode})`;
90
- logError(this.agent.name, `Process exited with code ${result.exitCode} and no output`);
91
- return this.makeErrorResult("error", msg);
92
- }
93
-
94
- /** Handle timeout scenario */
95
- protected handleTimeout(): T {
96
- const msg = `${this.getCliName()} TIMEOUT after ${this.timeout}s`;
97
- logWarn(this.agent.name, msg);
98
- return this.makeErrorResult("error", `${this.agent.name} timed out after ${this.timeout}s`);
99
- }
100
-
101
- // ─── Shared Infrastructure ──────────────────────────────────────────────
102
-
103
- /** Log parsed JSON result */
104
- protected logParsedResult(obj: Record<string, unknown> | null): void {
105
- if (this.contextPath && obj) {
106
- debugLog(this.contextPath, this.sessionName, `agent:${this.agent.name}`, "parsed_result", {
107
- parsed_keys: Object.keys(obj),
108
- verdict: obj.verdict ?? null,
109
- has_summary: Boolean(obj.summary),
110
- issues_count: Array.isArray(obj.issues) ? (obj.issues as unknown[]).length : 0,
111
- });
112
- }
113
-
114
- if (obj) {
115
- logInfo(this.agent.name, `Parsed JSON successfully, verdict: ${obj.verdict ?? "N/A"}`);
116
- } else {
117
- logWarn(this.agent.name, "Failed to parse JSON from output");
118
- }
119
- }
120
-
121
- /** Log subprocess execution results */
122
- protected logSubprocessResult(result: ExecResult, raw: string, err: string): void {
123
- logDebug(this.agent.name, `Exit code: ${result.exitCode}`);
124
- logDebug(this.agent.name, `stdout length: ${raw.length} chars`);
125
- if (err) logDebug(this.agent.name, `stderr: ${err.slice(0, 500)}`);
126
-
127
- // Debug logging
128
- if (this.contextPath) {
129
- debugRaw(this.contextPath, this.sessionName, `agent:${this.agent.name}`, "stdout", raw);
130
- if (err) {
131
- debugRaw(this.contextPath, this.sessionName, `agent:${this.agent.name}`, "stderr", err);
132
- }
133
- debugLog(this.contextPath, this.sessionName, `agent:${this.agent.name}`, "subprocess_info", {
134
- exit_code: result.exitCode,
135
- stdout_len: raw.length,
136
- stderr_len: err.length,
137
- model: this.agent.model,
138
- provider: this.agent.provider,
139
- timeout: this.timeout,
140
- });
141
- }
142
-
143
- if (raw) logDebug(this.agent.name, `stdout preview: ${raw.slice(0, 500)}`);
144
- }
145
-
146
- /** Construct a T for error/skip/timeout scenarios. Subclasses define shape. */
147
- protected abstract makeErrorResult(type: "skip" | "error", message: string): T;
148
-
149
- /** Create skip result when CLI not found */
150
- protected makeSkipResult(reason: string): T {
151
- logWarn(this.agent.name, reason);
152
- return this.makeErrorResult("skip", reason);
153
- }
154
-
155
- /** Parse JSON from CLI output */
156
- protected abstract parseOutput(raw: string, result: ExecResult): Record<string, unknown> | null;
157
-
158
- /**
159
- * Template method - orchestrates the review flow.
160
- * Subclasses override abstract methods to customize behavior.
161
- */
162
- async review(plan: string): Promise<T> {
163
- // 1. Find CLI executable
164
- const cliPath = this.findCli();
165
- if (!cliPath) {
166
- return this.makeSkipResult(`${this.getCliName()} CLI not found on PATH`);
167
- }
168
-
169
- logDebug(this.agent.name, `Found ${this.getCliName()} CLI at: ${cliPath}`);
170
-
171
- // 2. Build prompt and args (provider-specific)
172
- const prompt = this.buildPrompt(plan);
173
- const args = this.buildCliArgs();
174
-
175
- logInfo(this.agent.name, `Running ${this.getCliName()} with model: ${this.agent.model}, timeout: ${this.timeout}s`);
176
-
177
- // 3. Execute subprocess
178
- const env = getInternalSubprocessEnv();
179
- const normalizedCliPath = normalizePathForCli(cliPath);
180
- const result = await execFileAsync(normalizedCliPath, args, {
181
- input: prompt,
182
- timeout: this.timeout * 1000,
183
- env: env as Record<string, string>,
184
- maxBuffer: 10 * 1024 * 1024,
185
- shell: process.platform === "win32",
186
- });
187
-
188
- // 4. Handle timeout
189
- if (result.killed || result.signal === "SIGTERM") {
190
- return this.handleTimeout();
191
- }
192
-
193
- // 5. Extract output (provider-specific)
194
- const { raw, err } = this.extractOutput(result);
195
-
196
- // 6. Handle exit errors
197
- if (!raw && !err && result.exitCode !== 0) {
198
- return this.handleExitError(result);
199
- }
200
-
201
- // 7. Log subprocess results
202
- this.logSubprocessResult(result, raw, err);
203
-
204
- // 8. Parse JSON output (provider-specific)
205
- const obj = this.parseOutput(raw, result);
206
-
207
- // 9. Log parsed result
208
- this.logParsedResult(obj);
209
-
210
- // 10. Coerce to result type T (provider-specific)
211
- const coerced = this.coerceResult(obj, raw, err);
212
-
213
- // 11. Cleanup (optional override)
214
- await this.cleanup();
215
-
216
- return coerced;
217
- }
218
- }
6
+ export { BaseCliAgent, type AgentExecutionConfig, type AgentDebugLogger } from "../../../../../_shared/lib-ts/agent-exec/base-agent.js";
7
+ export type { ExecutionResult as ExecResult } from "../../../../../_shared/lib-ts/agent-exec/execution-backend.js";
@@ -4,7 +4,7 @@
4
4
  */
5
5
 
6
6
  export { AgentReviewer, runAgentReview } from "./agent.js";
7
- export { BaseCliAgent } from "./base/base-agent.js";
7
+ export { BaseCliAgent, type AgentExecutionConfig, type AgentDebugLogger } from "./base/base-agent.js";
8
8
  export { ClaudeAgent } from "./providers/claude-agent.js";
9
9
  export { CodexAgent } from "./providers/codex-agent.js";
10
10
  export { GeminiAgent } from "./providers/gemini-agent.js";
@@ -3,7 +3,8 @@
3
3
  * Uses claude CLI with --json-schema and --system-prompt flags.
4
4
  */
5
5
 
6
- import { shellQuoteWin } from "../../../../../_shared/lib-ts/base/subprocess-utils.js";
6
+ import { buildCliInvocation, reviewSpec } from "../../../../../_shared/lib-ts/base/cli-args.js";
7
+ import type { ExecutionResult } from "../../../../../_shared/lib-ts/agent-exec/execution-backend.js";
7
8
  import { parseCliOutput } from "../../../../lib-ts/cli-output-parser.js";
8
9
  import { coerceToReview } from "../../../../lib-ts/json-parser.js";
9
10
  import type { ReviewerResult } from "../../../../lib-ts/types.js";
@@ -15,46 +16,15 @@ import { makeResult } from "../types.js";
15
16
  * Claude CLI-based agent reviewer.
16
17
  * Extends BaseCliAgent with Claude-specific prompt and argument handling.
17
18
  */
18
- // ---------------------------------------------------------------------------
19
- // Preflight (standalone — no instance needed)
20
- // ---------------------------------------------------------------------------
21
-
22
- export const CLAUDE_PREFLIGHT_INPUT = "Respond with exactly: ok";
23
-
24
- export function claudePreflightArgs(model: string): string[] {
25
- return [
26
- "--model", model,
27
- "--max-turns", "1",
28
- "--output-format", "json",
29
- "--setting-sources", process.platform === "win32" ? '""' : "",
30
- "-p",
31
- "--no-session-persistence",
32
- ];
33
- }
34
-
35
- // ---------------------------------------------------------------------------
36
- // Agent Class
37
- // ---------------------------------------------------------------------------
38
-
39
19
  export class ClaudeAgent extends BaseCliAgent<ReviewerResult> {
40
20
  protected buildCliArgs(): string[] {
41
- const schemaJson = JSON.stringify(this.schema);
42
- const cmdArgs = [
43
- "--model", this.agent.model,
44
- "--output-format", "json",
45
- "--json-schema", shellQuoteWin(schemaJson),
46
- "--max-turns", "3",
47
- "--setting-sources", process.platform === "win32" ? '""' : "",
48
- "-p",
49
- "--no-session-persistence", // Prevent subprocess from creating session records
50
- ];
51
-
52
- if (this.agent.system_prompt) {
53
- const fullPrompt = AGENT_REVIEW_PROMPT_PREFIX + "\n\n---\n\n" + this.agent.system_prompt;
54
- cmdArgs.push("--system-prompt", shellQuoteWin(fullPrompt));
55
- }
21
+ const fullPrompt = this.agent.system_prompt
22
+ ? AGENT_REVIEW_PROMPT_PREFIX + "\n\n---\n\n" + this.agent.system_prompt
23
+ : undefined;
56
24
 
57
- return cmdArgs;
25
+ return buildCliInvocation(
26
+ reviewSpec("claude", this.agent.model, this.schema, fullPrompt),
27
+ ).args;
58
28
  }
59
29
 
60
30
  protected buildPrompt(plan: string): string {
@@ -81,7 +51,7 @@ ${plan}
81
51
  return makeResult(this.agent.name, false, type, {}, "", message);
82
52
  }
83
53
 
84
- protected parseOutput(raw: string, _result: unknown): Record<string, unknown> | null {
54
+ protected parseOutput(raw: string, _result: ExecutionResult): Record<string, unknown> | null {
85
55
  return parseCliOutput(raw, ["verdict", "summary"]);
86
56
  }
87
57
  }
@@ -7,25 +7,17 @@ import * as fs from "node:fs";
7
7
  import * as os from "node:os";
8
8
  import * as path from "node:path";
9
9
 
10
+ import { buildCliInvocation } from "../../../../../_shared/lib-ts/base/cli-args.js";
11
+ import type { ExecutionResult } from "../../../../../_shared/lib-ts/agent-exec/execution-backend.js";
10
12
  import { logDebug, logWarn } from "../../../../../_shared/lib-ts/base/logger.js";
11
- import { getInternalSubprocessEnv, execFileAsync, normalizePathForCli, shellQuoteWin } from "../../../../../_shared/lib-ts/base/subprocess-utils.js";
13
+ import { getInternalSubprocessEnv, normalizePathForCli, shellQuoteWin } from "../../../../../_shared/lib-ts/base/subprocess-utils.js";
12
14
  import { debugLog, debugRaw } from "../../../../lib-ts/debug.js";
13
15
  import { parseJsonMaybe, coerceToReview } from "../../../../lib-ts/json-parser.js";
14
16
  import type { ReviewerResult } from "../../../../lib-ts/types.js";
15
- import { BaseCliAgent, type ExecResult } from "../base/base-agent.js";
17
+ import { BaseCliAgent } from "../base/base-agent.js";
16
18
  import { AGENT_REVIEW_PROMPT_PREFIX } from "../schemas.js";
17
19
  import { makeResult } from "../types.js";
18
20
 
19
- // ---------------------------------------------------------------------------
20
- // Preflight (standalone — no instance needed)
21
- // ---------------------------------------------------------------------------
22
-
23
- export const CODEX_PREFLIGHT_INPUT = "Respond with exactly: ok";
24
-
25
- export function codexPreflightArgs(model: string): string[] {
26
- return ["exec", "--sandbox", "read-only", "--model", model, "-"];
27
- }
28
-
29
21
  // ---------------------------------------------------------------------------
30
22
  // Agent Class
31
23
  // ---------------------------------------------------------------------------
@@ -52,11 +44,14 @@ export class CodexAgent extends BaseCliAgent<ReviewerResult> {
52
44
  const normalizedSchema = shellQuoteWin(normalizePathForCli(schemaPath));
53
45
  const normalizedOut = shellQuoteWin(normalizePathForCli(outPath));
54
46
 
55
- const cmdArgs = ["exec", "--sandbox", "read-only"];
56
- if (this.agent.model) cmdArgs.push("--model", this.agent.model);
57
- cmdArgs.push("--output-schema", normalizedSchema, "-o", normalizedOut, "-");
58
-
59
- return cmdArgs;
47
+ return buildCliInvocation({
48
+ provider: "codex",
49
+ model: this.agent.model,
50
+ mode: "structured",
51
+ sandbox: "read-only",
52
+ outputSchemaPath: normalizedSchema,
53
+ outputFilePath: normalizedOut,
54
+ }).args;
60
55
  }
61
56
 
62
57
  protected buildPrompt(plan: string): string {
@@ -91,7 +86,7 @@ export class CodexAgent extends BaseCliAgent<ReviewerResult> {
91
86
  return makeResult(this.agent.name, ok, verdict, norm, raw, err);
92
87
  }
93
88
 
94
- protected extractOutput(result: ExecResult): { raw: string; err: string } {
89
+ protected extractOutput(result: ExecutionResult): { raw: string; err: string } {
95
90
  const outPath = this.getOutputPath();
96
91
  let raw = "";
97
92
  const outExists = fs.existsSync(outPath);
@@ -133,7 +128,7 @@ export class CodexAgent extends BaseCliAgent<ReviewerResult> {
133
128
  return makeResult(this.agent.name, false, type, {}, "", message);
134
129
  }
135
130
 
136
- protected parseOutput(raw: string, result: ExecResult): Record<string, unknown> | null {
131
+ protected parseOutput(raw: string, result: ExecutionResult): Record<string, unknown> | null {
137
132
  return parseJsonMaybe(raw) ?? parseJsonMaybe(result.stdout);
138
133
  }
139
134
 
@@ -156,10 +151,13 @@ export class CodexAgent extends BaseCliAgent<ReviewerResult> {
156
151
 
157
152
  try {
158
153
  const env = getInternalSubprocessEnv();
159
- const result = await execFileAsync(cliPath, args, {
154
+ const normalizedCliPath = normalizePathForCli(cliPath);
155
+ const result = await this.backend.execute({
156
+ cliPath: normalizedCliPath,
157
+ args,
160
158
  input: prompt,
161
- timeout: this.timeout * 1000,
162
159
  env: env as Record<string, string>,
160
+ timeoutMs: this.timeout * 1000,
163
161
  maxBuffer: 10 * 1024 * 1024,
164
162
  shell: process.platform === "win32",
165
163
  });
@@ -3,6 +3,7 @@
3
3
  * Placeholder for future implementation.
4
4
  */
5
5
 
6
+ import type { ExecutionResult } from "../../../../../_shared/lib-ts/agent-exec/execution-backend.js";
6
7
  import type { ReviewerResult } from "../../../../lib-ts/types.js";
7
8
  import { BaseCliAgent } from "../base/base-agent.js";
8
9
  import { makeResult } from "../types.js";
@@ -33,7 +34,7 @@ export class GeminiAgent extends BaseCliAgent<ReviewerResult> {
33
34
  return makeResult(this.agent.name, false, type, {}, "", message);
34
35
  }
35
36
 
36
- protected parseOutput(_raw: string, _result: unknown): Record<string, unknown> | null {
37
+ protected parseOutput(_raw: string, _result: ExecutionResult): Record<string, unknown> | null {
37
38
  throw new Error("GeminiAgent not implemented");
38
39
  }
39
40
  }
@@ -3,8 +3,11 @@
3
3
  * Analyzes plan complexity and selects reviewer agents via Claude CLI.
4
4
  */
5
5
 
6
+ import { buildCliInvocation, reviewSpec } from "../../../../../_shared/lib-ts/base/cli-args.js";
7
+ import type { ExecutionBackend } from "../../../../../_shared/lib-ts/agent-exec/execution-backend.js";
8
+ import type { ExecutionResult } from "../../../../../_shared/lib-ts/agent-exec/execution-backend.js";
6
9
  import { logDebug } from "../../../../../_shared/lib-ts/base/logger.js";
7
- import { shellQuoteWin } from "../../../../../_shared/lib-ts/base/subprocess-utils.js";
10
+ import { debugLog, debugRaw } from "../../../../lib-ts/debug.js";
8
11
  import { parseCliOutput } from "../../../../lib-ts/cli-output-parser.js";
9
12
  import type { AgentConfig, OrchestratorResult, ComplexityCategory } from "../../../../lib-ts/types.js";
10
13
  import { BaseCliAgent } from "../base/base-agent.js";
@@ -51,6 +54,7 @@ export class OrchestratorClaudeAgent extends BaseCliAgent<OrchestratorResult> {
51
54
  timeout: number,
52
55
  contextPath?: string,
53
56
  sessionName?: string,
57
+ backend?: ExecutionBackend,
54
58
  ) {
55
59
  // Build schema dynamically based on valid agent names
56
60
  const nonMandatory = agentLibrary.filter(
@@ -63,7 +67,10 @@ export class OrchestratorClaudeAgent extends BaseCliAgent<OrchestratorResult> {
63
67
  ? buildOrchestratorSchema(validNames, categories)
64
68
  : ORCHESTRATOR_SCHEMA;
65
69
 
66
- super(agent, schema, timeout, contextPath, sessionName);
70
+ super({
71
+ agent, schema, timeout, contextPath, sessionName,
72
+ debugLogger: { log: debugLog, raw: debugRaw },
73
+ }, backend);
67
74
 
68
75
  this.nonMandatory = nonMandatory;
69
76
  this.validNames = validNames;
@@ -79,8 +86,6 @@ export class OrchestratorClaudeAgent extends BaseCliAgent<OrchestratorResult> {
79
86
  }
80
87
 
81
88
  protected buildCliArgs(): string[] {
82
- const schemaJson = JSON.stringify(this.schema);
83
-
84
89
  const systemPrompt = `You are a plan orchestrator for code review. Your job is to analyze plans and select appropriate reviewer agents.
85
90
 
86
91
  You MUST call StructuredOutput immediately with your analysis. Do NOT ask questions or use any other tools.
@@ -91,16 +96,9 @@ When selecting agents:
91
96
  - Only select agents whose categories match the plan category
92
97
  - Fewer agents for simple plans, more for complex plans`;
93
98
 
94
- return [
95
- "--model", this.agent.model,
96
- "--output-format", "json",
97
- "--json-schema", shellQuoteWin(schemaJson),
98
- "--max-turns", "3",
99
- "--setting-sources", process.platform === "win32" ? '""' : "",
100
- "--system-prompt", shellQuoteWin(systemPrompt),
101
- "-p",
102
- "--no-session-persistence", // Prevent subprocess from creating session records
103
- ];
99
+ return buildCliInvocation(
100
+ reviewSpec("claude", this.agent.model, this.schema, systemPrompt),
101
+ ).args;
104
102
  }
105
103
 
106
104
  protected buildPrompt(plan: string): string {
@@ -180,7 +178,7 @@ Call StructuredOutput now with: complexity, category, selectedAgents, reasoning`
180
178
  );
181
179
  }
182
180
 
183
- protected parseOutput(raw: string, _result: unknown): Record<string, unknown> | null {
181
+ protected parseOutput(raw: string, _result: ExecutionResult): Record<string, unknown> | null {
184
182
  return parseCliOutput(raw);
185
183
  }
186
184