@open-code-review/cli 1.5.1 → 1.6.0

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 (59) hide show
  1. package/README.md +57 -127
  2. package/dist/dashboard/client/assets/{_basePickBy-BJKCdvle.js → _basePickBy-BGuMbEDR.js} +1 -1
  3. package/dist/dashboard/client/assets/{_baseUniq-L_sxIO0r.js → _baseUniq-Bx8loabg.js} +1 -1
  4. package/dist/dashboard/client/assets/{arc-tqAEcLt5.js → arc-DUgpt7nY.js} +1 -1
  5. package/dist/dashboard/client/assets/{architectureDiagram-VXUJARFQ-CrKQo6Ye.js → architectureDiagram-VXUJARFQ-D25nt6Xz.js} +1 -1
  6. package/dist/dashboard/client/assets/{blockDiagram-VD42YOAC-DXOc89nw.js → blockDiagram-VD42YOAC-D8PUF3h4.js} +1 -1
  7. package/dist/dashboard/client/assets/{c4Diagram-YG6GDRKO-Ba-jYbw0.js → c4Diagram-YG6GDRKO-lorsCz-I.js} +1 -1
  8. package/dist/dashboard/client/assets/channel-yW2sWou_.js +1 -0
  9. package/dist/dashboard/client/assets/{chunk-4BX2VUAB-D1G3HCqL.js → chunk-4BX2VUAB-8lVyfRJM.js} +1 -1
  10. package/dist/dashboard/client/assets/{chunk-55IACEB6-FI7g4AjR.js → chunk-55IACEB6-C4SjgsZO.js} +1 -1
  11. package/dist/dashboard/client/assets/{chunk-B4BG7PRW-DhEGFGWs.js → chunk-B4BG7PRW-BXzTPbH1.js} +1 -1
  12. package/dist/dashboard/client/assets/{chunk-DI55MBZ5-Da3-6ZE4.js → chunk-DI55MBZ5-Bp7QllDt.js} +1 -1
  13. package/dist/dashboard/client/assets/{chunk-FMBD7UC4-D0QLOjiy.js → chunk-FMBD7UC4-B4g9S67N.js} +1 -1
  14. package/dist/dashboard/client/assets/{chunk-QN33PNHL-WkfgpbLo.js → chunk-QN33PNHL-Dyk7Hc0J.js} +1 -1
  15. package/dist/dashboard/client/assets/{chunk-QZHKN3VN-Bqn0IO1w.js → chunk-QZHKN3VN-DTvkGdnm.js} +1 -1
  16. package/dist/dashboard/client/assets/{chunk-TZMSLE5B-CC_K_BeL.js → chunk-TZMSLE5B-BAeZLvrI.js} +1 -1
  17. package/dist/dashboard/client/assets/classDiagram-2ON5EDUG-1pMX5UXO.js +1 -0
  18. package/dist/dashboard/client/assets/classDiagram-v2-WZHVMYZB-1pMX5UXO.js +1 -0
  19. package/dist/dashboard/client/assets/clone-DQwdw3YR.js +1 -0
  20. package/dist/dashboard/client/assets/{cose-bilkent-S5V4N54A-D8urqxIF.js → cose-bilkent-S5V4N54A--6-kzrdu.js} +1 -1
  21. package/dist/dashboard/client/assets/{dagre-6UL2VRFP-w2xS0ztU.js → dagre-6UL2VRFP-D10_QE2P.js} +1 -1
  22. package/dist/dashboard/client/assets/{diagram-PSM6KHXK-DlOtv6zO.js → diagram-PSM6KHXK-kS1x75Bl.js} +1 -1
  23. package/dist/dashboard/client/assets/{diagram-QEK2KX5R-EpxsVLZY.js → diagram-QEK2KX5R-D_LLCPas.js} +1 -1
  24. package/dist/dashboard/client/assets/{diagram-S2PKOQOG-kmITzl42.js → diagram-S2PKOQOG-Duy1t5UO.js} +1 -1
  25. package/dist/dashboard/client/assets/{erDiagram-Q2GNP2WA-Bvyepu_Z.js → erDiagram-Q2GNP2WA-DyQXwzLf.js} +1 -1
  26. package/dist/dashboard/client/assets/{flowDiagram-NV44I4VS-BokLAZN0.js → flowDiagram-NV44I4VS-D9U11XVw.js} +1 -1
  27. package/dist/dashboard/client/assets/{ganttDiagram-JELNMOA3-i5ZSGuTN.js → ganttDiagram-JELNMOA3-STy-TC-3.js} +1 -1
  28. package/dist/dashboard/client/assets/{gitGraphDiagram-V2S2FVAM-CIayQ8P9.js → gitGraphDiagram-V2S2FVAM-B04PgURg.js} +1 -1
  29. package/dist/dashboard/client/assets/{graph-C3ouLF2F.js → graph-AiGwnT5H.js} +1 -1
  30. package/dist/dashboard/client/assets/{index-icxlpW-l.js → index-BzQ3i_QR.js} +109 -107
  31. package/dist/dashboard/client/assets/index-CGGYXSm-.css +1 -0
  32. package/dist/dashboard/client/assets/{infoDiagram-HS3SLOUP-wxe8NO00.js → infoDiagram-HS3SLOUP-D4arwl6T.js} +1 -1
  33. package/dist/dashboard/client/assets/{journeyDiagram-XKPGCS4Q-BeHCbOFN.js → journeyDiagram-XKPGCS4Q-CsKqlKkf.js} +1 -1
  34. package/dist/dashboard/client/assets/{kanban-definition-3W4ZIXB7-DxUlb4wo.js → kanban-definition-3W4ZIXB7-CUFnzQE3.js} +1 -1
  35. package/dist/dashboard/client/assets/{layout-CYsQ5kjv.js → layout-BvvYJVPv.js} +1 -1
  36. package/dist/dashboard/client/assets/{linear-ByuMiLUn.js → linear-BiBJkzyE.js} +1 -1
  37. package/dist/dashboard/client/assets/{mermaid-renderer-cx-n1jFM.js → mermaid-renderer-DGUmIWXY.js} +4 -4
  38. package/dist/dashboard/client/assets/{mindmap-definition-VGOIOE7T-CI5zvW3G.js → mindmap-definition-VGOIOE7T-D-Kc9Xgu.js} +1 -1
  39. package/dist/dashboard/client/assets/{pieDiagram-ADFJNKIX-lC7QV-4L.js → pieDiagram-ADFJNKIX-CooPKLnX.js} +1 -1
  40. package/dist/dashboard/client/assets/{quadrantDiagram-AYHSOK5B-DI7Bn_fF.js → quadrantDiagram-AYHSOK5B-3soPtaSQ.js} +1 -1
  41. package/dist/dashboard/client/assets/{requirementDiagram-UZGBJVZJ-BVuFGUp6.js → requirementDiagram-UZGBJVZJ-rE40t0IG.js} +1 -1
  42. package/dist/dashboard/client/assets/{sankeyDiagram-TZEHDZUN-C-3hBPRk.js → sankeyDiagram-TZEHDZUN-CrgDF_jW.js} +1 -1
  43. package/dist/dashboard/client/assets/{sequenceDiagram-WL72ISMW-CLS6xCbv.js → sequenceDiagram-WL72ISMW-B628IlDW.js} +1 -1
  44. package/dist/dashboard/client/assets/{stateDiagram-FKZM4ZOC-XOLrkoEE.js → stateDiagram-FKZM4ZOC-C4yb7S9D.js} +1 -1
  45. package/dist/dashboard/client/assets/stateDiagram-v2-4FDKWEC3-BoFeOfLI.js +1 -0
  46. package/dist/dashboard/client/assets/{timeline-definition-IT6M3QCI-N9m6IkH5.js → timeline-definition-IT6M3QCI-5uLN4f_J.js} +1 -1
  47. package/dist/dashboard/client/assets/{treemap-GDKQZRPO-ayvdfxB1.js → treemap-GDKQZRPO-BHXME3bw.js} +1 -1
  48. package/dist/dashboard/client/assets/{xychartDiagram-PRI3JC2R-CUmVEVIH.js → xychartDiagram-PRI3JC2R-BYTod6eI.js} +1 -1
  49. package/dist/dashboard/client/index.html +2 -2
  50. package/dist/dashboard/server.js +532 -58
  51. package/dist/index.js +523 -15
  52. package/dist/lib/db/index.js +522 -0
  53. package/package.json +2 -2
  54. package/dist/dashboard/client/assets/channel-OmrThJE3.js +0 -1
  55. package/dist/dashboard/client/assets/classDiagram-2ON5EDUG-Dg5ffKNR.js +0 -1
  56. package/dist/dashboard/client/assets/classDiagram-v2-WZHVMYZB-Dg5ffKNR.js +0 -1
  57. package/dist/dashboard/client/assets/clone-CKI4Qu1i.js +0 -1
  58. package/dist/dashboard/client/assets/index-CPEavIIM.css +0 -1
  59. package/dist/dashboard/client/assets/stateDiagram-v2-4FDKWEC3-Cy33HZ1p.js +0 -1
package/dist/index.js CHANGED
@@ -2157,7 +2157,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
2157
2157
  *
2158
2158
  * @private
2159
2159
  */
2160
- _executeSubCommand(subcommand, args) {
2160
+ _executeSubCommand(subcommand2, args) {
2161
2161
  args = args.slice();
2162
2162
  let launchWithNode = false;
2163
2163
  const sourceExt = [".js", ".ts", ".tsx", ".mjs", ".cjs"];
@@ -2173,7 +2173,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
2173
2173
  }
2174
2174
  this._checkForMissingMandatoryOptions();
2175
2175
  this._checkForConflictingOptions();
2176
- let executableFile = subcommand._executableFile || `${this._name}-${subcommand._name}`;
2176
+ let executableFile = subcommand2._executableFile || `${this._name}-${subcommand2._name}`;
2177
2177
  let executableDir = this._executableDir || "";
2178
2178
  if (this._scriptPath) {
2179
2179
  let resolvedScriptPath;
@@ -2189,7 +2189,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
2189
2189
  }
2190
2190
  if (executableDir) {
2191
2191
  let localFile = findFile(executableDir, executableFile);
2192
- if (!localFile && !subcommand._executableFile && this._scriptPath) {
2192
+ if (!localFile && !subcommand2._executableFile && this._scriptPath) {
2193
2193
  const legacyName = path2.basename(
2194
2194
  this._scriptPath,
2195
2195
  path2.extname(this._scriptPath)
@@ -2197,7 +2197,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
2197
2197
  if (legacyName !== this._name) {
2198
2198
  localFile = findFile(
2199
2199
  executableDir,
2200
- `${legacyName}-${subcommand._name}`
2200
+ `${legacyName}-${subcommand2._name}`
2201
2201
  );
2202
2202
  }
2203
2203
  }
@@ -2217,7 +2217,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
2217
2217
  this._checkForMissingExecutable(
2218
2218
  executableFile,
2219
2219
  executableDir,
2220
- subcommand._name
2220
+ subcommand2._name
2221
2221
  );
2222
2222
  args.unshift(executableFile);
2223
2223
  args = incrementNodeInspectorPort(process13.execArgv).concat(args);
@@ -2253,7 +2253,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
2253
2253
  this._checkForMissingExecutable(
2254
2254
  executableFile,
2255
2255
  executableDir,
2256
- subcommand._name
2256
+ subcommand2._name
2257
2257
  );
2258
2258
  } else if (err.code === "EACCES") {
2259
2259
  throw new Error(`'${executableFile}' not executable`);
@@ -16283,6 +16283,30 @@ var init_migrations = __esm({
16283
16283
  ALTER TABLE orchestration_events_new RENAME TO orchestration_events;
16284
16284
  CREATE INDEX idx_events_session ON orchestration_events(session_id);
16285
16285
  CREATE INDEX idx_events_type ON orchestration_events(event_type);
16286
+ `
16287
+ },
16288
+ {
16289
+ version: 6,
16290
+ description: "Add orchestrator-first columns to review_rounds for round-meta.json support",
16291
+ sql: `
16292
+ ALTER TABLE review_rounds ADD COLUMN source TEXT DEFAULT NULL;
16293
+ ALTER TABLE review_rounds ADD COLUMN reviewer_count INTEGER DEFAULT 0;
16294
+ ALTER TABLE review_rounds ADD COLUMN total_finding_count INTEGER DEFAULT 0;
16295
+ `
16296
+ },
16297
+ {
16298
+ version: 7,
16299
+ description: "Add category column to review_findings for blocker/should_fix/suggestion classification",
16300
+ sql: `
16301
+ ALTER TABLE review_findings ADD COLUMN category TEXT DEFAULT NULL;
16302
+ `
16303
+ },
16304
+ {
16305
+ version: 8,
16306
+ description: "Add orchestrator-first columns to map_runs for map-meta.json support",
16307
+ sql: `
16308
+ ALTER TABLE map_runs ADD COLUMN source TEXT DEFAULT NULL;
16309
+ ALTER TABLE map_runs ADD COLUMN section_count INTEGER DEFAULT 0;
16286
16310
  `
16287
16311
  }
16288
16312
  ];
@@ -20187,21 +20211,30 @@ import { join } from "node:path";
20187
20211
  var START_MARKER = "# OCR:START \u2014 managed by open-code-review (do not edit this block)";
20188
20212
  var END_MARKER = "# OCR:END";
20189
20213
  var MANAGED_ENTRIES = ["sessions/", "data/", "*.db-shm", "*.db-wal"];
20214
+ var LEGACY_LINES = /* @__PURE__ */ new Set([
20215
+ "# OCR session files",
20216
+ "sessions/",
20217
+ "data",
20218
+ "data/"
20219
+ ]);
20190
20220
  function buildManagedBlock() {
20191
20221
  return [START_MARKER, ...MANAGED_ENTRIES, END_MARKER].join("\n");
20192
20222
  }
20193
20223
  function escapeRegex(str) {
20194
20224
  return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
20195
20225
  }
20226
+ function stripLegacyLines(content) {
20227
+ return content.split("\n").filter((line) => !LEGACY_LINES.has(line.trim())).join("\n");
20228
+ }
20196
20229
  function ensureGitignore(ocrDir) {
20197
20230
  const gitignorePath = join(ocrDir, ".gitignore");
20198
20231
  const block = buildManagedBlock();
20199
20232
  let content = existsSync(gitignorePath) ? readFileSync2(gitignorePath, "utf-8") : "";
20200
- const regex2 = new RegExp(
20233
+ const blockRegex = new RegExp(
20201
20234
  `${escapeRegex(START_MARKER)}[\\s\\S]*?${escapeRegex(END_MARKER)}\\n?`,
20202
20235
  "g"
20203
20236
  );
20204
- if (regex2.test(content)) {
20237
+ if (blockRegex.test(content)) {
20205
20238
  content = content.replace(
20206
20239
  new RegExp(
20207
20240
  `${escapeRegex(START_MARKER)}[\\s\\S]*?${escapeRegex(END_MARKER)}\\n?`,
@@ -20210,7 +20243,7 @@ function ensureGitignore(ocrDir) {
20210
20243
  block + "\n"
20211
20244
  );
20212
20245
  } else {
20213
- content = content.trimEnd();
20246
+ content = stripLegacyLines(content).trimEnd();
20214
20247
  if (content.length > 0) {
20215
20248
  content += "\n\n";
20216
20249
  }
@@ -24507,12 +24540,19 @@ function renderCombinedProgress(sessionPath, preservedStartTimes, ocrDir) {
24507
24540
  }
24508
24541
 
24509
24542
  // src/commands/state.ts
24510
- import { existsSync as existsSync12, mkdirSync as mkdirSync4 } from "node:fs";
24543
+ import { existsSync as existsSync12, mkdirSync as mkdirSync5 } from "node:fs";
24511
24544
  import { join as join14 } from "node:path";
24512
24545
 
24513
24546
  // src/lib/state/index.ts
24514
24547
  init_db();
24515
- import { existsSync as existsSync11, readdirSync as readdirSync6, statSync as statSync2 } from "node:fs";
24548
+ import {
24549
+ existsSync as existsSync11,
24550
+ mkdirSync as mkdirSync4,
24551
+ readdirSync as readdirSync6,
24552
+ readFileSync as readFileSync9,
24553
+ statSync as statSync2,
24554
+ writeFileSync as writeFileSync7
24555
+ } from "node:fs";
24516
24556
  import { join as join13 } from "node:path";
24517
24557
  async function stateInit(params) {
24518
24558
  const { sessionId, branch, workflowType, sessionDir, ocrDir } = params;
@@ -24669,6 +24709,253 @@ async function resolveActiveSession(ocrDir) {
24669
24709
  sessionDir: session.session_dir
24670
24710
  };
24671
24711
  }
24712
+ function readJsonFromSource(params) {
24713
+ if (params.source === "file") {
24714
+ if (!existsSync11(params.filePath)) {
24715
+ throw new Error(`File not found: ${params.filePath}`);
24716
+ }
24717
+ return readFileSync9(params.filePath, "utf-8");
24718
+ }
24719
+ return params.data;
24720
+ }
24721
+ function parseRawJson(raw, label) {
24722
+ try {
24723
+ return JSON.parse(raw);
24724
+ } catch (err) {
24725
+ throw new Error(
24726
+ `Failed to parse ${label}: ${err instanceof Error ? err.message : "invalid JSON"}`
24727
+ );
24728
+ }
24729
+ }
24730
+ function resolveSessionForCompletion(db, explicitId) {
24731
+ if (explicitId) {
24732
+ const existing = getSession(db, explicitId);
24733
+ if (!existing) throw new Error(`Session not found: ${explicitId}`);
24734
+ return {
24735
+ id: existing.id,
24736
+ session_dir: existing.session_dir,
24737
+ current_round: existing.current_round,
24738
+ current_map_run: existing.current_map_run
24739
+ };
24740
+ }
24741
+ const active = getLatestActiveSession(db);
24742
+ if (!active) throw new Error("No active session found");
24743
+ return {
24744
+ id: active.id,
24745
+ session_dir: active.session_dir,
24746
+ current_round: active.current_round,
24747
+ current_map_run: active.current_map_run
24748
+ };
24749
+ }
24750
+ var VALID_CATEGORIES = /* @__PURE__ */ new Set(["blocker", "should_fix", "suggestion", "style"]);
24751
+ var VALID_SEVERITIES = /* @__PURE__ */ new Set(["critical", "high", "medium", "low", "info"]);
24752
+ function validateRoundMeta(meta) {
24753
+ if (!meta || typeof meta !== "object") {
24754
+ throw new Error("round-meta.json must be a JSON object");
24755
+ }
24756
+ const obj = meta;
24757
+ if (obj.schema_version !== 1) {
24758
+ throw new Error(
24759
+ `Unsupported schema_version: ${String(obj.schema_version)}. Expected 1.`
24760
+ );
24761
+ }
24762
+ if (typeof obj.verdict !== "string" || obj.verdict.trim().length === 0) {
24763
+ throw new Error("round-meta.json must contain a non-empty verdict string");
24764
+ }
24765
+ if (!Array.isArray(obj.reviewers)) {
24766
+ throw new Error("round-meta.json must contain a reviewers array");
24767
+ }
24768
+ for (const reviewer of obj.reviewers) {
24769
+ if (!reviewer || typeof reviewer !== "object") {
24770
+ throw new Error("Each reviewer must be an object");
24771
+ }
24772
+ const r = reviewer;
24773
+ if (typeof r.type !== "string") {
24774
+ throw new Error("Each reviewer must have a type string");
24775
+ }
24776
+ if (typeof r.instance !== "number") {
24777
+ throw new Error("Each reviewer must have an instance number");
24778
+ }
24779
+ if (!Array.isArray(r.findings)) {
24780
+ throw new Error(`Reviewer ${r.type}-${r.instance} must have a findings array`);
24781
+ }
24782
+ for (const finding of r.findings) {
24783
+ if (!finding || typeof finding !== "object") {
24784
+ throw new Error("Each finding must be an object");
24785
+ }
24786
+ const f = finding;
24787
+ if (typeof f.title !== "string" || f.title.trim().length === 0) {
24788
+ throw new Error("Each finding must have a non-empty title");
24789
+ }
24790
+ if (typeof f.category !== "string" || !VALID_CATEGORIES.has(f.category)) {
24791
+ throw new Error(
24792
+ `Finding "${f.title}" has invalid category: "${String(f.category)}". Must be one of: ${[...VALID_CATEGORIES].join(", ")}`
24793
+ );
24794
+ }
24795
+ if (typeof f.severity !== "string" || !VALID_SEVERITIES.has(f.severity)) {
24796
+ throw new Error(
24797
+ `Finding "${f.title}" has invalid severity: "${String(f.severity)}". Must be one of: ${[...VALID_SEVERITIES].join(", ")}`
24798
+ );
24799
+ }
24800
+ if (typeof f.summary !== "string") {
24801
+ throw new Error(`Finding "${f.title}" must have a summary string`);
24802
+ }
24803
+ if (f.file_path !== void 0 && typeof f.file_path !== "string") {
24804
+ throw new Error(`Finding "${f.title}" has invalid file_path: expected string`);
24805
+ }
24806
+ if (f.line_start !== void 0 && typeof f.line_start !== "number") {
24807
+ throw new Error(`Finding "${f.title}" has invalid line_start: expected number`);
24808
+ }
24809
+ if (f.line_end !== void 0 && typeof f.line_end !== "number") {
24810
+ throw new Error(`Finding "${f.title}" has invalid line_end: expected number`);
24811
+ }
24812
+ if (f.flagged_by !== void 0 && !Array.isArray(f.flagged_by)) {
24813
+ throw new Error(`Finding "${f.title}" has invalid flagged_by: expected array`);
24814
+ }
24815
+ }
24816
+ }
24817
+ return meta;
24818
+ }
24819
+ function computeRoundCounts(meta) {
24820
+ const allFindings = [];
24821
+ for (const reviewer of meta.reviewers) {
24822
+ allFindings.push(...reviewer.findings);
24823
+ }
24824
+ return {
24825
+ blockerCount: allFindings.filter((f) => f.category === "blocker").length,
24826
+ shouldFixCount: allFindings.filter((f) => f.category === "should_fix").length,
24827
+ suggestionCount: allFindings.filter((f) => f.category === "suggestion").length,
24828
+ reviewerCount: meta.reviewers.length,
24829
+ totalFindingCount: allFindings.length
24830
+ };
24831
+ }
24832
+ async function stateRoundComplete(params) {
24833
+ const { ocrDir } = params;
24834
+ const db = await ensureDatabase(ocrDir);
24835
+ const dbPath = join13(ocrDir, "data", "ocr.db");
24836
+ const rawJsonString = readJsonFromSource(params);
24837
+ const label = params.source === "file" ? params.filePath : "stdin";
24838
+ const raw = parseRawJson(rawJsonString, label);
24839
+ const meta = validateRoundMeta(raw);
24840
+ const counts = computeRoundCounts(meta);
24841
+ const session = resolveSessionForCompletion(db, params.sessionId);
24842
+ const roundNumber = params.round ?? session.current_round;
24843
+ let metaPath;
24844
+ if (params.source === "stdin") {
24845
+ const roundDir = join13(session.session_dir, "rounds", `round-${roundNumber}`);
24846
+ mkdirSync4(roundDir, { recursive: true });
24847
+ metaPath = join13(roundDir, "round-meta.json");
24848
+ writeFileSync7(metaPath, JSON.stringify(meta, null, 2));
24849
+ }
24850
+ insertEvent(db, {
24851
+ session_id: session.id,
24852
+ event_type: "round_completed",
24853
+ phase: "synthesis",
24854
+ phase_number: 7,
24855
+ round: roundNumber,
24856
+ metadata: JSON.stringify({
24857
+ verdict: meta.verdict,
24858
+ blocker_count: counts.blockerCount,
24859
+ should_fix_count: counts.shouldFixCount,
24860
+ suggestion_count: counts.suggestionCount,
24861
+ reviewer_count: counts.reviewerCount,
24862
+ total_finding_count: counts.totalFindingCount,
24863
+ source: "orchestrator"
24864
+ })
24865
+ });
24866
+ saveDatabase(db, dbPath);
24867
+ return { sessionId: session.id, round: roundNumber, metaPath };
24868
+ }
24869
+ function validateMapMeta(meta) {
24870
+ if (!meta || typeof meta !== "object") {
24871
+ throw new Error("map-meta.json must be a JSON object");
24872
+ }
24873
+ const obj = meta;
24874
+ if (obj.schema_version !== 1) {
24875
+ throw new Error(
24876
+ `Unsupported schema_version: ${String(obj.schema_version)}. Expected 1.`
24877
+ );
24878
+ }
24879
+ if (!Array.isArray(obj.sections)) {
24880
+ throw new Error("map-meta.json must contain a sections array");
24881
+ }
24882
+ for (const section of obj.sections) {
24883
+ if (!section || typeof section !== "object") {
24884
+ throw new Error("Each section must be an object");
24885
+ }
24886
+ const s = section;
24887
+ if (typeof s.section_number !== "number") {
24888
+ throw new Error("Each section must have a section_number");
24889
+ }
24890
+ if (typeof s.title !== "string" || s.title.trim().length === 0) {
24891
+ throw new Error("Each section must have a non-empty title");
24892
+ }
24893
+ if (!Array.isArray(s.files)) {
24894
+ throw new Error(`Section "${s.title}" must have a files array`);
24895
+ }
24896
+ for (const file of s.files) {
24897
+ if (!file || typeof file !== "object") {
24898
+ throw new Error("Each file must be an object");
24899
+ }
24900
+ const f = file;
24901
+ if (typeof f.file_path !== "string" || f.file_path.trim().length === 0) {
24902
+ throw new Error("Each file must have a non-empty file_path");
24903
+ }
24904
+ if (typeof f.role !== "string") {
24905
+ throw new Error(`File "${f.file_path}" must have a role string`);
24906
+ }
24907
+ if (typeof f.lines_added !== "number") {
24908
+ throw new Error(`File "${f.file_path}" must have a lines_added number`);
24909
+ }
24910
+ if (typeof f.lines_deleted !== "number") {
24911
+ throw new Error(`File "${f.file_path}" must have a lines_deleted number`);
24912
+ }
24913
+ }
24914
+ }
24915
+ if (obj.dependencies !== void 0 && !Array.isArray(obj.dependencies)) {
24916
+ throw new Error("map-meta.json dependencies must be an array if provided");
24917
+ }
24918
+ return meta;
24919
+ }
24920
+ function computeMapCounts(meta) {
24921
+ return {
24922
+ sectionCount: meta.sections.length,
24923
+ fileCount: meta.sections.reduce((sum, s) => sum + s.files.length, 0)
24924
+ };
24925
+ }
24926
+ async function stateMapComplete(params) {
24927
+ const { ocrDir } = params;
24928
+ const db = await ensureDatabase(ocrDir);
24929
+ const dbPath = join13(ocrDir, "data", "ocr.db");
24930
+ const rawJsonString = readJsonFromSource(params);
24931
+ const label = params.source === "file" ? params.filePath : "stdin";
24932
+ const raw = parseRawJson(rawJsonString, label);
24933
+ const meta = validateMapMeta(raw);
24934
+ const counts = computeMapCounts(meta);
24935
+ const session = resolveSessionForCompletion(db, params.sessionId);
24936
+ const mapRunNumber = params.mapRun ?? session.current_map_run;
24937
+ let metaPath;
24938
+ if (params.source === "stdin") {
24939
+ const runDir = join13(session.session_dir, "map", "runs", `run-${mapRunNumber}`);
24940
+ mkdirSync4(runDir, { recursive: true });
24941
+ metaPath = join13(runDir, "map-meta.json");
24942
+ writeFileSync7(metaPath, JSON.stringify(meta, null, 2));
24943
+ }
24944
+ insertEvent(db, {
24945
+ session_id: session.id,
24946
+ event_type: "map_completed",
24947
+ phase: "synthesis",
24948
+ phase_number: 5,
24949
+ round: mapRunNumber,
24950
+ metadata: JSON.stringify({
24951
+ section_count: counts.sectionCount,
24952
+ file_count: counts.fileCount,
24953
+ source: "orchestrator"
24954
+ })
24955
+ });
24956
+ saveDatabase(db, dbPath);
24957
+ return { sessionId: session.id, mapRun: mapRunNumber, metaPath };
24958
+ }
24672
24959
  async function stateSync(ocrDir) {
24673
24960
  const db = await ensureDatabase(ocrDir);
24674
24961
  const dbPath = join13(ocrDir, "data", "ocr.db");
@@ -24718,6 +25005,17 @@ async function stateSync(ocrDir) {
24718
25005
  }
24719
25006
 
24720
25007
  // src/commands/state.ts
25008
+ async function readStdin() {
25009
+ const chunks = [];
25010
+ for await (const chunk of process.stdin) {
25011
+ chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
25012
+ }
25013
+ const data = Buffer.concat(chunks).toString("utf-8").trim();
25014
+ if (data.length === 0) {
25015
+ throw new Error("No data received on stdin");
25016
+ }
25017
+ return data;
25018
+ }
24721
25019
  var initSubcommand = new Command("init").description("Initialize a new OCR session").requiredOption("--session-id <id>", "Session ID").requiredOption("--branch <branch>", "Branch name").requiredOption(
24722
25020
  "--workflow-type <type>",
24723
25021
  "Workflow type (review or map)",
@@ -24736,7 +25034,7 @@ var initSubcommand = new Command("init").description("Initialize a new OCR sessi
24736
25034
  const ocrDir = join14(targetDir, ".ocr");
24737
25035
  const sessionDir = options.sessionDir ?? join14(ocrDir, "sessions", options.sessionId);
24738
25036
  if (!existsSync12(sessionDir)) {
24739
- mkdirSync4(sessionDir, { recursive: true });
25037
+ mkdirSync5(sessionDir, { recursive: true });
24740
25038
  }
24741
25039
  try {
24742
25040
  const sessionId = await stateInit({
@@ -24762,6 +25060,23 @@ var transitionSubcommand = new Command("transition").description("Transition ses
24762
25060
  const targetDir = process.cwd();
24763
25061
  requireOcrSetup(targetDir);
24764
25062
  const ocrDir = join14(targetDir, ".ocr");
25063
+ const VALID_PHASES = /* @__PURE__ */ new Set([
25064
+ "context",
25065
+ "change-context",
25066
+ "analysis",
25067
+ "reviews",
25068
+ "aggregation",
25069
+ "discourse",
25070
+ "synthesis",
25071
+ "complete",
25072
+ "map-context",
25073
+ "topology",
25074
+ "flow-analysis",
25075
+ "requirements-mapping"
25076
+ ]);
25077
+ if (!VALID_PHASES.has(options.phase)) {
25078
+ throw new Error(`Invalid phase: "${options.phase}". Must be one of: ${[...VALID_PHASES].join(", ")}`);
25079
+ }
24765
25080
  try {
24766
25081
  const sessionId = options.sessionId ?? (await resolveActiveSession(ocrDir)).id;
24767
25082
  await stateTransition({
@@ -24890,7 +25205,105 @@ var syncSubcommand = new Command("sync").description("Rebuild session state from
24890
25205
  process.exit(1);
24891
25206
  }
24892
25207
  });
24893
- var stateCommand = new Command("state").description("Manage OCR session state").addCommand(initSubcommand).addCommand(transitionSubcommand).addCommand(closeSubcommand).addCommand(showSubcommand).addCommand(syncSubcommand);
25208
+ var roundCompleteSubcommand = new Command("round-complete").description("Import structured round data into SQLite").option("--file <path>", "Path to round-meta.json").option("--stdin", "Read round-meta JSON from stdin (recommended)").option("--session-id <id>", "Session ID (auto-detects latest active if omitted)").option("--round <number>", "Round number (auto-detects current if omitted)", parseInt).action(
25209
+ async (options) => {
25210
+ const targetDir = process.cwd();
25211
+ requireOcrSetup(targetDir);
25212
+ const ocrDir = join14(targetDir, ".ocr");
25213
+ if (!options.file && !options.stdin) {
25214
+ console.error(source_default.red("Error: Provide either --file <path> or --stdin"));
25215
+ process.exit(1);
25216
+ }
25217
+ if (options.file && options.stdin) {
25218
+ console.error(source_default.red("Error: --file and --stdin are mutually exclusive"));
25219
+ process.exit(1);
25220
+ }
25221
+ try {
25222
+ let result;
25223
+ if (options.stdin) {
25224
+ const data = await readStdin();
25225
+ result = await stateRoundComplete({
25226
+ source: "stdin",
25227
+ ocrDir,
25228
+ data,
25229
+ sessionId: options.sessionId,
25230
+ round: options.round
25231
+ });
25232
+ } else if (options.file) {
25233
+ result = await stateRoundComplete({
25234
+ source: "file",
25235
+ ocrDir,
25236
+ filePath: options.file,
25237
+ sessionId: options.sessionId,
25238
+ round: options.round
25239
+ });
25240
+ } else {
25241
+ process.exit(1);
25242
+ }
25243
+ console.log(source_default.green("Round data imported successfully."));
25244
+ if (result.metaPath) {
25245
+ console.log(source_default.dim(`Wrote ${result.metaPath}`));
25246
+ }
25247
+ } catch (error) {
25248
+ console.error(
25249
+ source_default.red(
25250
+ `Error: ${error instanceof Error ? error.message : "Failed to import round data"}`
25251
+ )
25252
+ );
25253
+ process.exit(1);
25254
+ }
25255
+ }
25256
+ );
25257
+ var mapCompleteSubcommand = new Command("map-complete").description("Import structured map run data into SQLite").option("--file <path>", "Path to map-meta.json").option("--stdin", "Read map-meta JSON from stdin (recommended)").option("--session-id <id>", "Session ID (auto-detects latest active if omitted)").option("--map-run <number>", "Map run number (auto-detects current if omitted)", parseInt).action(
25258
+ async (options) => {
25259
+ const targetDir = process.cwd();
25260
+ requireOcrSetup(targetDir);
25261
+ const ocrDir = join14(targetDir, ".ocr");
25262
+ if (!options.file && !options.stdin) {
25263
+ console.error(source_default.red("Error: Provide either --file <path> or --stdin"));
25264
+ process.exit(1);
25265
+ }
25266
+ if (options.file && options.stdin) {
25267
+ console.error(source_default.red("Error: --file and --stdin are mutually exclusive"));
25268
+ process.exit(1);
25269
+ }
25270
+ try {
25271
+ let result;
25272
+ if (options.stdin) {
25273
+ const data = await readStdin();
25274
+ result = await stateMapComplete({
25275
+ source: "stdin",
25276
+ ocrDir,
25277
+ data,
25278
+ sessionId: options.sessionId,
25279
+ mapRun: options.mapRun
25280
+ });
25281
+ } else if (options.file) {
25282
+ result = await stateMapComplete({
25283
+ source: "file",
25284
+ ocrDir,
25285
+ filePath: options.file,
25286
+ sessionId: options.sessionId,
25287
+ mapRun: options.mapRun
25288
+ });
25289
+ } else {
25290
+ process.exit(1);
25291
+ }
25292
+ console.log(source_default.green("Map data imported successfully."));
25293
+ if (result.metaPath) {
25294
+ console.log(source_default.dim(`Wrote ${result.metaPath}`));
25295
+ }
25296
+ } catch (error) {
25297
+ console.error(
25298
+ source_default.red(
25299
+ `Error: ${error instanceof Error ? error.message : "Failed to import map data"}`
25300
+ )
25301
+ );
25302
+ process.exit(1);
25303
+ }
25304
+ }
25305
+ );
25306
+ var stateCommand = new Command("state").description("Manage OCR session state").addCommand(initSubcommand).addCommand(transitionSubcommand).addCommand(closeSubcommand).addCommand(showSubcommand).addCommand(syncSubcommand).addCommand(roundCompleteSubcommand).addCommand(mapCompleteSubcommand);
24894
25307
 
24895
25308
  // src/commands/update.ts
24896
25309
  import { existsSync as existsSync13 } from "node:fs";
@@ -25203,8 +25616,94 @@ var doctorCommand = new Command("doctor").description("Check OCR installation an
25203
25616
  console.log();
25204
25617
  });
25205
25618
 
25619
+ // src/lib/update-check.ts
25620
+ import { homedir } from "node:os";
25621
+ import { join as join18 } from "node:path";
25622
+ import { readFileSync as readFileSync10, writeFileSync as writeFileSync8, mkdirSync as mkdirSync6 } from "node:fs";
25623
+ var PACKAGE_NAME = "@open-code-review/cli";
25624
+ var REGISTRY_URL = `https://registry.npmjs.org/${PACKAGE_NAME}/latest`;
25625
+ var CACHE_DIR = join18(homedir(), ".ocr");
25626
+ var CACHE_FILE = join18(CACHE_DIR, "update-check.json");
25627
+ var CHECK_INTERVAL_MS = 4 * 60 * 60 * 1e3;
25628
+ var FETCH_TIMEOUT_MS = 3e3;
25629
+ function readCache(cacheFile) {
25630
+ try {
25631
+ return JSON.parse(readFileSync10(cacheFile, "utf-8"));
25632
+ } catch {
25633
+ return null;
25634
+ }
25635
+ }
25636
+ function writeCache(cacheFile, cache) {
25637
+ try {
25638
+ mkdirSync6(join18(cacheFile, ".."), { recursive: true });
25639
+ writeFileSync8(cacheFile, JSON.stringify(cache));
25640
+ } catch {
25641
+ }
25642
+ }
25643
+ function isNewer(latest, current) {
25644
+ const l = latest.split(".").map(Number);
25645
+ const c = current.split(".").map(Number);
25646
+ for (let i = 0; i < 3; i++) {
25647
+ if ((l[i] ?? 0) > (c[i] ?? 0)) return true;
25648
+ if ((l[i] ?? 0) < (c[i] ?? 0)) return false;
25649
+ }
25650
+ return false;
25651
+ }
25652
+ function detectUpdateCommand() {
25653
+ return `npm i -g ${PACKAGE_NAME}@latest && ocr update`;
25654
+ }
25655
+ async function checkForUpdate(currentVersion, options) {
25656
+ if (process.env.CI || process.env.OCR_NO_UPDATE_CHECK) {
25657
+ return null;
25658
+ }
25659
+ const cacheFile = join18(options?.cacheDir ?? CACHE_DIR, "update-check.json");
25660
+ try {
25661
+ const cache = readCache(cacheFile);
25662
+ if (cache && Date.now() - cache.lastCheck < CHECK_INTERVAL_MS) {
25663
+ if (!cache.latestVersion) return null;
25664
+ if (!isNewer(cache.latestVersion, currentVersion)) return null;
25665
+ return {
25666
+ updateAvailable: true,
25667
+ currentVersion,
25668
+ latestVersion: cache.latestVersion,
25669
+ updateCommand: detectUpdateCommand()
25670
+ };
25671
+ }
25672
+ const response = await fetch(REGISTRY_URL, {
25673
+ signal: AbortSignal.timeout(FETCH_TIMEOUT_MS)
25674
+ });
25675
+ const data = await response.json();
25676
+ const latestVersion = data.version ?? null;
25677
+ writeCache(cacheFile, { lastCheck: Date.now(), latestVersion });
25678
+ if (!latestVersion || !isNewer(latestVersion, currentVersion)) {
25679
+ return null;
25680
+ }
25681
+ return {
25682
+ updateAvailable: true,
25683
+ currentVersion,
25684
+ latestVersion,
25685
+ updateCommand: detectUpdateCommand()
25686
+ };
25687
+ } catch {
25688
+ writeCache(cacheFile, { lastCheck: Date.now(), latestVersion: null });
25689
+ return null;
25690
+ }
25691
+ }
25692
+ function printUpdateNotification(result) {
25693
+ const line1 = source_default.yellow(" Update available: ") + source_default.dim(result.currentVersion) + source_default.yellow(" \u2192 ") + source_default.green(result.latestVersion);
25694
+ const line2 = source_default.dim(" Run: ") + source_default.bold(result.updateCommand);
25695
+ process.stderr.write(`
25696
+ ${line1}
25697
+ ${line2}
25698
+
25699
+ `);
25700
+ }
25701
+
25206
25702
  // src/index.ts
25207
- var cliVersion = true ? "1.5.0" : createRequire(import.meta.url)("../package.json").version;
25703
+ var cliVersion = true ? "1.6.0" : createRequire(import.meta.url)("../package.json").version;
25704
+ var HUMAN_COMMANDS = /* @__PURE__ */ new Set(["init", "update", "doctor", "dashboard", "progress"]);
25705
+ var subcommand = process.argv[2];
25706
+ var updateCheck = subcommand && HUMAN_COMMANDS.has(subcommand) ? checkForUpdate(cliVersion) : null;
25208
25707
  var program2 = new Command();
25209
25708
  program2.name("ocr").description("Open Code Review - AI-powered multi-agent code review").version(cliVersion);
25210
25709
  program2.addCommand(initCommand);
@@ -25213,7 +25712,16 @@ program2.addCommand(stateCommand);
25213
25712
  program2.addCommand(updateCommand);
25214
25713
  program2.addCommand(dashboardCommand);
25215
25714
  program2.addCommand(doctorCommand);
25216
- program2.parse();
25715
+ await program2.parseAsync();
25716
+ if (updateCheck) {
25717
+ const updateResult = await Promise.race([
25718
+ updateCheck,
25719
+ new Promise((r) => setTimeout(() => r(null), 500))
25720
+ ]);
25721
+ if (updateResult?.updateAvailable) {
25722
+ printUpdateNotification(updateResult);
25723
+ }
25724
+ }
25217
25725
  /*! Bundled license information:
25218
25726
 
25219
25727
  chokidar/esm/index.js: