@open-code-review/cli 1.9.0 → 1.10.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.
package/dist/index.js CHANGED
@@ -16307,6 +16307,14 @@ var init_migrations = __esm({
16307
16307
  sql: `
16308
16308
  ALTER TABLE map_runs ADD COLUMN source TEXT DEFAULT NULL;
16309
16309
  ALTER TABLE map_runs ADD COLUMN section_count INTEGER DEFAULT 0;
16310
+ `
16311
+ },
16312
+ {
16313
+ version: 9,
16314
+ description: "Add uid column to command_executions for JSONL-backed recovery",
16315
+ sql: `
16316
+ ALTER TABLE command_executions ADD COLUMN uid TEXT;
16317
+ CREATE UNIQUE INDEX idx_command_executions_uid ON command_executions(uid);
16310
16318
  `
16311
16319
  }
16312
16320
  ];
@@ -16428,14 +16436,121 @@ var init_queries = __esm({
16428
16436
  }
16429
16437
  });
16430
16438
 
16439
+ // src/lib/db/command-log.ts
16440
+ import { appendFileSync, existsSync as existsSync9, mkdirSync as mkdirSync3, readFileSync as readFileSync8, renameSync, writeFileSync as writeFileSync6 } from "node:fs";
16441
+ import { dirname as dirname4, join as join11 } from "node:path";
16442
+ import { randomUUID as randomUUID2 } from "node:crypto";
16443
+ function generateCommandUid() {
16444
+ return randomUUID2();
16445
+ }
16446
+ function cacheDir(ocrDir) {
16447
+ return join11(ocrDir, "data", CACHE_DIR);
16448
+ }
16449
+ function commandLogPath(ocrDir) {
16450
+ return join11(cacheDir(ocrDir), FILENAME);
16451
+ }
16452
+ function appendCommandLog(ocrDir, entry) {
16453
+ try {
16454
+ const filePath = commandLogPath(ocrDir);
16455
+ const dir = dirname4(filePath);
16456
+ if (!existsSync9(dir)) mkdirSync3(dir, { recursive: true });
16457
+ const line = JSON.stringify(entry) + "\n";
16458
+ appendFileSync(filePath, line, { encoding: "utf-8" });
16459
+ if (approxLineCount >= 0) approxLineCount++;
16460
+ rotateIfNeeded(filePath);
16461
+ } catch {
16462
+ }
16463
+ }
16464
+ function readCommandLog(ocrDir) {
16465
+ const filePath = commandLogPath(ocrDir);
16466
+ if (!existsSync9(filePath)) return [];
16467
+ const content = readFileSync8(filePath, "utf-8");
16468
+ const entries = [];
16469
+ for (const line of content.split("\n")) {
16470
+ if (!line.trim()) continue;
16471
+ try {
16472
+ entries.push(JSON.parse(line));
16473
+ } catch {
16474
+ }
16475
+ }
16476
+ return entries;
16477
+ }
16478
+ function replayCommandLog(db, ocrDir) {
16479
+ const entries = readCommandLog(ocrDir);
16480
+ if (entries.length === 0) return 0;
16481
+ const latest = /* @__PURE__ */ new Map();
16482
+ for (const entry of entries) {
16483
+ if (!entry.uid || !entry.command || !entry.started_at) continue;
16484
+ const existing = latest.get(entry.uid);
16485
+ if (!existing || entry.event !== "start") {
16486
+ latest.set(entry.uid, entry);
16487
+ }
16488
+ }
16489
+ let imported = 0;
16490
+ for (const entry of latest.values()) {
16491
+ if (entry.event === "start" && !entry.finished_at) continue;
16492
+ const existing = db.exec(
16493
+ "SELECT COUNT(*) as c FROM command_executions WHERE uid = ?",
16494
+ [entry.uid]
16495
+ );
16496
+ if ((existing[0]?.values[0]?.[0] ?? 0) > 0) continue;
16497
+ db.run(
16498
+ `INSERT INTO command_executions
16499
+ (uid, command, args, exit_code, started_at, finished_at, pid, is_detached)
16500
+ VALUES (?, ?, ?, ?, ?, ?, NULL, ?)`,
16501
+ [
16502
+ entry.uid,
16503
+ entry.command,
16504
+ entry.args,
16505
+ entry.exit_code,
16506
+ entry.started_at,
16507
+ entry.finished_at,
16508
+ entry.is_detached
16509
+ ]
16510
+ );
16511
+ imported++;
16512
+ }
16513
+ return imported;
16514
+ }
16515
+ function rotateIfNeeded(filePath) {
16516
+ try {
16517
+ if (approxLineCount >= 0 && approxLineCount <= MAX_LINES) return;
16518
+ const content = readFileSync8(filePath, "utf-8");
16519
+ const lines = content.split("\n").filter((l) => l.trim());
16520
+ approxLineCount = lines.length;
16521
+ if (approxLineCount <= MAX_LINES) return;
16522
+ const kept = lines.slice(lines.length - KEEP_LINES);
16523
+ const tmpPath = `${filePath}.${process.pid}.tmp`;
16524
+ writeFileSync6(tmpPath, kept.join("\n") + "\n", { encoding: "utf-8" });
16525
+ renameSync(tmpPath, filePath);
16526
+ approxLineCount = KEEP_LINES;
16527
+ } catch {
16528
+ }
16529
+ }
16530
+ var CACHE_DIR, FILENAME, MAX_LINES, KEEP_LINES, approxLineCount;
16531
+ var init_command_log = __esm({
16532
+ "src/lib/db/command-log.ts"() {
16533
+ "use strict";
16534
+ CACHE_DIR = ".cache";
16535
+ FILENAME = "command-history.jsonl";
16536
+ MAX_LINES = 5e3;
16537
+ KEEP_LINES = 4e3;
16538
+ approxLineCount = -1;
16539
+ }
16540
+ });
16541
+
16431
16542
  // src/lib/db/index.ts
16432
16543
  var db_exports = {};
16433
16544
  __export(db_exports, {
16434
16545
  MIGRATIONS: () => MIGRATIONS,
16546
+ appendCommandLog: () => appendCommandLog,
16435
16547
  applyPragmas: () => applyPragmas,
16548
+ cacheDir: () => cacheDir,
16436
16549
  closeAllDatabases: () => closeAllDatabases,
16437
16550
  closeDatabase: () => closeDatabase,
16551
+ commandLogPath: () => commandLogPath,
16438
16552
  ensureDatabase: () => ensureDatabase,
16553
+ generateCommandUid: () => generateCommandUid,
16439
16554
  getAllSessions: () => getAllSessions,
16440
16555
  getDb: () => getDb,
16441
16556
  getEventsForSession: () => getEventsForSession,
@@ -16446,20 +16561,22 @@ __export(db_exports, {
16446
16561
  insertSession: () => insertSession,
16447
16562
  locateWasm: () => locateWasm,
16448
16563
  openDatabase: () => openDatabase,
16564
+ readCommandLog: () => readCommandLog,
16565
+ replayCommandLog: () => replayCommandLog,
16449
16566
  resultToRow: () => resultToRow,
16450
16567
  resultToRows: () => resultToRows,
16451
16568
  runMigrations: () => runMigrations,
16452
16569
  saveDatabase: () => saveDatabase,
16453
16570
  updateSession: () => updateSession
16454
16571
  });
16455
- import { existsSync as existsSync9, mkdirSync as mkdirSync3, readFileSync as readFileSync8, renameSync, writeFileSync as writeFileSync6 } from "node:fs";
16456
- import { dirname as dirname4, join as join11 } from "node:path";
16572
+ import { existsSync as existsSync10, mkdirSync as mkdirSync4, readFileSync as readFileSync9, renameSync as renameSync2, writeFileSync as writeFileSync7 } from "node:fs";
16573
+ import { dirname as dirname5, join as join12 } from "node:path";
16457
16574
  import { createRequire as createRequire2 } from "node:module";
16458
16575
  import initSqlJs from "sql.js";
16459
16576
  function locateWasm() {
16460
16577
  const require3 = createRequire2(import.meta.url);
16461
16578
  const sqlJsPath = require3.resolve("sql.js");
16462
- return join11(dirname4(sqlJsPath), "sql-wasm.wasm");
16579
+ return join12(dirname5(sqlJsPath), "sql-wasm.wasm");
16463
16580
  }
16464
16581
  function applyPragmas(db) {
16465
16582
  db.run("PRAGMA foreign_keys = ON;");
@@ -16471,7 +16588,7 @@ async function openDatabase(dbPath) {
16471
16588
  if (cached) {
16472
16589
  return cached;
16473
16590
  }
16474
- const wasmBuffer = readFileSync8(locateWasm());
16591
+ const wasmBuffer = readFileSync9(locateWasm());
16475
16592
  const wasmBinary = wasmBuffer.buffer.slice(
16476
16593
  wasmBuffer.byteOffset,
16477
16594
  wasmBuffer.byteOffset + wasmBuffer.byteLength
@@ -16480,8 +16597,8 @@ async function openDatabase(dbPath) {
16480
16597
  wasmBinary
16481
16598
  });
16482
16599
  let db;
16483
- if (existsSync9(dbPath)) {
16484
- const fileBuffer = readFileSync8(dbPath);
16600
+ if (existsSync10(dbPath)) {
16601
+ const fileBuffer = readFileSync9(dbPath);
16485
16602
  db = new SQL.Database(fileBuffer);
16486
16603
  } else {
16487
16604
  db = new SQL.Database();
@@ -16492,24 +16609,24 @@ async function openDatabase(dbPath) {
16492
16609
  }
16493
16610
  function saveDatabase(db, dbPath) {
16494
16611
  const data = db.export();
16495
- const dir = dirname4(dbPath);
16496
- if (!existsSync9(dir)) {
16497
- mkdirSync3(dir, { recursive: true });
16612
+ const dir = dirname5(dbPath);
16613
+ if (!existsSync10(dir)) {
16614
+ mkdirSync4(dir, { recursive: true });
16498
16615
  }
16499
- const tmpPath = dbPath + ".tmp";
16500
- writeFileSync6(tmpPath, Buffer.from(data));
16501
- renameSync(tmpPath, dbPath);
16616
+ const tmpPath = `${dbPath}.${process.pid}.tmp`;
16617
+ writeFileSync7(tmpPath, Buffer.from(data));
16618
+ renameSync2(tmpPath, dbPath);
16502
16619
  }
16503
16620
  async function getDb(ocrDir) {
16504
- const dbPath = join11(ocrDir, "data", "ocr.db");
16621
+ const dbPath = join12(ocrDir, "data", "ocr.db");
16505
16622
  return openDatabase(dbPath);
16506
16623
  }
16507
16624
  async function ensureDatabase(ocrDir) {
16508
- const dataDir = join11(ocrDir, "data");
16509
- if (!existsSync9(dataDir)) {
16510
- mkdirSync3(dataDir, { recursive: true });
16625
+ const dataDir = join12(ocrDir, "data");
16626
+ if (!existsSync10(dataDir)) {
16627
+ mkdirSync4(dataDir, { recursive: true });
16511
16628
  }
16512
- const dbPath = join11(dataDir, "ocr.db");
16629
+ const dbPath = join12(dataDir, "ocr.db");
16513
16630
  const db = await openDatabase(dbPath);
16514
16631
  runMigrations(db);
16515
16632
  saveDatabase(db, dbPath);
@@ -16536,6 +16653,7 @@ var init_db = __esm({
16536
16653
  init_queries();
16537
16654
  init_migrations();
16538
16655
  init_result_mapper();
16656
+ init_command_log();
16539
16657
  connections = /* @__PURE__ */ new Map();
16540
16658
  }
16541
16659
  });
@@ -20776,7 +20894,7 @@ ${hint}
20776
20894
  }
20777
20895
 
20778
20896
  // src/lib/version.ts
20779
- var CLI_VERSION = true ? "1.9.0" : createRequire(import.meta.url)("../../package.json").version;
20897
+ var CLI_VERSION = true ? "1.10.0" : createRequire(import.meta.url)("../../package.json").version;
20780
20898
 
20781
20899
  // src/lib/deps.ts
20782
20900
  import { execFileSync } from "node:child_process";
@@ -21816,9 +21934,9 @@ var NodeFsHandler = class {
21816
21934
  if (this.fsw.closed) {
21817
21935
  return;
21818
21936
  }
21819
- const dirname6 = sysPath.dirname(file);
21937
+ const dirname7 = sysPath.dirname(file);
21820
21938
  const basename8 = sysPath.basename(file);
21821
- const parent = this.fsw._getWatchedDir(dirname6);
21939
+ const parent = this.fsw._getWatchedDir(dirname7);
21822
21940
  let prevStats = stats;
21823
21941
  if (parent.has(basename8))
21824
21942
  return;
@@ -21845,7 +21963,7 @@ var NodeFsHandler = class {
21845
21963
  prevStats = newStats2;
21846
21964
  }
21847
21965
  } catch (error) {
21848
- this.fsw._remove(dirname6, basename8);
21966
+ this.fsw._remove(dirname7, basename8);
21849
21967
  }
21850
21968
  } else if (parent.has(basename8)) {
21851
21969
  const at = newStats.atimeMs;
@@ -22778,8 +22896,8 @@ function watch(paths, options = {}) {
22778
22896
  }
22779
22897
 
22780
22898
  // src/commands/progress.ts
22781
- import { existsSync as existsSync10, readdirSync as readdirSync5, statSync } from "node:fs";
22782
- import { join as join12, basename as basename7 } from "node:path";
22899
+ import { existsSync as existsSync11, readdirSync as readdirSync5, statSync } from "node:fs";
22900
+ import { join as join13, basename as basename7 } from "node:path";
22783
22901
 
22784
22902
  // ../../node_modules/.pnpm/log-update@7.0.2/node_modules/log-update/index.js
22785
22903
  import process12 from "node:process";
@@ -24415,15 +24533,15 @@ function debounce(fn, delay) {
24415
24533
  };
24416
24534
  }
24417
24535
  function findLatestActiveSession(sessionsDir) {
24418
- if (!existsSync10(sessionsDir)) {
24536
+ if (!existsSync11(sessionsDir)) {
24419
24537
  return null;
24420
24538
  }
24421
24539
  const sessions = readdirSync5(sessionsDir).filter((name) => {
24422
- const sessionPath = join12(sessionsDir, name);
24540
+ const sessionPath = join13(sessionsDir, name);
24423
24541
  return statSync(sessionPath).isDirectory();
24424
24542
  }).sort().reverse();
24425
24543
  for (const session of sessions) {
24426
- const sessionPath = join12(sessionsDir, session);
24544
+ const sessionPath = join13(sessionsDir, session);
24427
24545
  if (isSessionActive(sessionPath)) {
24428
24546
  return session;
24429
24547
  }
@@ -24438,8 +24556,8 @@ function getStrategyForSession(sessionPath, explicitWorkflow) {
24438
24556
  return getStrategy(workflowType) ?? null;
24439
24557
  }
24440
24558
  async function initProgressDb(ocrDir) {
24441
- const dbPath = join12(ocrDir, "data", "ocr.db");
24442
- if (!existsSync10(dbPath)) {
24559
+ const dbPath = join13(ocrDir, "data", "ocr.db");
24560
+ if (!existsSync11(dbPath)) {
24443
24561
  return;
24444
24562
  }
24445
24563
  try {
@@ -24464,11 +24582,11 @@ var progressCommand = new Command("progress").description("Watch real-time progr
24464
24582
  const targetDir = process.cwd();
24465
24583
  requireOcrSetup(targetDir);
24466
24584
  const sessionsDir = ensureSessionsDir(targetDir);
24467
- const ocrDir = join12(targetDir, ".ocr");
24585
+ const ocrDir = join13(targetDir, ".ocr");
24468
24586
  await initProgressDb(ocrDir);
24469
24587
  if (options.session) {
24470
- const sessionPath = join12(sessionsDir, options.session);
24471
- if (!existsSync10(sessionPath)) {
24588
+ const sessionPath = join13(sessionsDir, options.session);
24589
+ if (!existsSync11(sessionPath)) {
24472
24590
  console.error(source_default.red(`Session not found: ${options.session}`));
24473
24591
  process.exit(1);
24474
24592
  }
@@ -24529,7 +24647,7 @@ var progressCommand = new Command("progress").description("Watch real-time progr
24529
24647
  return;
24530
24648
  }
24531
24649
  let currentSession = findLatestActiveSession(sessionsDir);
24532
- let currentSessionPath = currentSession ? join12(sessionsDir, currentSession) : null;
24650
+ let currentSessionPath = currentSession ? join13(sessionsDir, currentSession) : null;
24533
24651
  let sessionWatcher = null;
24534
24652
  const preservedStartTimes = {
24535
24653
  review: void 0,
@@ -24537,11 +24655,11 @@ var progressCommand = new Command("progress").description("Watch real-time progr
24537
24655
  };
24538
24656
  let currentStrategy = null;
24539
24657
  const updateDisplayImpl = () => {
24540
- if (!currentSessionPath || !existsSync10(currentSessionPath) || !isSessionActive(currentSessionPath)) {
24658
+ if (!currentSessionPath || !existsSync11(currentSessionPath) || !isSessionActive(currentSessionPath)) {
24541
24659
  const latestActive = findLatestActiveSession(sessionsDir);
24542
24660
  if (latestActive && latestActive !== currentSession) {
24543
24661
  currentSession = latestActive;
24544
- currentSessionPath = join12(sessionsDir, latestActive);
24662
+ currentSessionPath = join13(sessionsDir, latestActive);
24545
24663
  preservedStartTimes.review = void 0;
24546
24664
  preservedStartTimes.map = void 0;
24547
24665
  currentStrategy = null;
@@ -24554,7 +24672,7 @@ var progressCommand = new Command("progress").description("Watch real-time progr
24554
24672
  currentStrategy = null;
24555
24673
  }
24556
24674
  }
24557
- if (currentSessionPath && existsSync10(currentSessionPath)) {
24675
+ if (currentSessionPath && existsSync11(currentSessionPath)) {
24558
24676
  if (!options.workflow) {
24559
24677
  const activeWorkflows = detectActiveWorkflows(currentSessionPath);
24560
24678
  if (activeWorkflows.length > 1) {
@@ -24608,15 +24726,15 @@ var progressCommand = new Command("progress").description("Watch real-time progr
24608
24726
  watchSession(currentSessionPath);
24609
24727
  }
24610
24728
  const timerInterval = setInterval(updateDisplay, 1e3);
24611
- const watchDir = existsSync10(ocrDir) ? ocrDir : targetDir;
24729
+ const watchDir = existsSync11(ocrDir) ? ocrDir : targetDir;
24612
24730
  const dirWatcher = watch(watchDir, {
24613
24731
  persistent: true,
24614
24732
  ignoreInitial: true,
24615
24733
  depth: 3
24616
24734
  });
24617
24735
  dirWatcher.on("addDir", (dirPath) => {
24618
- const parentDir = join12(dirPath, "..");
24619
- const isDirectChild = parentDir.endsWith("sessions") || parentDir.endsWith(join12(".ocr", "sessions"));
24736
+ const parentDir = join13(dirPath, "..");
24737
+ const isDirectChild = parentDir.endsWith("sessions") || parentDir.endsWith(join13(".ocr", "sessions"));
24620
24738
  if (isDirectChild && !dirPath.endsWith("sessions")) {
24621
24739
  const newSession = basename7(dirPath);
24622
24740
  currentSession = newSession;
@@ -24718,34 +24836,34 @@ function renderCombinedProgress(sessionPath, preservedStartTimes, ocrDir) {
24718
24836
  }
24719
24837
 
24720
24838
  // src/commands/state.ts
24721
- import { existsSync as existsSync12, mkdirSync as mkdirSync5 } from "node:fs";
24722
- import { join as join14 } from "node:path";
24839
+ import { existsSync as existsSync13, mkdirSync as mkdirSync6 } from "node:fs";
24840
+ import { join as join15 } from "node:path";
24723
24841
 
24724
24842
  // src/lib/state/index.ts
24725
24843
  init_db();
24726
24844
  import {
24727
- existsSync as existsSync11,
24728
- mkdirSync as mkdirSync4,
24845
+ existsSync as existsSync12,
24846
+ mkdirSync as mkdirSync5,
24729
24847
  readdirSync as readdirSync6,
24730
- readFileSync as readFileSync9,
24848
+ readFileSync as readFileSync10,
24731
24849
  statSync as statSync2,
24732
- writeFileSync as writeFileSync7
24850
+ writeFileSync as writeFileSync8
24733
24851
  } from "node:fs";
24734
- import { join as join13 } from "node:path";
24852
+ import { join as join14 } from "node:path";
24735
24853
  async function stateInit(params) {
24736
24854
  const { sessionId, branch, workflowType, sessionDir, ocrDir } = params;
24737
24855
  const db = await ensureDatabase(ocrDir);
24738
- const dbPath = join13(ocrDir, "data", "ocr.db");
24856
+ const dbPath = join14(ocrDir, "data", "ocr.db");
24739
24857
  const existing = getSession(db, sessionId);
24740
24858
  if (existing) {
24741
- const roundsDir = join13(sessionDir, "rounds");
24859
+ const roundsDir = join14(sessionDir, "rounds");
24742
24860
  let nextRound = 1;
24743
- if (existsSync11(roundsDir)) {
24861
+ if (existsSync12(roundsDir)) {
24744
24862
  const roundDirs = readdirSync6(roundsDir).filter((d) => /^round-\d+$/.test(d)).map((d) => parseInt(d.replace("round-", ""), 10)).sort((a, b) => a - b);
24745
24863
  if (roundDirs.length > 0) {
24746
24864
  const highest = roundDirs[roundDirs.length - 1];
24747
- const hasFinal = existsSync11(
24748
- join13(roundsDir, `round-${highest}`, "final.md")
24865
+ const hasFinal = existsSync12(
24866
+ join14(roundsDir, `round-${highest}`, "final.md")
24749
24867
  );
24750
24868
  nextRound = hasFinal ? highest + 1 : highest;
24751
24869
  }
@@ -24789,7 +24907,7 @@ async function stateInit(params) {
24789
24907
  async function stateTransition(params) {
24790
24908
  const { sessionId, phase, phaseNumber, round, mapRun, ocrDir } = params;
24791
24909
  const db = await ensureDatabase(ocrDir);
24792
- const dbPath = join13(ocrDir, "data", "ocr.db");
24910
+ const dbPath = join14(ocrDir, "data", "ocr.db");
24793
24911
  const existing = getSession(db, sessionId);
24794
24912
  if (!existing) {
24795
24913
  throw new Error(`Session not found: ${sessionId}`);
@@ -24822,7 +24940,7 @@ async function stateTransition(params) {
24822
24940
  async function stateClose(params) {
24823
24941
  const { sessionId, ocrDir } = params;
24824
24942
  const db = await ensureDatabase(ocrDir);
24825
- const dbPath = join13(ocrDir, "data", "ocr.db");
24943
+ const dbPath = join14(ocrDir, "data", "ocr.db");
24826
24944
  const existing = getSession(db, sessionId);
24827
24945
  if (!existing) {
24828
24946
  throw new Error(`Session not found: ${sessionId}`);
@@ -24889,10 +25007,10 @@ async function resolveActiveSession(ocrDir) {
24889
25007
  }
24890
25008
  function readJsonFromSource(params) {
24891
25009
  if (params.source === "file") {
24892
- if (!existsSync11(params.filePath)) {
25010
+ if (!existsSync12(params.filePath)) {
24893
25011
  throw new Error(`File not found: ${params.filePath}`);
24894
25012
  }
24895
- return readFileSync9(params.filePath, "utf-8");
25013
+ return readFileSync10(params.filePath, "utf-8");
24896
25014
  }
24897
25015
  return params.data;
24898
25016
  }
@@ -25026,7 +25144,7 @@ function computeRoundCounts(meta) {
25026
25144
  async function stateRoundComplete(params) {
25027
25145
  const { ocrDir } = params;
25028
25146
  const db = await ensureDatabase(ocrDir);
25029
- const dbPath = join13(ocrDir, "data", "ocr.db");
25147
+ const dbPath = join14(ocrDir, "data", "ocr.db");
25030
25148
  const rawJsonString = readJsonFromSource(params);
25031
25149
  const label = params.source === "file" ? params.filePath : "stdin";
25032
25150
  const raw = parseRawJson(rawJsonString, label);
@@ -25036,10 +25154,10 @@ async function stateRoundComplete(params) {
25036
25154
  const roundNumber = params.round ?? session.current_round;
25037
25155
  let metaPath;
25038
25156
  if (params.source === "stdin") {
25039
- const roundDir = join13(session.session_dir, "rounds", `round-${roundNumber}`);
25040
- mkdirSync4(roundDir, { recursive: true });
25041
- metaPath = join13(roundDir, "round-meta.json");
25042
- writeFileSync7(metaPath, JSON.stringify(meta, null, 2));
25157
+ const roundDir = join14(session.session_dir, "rounds", `round-${roundNumber}`);
25158
+ mkdirSync5(roundDir, { recursive: true });
25159
+ metaPath = join14(roundDir, "round-meta.json");
25160
+ writeFileSync8(metaPath, JSON.stringify(meta, null, 2));
25043
25161
  }
25044
25162
  insertEvent(db, {
25045
25163
  session_id: session.id,
@@ -25120,7 +25238,7 @@ function computeMapCounts(meta) {
25120
25238
  async function stateMapComplete(params) {
25121
25239
  const { ocrDir } = params;
25122
25240
  const db = await ensureDatabase(ocrDir);
25123
- const dbPath = join13(ocrDir, "data", "ocr.db");
25241
+ const dbPath = join14(ocrDir, "data", "ocr.db");
25124
25242
  const rawJsonString = readJsonFromSource(params);
25125
25243
  const label = params.source === "file" ? params.filePath : "stdin";
25126
25244
  const raw = parseRawJson(rawJsonString, label);
@@ -25130,10 +25248,10 @@ async function stateMapComplete(params) {
25130
25248
  const mapRunNumber = params.mapRun ?? session.current_map_run;
25131
25249
  let metaPath;
25132
25250
  if (params.source === "stdin") {
25133
- const runDir = join13(session.session_dir, "map", "runs", `run-${mapRunNumber}`);
25134
- mkdirSync4(runDir, { recursive: true });
25135
- metaPath = join13(runDir, "map-meta.json");
25136
- writeFileSync7(metaPath, JSON.stringify(meta, null, 2));
25251
+ const runDir = join14(session.session_dir, "map", "runs", `run-${mapRunNumber}`);
25252
+ mkdirSync5(runDir, { recursive: true });
25253
+ metaPath = join14(runDir, "map-meta.json");
25254
+ writeFileSync8(metaPath, JSON.stringify(meta, null, 2));
25137
25255
  }
25138
25256
  insertEvent(db, {
25139
25257
  session_id: session.id,
@@ -25152,24 +25270,24 @@ async function stateMapComplete(params) {
25152
25270
  }
25153
25271
  async function stateSync(ocrDir) {
25154
25272
  const db = await ensureDatabase(ocrDir);
25155
- const dbPath = join13(ocrDir, "data", "ocr.db");
25156
- const sessionsRoot = join13(ocrDir, "sessions");
25157
- if (!existsSync11(sessionsRoot)) {
25273
+ const dbPath = join14(ocrDir, "data", "ocr.db");
25274
+ const sessionsRoot = join14(ocrDir, "sessions");
25275
+ if (!existsSync12(sessionsRoot)) {
25158
25276
  return 0;
25159
25277
  }
25160
25278
  const entries = readdirSync6(sessionsRoot).filter((name) => {
25161
- const fullPath = join13(sessionsRoot, name);
25279
+ const fullPath = join14(sessionsRoot, name);
25162
25280
  return statSync2(fullPath).isDirectory();
25163
25281
  });
25164
25282
  let synced = 0;
25165
25283
  for (const dirName of entries) {
25166
- const dirPath = join13(sessionsRoot, dirName);
25284
+ const dirPath = join14(sessionsRoot, dirName);
25167
25285
  const existing = getSession(db, dirName);
25168
25286
  if (existing) {
25169
25287
  continue;
25170
25288
  }
25171
- const hasRoundsDir = existsSync11(join13(dirPath, "rounds"));
25172
- const hasMapDir = existsSync11(join13(dirPath, "map"));
25289
+ const hasRoundsDir = existsSync12(join14(dirPath, "rounds"));
25290
+ const hasMapDir = existsSync12(join14(dirPath, "map"));
25173
25291
  const workflowType = hasMapDir && !hasRoundsDir ? "map" : "review";
25174
25292
  const branchMatch = dirName.match(/^\d{4}-\d{2}-\d{2}-(.+)$/);
25175
25293
  const branch = branchMatch?.[1] ?? dirName;
@@ -25199,6 +25317,8 @@ async function stateSync(ocrDir) {
25199
25317
  }
25200
25318
 
25201
25319
  // src/commands/state.ts
25320
+ init_command_log();
25321
+ init_db();
25202
25322
  async function readStdin() {
25203
25323
  const chunks = [];
25204
25324
  for await (const chunk of process.stdin) {
@@ -25225,10 +25345,10 @@ var initSubcommand = new Command("init").description("Initialize a new OCR sessi
25225
25345
  async (options) => {
25226
25346
  const targetDir = process.cwd();
25227
25347
  requireOcrSetup(targetDir);
25228
- const ocrDir = join14(targetDir, ".ocr");
25229
- const sessionDir = options.sessionDir ?? join14(ocrDir, "sessions", options.sessionId);
25230
- if (!existsSync12(sessionDir)) {
25231
- mkdirSync5(sessionDir, { recursive: true });
25348
+ const ocrDir = join15(targetDir, ".ocr");
25349
+ const sessionDir = options.sessionDir ?? join15(ocrDir, "sessions", options.sessionId);
25350
+ if (!existsSync13(sessionDir)) {
25351
+ mkdirSync6(sessionDir, { recursive: true });
25232
25352
  }
25233
25353
  try {
25234
25354
  const sessionId = await stateInit({
@@ -25253,7 +25373,7 @@ var transitionSubcommand = new Command("transition").description("Transition ses
25253
25373
  async (options) => {
25254
25374
  const targetDir = process.cwd();
25255
25375
  requireOcrSetup(targetDir);
25256
- const ocrDir = join14(targetDir, ".ocr");
25376
+ const ocrDir = join15(targetDir, ".ocr");
25257
25377
  const VALID_PHASES = /* @__PURE__ */ new Set([
25258
25378
  "context",
25259
25379
  "change-context",
@@ -25297,7 +25417,7 @@ var transitionSubcommand = new Command("transition").description("Transition ses
25297
25417
  var closeSubcommand = new Command("close").description("Close a session").option("--session-id <id>", "Session ID (auto-detects latest active if omitted)").action(async (options) => {
25298
25418
  const targetDir = process.cwd();
25299
25419
  requireOcrSetup(targetDir);
25300
- const ocrDir = join14(targetDir, ".ocr");
25420
+ const ocrDir = join15(targetDir, ".ocr");
25301
25421
  try {
25302
25422
  const sessionId = options.sessionId ?? (await resolveActiveSession(ocrDir)).id;
25303
25423
  await stateClose({
@@ -25317,7 +25437,7 @@ var closeSubcommand = new Command("close").description("Close a session").option
25317
25437
  var showSubcommand = new Command("show").description("Show current session state").option("--session-id <id>", "Session ID (defaults to latest active)").option("--json", "Output as JSON").action(async (options) => {
25318
25438
  const targetDir = process.cwd();
25319
25439
  requireOcrSetup(targetDir);
25320
- const ocrDir = join14(targetDir, ".ocr");
25440
+ const ocrDir = join15(targetDir, ".ocr");
25321
25441
  try {
25322
25442
  const result = await stateShow(ocrDir, options.sessionId);
25323
25443
  if (!result) {
@@ -25386,10 +25506,20 @@ var showSubcommand = new Command("show").description("Show current session state
25386
25506
  var syncSubcommand = new Command("sync").description("Rebuild session state from filesystem artifacts").action(async () => {
25387
25507
  const targetDir = process.cwd();
25388
25508
  requireOcrSetup(targetDir);
25389
- const ocrDir = join14(targetDir, ".ocr");
25509
+ const ocrDir = join15(targetDir, ".ocr");
25390
25510
  try {
25391
25511
  const synced = await stateSync(ocrDir);
25392
25512
  console.log(`Synced ${synced} session${synced !== 1 ? "s" : ""} from filesystem.`);
25513
+ const db = await getDb(ocrDir);
25514
+ const countResult = db.exec("SELECT COUNT(*) as c FROM command_executions");
25515
+ const totalCmds = countResult[0]?.values[0]?.[0] ?? 0;
25516
+ if (totalCmds === 0) {
25517
+ const recovered = replayCommandLog(db, ocrDir);
25518
+ if (recovered > 0) {
25519
+ saveDatabase(db, join15(ocrDir, "data", "ocr.db"));
25520
+ console.log(`Recovered ${recovered} command${recovered !== 1 ? "s" : ""} from backup log.`);
25521
+ }
25522
+ }
25393
25523
  } catch (error) {
25394
25524
  console.error(
25395
25525
  source_default.red(
@@ -25403,7 +25533,7 @@ var roundCompleteSubcommand = new Command("round-complete").description("Import
25403
25533
  async (options) => {
25404
25534
  const targetDir = process.cwd();
25405
25535
  requireOcrSetup(targetDir);
25406
- const ocrDir = join14(targetDir, ".ocr");
25536
+ const ocrDir = join15(targetDir, ".ocr");
25407
25537
  if (!options.file && !options.stdin) {
25408
25538
  console.error(source_default.red("Error: Provide either --file <path> or --stdin"));
25409
25539
  process.exit(1);
@@ -25452,7 +25582,7 @@ var mapCompleteSubcommand = new Command("map-complete").description("Import stru
25452
25582
  async (options) => {
25453
25583
  const targetDir = process.cwd();
25454
25584
  requireOcrSetup(targetDir);
25455
- const ocrDir = join14(targetDir, ".ocr");
25585
+ const ocrDir = join15(targetDir, ".ocr");
25456
25586
  if (!options.file && !options.stdin) {
25457
25587
  console.error(source_default.red("Error: Provide either --file <path> or --stdin"));
25458
25588
  process.exit(1);
@@ -25500,16 +25630,16 @@ var mapCompleteSubcommand = new Command("map-complete").description("Import stru
25500
25630
  var stateCommand = new Command("state").description("Manage OCR session state").addCommand(initSubcommand).addCommand(transitionSubcommand).addCommand(closeSubcommand).addCommand(showSubcommand).addCommand(syncSubcommand).addCommand(roundCompleteSubcommand).addCommand(mapCompleteSubcommand);
25501
25631
 
25502
25632
  // src/commands/update.ts
25503
- import { existsSync as existsSync13 } from "node:fs";
25504
- import { join as join15 } from "node:path";
25633
+ import { existsSync as existsSync14 } from "node:fs";
25634
+ import { join as join16 } from "node:path";
25505
25635
  function detectConfiguredTools(targetDir) {
25506
25636
  return AI_TOOLS.filter((tool) => {
25507
25637
  if (tool.commandStrategy === "subdirectory") {
25508
- const ocrDir = join15(targetDir, tool.commandsDir, "ocr");
25509
- return existsSync13(ocrDir);
25638
+ const ocrDir = join16(targetDir, tool.commandsDir, "ocr");
25639
+ return existsSync14(ocrDir);
25510
25640
  } else {
25511
- const reviewCmd = join15(targetDir, tool.commandsDir, "ocr-review.md");
25512
- return existsSync13(reviewCmd);
25641
+ const reviewCmd = join16(targetDir, tool.commandsDir, "ocr-review.md");
25642
+ return existsSync14(reviewCmd);
25513
25643
  }
25514
25644
  });
25515
25645
  }
@@ -25583,7 +25713,7 @@ var updateCommand = new Command("update").description("Update OCR assets after p
25583
25713
  const result = installForTool(tool, targetDir);
25584
25714
  results.push(result);
25585
25715
  }
25586
- ensureGitignore(join15(targetDir, ".ocr"));
25716
+ ensureGitignore(join16(targetDir, ".ocr"));
25587
25717
  spinner.stop();
25588
25718
  const successful = results.filter((r) => r.success);
25589
25719
  const failed = results.filter((r) => !r.success);
@@ -25619,10 +25749,10 @@ var updateCommand = new Command("update").description("Update OCR assets after p
25619
25749
  if (updateInject) {
25620
25750
  if (options.dryRun) {
25621
25751
  console.log(source_default.dim(" Would update:"));
25622
- if (existsSync13(join15(targetDir, "AGENTS.md"))) {
25752
+ if (existsSync14(join16(targetDir, "AGENTS.md"))) {
25623
25753
  console.log(source_default.dim(" \u2022 AGENTS.md (OCR managed block)"));
25624
25754
  }
25625
- if (existsSync13(join15(targetDir, "CLAUDE.md"))) {
25755
+ if (existsSync14(join16(targetDir, "CLAUDE.md"))) {
25626
25756
  console.log(source_default.dim(" \u2022 CLAUDE.md (OCR managed block)"));
25627
25757
  }
25628
25758
  console.log();
@@ -25654,14 +25784,14 @@ var updateCommand = new Command("update").description("Update OCR assets after p
25654
25784
  });
25655
25785
 
25656
25786
  // src/commands/dashboard.ts
25657
- import { existsSync as existsSync14 } from "node:fs";
25658
- import { join as join16, dirname as dirname5 } from "node:path";
25787
+ import { existsSync as existsSync15 } from "node:fs";
25788
+ import { join as join17, dirname as dirname6 } from "node:path";
25659
25789
  import { fileURLToPath } from "node:url";
25660
25790
  init_db();
25661
25791
  var __filename = fileURLToPath(import.meta.url);
25662
- var __dirname = dirname5(__filename);
25792
+ var __dirname = dirname6(__filename);
25663
25793
  function resolveServerPath() {
25664
- return join16(__dirname, "dashboard", "server.js");
25794
+ return join17(__dirname, "dashboard", "server.js");
25665
25795
  }
25666
25796
  var dashboardCommand = new Command("dashboard").description("Start the OCR dashboard web interface").option("-p, --port <port>", "Port to run the server on", "4173").option("--no-open", "Don't open the browser automatically").action(
25667
25797
  async (options) => {
@@ -25672,7 +25802,7 @@ var dashboardCommand = new Command("dashboard").description("Start the OCR dashb
25672
25802
  console.error(source_default.red(`Error: Invalid port "${options.port}". Must be 1-65535.`));
25673
25803
  process.exit(1);
25674
25804
  }
25675
- const ocrDir = join16(targetDir, ".ocr");
25805
+ const ocrDir = join17(targetDir, ".ocr");
25676
25806
  try {
25677
25807
  await ensureDatabase(ocrDir);
25678
25808
  closeAllDatabases();
@@ -25686,7 +25816,7 @@ var dashboardCommand = new Command("dashboard").description("Start the OCR dashb
25686
25816
  process.exit(1);
25687
25817
  }
25688
25818
  const serverPath = resolveServerPath();
25689
- if (!existsSync14(serverPath)) {
25819
+ if (!existsSync15(serverPath)) {
25690
25820
  console.error(source_default.red("Error: Dashboard server bundle not found."));
25691
25821
  console.error(
25692
25822
  source_default.dim(` Expected at: ${serverPath}`)
@@ -25720,8 +25850,8 @@ var dashboardCommand = new Command("dashboard").description("Start the OCR dashb
25720
25850
  );
25721
25851
 
25722
25852
  // src/commands/doctor.ts
25723
- import { existsSync as existsSync15 } from "node:fs";
25724
- import { join as join17 } from "node:path";
25853
+ import { existsSync as existsSync16 } from "node:fs";
25854
+ import { join as join18 } from "node:path";
25725
25855
  var doctorCommand = new Command("doctor").description("Check OCR installation and verify all dependencies").action(() => {
25726
25856
  printHeader();
25727
25857
  const targetDir = process.cwd();
@@ -25735,10 +25865,10 @@ var doctorCommand = new Command("doctor").description("Check OCR installation an
25735
25865
  console.log(source_default.bold(" OCR Installation"));
25736
25866
  console.log();
25737
25867
  const ocrStatus = checkOcrSetup(targetDir);
25738
- const configPath = join17(targetDir, ".ocr", "config.yaml");
25739
- const dbPath = join17(targetDir, ".ocr", "data", "ocr.db");
25740
- const hasConfig = existsSync15(configPath);
25741
- const hasDb = existsSync15(dbPath);
25868
+ const configPath = join18(targetDir, ".ocr", "config.yaml");
25869
+ const dbPath = join18(targetDir, ".ocr", "data", "ocr.db");
25870
+ const hasConfig = existsSync16(configPath);
25871
+ const hasDb = existsSync16(dbPath);
25742
25872
  const ocrChecks = [
25743
25873
  { label: ".ocr/skills/", ok: ocrStatus.hasSkills },
25744
25874
  { label: ".ocr/sessions/", ok: ocrStatus.hasSessions },
@@ -25812,8 +25942,8 @@ var doctorCommand = new Command("doctor").description("Check OCR installation an
25812
25942
  });
25813
25943
 
25814
25944
  // src/commands/reviewers.ts
25815
- import { writeFileSync as writeFileSync8, renameSync as renameSync2 } from "node:fs";
25816
- import { join as join18 } from "node:path";
25945
+ import { writeFileSync as writeFileSync9, renameSync as renameSync3 } from "node:fs";
25946
+ import { join as join19 } from "node:path";
25817
25947
  async function readStdin2() {
25818
25948
  const chunks = [];
25819
25949
  for await (const chunk of process.stdin) {
@@ -25876,20 +26006,20 @@ function validateReviewersMeta(data) {
25876
26006
  var syncSubcommand2 = new Command("sync").description("Sync reviewers-meta.json from reviewer markdown files or structured JSON").option("--stdin", "Read reviewers JSON from stdin (for AI-invoked sync)").action(async (options) => {
25877
26007
  const targetDir = process.cwd();
25878
26008
  requireOcrSetup(targetDir);
25879
- const ocrDir = join18(targetDir, ".ocr");
26009
+ const ocrDir = join19(targetDir, ".ocr");
25880
26010
  if (!options.stdin) {
25881
26011
  try {
25882
- const reviewersDir = join18(ocrDir, "skills", "references", "reviewers");
25883
- const configPath = join18(ocrDir, "config.yaml");
26012
+ const reviewersDir = join19(ocrDir, "skills", "references", "reviewers");
26013
+ const configPath = join19(ocrDir, "config.yaml");
25884
26014
  const meta = generateReviewersMeta(reviewersDir, configPath);
25885
26015
  if (!meta || meta.reviewers.length === 0) {
25886
26016
  console.error(source_default.yellow("No reviewer files found in .ocr/skills/references/reviewers/"));
25887
26017
  process.exit(1);
25888
26018
  }
25889
- const metaPath = join18(ocrDir, "reviewers-meta.json");
26019
+ const metaPath = join19(ocrDir, "reviewers-meta.json");
25890
26020
  const tmpPath = metaPath + ".tmp";
25891
- writeFileSync8(tmpPath, JSON.stringify(meta, null, 2) + "\n");
25892
- renameSync2(tmpPath, metaPath);
26021
+ writeFileSync9(tmpPath, JSON.stringify(meta, null, 2) + "\n");
26022
+ renameSync3(tmpPath, metaPath);
25893
26023
  const tierCounts = meta.reviewers.reduce(
25894
26024
  (acc, r) => {
25895
26025
  acc[r.tier] = (acc[r.tier] ?? 0) + 1;
@@ -25916,10 +26046,10 @@ var syncSubcommand2 = new Command("sync").description("Sync reviewers-meta.json
25916
26046
  throw new Error("Invalid JSON on stdin");
25917
26047
  }
25918
26048
  const meta = validateReviewersMeta(parsed);
25919
- const metaPath = join18(ocrDir, "reviewers-meta.json");
26049
+ const metaPath = join19(ocrDir, "reviewers-meta.json");
25920
26050
  const tmpPath = metaPath + ".tmp";
25921
- writeFileSync8(tmpPath, JSON.stringify(meta, null, 2) + "\n");
25922
- renameSync2(tmpPath, metaPath);
26051
+ writeFileSync9(tmpPath, JSON.stringify(meta, null, 2) + "\n");
26052
+ renameSync3(tmpPath, metaPath);
25923
26053
  const tierCounts = meta.reviewers.reduce(
25924
26054
  (acc, r) => {
25925
26055
  acc[r.tier] = (acc[r.tier] ?? 0) + 1;
@@ -25944,25 +26074,25 @@ var reviewersCommand = new Command("reviewers").description("Manage OCR reviewer
25944
26074
 
25945
26075
  // src/lib/update-check.ts
25946
26076
  import { homedir } from "node:os";
25947
- import { join as join19 } from "node:path";
25948
- import { readFileSync as readFileSync10, writeFileSync as writeFileSync9, mkdirSync as mkdirSync6 } from "node:fs";
26077
+ import { join as join20 } from "node:path";
26078
+ import { readFileSync as readFileSync11, writeFileSync as writeFileSync10, mkdirSync as mkdirSync7 } from "node:fs";
25949
26079
  var PACKAGE_NAME = "@open-code-review/cli";
25950
26080
  var REGISTRY_URL = `https://registry.npmjs.org/${PACKAGE_NAME}/latest`;
25951
- var CACHE_DIR = join19(homedir(), ".ocr");
25952
- var CACHE_FILE = join19(CACHE_DIR, "update-check.json");
26081
+ var CACHE_DIR2 = join20(homedir(), ".ocr");
26082
+ var CACHE_FILE = join20(CACHE_DIR2, "update-check.json");
25953
26083
  var CHECK_INTERVAL_MS = 4 * 60 * 60 * 1e3;
25954
26084
  var FETCH_TIMEOUT_MS = 3e3;
25955
26085
  function readCache(cacheFile) {
25956
26086
  try {
25957
- return JSON.parse(readFileSync10(cacheFile, "utf-8"));
26087
+ return JSON.parse(readFileSync11(cacheFile, "utf-8"));
25958
26088
  } catch {
25959
26089
  return null;
25960
26090
  }
25961
26091
  }
25962
26092
  function writeCache(cacheFile, cache) {
25963
26093
  try {
25964
- mkdirSync6(join19(cacheFile, ".."), { recursive: true });
25965
- writeFileSync9(cacheFile, JSON.stringify(cache));
26094
+ mkdirSync7(join20(cacheFile, ".."), { recursive: true });
26095
+ writeFileSync10(cacheFile, JSON.stringify(cache));
25966
26096
  } catch {
25967
26097
  }
25968
26098
  }
@@ -25982,7 +26112,7 @@ async function checkForUpdate(currentVersion, options) {
25982
26112
  if (process.env.CI || process.env.OCR_NO_UPDATE_CHECK) {
25983
26113
  return null;
25984
26114
  }
25985
- const cacheFile = join19(options?.cacheDir ?? CACHE_DIR, "update-check.json");
26115
+ const cacheFile = join20(options?.cacheDir ?? CACHE_DIR2, "update-check.json");
25986
26116
  try {
25987
26117
  const cache = readCache(cacheFile);
25988
26118
  if (cache && Date.now() - cache.lastCheck < CHECK_INTERVAL_MS) {