@nk070281sjv/cli 2.3.27 → 2.3.31
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 +240 -121
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -30775,7 +30775,7 @@ ${hint}
|
|
|
30775
30775
|
}
|
|
30776
30776
|
|
|
30777
30777
|
// src/lib/version.ts
|
|
30778
|
-
var CLI_VERSION = true ? "2.3.
|
|
30778
|
+
var CLI_VERSION = true ? "2.3.31" : createRequire(import.meta.url)("../../package.json").version;
|
|
30779
30779
|
|
|
30780
30780
|
// src/lib/deps.ts
|
|
30781
30781
|
init_src();
|
|
@@ -35280,6 +35280,7 @@ function validateReviewer(value, path2, errors) {
|
|
|
35280
35280
|
stringValue(obj.model, `${path2}.model`, errors);
|
|
35281
35281
|
stringValue(obj.promptPath, `${path2}.promptPath`, errors);
|
|
35282
35282
|
stringValue(obj.reviewPath, `${path2}.reviewPath`, errors);
|
|
35283
|
+
stringValue(obj.promptId, `${path2}.promptId`, errors);
|
|
35283
35284
|
}
|
|
35284
35285
|
function validatePipelineArtifactRef(value, path2, errors) {
|
|
35285
35286
|
const obj = object(value, path2, errors);
|
|
@@ -37417,6 +37418,7 @@ function extractVendorSessionId(events) {
|
|
|
37417
37418
|
|
|
37418
37419
|
// src/lib/agent-orchestrator/review-orchestrator.ts
|
|
37419
37420
|
import { existsSync as existsSync22, statSync as statSync5, readFileSync as readFileSync16 } from "node:fs";
|
|
37421
|
+
import { createHash as createHash2 } from "node:crypto";
|
|
37420
37422
|
import { join as join27 } from "node:path";
|
|
37421
37423
|
|
|
37422
37424
|
// src/lib/agent-orchestrator/artifact-writer.ts
|
|
@@ -37957,6 +37959,7 @@ function isRecord(value) {
|
|
|
37957
37959
|
|
|
37958
37960
|
// src/lib/agent-orchestrator/prompt-writer.ts
|
|
37959
37961
|
import { mkdir as mkdir3, readFile as readFile3, writeFile as writeFile3 } from "node:fs/promises";
|
|
37962
|
+
import { createHash } from "node:crypto";
|
|
37960
37963
|
import { dirname as dirname11, join as join26, relative as relative3 } from "node:path";
|
|
37961
37964
|
async function writePromptSnapshots(input) {
|
|
37962
37965
|
const promptsDir = join26(input.context.roundDir, "prompts");
|
|
@@ -37973,9 +37976,10 @@ async function writePromptSnapshots(input) {
|
|
|
37973
37976
|
}
|
|
37974
37977
|
const promptPath = `prompts/${reviewer.persona}-${reviewer.instance_index}.md`;
|
|
37975
37978
|
const reviewPath = `reviews/${reviewer.persona}-${reviewer.instance_index}.md`;
|
|
37979
|
+
const promptId = reviewerPromptId(input.context, reviewer, promptPath);
|
|
37976
37980
|
await writeFile3(
|
|
37977
37981
|
join26(input.context.roundDir, promptPath),
|
|
37978
|
-
await reviewerPrompt(input.context, reviewer, promptPath, reviewPath, sharedReviewerPrefix),
|
|
37982
|
+
await reviewerPrompt(input.context, reviewer, promptPath, reviewPath, sharedReviewerPrefix, promptId),
|
|
37979
37983
|
"utf-8"
|
|
37980
37984
|
);
|
|
37981
37985
|
reviewers.push({
|
|
@@ -37983,13 +37987,14 @@ async function writePromptSnapshots(input) {
|
|
|
37983
37987
|
instance: reviewer.instance_index,
|
|
37984
37988
|
model: reviewer.model,
|
|
37985
37989
|
promptPath,
|
|
37986
|
-
reviewPath
|
|
37990
|
+
reviewPath,
|
|
37991
|
+
promptId
|
|
37987
37992
|
});
|
|
37988
37993
|
}
|
|
37989
37994
|
const pipeline = {
|
|
37990
37995
|
aggregation: await writePipelinePrompt(input, "aggregation", "aggregation.md"),
|
|
37991
37996
|
validation: await writePipelinePrompt(input, "validation", "validation.md"),
|
|
37992
|
-
synthesis: await writePipelinePrompt(input, "synthesis", "
|
|
37997
|
+
synthesis: await writePipelinePrompt(input, "synthesis", "synthesis.md"),
|
|
37993
37998
|
translation_uk: await writePipelinePrompt(input, "translation_uk", "final.uk.md")
|
|
37994
37999
|
};
|
|
37995
38000
|
const resolvedTeam = {
|
|
@@ -38017,7 +38022,7 @@ async function writePipelinePrompt(input, stage, artifactPath) {
|
|
|
38017
38022
|
artifactPath
|
|
38018
38023
|
};
|
|
38019
38024
|
}
|
|
38020
|
-
async function reviewerPrompt(context, reviewer, promptPath, reviewPath, sharedPrefix) {
|
|
38025
|
+
async function reviewerPrompt(context, reviewer, promptPath, reviewPath, sharedPrefix, promptId) {
|
|
38021
38026
|
const personaPath = reviewerPersonaPath(context, reviewer);
|
|
38022
38027
|
const personaContent = await readTextFile(personaPath);
|
|
38023
38028
|
return [
|
|
@@ -38027,6 +38032,7 @@ async function reviewerPrompt(context, reviewer, promptPath, reviewPath, sharedP
|
|
|
38027
38032
|
`Reviewer id: ${reviewer.persona}-${reviewer.instance_index}`,
|
|
38028
38033
|
`Agent identity: ${reviewer.name}`,
|
|
38029
38034
|
`Model alias and resolved model: ${reviewer.model}`,
|
|
38035
|
+
`OCR prompt id: ${promptId}`,
|
|
38030
38036
|
"",
|
|
38031
38037
|
"## Reviewer Persona",
|
|
38032
38038
|
"",
|
|
@@ -38041,8 +38047,9 @@ async function reviewerPrompt(context, reviewer, promptPath, reviewPath, sharedP
|
|
|
38041
38047
|
"- Persona focus areas are for detecting issues only; this output contract overrides any persona request to propose fixes or broad guidance.",
|
|
38042
38048
|
"",
|
|
38043
38049
|
"Output contract:",
|
|
38044
|
-
`- Return review
|
|
38045
|
-
"- Return
|
|
38050
|
+
`- Return the review artifact through stdout for ${reviewPath}.`,
|
|
38051
|
+
"- Return exactly one fenced ```ocr-json block and no prose outside it.",
|
|
38052
|
+
`- The ocr-json block must include top-level "prompt_id": "${promptId}".`,
|
|
38046
38053
|
"- Include maximum 5 findings, sorted by severity and production impact.",
|
|
38047
38054
|
"- No introductions, no progress narration, no endpoint tables, no code snippets, no broad summaries.",
|
|
38048
38055
|
"- Include critical, high, and medium findings. Include low only when it has concrete runtime/user impact. Exclude info/style/theoretical-only items.",
|
|
@@ -38052,10 +38059,11 @@ async function reviewerPrompt(context, reviewer, promptPath, reviewPath, sharedP
|
|
|
38052
38059
|
"- Do not claim skipped agents ran.",
|
|
38053
38060
|
"",
|
|
38054
38061
|
"Required ocr-json schema:",
|
|
38055
|
-
"- Output exactly this compact JSON shape inside
|
|
38062
|
+
"- Output exactly this compact JSON shape inside a fenced ```ocr-json block:",
|
|
38056
38063
|
"",
|
|
38057
|
-
"```json",
|
|
38064
|
+
"```ocr-json",
|
|
38058
38065
|
"{",
|
|
38066
|
+
` "prompt_id": "${promptId}",`,
|
|
38059
38067
|
' "findings": [',
|
|
38060
38068
|
" {",
|
|
38061
38069
|
' "sev": "high",',
|
|
@@ -38081,6 +38089,18 @@ async function reviewerPrompt(context, reviewer, promptPath, reviewPath, sharedP
|
|
|
38081
38089
|
""
|
|
38082
38090
|
].join("\n");
|
|
38083
38091
|
}
|
|
38092
|
+
function reviewerPromptId(context, reviewer, promptPath) {
|
|
38093
|
+
const input = [
|
|
38094
|
+
context.sessionId,
|
|
38095
|
+
String(context.round),
|
|
38096
|
+
reviewer.persona,
|
|
38097
|
+
String(reviewer.instance_index),
|
|
38098
|
+
reviewer.name,
|
|
38099
|
+
reviewer.model,
|
|
38100
|
+
promptPath
|
|
38101
|
+
].join("\n");
|
|
38102
|
+
return `ocr-prompt:${createHash("sha256").update(input).digest("hex").slice(0, 24)}`;
|
|
38103
|
+
}
|
|
38084
38104
|
async function reviewerSharedPrefix(context) {
|
|
38085
38105
|
const sections = [
|
|
38086
38106
|
"# OCR Reviewer Shared Context",
|
|
@@ -38179,22 +38199,25 @@ function pipelinePrompt(context, stage, agent, promptPath, artifactPath) {
|
|
|
38179
38199
|
"Output contract:",
|
|
38180
38200
|
`- Return markdown through stdout for ${artifactPath}.`,
|
|
38181
38201
|
...stage === "translation_uk" ? [
|
|
38182
|
-
"- Translate the complete final review to Ukrainian.",
|
|
38183
|
-
"-
|
|
38184
|
-
"- Do not
|
|
38202
|
+
"- Translate the complete human final review to Ukrainian.",
|
|
38203
|
+
"- Return only readable Ukrainian Markdown for a developer.",
|
|
38204
|
+
"- Do not include ```ocr-json, JSON, analysis notes, verification notes, or progress narration.",
|
|
38205
|
+
"- Preserve markdown structure, headings, bullets, code spans, file paths, line numbers, commands, and model names.",
|
|
38185
38206
|
"- Do not add new findings, remove findings, change severity/category/verdict, or change counts."
|
|
38186
38207
|
] : stage === "aggregation" ? [
|
|
38187
|
-
"- Return
|
|
38208
|
+
"- Return exactly one fenced ```ocr-json block and no prose outside it.",
|
|
38188
38209
|
"- Keep the output compact: at most 8 findings and at most 12 dropped entries.",
|
|
38189
38210
|
"- Do not include code snippets, long quotes, markdown tables, or reviewer narrative.",
|
|
38190
38211
|
"- Read the original reviewer files, but aggregate only their structured findings and concrete evidence."
|
|
38191
|
-
] : ["-
|
|
38212
|
+
] : ["- Return exactly one fenced ```ocr-json block and no prose outside it."],
|
|
38192
38213
|
"- Do not write files directly.",
|
|
38193
38214
|
"",
|
|
38194
38215
|
...stage === "translation_uk" ? [
|
|
38195
38216
|
"Translation contract:",
|
|
38196
|
-
"- The output must be a Ukrainian-language version of final.md.",
|
|
38197
|
-
"-
|
|
38217
|
+
"- The output must be a Ukrainian-language version of final.md for humans.",
|
|
38218
|
+
"- Start with the translated top-level heading.",
|
|
38219
|
+
"- Do not wrap the answer in a markdown code fence.",
|
|
38220
|
+
"- Do not include JSON or machine-readable metadata.",
|
|
38198
38221
|
""
|
|
38199
38222
|
] : ["Required ocr-json schema:", schemaHint(stage), ""],
|
|
38200
38223
|
"Forbidden behavior:",
|
|
@@ -38205,7 +38228,7 @@ function pipelinePrompt(context, stage, agent, promptPath, artifactPath) {
|
|
|
38205
38228
|
}
|
|
38206
38229
|
function schemaHint(stage) {
|
|
38207
38230
|
if (stage === "translation_uk") {
|
|
38208
|
-
return "No
|
|
38231
|
+
return "No machine schema. Produce human-readable Ukrainian Markdown only.";
|
|
38209
38232
|
}
|
|
38210
38233
|
if (stage === "aggregation") {
|
|
38211
38234
|
return [
|
|
@@ -38219,7 +38242,7 @@ function schemaHint(stage) {
|
|
|
38219
38242
|
" - Keep low only when duplicated by 2+ reviewers or when it has concrete production/runtime impact.",
|
|
38220
38243
|
" - Drop info, style, theoretical-only, and future-extensibility-only items.",
|
|
38221
38244
|
"",
|
|
38222
|
-
"```json",
|
|
38245
|
+
"```ocr-json",
|
|
38223
38246
|
"{",
|
|
38224
38247
|
' "findings": [',
|
|
38225
38248
|
" {",
|
|
@@ -38261,7 +38284,7 @@ function schemaHint(stage) {
|
|
|
38261
38284
|
"- Verify claims against the actual code before confirming. Do not trust reviewer text without code evidence.",
|
|
38262
38285
|
"- Put every aggregation finding into exactly one of `confirmed`, `downgraded`, or `rejected`.",
|
|
38263
38286
|
"",
|
|
38264
|
-
"```json",
|
|
38287
|
+
"```ocr-json",
|
|
38265
38288
|
"{",
|
|
38266
38289
|
' "confirmed": [',
|
|
38267
38290
|
" {",
|
|
@@ -38303,7 +38326,7 @@ function schemaHint(stage) {
|
|
|
38303
38326
|
"- Write developer-facing findings from validation results, not from unvalidated reviewer claims.",
|
|
38304
38327
|
"- Include only confirmed and downgraded issues that a developer can act on.",
|
|
38305
38328
|
"",
|
|
38306
|
-
"```json",
|
|
38329
|
+
"```ocr-json",
|
|
38307
38330
|
"{",
|
|
38308
38331
|
' "verdict": "REQUEST CHANGES",',
|
|
38309
38332
|
' "synthesis_counts": {',
|
|
@@ -38432,7 +38455,48 @@ async function runReviewerPhase(input) {
|
|
|
38432
38455
|
} finally {
|
|
38433
38456
|
clearInterval(progressTimer);
|
|
38434
38457
|
}
|
|
38435
|
-
const
|
|
38458
|
+
const byId = new Map(input.resolvedTeam.reviewers.map((reviewer) => [
|
|
38459
|
+
`${reviewer.type}-${reviewer.instance}`,
|
|
38460
|
+
reviewer
|
|
38461
|
+
]));
|
|
38462
|
+
const sortedResults = sortResults(results, requests);
|
|
38463
|
+
const reviewerArtifacts = /* @__PURE__ */ new Map();
|
|
38464
|
+
const artifactStatuses = [];
|
|
38465
|
+
let failed = sortedResults.find((result) => result.exitCode !== 0);
|
|
38466
|
+
let validationFailure;
|
|
38467
|
+
for (const result of sortedResults) {
|
|
38468
|
+
const reviewer = byId.get(result.id);
|
|
38469
|
+
if (!reviewer) continue;
|
|
38470
|
+
if (result.exitCode !== 0) {
|
|
38471
|
+
artifactStatuses.push({
|
|
38472
|
+
id: result.id,
|
|
38473
|
+
exit_code: result.exitCode,
|
|
38474
|
+
artifact_written: false
|
|
38475
|
+
});
|
|
38476
|
+
continue;
|
|
38477
|
+
}
|
|
38478
|
+
const validated = normalizeReviewerArtifact(processOutput(result), reviewer.promptId);
|
|
38479
|
+
if (!validated.ok) {
|
|
38480
|
+
validationFailure ??= { result, errors: validated.errors };
|
|
38481
|
+
artifactStatuses.push({
|
|
38482
|
+
id: result.id,
|
|
38483
|
+
exit_code: 1,
|
|
38484
|
+
artifact_written: false,
|
|
38485
|
+
validation_errors: validated.errors
|
|
38486
|
+
});
|
|
38487
|
+
continue;
|
|
38488
|
+
}
|
|
38489
|
+
reviewerArtifacts.set(reviewer.reviewPath, validated.artifact);
|
|
38490
|
+
artifactStatuses.push({
|
|
38491
|
+
id: result.id,
|
|
38492
|
+
exit_code: result.exitCode,
|
|
38493
|
+
artifact_written: true,
|
|
38494
|
+
artifact_path: join27(input.context.roundDir, reviewer.reviewPath)
|
|
38495
|
+
});
|
|
38496
|
+
}
|
|
38497
|
+
for (const [reviewPath, artifact] of reviewerArtifacts) {
|
|
38498
|
+
await writeRoundArtifact(input.context.roundDir, reviewPath, artifact);
|
|
38499
|
+
}
|
|
38436
38500
|
if (failed) {
|
|
38437
38501
|
const failedRequest = requests.find((request) => request.id === failed.id);
|
|
38438
38502
|
const diagnostic = buildFailureDiagnostic(failed, failedRequest);
|
|
@@ -38451,9 +38515,11 @@ async function runReviewerPhase(input) {
|
|
|
38451
38515
|
diagnostic,
|
|
38452
38516
|
stderr: failed.stderr,
|
|
38453
38517
|
stdout_excerpt: excerpt(failed.stdout),
|
|
38454
|
-
|
|
38455
|
-
|
|
38456
|
-
|
|
38518
|
+
artifact_statuses: artifactStatuses,
|
|
38519
|
+
results: artifactStatuses.map((status) => ({
|
|
38520
|
+
id: status.id,
|
|
38521
|
+
exit_code: status.exit_code,
|
|
38522
|
+
artifact_written: status.artifact_written
|
|
38457
38523
|
}))
|
|
38458
38524
|
},
|
|
38459
38525
|
null,
|
|
@@ -38474,71 +38540,54 @@ async function runReviewerPhase(input) {
|
|
|
38474
38540
|
...diagnostic.stderr_excerpt ? [`stderr: ${diagnostic.stderr_excerpt}`] : [],
|
|
38475
38541
|
...diagnostic.stdout_excerpt ? [`stdout: ${diagnostic.stdout_excerpt}`] : []
|
|
38476
38542
|
],
|
|
38477
|
-
results:
|
|
38543
|
+
results: sortedResults
|
|
38478
38544
|
};
|
|
38479
38545
|
}
|
|
38480
|
-
|
|
38481
|
-
|
|
38482
|
-
|
|
38483
|
-
|
|
38484
|
-
|
|
38485
|
-
|
|
38486
|
-
|
|
38487
|
-
|
|
38488
|
-
|
|
38489
|
-
|
|
38490
|
-
|
|
38491
|
-
|
|
38492
|
-
|
|
38493
|
-
|
|
38494
|
-
|
|
38495
|
-
{
|
|
38496
|
-
|
|
38497
|
-
|
|
38498
|
-
|
|
38499
|
-
exit_code: 1,
|
|
38500
|
-
model: requests.find((request) => request.id === result.id)?.model,
|
|
38501
|
-
prompt_path: requests.find((request) => request.id === result.id)?.promptPath,
|
|
38502
|
-
cwd: requests.find((request) => request.id === result.id)?.cwd,
|
|
38503
|
-
diagnostic: {
|
|
38504
|
-
summary: "Reviewer output did not contain a valid compact ocr-json artifact. OCR refused to pass prose, plan-mode text, or malformed JSON into aggregation.",
|
|
38505
|
-
hint: "Inspect the saved reviewer prompt and process log. The reviewer must return exactly one fenced ```ocr-json block with structured findings.",
|
|
38506
|
-
stdout_excerpt: excerpt(processOutput(result))
|
|
38507
|
-
},
|
|
38508
|
-
stderr: result.stderr,
|
|
38509
|
-
stdout_excerpt: excerpt(result.stdout),
|
|
38510
|
-
validation_errors: validated.errors,
|
|
38511
|
-
results: sortResults(
|
|
38512
|
-
results.map((item) => item.id === result.id ? failedResult : item),
|
|
38513
|
-
requests
|
|
38514
|
-
).map((item) => ({
|
|
38515
|
-
id: item.id,
|
|
38516
|
-
exit_code: item.exitCode
|
|
38517
|
-
}))
|
|
38546
|
+
if (validationFailure) {
|
|
38547
|
+
const failedResult = { ...validationFailure.result, exitCode: 1 };
|
|
38548
|
+
const failureResults = sortedResults.map((item) => item.id === failedResult.id ? failedResult : item);
|
|
38549
|
+
await writeRoundArtifact(
|
|
38550
|
+
input.context.roundDir,
|
|
38551
|
+
"failure-summary.json",
|
|
38552
|
+
JSON.stringify(
|
|
38553
|
+
{
|
|
38554
|
+
schema_version: 1,
|
|
38555
|
+
phase: "reviews",
|
|
38556
|
+
failed_id: validationFailure.result.id,
|
|
38557
|
+
exit_code: 1,
|
|
38558
|
+
model: requests.find((request) => request.id === validationFailure.result.id)?.model,
|
|
38559
|
+
prompt_path: requests.find((request) => request.id === validationFailure.result.id)?.promptPath,
|
|
38560
|
+
cwd: requests.find((request) => request.id === validationFailure.result.id)?.cwd,
|
|
38561
|
+
diagnostic: {
|
|
38562
|
+
summary: "Reviewer output did not contain a valid compact ocr-json artifact. OCR refused to pass prose, plan-mode text, or malformed JSON into aggregation.",
|
|
38563
|
+
hint: "Inspect the saved reviewer prompt and process log. The reviewer must return exactly one fenced ```ocr-json block with structured findings.",
|
|
38564
|
+
stdout_excerpt: excerpt(processOutput(validationFailure.result))
|
|
38518
38565
|
},
|
|
38519
|
-
|
|
38520
|
-
|
|
38521
|
-
|
|
38522
|
-
|
|
38523
|
-
|
|
38524
|
-
|
|
38525
|
-
|
|
38526
|
-
|
|
38527
|
-
|
|
38528
|
-
|
|
38529
|
-
|
|
38530
|
-
|
|
38531
|
-
|
|
38532
|
-
|
|
38533
|
-
|
|
38534
|
-
|
|
38535
|
-
|
|
38536
|
-
|
|
38537
|
-
|
|
38538
|
-
|
|
38539
|
-
|
|
38566
|
+
stderr: validationFailure.result.stderr,
|
|
38567
|
+
stdout_excerpt: excerpt(validationFailure.result.stdout),
|
|
38568
|
+
validation_errors: validationFailure.errors,
|
|
38569
|
+
artifact_statuses: artifactStatuses,
|
|
38570
|
+
results: artifactStatuses.map((status) => ({
|
|
38571
|
+
id: status.id,
|
|
38572
|
+
exit_code: status.exit_code,
|
|
38573
|
+
artifact_written: status.artifact_written
|
|
38574
|
+
}))
|
|
38575
|
+
},
|
|
38576
|
+
null,
|
|
38577
|
+
2
|
|
38578
|
+
)
|
|
38579
|
+
);
|
|
38580
|
+
return {
|
|
38581
|
+
ok: false,
|
|
38582
|
+
failedId: validationFailure.result.id,
|
|
38583
|
+
errors: [
|
|
38584
|
+
`Reviewer ${validationFailure.result.id} produced invalid ocr-json output.`,
|
|
38585
|
+
...validationFailure.errors
|
|
38586
|
+
],
|
|
38587
|
+
results: failureResults
|
|
38588
|
+
};
|
|
38540
38589
|
}
|
|
38541
|
-
return { ok: true, results:
|
|
38590
|
+
return { ok: true, results: sortedResults };
|
|
38542
38591
|
}
|
|
38543
38592
|
async function runOpenCodeProcessAgentReview(input) {
|
|
38544
38593
|
const runner = input.runner ?? new OpenCodeProcessRunner();
|
|
@@ -38725,21 +38774,38 @@ async function runPipelineStages(input) {
|
|
|
38725
38774
|
results
|
|
38726
38775
|
};
|
|
38727
38776
|
}
|
|
38728
|
-
|
|
38729
|
-
|
|
38730
|
-
if (validationErrors.length > 0) {
|
|
38777
|
+
const normalizedArtifact = normalizeStageArtifact(processOutput(result));
|
|
38778
|
+
if (!normalizedArtifact.ok) {
|
|
38731
38779
|
await journal.endInstance(agentSessionId, {
|
|
38732
38780
|
exitCode: 1,
|
|
38733
|
-
note:
|
|
38781
|
+
note: normalizedArtifact.errors.join("\n")
|
|
38734
38782
|
});
|
|
38735
|
-
await writePipelineFailure(input.context.roundDir, stage, result, results,
|
|
38783
|
+
await writePipelineFailure(input.context.roundDir, stage, result, results, normalizedArtifact.errors);
|
|
38736
38784
|
return {
|
|
38737
38785
|
ok: false,
|
|
38738
38786
|
failedStage: stage,
|
|
38739
|
-
errors:
|
|
38787
|
+
errors: normalizedArtifact.errors,
|
|
38740
38788
|
results
|
|
38741
38789
|
};
|
|
38742
38790
|
}
|
|
38791
|
+
await writeRoundArtifact(input.context.roundDir, ref.artifactPath, normalizedArtifact.artifact);
|
|
38792
|
+
const validation = validateStageArtifact(input.context.roundDir, stage, ref.artifactPath);
|
|
38793
|
+
if (!validation.ok) {
|
|
38794
|
+
await journal.endInstance(agentSessionId, {
|
|
38795
|
+
exitCode: 1,
|
|
38796
|
+
note: validation.errors.join("\n")
|
|
38797
|
+
});
|
|
38798
|
+
await writePipelineFailure(input.context.roundDir, stage, result, results, validation.errors);
|
|
38799
|
+
return {
|
|
38800
|
+
ok: false,
|
|
38801
|
+
failedStage: stage,
|
|
38802
|
+
errors: validation.errors,
|
|
38803
|
+
results
|
|
38804
|
+
};
|
|
38805
|
+
}
|
|
38806
|
+
if (stage === "synthesis") {
|
|
38807
|
+
await writeRoundArtifact(input.context.roundDir, "final.md", renderHumanFinalReview(validation.value));
|
|
38808
|
+
}
|
|
38743
38809
|
await journal.endInstance(agentSessionId, { exitCode: 0 });
|
|
38744
38810
|
}
|
|
38745
38811
|
return { ok: true, results };
|
|
@@ -38834,15 +38900,8 @@ async function runUkrainianTranslation(input) {
|
|
|
38834
38900
|
});
|
|
38835
38901
|
return result;
|
|
38836
38902
|
}
|
|
38837
|
-
const
|
|
38838
|
-
const
|
|
38839
|
-
sourceFinalMarkdown,
|
|
38840
|
-
processOutput(result)
|
|
38841
|
-
);
|
|
38842
|
-
const translationErrors = validateUkrainianTranslationOutput(
|
|
38843
|
-
sourceFinalMarkdown,
|
|
38844
|
-
translatedMarkdown
|
|
38845
|
-
);
|
|
38903
|
+
const translatedMarkdown = normalizeHumanMarkdownOutput(processOutput(result));
|
|
38904
|
+
const translationErrors = validateUkrainianTranslationOutput(translatedMarkdown);
|
|
38846
38905
|
if (translationErrors.length > 0) {
|
|
38847
38906
|
await patchProcessMeta(input.context.roundDir, logPaths.meta, {
|
|
38848
38907
|
status: "failed",
|
|
@@ -38862,7 +38921,7 @@ async function runUkrainianTranslation(input) {
|
|
|
38862
38921
|
failed_id: request.id,
|
|
38863
38922
|
exit_code: 1,
|
|
38864
38923
|
diagnostic: {
|
|
38865
|
-
summary: "Ukrainian translation output
|
|
38924
|
+
summary: "Ukrainian translation output was not a readable human Markdown final review."
|
|
38866
38925
|
},
|
|
38867
38926
|
validation_errors: translationErrors,
|
|
38868
38927
|
stdout_excerpt: excerpt(result.stdout)
|
|
@@ -38900,23 +38959,21 @@ async function patchProcessMeta(roundDir, metaPath, patch) {
|
|
|
38900
38959
|
}
|
|
38901
38960
|
await writeRoundArtifact(roundDir, metaPath, JSON.stringify({ ...current, ...patch }, null, 2));
|
|
38902
38961
|
}
|
|
38903
|
-
function validateUkrainianTranslationOutput(
|
|
38904
|
-
const
|
|
38905
|
-
if (!
|
|
38906
|
-
|
|
38907
|
-
|
|
38908
|
-
|
|
38909
|
-
|
|
38962
|
+
function validateUkrainianTranslationOutput(translatedMarkdown) {
|
|
38963
|
+
const errors = [];
|
|
38964
|
+
if (!translatedMarkdown.trim()) errors.push("final.uk.md must not be empty.");
|
|
38965
|
+
if (/```ocr-json/i.test(translatedMarkdown) || /^\s*[{[]/.test(translatedMarkdown)) {
|
|
38966
|
+
errors.push("final.uk.md must be human Markdown without JSON or ocr-json blocks.");
|
|
38967
|
+
}
|
|
38968
|
+
if (!translatedMarkdown.trimStart().startsWith("#")) {
|
|
38969
|
+
errors.push("final.uk.md must start with a Markdown heading.");
|
|
38910
38970
|
}
|
|
38911
|
-
return
|
|
38971
|
+
return errors;
|
|
38912
38972
|
}
|
|
38913
|
-
function
|
|
38914
|
-
const
|
|
38915
|
-
|
|
38916
|
-
|
|
38917
|
-
return `${translatedWithoutJson}
|
|
38918
|
-
|
|
38919
|
-
${sourceBlock}
|
|
38973
|
+
function normalizeHumanMarkdownOutput(markdown) {
|
|
38974
|
+
const trimmed = markdown.trim();
|
|
38975
|
+
const fenced3 = trimmed.match(/^```(?:markdown|md)?\s*([\s\S]*?)```$/i);
|
|
38976
|
+
return `${(fenced3?.[1] ?? trimmed).trimEnd()}
|
|
38920
38977
|
`;
|
|
38921
38978
|
}
|
|
38922
38979
|
function extractOcrJsonFence(markdown) {
|
|
@@ -38930,15 +38987,61 @@ function sortResults(results, requests) {
|
|
|
38930
38987
|
function validateStageArtifact(roundDir, stage, artifactPath) {
|
|
38931
38988
|
const markdown = readFileSync16(join27(roundDir, artifactPath), "utf-8");
|
|
38932
38989
|
const extracted = extractOcrJsonBlock(markdown);
|
|
38933
|
-
if (!extracted.ok) return extracted
|
|
38990
|
+
if (!extracted.ok) return extracted;
|
|
38934
38991
|
const validated = stage === "aggregation" ? validateAggregationJson(extracted.value) : stage === "validation" ? validateValidationJson(extracted.value) : validateSynthesisJson(extracted.value);
|
|
38935
|
-
return validated
|
|
38992
|
+
return validated;
|
|
38993
|
+
}
|
|
38994
|
+
function normalizeStageArtifact(markdown) {
|
|
38995
|
+
const extracted = extractOcrJsonBlock(markdown);
|
|
38996
|
+
if (!extracted.ok) return extracted;
|
|
38997
|
+
const fence = extractOcrJsonFence(markdown);
|
|
38998
|
+
if (!fence) return { ok: false, errors: ["Expected exactly one fenced ```ocr-json block."] };
|
|
38999
|
+
return { ok: true, artifact: `${fence}
|
|
39000
|
+
` };
|
|
39001
|
+
}
|
|
39002
|
+
function renderHumanFinalReview(synthesis) {
|
|
39003
|
+
const grouped = {
|
|
39004
|
+
blocker: synthesis.findings.filter((finding) => finding.category === "blocker"),
|
|
39005
|
+
should_fix: synthesis.findings.filter((finding) => finding.category === "should_fix"),
|
|
39006
|
+
suggestion: synthesis.findings.filter((finding) => finding.category === "suggestion"),
|
|
39007
|
+
style: synthesis.findings.filter((finding) => finding.category === "style")
|
|
39008
|
+
};
|
|
39009
|
+
return [
|
|
39010
|
+
"# OCR Final Review",
|
|
39011
|
+
"",
|
|
39012
|
+
`**Verdict:** ${synthesis.verdict}`,
|
|
39013
|
+
"",
|
|
39014
|
+
`**Counts:** ${synthesis.synthesis_counts.blockers} blocker(s), ${synthesis.synthesis_counts.should_fix} should-fix item(s), ${synthesis.synthesis_counts.suggestions} suggestion(s).`,
|
|
39015
|
+
"",
|
|
39016
|
+
...renderFindingSection("Blockers", grouped.blocker),
|
|
39017
|
+
...renderFindingSection("Should Fix", grouped.should_fix),
|
|
39018
|
+
...renderFindingSection("Suggestions", grouped.suggestion),
|
|
39019
|
+
...renderFindingSection("Style", grouped.style)
|
|
39020
|
+
].join("\n").trimEnd() + "\n";
|
|
39021
|
+
}
|
|
39022
|
+
function renderFindingSection(title, findings) {
|
|
39023
|
+
if (findings.length === 0) return [`## ${title}`, "", "None.", ""];
|
|
39024
|
+
return [
|
|
39025
|
+
`## ${title}`,
|
|
39026
|
+
"",
|
|
39027
|
+
...findings.flatMap((finding, index) => [
|
|
39028
|
+
`### ${index + 1}. ${finding.title}`,
|
|
39029
|
+
"",
|
|
39030
|
+
`- **Severity:** ${finding.severity}`,
|
|
39031
|
+
`- **Location:** ${finding.file_path ?? "n/a"}${finding.line_start ? `:${finding.line_start}` : ""}${finding.line_end && finding.line_end !== finding.line_start ? `-${finding.line_end}` : ""}`,
|
|
39032
|
+
`- **Issue:** ${finding.summary}`,
|
|
39033
|
+
...finding.flagged_by?.length ? [`- **Flagged by:** ${finding.flagged_by.join(", ")}`] : [],
|
|
39034
|
+
...finding.validation_source ? [`- **Validation:** ${finding.validation_source}`] : [],
|
|
39035
|
+
""
|
|
39036
|
+
])
|
|
39037
|
+
];
|
|
38936
39038
|
}
|
|
38937
39039
|
function isNonEmptyFile(path2) {
|
|
38938
39040
|
return existsSync22(path2) && statSync5(path2).isFile() && statSync5(path2).size > 0;
|
|
38939
39041
|
}
|
|
38940
39042
|
async function writeProcessStartLog(roundDir, request) {
|
|
38941
39043
|
const metaPath = `process-logs/${request.id}.meta.json`;
|
|
39044
|
+
const promptMeta = readPromptMeta(request.promptPath);
|
|
38942
39045
|
await writeRoundArtifact(
|
|
38943
39046
|
roundDir,
|
|
38944
39047
|
metaPath,
|
|
@@ -38949,6 +39052,7 @@ async function writeProcessStartLog(roundDir, request) {
|
|
|
38949
39052
|
phase: request.phase,
|
|
38950
39053
|
model: request.model,
|
|
38951
39054
|
prompt_path: request.promptPath,
|
|
39055
|
+
...promptMeta,
|
|
38952
39056
|
status: "running",
|
|
38953
39057
|
started_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
38954
39058
|
},
|
|
@@ -38966,6 +39070,7 @@ async function writeProcessLogs(roundDir, request, result) {
|
|
|
38966
39070
|
const absoluteMetaPath = join27(roundDir, metaPath);
|
|
38967
39071
|
const startedAt = readStartedAt(absoluteMetaPath) ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
38968
39072
|
const completedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
39073
|
+
const promptMeta = readPromptMeta(request.promptPath);
|
|
38969
39074
|
await writeRoundArtifact(roundDir, stdoutPath, result.stdout);
|
|
38970
39075
|
await writeRoundArtifact(roundDir, stderrPath, result.stderr);
|
|
38971
39076
|
await writeRoundArtifact(
|
|
@@ -38978,6 +39083,7 @@ async function writeProcessLogs(roundDir, request, result) {
|
|
|
38978
39083
|
phase: request.phase,
|
|
38979
39084
|
model: request.model,
|
|
38980
39085
|
prompt_path: request.promptPath,
|
|
39086
|
+
...promptMeta,
|
|
38981
39087
|
status: result.exitCode === 0 ? "completed" : "failed",
|
|
38982
39088
|
started_at: startedAt,
|
|
38983
39089
|
completed_at: completedAt,
|
|
@@ -38994,6 +39100,16 @@ async function writeProcessLogs(roundDir, request, result) {
|
|
|
38994
39100
|
);
|
|
38995
39101
|
return { stdout: stdoutPath, stderr: stderrPath, meta: metaPath };
|
|
38996
39102
|
}
|
|
39103
|
+
function readPromptMeta(promptPath) {
|
|
39104
|
+
if (!existsSync22(promptPath)) return {};
|
|
39105
|
+
const prompt = readFileSync16(promptPath, "utf-8");
|
|
39106
|
+
const bytes = Buffer.byteLength(prompt, "utf-8");
|
|
39107
|
+
return {
|
|
39108
|
+
prompt_sha256: createHash2("sha256").update(prompt).digest("hex"),
|
|
39109
|
+
prompt_bytes: bytes,
|
|
39110
|
+
prompt_delivery: prompt.length <= DEFAULT_PROMPT_ARGV_LIMIT ? "inline" : "file"
|
|
39111
|
+
};
|
|
39112
|
+
}
|
|
38997
39113
|
function readStartedAt(path2) {
|
|
38998
39114
|
if (!existsSync22(path2)) return void 0;
|
|
38999
39115
|
try {
|
|
@@ -39007,21 +39123,24 @@ function readStartedAt(path2) {
|
|
|
39007
39123
|
function processOutput(result) {
|
|
39008
39124
|
return result.outputText ?? result.stdout;
|
|
39009
39125
|
}
|
|
39010
|
-
function normalizeReviewerArtifact(markdown) {
|
|
39126
|
+
function normalizeReviewerArtifact(markdown, expectedPromptId) {
|
|
39011
39127
|
const extracted = extractOcrJsonBlock(markdown);
|
|
39012
39128
|
if (!extracted.ok) return extracted;
|
|
39013
|
-
const validationErrors = validateReviewerJson(extracted.value);
|
|
39129
|
+
const validationErrors = validateReviewerJson(extracted.value, expectedPromptId);
|
|
39014
39130
|
if (validationErrors.length > 0) return { ok: false, errors: validationErrors };
|
|
39015
39131
|
const fence = extractOcrJsonFence(markdown);
|
|
39016
39132
|
if (!fence) return { ok: false, errors: ["Expected exactly one fenced ```ocr-json block."] };
|
|
39017
39133
|
return { ok: true, artifact: `${fence}
|
|
39018
39134
|
` };
|
|
39019
39135
|
}
|
|
39020
|
-
function validateReviewerJson(value) {
|
|
39136
|
+
function validateReviewerJson(value, expectedPromptId) {
|
|
39021
39137
|
const errors = [];
|
|
39022
39138
|
if (!isRecord2(value)) {
|
|
39023
39139
|
return ["Reviewer ocr-json must be an object."];
|
|
39024
39140
|
}
|
|
39141
|
+
if (value.prompt_id !== expectedPromptId) {
|
|
39142
|
+
errors.push(`Reviewer ocr-json prompt_id must equal ${expectedPromptId}.`);
|
|
39143
|
+
}
|
|
39025
39144
|
if (!Array.isArray(value.findings)) {
|
|
39026
39145
|
errors.push("Reviewer ocr-json findings must be an array.");
|
|
39027
39146
|
return errors;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nk070281sjv/cli",
|
|
3
|
-
"version": "2.3.
|
|
3
|
+
"version": "2.3.31",
|
|
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.31",
|
|
41
41
|
"chalk": "^5.4.1",
|
|
42
42
|
"chokidar": "^4.0.3",
|
|
43
43
|
"commander": "^13.0.0",
|