@nk070281sjv/cli 2.3.11 → 2.3.17
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/README.md +7 -0
- package/dist/index.js +414 -65
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -69,6 +69,7 @@ pipeline_agents:
|
|
|
69
69
|
aggregation: { model: big-brain }
|
|
70
70
|
validation: { model: big-brain }
|
|
71
71
|
synthesis: { model: big-brain }
|
|
72
|
+
translation_uk: { model: big-brain }
|
|
72
73
|
```
|
|
73
74
|
|
|
74
75
|
Override per-review from the dashboard's Command Center, or via `--team` on the CLI. The dashboard auto-discovers every model your installed vendor (Claude Code or OpenCode) offers.
|
|
@@ -77,8 +78,14 @@ OpenCode reviews use `ocr review run-agents` internally. The CLI starts every
|
|
|
77
78
|
resolved reviewer process, writes exact prompt snapshots under
|
|
78
79
|
`rounds/round-{n}/prompts/`, writes reviewer stdout under `reviews/`, and then
|
|
79
80
|
runs `aggregation`, `validation`, and `synthesis` as separate process agents.
|
|
81
|
+
After `final.md` is written, it runs `translation_uk` and writes `final.uk.md`
|
|
82
|
+
as a Ukrainian human-readable copy; translation failure is logged without
|
|
83
|
+
invalidating the completed English review.
|
|
80
84
|
If any configured reviewer or pipeline model is missing or any process fails,
|
|
81
85
|
the round stops instead of continuing with a partial team or fabricated output.
|
|
86
|
+
For normal OpenCode use, run `ocr review run-agents --fresh` without
|
|
87
|
+
`--background`; the command keeps the shell call active, prints progress, and
|
|
88
|
+
returns only when the review is completed or failed.
|
|
82
89
|
|
|
83
90
|
## Commands
|
|
84
91
|
|
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.17" : createRequire(import.meta.url)("../../package.json").version;
|
|
30783
30783
|
|
|
30784
30784
|
// src/lib/deps.ts
|
|
30785
30785
|
init_src();
|
|
@@ -35260,7 +35260,7 @@ function validateResolvedTeamSnapshot(value) {
|
|
|
35260
35260
|
validatePipelineArtifactRef(pipeline[stage], `pipeline.${stage}`, errors);
|
|
35261
35261
|
}
|
|
35262
35262
|
for (const key of Object.keys(pipeline)) {
|
|
35263
|
-
if (!["aggregation", "validation", "synthesis"].includes(key)) {
|
|
35263
|
+
if (!["aggregation", "validation", "synthesis", "translation_uk"].includes(key)) {
|
|
35264
35264
|
errors.push(`pipeline.${key} is not a supported pipeline stage.`);
|
|
35265
35265
|
}
|
|
35266
35266
|
}
|
|
@@ -37234,6 +37234,12 @@ var import_yaml3 = __toESM(require_dist(), 1);
|
|
|
37234
37234
|
import { existsSync as existsSync20, readFileSync as readFileSync15 } from "node:fs";
|
|
37235
37235
|
import { join as join23 } from "node:path";
|
|
37236
37236
|
var PIPELINE_STAGES = [
|
|
37237
|
+
"aggregation",
|
|
37238
|
+
"validation",
|
|
37239
|
+
"synthesis",
|
|
37240
|
+
"translation_uk"
|
|
37241
|
+
];
|
|
37242
|
+
var CORE_PIPELINE_STAGES = [
|
|
37237
37243
|
"aggregation",
|
|
37238
37244
|
"validation",
|
|
37239
37245
|
"synthesis"
|
|
@@ -37275,9 +37281,14 @@ function readPipelineAgents(root, aliases) {
|
|
|
37275
37281
|
throw new Error(`pipeline_agents: unknown stage(s): ${unknownKeys.join(", ")}`);
|
|
37276
37282
|
}
|
|
37277
37283
|
const out = {};
|
|
37278
|
-
for (const stage of
|
|
37284
|
+
for (const stage of CORE_PIPELINE_STAGES) {
|
|
37279
37285
|
out[stage] = readPipelineAgent(stage, obj[stage], aliases);
|
|
37280
37286
|
}
|
|
37287
|
+
out.translation_uk = obj.translation_uk ? readPipelineAgent("translation_uk", obj.translation_uk, aliases) : {
|
|
37288
|
+
stage: "translation_uk",
|
|
37289
|
+
model: out.synthesis.model,
|
|
37290
|
+
requestedModel: out.synthesis.requestedModel
|
|
37291
|
+
};
|
|
37281
37292
|
return out;
|
|
37282
37293
|
}
|
|
37283
37294
|
function readPipelineAgent(stage, value, aliases) {
|
|
@@ -37420,7 +37431,8 @@ import { dirname as dirname10, join as join25 } from "node:path";
|
|
|
37420
37431
|
import { execFile } from "node:child_process";
|
|
37421
37432
|
import { promisify } from "node:util";
|
|
37422
37433
|
var execFileAsync = promisify(execFile);
|
|
37423
|
-
var
|
|
37434
|
+
var GIT_SUMMARY_LIMIT = 2e4;
|
|
37435
|
+
var GIT_PATCH_LIMIT = 8e4;
|
|
37424
37436
|
var REVIEW_SNAPSHOT_EXCLUDES = [".ocr", ".opencode"];
|
|
37425
37437
|
async function prepareReviewContext(input) {
|
|
37426
37438
|
const roundDir = join25(input.sessionDir, "rounds", `round-${input.round}`);
|
|
@@ -37438,7 +37450,15 @@ async function prepareReviewContext(input) {
|
|
|
37438
37450
|
"# Review Context\n\nNo shared review context has been recorded yet.\n"
|
|
37439
37451
|
);
|
|
37440
37452
|
const reviewBriefPath = join25(roundDir, "review-brief.md");
|
|
37441
|
-
const gitSnapshot = input.cwd ? await collectGitSnapshot(input.cwd) : void 0;
|
|
37453
|
+
const gitSnapshot = input.cwd ? await collectGitSnapshot(input.cwd, input.target) : void 0;
|
|
37454
|
+
const diffPatchPath = gitSnapshot ? join25(roundDir, "diff.patch") : void 0;
|
|
37455
|
+
if (gitSnapshot && diffPatchPath) {
|
|
37456
|
+
await writeFile2(
|
|
37457
|
+
diffPatchPath,
|
|
37458
|
+
gitSnapshot.diffPatch || "No textual diff patch was captured by the CLI.\n",
|
|
37459
|
+
"utf-8"
|
|
37460
|
+
);
|
|
37461
|
+
}
|
|
37442
37462
|
await writeFile2(
|
|
37443
37463
|
reviewBriefPath,
|
|
37444
37464
|
[
|
|
@@ -37448,11 +37468,12 @@ async function prepareReviewContext(input) {
|
|
|
37448
37468
|
`Round: ${input.round}`,
|
|
37449
37469
|
`Discovered standards: ${discoveredStandardsPath}`,
|
|
37450
37470
|
`Shared context: ${contextPath}`,
|
|
37471
|
+
...diffPatchPath ? [`Diff patch: ${diffPatchPath}`] : [],
|
|
37451
37472
|
...input.cwd ? [`Repository root: ${input.cwd}`] : [],
|
|
37452
37473
|
"",
|
|
37453
37474
|
"## Scope",
|
|
37454
37475
|
"",
|
|
37455
|
-
"Review the current working tree changes. Focus on changed files and the unchanged code needed to validate those changes.",
|
|
37476
|
+
gitSnapshot?.baseRef ? `Review branch changes against ${gitSnapshot.baseRef}, plus current working tree changes. Focus on changed files and the unchanged code needed to validate those changes.` : "Review the current working tree changes. Focus on changed files and the unchanged code needed to validate those changes.",
|
|
37456
37477
|
"Ignore local AI tooling/config churn under `.ocr/` and `.opencode/` unless the user explicitly asks to review OCR/OpenCode setup changes.",
|
|
37457
37478
|
"",
|
|
37458
37479
|
...gitSnapshot ? [
|
|
@@ -37464,6 +37485,12 @@ async function prepareReviewContext(input) {
|
|
|
37464
37485
|
"",
|
|
37465
37486
|
fenced("text", gitSnapshot.changedFiles || "No changed files reported."),
|
|
37466
37487
|
"",
|
|
37488
|
+
...gitSnapshot.baseRef ? [
|
|
37489
|
+
"## Branch Base",
|
|
37490
|
+
"",
|
|
37491
|
+
fenced("text", gitSnapshot.baseRef),
|
|
37492
|
+
""
|
|
37493
|
+
] : [],
|
|
37467
37494
|
"## Diff Stat",
|
|
37468
37495
|
"",
|
|
37469
37496
|
fenced("text", gitSnapshot.diffStat || "No diff stat available."),
|
|
@@ -37502,6 +37529,7 @@ async function prepareReviewContext(input) {
|
|
|
37502
37529
|
discovered_standards: discoveredStandardsPath,
|
|
37503
37530
|
context: contextPath,
|
|
37504
37531
|
review_brief: reviewBriefPath,
|
|
37532
|
+
...diffPatchPath ? { diff_patch: diffPatchPath } : {},
|
|
37505
37533
|
...requirementsPath ? { requirements: requirementsPath } : {}
|
|
37506
37534
|
}
|
|
37507
37535
|
},
|
|
@@ -37518,39 +37546,110 @@ async function prepareReviewContext(input) {
|
|
|
37518
37546
|
discoveredStandardsPath,
|
|
37519
37547
|
contextPath,
|
|
37520
37548
|
reviewBriefPath,
|
|
37549
|
+
diffPatchPath,
|
|
37521
37550
|
requirementsPath,
|
|
37522
37551
|
metadataPath,
|
|
37523
37552
|
requirementsExpected: Boolean(requirementsPath)
|
|
37524
37553
|
};
|
|
37525
37554
|
}
|
|
37526
|
-
async function collectGitSnapshot(cwd) {
|
|
37527
|
-
const
|
|
37555
|
+
async function collectGitSnapshot(cwd, target = ".") {
|
|
37556
|
+
const explicitBase = target && target !== "." ? target : void 0;
|
|
37557
|
+
const autoBase = explicitBase ? void 0 : await resolveDefaultRemoteBase(cwd);
|
|
37558
|
+
const baseRef = explicitBase ?? autoBase;
|
|
37559
|
+
const branchDiffArgs = baseRef ? [`${baseRef}...HEAD`, "--", ...gitReviewPathspec()] : void 0;
|
|
37560
|
+
const [
|
|
37561
|
+
status,
|
|
37562
|
+
cachedFiles,
|
|
37563
|
+
worktreeFiles,
|
|
37564
|
+
untrackedFiles,
|
|
37565
|
+
branchFiles,
|
|
37566
|
+
worktreeDiffStat,
|
|
37567
|
+
branchDiffStat,
|
|
37568
|
+
worktreeDiffPatch,
|
|
37569
|
+
branchDiffPatch
|
|
37570
|
+
] = await Promise.all([
|
|
37528
37571
|
runGit(cwd, ["status", "--short"]),
|
|
37529
37572
|
runGit(cwd, ["diff", "--cached", "--name-only", "--", ...gitReviewPathspec()]),
|
|
37530
37573
|
runGit(cwd, ["diff", "--name-only", "HEAD", "--", ...gitReviewPathspec()]),
|
|
37531
|
-
runGit(cwd, ["
|
|
37574
|
+
runGit(cwd, ["ls-files", "--others", "--exclude-standard", "--", ...gitReviewPathspec()]),
|
|
37575
|
+
branchDiffArgs ? runGit(cwd, ["diff", "--name-only", ...branchDiffArgs]) : Promise.resolve(""),
|
|
37576
|
+
runGit(cwd, ["diff", "--stat", "HEAD", "--", ...gitReviewPathspec()]),
|
|
37577
|
+
branchDiffArgs ? runGit(cwd, ["diff", "--stat", ...branchDiffArgs]) : Promise.resolve(""),
|
|
37578
|
+
runGit(cwd, ["diff", "--patch", "HEAD", "--", ...gitReviewPathspec()], GIT_PATCH_LIMIT),
|
|
37579
|
+
branchDiffArgs ? runGit(cwd, ["diff", "--patch", ...branchDiffArgs], GIT_PATCH_LIMIT) : Promise.resolve("")
|
|
37532
37580
|
]);
|
|
37581
|
+
const untrackedFileList = filterIgnoredPaths(uniqueLines(untrackedFiles));
|
|
37582
|
+
const untrackedDiffPatch = await collectUntrackedDiffPatch(cwd, untrackedFileList);
|
|
37583
|
+
const changedFiles = filterIgnoredPaths(uniqueLines([
|
|
37584
|
+
branchFiles,
|
|
37585
|
+
cachedFiles,
|
|
37586
|
+
worktreeFiles,
|
|
37587
|
+
untrackedFileList
|
|
37588
|
+
].join("\n")));
|
|
37589
|
+
const diffStat = uniqueSections([
|
|
37590
|
+
branchDiffStat ? `Branch diff (${baseRef}...HEAD):
|
|
37591
|
+
${branchDiffStat}` : "",
|
|
37592
|
+
worktreeDiffStat ? `Working tree diff (HEAD):
|
|
37593
|
+
${worktreeDiffStat}` : ""
|
|
37594
|
+
]);
|
|
37595
|
+
const diffPatch = truncate(uniqueSections([
|
|
37596
|
+
branchDiffPatch ? `Branch diff (${baseRef}...HEAD):
|
|
37597
|
+
${branchDiffPatch}` : "",
|
|
37598
|
+
worktreeDiffPatch ? `Working tree diff (HEAD):
|
|
37599
|
+
${worktreeDiffPatch}` : "",
|
|
37600
|
+
untrackedDiffPatch ? `Untracked files diff:
|
|
37601
|
+
${untrackedDiffPatch}` : ""
|
|
37602
|
+
]), GIT_PATCH_LIMIT);
|
|
37533
37603
|
return {
|
|
37534
37604
|
status: filterIgnoredStatusLines(status),
|
|
37535
|
-
changedFiles
|
|
37536
|
-
|
|
37537
|
-
|
|
37605
|
+
changedFiles,
|
|
37606
|
+
diffStat,
|
|
37607
|
+
diffPatch,
|
|
37608
|
+
baseRef
|
|
37538
37609
|
};
|
|
37539
37610
|
}
|
|
37611
|
+
async function collectUntrackedDiffPatch(cwd, files) {
|
|
37612
|
+
const patches = [];
|
|
37613
|
+
let remaining = GIT_PATCH_LIMIT;
|
|
37614
|
+
for (const file of files.split(/\r?\n/).filter(Boolean)) {
|
|
37615
|
+
if (remaining <= 0) {
|
|
37616
|
+
patches.push("[truncated by OCR CLI]");
|
|
37617
|
+
break;
|
|
37618
|
+
}
|
|
37619
|
+
const patch = await runGit(cwd, ["diff", "--no-index", "--", "/dev/null", file], remaining);
|
|
37620
|
+
if (patch) {
|
|
37621
|
+
patches.push(patch);
|
|
37622
|
+
remaining -= patch.length;
|
|
37623
|
+
}
|
|
37624
|
+
}
|
|
37625
|
+
return patches.join("\n\n");
|
|
37626
|
+
}
|
|
37540
37627
|
function gitReviewPathspec() {
|
|
37541
|
-
return [".", ...REVIEW_SNAPSHOT_EXCLUDES.
|
|
37628
|
+
return [".", ...REVIEW_SNAPSHOT_EXCLUDES.flatMap((path2) => [
|
|
37629
|
+
`:(exclude)${path2}`,
|
|
37630
|
+
`:(exclude)${path2}/**`
|
|
37631
|
+
])];
|
|
37632
|
+
}
|
|
37633
|
+
async function resolveDefaultRemoteBase(cwd) {
|
|
37634
|
+
const originHead = await runGit(cwd, ["symbolic-ref", "--quiet", "--short", "refs/remotes/origin/HEAD"]);
|
|
37635
|
+
if (originHead) return originHead;
|
|
37636
|
+
for (const candidate of ["origin/main", "origin/master", "origin/develop", "main", "master", "develop"]) {
|
|
37637
|
+
const exists = await runGit(cwd, ["rev-parse", "--verify", "-q", candidate]);
|
|
37638
|
+
if (exists) return candidate;
|
|
37639
|
+
}
|
|
37640
|
+
return void 0;
|
|
37542
37641
|
}
|
|
37543
|
-
async function runGit(cwd, args) {
|
|
37642
|
+
async function runGit(cwd, args, limit = GIT_SUMMARY_LIMIT) {
|
|
37544
37643
|
try {
|
|
37545
37644
|
const { stdout, stderr } = await execFileAsync("git", args, {
|
|
37546
37645
|
cwd,
|
|
37547
|
-
maxBuffer:
|
|
37646
|
+
maxBuffer: limit * 2
|
|
37548
37647
|
});
|
|
37549
37648
|
return truncate(`${stdout}${stderr ? `
|
|
37550
|
-
${stderr}` : ""}`.trim());
|
|
37649
|
+
${stderr}` : ""}`.trim(), limit);
|
|
37551
37650
|
} catch (error) {
|
|
37552
37651
|
if (error && typeof error === "object" && "stdout" in error && typeof error.stdout === "string") {
|
|
37553
|
-
return truncate(error.stdout.trim());
|
|
37652
|
+
return truncate(error.stdout.trim(), limit);
|
|
37554
37653
|
}
|
|
37555
37654
|
return "";
|
|
37556
37655
|
}
|
|
@@ -37559,6 +37658,9 @@ function uniqueLines(value) {
|
|
|
37559
37658
|
const lines = value.split(/\r?\n/).map((line) => line.trim()).filter(Boolean);
|
|
37560
37659
|
return [...new Set(lines)].join("\n");
|
|
37561
37660
|
}
|
|
37661
|
+
function uniqueSections(values) {
|
|
37662
|
+
return values.filter((value) => value.trim() !== "").join("\n\n");
|
|
37663
|
+
}
|
|
37562
37664
|
function filterIgnoredStatusLines(value) {
|
|
37563
37665
|
return value.split(/\r?\n/).filter((line) => {
|
|
37564
37666
|
const pathPart = line.slice(3).trim();
|
|
@@ -37574,9 +37676,9 @@ function isIgnoredReviewPath(path2) {
|
|
|
37574
37676
|
(ignored) => normalized === ignored || normalized.startsWith(`${ignored}/`)
|
|
37575
37677
|
);
|
|
37576
37678
|
}
|
|
37577
|
-
function truncate(value) {
|
|
37578
|
-
if (value.length <=
|
|
37579
|
-
return `${value.slice(0,
|
|
37679
|
+
function truncate(value, limit) {
|
|
37680
|
+
if (value.length <= limit) return value;
|
|
37681
|
+
return `${value.slice(0, limit)}
|
|
37580
37682
|
[truncated by OCR CLI]`;
|
|
37581
37683
|
}
|
|
37582
37684
|
function fenced(language, value) {
|
|
@@ -37869,13 +37971,14 @@ function isRecord(value) {
|
|
|
37869
37971
|
}
|
|
37870
37972
|
|
|
37871
37973
|
// src/lib/agent-orchestrator/prompt-writer.ts
|
|
37872
|
-
import { mkdir as mkdir3, writeFile as writeFile3 } from "node:fs/promises";
|
|
37974
|
+
import { mkdir as mkdir3, readFile as readFile3, writeFile as writeFile3 } from "node:fs/promises";
|
|
37873
37975
|
import { dirname as dirname11, join as join26, relative as relative3 } from "node:path";
|
|
37874
37976
|
async function writePromptSnapshots(input) {
|
|
37875
37977
|
const promptsDir = join26(input.context.roundDir, "prompts");
|
|
37876
37978
|
const reviewsDir = join26(input.context.roundDir, "reviews");
|
|
37877
37979
|
await mkdir3(promptsDir, { recursive: true });
|
|
37878
37980
|
await mkdir3(reviewsDir, { recursive: true });
|
|
37981
|
+
const sharedReviewerPrefix = await reviewerSharedPrefix(input.context);
|
|
37879
37982
|
const reviewers = [];
|
|
37880
37983
|
for (const reviewer of input.reviewers) {
|
|
37881
37984
|
if (!reviewer.model) {
|
|
@@ -37887,7 +37990,7 @@ async function writePromptSnapshots(input) {
|
|
|
37887
37990
|
const reviewPath = `reviews/${reviewer.persona}-${reviewer.instance_index}.md`;
|
|
37888
37991
|
await writeFile3(
|
|
37889
37992
|
join26(input.context.roundDir, promptPath),
|
|
37890
|
-
reviewerPrompt(input.context, reviewer, promptPath, reviewPath),
|
|
37993
|
+
await reviewerPrompt(input.context, reviewer, promptPath, reviewPath, sharedReviewerPrefix),
|
|
37891
37994
|
"utf-8"
|
|
37892
37995
|
);
|
|
37893
37996
|
reviewers.push({
|
|
@@ -37901,7 +38004,8 @@ async function writePromptSnapshots(input) {
|
|
|
37901
38004
|
const pipeline = {
|
|
37902
38005
|
aggregation: await writePipelinePrompt(input, "aggregation", "aggregation.md"),
|
|
37903
38006
|
validation: await writePipelinePrompt(input, "validation", "validation.md"),
|
|
37904
|
-
synthesis: await writePipelinePrompt(input, "synthesis", "final.md")
|
|
38007
|
+
synthesis: await writePipelinePrompt(input, "synthesis", "final.md"),
|
|
38008
|
+
translation_uk: await writePipelinePrompt(input, "translation_uk", "final.uk.md")
|
|
37905
38009
|
};
|
|
37906
38010
|
const resolvedTeam = {
|
|
37907
38011
|
sessionId: input.context.sessionId,
|
|
@@ -37916,7 +38020,7 @@ async function writePromptSnapshots(input) {
|
|
|
37916
38020
|
}
|
|
37917
38021
|
async function writePipelinePrompt(input, stage, artifactPath) {
|
|
37918
38022
|
const agent = input.pipelineAgents[stage];
|
|
37919
|
-
const promptPath = `prompts/${stage}.md`;
|
|
38023
|
+
const promptPath = `prompts/${displayStageName(stage)}.md`;
|
|
37920
38024
|
await writeFile3(
|
|
37921
38025
|
join26(input.context.roundDir, promptPath),
|
|
37922
38026
|
pipelinePrompt(input.context, stage, agent, promptPath, artifactPath),
|
|
@@ -37928,33 +38032,22 @@ async function writePipelinePrompt(input, stage, artifactPath) {
|
|
|
37928
38032
|
artifactPath
|
|
37929
38033
|
};
|
|
37930
38034
|
}
|
|
37931
|
-
function reviewerPrompt(context, reviewer, promptPath, reviewPath) {
|
|
38035
|
+
async function reviewerPrompt(context, reviewer, promptPath, reviewPath, sharedPrefix) {
|
|
38036
|
+
const personaPath = reviewerPersonaPath(context, reviewer);
|
|
38037
|
+
const personaContent = await readTextFile(personaPath);
|
|
37932
38038
|
return [
|
|
37933
|
-
|
|
38039
|
+
sharedPrefix,
|
|
38040
|
+
"# Reviewer-Specific Instructions",
|
|
37934
38041
|
"",
|
|
38042
|
+
`Reviewer id: ${reviewer.persona}-${reviewer.instance_index}`,
|
|
37935
38043
|
`Agent identity: ${reviewer.name}`,
|
|
37936
38044
|
`Model alias and resolved model: ${reviewer.model}`,
|
|
37937
|
-
`
|
|
37938
|
-
`
|
|
37939
|
-
"Phase: reviews",
|
|
38045
|
+
`Persona source: ${personaPath}`,
|
|
38046
|
+
`Relative persona source: ${relative3(context.roundDir, personaPath)}`,
|
|
37940
38047
|
"",
|
|
37941
|
-
"
|
|
37942
|
-
`- ${reviewerPersonaPath(context, reviewer)}`,
|
|
37943
|
-
`- ${context.discoveredStandardsPath}`,
|
|
37944
|
-
`- ${context.contextPath}`,
|
|
37945
|
-
`- ${context.reviewBriefPath}`,
|
|
37946
|
-
...context.requirementsPath ? [`- ${context.requirementsPath}`] : [],
|
|
38048
|
+
"## Reviewer Persona",
|
|
37947
38049
|
"",
|
|
37948
|
-
"
|
|
37949
|
-
"- Read exactly the files listed above; they are session-scoped snapshots.",
|
|
37950
|
-
"- Do not look for legacy root files such as .ocr/context.md, .ocr/discovered-standards.md, or .ocr/review-brief.md.",
|
|
37951
|
-
"- If a listed file is missing, report that failure in stdout instead of searching for substitutes.",
|
|
37952
|
-
"- Relative equivalents from the round directory:",
|
|
37953
|
-
` - ${relative3(context.roundDir, context.discoveredStandardsPath)}`,
|
|
37954
|
-
` - ${relative3(context.roundDir, context.contextPath)}`,
|
|
37955
|
-
` - ${relative3(context.roundDir, context.reviewBriefPath)}`,
|
|
37956
|
-
` - ${relative3(context.roundDir, reviewerPersonaPath(context, reviewer))}`,
|
|
37957
|
-
...context.requirementsPath ? [` - ${relative3(context.roundDir, context.requirementsPath)}`] : [],
|
|
38050
|
+
fenced2("markdown", personaContent),
|
|
37958
38051
|
"",
|
|
37959
38052
|
"Review focus:",
|
|
37960
38053
|
"- Apply the reviewer persona file as your primary review lens.",
|
|
@@ -37975,15 +38068,72 @@ function reviewerPrompt(context, reviewer, promptPath, reviewPath) {
|
|
|
37975
38068
|
""
|
|
37976
38069
|
].join("\n");
|
|
37977
38070
|
}
|
|
38071
|
+
async function reviewerSharedPrefix(context) {
|
|
38072
|
+
const sections = [
|
|
38073
|
+
"# OCR Reviewer Shared Context",
|
|
38074
|
+
"",
|
|
38075
|
+
"This prefix is intentionally identical for every reviewer in this round to improve prompt-cache locality.",
|
|
38076
|
+
"",
|
|
38077
|
+
"## Session",
|
|
38078
|
+
"",
|
|
38079
|
+
`Session id: ${context.sessionId}`,
|
|
38080
|
+
`Round number: ${context.round}`,
|
|
38081
|
+
"Phase: reviews",
|
|
38082
|
+
`Session directory: ${context.sessionDir}`,
|
|
38083
|
+
`Round directory: ${context.roundDir}`,
|
|
38084
|
+
"",
|
|
38085
|
+
"## Review Scope Rules",
|
|
38086
|
+
"",
|
|
38087
|
+
"- Review the changed application code described in the review brief.",
|
|
38088
|
+
"- Ignore local AI tooling/config churn under `.ocr/` and `.opencode/` unless the user explicitly asks to review OCR/OpenCode setup changes.",
|
|
38089
|
+
"- Inspect surrounding source code only when needed to validate a finding.",
|
|
38090
|
+
"- Report only findings supported by code evidence and concrete file references.",
|
|
38091
|
+
"- Prefer targeted `git diff -- <path>` and file reads over broad repository scans.",
|
|
38092
|
+
"",
|
|
38093
|
+
"## Source Files Embedded Below",
|
|
38094
|
+
"",
|
|
38095
|
+
`- Discovered standards: ${context.discoveredStandardsPath}`,
|
|
38096
|
+
`- Shared context: ${context.contextPath}`,
|
|
38097
|
+
`- Review brief: ${context.reviewBriefPath}`,
|
|
38098
|
+
...context.diffPatchPath ? [`- Diff patch: ${context.diffPatchPath}`] : [],
|
|
38099
|
+
...context.requirementsPath ? [`- Requirements: ${context.requirementsPath}`] : [],
|
|
38100
|
+
"",
|
|
38101
|
+
embeddedFileSection("Discovered Standards", context.discoveredStandardsPath, await readTextFile(context.discoveredStandardsPath)),
|
|
38102
|
+
embeddedFileSection("Shared Review Context", context.contextPath, await readTextFile(context.contextPath)),
|
|
38103
|
+
embeddedFileSection("Review Brief", context.reviewBriefPath, await readTextFile(context.reviewBriefPath)),
|
|
38104
|
+
...context.diffPatchPath ? [embeddedFileSection("Diff Patch", context.diffPatchPath, await readTextFile(context.diffPatchPath))] : [],
|
|
38105
|
+
...context.requirementsPath ? [embeddedFileSection("Requirements", context.requirementsPath, await readTextFile(context.requirementsPath))] : [],
|
|
38106
|
+
""
|
|
38107
|
+
];
|
|
38108
|
+
return sections.join("\n");
|
|
38109
|
+
}
|
|
38110
|
+
function embeddedFileSection(title, path2, content) {
|
|
38111
|
+
return [
|
|
38112
|
+
`## ${title}`,
|
|
38113
|
+
"",
|
|
38114
|
+
`Source: ${path2}`,
|
|
38115
|
+
"",
|
|
38116
|
+
fenced2("markdown", content),
|
|
38117
|
+
""
|
|
38118
|
+
].join("\n");
|
|
38119
|
+
}
|
|
38120
|
+
function fenced2(language, value) {
|
|
38121
|
+
return `\`\`\`${language}
|
|
38122
|
+
${value.trim()}
|
|
38123
|
+
\`\`\``;
|
|
38124
|
+
}
|
|
38125
|
+
async function readTextFile(path2) {
|
|
38126
|
+
return readFile3(path2, "utf-8");
|
|
38127
|
+
}
|
|
37978
38128
|
function reviewerPersonaPath(context, reviewer) {
|
|
37979
38129
|
const ocrDir = dirname11(dirname11(context.sessionDir));
|
|
37980
38130
|
return join26(ocrDir, "skills", "references", "reviewers", `${reviewer.persona}.md`);
|
|
37981
38131
|
}
|
|
37982
38132
|
function pipelinePrompt(context, stage, agent, promptPath, artifactPath) {
|
|
37983
38133
|
return [
|
|
37984
|
-
`# Pipeline Agent: ${stage}`,
|
|
38134
|
+
`# Pipeline Agent: ${displayStageName(stage)}`,
|
|
37985
38135
|
"",
|
|
37986
|
-
`Agent identity: ${stage}`,
|
|
38136
|
+
`Agent identity: ${displayStageName(stage)}`,
|
|
37987
38137
|
`Model alias and resolved model: ${agent.requestedModel} -> ${agent.model}`,
|
|
37988
38138
|
`Session id: ${context.sessionId}`,
|
|
37989
38139
|
`Round number: ${context.round}`,
|
|
@@ -37993,9 +38143,11 @@ function pipelinePrompt(context, stage, agent, promptPath, artifactPath) {
|
|
|
37993
38143
|
`- ${context.discoveredStandardsPath}`,
|
|
37994
38144
|
`- ${context.contextPath}`,
|
|
37995
38145
|
`- ${context.reviewBriefPath}`,
|
|
37996
|
-
|
|
37997
|
-
|
|
37998
|
-
|
|
38146
|
+
...stage === "translation_uk" ? [join26(context.roundDir, "final.md")] : [
|
|
38147
|
+
join26(context.roundDir, "reviews"),
|
|
38148
|
+
...stage !== "aggregation" ? [join26(context.roundDir, "aggregation.md")] : [],
|
|
38149
|
+
...stage === "synthesis" ? [join26(context.roundDir, "validation.md")] : []
|
|
38150
|
+
],
|
|
37999
38151
|
"",
|
|
38000
38152
|
"Input path rules:",
|
|
38001
38153
|
"- Read exactly the files and directories listed above; they are session-scoped snapshots.",
|
|
@@ -38005,18 +38157,28 @@ function pipelinePrompt(context, stage, agent, promptPath, artifactPath) {
|
|
|
38005
38157
|
` - ${relative3(context.roundDir, context.discoveredStandardsPath)}`,
|
|
38006
38158
|
` - ${relative3(context.roundDir, context.contextPath)}`,
|
|
38007
38159
|
` - ${relative3(context.roundDir, context.reviewBriefPath)}`,
|
|
38008
|
-
" -
|
|
38009
|
-
|
|
38010
|
-
|
|
38160
|
+
...stage === "translation_uk" ? [" - final.md"] : [
|
|
38161
|
+
" - reviews/",
|
|
38162
|
+
...stage !== "aggregation" ? [" - aggregation.md"] : [],
|
|
38163
|
+
...stage === "synthesis" ? [" - validation.md"] : []
|
|
38164
|
+
],
|
|
38011
38165
|
"",
|
|
38012
38166
|
"Output contract:",
|
|
38013
38167
|
`- Return markdown through stdout for ${artifactPath}.`,
|
|
38014
|
-
|
|
38168
|
+
...stage === "translation_uk" ? [
|
|
38169
|
+
"- Translate the complete final review to Ukrainian.",
|
|
38170
|
+
"- Preserve markdown structure, headings, bullets, code spans, file paths, line numbers, and the fenced ```ocr-json block exactly as data.",
|
|
38171
|
+
"- Do not translate JSON field names, enum values, file paths, code identifiers, stack traces, commands, or model names.",
|
|
38172
|
+
"- Do not add new findings, remove findings, change severity/category/verdict, or change counts."
|
|
38173
|
+
] : ["- Include exactly one fenced ```ocr-json block."],
|
|
38015
38174
|
"- Do not write files directly.",
|
|
38016
38175
|
"",
|
|
38017
|
-
"
|
|
38018
|
-
|
|
38019
|
-
|
|
38176
|
+
...stage === "translation_uk" ? [
|
|
38177
|
+
"Translation contract:",
|
|
38178
|
+
"- The output must be a Ukrainian-language version of final.md.",
|
|
38179
|
+
"- Keep the original ocr-json block valid JSON and semantically identical.",
|
|
38180
|
+
""
|
|
38181
|
+
] : ["Required ocr-json schema:", schemaHint(stage), ""],
|
|
38020
38182
|
"Forbidden behavior:",
|
|
38021
38183
|
`- Do not modify ${promptPath} or ${artifactPath}.`,
|
|
38022
38184
|
"- Do not pretend missing reviewer outputs exist.",
|
|
@@ -38024,6 +38186,9 @@ function pipelinePrompt(context, stage, agent, promptPath, artifactPath) {
|
|
|
38024
38186
|
].join("\n");
|
|
38025
38187
|
}
|
|
38026
38188
|
function schemaHint(stage) {
|
|
38189
|
+
if (stage === "translation_uk") {
|
|
38190
|
+
return "No new schema. Preserve the existing final.md ocr-json block exactly.";
|
|
38191
|
+
}
|
|
38027
38192
|
if (stage === "aggregation") {
|
|
38028
38193
|
return [
|
|
38029
38194
|
"AggregationJson:",
|
|
@@ -38141,6 +38306,9 @@ function schemaHint(stage) {
|
|
|
38141
38306
|
"- `synthesis_counts` must exactly match the categories in `findings`."
|
|
38142
38307
|
].join("\n");
|
|
38143
38308
|
}
|
|
38309
|
+
function displayStageName(stage) {
|
|
38310
|
+
return stage === "translation_uk" ? "translation-uk" : stage;
|
|
38311
|
+
}
|
|
38144
38312
|
|
|
38145
38313
|
// src/lib/agent-orchestrator/review-orchestrator.ts
|
|
38146
38314
|
async function runReviewerPhase(input) {
|
|
@@ -38297,7 +38465,8 @@ async function runOpenCodeProcessAgentReview(input) {
|
|
|
38297
38465
|
sessionDir: input.sessionDir,
|
|
38298
38466
|
round: input.round,
|
|
38299
38467
|
requirements: input.requirements,
|
|
38300
|
-
cwd: input.cwd
|
|
38468
|
+
cwd: input.cwd,
|
|
38469
|
+
target: input.target
|
|
38301
38470
|
});
|
|
38302
38471
|
const snapshot = await writePromptSnapshots({
|
|
38303
38472
|
context,
|
|
@@ -38358,12 +38527,23 @@ async function runOpenCodeProcessAgentReview(input) {
|
|
|
38358
38527
|
errors: pipelineResult.errors
|
|
38359
38528
|
};
|
|
38360
38529
|
}
|
|
38530
|
+
const translationResult = await runUkrainianTranslation({
|
|
38531
|
+
context,
|
|
38532
|
+
resolvedTeam: snapshot.resolvedTeam,
|
|
38533
|
+
cwd: input.cwd,
|
|
38534
|
+
runner,
|
|
38535
|
+
journal,
|
|
38536
|
+
emit
|
|
38537
|
+
});
|
|
38361
38538
|
emit({ event: "pipeline:complete", session_id: input.sessionId });
|
|
38362
38539
|
return {
|
|
38363
38540
|
ok: true,
|
|
38364
38541
|
resolvedTeam: snapshot.resolvedTeam,
|
|
38365
38542
|
reviewerResults: reviewerResult.results,
|
|
38366
|
-
pipelineResults:
|
|
38543
|
+
pipelineResults: [
|
|
38544
|
+
...pipelineResult.results,
|
|
38545
|
+
...translationResult ? [translationResult] : []
|
|
38546
|
+
]
|
|
38367
38547
|
};
|
|
38368
38548
|
} finally {
|
|
38369
38549
|
await runner.close?.();
|
|
@@ -38478,6 +38658,167 @@ async function runPipelineStages(input) {
|
|
|
38478
38658
|
}
|
|
38479
38659
|
return { ok: true, results };
|
|
38480
38660
|
}
|
|
38661
|
+
async function runUkrainianTranslation(input) {
|
|
38662
|
+
const ref = input.resolvedTeam.pipeline.translation_uk;
|
|
38663
|
+
const emit = input.emit ?? (() => void 0);
|
|
38664
|
+
if (!ref) return void 0;
|
|
38665
|
+
const finalPath = join27(input.context.roundDir, "final.md");
|
|
38666
|
+
if (!isNonEmptyFile(finalPath)) return void 0;
|
|
38667
|
+
const journal = input.journal ?? new NoopAgentLifecycleJournal();
|
|
38668
|
+
const request = {
|
|
38669
|
+
id: "translation-uk",
|
|
38670
|
+
model: ref.model,
|
|
38671
|
+
promptPath: join27(input.context.roundDir, ref.promptPath),
|
|
38672
|
+
cwd: input.cwd,
|
|
38673
|
+
phase: "translation_uk"
|
|
38674
|
+
};
|
|
38675
|
+
const agentSessionId = await journal.startInstance({
|
|
38676
|
+
workflowId: input.context.sessionId,
|
|
38677
|
+
persona: "pipeline",
|
|
38678
|
+
instance: 1,
|
|
38679
|
+
name: request.id,
|
|
38680
|
+
model: ref.model,
|
|
38681
|
+
phase: request.phase
|
|
38682
|
+
});
|
|
38683
|
+
await journal.beat(agentSessionId);
|
|
38684
|
+
const startLogPaths = await writeProcessStartLog(input.context.roundDir, request);
|
|
38685
|
+
emit({
|
|
38686
|
+
event: "agent:start",
|
|
38687
|
+
phase: request.phase,
|
|
38688
|
+
id: request.id,
|
|
38689
|
+
model: request.model,
|
|
38690
|
+
prompt_path: request.promptPath,
|
|
38691
|
+
meta_log: startLogPaths.meta
|
|
38692
|
+
});
|
|
38693
|
+
const progressTimer = setInterval(() => {
|
|
38694
|
+
emit({
|
|
38695
|
+
event: "agent:progress",
|
|
38696
|
+
phase: request.phase,
|
|
38697
|
+
id: request.id,
|
|
38698
|
+
status: "running"
|
|
38699
|
+
});
|
|
38700
|
+
}, 15e3);
|
|
38701
|
+
progressTimer.unref?.();
|
|
38702
|
+
let result;
|
|
38703
|
+
try {
|
|
38704
|
+
result = await input.runner.run(request, new AbortController().signal);
|
|
38705
|
+
} finally {
|
|
38706
|
+
clearInterval(progressTimer);
|
|
38707
|
+
}
|
|
38708
|
+
const logPaths = await writeProcessLogs(input.context.roundDir, request, result);
|
|
38709
|
+
emit({
|
|
38710
|
+
event: "agent:complete",
|
|
38711
|
+
phase: request.phase,
|
|
38712
|
+
id: request.id,
|
|
38713
|
+
exit_code: result.exitCode,
|
|
38714
|
+
stdout_log: logPaths.stdout,
|
|
38715
|
+
stderr_log: logPaths.stderr,
|
|
38716
|
+
meta_log: logPaths.meta
|
|
38717
|
+
});
|
|
38718
|
+
const vendorId = extractVendorSessionId(result.ndjsonEvents);
|
|
38719
|
+
if (vendorId) await journal.bindVendorId(agentSessionId, vendorId);
|
|
38720
|
+
if (result.exitCode !== 0) {
|
|
38721
|
+
const diagnostic = buildFailureDiagnostic(result, request);
|
|
38722
|
+
await journal.endInstance(agentSessionId, {
|
|
38723
|
+
exitCode: result.exitCode,
|
|
38724
|
+
note: diagnostic.summary
|
|
38725
|
+
});
|
|
38726
|
+
await writeRoundArtifact(
|
|
38727
|
+
input.context.roundDir,
|
|
38728
|
+
"translation-uk-failure.json",
|
|
38729
|
+
JSON.stringify(
|
|
38730
|
+
{
|
|
38731
|
+
schema_version: 1,
|
|
38732
|
+
phase: "translation_uk",
|
|
38733
|
+
failed_id: request.id,
|
|
38734
|
+
exit_code: result.exitCode,
|
|
38735
|
+
diagnostic,
|
|
38736
|
+
stderr: result.stderr,
|
|
38737
|
+
stdout_excerpt: excerpt(result.stdout)
|
|
38738
|
+
},
|
|
38739
|
+
null,
|
|
38740
|
+
2
|
|
38741
|
+
)
|
|
38742
|
+
);
|
|
38743
|
+
emit({
|
|
38744
|
+
event: "translation:failed",
|
|
38745
|
+
phase: request.phase,
|
|
38746
|
+
id: request.id,
|
|
38747
|
+
artifact_path: ref.artifactPath
|
|
38748
|
+
});
|
|
38749
|
+
return result;
|
|
38750
|
+
}
|
|
38751
|
+
const translationErrors = validateUkrainianTranslationOutput(
|
|
38752
|
+
readFileSync16(finalPath, "utf-8"),
|
|
38753
|
+
processOutput(result)
|
|
38754
|
+
);
|
|
38755
|
+
if (translationErrors.length > 0) {
|
|
38756
|
+
await patchProcessMeta(input.context.roundDir, logPaths.meta, {
|
|
38757
|
+
status: "failed",
|
|
38758
|
+
validation_errors: translationErrors
|
|
38759
|
+
});
|
|
38760
|
+
await journal.endInstance(agentSessionId, {
|
|
38761
|
+
exitCode: 1,
|
|
38762
|
+
note: translationErrors.join("\n")
|
|
38763
|
+
});
|
|
38764
|
+
await writeRoundArtifact(
|
|
38765
|
+
input.context.roundDir,
|
|
38766
|
+
"translation-uk-failure.json",
|
|
38767
|
+
JSON.stringify(
|
|
38768
|
+
{
|
|
38769
|
+
schema_version: 1,
|
|
38770
|
+
phase: "translation_uk",
|
|
38771
|
+
failed_id: request.id,
|
|
38772
|
+
exit_code: 1,
|
|
38773
|
+
diagnostic: {
|
|
38774
|
+
summary: "Ukrainian translation output did not preserve the final review ocr-json block."
|
|
38775
|
+
},
|
|
38776
|
+
validation_errors: translationErrors,
|
|
38777
|
+
stdout_excerpt: excerpt(result.stdout)
|
|
38778
|
+
},
|
|
38779
|
+
null,
|
|
38780
|
+
2
|
|
38781
|
+
)
|
|
38782
|
+
);
|
|
38783
|
+
emit({
|
|
38784
|
+
event: "translation:failed",
|
|
38785
|
+
phase: request.phase,
|
|
38786
|
+
id: request.id,
|
|
38787
|
+
artifact_path: ref.artifactPath
|
|
38788
|
+
});
|
|
38789
|
+
return { ...result, exitCode: 1 };
|
|
38790
|
+
}
|
|
38791
|
+
await writeRoundArtifact(input.context.roundDir, ref.artifactPath, processOutput(result));
|
|
38792
|
+
await journal.endInstance(agentSessionId, { exitCode: 0 });
|
|
38793
|
+
emit({
|
|
38794
|
+
event: "translation:complete",
|
|
38795
|
+
phase: request.phase,
|
|
38796
|
+
id: request.id,
|
|
38797
|
+
artifact_path: ref.artifactPath
|
|
38798
|
+
});
|
|
38799
|
+
return result;
|
|
38800
|
+
}
|
|
38801
|
+
async function patchProcessMeta(roundDir, metaPath, patch) {
|
|
38802
|
+
const absolutePath = join27(roundDir, metaPath);
|
|
38803
|
+
let current = {};
|
|
38804
|
+
try {
|
|
38805
|
+
const parsed = JSON.parse(readFileSync16(absolutePath, "utf-8"));
|
|
38806
|
+
if (isRecord2(parsed)) current = parsed;
|
|
38807
|
+
} catch {
|
|
38808
|
+
current = {};
|
|
38809
|
+
}
|
|
38810
|
+
await writeRoundArtifact(roundDir, metaPath, JSON.stringify({ ...current, ...patch }, null, 2));
|
|
38811
|
+
}
|
|
38812
|
+
function validateUkrainianTranslationOutput(sourceMarkdown, translatedMarkdown) {
|
|
38813
|
+
const sourceJson = extractOcrJsonBlock(sourceMarkdown);
|
|
38814
|
+
if (!sourceJson.ok) return sourceJson.errors.map((error) => `source final.md: ${error}`);
|
|
38815
|
+
const translatedJson = extractOcrJsonBlock(translatedMarkdown);
|
|
38816
|
+
if (!translatedJson.ok) return translatedJson.errors.map((error) => `final.uk.md: ${error}`);
|
|
38817
|
+
if (JSON.stringify(sourceJson.value) !== JSON.stringify(translatedJson.value)) {
|
|
38818
|
+
return ["final.uk.md ocr-json block must be identical to final.md ocr-json block."];
|
|
38819
|
+
}
|
|
38820
|
+
return [];
|
|
38821
|
+
}
|
|
38481
38822
|
function sortResults(results, requests) {
|
|
38482
38823
|
const order = new Map(requests.map((request, index) => [request.id, index]));
|
|
38483
38824
|
return [...results].sort((a, b) => (order.get(a.id) ?? 0) - (order.get(b.id) ?? 0));
|
|
@@ -38533,7 +38874,7 @@ async function writeProcessLogs(roundDir, request, result) {
|
|
|
38533
38874
|
phase: request.phase,
|
|
38534
38875
|
model: request.model,
|
|
38535
38876
|
prompt_path: request.promptPath,
|
|
38536
|
-
status: "completed",
|
|
38877
|
+
status: result.exitCode === 0 ? "completed" : "failed",
|
|
38537
38878
|
started_at: startedAt,
|
|
38538
38879
|
completed_at: completedAt,
|
|
38539
38880
|
duration_ms: Math.max(0, Date.parse(completedAt) - Date.parse(startedAt)),
|
|
@@ -38697,7 +39038,7 @@ function newSessionId() {
|
|
|
38697
39038
|
const date = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
38698
39039
|
return `${date}-opencode-process-review-${Date.now()}`;
|
|
38699
39040
|
}
|
|
38700
|
-
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(
|
|
39041
|
+
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").option("--background", "Detach and return immediately; not for normal OpenCode UI progress").addOption(new Option("--worker", "Internal detached worker mode").hideHelp()).action(
|
|
38701
39042
|
async (target, options) => {
|
|
38702
39043
|
const targetDir = process.cwd();
|
|
38703
39044
|
requireOcrSetup(targetDir);
|
|
@@ -38726,7 +39067,7 @@ var runAgentsSubcommand = new Command("run-agents").description("Run OCR review
|
|
|
38726
39067
|
throw new Error(
|
|
38727
39068
|
[
|
|
38728
39069
|
"run-agents requires either --fresh or --session-id.",
|
|
38729
|
-
"Start a new process-agent review with: ocr review run-agents --fresh",
|
|
39070
|
+
"Start a new OpenCode process-agent review with: ocr review run-agents --fresh",
|
|
38730
39071
|
"Resume a prepared workflow round with: ocr review run-agents --session-id <id> --round <n>"
|
|
38731
39072
|
].join("\n")
|
|
38732
39073
|
);
|
|
@@ -38755,6 +39096,10 @@ var runAgentsSubcommand = new Command("run-agents").description("Run OCR review
|
|
|
38755
39096
|
console.log(`Progress: ocr review watch --session-id ${sessionId}`);
|
|
38756
39097
|
console.log(`Status: ocr review status --session-id ${sessionId}`);
|
|
38757
39098
|
console.log(`Artifacts: ${sessionDir}`);
|
|
39099
|
+
if (options.background) {
|
|
39100
|
+
console.log("Detached mode: this command is returning now. OpenCode UI will not show live progress unless the parent keeps polling status.");
|
|
39101
|
+
return;
|
|
39102
|
+
}
|
|
38758
39103
|
console.log("Monitoring background review until it finishes. If OpenCode stops waiting, the background review continues.");
|
|
38759
39104
|
const exitCode = await waitForDetachedRun(sessionDir, round);
|
|
38760
39105
|
process.exit(exitCode);
|
|
@@ -38774,6 +39119,7 @@ var runAgentsSubcommand = new Command("run-agents").description("Run OCR review
|
|
|
38774
39119
|
sessionDir,
|
|
38775
39120
|
round,
|
|
38776
39121
|
cwd: targetDir,
|
|
39122
|
+
target,
|
|
38777
39123
|
reviewers,
|
|
38778
39124
|
pipelineAgents,
|
|
38779
39125
|
requirements: readRequirements(options.requirements),
|
|
@@ -38916,13 +39262,16 @@ function renderReviewStatus(sessionDir, round) {
|
|
|
38916
39262
|
if (runningReviewers.length > 0) {
|
|
38917
39263
|
console.log(`Running reviewers: ${runningReviewers.map((meta) => meta.id).join(", ")}`);
|
|
38918
39264
|
}
|
|
38919
|
-
for (const phase of ["aggregation", "validation", "synthesis"]) {
|
|
39265
|
+
for (const phase of ["aggregation", "validation", "synthesis", "translation-uk"]) {
|
|
38920
39266
|
const meta = metas.find((item) => item.id === phase);
|
|
38921
39267
|
console.log(`Pipeline ${phase}: ${String(meta?.status ?? "pending")}`);
|
|
38922
39268
|
}
|
|
38923
39269
|
if (existsSync23(join28(roundDir, "final.md"))) {
|
|
38924
39270
|
console.log(source_default.green(`Final: ${join28(roundDir, "final.md")}`));
|
|
38925
39271
|
}
|
|
39272
|
+
if (existsSync23(join28(roundDir, "final.uk.md"))) {
|
|
39273
|
+
console.log(source_default.green(`Final Ukrainian: ${join28(roundDir, "final.uk.md")}`));
|
|
39274
|
+
}
|
|
38926
39275
|
}
|
|
38927
39276
|
function readdirMetaFiles(dir) {
|
|
38928
39277
|
return existsSync23(dir) ? Array.from(new Set(readdirSync9(dir))).filter((name) => name.endsWith(".meta.json")).sort().map((name) => join28(dir, name)) : [];
|
|
@@ -39054,7 +39403,7 @@ function readDetachedSnapshot(sessionDir, round) {
|
|
|
39054
39403
|
totalReviews: reviewers.length,
|
|
39055
39404
|
runningReviews: reviewers.filter((meta) => meta.status === "running").map((meta) => String(meta.id)).sort(),
|
|
39056
39405
|
pipeline: Object.fromEntries(
|
|
39057
|
-
["aggregation", "validation", "synthesis"].map((phase) => [
|
|
39406
|
+
["aggregation", "validation", "synthesis", "translation-uk"].map((phase) => [
|
|
39058
39407
|
phase,
|
|
39059
39408
|
String(metas.find((meta) => meta.id === phase)?.status ?? "pending")
|
|
39060
39409
|
])
|
|
@@ -39067,7 +39416,7 @@ function formatDetachedProgress(snapshot) {
|
|
|
39067
39416
|
`OCR background status: ${snapshot.status}${snapshot.pid ? ` pid=${snapshot.pid} live=${snapshot.live ? "yes" : "no"}` : ""}`,
|
|
39068
39417
|
`reviews ${snapshot.completedReviews}/${snapshot.totalReviews || "?"} complete`,
|
|
39069
39418
|
`running: ${running}`,
|
|
39070
|
-
`pipeline: aggregation=${snapshot.pipeline.aggregation}, validation=${snapshot.pipeline.validation}, synthesis=${snapshot.pipeline.synthesis}`
|
|
39419
|
+
`pipeline: aggregation=${snapshot.pipeline.aggregation}, validation=${snapshot.pipeline.validation}, synthesis=${snapshot.pipeline.synthesis}, translation-uk=${snapshot.pipeline["translation-uk"]}`
|
|
39071
39420
|
].join(" | ");
|
|
39072
39421
|
}
|
|
39073
39422
|
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.";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nk070281sjv/cli",
|
|
3
|
-
"version": "2.3.
|
|
3
|
+
"version": "2.3.17",
|
|
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.17",
|
|
41
41
|
"chalk": "^5.4.1",
|
|
42
42
|
"chokidar": "^4.0.3",
|
|
43
43
|
"commander": "^13.0.0",
|