aiwcli 0.10.3 → 0.11.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/bin/run.js +1 -1
- package/dist/commands/clear.js +28 -131
- package/dist/commands/init/index.js +3 -3
- package/dist/lib/gitignore-manager.d.ts +32 -0
- package/dist/lib/gitignore-manager.js +141 -2
- package/dist/templates/CLAUDE.md +8 -8
- package/dist/templates/_shared/.claude/commands/handoff-resume.md +64 -0
- package/dist/templates/_shared/.claude/commands/handoff.md +16 -10
- package/dist/templates/_shared/.claude/settings.json +7 -7
- package/dist/templates/_shared/hooks-ts/_utils/git-state.ts +2 -0
- package/dist/templates/_shared/hooks-ts/archive_plan.ts +159 -0
- package/dist/templates/_shared/hooks-ts/context_monitor.ts +147 -0
- package/dist/templates/_shared/hooks-ts/file-suggestion.ts +130 -0
- package/dist/templates/_shared/hooks-ts/pre_compact.ts +49 -0
- package/dist/templates/_shared/hooks-ts/session_end.ts +107 -0
- package/dist/templates/_shared/hooks-ts/session_start.ts +144 -0
- package/dist/templates/_shared/hooks-ts/task_create_capture.ts +48 -0
- package/dist/templates/_shared/hooks-ts/task_update_capture.ts +74 -0
- package/dist/templates/_shared/hooks-ts/user_prompt_submit.ts +83 -0
- package/dist/templates/_shared/lib-ts/CLAUDE.md +318 -0
- package/dist/templates/_shared/lib-ts/base/atomic-write.ts +12 -12
- package/dist/templates/_shared/lib-ts/base/constants.ts +22 -15
- package/dist/templates/_shared/lib-ts/base/git-state.ts +1 -1
- package/dist/templates/_shared/lib-ts/base/hook-utils.ts +129 -50
- package/dist/templates/_shared/lib-ts/base/inference.ts +28 -21
- package/dist/templates/_shared/lib-ts/base/logger.ts +15 -2
- package/dist/templates/_shared/lib-ts/base/state-io.ts +9 -7
- package/dist/templates/_shared/lib-ts/base/stop-words.ts +131 -131
- package/dist/templates/_shared/lib-ts/base/subprocess-utils.ts +142 -0
- package/dist/templates/_shared/lib-ts/base/utils.ts +69 -69
- package/dist/templates/_shared/lib-ts/context/context-formatter.ts +30 -24
- package/dist/templates/_shared/lib-ts/context/context-selector.ts +50 -32
- package/dist/templates/_shared/lib-ts/context/context-store.ts +76 -48
- package/dist/templates/_shared/lib-ts/context/plan-manager.ts +43 -23
- package/dist/templates/_shared/lib-ts/context/task-tracker.ts +10 -6
- package/dist/templates/_shared/lib-ts/handoff/document-generator.ts +11 -10
- package/dist/templates/_shared/lib-ts/handoff/handoff-reader.ts +158 -0
- package/dist/templates/_shared/lib-ts/templates/formatters.ts +6 -4
- package/dist/templates/_shared/lib-ts/types.ts +68 -55
- package/dist/templates/_shared/scripts/resolve_context.ts +24 -0
- package/dist/templates/_shared/scripts/resume_handoff.ts +345 -0
- package/dist/templates/_shared/scripts/save_handoff.ts +3 -3
- package/dist/templates/_shared/scripts/status_line.ts +687 -0
- package/dist/templates/cc-native/.claude/settings.json +175 -185
- package/dist/templates/cc-native/TEMPLATE-SCHEMA.md +15 -17
- package/dist/templates/cc-native/_cc-native/agents/CLAUDE.md +0 -2
- package/dist/templates/cc-native/_cc-native/hooks/CLAUDE.md +109 -135
- package/dist/templates/cc-native/_cc-native/hooks/add_plan_context.ts +119 -0
- package/dist/templates/cc-native/_cc-native/hooks/cc-native-plan-review.ts +1027 -0
- package/dist/templates/cc-native/_cc-native/hooks/plan_questions_early.ts +61 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/aggregate-agents.ts +156 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/artifacts.ts +792 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/cc-native-state.ts +199 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/cli-output-parser.ts +144 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/config.ts +57 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/constants.ts +83 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/corroboration.ts +115 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/debug.ts +80 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/index.ts +120 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/json-parser.ts +168 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/nul +3 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/orchestrator.ts +250 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/reviewers/agent.ts +275 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/reviewers/codex.ts +130 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/reviewers/gemini.ts +107 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/reviewers/index.ts +10 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/reviewers/types.ts +23 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/state.ts +240 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/tsconfig.json +18 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/types.ts +385 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/verdict.ts +72 -0
- package/dist/templates/cc-native/_cc-native/plan-review.config.json +14 -1
- package/oclif.manifest.json +1 -1
- package/package.json +2 -2
- package/dist/templates/_shared/hooks/__init__.py +0 -16
- package/dist/templates/_shared/hooks/__pycache__/__init__.cpython-313.pyc +0 -0
- package/dist/templates/_shared/hooks/__pycache__/archive_plan.cpython-313.pyc +0 -0
- package/dist/templates/_shared/hooks/__pycache__/context_enforcer.cpython-313.pyc +0 -0
- package/dist/templates/_shared/hooks/__pycache__/context_monitor.cpython-313.pyc +0 -0
- package/dist/templates/_shared/hooks/__pycache__/file-suggestion.cpython-313.pyc +0 -0
- package/dist/templates/_shared/hooks/__pycache__/pre_compact.cpython-313.pyc +0 -0
- package/dist/templates/_shared/hooks/__pycache__/session_end.cpython-313.pyc +0 -0
- package/dist/templates/_shared/hooks/__pycache__/session_start.cpython-313.pyc +0 -0
- package/dist/templates/_shared/hooks/__pycache__/task_create_atomicity.cpython-313.pyc +0 -0
- package/dist/templates/_shared/hooks/__pycache__/task_create_capture.cpython-313.pyc +0 -0
- package/dist/templates/_shared/hooks/__pycache__/task_update_capture.cpython-313.pyc +0 -0
- package/dist/templates/_shared/hooks/__pycache__/user_prompt_submit.cpython-313.pyc +0 -0
- package/dist/templates/_shared/hooks/archive_plan.py +0 -177
- package/dist/templates/_shared/hooks/context_monitor.py +0 -270
- package/dist/templates/_shared/hooks/file-suggestion.py +0 -215
- package/dist/templates/_shared/hooks/pre_compact.py +0 -104
- package/dist/templates/_shared/hooks/session_end.py +0 -173
- package/dist/templates/_shared/hooks/session_start.py +0 -206
- package/dist/templates/_shared/hooks/task_create_capture.py +0 -108
- package/dist/templates/_shared/hooks/task_update_capture.py +0 -145
- package/dist/templates/_shared/hooks/user_prompt_submit.py +0 -139
- package/dist/templates/_shared/lib/__init__.py +0 -1
- package/dist/templates/_shared/lib/__pycache__/__init__.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/base/__init__.py +0 -65
- package/dist/templates/_shared/lib/base/__pycache__/__init__.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/base/__pycache__/atomic_write.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/base/__pycache__/constants.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/base/__pycache__/hook_utils.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/base/__pycache__/inference.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/base/__pycache__/logger.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/base/__pycache__/stop_words.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/base/__pycache__/subprocess_utils.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/base/__pycache__/utils.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/base/atomic_write.py +0 -180
- package/dist/templates/_shared/lib/base/constants.py +0 -358
- package/dist/templates/_shared/lib/base/hook_utils.py +0 -339
- package/dist/templates/_shared/lib/base/inference.py +0 -307
- package/dist/templates/_shared/lib/base/logger.py +0 -305
- package/dist/templates/_shared/lib/base/stop_words.py +0 -221
- package/dist/templates/_shared/lib/base/subprocess_utils.py +0 -46
- package/dist/templates/_shared/lib/base/utils.py +0 -263
- package/dist/templates/_shared/lib/context/__init__.py +0 -102
- package/dist/templates/_shared/lib/context/__pycache__/__init__.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/context/__pycache__/cache.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/context/__pycache__/context_extractor.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/context/__pycache__/context_formatter.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/context/__pycache__/context_manager.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/context/__pycache__/context_selector.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/context/__pycache__/context_store.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/context/__pycache__/discovery.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/context/__pycache__/event_log.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/context/__pycache__/plan_archive.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/context/__pycache__/plan_manager.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/context/__pycache__/task_sync.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/context/__pycache__/task_tracker.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/context/context_formatter.py +0 -317
- package/dist/templates/_shared/lib/context/context_selector.py +0 -508
- package/dist/templates/_shared/lib/context/context_store.py +0 -653
- package/dist/templates/_shared/lib/context/plan_manager.py +0 -303
- package/dist/templates/_shared/lib/context/task_tracker.py +0 -188
- package/dist/templates/_shared/lib/handoff/__init__.py +0 -22
- package/dist/templates/_shared/lib/handoff/__pycache__/__init__.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/handoff/__pycache__/document_generator.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/handoff/document_generator.py +0 -278
- package/dist/templates/_shared/lib/templates/README.md +0 -206
- package/dist/templates/_shared/lib/templates/__init__.py +0 -36
- package/dist/templates/_shared/lib/templates/__pycache__/__init__.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/templates/__pycache__/formatters.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/templates/__pycache__/persona_questions.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/templates/__pycache__/plan_context.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/templates/formatters.py +0 -146
- package/dist/templates/_shared/lib/templates/plan_context.py +0 -73
- package/dist/templates/_shared/scripts/__pycache__/save_handoff.cpython-313.pyc +0 -0
- package/dist/templates/_shared/scripts/__pycache__/status_line.cpython-313.pyc +0 -0
- package/dist/templates/_shared/scripts/save_handoff.py +0 -357
- package/dist/templates/_shared/scripts/status_line.py +0 -716
- package/dist/templates/cc-native/.claude/commands/cc-native/fresh-perspective.md +0 -8
- package/dist/templates/cc-native/.windsurf/workflows/cc-native/fresh-perspective.md +0 -8
- package/dist/templates/cc-native/MIGRATION.md +0 -86
- package/dist/templates/cc-native/_cc-native/hooks/__pycache__/add_plan_context.cpython-313.pyc +0 -0
- package/dist/templates/cc-native/_cc-native/hooks/__pycache__/cc-native-plan-review.cpython-313.pyc +0 -0
- package/dist/templates/cc-native/_cc-native/hooks/__pycache__/mark_questions_asked.cpython-313.pyc +0 -0
- package/dist/templates/cc-native/_cc-native/hooks/__pycache__/plan_accepted.cpython-313.pyc +0 -0
- package/dist/templates/cc-native/_cc-native/hooks/__pycache__/plan_questions_early.cpython-313.pyc +0 -0
- package/dist/templates/cc-native/_cc-native/hooks/__pycache__/suggest-fresh-perspective.cpython-313.pyc +0 -0
- package/dist/templates/cc-native/_cc-native/hooks/add_plan_context.py +0 -130
- package/dist/templates/cc-native/_cc-native/hooks/cc-native-plan-review.py +0 -954
- package/dist/templates/cc-native/_cc-native/hooks/plan_questions_early.py +0 -81
- package/dist/templates/cc-native/_cc-native/hooks/suggest-fresh-perspective.py +0 -340
- package/dist/templates/cc-native/_cc-native/lib/CLAUDE.md +0 -265
- package/dist/templates/cc-native/_cc-native/lib/__init__.py +0 -53
- package/dist/templates/cc-native/_cc-native/lib/__pycache__/__init__.cpython-313.pyc +0 -0
- package/dist/templates/cc-native/_cc-native/lib/__pycache__/atomic_write.cpython-313.pyc +0 -0
- package/dist/templates/cc-native/_cc-native/lib/__pycache__/constants.cpython-313.pyc +0 -0
- package/dist/templates/cc-native/_cc-native/lib/__pycache__/debug.cpython-313.pyc +0 -0
- package/dist/templates/cc-native/_cc-native/lib/__pycache__/orchestrator.cpython-313.pyc +0 -0
- package/dist/templates/cc-native/_cc-native/lib/__pycache__/state.cpython-313.pyc +0 -0
- package/dist/templates/cc-native/_cc-native/lib/__pycache__/utils.cpython-313.pyc +0 -0
- package/dist/templates/cc-native/_cc-native/lib/constants.py +0 -45
- package/dist/templates/cc-native/_cc-native/lib/debug.py +0 -139
- package/dist/templates/cc-native/_cc-native/lib/orchestrator.py +0 -362
- package/dist/templates/cc-native/_cc-native/lib/reviewers/__init__.py +0 -28
- package/dist/templates/cc-native/_cc-native/lib/reviewers/__pycache__/__init__.cpython-313.pyc +0 -0
- package/dist/templates/cc-native/_cc-native/lib/reviewers/__pycache__/agent.cpython-313.pyc +0 -0
- package/dist/templates/cc-native/_cc-native/lib/reviewers/__pycache__/base.cpython-313.pyc +0 -0
- package/dist/templates/cc-native/_cc-native/lib/reviewers/__pycache__/codex.cpython-313.pyc +0 -0
- package/dist/templates/cc-native/_cc-native/lib/reviewers/__pycache__/gemini.cpython-313.pyc +0 -0
- package/dist/templates/cc-native/_cc-native/lib/reviewers/agent.py +0 -215
- package/dist/templates/cc-native/_cc-native/lib/reviewers/base.py +0 -88
- package/dist/templates/cc-native/_cc-native/lib/reviewers/codex.py +0 -124
- package/dist/templates/cc-native/_cc-native/lib/reviewers/gemini.py +0 -108
- package/dist/templates/cc-native/_cc-native/lib/state.py +0 -268
- package/dist/templates/cc-native/_cc-native/lib/utils.py +0 -1071
- package/dist/templates/cc-native/_cc-native/scripts/__pycache__/aggregate_agents.cpython-313.pyc +0 -0
- package/dist/templates/cc-native/_cc-native/scripts/aggregate_agents.py +0 -168
- package/dist/templates/cc-native/_cc-native/workflows/fresh-perspective.md +0 -134
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Agent-based plan reviewer with multi-provider support.
|
|
3
|
+
* Routes to Claude or Codex CLI based on agent.provider field.
|
|
4
|
+
* See cc-native-plan-review-spec.md §4.10
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import * as fs from "node:fs";
|
|
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";
|
|
15
|
+
import type { AgentConfig, ReviewerResult, ReviewOptions } from "../types.js";
|
|
16
|
+
import { AGENT_REVIEW_PROMPT_PREFIX } from "../types.js";
|
|
17
|
+
import { makeResult } from "./types.js";
|
|
18
|
+
import type { Reviewer } from "./types.js";
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Agent reviewer — runs a CLI instance with a custom persona.
|
|
22
|
+
*/
|
|
23
|
+
export class AgentReviewer implements Reviewer {
|
|
24
|
+
constructor(private agent: AgentConfig) {}
|
|
25
|
+
|
|
26
|
+
async review(
|
|
27
|
+
plan: string,
|
|
28
|
+
schema: Record<string, unknown>,
|
|
29
|
+
options: ReviewOptions,
|
|
30
|
+
): Promise<ReviewerResult> {
|
|
31
|
+
return runAgentReview(
|
|
32
|
+
plan,
|
|
33
|
+
this.agent,
|
|
34
|
+
schema,
|
|
35
|
+
options.timeout,
|
|
36
|
+
options.context_path,
|
|
37
|
+
options.session_name ?? "unknown",
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Run a single agent to review the plan.
|
|
44
|
+
* Routes to provider-specific implementation based on agent.provider.
|
|
45
|
+
* Never throws — returns error ReviewerResult on failure.
|
|
46
|
+
*/
|
|
47
|
+
export async function runAgentReview(
|
|
48
|
+
plan: string,
|
|
49
|
+
agent: AgentConfig,
|
|
50
|
+
schema: Record<string, unknown>,
|
|
51
|
+
timeout: number,
|
|
52
|
+
contextPath?: string,
|
|
53
|
+
sessionName = "unknown",
|
|
54
|
+
): 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
|
+
try {
|
|
212
|
+
const schemaPath = path.join(tmpDir, "schema.json");
|
|
213
|
+
const outPath = path.join(tmpDir, "output.json");
|
|
214
|
+
fs.writeFileSync(schemaPath, JSON.stringify(schema, null, 2), "utf-8");
|
|
215
|
+
|
|
216
|
+
const cmdArgs = ["exec", "--sandbox", "read-only"];
|
|
217
|
+
if (agent.model) cmdArgs.push("--model", agent.model);
|
|
218
|
+
cmdArgs.push("--output-schema", schemaPath, "-o", outPath, "-");
|
|
219
|
+
|
|
220
|
+
logInfo(agent.name, `Running Codex with model: ${agent.model}, timeout: ${timeout}s`);
|
|
221
|
+
|
|
222
|
+
const result = await execFileAsync(codexPath, cmdArgs, {
|
|
223
|
+
input: fullPrompt,
|
|
224
|
+
timeout: timeout * 1000,
|
|
225
|
+
maxBuffer: 10 * 1024 * 1024,
|
|
226
|
+
shell: process.platform === "win32",
|
|
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`);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
if (!result.stdout && !result.stderr && !fs.existsSync(outPath) && result.exitCode !== 0) {
|
|
235
|
+
logError(agent.name, `Codex exited with code ${result.exitCode} and no output`);
|
|
236
|
+
return makeResult(agent.name, false, "error", {}, "", `${agent.name} (codex) failed (exit ${result.exitCode})`);
|
|
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
|
+
}
|
|
274
|
+
}
|
|
275
|
+
}
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Codex CLI plan reviewer.
|
|
3
|
+
* Runs Codex in non-interactive mode with read-only sandbox.
|
|
4
|
+
* See cc-native-plan-review-spec.md §4.11
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import * as fs from "node:fs";
|
|
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 { findExecutable, execFileAsync } from "../../../_shared/lib-ts/base/subprocess-utils.js";
|
|
12
|
+
import { parseJsonMaybe, coerceToReview } from "../json-parser.js";
|
|
13
|
+
import { REVIEW_PROMPT_PREFIX } from "../types.js";
|
|
14
|
+
import type { ReviewerResult, ReviewOptions } from "../types.js";
|
|
15
|
+
import { makeResult } from "./types.js";
|
|
16
|
+
import type { Reviewer } from "./types.js";
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Codex reviewer — runs codex exec --sandbox read-only.
|
|
20
|
+
*/
|
|
21
|
+
export class CodexReviewer implements Reviewer {
|
|
22
|
+
private settings: Record<string, unknown>;
|
|
23
|
+
|
|
24
|
+
constructor(settings: Record<string, unknown>) {
|
|
25
|
+
this.settings = settings;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
async review(
|
|
29
|
+
plan: string,
|
|
30
|
+
schema: Record<string, unknown>,
|
|
31
|
+
options: ReviewOptions,
|
|
32
|
+
): Promise<ReviewerResult> {
|
|
33
|
+
return runCodexReview(plan, schema, this.settings);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Run Codex CLI to review the plan.
|
|
39
|
+
* Never throws — returns error ReviewerResult on failure.
|
|
40
|
+
*/
|
|
41
|
+
export async function runCodexReview(
|
|
42
|
+
plan: string,
|
|
43
|
+
schema: Record<string, unknown>,
|
|
44
|
+
settings: Record<string, unknown>,
|
|
45
|
+
): Promise<ReviewerResult> {
|
|
46
|
+
const codexSettings =
|
|
47
|
+
((settings.reviewers as Record<string, unknown> | undefined)?.codex as
|
|
48
|
+
| Record<string, unknown>
|
|
49
|
+
| undefined) ?? {};
|
|
50
|
+
const timeout = (codexSettings.timeout as number) ?? 120;
|
|
51
|
+
const model = (codexSettings.model as string) ?? "";
|
|
52
|
+
|
|
53
|
+
const codexPath = findExecutable("codex");
|
|
54
|
+
if (!codexPath) {
|
|
55
|
+
logWarn("codex", "CLI not found on PATH");
|
|
56
|
+
return makeResult("codex", false, "skip", {}, "", "codex CLI not found on PATH");
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
logDebug("codex", `Found CLI at: ${codexPath}`);
|
|
60
|
+
|
|
61
|
+
const prompt = `${REVIEW_PROMPT_PREFIX}
|
|
62
|
+
Return ONLY a JSON object that matches this JSON Schema:
|
|
63
|
+
${JSON.stringify(schema)}
|
|
64
|
+
|
|
65
|
+
PLAN:
|
|
66
|
+
<<<
|
|
67
|
+
${plan}
|
|
68
|
+
>>>
|
|
69
|
+
`;
|
|
70
|
+
|
|
71
|
+
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "codex-review-"));
|
|
72
|
+
|
|
73
|
+
try {
|
|
74
|
+
const schemaPath = path.join(tmpDir, "schema.json");
|
|
75
|
+
const outPath = path.join(tmpDir, "codex_review.json");
|
|
76
|
+
|
|
77
|
+
fs.writeFileSync(schemaPath, JSON.stringify(schema, null, 2), "utf-8");
|
|
78
|
+
|
|
79
|
+
const cmdArgs = ["exec", "--sandbox", "read-only"];
|
|
80
|
+
|
|
81
|
+
if (model) {
|
|
82
|
+
cmdArgs.push("--model", model);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
cmdArgs.push("--output-schema", schemaPath, "-o", outPath, "-");
|
|
86
|
+
|
|
87
|
+
logDebug("codex", `Running command: codex ${cmdArgs.join(" ")}`);
|
|
88
|
+
|
|
89
|
+
const result = await execFileAsync(codexPath, cmdArgs, {
|
|
90
|
+
input: prompt,
|
|
91
|
+
timeout: timeout * 1000,
|
|
92
|
+
maxBuffer: 10 * 1024 * 1024,
|
|
93
|
+
shell: process.platform === "win32",
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
if (result.killed || result.signal === "SIGTERM") {
|
|
97
|
+
logWarn("codex", `TIMEOUT after ${timeout}s`);
|
|
98
|
+
return makeResult("codex", false, "error", {}, "", `codex timed out after ${timeout}s`);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
if (!result.stdout && !result.stderr && !fs.existsSync(outPath) && result.exitCode !== 0) {
|
|
102
|
+
logError("codex", `Process exited with code ${result.exitCode} and no output`);
|
|
103
|
+
return makeResult("codex", false, "error", {}, "", `codex failed to run (exit ${result.exitCode})`);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
logDebug("codex", `Exit code: ${result.exitCode}`);
|
|
107
|
+
|
|
108
|
+
let raw = "";
|
|
109
|
+
if (fs.existsSync(outPath)) {
|
|
110
|
+
raw = fs.readFileSync(outPath, "utf-8");
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const obj = parseJsonMaybe(raw) ?? parseJsonMaybe(result.stdout);
|
|
114
|
+
const [ok, verdict, norm] = coerceToReview(
|
|
115
|
+
obj,
|
|
116
|
+
"Retry or check CLI auth/config.",
|
|
117
|
+
);
|
|
118
|
+
|
|
119
|
+
const err = result.stderr.trim();
|
|
120
|
+
return makeResult("codex", ok, verdict, norm, raw || result.stdout, err);
|
|
121
|
+
} finally {
|
|
122
|
+
// Clean up temp directory
|
|
123
|
+
try {
|
|
124
|
+
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
125
|
+
} catch {
|
|
126
|
+
// Best effort cleanup
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Gemini CLI plan reviewer.
|
|
3
|
+
* Runs Gemini CLI in YOLO mode (auto-approve).
|
|
4
|
+
* See cc-native-plan-review-spec.md §4.12
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { logDebug, logInfo, logWarn, logError } from "../../../_shared/lib-ts/base/logger.js";
|
|
8
|
+
import { findExecutable, execFileAsync } from "../../../_shared/lib-ts/base/subprocess-utils.js";
|
|
9
|
+
import { parseJsonMaybe, coerceToReview } from "../json-parser.js";
|
|
10
|
+
import type { ReviewerResult, ReviewOptions } from "../types.js";
|
|
11
|
+
import { makeResult } from "./types.js";
|
|
12
|
+
import type { Reviewer } from "./types.js";
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Gemini reviewer — runs gemini -y -p <instruction>.
|
|
16
|
+
*/
|
|
17
|
+
export class GeminiReviewer implements Reviewer {
|
|
18
|
+
private settings: Record<string, unknown>;
|
|
19
|
+
|
|
20
|
+
constructor(settings: Record<string, unknown>) {
|
|
21
|
+
this.settings = settings;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
async review(
|
|
25
|
+
plan: string,
|
|
26
|
+
schema: Record<string, unknown>,
|
|
27
|
+
options: ReviewOptions,
|
|
28
|
+
): Promise<ReviewerResult> {
|
|
29
|
+
return runGeminiReview(plan, schema, this.settings);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Run Gemini CLI to review the plan.
|
|
35
|
+
* Never throws — returns error ReviewerResult on failure.
|
|
36
|
+
*/
|
|
37
|
+
export async function runGeminiReview(
|
|
38
|
+
plan: string,
|
|
39
|
+
schema: Record<string, unknown>,
|
|
40
|
+
settings: Record<string, unknown>,
|
|
41
|
+
): Promise<ReviewerResult> {
|
|
42
|
+
const geminiSettings =
|
|
43
|
+
((settings.reviewers as Record<string, unknown> | undefined)?.gemini as
|
|
44
|
+
| Record<string, unknown>
|
|
45
|
+
| undefined) ?? {};
|
|
46
|
+
const timeout = (geminiSettings.timeout as number) ?? 120;
|
|
47
|
+
const model = (geminiSettings.model as string) ?? "";
|
|
48
|
+
|
|
49
|
+
const geminiPath = findExecutable("gemini");
|
|
50
|
+
if (!geminiPath) {
|
|
51
|
+
logWarn("gemini", "CLI not found on PATH");
|
|
52
|
+
return makeResult("gemini", false, "skip", {}, "", "gemini CLI not found on PATH");
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
logDebug("gemini", `Found CLI at: ${geminiPath}`);
|
|
56
|
+
|
|
57
|
+
const instruction = `
|
|
58
|
+
|
|
59
|
+
Review the PLAN above as a senior staff software engineer. Focus on:
|
|
60
|
+
- missing steps, unclear assumptions, edge cases
|
|
61
|
+
- security/privacy concerns
|
|
62
|
+
- testing/rollout/rollback completeness
|
|
63
|
+
- operational concerns (observability, failure modes)
|
|
64
|
+
|
|
65
|
+
Return ONLY a JSON object that matches this JSON Schema (no markdown, no code fences):
|
|
66
|
+
${JSON.stringify(schema)}
|
|
67
|
+
`;
|
|
68
|
+
|
|
69
|
+
const cmdArgs = ["-y", "-p", instruction];
|
|
70
|
+
|
|
71
|
+
if (model) {
|
|
72
|
+
cmdArgs.push("--model", model);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
logDebug("gemini", "Running command: gemini -y -p <instruction>");
|
|
76
|
+
|
|
77
|
+
const result = await execFileAsync(geminiPath, cmdArgs, {
|
|
78
|
+
input: plan,
|
|
79
|
+
timeout: timeout * 1000,
|
|
80
|
+
maxBuffer: 10 * 1024 * 1024,
|
|
81
|
+
shell: process.platform === "win32",
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
if (result.killed || result.signal === "SIGTERM") {
|
|
85
|
+
logWarn("gemini", `TIMEOUT after ${timeout}s`);
|
|
86
|
+
return makeResult("gemini", false, "error", {}, "", `gemini timed out after ${timeout}s`);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
if (!result.stdout && !result.stderr && result.exitCode !== 0) {
|
|
90
|
+
logError("gemini", `Process exited with code ${result.exitCode}`);
|
|
91
|
+
return makeResult("gemini", false, "error", {}, "", `gemini failed to run (exit ${result.exitCode})`);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
logDebug("gemini", `Exit code: ${result.exitCode}`);
|
|
95
|
+
|
|
96
|
+
const raw = result.stdout.trim();
|
|
97
|
+
const err = result.stderr.trim();
|
|
98
|
+
|
|
99
|
+
const obj = parseJsonMaybe(raw);
|
|
100
|
+
const [ok, verdict, norm] = coerceToReview(
|
|
101
|
+
obj,
|
|
102
|
+
"Retry or check CLI auth/config.",
|
|
103
|
+
);
|
|
104
|
+
|
|
105
|
+
return makeResult("gemini", ok, verdict, norm, raw, err);
|
|
106
|
+
}
|
|
107
|
+
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reviewers package — re-exports all reviewer implementations.
|
|
3
|
+
* See cc-native-plan-review-spec.md §4.9
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export { AgentReviewer, runAgentReview } from "./agent.js";
|
|
7
|
+
export { CodexReviewer, runCodexReview } from "./codex.js";
|
|
8
|
+
export { GeminiReviewer, runGeminiReview } from "./gemini.js";
|
|
9
|
+
export type { Reviewer, ReviewerResult, ReviewOptions } from "./types.js";
|
|
10
|
+
export { makeResult } from "./types.js";
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reviewer interface and options for plan review implementations.
|
|
3
|
+
* See cc-native-plan-review-spec.md §4.9
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { ReviewData, ReviewerResult, Verdict } from "../types.js";
|
|
7
|
+
|
|
8
|
+
// Re-export for convenience
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
/** Create a standard ReviewerResult. Shared by all reviewer implementations. */
|
|
12
|
+
export function makeResult(
|
|
13
|
+
name: string,
|
|
14
|
+
ok: boolean,
|
|
15
|
+
verdict: Verdict,
|
|
16
|
+
data: Record<string, unknown> | ReviewData,
|
|
17
|
+
raw: string,
|
|
18
|
+
err: string,
|
|
19
|
+
): ReviewerResult {
|
|
20
|
+
return { name, ok, verdict, data: data as Record<string, unknown>, raw, err };
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export {type Reviewer, type ReviewerResult, type ReviewOptions} from "../types.js";
|