@nk070281sjv/cli 2.3.5 → 2.3.7

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.
Files changed (2) hide show
  1. package/dist/index.js +291 -31
  2. 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.5" : createRequire(import.meta.url)("../../package.json").version;
30782
+ var CLI_VERSION = true ? "2.3.7" : 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 dirname12 = sysPath.dirname(file);
31824
+ const dirname13 = sysPath.dirname(file);
31825
31825
  const basename9 = sysPath.basename(file);
31826
- const parent = this.fsw._getWatchedDir(dirname12);
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(dirname12, basename9);
31853
+ this.fsw._remove(dirname13, basename9);
31854
31854
  }
31855
31855
  } else if (parent.has(basename9)) {
31856
31856
  const at = newStats.atimeMs;
@@ -37409,6 +37409,10 @@ async function writeRoundArtifact(roundDir, relativePath, content) {
37409
37409
  import { mkdir as mkdir2, readFile, writeFile as writeFile2 } from "node:fs/promises";
37410
37410
  import { existsSync as existsSync21 } from "node:fs";
37411
37411
  import { dirname as dirname10, join as join25 } from "node:path";
37412
+ import { execFile } from "node:child_process";
37413
+ import { promisify } from "node:util";
37414
+ var execFileAsync = promisify(execFile);
37415
+ var GIT_SNAPSHOT_LIMIT = 2e4;
37412
37416
  async function prepareReviewContext(input) {
37413
37417
  const roundDir = join25(input.sessionDir, "rounds", `round-${input.round}`);
37414
37418
  await mkdir2(roundDir, { recursive: true });
@@ -37425,6 +37429,7 @@ async function prepareReviewContext(input) {
37425
37429
  "# Review Context\n\nNo shared review context has been recorded yet.\n"
37426
37430
  );
37427
37431
  const reviewBriefPath = join25(roundDir, "review-brief.md");
37432
+ const gitSnapshot = input.cwd ? await collectGitSnapshot(input.cwd) : void 0;
37428
37433
  await writeFile2(
37429
37434
  reviewBriefPath,
37430
37435
  [
@@ -37434,6 +37439,37 @@ async function prepareReviewContext(input) {
37434
37439
  `Round: ${input.round}`,
37435
37440
  `Discovered standards: ${discoveredStandardsPath}`,
37436
37441
  `Shared context: ${contextPath}`,
37442
+ ...input.cwd ? [`Repository root: ${input.cwd}`] : [],
37443
+ "",
37444
+ "## Scope",
37445
+ "",
37446
+ "Review the current working tree changes. Focus on changed files and the unchanged code needed to validate those changes.",
37447
+ "Do not review generated OCR session artifacts under `.ocr/sessions/` unless they are explicitly part of the requested change.",
37448
+ "",
37449
+ ...gitSnapshot ? [
37450
+ "## Git Status",
37451
+ "",
37452
+ fenced("text", gitSnapshot.status || "No working tree changes reported."),
37453
+ "",
37454
+ "## Changed Files",
37455
+ "",
37456
+ fenced("text", gitSnapshot.changedFiles || "No changed files reported."),
37457
+ "",
37458
+ "## Diff Stat",
37459
+ "",
37460
+ fenced("text", gitSnapshot.diffStat || "No diff stat available."),
37461
+ ""
37462
+ ] : [
37463
+ "## Git Snapshot",
37464
+ "",
37465
+ "No repository snapshot was captured by the CLI.",
37466
+ ""
37467
+ ],
37468
+ "## Reviewer Guidance",
37469
+ "",
37470
+ "- Read this brief first, then inspect relevant changed files and surrounding code as needed.",
37471
+ "- Prefer targeted `git diff -- <path>` and file reads over broad repository scans.",
37472
+ "- Report only findings supported by code evidence and concrete file references.",
37437
37473
  ""
37438
37474
  ].join("\n"),
37439
37475
  "utf-8"
@@ -37478,6 +37514,49 @@ async function prepareReviewContext(input) {
37478
37514
  requirementsExpected: Boolean(requirementsPath)
37479
37515
  };
37480
37516
  }
37517
+ async function collectGitSnapshot(cwd) {
37518
+ const [status, cachedFiles, worktreeFiles, diffStat] = await Promise.all([
37519
+ runGit(cwd, ["status", "--short"]),
37520
+ runGit(cwd, ["diff", "--cached", "--name-only"]),
37521
+ runGit(cwd, ["diff", "--name-only", "HEAD"]),
37522
+ runGit(cwd, ["diff", "--stat", "HEAD"])
37523
+ ]);
37524
+ return {
37525
+ status,
37526
+ changedFiles: uniqueLines(`${cachedFiles}
37527
+ ${worktreeFiles}`),
37528
+ diffStat
37529
+ };
37530
+ }
37531
+ async function runGit(cwd, args) {
37532
+ try {
37533
+ const { stdout, stderr } = await execFileAsync("git", args, {
37534
+ cwd,
37535
+ maxBuffer: GIT_SNAPSHOT_LIMIT * 2
37536
+ });
37537
+ return truncate(`${stdout}${stderr ? `
37538
+ ${stderr}` : ""}`.trim());
37539
+ } catch (error) {
37540
+ if (error && typeof error === "object" && "stdout" in error && typeof error.stdout === "string") {
37541
+ return truncate(error.stdout.trim());
37542
+ }
37543
+ return "";
37544
+ }
37545
+ }
37546
+ function uniqueLines(value) {
37547
+ const lines = value.split(/\r?\n/).map((line) => line.trim()).filter(Boolean);
37548
+ return [...new Set(lines)].join("\n");
37549
+ }
37550
+ function truncate(value) {
37551
+ if (value.length <= GIT_SNAPSHOT_LIMIT) return value;
37552
+ return `${value.slice(0, GIT_SNAPSHOT_LIMIT)}
37553
+ [truncated by OCR CLI]`;
37554
+ }
37555
+ function fenced(language, value) {
37556
+ return `\`\`\`${language}
37557
+ ${value}
37558
+ \`\`\``;
37559
+ }
37481
37560
  async function ensureMarkdownFile(path2, fallbackContent) {
37482
37561
  if (existsSync21(path2)) {
37483
37562
  await readFile(path2, "utf-8");
@@ -37516,6 +37595,9 @@ function buildOpenCodeRunArgs(input) {
37516
37595
  if (input.attachUrl) {
37517
37596
  base.push("--attach", input.attachUrl, "--dir", input.cwd);
37518
37597
  }
37598
+ if (input.pure) {
37599
+ base.push("--pure");
37600
+ }
37519
37601
  const limit = input.promptArgvLimit ?? DEFAULT_PROMPT_ARGV_LIMIT;
37520
37602
  if (input.prompt.length <= limit) {
37521
37603
  return [...base, input.prompt];
@@ -37534,15 +37616,25 @@ function parseOpenCodeJsonEvents(stdout) {
37534
37616
  }
37535
37617
  return events;
37536
37618
  }
37619
+ function extractOpenCodeText(events) {
37620
+ return events.map((event) => {
37621
+ if (!isRecord(event)) return "";
37622
+ const part = event.part;
37623
+ if (!isRecord(part) || part.type !== "text") return "";
37624
+ return typeof part.text === "string" ? part.text : "";
37625
+ }).join("").trimStart();
37626
+ }
37537
37627
  var OpenCodeProcessRunner = class {
37538
37628
  spawn;
37539
37629
  promptArgvLimit;
37540
37630
  serveReadyTimeoutMs;
37631
+ pure;
37541
37632
  server;
37542
37633
  constructor(options = {}) {
37543
37634
  this.spawn = options.spawn ?? spawnBinary;
37544
37635
  this.promptArgvLimit = options.promptArgvLimit ?? DEFAULT_PROMPT_ARGV_LIMIT;
37545
- this.serveReadyTimeoutMs = options.serveReadyTimeoutMs ?? 15e3;
37636
+ this.serveReadyTimeoutMs = options.serveReadyTimeoutMs ?? 12e4;
37637
+ this.pure = options.pure ?? true;
37546
37638
  }
37547
37639
  async run(request, signal) {
37548
37640
  const prompt = await readFile2(request.promptPath, "utf-8");
@@ -37555,7 +37647,8 @@ var OpenCodeProcessRunner = class {
37555
37647
  exitCode: -1,
37556
37648
  stdout: "",
37557
37649
  stderr: error instanceof Error ? error.message : "Failed to start OpenCode server.",
37558
- ndjsonEvents: []
37650
+ ndjsonEvents: [],
37651
+ outputText: ""
37559
37652
  };
37560
37653
  }
37561
37654
  const args = buildOpenCodeRunArgs({
@@ -37565,6 +37658,7 @@ var OpenCodeProcessRunner = class {
37565
37658
  promptPath: request.promptPath,
37566
37659
  cwd: request.cwd,
37567
37660
  attachUrl,
37661
+ pure: this.pure,
37568
37662
  promptArgvLimit: this.promptArgvLimit
37569
37663
  });
37570
37664
  return new Promise((resolve4) => {
@@ -37574,7 +37668,8 @@ var OpenCodeProcessRunner = class {
37574
37668
  exitCode: -1,
37575
37669
  stdout: "",
37576
37670
  stderr: "OpenCode process aborted before spawn.",
37577
- ndjsonEvents: []
37671
+ ndjsonEvents: [],
37672
+ outputText: ""
37578
37673
  });
37579
37674
  return;
37580
37675
  }
@@ -37604,21 +37699,25 @@ var OpenCodeProcessRunner = class {
37604
37699
  stderr += chunk;
37605
37700
  });
37606
37701
  child.on("error", (err) => {
37702
+ const ndjsonEvents = parseOpenCodeJsonEvents(stdout);
37607
37703
  settle({
37608
37704
  id: request.id,
37609
37705
  exitCode: -1,
37610
37706
  stdout,
37611
37707
  stderr: stderr || err.message,
37612
- ndjsonEvents: parseOpenCodeJsonEvents(stdout)
37708
+ ndjsonEvents,
37709
+ outputText: extractOpenCodeText(ndjsonEvents)
37613
37710
  });
37614
37711
  });
37615
37712
  child.on("close", (code) => {
37713
+ const ndjsonEvents = parseOpenCodeJsonEvents(stdout);
37616
37714
  settle({
37617
37715
  id: request.id,
37618
37716
  exitCode: code ?? -1,
37619
37717
  stdout,
37620
37718
  stderr,
37621
- ndjsonEvents: parseOpenCodeJsonEvents(stdout)
37719
+ ndjsonEvents,
37720
+ outputText: extractOpenCodeText(ndjsonEvents)
37622
37721
  });
37623
37722
  });
37624
37723
  });
@@ -37656,7 +37755,14 @@ var OpenCodeProcessRunner = class {
37656
37755
  }
37657
37756
  const child = this.spawn(
37658
37757
  "opencode",
37659
- ["serve", "--hostname", "127.0.0.1", "--port", "0"],
37758
+ [
37759
+ "serve",
37760
+ "--hostname",
37761
+ "127.0.0.1",
37762
+ "--port",
37763
+ "0",
37764
+ ...this.pure ? ["--pure"] : []
37765
+ ],
37660
37766
  {
37661
37767
  cwd,
37662
37768
  stdio: ["ignore", "pipe", "pipe"]
@@ -37731,10 +37837,13 @@ ${server.stderr}`);
37731
37837
  function parseServeUrl(output) {
37732
37838
  return output.match(/opencode server listening on (https?:\/\/\S+)/)?.[1];
37733
37839
  }
37840
+ function isRecord(value) {
37841
+ return typeof value === "object" && value !== null;
37842
+ }
37734
37843
 
37735
37844
  // src/lib/agent-orchestrator/prompt-writer.ts
37736
37845
  import { mkdir as mkdir3, writeFile as writeFile3 } from "node:fs/promises";
37737
- import { join as join26, relative as relative3 } from "node:path";
37846
+ import { dirname as dirname11, join as join26, relative as relative3 } from "node:path";
37738
37847
  async function writePromptSnapshots(input) {
37739
37848
  const promptsDir = join26(input.context.roundDir, "prompts");
37740
37849
  const reviewsDir = join26(input.context.roundDir, "reviews");
@@ -37803,6 +37912,7 @@ function reviewerPrompt(context, reviewer, promptPath, reviewPath) {
37803
37912
  "Phase: reviews",
37804
37913
  "",
37805
37914
  "Input files to read before reviewing:",
37915
+ `- ${reviewerPersonaPath(context, reviewer)}`,
37806
37916
  `- ${context.discoveredStandardsPath}`,
37807
37917
  `- ${context.contextPath}`,
37808
37918
  `- ${context.reviewBriefPath}`,
@@ -37816,8 +37926,14 @@ function reviewerPrompt(context, reviewer, promptPath, reviewPath) {
37816
37926
  ` - ${relative3(context.roundDir, context.discoveredStandardsPath)}`,
37817
37927
  ` - ${relative3(context.roundDir, context.contextPath)}`,
37818
37928
  ` - ${relative3(context.roundDir, context.reviewBriefPath)}`,
37929
+ ` - ${relative3(context.roundDir, reviewerPersonaPath(context, reviewer))}`,
37819
37930
  ...context.requirementsPath ? [` - ${relative3(context.roundDir, context.requirementsPath)}`] : [],
37820
37931
  "",
37932
+ "Review focus:",
37933
+ "- Apply the reviewer persona file as your primary review lens.",
37934
+ "- Use the review brief as the source of truth for changed files and scope.",
37935
+ "- Inspect surrounding source code only when needed to validate a finding.",
37936
+ "",
37821
37937
  "Output contract:",
37822
37938
  `- Return review markdown through stdout for ${reviewPath}.`,
37823
37939
  "- Do not write files directly.",
@@ -37832,6 +37948,10 @@ function reviewerPrompt(context, reviewer, promptPath, reviewPath) {
37832
37948
  ""
37833
37949
  ].join("\n");
37834
37950
  }
37951
+ function reviewerPersonaPath(context, reviewer) {
37952
+ const ocrDir = dirname11(dirname11(context.sessionDir));
37953
+ return join26(ocrDir, "skills", "references", "reviewers", `${reviewer.persona}.md`);
37954
+ }
37835
37955
  function pipelinePrompt(context, stage, agent, promptPath, artifactPath) {
37836
37956
  return [
37837
37957
  `# Pipeline Agent: ${stage}`,
@@ -37889,6 +38009,7 @@ function schemaHint(stage) {
37889
38009
  // src/lib/agent-orchestrator/review-orchestrator.ts
37890
38010
  async function runReviewerPhase(input) {
37891
38011
  const journal = input.journal ?? new NoopAgentLifecycleJournal();
38012
+ const emit = input.emit ?? (() => void 0);
37892
38013
  const controller = new AbortController();
37893
38014
  const reviewerRequests = input.resolvedTeam.reviewers.map((reviewer) => {
37894
38015
  if (!reviewer.model.trim()) {
@@ -37907,6 +38028,18 @@ async function runReviewerPhase(input) {
37907
38028
  };
37908
38029
  });
37909
38030
  const requests = reviewerRequests.map((item) => item.request);
38031
+ const running = new Set(requests.map((request) => request.id));
38032
+ const completed = /* @__PURE__ */ new Set();
38033
+ const progressTimer = setInterval(() => {
38034
+ emit({
38035
+ event: "reviews:progress",
38036
+ phase: "reviews",
38037
+ completed: completed.size,
38038
+ total: requests.length,
38039
+ running: [...running].sort()
38040
+ });
38041
+ }, 3e4);
38042
+ progressTimer.unref?.();
37910
38043
  const promises = reviewerRequests.map(async ({ reviewer, request }) => {
37911
38044
  const agentSessionId = await journal.startInstance({
37912
38045
  workflowId: input.context.sessionId,
@@ -37917,7 +38050,28 @@ async function runReviewerPhase(input) {
37917
38050
  phase: "reviews"
37918
38051
  });
37919
38052
  await journal.beat(agentSessionId);
38053
+ const startLogPaths = await writeProcessStartLog(input.context.roundDir, request);
38054
+ emit({
38055
+ event: "agent:start",
38056
+ phase: "reviews",
38057
+ id: request.id,
38058
+ model: request.model,
38059
+ prompt_path: request.promptPath,
38060
+ meta_log: startLogPaths.meta
38061
+ });
37920
38062
  const result = await input.runner.run(request, controller.signal);
38063
+ const logPaths = await writeProcessLogs(input.context.roundDir, request, result);
38064
+ running.delete(request.id);
38065
+ completed.add(request.id);
38066
+ emit({
38067
+ event: "agent:complete",
38068
+ phase: "reviews",
38069
+ id: request.id,
38070
+ exit_code: result.exitCode,
38071
+ stdout_log: logPaths.stdout,
38072
+ stderr_log: logPaths.stderr,
38073
+ meta_log: logPaths.meta
38074
+ });
37921
38075
  const vendorId = extractVendorSessionId(result.ndjsonEvents);
37922
38076
  if (vendorId) await journal.bindVendorId(agentSessionId, vendorId);
37923
38077
  await journal.endInstance(agentSessionId, {
@@ -37927,15 +38081,19 @@ async function runReviewerPhase(input) {
37927
38081
  return result;
37928
38082
  });
37929
38083
  const results = [];
37930
- await Promise.all(
37931
- promises.map(async (promise) => {
37932
- const result = await promise;
37933
- results.push(result);
37934
- if (result.exitCode !== 0) {
37935
- controller.abort();
37936
- }
37937
- })
37938
- );
38084
+ try {
38085
+ await Promise.all(
38086
+ promises.map(async (promise) => {
38087
+ const result = await promise;
38088
+ results.push(result);
38089
+ if (result.exitCode !== 0) {
38090
+ controller.abort();
38091
+ }
38092
+ })
38093
+ );
38094
+ } finally {
38095
+ clearInterval(progressTimer);
38096
+ }
37939
38097
  const failed = results.find((result) => result.exitCode !== 0);
37940
38098
  if (failed) {
37941
38099
  const failedRequest = requests.find((request) => request.id === failed.id);
@@ -37988,7 +38146,7 @@ async function runReviewerPhase(input) {
37988
38146
  for (const result of sortResults(results, requests)) {
37989
38147
  const reviewer = byId.get(result.id);
37990
38148
  if (!reviewer) continue;
37991
- await writeRoundArtifact(input.context.roundDir, reviewer.reviewPath, result.stdout);
38149
+ await writeRoundArtifact(input.context.roundDir, reviewer.reviewPath, processOutput(result));
37992
38150
  }
37993
38151
  return { ok: true, results: sortResults(results, requests) };
37994
38152
  }
@@ -38002,7 +38160,8 @@ async function runOpenCodeProcessAgentReview(input) {
38002
38160
  sessionId: input.sessionId,
38003
38161
  sessionDir: input.sessionDir,
38004
38162
  round: input.round,
38005
- requirements: input.requirements
38163
+ requirements: input.requirements,
38164
+ cwd: input.cwd
38006
38165
  });
38007
38166
  const snapshot = await writePromptSnapshots({
38008
38167
  context,
@@ -38021,7 +38180,8 @@ async function runOpenCodeProcessAgentReview(input) {
38021
38180
  resolvedTeam: snapshot.resolvedTeam,
38022
38181
  cwd: input.cwd,
38023
38182
  runner,
38024
- journal
38183
+ journal,
38184
+ emit
38025
38185
  });
38026
38186
  if (!reviewerResult.ok) {
38027
38187
  emit({
@@ -38042,7 +38202,8 @@ async function runOpenCodeProcessAgentReview(input) {
38042
38202
  resolvedTeam: snapshot.resolvedTeam,
38043
38203
  cwd: input.cwd,
38044
38204
  runner,
38045
- journal
38205
+ journal,
38206
+ emit
38046
38207
  });
38047
38208
  if (!pipelineResult.ok) {
38048
38209
  emit({
@@ -38069,6 +38230,7 @@ async function runOpenCodeProcessAgentReview(input) {
38069
38230
  }
38070
38231
  async function runPipelineStages(input) {
38071
38232
  const journal = input.journal ?? new NoopAgentLifecycleJournal();
38233
+ const emit = input.emit ?? (() => void 0);
38072
38234
  const missingReviews = input.resolvedTeam.reviewers.map((reviewer) => reviewer.reviewPath).filter((reviewPath) => !isNonEmptyFile(join27(input.context.roundDir, reviewPath)));
38073
38235
  if (missingReviews.length > 0) {
38074
38236
  return {
@@ -38097,7 +38259,26 @@ async function runPipelineStages(input) {
38097
38259
  cwd: input.cwd,
38098
38260
  phase: stage
38099
38261
  };
38262
+ const startLogPaths = await writeProcessStartLog(input.context.roundDir, request);
38263
+ emit({
38264
+ event: "agent:start",
38265
+ phase: stage,
38266
+ id: request.id,
38267
+ model: request.model,
38268
+ prompt_path: request.promptPath,
38269
+ meta_log: startLogPaths.meta
38270
+ });
38100
38271
  const result = await input.runner.run(request, new AbortController().signal);
38272
+ const logPaths = await writeProcessLogs(input.context.roundDir, request, result);
38273
+ emit({
38274
+ event: "agent:complete",
38275
+ phase: stage,
38276
+ id: request.id,
38277
+ exit_code: result.exitCode,
38278
+ stdout_log: logPaths.stdout,
38279
+ stderr_log: logPaths.stderr,
38280
+ meta_log: logPaths.meta
38281
+ });
38101
38282
  results.push(result);
38102
38283
  const vendorId = extractVendorSessionId(result.ndjsonEvents);
38103
38284
  if (vendorId) await journal.bindVendorId(agentSessionId, vendorId);
@@ -38123,7 +38304,7 @@ async function runPipelineStages(input) {
38123
38304
  results
38124
38305
  };
38125
38306
  }
38126
- await writeRoundArtifact(input.context.roundDir, ref.artifactPath, result.stdout);
38307
+ await writeRoundArtifact(input.context.roundDir, ref.artifactPath, processOutput(result));
38127
38308
  const validationErrors = validateStageArtifact(input.context.roundDir, stage, ref.artifactPath);
38128
38309
  if (validationErrors.length > 0) {
38129
38310
  await journal.endInstance(agentSessionId, {
@@ -38156,6 +38337,85 @@ function validateStageArtifact(roundDir, stage, artifactPath) {
38156
38337
  function isNonEmptyFile(path2) {
38157
38338
  return existsSync22(path2) && statSync5(path2).isFile() && statSync5(path2).size > 0;
38158
38339
  }
38340
+ async function writeProcessStartLog(roundDir, request) {
38341
+ const metaPath = `process-logs/${request.id}.meta.json`;
38342
+ await writeRoundArtifact(
38343
+ roundDir,
38344
+ metaPath,
38345
+ JSON.stringify(
38346
+ {
38347
+ schema_version: 1,
38348
+ id: request.id,
38349
+ phase: request.phase,
38350
+ model: request.model,
38351
+ prompt_path: request.promptPath,
38352
+ status: "running",
38353
+ started_at: (/* @__PURE__ */ new Date()).toISOString()
38354
+ },
38355
+ null,
38356
+ 2
38357
+ )
38358
+ );
38359
+ return { meta: metaPath };
38360
+ }
38361
+ async function writeProcessLogs(roundDir, request, result) {
38362
+ const prefix = `process-logs/${request.id}`;
38363
+ const stdoutPath = `${prefix}.stdout.ndjson`;
38364
+ const stderrPath = `${prefix}.stderr.log`;
38365
+ const metaPath = `${prefix}.meta.json`;
38366
+ const absoluteMetaPath = join27(roundDir, metaPath);
38367
+ const startedAt = readStartedAt(absoluteMetaPath) ?? (/* @__PURE__ */ new Date()).toISOString();
38368
+ const completedAt = (/* @__PURE__ */ new Date()).toISOString();
38369
+ await writeRoundArtifact(roundDir, stdoutPath, result.stdout);
38370
+ await writeRoundArtifact(roundDir, stderrPath, result.stderr);
38371
+ await writeRoundArtifact(
38372
+ roundDir,
38373
+ metaPath,
38374
+ JSON.stringify(
38375
+ {
38376
+ schema_version: 1,
38377
+ id: request.id,
38378
+ phase: request.phase,
38379
+ model: request.model,
38380
+ prompt_path: request.promptPath,
38381
+ status: "completed",
38382
+ started_at: startedAt,
38383
+ completed_at: completedAt,
38384
+ duration_ms: Math.max(0, Date.parse(completedAt) - Date.parse(startedAt)),
38385
+ exit_code: result.exitCode,
38386
+ output_text_bytes: Buffer.byteLength(processOutput(result), "utf-8"),
38387
+ stdout_bytes: Buffer.byteLength(result.stdout, "utf-8"),
38388
+ stderr_bytes: Buffer.byteLength(result.stderr, "utf-8"),
38389
+ opencode_session_ids: extractUniqueSessionIds(result.ndjsonEvents)
38390
+ },
38391
+ null,
38392
+ 2
38393
+ )
38394
+ );
38395
+ return { stdout: stdoutPath, stderr: stderrPath, meta: metaPath };
38396
+ }
38397
+ function readStartedAt(path2) {
38398
+ if (!existsSync22(path2)) return void 0;
38399
+ try {
38400
+ const parsed = JSON.parse(readFileSync16(path2, "utf-8"));
38401
+ if (!isRecord2(parsed)) return void 0;
38402
+ return typeof parsed.started_at === "string" ? parsed.started_at : void 0;
38403
+ } catch {
38404
+ return void 0;
38405
+ }
38406
+ }
38407
+ function processOutput(result) {
38408
+ return result.outputText ?? result.stdout;
38409
+ }
38410
+ function extractUniqueSessionIds(events) {
38411
+ const ids = /* @__PURE__ */ new Set();
38412
+ for (const event of events) {
38413
+ if (!isRecord2(event)) continue;
38414
+ const sessionID = event.sessionID;
38415
+ if (typeof sessionID === "string") ids.add(sessionID);
38416
+ }
38417
+ return [...ids];
38418
+ }
38159
38419
  async function writePipelineFailure(roundDir, stage, result, results, validationErrors = [], diagnostic = buildFailureDiagnostic(result)) {
38160
38420
  await writeRoundArtifact(
38161
38421
  roundDir,
@@ -38217,11 +38477,11 @@ function buildFailureDiagnostic(result, request) {
38217
38477
  }
38218
38478
  function extractOpenCodeErrorMessage(events) {
38219
38479
  for (const event of events) {
38220
- if (!isRecord(event) || event.type !== "error") continue;
38480
+ if (!isRecord2(event) || event.type !== "error") continue;
38221
38481
  const error = event.error;
38222
- if (!isRecord(error)) continue;
38482
+ if (!isRecord2(error)) continue;
38223
38483
  const data = error.data;
38224
- if (isRecord(data) && typeof data.message === "string") return data.message;
38484
+ if (isRecord2(data) && typeof data.message === "string") return data.message;
38225
38485
  if (typeof error.message === "string") return error.message;
38226
38486
  }
38227
38487
  return void 0;
@@ -38234,7 +38494,7 @@ function excerpt(value, limit = 1200) {
38234
38494
  function stripAnsi2(value) {
38235
38495
  return value.replace(/\u001b\[[0-9;]*m/g, "");
38236
38496
  }
38237
- function isRecord(value) {
38497
+ function isRecord2(value) {
38238
38498
  return typeof value === "object" && value !== null;
38239
38499
  }
38240
38500
 
@@ -38624,12 +38884,12 @@ var updateCommand = new Command("update").description("Update OCR assets after p
38624
38884
 
38625
38885
  // src/commands/dashboard.ts
38626
38886
  import { existsSync as existsSync25 } from "node:fs";
38627
- import { join as join30, dirname as dirname11 } from "node:path";
38887
+ import { join as join30, dirname as dirname12 } from "node:path";
38628
38888
  import { fileURLToPath } from "node:url";
38629
38889
  init_src();
38630
38890
  init_db();
38631
38891
  var __filename = fileURLToPath(import.meta.url);
38632
- var __dirname = dirname11(__filename);
38892
+ var __dirname = dirname12(__filename);
38633
38893
  function resolveServerPath() {
38634
38894
  return join30(__dirname, "dashboard", "server.js");
38635
38895
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nk070281sjv/cli",
3
- "version": "2.3.5",
3
+ "version": "2.3.7",
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.5",
40
+ "@nk070281sjv/agents": "2.3.7",
41
41
  "chalk": "^5.4.1",
42
42
  "chokidar": "^4.0.3",
43
43
  "commander": "^13.0.0",