aiwcli 0.11.1 → 0.12.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/dist/commands/clear.d.ts +8 -0
- package/dist/commands/clear.js +86 -0
- package/dist/lib/bmad-installer.d.ts +2 -27
- package/dist/lib/bmad-installer.js +3 -43
- package/dist/lib/claude-settings-types.d.ts +2 -1
- package/dist/lib/env-compat.d.ts +0 -8
- package/dist/lib/env-compat.js +0 -12
- package/dist/lib/git/index.d.ts +0 -1
- package/dist/lib/gitignore-manager.d.ts +0 -2
- package/dist/lib/gitignore-manager.js +1 -1
- package/dist/lib/hooks-merger.d.ts +1 -15
- package/dist/lib/hooks-merger.js +1 -1
- package/dist/lib/index.d.ts +3 -7
- package/dist/lib/index.js +3 -11
- package/dist/lib/output.d.ts +2 -1
- package/dist/lib/settings-hierarchy.d.ts +1 -13
- package/dist/lib/settings-hierarchy.js +1 -1
- package/dist/lib/template-installer.d.ts +5 -9
- package/dist/lib/template-installer.js +3 -13
- package/dist/lib/template-linter.d.ts +3 -10
- package/dist/lib/template-linter.js +2 -2
- package/dist/lib/template-resolver.d.ts +6 -0
- package/dist/lib/template-resolver.js +10 -0
- package/dist/lib/template-settings-reconstructor.d.ts +1 -1
- package/dist/lib/template-settings-reconstructor.js +17 -24
- package/dist/lib/terminal.d.ts +3 -14
- package/dist/lib/terminal.js +0 -4
- package/dist/lib/version.d.ts +2 -11
- package/dist/lib/version.js +3 -3
- package/dist/lib/windsurf-hooks-merger.d.ts +1 -15
- package/dist/lib/windsurf-hooks-merger.js +1 -1
- package/dist/templates/_shared/.codex/workflows/handoff.md +1 -1
- package/dist/templates/_shared/.windsurf/workflows/handoff.md +1 -1
- package/dist/templates/_shared/hooks-ts/session_end.ts +75 -4
- package/dist/templates/_shared/hooks-ts/session_start.ts +11 -13
- package/dist/templates/_shared/hooks-ts/user_prompt_submit.ts +6 -8
- package/dist/templates/_shared/lib-ts/CLAUDE.md +56 -7
- package/dist/templates/_shared/lib-ts/base/hook-utils.ts +176 -29
- package/dist/templates/_shared/lib-ts/base/logger.ts +1 -1
- package/dist/templates/_shared/lib-ts/base/state-io.ts +11 -2
- package/dist/templates/_shared/lib-ts/base/subprocess-utils.ts +181 -165
- package/dist/templates/_shared/lib-ts/context/plan-manager.ts +14 -13
- package/dist/templates/_shared/lib-ts/handoff/handoff-reader.ts +3 -2
- package/dist/templates/_shared/lib-ts/package.json +1 -2
- package/dist/templates/_shared/lib-ts/templates/plan-context.ts +27 -34
- package/dist/templates/_shared/lib-ts/types.ts +17 -2
- package/dist/templates/_shared/scripts/resume_handoff.ts +4 -4
- package/dist/templates/_shared/scripts/save_handoff.ts +7 -7
- package/dist/templates/_shared/scripts/status_line.ts +104 -71
- package/dist/templates/_shared/workflows/handoff.md +1 -1
- package/dist/templates/cc-native/.claude/settings.json +182 -175
- package/dist/templates/cc-native/_cc-native/agents/CLAUDE.md +23 -1
- package/dist/templates/cc-native/_cc-native/agents/plan-questions/PLAN-QUESTIONER.md +70 -0
- package/dist/templates/cc-native/_cc-native/hooks/CLAUDE.md +6 -1
- package/dist/templates/cc-native/_cc-native/hooks/cc-native-plan-review.ts +142 -111
- package/dist/templates/cc-native/_cc-native/hooks/enhance_plan_post_subagent.ts +54 -0
- package/dist/templates/cc-native/_cc-native/hooks/enhance_plan_post_write.ts +52 -0
- package/dist/templates/cc-native/_cc-native/hooks/mark_questions_asked.ts +53 -0
- package/dist/templates/cc-native/_cc-native/hooks/plan_questions_early.ts +19 -19
- package/dist/templates/cc-native/_cc-native/lib-ts/aggregate-agents.ts +6 -5
- package/dist/templates/cc-native/_cc-native/lib-ts/artifacts.ts +114 -83
- package/dist/templates/cc-native/_cc-native/lib-ts/cc-native-state.ts +107 -10
- package/dist/templates/cc-native/_cc-native/lib-ts/cli-output-parser.ts +1 -1
- package/dist/templates/cc-native/_cc-native/lib-ts/corroboration.ts +6 -2
- package/dist/templates/cc-native/_cc-native/lib-ts/index.ts +0 -4
- package/dist/templates/cc-native/_cc-native/lib-ts/orchestrator.ts +40 -219
- package/dist/templates/cc-native/_cc-native/lib-ts/plan-enhancement.ts +41 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/plan-questions.ts +102 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/reviewers/agent.ts +26 -227
- package/dist/templates/cc-native/_cc-native/lib-ts/reviewers/base/base-agent.ts +217 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/reviewers/index.ts +4 -2
- package/dist/templates/cc-native/_cc-native/lib-ts/reviewers/providers/claude-agent.ts +65 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/reviewers/providers/codex-agent.ts +185 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/reviewers/providers/gemini-agent.ts +39 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/reviewers/providers/orchestrator-claude-agent.ts +195 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/reviewers/schemas.ts +201 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/reviewers/types.ts +2 -2
- package/dist/templates/cc-native/_cc-native/lib-ts/state.ts +17 -16
- package/dist/templates/cc-native/_cc-native/lib-ts/types.ts +13 -108
- package/dist/templates/cc-native/_cc-native/lib-ts/verdict.ts +3 -3
- package/dist/templates/cc-native/_cc-native/plan-review.config.json +2 -14
- package/oclif.manifest.json +1 -1
- package/package.json +1 -2
- package/dist/templates/cc-native/_cc-native/hooks/add_plan_context.ts +0 -119
- package/dist/templates/cc-native/_cc-native/lib-ts/reviewers/codex.ts +0 -130
- package/dist/templates/cc-native/_cc-native/lib-ts/reviewers/gemini.ts +0 -107
- /package/dist/templates/cc-native/_cc-native/agents/{ARCH-EVOLUTION.md → plan-review/ARCH-EVOLUTION.md} +0 -0
- /package/dist/templates/cc-native/_cc-native/agents/{ARCH-PATTERNS.md → plan-review/ARCH-PATTERNS.md} +0 -0
- /package/dist/templates/cc-native/_cc-native/agents/{ARCH-STRUCTURE.md → plan-review/ARCH-STRUCTURE.md} +0 -0
- /package/dist/templates/cc-native/_cc-native/agents/{ASSUMPTION-TRACER.md → plan-review/ASSUMPTION-TRACER.md} +0 -0
- /package/dist/templates/cc-native/_cc-native/agents/{CLARITY-AUDITOR.md → plan-review/CLARITY-AUDITOR.md} +0 -0
- /package/dist/templates/cc-native/_cc-native/agents/{COMPLETENESS-FEASIBILITY.md → plan-review/COMPLETENESS-FEASIBILITY.md} +0 -0
- /package/dist/templates/cc-native/_cc-native/agents/{COMPLETENESS-GAPS.md → plan-review/COMPLETENESS-GAPS.md} +0 -0
- /package/dist/templates/cc-native/_cc-native/agents/{COMPLETENESS-ORDERING.md → plan-review/COMPLETENESS-ORDERING.md} +0 -0
- /package/dist/templates/cc-native/_cc-native/agents/{CONSTRAINT-VALIDATOR.md → plan-review/CONSTRAINT-VALIDATOR.md} +0 -0
- /package/dist/templates/cc-native/_cc-native/agents/{DESIGN-ADR-VALIDATOR.md → plan-review/DESIGN-ADR-VALIDATOR.md} +0 -0
- /package/dist/templates/cc-native/_cc-native/agents/{DESIGN-SCALE-MATCHER.md → plan-review/DESIGN-SCALE-MATCHER.md} +0 -0
- /package/dist/templates/cc-native/_cc-native/agents/{DEVILS-ADVOCATE.md → plan-review/DEVILS-ADVOCATE.md} +0 -0
- /package/dist/templates/cc-native/_cc-native/agents/{DOCUMENTATION-PHILOSOPHY.md → plan-review/DOCUMENTATION-PHILOSOPHY.md} +0 -0
- /package/dist/templates/cc-native/_cc-native/agents/{HANDOFF-READINESS.md → plan-review/HANDOFF-READINESS.md} +0 -0
- /package/dist/templates/cc-native/_cc-native/agents/{HIDDEN-COMPLEXITY.md → plan-review/HIDDEN-COMPLEXITY.md} +0 -0
- /package/dist/templates/cc-native/_cc-native/agents/{INCREMENTAL-DELIVERY.md → plan-review/INCREMENTAL-DELIVERY.md} +0 -0
- /package/dist/templates/cc-native/_cc-native/agents/{RISK-DEPENDENCY.md → plan-review/RISK-DEPENDENCY.md} +0 -0
- /package/dist/templates/cc-native/_cc-native/agents/{RISK-FMEA.md → plan-review/RISK-FMEA.md} +0 -0
- /package/dist/templates/cc-native/_cc-native/agents/{RISK-PREMORTEM.md → plan-review/RISK-PREMORTEM.md} +0 -0
- /package/dist/templates/cc-native/_cc-native/agents/{RISK-REVERSIBILITY.md → plan-review/RISK-REVERSIBILITY.md} +0 -0
- /package/dist/templates/cc-native/_cc-native/agents/{SCOPE-BOUNDARY.md → plan-review/SCOPE-BOUNDARY.md} +0 -0
- /package/dist/templates/cc-native/_cc-native/agents/{SIMPLICITY-GUARDIAN.md → plan-review/SIMPLICITY-GUARDIAN.md} +0 -0
- /package/dist/templates/cc-native/_cc-native/agents/{SKEPTIC.md → plan-review/SKEPTIC.md} +0 -0
- /package/dist/templates/cc-native/_cc-native/agents/{TESTDRIVEN-BEHAVIOR-AUDITOR.md → plan-review/TESTDRIVEN-BEHAVIOR-AUDITOR.md} +0 -0
- /package/dist/templates/cc-native/_cc-native/agents/{TESTDRIVEN-CHARACTERIZATION.md → plan-review/TESTDRIVEN-CHARACTERIZATION.md} +0 -0
- /package/dist/templates/cc-native/_cc-native/agents/{TESTDRIVEN-FIRST-VALIDATOR.md → plan-review/TESTDRIVEN-FIRST-VALIDATOR.md} +0 -0
- /package/dist/templates/cc-native/_cc-native/agents/{TESTDRIVEN-PYRAMID-ANALYZER.md → plan-review/TESTDRIVEN-PYRAMID-ANALYZER.md} +0 -0
- /package/dist/templates/cc-native/_cc-native/agents/{TRADEOFF-COSTS.md → plan-review/TRADEOFF-COSTS.md} +0 -0
- /package/dist/templates/cc-native/_cc-native/agents/{TRADEOFF-STAKEHOLDERS.md → plan-review/TRADEOFF-STAKEHOLDERS.md} +0 -0
- /package/dist/templates/cc-native/_cc-native/agents/{VERIFY-COVERAGE.md → plan-review/VERIFY-COVERAGE.md} +0 -0
- /package/dist/templates/cc-native/_cc-native/agents/{VERIFY-STRENGTH.md → plan-review/VERIFY-STRENGTH.md} +0 -0
|
@@ -1,21 +1,16 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Agent-based plan reviewer with multi-provider support.
|
|
3
|
-
* Routes to Claude
|
|
3
|
+
* Routes to provider-specific implementations (Claude, Codex, Gemini).
|
|
4
4
|
* See cc-native-plan-review-spec.md §4.10
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
import
|
|
8
|
-
import * as os from "node:os";
|
|
9
|
-
import * as path from "node:path";
|
|
10
|
-
import { logDebug, logInfo, logWarn, logError } from "../../../_shared/lib-ts/base/logger.js";
|
|
11
|
-
import { getInternalSubprocessEnv, findExecutable, execFileAsync } from "../../../_shared/lib-ts/base/subprocess-utils.js";
|
|
12
|
-
import { parseCliOutput } from "../cli-output-parser.js";
|
|
13
|
-
import { parseJsonMaybe, coerceToReview } from "../json-parser.js";
|
|
14
|
-
import { debugLog, debugRaw } from "../debug.js";
|
|
7
|
+
import { logWarn } from "../../../_shared/lib-ts/base/logger.js";
|
|
15
8
|
import type { AgentConfig, ReviewerResult, ReviewOptions } from "../types.js";
|
|
16
|
-
import {
|
|
17
|
-
import {
|
|
9
|
+
import { ClaudeAgent } from "./providers/claude-agent.js";
|
|
10
|
+
import { CodexAgent } from "./providers/codex-agent.js";
|
|
11
|
+
import { GeminiAgent } from "./providers/gemini-agent.js";
|
|
18
12
|
import type { Reviewer } from "./types.js";
|
|
13
|
+
import { makeResult } from "./types.js";
|
|
19
14
|
|
|
20
15
|
/**
|
|
21
16
|
* Agent reviewer — runs a CLI instance with a custom persona.
|
|
@@ -52,224 +47,28 @@ export async function runAgentReview(
|
|
|
52
47
|
contextPath?: string,
|
|
53
48
|
sessionName = "unknown",
|
|
54
49
|
): Promise<ReviewerResult> {
|
|
55
|
-
if (agent.provider === "codex") {
|
|
56
|
-
return runAgentReviewCodex(plan, agent, schema, timeout, contextPath, sessionName);
|
|
57
|
-
}
|
|
58
|
-
// Default: Claude (existing implementation)
|
|
59
|
-
return runAgentReviewClaude(plan, agent, schema, timeout, contextPath, sessionName);
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
/**
|
|
63
|
-
* Run a single Claude Code agent to review the plan.
|
|
64
|
-
*/
|
|
65
|
-
async function runAgentReviewClaude(
|
|
66
|
-
plan: string,
|
|
67
|
-
agent: AgentConfig,
|
|
68
|
-
schema: Record<string, unknown>,
|
|
69
|
-
timeout: number,
|
|
70
|
-
contextPath?: string,
|
|
71
|
-
sessionName = "unknown",
|
|
72
|
-
): Promise<ReviewerResult> {
|
|
73
|
-
const claudePath = findExecutable("claude");
|
|
74
|
-
if (!claudePath) {
|
|
75
|
-
logWarn(agent.name, "Claude CLI not found on PATH");
|
|
76
|
-
return makeResult(agent.name, false, "skip", {}, "", "claude CLI not found on PATH");
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
logDebug(agent.name, `Found Claude CLI at: ${claudePath}`);
|
|
80
|
-
|
|
81
|
-
const prompt = `IMMEDIATELY call StructuredOutput with your review of the plan below.
|
|
82
|
-
Do NOT output any text before calling StructuredOutput.
|
|
83
|
-
|
|
84
|
-
PLAN:
|
|
85
|
-
<<<
|
|
86
|
-
${plan}
|
|
87
|
-
>>>
|
|
88
|
-
`;
|
|
89
|
-
|
|
90
|
-
const schemaJson = JSON.stringify(schema);
|
|
91
|
-
const cmdArgs = [
|
|
92
|
-
"--model", agent.model,
|
|
93
|
-
"--output-format", "json",
|
|
94
|
-
"--json-schema", schemaJson,
|
|
95
|
-
"--max-turns", "3",
|
|
96
|
-
"--setting-sources", "",
|
|
97
|
-
"-p",
|
|
98
|
-
];
|
|
99
|
-
|
|
100
|
-
if (agent.system_prompt) {
|
|
101
|
-
const fullPrompt = AGENT_REVIEW_PROMPT_PREFIX + "\n\n---\n\n" + agent.system_prompt;
|
|
102
|
-
cmdArgs.push("--system-prompt", fullPrompt);
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
logInfo(agent.name, `Running Claude with model: ${agent.model}, timeout: ${timeout}s`);
|
|
106
|
-
|
|
107
|
-
const env = getInternalSubprocessEnv();
|
|
108
|
-
|
|
109
|
-
const result = await execFileAsync(claudePath, cmdArgs, {
|
|
110
|
-
input: prompt,
|
|
111
|
-
timeout: timeout * 1000,
|
|
112
|
-
env: env as Record<string, string>,
|
|
113
|
-
maxBuffer: 10 * 1024 * 1024,
|
|
114
|
-
shell: process.platform === "win32",
|
|
115
|
-
});
|
|
116
|
-
|
|
117
|
-
if (result.killed || result.signal === "SIGTERM") {
|
|
118
|
-
logWarn(agent.name, `Claude TIMEOUT after ${timeout}s`);
|
|
119
|
-
return makeResult(agent.name, false, "error", {}, "", `${agent.name} timed out after ${timeout}s`);
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
const raw = result.stdout.trim();
|
|
123
|
-
const err = result.stderr.trim();
|
|
124
|
-
|
|
125
|
-
if (!raw && !err && result.exitCode !== 0) {
|
|
126
|
-
logError(agent.name, `Process exited with code ${result.exitCode} and no output`);
|
|
127
|
-
return makeResult(agent.name, false, "error", {}, "", `${agent.name} failed to run (exit ${result.exitCode})`);
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
logDebug(agent.name, `Exit code: ${result.exitCode}`);
|
|
131
|
-
logDebug(agent.name, `stdout length: ${raw.length} chars`);
|
|
132
|
-
if (err) logDebug(agent.name, `stderr: ${err.slice(0, 500)}`);
|
|
133
|
-
|
|
134
|
-
// Debug logging
|
|
135
|
-
if (contextPath) {
|
|
136
|
-
debugRaw(contextPath, sessionName, `agent:${agent.name}`, "stdout", raw);
|
|
137
|
-
if (err) {
|
|
138
|
-
debugRaw(contextPath, sessionName, `agent:${agent.name}`, "stderr", err);
|
|
139
|
-
}
|
|
140
|
-
debugLog(contextPath, sessionName, `agent:${agent.name}`, "subprocess_info", {
|
|
141
|
-
exit_code: result.exitCode,
|
|
142
|
-
stdout_len: raw.length,
|
|
143
|
-
stderr_len: err.length,
|
|
144
|
-
model: agent.model,
|
|
145
|
-
provider: "claude",
|
|
146
|
-
timeout,
|
|
147
|
-
});
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
if (raw) logDebug(agent.name, `stdout preview: ${raw.slice(0, 500)}`);
|
|
151
|
-
|
|
152
|
-
const obj = parseCliOutput(raw, ["verdict", "summary"]);
|
|
153
|
-
|
|
154
|
-
if (contextPath) {
|
|
155
|
-
debugLog(contextPath, sessionName, `agent:${agent.name}`, "parsed_result", {
|
|
156
|
-
parsed_keys: obj ? Object.keys(obj) : null,
|
|
157
|
-
verdict: obj?.verdict ?? null,
|
|
158
|
-
has_summary: obj ? Boolean(obj.summary) : false,
|
|
159
|
-
issues_count: obj && Array.isArray(obj.issues) ? (obj.issues as unknown[]).length : 0,
|
|
160
|
-
});
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
if (obj) {
|
|
164
|
-
logInfo(agent.name, `Parsed JSON successfully, verdict: ${obj.verdict ?? "N/A"}`);
|
|
165
|
-
} else {
|
|
166
|
-
logWarn(agent.name, "Failed to parse JSON from output");
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
const [ok, verdict, norm] = coerceToReview(
|
|
170
|
-
obj as Record<string, unknown> | null,
|
|
171
|
-
"Retry or check agent configuration.",
|
|
172
|
-
);
|
|
173
|
-
|
|
174
|
-
return makeResult(agent.name, ok, verdict, norm, raw, err);
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
/**
|
|
178
|
-
* Run a single Codex CLI agent to review the plan.
|
|
179
|
-
* Adapts the codex.ts reviewer pattern for agent-based review with persona.
|
|
180
|
-
*/
|
|
181
|
-
async function runAgentReviewCodex(
|
|
182
|
-
plan: string,
|
|
183
|
-
agent: AgentConfig,
|
|
184
|
-
schema: Record<string, unknown>,
|
|
185
|
-
timeout: number,
|
|
186
|
-
contextPath?: string,
|
|
187
|
-
sessionName = "unknown",
|
|
188
|
-
): Promise<ReviewerResult> {
|
|
189
|
-
const codexPath = findExecutable("codex");
|
|
190
|
-
if (!codexPath) {
|
|
191
|
-
logWarn(agent.name, "Codex CLI not found on PATH, skipping");
|
|
192
|
-
return makeResult(agent.name, false, "skip", {}, "", "codex CLI not found on PATH");
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
// Codex has no --system-prompt flag, so we prepend the agent persona to stdin.
|
|
196
|
-
const fullPrompt = [
|
|
197
|
-
AGENT_REVIEW_PROMPT_PREFIX,
|
|
198
|
-
"---",
|
|
199
|
-
agent.system_prompt || "",
|
|
200
|
-
"---",
|
|
201
|
-
`Return ONLY a JSON object matching this schema:\n${JSON.stringify(schema)}`,
|
|
202
|
-
"",
|
|
203
|
-
"PLAN:",
|
|
204
|
-
"<<<",
|
|
205
|
-
plan,
|
|
206
|
-
">>>",
|
|
207
|
-
].join("\n\n");
|
|
208
|
-
|
|
209
|
-
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), `codex-agent-${agent.name}-`));
|
|
210
|
-
|
|
211
50
|
try {
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
if (result.killed || result.signal === "SIGTERM") {
|
|
230
|
-
logWarn(agent.name, `Codex TIMEOUT after ${timeout}s`);
|
|
231
|
-
return makeResult(agent.name, false, "error", {}, "", `${agent.name} (codex) timed out after ${timeout}s`);
|
|
51
|
+
let reviewer;
|
|
52
|
+
|
|
53
|
+
switch (agent.provider) {
|
|
54
|
+
case "codex": {
|
|
55
|
+
reviewer = new CodexAgent(agent, schema, timeout, contextPath, sessionName);
|
|
56
|
+
break;
|
|
57
|
+
}
|
|
58
|
+
case "gemini": {
|
|
59
|
+
reviewer = new GeminiAgent(agent, schema, timeout, contextPath, sessionName);
|
|
60
|
+
break;
|
|
61
|
+
}
|
|
62
|
+
case "claude":
|
|
63
|
+
default: {
|
|
64
|
+
reviewer = new ClaudeAgent(agent, schema, timeout, contextPath, sessionName);
|
|
65
|
+
break;
|
|
66
|
+
}
|
|
232
67
|
}
|
|
233
68
|
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
// Read output: prefer temp file, fallback to stdout
|
|
240
|
-
let raw = "";
|
|
241
|
-
if (fs.existsSync(outPath)) {
|
|
242
|
-
raw = fs.readFileSync(outPath, "utf-8");
|
|
243
|
-
}
|
|
244
|
-
const err = result.stderr.trim();
|
|
245
|
-
|
|
246
|
-
// Debug logging
|
|
247
|
-
if (contextPath) {
|
|
248
|
-
debugRaw(contextPath, sessionName, `agent:${agent.name}`, "stdout", raw || result.stdout);
|
|
249
|
-
if (err) debugRaw(contextPath, sessionName, `agent:${agent.name}`, "stderr", err);
|
|
250
|
-
debugLog(contextPath, sessionName, `agent:${agent.name}`, "subprocess_info", {
|
|
251
|
-
exit_code: result.exitCode,
|
|
252
|
-
stdout_len: (raw || result.stdout).length,
|
|
253
|
-
stderr_len: err.length,
|
|
254
|
-
model: agent.model,
|
|
255
|
-
provider: "codex",
|
|
256
|
-
timeout,
|
|
257
|
-
});
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
// Parse output
|
|
261
|
-
const obj = parseJsonMaybe(raw) ?? parseJsonMaybe(result.stdout);
|
|
262
|
-
const [ok, verdict, norm] = coerceToReview(
|
|
263
|
-
obj,
|
|
264
|
-
"Retry or check Codex CLI auth/config.",
|
|
265
|
-
);
|
|
266
|
-
|
|
267
|
-
return makeResult(agent.name, ok, verdict, norm, raw || result.stdout, err);
|
|
268
|
-
} finally {
|
|
269
|
-
try {
|
|
270
|
-
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
271
|
-
} catch (e) {
|
|
272
|
-
logDebug(agent.name, `Failed to cleanup temp dir ${tmpDir}: ${e}`);
|
|
273
|
-
}
|
|
69
|
+
return await reviewer.review(plan);
|
|
70
|
+
} catch (error) {
|
|
71
|
+
logWarn(agent.name, `Unexpected error creating reviewer: ${error}`);
|
|
72
|
+
return makeResult(agent.name, false, "error", {}, "", `Failed: ${error}`);
|
|
274
73
|
}
|
|
275
74
|
}
|
|
@@ -0,0 +1,217 @@
|
|
|
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.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { logDebug, logInfo, logWarn, logError } from "../../../../_shared/lib-ts/base/logger.js";
|
|
8
|
+
import { getInternalSubprocessEnv, findExecutable, execFileAsync } from "../../../../_shared/lib-ts/base/subprocess-utils.js";
|
|
9
|
+
import { debugLog, debugRaw } from "../../debug.js";
|
|
10
|
+
import type { AgentConfig } from "../../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 result = await execFileAsync(cliPath, args, {
|
|
180
|
+
input: prompt,
|
|
181
|
+
timeout: this.timeout * 1000,
|
|
182
|
+
env: env as Record<string, string>,
|
|
183
|
+
maxBuffer: 10 * 1024 * 1024,
|
|
184
|
+
shell: process.platform === "win32",
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
// 4. Handle timeout
|
|
188
|
+
if (result.killed || result.signal === "SIGTERM") {
|
|
189
|
+
return this.handleTimeout();
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// 5. Extract output (provider-specific)
|
|
193
|
+
const { raw, err } = this.extractOutput(result);
|
|
194
|
+
|
|
195
|
+
// 6. Handle exit errors
|
|
196
|
+
if (!raw && !err && result.exitCode !== 0) {
|
|
197
|
+
return this.handleExitError(result);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// 7. Log subprocess results
|
|
201
|
+
this.logSubprocessResult(result, raw, err);
|
|
202
|
+
|
|
203
|
+
// 8. Parse JSON output (provider-specific)
|
|
204
|
+
const obj = this.parseOutput(raw, result);
|
|
205
|
+
|
|
206
|
+
// 9. Log parsed result
|
|
207
|
+
this.logParsedResult(obj);
|
|
208
|
+
|
|
209
|
+
// 10. Coerce to result type T (provider-specific)
|
|
210
|
+
const coerced = this.coerceResult(obj, raw, err);
|
|
211
|
+
|
|
212
|
+
// 11. Cleanup (optional override)
|
|
213
|
+
await this.cleanup();
|
|
214
|
+
|
|
215
|
+
return coerced;
|
|
216
|
+
}
|
|
217
|
+
}
|
|
@@ -4,7 +4,9 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
export { AgentReviewer, runAgentReview } from "./agent.js";
|
|
7
|
-
export {
|
|
8
|
-
export {
|
|
7
|
+
export { BaseCliAgent } from "./base/base-agent.js";
|
|
8
|
+
export { ClaudeAgent } from "./providers/claude-agent.js";
|
|
9
|
+
export { CodexAgent } from "./providers/codex-agent.js";
|
|
10
|
+
export { GeminiAgent } from "./providers/gemini-agent.js";
|
|
9
11
|
export type { Reviewer, ReviewerResult, ReviewOptions } from "./types.js";
|
|
10
12
|
export { makeResult } from "./types.js";
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Claude CLI agent reviewer implementation.
|
|
3
|
+
* Uses claude CLI with --json-schema and --system-prompt flags.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { shellQuoteWin } from "../../../../_shared/lib-ts/base/subprocess-utils.js";
|
|
7
|
+
import { parseCliOutput } from "../../cli-output-parser.js";
|
|
8
|
+
import { coerceToReview } from "../../json-parser.js";
|
|
9
|
+
import type { ReviewerResult } from "../../types.js";
|
|
10
|
+
import { BaseCliAgent } from "../base/base-agent.js";
|
|
11
|
+
import { AGENT_REVIEW_PROMPT_PREFIX } from "../schemas.js";
|
|
12
|
+
import { makeResult } from "../types.js";
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Claude CLI-based agent reviewer.
|
|
16
|
+
* Extends BaseCliAgent with Claude-specific prompt and argument handling.
|
|
17
|
+
*/
|
|
18
|
+
export class ClaudeAgent extends BaseCliAgent<ReviewerResult> {
|
|
19
|
+
protected buildCliArgs(): string[] {
|
|
20
|
+
const schemaJson = JSON.stringify(this.schema);
|
|
21
|
+
const cmdArgs = [
|
|
22
|
+
"--model", this.agent.model,
|
|
23
|
+
"--output-format", "json",
|
|
24
|
+
"--json-schema", shellQuoteWin(schemaJson),
|
|
25
|
+
"--max-turns", "3",
|
|
26
|
+
"--setting-sources", process.platform === "win32" ? '""' : "",
|
|
27
|
+
"-p",
|
|
28
|
+
];
|
|
29
|
+
|
|
30
|
+
if (this.agent.system_prompt) {
|
|
31
|
+
const fullPrompt = AGENT_REVIEW_PROMPT_PREFIX + "\n\n---\n\n" + this.agent.system_prompt;
|
|
32
|
+
cmdArgs.push("--system-prompt", shellQuoteWin(fullPrompt));
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return cmdArgs;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
protected buildPrompt(plan: string): string {
|
|
39
|
+
return `IMMEDIATELY call StructuredOutput with your review of the plan below.
|
|
40
|
+
Do NOT output any text before calling StructuredOutput.
|
|
41
|
+
|
|
42
|
+
PLAN:
|
|
43
|
+
<<<
|
|
44
|
+
${plan}
|
|
45
|
+
>>>
|
|
46
|
+
`;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
protected coerceResult(obj: Record<string, unknown> | null, raw: string, err: string): ReviewerResult {
|
|
50
|
+
const [ok, verdict, norm] = coerceToReview(obj, this.getDefaultErrorMessage());
|
|
51
|
+
return makeResult(this.agent.name, ok, verdict, norm, raw, err);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
protected getCliName(): string {
|
|
55
|
+
return "claude";
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
protected makeErrorResult(type: "skip" | "error", message: string): ReviewerResult {
|
|
59
|
+
return makeResult(this.agent.name, false, type, {}, "", message);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
protected parseOutput(raw: string, _result: unknown): Record<string, unknown> | null {
|
|
63
|
+
return parseCliOutput(raw, ["verdict", "summary"]);
|
|
64
|
+
}
|
|
65
|
+
}
|