@nk070281sjv/cli 2.3.6 → 2.3.8
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 +440 -30
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -30779,7 +30779,7 @@ ${hint}
|
|
|
30779
30779
|
}
|
|
30780
30780
|
|
|
30781
30781
|
// src/lib/version.ts
|
|
30782
|
-
var CLI_VERSION = true ? "2.3.
|
|
30782
|
+
var CLI_VERSION = true ? "2.3.8" : createRequire(import.meta.url)("../../package.json").version;
|
|
30783
30783
|
|
|
30784
30784
|
// src/lib/deps.ts
|
|
30785
30785
|
init_src();
|
|
@@ -31821,9 +31821,9 @@ var NodeFsHandler = class {
|
|
|
31821
31821
|
if (this.fsw.closed) {
|
|
31822
31822
|
return;
|
|
31823
31823
|
}
|
|
31824
|
-
const
|
|
31824
|
+
const dirname13 = sysPath.dirname(file);
|
|
31825
31825
|
const basename9 = sysPath.basename(file);
|
|
31826
|
-
const parent = this.fsw._getWatchedDir(
|
|
31826
|
+
const parent = this.fsw._getWatchedDir(dirname13);
|
|
31827
31827
|
let prevStats = stats;
|
|
31828
31828
|
if (parent.has(basename9))
|
|
31829
31829
|
return;
|
|
@@ -31850,7 +31850,7 @@ var NodeFsHandler = class {
|
|
|
31850
31850
|
prevStats = newStats2;
|
|
31851
31851
|
}
|
|
31852
31852
|
} catch (error) {
|
|
31853
|
-
this.fsw._remove(
|
|
31853
|
+
this.fsw._remove(dirname13, basename9);
|
|
31854
31854
|
}
|
|
31855
31855
|
} else if (parent.has(basename9)) {
|
|
31856
31856
|
const at = newStats.atimeMs;
|
|
@@ -37218,7 +37218,15 @@ var teamCommand = new Command("team").description("Resolve and persist team comp
|
|
|
37218
37218
|
// src/commands/review.ts
|
|
37219
37219
|
init_src();
|
|
37220
37220
|
import { join as join28 } from "node:path";
|
|
37221
|
-
import {
|
|
37221
|
+
import {
|
|
37222
|
+
closeSync,
|
|
37223
|
+
existsSync as existsSync23,
|
|
37224
|
+
mkdirSync as mkdirSync8,
|
|
37225
|
+
openSync,
|
|
37226
|
+
readdirSync as readdirSync9,
|
|
37227
|
+
readFileSync as readFileSync17,
|
|
37228
|
+
writeFileSync as writeFileSync9
|
|
37229
|
+
} from "node:fs";
|
|
37222
37230
|
init_db();
|
|
37223
37231
|
|
|
37224
37232
|
// ../shared/config/src/pipeline-config.ts
|
|
@@ -37409,6 +37417,11 @@ async function writeRoundArtifact(roundDir, relativePath, content) {
|
|
|
37409
37417
|
import { mkdir as mkdir2, readFile, writeFile as writeFile2 } from "node:fs/promises";
|
|
37410
37418
|
import { existsSync as existsSync21 } from "node:fs";
|
|
37411
37419
|
import { dirname as dirname10, join as join25 } from "node:path";
|
|
37420
|
+
import { execFile } from "node:child_process";
|
|
37421
|
+
import { promisify } from "node:util";
|
|
37422
|
+
var execFileAsync = promisify(execFile);
|
|
37423
|
+
var GIT_SNAPSHOT_LIMIT = 2e4;
|
|
37424
|
+
var REVIEW_SNAPSHOT_EXCLUDES = [".ocr", ".opencode"];
|
|
37412
37425
|
async function prepareReviewContext(input) {
|
|
37413
37426
|
const roundDir = join25(input.sessionDir, "rounds", `round-${input.round}`);
|
|
37414
37427
|
await mkdir2(roundDir, { recursive: true });
|
|
@@ -37425,6 +37438,7 @@ async function prepareReviewContext(input) {
|
|
|
37425
37438
|
"# Review Context\n\nNo shared review context has been recorded yet.\n"
|
|
37426
37439
|
);
|
|
37427
37440
|
const reviewBriefPath = join25(roundDir, "review-brief.md");
|
|
37441
|
+
const gitSnapshot = input.cwd ? await collectGitSnapshot(input.cwd) : void 0;
|
|
37428
37442
|
await writeFile2(
|
|
37429
37443
|
reviewBriefPath,
|
|
37430
37444
|
[
|
|
@@ -37434,6 +37448,37 @@ async function prepareReviewContext(input) {
|
|
|
37434
37448
|
`Round: ${input.round}`,
|
|
37435
37449
|
`Discovered standards: ${discoveredStandardsPath}`,
|
|
37436
37450
|
`Shared context: ${contextPath}`,
|
|
37451
|
+
...input.cwd ? [`Repository root: ${input.cwd}`] : [],
|
|
37452
|
+
"",
|
|
37453
|
+
"## Scope",
|
|
37454
|
+
"",
|
|
37455
|
+
"Review the current working tree changes. Focus on changed files and the unchanged code needed to validate those changes.",
|
|
37456
|
+
"Ignore local AI tooling/config churn under `.ocr/` and `.opencode/` unless the user explicitly asks to review OCR/OpenCode setup changes.",
|
|
37457
|
+
"",
|
|
37458
|
+
...gitSnapshot ? [
|
|
37459
|
+
"## Git Status",
|
|
37460
|
+
"",
|
|
37461
|
+
fenced("text", gitSnapshot.status || "No working tree changes reported."),
|
|
37462
|
+
"",
|
|
37463
|
+
"## Changed Files",
|
|
37464
|
+
"",
|
|
37465
|
+
fenced("text", gitSnapshot.changedFiles || "No changed files reported."),
|
|
37466
|
+
"",
|
|
37467
|
+
"## Diff Stat",
|
|
37468
|
+
"",
|
|
37469
|
+
fenced("text", gitSnapshot.diffStat || "No diff stat available."),
|
|
37470
|
+
""
|
|
37471
|
+
] : [
|
|
37472
|
+
"## Git Snapshot",
|
|
37473
|
+
"",
|
|
37474
|
+
"No repository snapshot was captured by the CLI.",
|
|
37475
|
+
""
|
|
37476
|
+
],
|
|
37477
|
+
"## Reviewer Guidance",
|
|
37478
|
+
"",
|
|
37479
|
+
"- Read this brief first, then inspect relevant changed files and surrounding code as needed.",
|
|
37480
|
+
"- Prefer targeted `git diff -- <path>` and file reads over broad repository scans.",
|
|
37481
|
+
"- Report only findings supported by code evidence and concrete file references.",
|
|
37437
37482
|
""
|
|
37438
37483
|
].join("\n"),
|
|
37439
37484
|
"utf-8"
|
|
@@ -37478,6 +37523,67 @@ async function prepareReviewContext(input) {
|
|
|
37478
37523
|
requirementsExpected: Boolean(requirementsPath)
|
|
37479
37524
|
};
|
|
37480
37525
|
}
|
|
37526
|
+
async function collectGitSnapshot(cwd) {
|
|
37527
|
+
const [status, cachedFiles, worktreeFiles, diffStat] = await Promise.all([
|
|
37528
|
+
runGit(cwd, ["status", "--short"]),
|
|
37529
|
+
runGit(cwd, ["diff", "--cached", "--name-only", "--", ...gitReviewPathspec()]),
|
|
37530
|
+
runGit(cwd, ["diff", "--name-only", "HEAD", "--", ...gitReviewPathspec()]),
|
|
37531
|
+
runGit(cwd, ["diff", "--stat", "HEAD", "--", ...gitReviewPathspec()])
|
|
37532
|
+
]);
|
|
37533
|
+
return {
|
|
37534
|
+
status: filterIgnoredStatusLines(status),
|
|
37535
|
+
changedFiles: filterIgnoredPaths(uniqueLines(`${cachedFiles}
|
|
37536
|
+
${worktreeFiles}`)),
|
|
37537
|
+
diffStat
|
|
37538
|
+
};
|
|
37539
|
+
}
|
|
37540
|
+
function gitReviewPathspec() {
|
|
37541
|
+
return [".", ...REVIEW_SNAPSHOT_EXCLUDES.map((path2) => `:(exclude)${path2}`)];
|
|
37542
|
+
}
|
|
37543
|
+
async function runGit(cwd, args) {
|
|
37544
|
+
try {
|
|
37545
|
+
const { stdout, stderr } = await execFileAsync("git", args, {
|
|
37546
|
+
cwd,
|
|
37547
|
+
maxBuffer: GIT_SNAPSHOT_LIMIT * 2
|
|
37548
|
+
});
|
|
37549
|
+
return truncate(`${stdout}${stderr ? `
|
|
37550
|
+
${stderr}` : ""}`.trim());
|
|
37551
|
+
} catch (error) {
|
|
37552
|
+
if (error && typeof error === "object" && "stdout" in error && typeof error.stdout === "string") {
|
|
37553
|
+
return truncate(error.stdout.trim());
|
|
37554
|
+
}
|
|
37555
|
+
return "";
|
|
37556
|
+
}
|
|
37557
|
+
}
|
|
37558
|
+
function uniqueLines(value) {
|
|
37559
|
+
const lines = value.split(/\r?\n/).map((line) => line.trim()).filter(Boolean);
|
|
37560
|
+
return [...new Set(lines)].join("\n");
|
|
37561
|
+
}
|
|
37562
|
+
function filterIgnoredStatusLines(value) {
|
|
37563
|
+
return value.split(/\r?\n/).filter((line) => {
|
|
37564
|
+
const pathPart = line.slice(3).trim();
|
|
37565
|
+
return !isIgnoredReviewPath(pathPart);
|
|
37566
|
+
}).join("\n");
|
|
37567
|
+
}
|
|
37568
|
+
function filterIgnoredPaths(value) {
|
|
37569
|
+
return value.split(/\r?\n/).filter((line) => !isIgnoredReviewPath(line.trim())).join("\n");
|
|
37570
|
+
}
|
|
37571
|
+
function isIgnoredReviewPath(path2) {
|
|
37572
|
+
const normalized = path2.replace(/^"|"$/g, "");
|
|
37573
|
+
return REVIEW_SNAPSHOT_EXCLUDES.some(
|
|
37574
|
+
(ignored) => normalized === ignored || normalized.startsWith(`${ignored}/`)
|
|
37575
|
+
);
|
|
37576
|
+
}
|
|
37577
|
+
function truncate(value) {
|
|
37578
|
+
if (value.length <= GIT_SNAPSHOT_LIMIT) return value;
|
|
37579
|
+
return `${value.slice(0, GIT_SNAPSHOT_LIMIT)}
|
|
37580
|
+
[truncated by OCR CLI]`;
|
|
37581
|
+
}
|
|
37582
|
+
function fenced(language, value) {
|
|
37583
|
+
return `\`\`\`${language}
|
|
37584
|
+
${value}
|
|
37585
|
+
\`\`\``;
|
|
37586
|
+
}
|
|
37481
37587
|
async function ensureMarkdownFile(path2, fallbackContent) {
|
|
37482
37588
|
if (existsSync21(path2)) {
|
|
37483
37589
|
await readFile(path2, "utf-8");
|
|
@@ -37554,7 +37660,7 @@ var OpenCodeProcessRunner = class {
|
|
|
37554
37660
|
constructor(options = {}) {
|
|
37555
37661
|
this.spawn = options.spawn ?? spawnBinary;
|
|
37556
37662
|
this.promptArgvLimit = options.promptArgvLimit ?? DEFAULT_PROMPT_ARGV_LIMIT;
|
|
37557
|
-
this.serveReadyTimeoutMs = options.serveReadyTimeoutMs ??
|
|
37663
|
+
this.serveReadyTimeoutMs = options.serveReadyTimeoutMs ?? 12e4;
|
|
37558
37664
|
this.pure = options.pure ?? true;
|
|
37559
37665
|
}
|
|
37560
37666
|
async run(request, signal) {
|
|
@@ -37764,7 +37870,7 @@ function isRecord(value) {
|
|
|
37764
37870
|
|
|
37765
37871
|
// src/lib/agent-orchestrator/prompt-writer.ts
|
|
37766
37872
|
import { mkdir as mkdir3, writeFile as writeFile3 } from "node:fs/promises";
|
|
37767
|
-
import { join as join26, relative as relative3 } from "node:path";
|
|
37873
|
+
import { dirname as dirname11, join as join26, relative as relative3 } from "node:path";
|
|
37768
37874
|
async function writePromptSnapshots(input) {
|
|
37769
37875
|
const promptsDir = join26(input.context.roundDir, "prompts");
|
|
37770
37876
|
const reviewsDir = join26(input.context.roundDir, "reviews");
|
|
@@ -37833,6 +37939,7 @@ function reviewerPrompt(context, reviewer, promptPath, reviewPath) {
|
|
|
37833
37939
|
"Phase: reviews",
|
|
37834
37940
|
"",
|
|
37835
37941
|
"Input files to read before reviewing:",
|
|
37942
|
+
`- ${reviewerPersonaPath(context, reviewer)}`,
|
|
37836
37943
|
`- ${context.discoveredStandardsPath}`,
|
|
37837
37944
|
`- ${context.contextPath}`,
|
|
37838
37945
|
`- ${context.reviewBriefPath}`,
|
|
@@ -37846,8 +37953,14 @@ function reviewerPrompt(context, reviewer, promptPath, reviewPath) {
|
|
|
37846
37953
|
` - ${relative3(context.roundDir, context.discoveredStandardsPath)}`,
|
|
37847
37954
|
` - ${relative3(context.roundDir, context.contextPath)}`,
|
|
37848
37955
|
` - ${relative3(context.roundDir, context.reviewBriefPath)}`,
|
|
37956
|
+
` - ${relative3(context.roundDir, reviewerPersonaPath(context, reviewer))}`,
|
|
37849
37957
|
...context.requirementsPath ? [` - ${relative3(context.roundDir, context.requirementsPath)}`] : [],
|
|
37850
37958
|
"",
|
|
37959
|
+
"Review focus:",
|
|
37960
|
+
"- Apply the reviewer persona file as your primary review lens.",
|
|
37961
|
+
"- Use the review brief as the source of truth for changed files and scope.",
|
|
37962
|
+
"- Inspect surrounding source code only when needed to validate a finding.",
|
|
37963
|
+
"",
|
|
37851
37964
|
"Output contract:",
|
|
37852
37965
|
`- Return review markdown through stdout for ${reviewPath}.`,
|
|
37853
37966
|
"- Do not write files directly.",
|
|
@@ -37862,6 +37975,10 @@ function reviewerPrompt(context, reviewer, promptPath, reviewPath) {
|
|
|
37862
37975
|
""
|
|
37863
37976
|
].join("\n");
|
|
37864
37977
|
}
|
|
37978
|
+
function reviewerPersonaPath(context, reviewer) {
|
|
37979
|
+
const ocrDir = dirname11(dirname11(context.sessionDir));
|
|
37980
|
+
return join26(ocrDir, "skills", "references", "reviewers", `${reviewer.persona}.md`);
|
|
37981
|
+
}
|
|
37865
37982
|
function pipelinePrompt(context, stage, agent, promptPath, artifactPath) {
|
|
37866
37983
|
return [
|
|
37867
37984
|
`# Pipeline Agent: ${stage}`,
|
|
@@ -37938,6 +38055,18 @@ async function runReviewerPhase(input) {
|
|
|
37938
38055
|
};
|
|
37939
38056
|
});
|
|
37940
38057
|
const requests = reviewerRequests.map((item) => item.request);
|
|
38058
|
+
const running = new Set(requests.map((request) => request.id));
|
|
38059
|
+
const completed = /* @__PURE__ */ new Set();
|
|
38060
|
+
const progressTimer = setInterval(() => {
|
|
38061
|
+
emit({
|
|
38062
|
+
event: "reviews:progress",
|
|
38063
|
+
phase: "reviews",
|
|
38064
|
+
completed: completed.size,
|
|
38065
|
+
total: requests.length,
|
|
38066
|
+
running: [...running].sort()
|
|
38067
|
+
});
|
|
38068
|
+
}, 15e3);
|
|
38069
|
+
progressTimer.unref?.();
|
|
37941
38070
|
const promises = reviewerRequests.map(async ({ reviewer, request }) => {
|
|
37942
38071
|
const agentSessionId = await journal.startInstance({
|
|
37943
38072
|
workflowId: input.context.sessionId,
|
|
@@ -37959,6 +38088,8 @@ async function runReviewerPhase(input) {
|
|
|
37959
38088
|
});
|
|
37960
38089
|
const result = await input.runner.run(request, controller.signal);
|
|
37961
38090
|
const logPaths = await writeProcessLogs(input.context.roundDir, request, result);
|
|
38091
|
+
running.delete(request.id);
|
|
38092
|
+
completed.add(request.id);
|
|
37962
38093
|
emit({
|
|
37963
38094
|
event: "agent:complete",
|
|
37964
38095
|
phase: "reviews",
|
|
@@ -37977,15 +38108,19 @@ async function runReviewerPhase(input) {
|
|
|
37977
38108
|
return result;
|
|
37978
38109
|
});
|
|
37979
38110
|
const results = [];
|
|
37980
|
-
|
|
37981
|
-
|
|
37982
|
-
|
|
37983
|
-
|
|
37984
|
-
|
|
37985
|
-
|
|
37986
|
-
|
|
37987
|
-
|
|
37988
|
-
|
|
38111
|
+
try {
|
|
38112
|
+
await Promise.all(
|
|
38113
|
+
promises.map(async (promise) => {
|
|
38114
|
+
const result = await promise;
|
|
38115
|
+
results.push(result);
|
|
38116
|
+
if (result.exitCode !== 0) {
|
|
38117
|
+
controller.abort();
|
|
38118
|
+
}
|
|
38119
|
+
})
|
|
38120
|
+
);
|
|
38121
|
+
} finally {
|
|
38122
|
+
clearInterval(progressTimer);
|
|
38123
|
+
}
|
|
37989
38124
|
const failed = results.find((result) => result.exitCode !== 0);
|
|
37990
38125
|
if (failed) {
|
|
37991
38126
|
const failedRequest = requests.find((request) => request.id === failed.id);
|
|
@@ -38052,7 +38187,8 @@ async function runOpenCodeProcessAgentReview(input) {
|
|
|
38052
38187
|
sessionId: input.sessionId,
|
|
38053
38188
|
sessionDir: input.sessionDir,
|
|
38054
38189
|
round: input.round,
|
|
38055
|
-
requirements: input.requirements
|
|
38190
|
+
requirements: input.requirements,
|
|
38191
|
+
cwd: input.cwd
|
|
38056
38192
|
});
|
|
38057
38193
|
const snapshot = await writePromptSnapshots({
|
|
38058
38194
|
context,
|
|
@@ -38065,7 +38201,12 @@ async function runOpenCodeProcessAgentReview(input) {
|
|
|
38065
38201
|
round: input.round,
|
|
38066
38202
|
reviewers: snapshot.resolvedTeam.reviewers.length
|
|
38067
38203
|
});
|
|
38068
|
-
emit({
|
|
38204
|
+
emit({
|
|
38205
|
+
event: "reviews:start",
|
|
38206
|
+
session_id: input.sessionId,
|
|
38207
|
+
round: input.round,
|
|
38208
|
+
reviewers: snapshot.resolvedTeam.reviewers.length
|
|
38209
|
+
});
|
|
38069
38210
|
const reviewerResult = await runReviewerPhase({
|
|
38070
38211
|
context,
|
|
38071
38212
|
resolvedTeam: snapshot.resolvedTeam,
|
|
@@ -38159,7 +38300,21 @@ async function runPipelineStages(input) {
|
|
|
38159
38300
|
prompt_path: request.promptPath,
|
|
38160
38301
|
meta_log: startLogPaths.meta
|
|
38161
38302
|
});
|
|
38162
|
-
const
|
|
38303
|
+
const progressTimer = setInterval(() => {
|
|
38304
|
+
emit({
|
|
38305
|
+
event: "agent:progress",
|
|
38306
|
+
phase: stage,
|
|
38307
|
+
id: request.id,
|
|
38308
|
+
status: "running"
|
|
38309
|
+
});
|
|
38310
|
+
}, 15e3);
|
|
38311
|
+
progressTimer.unref?.();
|
|
38312
|
+
let result;
|
|
38313
|
+
try {
|
|
38314
|
+
result = await input.runner.run(request, new AbortController().signal);
|
|
38315
|
+
} finally {
|
|
38316
|
+
clearInterval(progressTimer);
|
|
38317
|
+
}
|
|
38163
38318
|
const logPaths = await writeProcessLogs(input.context.roundDir, request, result);
|
|
38164
38319
|
emit({
|
|
38165
38320
|
event: "agent:complete",
|
|
@@ -38254,6 +38409,9 @@ async function writeProcessLogs(roundDir, request, result) {
|
|
|
38254
38409
|
const stdoutPath = `${prefix}.stdout.ndjson`;
|
|
38255
38410
|
const stderrPath = `${prefix}.stderr.log`;
|
|
38256
38411
|
const metaPath = `${prefix}.meta.json`;
|
|
38412
|
+
const absoluteMetaPath = join27(roundDir, metaPath);
|
|
38413
|
+
const startedAt = readStartedAt(absoluteMetaPath) ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
38414
|
+
const completedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
38257
38415
|
await writeRoundArtifact(roundDir, stdoutPath, result.stdout);
|
|
38258
38416
|
await writeRoundArtifact(roundDir, stderrPath, result.stderr);
|
|
38259
38417
|
await writeRoundArtifact(
|
|
@@ -38267,7 +38425,9 @@ async function writeProcessLogs(roundDir, request, result) {
|
|
|
38267
38425
|
model: request.model,
|
|
38268
38426
|
prompt_path: request.promptPath,
|
|
38269
38427
|
status: "completed",
|
|
38270
|
-
|
|
38428
|
+
started_at: startedAt,
|
|
38429
|
+
completed_at: completedAt,
|
|
38430
|
+
duration_ms: Math.max(0, Date.parse(completedAt) - Date.parse(startedAt)),
|
|
38271
38431
|
exit_code: result.exitCode,
|
|
38272
38432
|
output_text_bytes: Buffer.byteLength(processOutput(result), "utf-8"),
|
|
38273
38433
|
stdout_bytes: Buffer.byteLength(result.stdout, "utf-8"),
|
|
@@ -38280,6 +38440,16 @@ async function writeProcessLogs(roundDir, request, result) {
|
|
|
38280
38440
|
);
|
|
38281
38441
|
return { stdout: stdoutPath, stderr: stderrPath, meta: metaPath };
|
|
38282
38442
|
}
|
|
38443
|
+
function readStartedAt(path2) {
|
|
38444
|
+
if (!existsSync22(path2)) return void 0;
|
|
38445
|
+
try {
|
|
38446
|
+
const parsed = JSON.parse(readFileSync16(path2, "utf-8"));
|
|
38447
|
+
if (!isRecord2(parsed)) return void 0;
|
|
38448
|
+
return typeof parsed.started_at === "string" ? parsed.started_at : void 0;
|
|
38449
|
+
} catch {
|
|
38450
|
+
return void 0;
|
|
38451
|
+
}
|
|
38452
|
+
}
|
|
38283
38453
|
function processOutput(result) {
|
|
38284
38454
|
return result.outputText ?? result.stdout;
|
|
38285
38455
|
}
|
|
@@ -38418,12 +38588,15 @@ function newSessionId() {
|
|
|
38418
38588
|
const date = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
38419
38589
|
return `${date}-opencode-process-review-${Date.now()}`;
|
|
38420
38590
|
}
|
|
38421
|
-
var runAgentsSubcommand = new Command("run-agents").description("Run OCR review through OpenCode process agents").argument("[target]", "Target path for fresh dashboard mode", ".").option("--session-id <workflow-session-id>", "Prepared workflow session id").option("--round <n>", "Prepared round number", (value) => Number.parseInt(value, 10)).option("--fresh", "Create a fresh workflow session before running process agents").option("--requirements <text-or-path>", "Inline requirements text or path to a requirements file").option("--team <json>", "JSON ReviewerInstance[] override for this run").action(
|
|
38591
|
+
var runAgentsSubcommand = new Command("run-agents").description("Run OCR review through OpenCode process agents").argument("[target]", "Target path for fresh dashboard mode", ".").option("--session-id <workflow-session-id>", "Prepared workflow session id").option("--round <n>", "Prepared round number", (value) => Number.parseInt(value, 10)).option("--fresh", "Create a fresh workflow session before running process agents").option("--requirements <text-or-path>", "Inline requirements text or path to a requirements file").option("--team <json>", "JSON ReviewerInstance[] override for this run").option("--foreground", "Run in the current process instead of detaching a background worker").addOption(new Option("--worker", "Internal detached worker mode").hideHelp()).action(
|
|
38422
38592
|
async (target, options) => {
|
|
38423
38593
|
const targetDir = process.cwd();
|
|
38424
38594
|
requireOcrSetup(targetDir);
|
|
38425
38595
|
const ocrDir = join28(targetDir, ".ocr");
|
|
38426
38596
|
const db = await ensureDatabase(ocrDir);
|
|
38597
|
+
let sessionDirForMeta;
|
|
38598
|
+
let roundForMeta;
|
|
38599
|
+
const modeForMeta = options.worker ? "worker" : options.foreground ? "foreground" : "detached";
|
|
38427
38600
|
try {
|
|
38428
38601
|
let sessionId = options.sessionId;
|
|
38429
38602
|
let round = options.round;
|
|
@@ -38454,6 +38627,33 @@ var runAgentsSubcommand = new Command("run-agents").description("Run OCR review
|
|
|
38454
38627
|
round = round ?? session.current_round;
|
|
38455
38628
|
sessionDir = session.session_dir;
|
|
38456
38629
|
}
|
|
38630
|
+
sessionDirForMeta = sessionDir;
|
|
38631
|
+
roundForMeta = round;
|
|
38632
|
+
if (!options.foreground && !options.worker) {
|
|
38633
|
+
const spawned = spawnDetachedRunAgents({
|
|
38634
|
+
targetDir,
|
|
38635
|
+
target,
|
|
38636
|
+
sessionId,
|
|
38637
|
+
sessionDir,
|
|
38638
|
+
round,
|
|
38639
|
+
requirements: options.requirements,
|
|
38640
|
+
team: options.team
|
|
38641
|
+
});
|
|
38642
|
+
console.log(`OCR review started in background.`);
|
|
38643
|
+
console.log(`Session: ${sessionId}`);
|
|
38644
|
+
console.log(`Round: ${round}`);
|
|
38645
|
+
console.log(`PID: ${spawned.pid}`);
|
|
38646
|
+
console.log(`Progress: ocr review watch --session-id ${sessionId}`);
|
|
38647
|
+
console.log(`Status: ocr review status --session-id ${sessionId}`);
|
|
38648
|
+
console.log(`Artifacts: ${sessionDir}`);
|
|
38649
|
+
return;
|
|
38650
|
+
}
|
|
38651
|
+
writeRunMeta(sessionDir, round, {
|
|
38652
|
+
status: "running",
|
|
38653
|
+
mode: options.worker ? "worker" : "foreground",
|
|
38654
|
+
pid: process.pid,
|
|
38655
|
+
started_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
38656
|
+
});
|
|
38457
38657
|
const { team } = loadTeamConfig(ocrDir);
|
|
38458
38658
|
const override = options.team ? parseTeamOverride(options.team) : void 0;
|
|
38459
38659
|
const reviewers = resolveTeamComposition(team, override);
|
|
@@ -38467,18 +38667,226 @@ var runAgentsSubcommand = new Command("run-agents").description("Run OCR review
|
|
|
38467
38667
|
pipelineAgents,
|
|
38468
38668
|
requirements: readRequirements(options.requirements),
|
|
38469
38669
|
journal: new PersistenceAgentLifecycleJournal(ocrDir),
|
|
38470
|
-
emit: (event) =>
|
|
38670
|
+
emit: (event) => {
|
|
38671
|
+
console.log(JSON.stringify(event));
|
|
38672
|
+
renderRunAgentsProgress(event);
|
|
38673
|
+
}
|
|
38471
38674
|
});
|
|
38472
38675
|
if (!result.ok) {
|
|
38676
|
+
writeRunMeta(sessionDir, round, {
|
|
38677
|
+
status: "failed",
|
|
38678
|
+
mode: options.worker ? "worker" : "foreground",
|
|
38679
|
+
pid: process.pid,
|
|
38680
|
+
completed_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
38681
|
+
errors: result.errors
|
|
38682
|
+
});
|
|
38473
38683
|
for (const error of result.errors) console.error(source_default.red(error));
|
|
38474
38684
|
process.exit(1);
|
|
38475
38685
|
}
|
|
38686
|
+
writeRunMeta(sessionDir, round, {
|
|
38687
|
+
status: "completed",
|
|
38688
|
+
mode: options.worker ? "worker" : "foreground",
|
|
38689
|
+
pid: process.pid,
|
|
38690
|
+
completed_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
38691
|
+
});
|
|
38476
38692
|
console.log(JSON.stringify({ event: "review:complete", session_id: sessionId, round }));
|
|
38477
38693
|
} catch (error) {
|
|
38694
|
+
if (sessionDirForMeta && roundForMeta) {
|
|
38695
|
+
writeRunMeta(sessionDirForMeta, roundForMeta, {
|
|
38696
|
+
status: "failed",
|
|
38697
|
+
mode: modeForMeta,
|
|
38698
|
+
pid: process.pid,
|
|
38699
|
+
completed_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
38700
|
+
errors: [error instanceof Error ? error.message : "Failed to run OpenCode process-agent review"]
|
|
38701
|
+
});
|
|
38702
|
+
}
|
|
38478
38703
|
fail3(error instanceof Error ? error.message : "Failed to run OpenCode process-agent review");
|
|
38479
38704
|
}
|
|
38480
38705
|
}
|
|
38481
38706
|
);
|
|
38707
|
+
var statusSubcommand2 = new Command("status").description("Show process-agent review status").requiredOption("--session-id <workflow-session-id>", "Workflow session id").option("--round <n>", "Round number", (value) => Number.parseInt(value, 10)).action(async (options) => {
|
|
38708
|
+
const targetDir = process.cwd();
|
|
38709
|
+
requireOcrSetup(targetDir);
|
|
38710
|
+
const ocrDir = join28(targetDir, ".ocr");
|
|
38711
|
+
const db = await ensureDatabase(ocrDir);
|
|
38712
|
+
const session = getSession(db, options.sessionId);
|
|
38713
|
+
if (!session) fail3(`Workflow session not found: ${options.sessionId}`);
|
|
38714
|
+
renderReviewStatus(session.session_dir, options.round ?? session.current_round);
|
|
38715
|
+
});
|
|
38716
|
+
var watchSubcommand = new Command("watch").description("Watch process-agent review progress").requiredOption("--session-id <workflow-session-id>", "Workflow session id").option("--round <n>", "Round number", (value) => Number.parseInt(value, 10)).option("--interval <seconds>", "Refresh interval", (value) => Number.parseInt(value, 10), 2).action(async (options) => {
|
|
38717
|
+
const targetDir = process.cwd();
|
|
38718
|
+
requireOcrSetup(targetDir);
|
|
38719
|
+
const ocrDir = join28(targetDir, ".ocr");
|
|
38720
|
+
const db = await ensureDatabase(ocrDir);
|
|
38721
|
+
const session = getSession(db, options.sessionId);
|
|
38722
|
+
if (!session) fail3(`Workflow session not found: ${options.sessionId}`);
|
|
38723
|
+
const round = options.round ?? session.current_round;
|
|
38724
|
+
renderReviewStatus(session.session_dir, round);
|
|
38725
|
+
setInterval(() => {
|
|
38726
|
+
process.stdout.write("\x1Bc");
|
|
38727
|
+
renderReviewStatus(session.session_dir, round);
|
|
38728
|
+
}, Math.max(1, options.interval) * 1e3);
|
|
38729
|
+
});
|
|
38730
|
+
function spawnDetachedRunAgents(input) {
|
|
38731
|
+
const roundDir = join28(input.sessionDir, "rounds", `round-${input.round}`);
|
|
38732
|
+
mkdirSync8(join28(roundDir, "process-logs"), { recursive: true });
|
|
38733
|
+
const stdoutPath = join28(roundDir, "process-logs", "run-agents-worker.stdout.log");
|
|
38734
|
+
const stderrPath = join28(roundDir, "process-logs", "run-agents-worker.stderr.log");
|
|
38735
|
+
const outFd = openSync(stdoutPath, "a");
|
|
38736
|
+
const errFd = openSync(stderrPath, "a");
|
|
38737
|
+
const entrypoint = process.argv[1];
|
|
38738
|
+
if (!entrypoint) fail3("Cannot locate OCR CLI entrypoint for background worker.");
|
|
38739
|
+
const args = [
|
|
38740
|
+
entrypoint,
|
|
38741
|
+
"review",
|
|
38742
|
+
"run-agents",
|
|
38743
|
+
input.target,
|
|
38744
|
+
"--session-id",
|
|
38745
|
+
input.sessionId,
|
|
38746
|
+
"--round",
|
|
38747
|
+
String(input.round),
|
|
38748
|
+
"--worker",
|
|
38749
|
+
"--foreground",
|
|
38750
|
+
...input.requirements ? ["--requirements", input.requirements] : [],
|
|
38751
|
+
...input.team ? ["--team", input.team] : []
|
|
38752
|
+
];
|
|
38753
|
+
const child = spawnBinary(process.execPath, args, {
|
|
38754
|
+
cwd: input.targetDir,
|
|
38755
|
+
detached: true,
|
|
38756
|
+
stdio: ["ignore", outFd, errFd]
|
|
38757
|
+
});
|
|
38758
|
+
child.unref();
|
|
38759
|
+
closeSync(outFd);
|
|
38760
|
+
closeSync(errFd);
|
|
38761
|
+
writeRunMeta(input.sessionDir, input.round, {
|
|
38762
|
+
status: "spawned",
|
|
38763
|
+
mode: "detached",
|
|
38764
|
+
pid: child.pid,
|
|
38765
|
+
started_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
38766
|
+
stdout_log: stdoutPath,
|
|
38767
|
+
stderr_log: stderrPath
|
|
38768
|
+
});
|
|
38769
|
+
return { pid: child.pid };
|
|
38770
|
+
}
|
|
38771
|
+
function writeRunMeta(sessionDir, round, patch) {
|
|
38772
|
+
const roundDir = join28(sessionDir, "rounds", `round-${round}`);
|
|
38773
|
+
mkdirSync8(roundDir, { recursive: true });
|
|
38774
|
+
const path2 = join28(roundDir, "run-meta.json");
|
|
38775
|
+
let current = { schema_version: 1 };
|
|
38776
|
+
if (existsSync23(path2)) {
|
|
38777
|
+
try {
|
|
38778
|
+
const parsed = JSON.parse(readFileSync17(path2, "utf-8"));
|
|
38779
|
+
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
38780
|
+
current = parsed;
|
|
38781
|
+
}
|
|
38782
|
+
} catch {
|
|
38783
|
+
current = { schema_version: 1 };
|
|
38784
|
+
}
|
|
38785
|
+
}
|
|
38786
|
+
writeFileSync9(path2, JSON.stringify({ ...current, ...patch }, null, 2), "utf-8");
|
|
38787
|
+
}
|
|
38788
|
+
function renderReviewStatus(sessionDir, round) {
|
|
38789
|
+
const roundDir = join28(sessionDir, "rounds", `round-${round}`);
|
|
38790
|
+
const processLogsDir = join28(roundDir, "process-logs");
|
|
38791
|
+
const runMeta = readJson(join28(roundDir, "run-meta.json"));
|
|
38792
|
+
console.log(source_default.bold(`OCR review status: ${sessionDir}`));
|
|
38793
|
+
console.log(`Round: ${round}`);
|
|
38794
|
+
if (runMeta) {
|
|
38795
|
+
const pid = typeof runMeta.pid === "number" ? runMeta.pid : void 0;
|
|
38796
|
+
console.log(
|
|
38797
|
+
`Runner: ${String(runMeta.status ?? "unknown")}${pid ? ` pid=${pid} live=${isPidLive(pid) ? "yes" : "no"}` : ""}`
|
|
38798
|
+
);
|
|
38799
|
+
}
|
|
38800
|
+
const metas = existsSync23(processLogsDir) ? readdirMetaFiles(processLogsDir).map((path2) => readJson(path2)).filter(isRecord3) : [];
|
|
38801
|
+
const reviewers = metas.filter((meta) => meta.phase === "reviews");
|
|
38802
|
+
const completedReviewers = reviewers.filter((meta) => meta.status === "completed");
|
|
38803
|
+
const runningReviewers = reviewers.filter((meta) => meta.status === "running");
|
|
38804
|
+
console.log(`Reviews: ${completedReviewers.length}/${reviewers.length || "?"} complete`);
|
|
38805
|
+
if (runningReviewers.length > 0) {
|
|
38806
|
+
console.log(`Running reviewers: ${runningReviewers.map((meta) => meta.id).join(", ")}`);
|
|
38807
|
+
}
|
|
38808
|
+
for (const phase of ["aggregation", "validation", "synthesis"]) {
|
|
38809
|
+
const meta = metas.find((item) => item.id === phase);
|
|
38810
|
+
console.log(`Pipeline ${phase}: ${String(meta?.status ?? "pending")}`);
|
|
38811
|
+
}
|
|
38812
|
+
if (existsSync23(join28(roundDir, "final.md"))) {
|
|
38813
|
+
console.log(source_default.green(`Final: ${join28(roundDir, "final.md")}`));
|
|
38814
|
+
}
|
|
38815
|
+
}
|
|
38816
|
+
function readdirMetaFiles(dir) {
|
|
38817
|
+
return existsSync23(dir) ? Array.from(new Set(readdirSync9(dir))).filter((name) => name.endsWith(".meta.json")).sort().map((name) => join28(dir, name)) : [];
|
|
38818
|
+
}
|
|
38819
|
+
function readJson(path2) {
|
|
38820
|
+
if (!existsSync23(path2)) return void 0;
|
|
38821
|
+
try {
|
|
38822
|
+
const parsed = JSON.parse(readFileSync17(path2, "utf-8"));
|
|
38823
|
+
return isRecord3(parsed) ? parsed : void 0;
|
|
38824
|
+
} catch {
|
|
38825
|
+
return void 0;
|
|
38826
|
+
}
|
|
38827
|
+
}
|
|
38828
|
+
function isPidLive(pid) {
|
|
38829
|
+
try {
|
|
38830
|
+
process.kill(pid, 0);
|
|
38831
|
+
return true;
|
|
38832
|
+
} catch {
|
|
38833
|
+
return false;
|
|
38834
|
+
}
|
|
38835
|
+
}
|
|
38836
|
+
function isRecord3(value) {
|
|
38837
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
38838
|
+
}
|
|
38839
|
+
function renderRunAgentsProgress(event) {
|
|
38840
|
+
const name = typeof event.event === "string" ? event.event : "";
|
|
38841
|
+
if (name === "reviews:start") {
|
|
38842
|
+
console.error(
|
|
38843
|
+
source_default.cyan(
|
|
38844
|
+
`OCR reviewers started: ${String(event.reviewers ?? "?")} agents running in parallel.`
|
|
38845
|
+
)
|
|
38846
|
+
);
|
|
38847
|
+
return;
|
|
38848
|
+
}
|
|
38849
|
+
if (name === "agent:start") {
|
|
38850
|
+
console.error(
|
|
38851
|
+
source_default.dim(
|
|
38852
|
+
`OCR agent started: ${String(event.id ?? "?")} (${String(event.phase ?? "?")}, ${String(event.model ?? "?")})`
|
|
38853
|
+
)
|
|
38854
|
+
);
|
|
38855
|
+
return;
|
|
38856
|
+
}
|
|
38857
|
+
if (name === "reviews:progress") {
|
|
38858
|
+
const running = Array.isArray(event.running) ? event.running.join(", ") : "?";
|
|
38859
|
+
console.error(
|
|
38860
|
+
source_default.cyan(
|
|
38861
|
+
`OCR reviews progress: ${String(event.completed ?? "?")}/${String(event.total ?? "?")} complete; running: ${running || "none"}`
|
|
38862
|
+
)
|
|
38863
|
+
);
|
|
38864
|
+
return;
|
|
38865
|
+
}
|
|
38866
|
+
if (name === "agent:complete") {
|
|
38867
|
+
console.error(
|
|
38868
|
+
source_default.green(
|
|
38869
|
+
`OCR agent completed: ${String(event.id ?? "?")} exit=${String(event.exit_code ?? "?")}`
|
|
38870
|
+
)
|
|
38871
|
+
);
|
|
38872
|
+
return;
|
|
38873
|
+
}
|
|
38874
|
+
if (name === "agent:progress") {
|
|
38875
|
+
console.error(
|
|
38876
|
+
source_default.cyan(
|
|
38877
|
+
`OCR agent still running: ${String(event.id ?? "?")} (${String(event.phase ?? "?")})`
|
|
38878
|
+
)
|
|
38879
|
+
);
|
|
38880
|
+
return;
|
|
38881
|
+
}
|
|
38882
|
+
if (name === "pipeline:start") {
|
|
38883
|
+
console.error(source_default.cyan("OCR pipeline started: aggregation -> validation -> synthesis."));
|
|
38884
|
+
return;
|
|
38885
|
+
}
|
|
38886
|
+
if (name === "pipeline:complete") {
|
|
38887
|
+
console.error(source_default.green("OCR pipeline complete."));
|
|
38888
|
+
}
|
|
38889
|
+
}
|
|
38482
38890
|
var CONTROL_PROMPT = "Resume this OCR review: run `ocr state status --json` and act on `next_action`, continuing forward from `current_phase` without redoing completed phases.";
|
|
38483
38891
|
var reviewCommand = new Command("review").description("Run or resume an OCR review").option("--resume <workflow-id>", "Resume a prior review by its workflow session id").action(async (options) => {
|
|
38484
38892
|
if (!options.resume) {
|
|
@@ -38599,6 +39007,8 @@ var reviewCommand = new Command("review").description("Run or resume an OCR revi
|
|
|
38599
39007
|
});
|
|
38600
39008
|
});
|
|
38601
39009
|
reviewCommand.addCommand(runAgentsSubcommand);
|
|
39010
|
+
reviewCommand.addCommand(statusSubcommand2);
|
|
39011
|
+
reviewCommand.addCommand(watchSubcommand);
|
|
38602
39012
|
|
|
38603
39013
|
// src/commands/update.ts
|
|
38604
39014
|
import { existsSync as existsSync24 } from "node:fs";
|
|
@@ -38760,12 +39170,12 @@ var updateCommand = new Command("update").description("Update OCR assets after p
|
|
|
38760
39170
|
|
|
38761
39171
|
// src/commands/dashboard.ts
|
|
38762
39172
|
import { existsSync as existsSync25 } from "node:fs";
|
|
38763
|
-
import { join as join30, dirname as
|
|
39173
|
+
import { join as join30, dirname as dirname12 } from "node:path";
|
|
38764
39174
|
import { fileURLToPath } from "node:url";
|
|
38765
39175
|
init_src();
|
|
38766
39176
|
init_db();
|
|
38767
39177
|
var __filename = fileURLToPath(import.meta.url);
|
|
38768
|
-
var __dirname =
|
|
39178
|
+
var __dirname = dirname12(__filename);
|
|
38769
39179
|
function resolveServerPath() {
|
|
38770
39180
|
return join30(__dirname, "dashboard", "server.js");
|
|
38771
39181
|
}
|
|
@@ -39285,7 +39695,7 @@ var pruneBackupsSubcommand = new Command("prune-backups").description("Delete ol
|
|
|
39285
39695
|
var dbCommand = new Command("db").description("Inspect and maintain the OCR SQLite database").addCommand(doctorSubcommand).addCommand(vacuumSubcommand).addCommand(pruneSubcommand).addCommand(pruneBackupsSubcommand);
|
|
39286
39696
|
|
|
39287
39697
|
// src/commands/reviewers.ts
|
|
39288
|
-
import { writeFileSync as
|
|
39698
|
+
import { writeFileSync as writeFileSync10, renameSync as renameSync2 } from "node:fs";
|
|
39289
39699
|
import { join as join33 } from "node:path";
|
|
39290
39700
|
init_src();
|
|
39291
39701
|
async function readStdin3() {
|
|
@@ -39394,7 +39804,7 @@ var syncSubcommand2 = new Command("sync").description("Sync reviewers-meta.json
|
|
|
39394
39804
|
}
|
|
39395
39805
|
const metaPath = join33(ocrDir, "reviewers-meta.json");
|
|
39396
39806
|
const tmpPath = metaPath + ".tmp";
|
|
39397
|
-
|
|
39807
|
+
writeFileSync10(tmpPath, JSON.stringify(meta, null, 2) + "\n");
|
|
39398
39808
|
renameSync2(tmpPath, metaPath);
|
|
39399
39809
|
const tierCounts = meta.reviewers.reduce(
|
|
39400
39810
|
(acc, r) => {
|
|
@@ -39424,7 +39834,7 @@ var syncSubcommand2 = new Command("sync").description("Sync reviewers-meta.json
|
|
|
39424
39834
|
const meta = validateReviewersMeta(parsed);
|
|
39425
39835
|
const metaPath = join33(ocrDir, "reviewers-meta.json");
|
|
39426
39836
|
const tmpPath = metaPath + ".tmp";
|
|
39427
|
-
|
|
39837
|
+
writeFileSync10(tmpPath, JSON.stringify(meta, null, 2) + "\n");
|
|
39428
39838
|
renameSync2(tmpPath, metaPath);
|
|
39429
39839
|
const tierCounts = meta.reviewers.reduce(
|
|
39430
39840
|
(acc, r) => {
|
|
@@ -39499,7 +39909,7 @@ var hostCommand = new Command("host").description("Inspect host (AI CLI) capabil
|
|
|
39499
39909
|
// src/lib/update-check.ts
|
|
39500
39910
|
import { homedir } from "node:os";
|
|
39501
39911
|
import { join as join34 } from "node:path";
|
|
39502
|
-
import { readFileSync as readFileSync19, writeFileSync as
|
|
39912
|
+
import { readFileSync as readFileSync19, writeFileSync as writeFileSync11, mkdirSync as mkdirSync9 } from "node:fs";
|
|
39503
39913
|
var PACKAGE_NAME = "@open-code-review/cli";
|
|
39504
39914
|
var REGISTRY_URL = `https://registry.npmjs.org/${PACKAGE_NAME}/latest`;
|
|
39505
39915
|
var CACHE_DIR2 = join34(homedir(), ".ocr");
|
|
@@ -39515,8 +39925,8 @@ function readCache(cacheFile) {
|
|
|
39515
39925
|
}
|
|
39516
39926
|
function writeCache(cacheFile, cache2) {
|
|
39517
39927
|
try {
|
|
39518
|
-
|
|
39519
|
-
|
|
39928
|
+
mkdirSync9(join34(cacheFile, ".."), { recursive: true });
|
|
39929
|
+
writeFileSync11(cacheFile, JSON.stringify(cache2));
|
|
39520
39930
|
} catch {
|
|
39521
39931
|
}
|
|
39522
39932
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nk070281sjv/cli",
|
|
3
|
-
"version": "2.3.
|
|
3
|
+
"version": "2.3.8",
|
|
4
4
|
"description": "CLI for Open Code Review - Multi-environment setup and progress tracking",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
},
|
|
38
38
|
"dependencies": {
|
|
39
39
|
"@inquirer/prompts": "^7.2.0",
|
|
40
|
-
"@nk070281sjv/agents": "2.3.
|
|
40
|
+
"@nk070281sjv/agents": "2.3.8",
|
|
41
41
|
"chalk": "^5.4.1",
|
|
42
42
|
"chokidar": "^4.0.3",
|
|
43
43
|
"commander": "^13.0.0",
|