@codemoot/cli 0.2.6 → 0.2.9
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/index.js +307 -65
- package/dist/index.js.map +1 -1
- package/dist/prompts-GB7HH4EL.js +0 -0
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -13,11 +13,13 @@ import { join as join2 } from "path";
|
|
|
13
13
|
|
|
14
14
|
// src/progress.ts
|
|
15
15
|
import chalk from "chalk";
|
|
16
|
-
var THROTTLE_MS =
|
|
16
|
+
var THROTTLE_MS = 3e4;
|
|
17
|
+
var MAX_CARRY_OVER = 64 * 1024;
|
|
17
18
|
function createProgressCallbacks(label = "codex") {
|
|
18
19
|
let lastActivityAt = 0;
|
|
19
20
|
let lastMessage = "";
|
|
20
21
|
let carryOver = "";
|
|
22
|
+
let droppedEvents = 0;
|
|
21
23
|
function printActivity(msg) {
|
|
22
24
|
const now = Date.now();
|
|
23
25
|
if (msg === lastMessage && now - lastActivityAt < THROTTLE_MS) return;
|
|
@@ -25,9 +27,20 @@ function createProgressCallbacks(label = "codex") {
|
|
|
25
27
|
lastMessage = msg;
|
|
26
28
|
console.error(chalk.dim(` [${label}] ${msg}`));
|
|
27
29
|
}
|
|
30
|
+
function parseLine(line) {
|
|
31
|
+
const trimmed = line.trim();
|
|
32
|
+
if (!trimmed) return;
|
|
33
|
+
try {
|
|
34
|
+
const event = JSON.parse(trimmed);
|
|
35
|
+
formatEvent(event, printActivity);
|
|
36
|
+
} catch {
|
|
37
|
+
droppedEvents++;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
28
40
|
return {
|
|
29
41
|
onSpawn(pid, command) {
|
|
30
|
-
|
|
42
|
+
const exe = command.replace(/^"([^"]+)".*/, "$1").split(/[\s/\\]+/).pop() ?? command;
|
|
43
|
+
console.error(chalk.dim(` [${label}] Started (PID: ${pid}, cmd: ${exe})`));
|
|
31
44
|
},
|
|
32
45
|
onStderr(_chunk) {
|
|
33
46
|
},
|
|
@@ -35,18 +48,26 @@ function createProgressCallbacks(label = "codex") {
|
|
|
35
48
|
const data = carryOver + chunk;
|
|
36
49
|
const lines = data.split("\n");
|
|
37
50
|
carryOver = lines.pop() ?? "";
|
|
51
|
+
if (carryOver.length > MAX_CARRY_OVER) {
|
|
52
|
+
carryOver = "";
|
|
53
|
+
droppedEvents++;
|
|
54
|
+
}
|
|
38
55
|
for (const line of lines) {
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
56
|
+
parseLine(line);
|
|
57
|
+
}
|
|
58
|
+
},
|
|
59
|
+
onClose() {
|
|
60
|
+
if (carryOver.trim()) {
|
|
61
|
+
parseLine(carryOver);
|
|
62
|
+
carryOver = "";
|
|
63
|
+
}
|
|
64
|
+
if (droppedEvents > 0) {
|
|
65
|
+
console.error(chalk.dim(` [${label}] ${droppedEvents} event(s) dropped (parse errors or buffer overflow)`));
|
|
66
|
+
droppedEvents = 0;
|
|
46
67
|
}
|
|
47
68
|
},
|
|
48
69
|
onHeartbeat(elapsedSec) {
|
|
49
|
-
if (elapsedSec %
|
|
70
|
+
if (elapsedSec % 60 === 0) {
|
|
50
71
|
printActivity(`${elapsedSec}s elapsed...`);
|
|
51
72
|
}
|
|
52
73
|
}
|
|
@@ -74,15 +95,6 @@ function formatEvent(event, print) {
|
|
|
74
95
|
}
|
|
75
96
|
return;
|
|
76
97
|
}
|
|
77
|
-
if (item.type === "agent_message") {
|
|
78
|
-
const text = String(item.text ?? "");
|
|
79
|
-
const firstLine = text.split("\n").find((l) => l.trim().length > 10);
|
|
80
|
-
if (firstLine) {
|
|
81
|
-
const preview = firstLine.trim().slice(0, 80);
|
|
82
|
-
print(`Response: ${preview}${firstLine.trim().length > 80 ? "..." : ""}`);
|
|
83
|
-
}
|
|
84
|
-
return;
|
|
85
|
-
}
|
|
86
98
|
}
|
|
87
99
|
if (type === "turn.completed") {
|
|
88
100
|
const usage = event.usage;
|
|
@@ -382,9 +394,9 @@ async function buildReviewCommand(buildId) {
|
|
|
382
394
|
try {
|
|
383
395
|
const tmpIndex = join2(projectDir, ".git", "codemoot-review-index");
|
|
384
396
|
try {
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
diff = execFileSync("git", ["diff", "--cached", "--"
|
|
397
|
+
execFileSync("git", ["read-tree", "HEAD"], { cwd: projectDir, encoding: "utf-8", env: { ...process.env, GIT_INDEX_FILE: tmpIndex } });
|
|
398
|
+
execFileSync("git", ["add", "-A"], { cwd: projectDir, encoding: "utf-8", env: { ...process.env, GIT_INDEX_FILE: tmpIndex } });
|
|
399
|
+
diff = execFileSync("git", ["diff", "--cached", run.baselineRef, "--"], { cwd: projectDir, encoding: "utf-8", maxBuffer: 1024 * 1024, env: { ...process.env, GIT_INDEX_FILE: tmpIndex } });
|
|
388
400
|
} finally {
|
|
389
401
|
try {
|
|
390
402
|
unlinkSync(tmpIndex);
|
|
@@ -495,7 +507,7 @@ Review for:
|
|
|
495
507
|
}
|
|
496
508
|
const output = {
|
|
497
509
|
buildId,
|
|
498
|
-
review: result.text,
|
|
510
|
+
review: result.text.slice(0, 2e3),
|
|
499
511
|
verdict: approved ? "approved" : "needs_revision",
|
|
500
512
|
sessionId: result.sessionId,
|
|
501
513
|
resumed: existingSession ? result.sessionId === existingSession : false,
|
|
@@ -617,7 +629,7 @@ async function debateTurnCommand(debateId, prompt, options) {
|
|
|
617
629
|
const output = {
|
|
618
630
|
debateId,
|
|
619
631
|
round: newRound,
|
|
620
|
-
response: existing.responseText,
|
|
632
|
+
response: existing.responseText?.slice(0, 2e3) ?? "",
|
|
621
633
|
sessionId: existing.sessionId,
|
|
622
634
|
resumed: false,
|
|
623
635
|
cached: true,
|
|
@@ -662,7 +674,7 @@ async function debateTurnCommand(debateId, prompt, options) {
|
|
|
662
674
|
const output = {
|
|
663
675
|
debateId,
|
|
664
676
|
round: newRound,
|
|
665
|
-
response: recheckRow.responseText,
|
|
677
|
+
response: recheckRow.responseText?.slice(0, 2e3) ?? "",
|
|
666
678
|
sessionId: recheckRow.sessionId,
|
|
667
679
|
resumed: false,
|
|
668
680
|
cached: true,
|
|
@@ -772,7 +784,8 @@ async function debateTurnCommand(debateId, prompt, options) {
|
|
|
772
784
|
const output = {
|
|
773
785
|
debateId,
|
|
774
786
|
round: newRound,
|
|
775
|
-
response: result.text,
|
|
787
|
+
response: result.text.slice(0, 2e3),
|
|
788
|
+
responseTruncated: result.text.length > 2e3,
|
|
776
789
|
sessionId: result.sessionId,
|
|
777
790
|
resumed,
|
|
778
791
|
cached: false,
|
|
@@ -1190,7 +1203,9 @@ Scan complete in ${(durationMs / 1e3).toFixed(1)}s`));
|
|
|
1190
1203
|
writeFileSync4(options.output, JSON.stringify(report, null, 2), "utf-8");
|
|
1191
1204
|
console.error(chalk5.green(` Findings written to ${options.output}`));
|
|
1192
1205
|
}
|
|
1193
|
-
|
|
1206
|
+
const reportJson = JSON.stringify(report, null, 2);
|
|
1207
|
+
console.log(reportJson.length > 2e3 ? `${reportJson.slice(0, 2e3)}
|
|
1208
|
+
... (truncated, ${reportJson.length} chars total \u2014 use --output to save full report)` : reportJson);
|
|
1194
1209
|
db.close();
|
|
1195
1210
|
} catch (error) {
|
|
1196
1211
|
db?.close();
|
|
@@ -2078,6 +2093,59 @@ Ask if user wants to fix and re-review. If yes:
|
|
|
2078
2093
|
- **No arg size limits**: Content is piped via stdin, not passed as CLI args.
|
|
2079
2094
|
- **Presets**: Use --preset for specialized reviews (security-audit, performance, quick-scan, pre-commit, api-review).
|
|
2080
2095
|
- **Background mode**: Add --background to enqueue and continue working.
|
|
2096
|
+
- **Output capped**: JSON \`review\` field is capped to 2KB to prevent terminal crashes. Full text is stored in session_events.
|
|
2097
|
+
- **Progress**: Heartbeat every 60s, throttled to 30s intervals. No agent_message dumps.
|
|
2098
|
+
`
|
|
2099
|
+
},
|
|
2100
|
+
{
|
|
2101
|
+
path: ".claude/skills/plan-review/SKILL.md",
|
|
2102
|
+
description: "/plan-review \u2014 GPT review of execution plans",
|
|
2103
|
+
content: `---
|
|
2104
|
+
name: plan-review
|
|
2105
|
+
description: Send execution plans to GPT for structured review via Codex CLI. Use when you have a written plan and want independent validation before implementation.
|
|
2106
|
+
user-invocable: true
|
|
2107
|
+
---
|
|
2108
|
+
|
|
2109
|
+
# /plan-review \u2014 GPT Review of Execution Plans
|
|
2110
|
+
|
|
2111
|
+
## Usage
|
|
2112
|
+
\`/plan-review <plan-file-or-description>\`
|
|
2113
|
+
|
|
2114
|
+
## Description
|
|
2115
|
+
Sends a Claude-authored execution plan to GPT via \`codemoot plan review\` for structured critique. GPT returns ISSUE (HIGH/MEDIUM/LOW) and SUGGEST lines with file-level references.
|
|
2116
|
+
|
|
2117
|
+
## Instructions
|
|
2118
|
+
|
|
2119
|
+
### Step 1: Prepare the plan
|
|
2120
|
+
- If the user provides a file path, use it directly
|
|
2121
|
+
- If reviewing the current conversation's plan, write to a temp file first
|
|
2122
|
+
|
|
2123
|
+
### Step 2: Run plan review
|
|
2124
|
+
\`\`\`bash
|
|
2125
|
+
codemoot plan review <plan-file> [--phase N] [--build BUILD_ID] [--timeout ms] [--output file]
|
|
2126
|
+
\`\`\`
|
|
2127
|
+
|
|
2128
|
+
### Step 3: Parse and present output
|
|
2129
|
+
JSON output includes: \`verdict\`, \`score\`, \`issues[]\` (severity + message), \`suggestions[]\`.
|
|
2130
|
+
|
|
2131
|
+
Present as:
|
|
2132
|
+
\`\`\`
|
|
2133
|
+
## GPT Plan Review
|
|
2134
|
+
**Score**: X/10 | **Verdict**: APPROVED/NEEDS_REVISION
|
|
2135
|
+
### Issues
|
|
2136
|
+
- [HIGH] description
|
|
2137
|
+
### Suggestions
|
|
2138
|
+
- suggestion text
|
|
2139
|
+
\`\`\`
|
|
2140
|
+
|
|
2141
|
+
### Step 4: If NEEDS_REVISION
|
|
2142
|
+
Fix HIGH issues, revise the plan, re-run. Session resume gives GPT context of prior review.
|
|
2143
|
+
|
|
2144
|
+
### Important Notes
|
|
2145
|
+
- **Session resume**: GPT remembers prior reviews via thread resume
|
|
2146
|
+
- **Codebase access**: GPT reads actual project files to verify plan references
|
|
2147
|
+
- **Output capped**: JSON \`review\` field is capped to 2KB. Use \`--output\` for full text
|
|
2148
|
+
- **Zero API cost**: Uses ChatGPT subscription via Codex CLI
|
|
2081
2149
|
`
|
|
2082
2150
|
},
|
|
2083
2151
|
{
|
|
@@ -2149,6 +2217,8 @@ Present: final position, agreements, disagreements, stats.
|
|
|
2149
2217
|
3. State persisted to SQLite
|
|
2150
2218
|
4. Zero API cost (ChatGPT subscription)
|
|
2151
2219
|
5. 600s default timeout per turn
|
|
2220
|
+
6. JSON \`response\` field capped to 2KB \u2014 check \`responseTruncated\` boolean
|
|
2221
|
+
7. Progress heartbeat every 60s, throttled to 30s intervals
|
|
2152
2222
|
`
|
|
2153
2223
|
},
|
|
2154
2224
|
{
|
|
@@ -2181,6 +2251,14 @@ Use /debate protocol. Loop until GPT says STANCE: SUPPORT.
|
|
|
2181
2251
|
- Send detailed implementation plan to GPT
|
|
2182
2252
|
- Revise on OPPOSE/UNCERTAIN \u2014 never skip
|
|
2183
2253
|
|
|
2254
|
+
### Phase 1.25: Plan Review (Recommended)
|
|
2255
|
+
Before user approval, validate the plan with GPT:
|
|
2256
|
+
\`\`\`bash
|
|
2257
|
+
codemoot plan review /path/to/plan.md --build BUILD_ID
|
|
2258
|
+
\`\`\`
|
|
2259
|
+
GPT reviews the plan against actual codebase, returns ISSUE/SUGGEST lines.
|
|
2260
|
+
Fix HIGH issues before presenting to user.
|
|
2261
|
+
|
|
2184
2262
|
### Phase 1.5: User Approval Gate
|
|
2185
2263
|
Present agreed plan. Wait for explicit approval via AskUserQuestion.
|
|
2186
2264
|
\`\`\`bash
|
|
@@ -2271,25 +2349,31 @@ For Claude/GPT disagreements, optionally debate via \`codemoot debate turn\`.
|
|
|
2271
2349
|
content: `# Codex Liaison Agent
|
|
2272
2350
|
|
|
2273
2351
|
## Role
|
|
2274
|
-
Specialized teammate that communicates with GPT via
|
|
2352
|
+
Specialized teammate that communicates with GPT via codemoot CLI to get independent reviews and iterate until quality threshold is met.
|
|
2275
2353
|
|
|
2276
2354
|
## How You Work
|
|
2277
|
-
1. Send content to GPT via \`
|
|
2278
|
-
2. Parse
|
|
2279
|
-
3. If
|
|
2280
|
-
4. Loop until
|
|
2355
|
+
1. Send content to GPT via \`codemoot review\` or \`codemoot plan review\`
|
|
2356
|
+
2. Parse JSON output for score, verdict, findings
|
|
2357
|
+
3. If NEEDS_REVISION: fix issues and re-submit (session resume retains context)
|
|
2358
|
+
4. Loop until APPROVED or max iterations
|
|
2281
2359
|
5. Report final version back to team lead
|
|
2282
2360
|
|
|
2283
|
-
##
|
|
2361
|
+
## Available Commands
|
|
2284
2362
|
\`\`\`bash
|
|
2285
|
-
|
|
2363
|
+
codemoot review <file-or-glob> # Code review
|
|
2364
|
+
codemoot review --diff HEAD~3..HEAD # Diff review
|
|
2365
|
+
codemoot plan review <plan-file> # Plan review
|
|
2366
|
+
codemoot debate turn DEBATE_ID "prompt" # Debate turn
|
|
2367
|
+
codemoot fix <file-or-glob> # Autofix loop
|
|
2286
2368
|
\`\`\`
|
|
2287
2369
|
|
|
2288
2370
|
## Important Rules
|
|
2289
|
-
- NEVER fabricate GPT's responses
|
|
2371
|
+
- NEVER fabricate GPT's responses \u2014 always parse actual JSON output
|
|
2290
2372
|
- NEVER skip iterations if GPT says NEEDS_REVISION
|
|
2291
2373
|
- Use your own judgment when GPT's feedback conflicts with project requirements
|
|
2292
|
-
-
|
|
2374
|
+
- JSON \`review\`/\`response\` fields are capped to 2KB; use \`--output\` for full text
|
|
2375
|
+
- All commands use session resume \u2014 GPT retains context across calls
|
|
2376
|
+
- Zero API cost (ChatGPT subscription via Codex CLI)
|
|
2293
2377
|
`
|
|
2294
2378
|
}
|
|
2295
2379
|
];
|
|
@@ -2313,6 +2397,8 @@ This project uses [CodeMoot](https://github.com/katarmal-ram/codemoot) for Claud
|
|
|
2313
2397
|
- \`codemoot fix <file>\` \u2014 Autofix loop: review \u2192 apply fixes \u2192 re-review
|
|
2314
2398
|
- \`codemoot debate start "topic"\` \u2014 Multi-round Claude vs GPT debate
|
|
2315
2399
|
- \`codemoot cleanup\` \u2014 Scan for unused deps, dead code, duplicates
|
|
2400
|
+
- \`codemoot plan generate "task"\` \u2014 Generate plans via multi-model loop
|
|
2401
|
+
- \`codemoot plan review <plan-file>\` \u2014 GPT review of execution plans
|
|
2316
2402
|
- \`codemoot shipit --profile safe\` \u2014 Composite workflow (lint+test+review)
|
|
2317
2403
|
- \`codemoot cost\` \u2014 Token usage dashboard
|
|
2318
2404
|
- \`codemoot doctor\` \u2014 Check prerequisites
|
|
@@ -2320,6 +2406,7 @@ This project uses [CodeMoot](https://github.com/katarmal-ram/codemoot) for Claud
|
|
|
2320
2406
|
### Slash Commands
|
|
2321
2407
|
- \`/codex-review\` \u2014 Quick GPT review (uses codemoot review internally)
|
|
2322
2408
|
- \`/debate\` \u2014 Start a Claude vs GPT debate
|
|
2409
|
+
- \`/plan-review\` \u2014 GPT review of execution plans
|
|
2323
2410
|
- \`/build\` \u2014 Full build loop: debate \u2192 plan \u2192 implement \u2192 GPT review \u2192 fix
|
|
2324
2411
|
- \`/cleanup\` \u2014 Bidirectional AI slop scanner
|
|
2325
2412
|
|
|
@@ -2376,8 +2463,9 @@ async function installSkillsCommand(options) {
|
|
|
2376
2463
|
if (options.force) {
|
|
2377
2464
|
const markerIdx = existing.indexOf(marker);
|
|
2378
2465
|
const before = existing.slice(0, markerIdx);
|
|
2379
|
-
const
|
|
2380
|
-
const
|
|
2466
|
+
const sectionEnd = existing.indexOf(CLAUDE_MD_SECTION.trimEnd(), markerIdx);
|
|
2467
|
+
const afterMarker = sectionEnd >= 0 ? existing.slice(sectionEnd + CLAUDE_MD_SECTION.trimEnd().length) : existing.slice(markerIdx + marker.length);
|
|
2468
|
+
const nextHeadingMatch = sectionEnd >= 0 ? null : afterMarker.match(/\n#{1,6} (?!CodeMoot)/);
|
|
2381
2469
|
const after = nextHeadingMatch ? afterMarker.slice(nextHeadingMatch.index) : "";
|
|
2382
2470
|
writeFileSync2(claudeMdPath, before.trimEnd() + "\n" + CLAUDE_MD_SECTION + after, "utf-8");
|
|
2383
2471
|
console.error(chalk11.green(" OK CLAUDE.md (updated CodeMoot section)"));
|
|
@@ -2432,7 +2520,7 @@ ${CLAUDE_MD_SECTION}`, "utf-8");
|
|
|
2432
2520
|
console.error("");
|
|
2433
2521
|
console.error(chalk11.cyan(` Installed: ${installed}, Skipped: ${skipped}`));
|
|
2434
2522
|
console.error("");
|
|
2435
|
-
console.error(chalk11.dim(" Slash commands: /codex-review, /debate, /build, /cleanup"));
|
|
2523
|
+
console.error(chalk11.dim(" Slash commands: /codex-review, /debate, /plan-review, /build, /cleanup"));
|
|
2436
2524
|
console.error(chalk11.dim(" CLAUDE.md: Claude now knows about codemoot commands & sessions"));
|
|
2437
2525
|
console.error(chalk11.dim(" Hook: Post-commit hint to run codemoot review"));
|
|
2438
2526
|
console.error("");
|
|
@@ -2723,8 +2811,15 @@ async function jobsStatusCommand(jobId) {
|
|
|
2723
2811
|
}
|
|
2724
2812
|
|
|
2725
2813
|
// src/commands/plan.ts
|
|
2726
|
-
import { writeFileSync as writeFileSync3 } from "fs";
|
|
2727
|
-
import {
|
|
2814
|
+
import { readFileSync as readFileSync4, writeFileSync as writeFileSync3 } from "fs";
|
|
2815
|
+
import {
|
|
2816
|
+
ModelRegistry as ModelRegistry4,
|
|
2817
|
+
Orchestrator,
|
|
2818
|
+
SessionManager as SessionManager7,
|
|
2819
|
+
buildHandoffEnvelope as buildHandoffEnvelope4,
|
|
2820
|
+
loadConfig as loadConfig6,
|
|
2821
|
+
openDatabase as openDatabase9
|
|
2822
|
+
} from "@codemoot/core";
|
|
2728
2823
|
import chalk15 from "chalk";
|
|
2729
2824
|
|
|
2730
2825
|
// src/render.ts
|
|
@@ -2819,19 +2914,14 @@ function printSessionSummary(result) {
|
|
|
2819
2914
|
}
|
|
2820
2915
|
|
|
2821
2916
|
// src/commands/plan.ts
|
|
2822
|
-
async function
|
|
2917
|
+
async function planGenerateCommand(task, options) {
|
|
2918
|
+
let db;
|
|
2823
2919
|
try {
|
|
2824
2920
|
const config = loadConfig6();
|
|
2825
2921
|
const projectDir = process.cwd();
|
|
2826
2922
|
const registry = ModelRegistry4.fromConfig(config, projectDir);
|
|
2827
|
-
const health = await registry.healthCheckAll();
|
|
2828
|
-
for (const [alias, hasKey] of health) {
|
|
2829
|
-
if (!hasKey) {
|
|
2830
|
-
console.warn(chalk15.yellow(`Warning: No API key for model "${alias}"`));
|
|
2831
|
-
}
|
|
2832
|
-
}
|
|
2833
2923
|
const dbPath = getDbPath();
|
|
2834
|
-
|
|
2924
|
+
db = openDatabase9(dbPath);
|
|
2835
2925
|
const orchestrator = new Orchestrator({ registry, db, config });
|
|
2836
2926
|
orchestrator.on("event", (event) => renderEvent(event, config));
|
|
2837
2927
|
const result = await orchestrator.plan(task, {
|
|
@@ -2839,22 +2929,172 @@ async function planCommand(task, options) {
|
|
|
2839
2929
|
});
|
|
2840
2930
|
if (options.output) {
|
|
2841
2931
|
writeFileSync3(options.output, result.finalOutput, "utf-8");
|
|
2842
|
-
console.
|
|
2932
|
+
console.error(chalk15.green(`Plan saved to ${options.output}`));
|
|
2843
2933
|
}
|
|
2844
2934
|
printSessionSummary(result);
|
|
2845
2935
|
db.close();
|
|
2846
2936
|
process.exit(result.status === "completed" ? 0 : 2);
|
|
2847
2937
|
} catch (error) {
|
|
2938
|
+
db?.close();
|
|
2939
|
+
console.error(chalk15.red(`Error: ${error instanceof Error ? error.message : String(error)}`));
|
|
2940
|
+
process.exit(1);
|
|
2941
|
+
}
|
|
2942
|
+
}
|
|
2943
|
+
async function planReviewCommand(planFile, options) {
|
|
2944
|
+
let db;
|
|
2945
|
+
try {
|
|
2946
|
+
let planContent;
|
|
2947
|
+
if (planFile === "-") {
|
|
2948
|
+
const chunks = [];
|
|
2949
|
+
for await (const chunk of process.stdin) {
|
|
2950
|
+
chunks.push(chunk);
|
|
2951
|
+
}
|
|
2952
|
+
planContent = Buffer.concat(chunks).toString("utf-8");
|
|
2953
|
+
} else {
|
|
2954
|
+
planContent = readFileSync4(planFile, "utf-8");
|
|
2955
|
+
}
|
|
2956
|
+
if (!planContent.trim()) {
|
|
2957
|
+
console.error(chalk15.red("Plan file is empty."));
|
|
2958
|
+
process.exit(1);
|
|
2959
|
+
}
|
|
2960
|
+
const config = loadConfig6();
|
|
2961
|
+
const projectDir = process.cwd();
|
|
2962
|
+
const registry = ModelRegistry4.fromConfig(config, projectDir);
|
|
2963
|
+
const adapter = registry.tryGetAdapter("codex-reviewer") ?? registry.tryGetAdapter("codex-architect");
|
|
2964
|
+
if (!adapter) {
|
|
2965
|
+
console.error(chalk15.red("No codex adapter found in config. Run: codemoot init"));
|
|
2966
|
+
process.exit(1);
|
|
2967
|
+
}
|
|
2968
|
+
const dbPath = getDbPath();
|
|
2969
|
+
db = openDatabase9(dbPath);
|
|
2970
|
+
const sessionMgr = new SessionManager7(db);
|
|
2971
|
+
const session2 = sessionMgr.resolveActive("plan-review");
|
|
2972
|
+
const overflowCheck = sessionMgr.preCallOverflowCheck(session2.id);
|
|
2973
|
+
if (overflowCheck.rolled) {
|
|
2974
|
+
console.error(chalk15.yellow(` ${overflowCheck.message}`));
|
|
2975
|
+
}
|
|
2976
|
+
const currentSession = sessionMgr.get(session2.id);
|
|
2977
|
+
const threadId = overflowCheck.rolled ? void 0 : currentSession?.codexThreadId ?? void 0;
|
|
2978
|
+
const phaseContext = options.phase ? `
|
|
2979
|
+
This is Phase ${options.phase} of a multi-phase plan.` : "";
|
|
2980
|
+
const buildContext = options.build ? `
|
|
2981
|
+
Build ID: ${options.build}` : "";
|
|
2982
|
+
const prompt = buildHandoffEnvelope4({
|
|
2983
|
+
command: "plan-review",
|
|
2984
|
+
task: `Review the following execution plan for completeness, correctness, and feasibility. Read relevant codebase files to verify the plan's assumptions.${phaseContext}${buildContext}
|
|
2985
|
+
|
|
2986
|
+
PLAN:
|
|
2987
|
+
${planContent.slice(0, 5e4)}
|
|
2988
|
+
|
|
2989
|
+
Review criteria:
|
|
2990
|
+
1. Are all files/functions mentioned actually present in the codebase?
|
|
2991
|
+
2. Are there missing steps or dependencies between phases?
|
|
2992
|
+
3. Are there architectural concerns or better approaches?
|
|
2993
|
+
4. Is the scope realistic for the described phases?
|
|
2994
|
+
5. Are there security or performance concerns?
|
|
2995
|
+
|
|
2996
|
+
Output format:
|
|
2997
|
+
- For each issue, output: ISSUE: [HIGH|MEDIUM|LOW] <description>
|
|
2998
|
+
- For each suggestion, output: SUGGEST: <description>
|
|
2999
|
+
- End with: VERDICT: APPROVED or VERDICT: NEEDS_REVISION
|
|
3000
|
+
- End with: SCORE: X/10`,
|
|
3001
|
+
constraints: [
|
|
3002
|
+
"Verify file paths and function names against the actual codebase before flagging issues.",
|
|
3003
|
+
"Be specific \u2014 reference exact files and line numbers when possible.",
|
|
3004
|
+
"Focus on feasibility, not style preferences."
|
|
3005
|
+
],
|
|
3006
|
+
resumed: Boolean(threadId)
|
|
3007
|
+
});
|
|
3008
|
+
const timeoutMs = (options.timeout ?? 300) * 1e3;
|
|
3009
|
+
const progress = createProgressCallbacks("plan-review");
|
|
3010
|
+
console.error(chalk15.cyan("Sending plan to codex for review..."));
|
|
3011
|
+
const result = await adapter.callWithResume(prompt, {
|
|
3012
|
+
sessionId: threadId,
|
|
3013
|
+
timeout: timeoutMs,
|
|
3014
|
+
...progress
|
|
3015
|
+
});
|
|
3016
|
+
if (result.sessionId) {
|
|
3017
|
+
sessionMgr.updateThreadId(session2.id, result.sessionId);
|
|
3018
|
+
}
|
|
3019
|
+
sessionMgr.addUsageFromResult(session2.id, result.usage, prompt, result.text);
|
|
3020
|
+
sessionMgr.recordEvent({
|
|
3021
|
+
sessionId: session2.id,
|
|
3022
|
+
command: "plan",
|
|
3023
|
+
subcommand: "review",
|
|
3024
|
+
promptPreview: `Plan review: ${planFile}${options.phase ? ` (phase ${options.phase})` : ""}`,
|
|
3025
|
+
responsePreview: result.text.slice(0, 500),
|
|
3026
|
+
promptFull: prompt,
|
|
3027
|
+
responseFull: result.text,
|
|
3028
|
+
usageJson: JSON.stringify(result.usage),
|
|
3029
|
+
durationMs: result.durationMs,
|
|
3030
|
+
codexThreadId: result.sessionId
|
|
3031
|
+
});
|
|
3032
|
+
const tail = result.text.slice(-500);
|
|
3033
|
+
const verdictMatch = tail.match(/VERDICT:\s*(APPROVED|NEEDS_REVISION)/i);
|
|
3034
|
+
const scoreMatch = tail.match(/SCORE:\s*(\d+)\/10/);
|
|
3035
|
+
const verdict = verdictMatch ? verdictMatch[1].toLowerCase() : "unknown";
|
|
3036
|
+
const score = scoreMatch ? Number.parseInt(scoreMatch[1], 10) : null;
|
|
3037
|
+
const issues = [];
|
|
3038
|
+
const suggestions = [];
|
|
3039
|
+
for (const line of result.text.split("\n")) {
|
|
3040
|
+
const issueMatch = line.match(/^[-*]?\s*ISSUE:\s*\[(HIGH|MEDIUM|LOW)]\s*(.*)/i);
|
|
3041
|
+
if (issueMatch) {
|
|
3042
|
+
issues.push({ severity: issueMatch[1].toLowerCase(), message: issueMatch[2].trim() });
|
|
3043
|
+
}
|
|
3044
|
+
const suggestMatch = line.match(/^[-*]?\s*SUGGEST:\s*(.*)/i);
|
|
3045
|
+
if (suggestMatch) {
|
|
3046
|
+
suggestions.push(suggestMatch[1].trim());
|
|
3047
|
+
}
|
|
3048
|
+
}
|
|
3049
|
+
const verdictColor = verdict === "approved" ? chalk15.green : chalk15.red;
|
|
3050
|
+
console.error(verdictColor(`
|
|
3051
|
+
Verdict: ${verdict.toUpperCase()} (${score ?? "?"}/10)`));
|
|
3052
|
+
if (issues.length > 0) {
|
|
3053
|
+
console.error(chalk15.yellow(`Issues (${issues.length}):`));
|
|
3054
|
+
for (const issue of issues) {
|
|
3055
|
+
const sevColor = issue.severity === "high" ? chalk15.red : issue.severity === "medium" ? chalk15.yellow : chalk15.dim;
|
|
3056
|
+
console.error(` ${sevColor(issue.severity.toUpperCase())} ${issue.message}`);
|
|
3057
|
+
}
|
|
3058
|
+
}
|
|
3059
|
+
if (suggestions.length > 0) {
|
|
3060
|
+
console.error(chalk15.cyan(`Suggestions (${suggestions.length}):`));
|
|
3061
|
+
for (const s of suggestions) {
|
|
3062
|
+
console.error(` ${chalk15.dim("\u2192")} ${s}`);
|
|
3063
|
+
}
|
|
3064
|
+
}
|
|
3065
|
+
console.error(chalk15.dim(`Duration: ${(result.durationMs / 1e3).toFixed(1)}s | Tokens: ${result.usage.totalTokens}`));
|
|
3066
|
+
const output = {
|
|
3067
|
+
planFile,
|
|
3068
|
+
phase: options.phase ?? null,
|
|
3069
|
+
buildId: options.build ?? null,
|
|
3070
|
+
verdict,
|
|
3071
|
+
score,
|
|
3072
|
+
issues,
|
|
3073
|
+
suggestions,
|
|
3074
|
+
review: result.text.slice(0, 2e3),
|
|
3075
|
+
sessionId: result.sessionId,
|
|
3076
|
+
resumed: threadId ? result.sessionId === threadId : false,
|
|
3077
|
+
usage: result.usage,
|
|
3078
|
+
durationMs: result.durationMs
|
|
3079
|
+
};
|
|
3080
|
+
console.log(JSON.stringify(output, null, 2));
|
|
3081
|
+
if (options.output) {
|
|
3082
|
+
writeFileSync3(options.output, JSON.stringify(output, null, 2), "utf-8");
|
|
3083
|
+
console.error(chalk15.green(`Review saved to ${options.output}`));
|
|
3084
|
+
}
|
|
3085
|
+
db.close();
|
|
3086
|
+
} catch (error) {
|
|
3087
|
+
db?.close();
|
|
2848
3088
|
console.error(chalk15.red(`Error: ${error instanceof Error ? error.message : String(error)}`));
|
|
2849
3089
|
process.exit(1);
|
|
2850
3090
|
}
|
|
2851
3091
|
}
|
|
2852
3092
|
|
|
2853
3093
|
// src/commands/review.ts
|
|
2854
|
-
import { loadConfig as loadConfig7, ModelRegistry as ModelRegistry5, BINARY_SNIFF_BYTES, REVIEW_DIFF_MAX_CHARS as REVIEW_DIFF_MAX_CHARS3, SessionManager as
|
|
3094
|
+
import { loadConfig as loadConfig7, ModelRegistry as ModelRegistry5, BINARY_SNIFF_BYTES, REVIEW_DIFF_MAX_CHARS as REVIEW_DIFF_MAX_CHARS3, SessionManager as SessionManager8, JobStore as JobStore3, openDatabase as openDatabase10, buildHandoffEnvelope as buildHandoffEnvelope5, getReviewPreset } from "@codemoot/core";
|
|
2855
3095
|
import chalk16 from "chalk";
|
|
2856
3096
|
import { execFileSync as execFileSync3, execSync as execSync4 } from "child_process";
|
|
2857
|
-
import { closeSync, globSync, openSync, readFileSync as
|
|
3097
|
+
import { closeSync, globSync, openSync, readFileSync as readFileSync5, readSync, statSync, existsSync as existsSync4 } from "fs";
|
|
2858
3098
|
import { resolve as resolve2 } from "path";
|
|
2859
3099
|
var MAX_FILE_SIZE = 100 * 1024;
|
|
2860
3100
|
var MAX_TOTAL_SIZE = 200 * 1024;
|
|
@@ -2916,7 +3156,7 @@ async function reviewCommand(fileOrGlob, options) {
|
|
|
2916
3156
|
return;
|
|
2917
3157
|
}
|
|
2918
3158
|
const db = openDatabase10(getDbPath());
|
|
2919
|
-
const sessionMgr = new
|
|
3159
|
+
const sessionMgr = new SessionManager8(db);
|
|
2920
3160
|
const session2 = options.session ? sessionMgr.get(options.session) : sessionMgr.resolveActive("review");
|
|
2921
3161
|
if (!session2) {
|
|
2922
3162
|
console.error(chalk16.red(options.session ? `Session not found: ${options.session}` : "No active session. Run: codemoot init"));
|
|
@@ -2956,7 +3196,7 @@ async function reviewCommand(fileOrGlob, options) {
|
|
|
2956
3196
|
process.exit(1);
|
|
2957
3197
|
}
|
|
2958
3198
|
}
|
|
2959
|
-
prompt =
|
|
3199
|
+
prompt = buildHandoffEnvelope5({
|
|
2960
3200
|
command: "review",
|
|
2961
3201
|
task: `TASK: ${instruction}
|
|
2962
3202
|
|
|
@@ -2985,7 +3225,7 @@ Start by listing candidate files, then inspect them thoroughly.`,
|
|
|
2985
3225
|
db.close();
|
|
2986
3226
|
process.exit(0);
|
|
2987
3227
|
}
|
|
2988
|
-
prompt =
|
|
3228
|
+
prompt = buildHandoffEnvelope5({
|
|
2989
3229
|
command: "review",
|
|
2990
3230
|
task: `Review the following code changes.
|
|
2991
3231
|
|
|
@@ -3031,7 +3271,7 @@ ${diff.slice(0, REVIEW_DIFF_MAX_CHARS3)}`,
|
|
|
3031
3271
|
console.error(chalk16.yellow(`Skipping ${filePath} (binary file)`));
|
|
3032
3272
|
continue;
|
|
3033
3273
|
}
|
|
3034
|
-
const content =
|
|
3274
|
+
const content = readFileSync5(filePath, "utf-8");
|
|
3035
3275
|
const relativePath = filePath.replace(projectDir, "").replace(/\\/g, "/").replace(/^\//, "");
|
|
3036
3276
|
files.push({ path: relativePath, content });
|
|
3037
3277
|
totalSize += stat.size;
|
|
@@ -3043,7 +3283,7 @@ ${diff.slice(0, REVIEW_DIFF_MAX_CHARS3)}`,
|
|
|
3043
3283
|
}
|
|
3044
3284
|
const fileContents = files.map((f) => `--- ${f.path} ---
|
|
3045
3285
|
${f.content}`).join("\n\n");
|
|
3046
|
-
prompt =
|
|
3286
|
+
prompt = buildHandoffEnvelope5({
|
|
3047
3287
|
command: "review",
|
|
3048
3288
|
task: `Review the following code files.
|
|
3049
3289
|
|
|
@@ -3098,7 +3338,7 @@ ${fileContents}`,
|
|
|
3098
3338
|
findings,
|
|
3099
3339
|
verdict: verdictMatch ? verdictMatch[1].toLowerCase() : "unknown",
|
|
3100
3340
|
score: scoreMatch ? Number.parseInt(scoreMatch[1], 10) : null,
|
|
3101
|
-
review: result.text,
|
|
3341
|
+
review: result.text.slice(0, 2e3),
|
|
3102
3342
|
sessionId: session2.id,
|
|
3103
3343
|
codexThreadId: result.sessionId,
|
|
3104
3344
|
resumed: sessionThreadId ? result.sessionId === sessionThreadId : false,
|
|
@@ -3566,7 +3806,7 @@ import {
|
|
|
3566
3806
|
JobStore as JobStore5,
|
|
3567
3807
|
ModelRegistry as ModelRegistry7,
|
|
3568
3808
|
REVIEW_DIFF_MAX_CHARS as REVIEW_DIFF_MAX_CHARS4,
|
|
3569
|
-
buildHandoffEnvelope as
|
|
3809
|
+
buildHandoffEnvelope as buildHandoffEnvelope6,
|
|
3570
3810
|
loadConfig as loadConfig10,
|
|
3571
3811
|
openDatabase as openDatabase13
|
|
3572
3812
|
} from "@codemoot/core";
|
|
@@ -3630,7 +3870,7 @@ async function workerCommand(options) {
|
|
|
3630
3870
|
const focus = payload.focus ?? "all";
|
|
3631
3871
|
const focusConstraint = focus === "all" ? "Review for: correctness, bugs, security, performance, code quality" : `Focus specifically on: ${focus}`;
|
|
3632
3872
|
if (payload.prompt) {
|
|
3633
|
-
prompt =
|
|
3873
|
+
prompt = buildHandoffEnvelope6({
|
|
3634
3874
|
command: "review",
|
|
3635
3875
|
task: `TASK: ${payload.prompt}
|
|
3636
3876
|
|
|
@@ -3651,7 +3891,7 @@ Start by listing candidate files, then inspect them thoroughly.`,
|
|
|
3651
3891
|
encoding: "utf-8",
|
|
3652
3892
|
maxBuffer: 1024 * 1024
|
|
3653
3893
|
});
|
|
3654
|
-
prompt =
|
|
3894
|
+
prompt = buildHandoffEnvelope6({
|
|
3655
3895
|
command: "review",
|
|
3656
3896
|
task: `Review these code changes.
|
|
3657
3897
|
|
|
@@ -3661,14 +3901,14 @@ ${diff.slice(0, REVIEW_DIFF_MAX_CHARS4)}`,
|
|
|
3661
3901
|
resumed: false
|
|
3662
3902
|
});
|
|
3663
3903
|
} else if (payload.files && Array.isArray(payload.files)) {
|
|
3664
|
-
prompt =
|
|
3904
|
+
prompt = buildHandoffEnvelope6({
|
|
3665
3905
|
command: "review",
|
|
3666
3906
|
task: `Review these files: ${payload.files.join(", ")}. Read each file and report issues.`,
|
|
3667
3907
|
constraints: [focusConstraint],
|
|
3668
3908
|
resumed: false
|
|
3669
3909
|
});
|
|
3670
3910
|
} else {
|
|
3671
|
-
prompt =
|
|
3911
|
+
prompt = buildHandoffEnvelope6({
|
|
3672
3912
|
command: "review",
|
|
3673
3913
|
task: "Review the codebase for issues. Start by listing key files.",
|
|
3674
3914
|
constraints: [focusConstraint],
|
|
@@ -3676,7 +3916,7 @@ ${diff.slice(0, REVIEW_DIFF_MAX_CHARS4)}`,
|
|
|
3676
3916
|
});
|
|
3677
3917
|
}
|
|
3678
3918
|
} else if (job.type === "cleanup") {
|
|
3679
|
-
prompt =
|
|
3919
|
+
prompt = buildHandoffEnvelope6({
|
|
3680
3920
|
command: "cleanup",
|
|
3681
3921
|
task: `Scan ${cwd} for: unused dependencies, dead code, duplicates, hardcoded values. Report findings with confidence levels.`,
|
|
3682
3922
|
constraints: [`Scope: ${payload.scope ?? "all"}`],
|
|
@@ -3741,7 +3981,9 @@ program.command("cleanup").description("Scan codebase for AI slop: security vuln
|
|
|
3741
3981
|
if (!/^\d+$/.test(v)) throw new InvalidArgumentError("Must be a non-negative integer");
|
|
3742
3982
|
return Number.parseInt(v, 10);
|
|
3743
3983
|
}, 10).option("--host-findings <path>", "JSON file with host AI findings for 3-way merge").option("--output <path>", "Write findings report to JSON file").option("--background", "Enqueue cleanup and return immediately").option("--no-gitignore", "Skip .gitignore rules (scan everything)").option("--quiet", "Suppress human-readable summary").action(cleanupCommand);
|
|
3744
|
-
|
|
3984
|
+
var plan = program.command("plan").description("Plan generation and review \u2014 write plans, get GPT review");
|
|
3985
|
+
plan.command("generate").description("Generate a plan using architect + reviewer loop").argument("<task>", "Task to plan").option("--rounds <n>", "Max plan-review rounds", (v) => Number.parseInt(v, 10), 3).option("--output <file>", "Save plan to file").action(planGenerateCommand);
|
|
3986
|
+
plan.command("review").description("Send a host-authored plan to codex for review").argument("<plan-file>", "Plan file to review (use - for stdin)").option("--build <id>", "Link review to a build ID").option("--phase <id>", 'Phase identifier (e.g. "1", "setup")').option("--timeout <seconds>", "Review timeout", (v) => Number.parseInt(v, 10), 300).option("--output <file>", "Save review result to file").action(planReviewCommand);
|
|
3745
3987
|
var debate = program.command("debate").description("Multi-model debate with session persistence");
|
|
3746
3988
|
debate.command("start").description("Start a new debate").argument("<topic>", "Debate topic or question").option("--max-rounds <n>", "Max debate rounds", (v) => Number.parseInt(v, 10), 5).action(debateStartCommand);
|
|
3747
3989
|
debate.command("turn").description("Send a prompt to GPT and get critique (with session resume)").argument("<debate-id>", "Debate ID from start command").argument("<prompt>", "Prompt to send to GPT").option("--round <n>", "Round number", (v) => Number.parseInt(v, 10)).option("--timeout <seconds>", "Timeout in seconds", (v) => Number.parseInt(v, 10), 600).action(debateTurnCommand);
|