@open-code-review/cli 1.6.0 → 1.8.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 +20 -4
  2. package/dist/dashboard/client/assets/{_basePickBy-BGuMbEDR.js → _basePickBy-DbLJVCA4.js} +1 -1
  3. package/dist/dashboard/client/assets/{_baseUniq-Bx8loabg.js → _baseUniq-IXEG0cJJ.js} +1 -1
  4. package/dist/dashboard/client/assets/{arc-DUgpt7nY.js → arc-lsKxmOJY.js} +1 -1
  5. package/dist/dashboard/client/assets/{architectureDiagram-VXUJARFQ-D25nt6Xz.js → architectureDiagram-VXUJARFQ-DfMlzFJX.js} +1 -1
  6. package/dist/dashboard/client/assets/{blockDiagram-VD42YOAC-D8PUF3h4.js → blockDiagram-VD42YOAC-bSpnd26J.js} +1 -1
  7. package/dist/dashboard/client/assets/{c4Diagram-YG6GDRKO-lorsCz-I.js → c4Diagram-YG6GDRKO-DPYmVhCZ.js} +1 -1
  8. package/dist/dashboard/client/assets/channel-C--wY_Wd.js +1 -0
  9. package/dist/dashboard/client/assets/{chunk-4BX2VUAB-8lVyfRJM.js → chunk-4BX2VUAB-CI9zC4lV.js} +1 -1
  10. package/dist/dashboard/client/assets/{chunk-55IACEB6-C4SjgsZO.js → chunk-55IACEB6-BqUdJdx5.js} +1 -1
  11. package/dist/dashboard/client/assets/{chunk-B4BG7PRW-BXzTPbH1.js → chunk-B4BG7PRW-DymQrTp-.js} +1 -1
  12. package/dist/dashboard/client/assets/{chunk-DI55MBZ5-Bp7QllDt.js → chunk-DI55MBZ5-lZ_9LKGJ.js} +1 -1
  13. package/dist/dashboard/client/assets/{chunk-FMBD7UC4-B4g9S67N.js → chunk-FMBD7UC4-DC5rgLNm.js} +1 -1
  14. package/dist/dashboard/client/assets/{chunk-QN33PNHL-Dyk7Hc0J.js → chunk-QN33PNHL-BrygpHrX.js} +1 -1
  15. package/dist/dashboard/client/assets/{chunk-QZHKN3VN-DTvkGdnm.js → chunk-QZHKN3VN-CWJqBdNg.js} +1 -1
  16. package/dist/dashboard/client/assets/{chunk-TZMSLE5B-BAeZLvrI.js → chunk-TZMSLE5B-BACgM5pG.js} +1 -1
  17. package/dist/dashboard/client/assets/classDiagram-2ON5EDUG-DoxmMlnf.js +1 -0
  18. package/dist/dashboard/client/assets/classDiagram-v2-WZHVMYZB-DoxmMlnf.js +1 -0
  19. package/dist/dashboard/client/assets/clone-BgvweD4v.js +1 -0
  20. package/dist/dashboard/client/assets/{cose-bilkent-S5V4N54A--6-kzrdu.js → cose-bilkent-S5V4N54A-BYvGIfo0.js} +1 -1
  21. package/dist/dashboard/client/assets/{dagre-6UL2VRFP-D10_QE2P.js → dagre-6UL2VRFP-B1rZyiLJ.js} +1 -1
  22. package/dist/dashboard/client/assets/{diagram-PSM6KHXK-kS1x75Bl.js → diagram-PSM6KHXK-Dvl5dQMd.js} +1 -1
  23. package/dist/dashboard/client/assets/{diagram-QEK2KX5R-D_LLCPas.js → diagram-QEK2KX5R-Cmntmhht.js} +1 -1
  24. package/dist/dashboard/client/assets/{diagram-S2PKOQOG-Duy1t5UO.js → diagram-S2PKOQOG-BqZcpG85.js} +1 -1
  25. package/dist/dashboard/client/assets/{erDiagram-Q2GNP2WA-DyQXwzLf.js → erDiagram-Q2GNP2WA-Cw7BALso.js} +1 -1
  26. package/dist/dashboard/client/assets/{flowDiagram-NV44I4VS-D9U11XVw.js → flowDiagram-NV44I4VS-B_amTHzQ.js} +1 -1
  27. package/dist/dashboard/client/assets/{ganttDiagram-JELNMOA3-STy-TC-3.js → ganttDiagram-JELNMOA3-B1j2-sTo.js} +1 -1
  28. package/dist/dashboard/client/assets/{gitGraphDiagram-V2S2FVAM-B04PgURg.js → gitGraphDiagram-V2S2FVAM-D5BkfAMt.js} +1 -1
  29. package/dist/dashboard/client/assets/{graph-AiGwnT5H.js → graph-B_v15DHv.js} +1 -1
  30. package/dist/dashboard/client/assets/index-UkJZZdYD.js +548 -0
  31. package/dist/dashboard/client/assets/index-Zl---B_3.css +1 -0
  32. package/dist/dashboard/client/assets/{infoDiagram-HS3SLOUP-D4arwl6T.js → infoDiagram-HS3SLOUP-C4dtIkj3.js} +1 -1
  33. package/dist/dashboard/client/assets/{journeyDiagram-XKPGCS4Q-CsKqlKkf.js → journeyDiagram-XKPGCS4Q-hha4Am8v.js} +1 -1
  34. package/dist/dashboard/client/assets/{kanban-definition-3W4ZIXB7-CUFnzQE3.js → kanban-definition-3W4ZIXB7-1EY8l7Ng.js} +1 -1
  35. package/dist/dashboard/client/assets/{layout-BvvYJVPv.js → layout-7SmAbjFT.js} +1 -1
  36. package/dist/dashboard/client/assets/{linear-BiBJkzyE.js → linear-BfjSBezh.js} +1 -1
  37. package/dist/dashboard/client/assets/{mermaid-renderer-DGUmIWXY.js → mermaid-renderer-PPIt-kY4.js} +4 -4
  38. package/dist/dashboard/client/assets/{mindmap-definition-VGOIOE7T-D-Kc9Xgu.js → mindmap-definition-VGOIOE7T-BFpjN9LY.js} +1 -1
  39. package/dist/dashboard/client/assets/{pieDiagram-ADFJNKIX-CooPKLnX.js → pieDiagram-ADFJNKIX-GBbQtDBQ.js} +1 -1
  40. package/dist/dashboard/client/assets/{quadrantDiagram-AYHSOK5B-3soPtaSQ.js → quadrantDiagram-AYHSOK5B-Dm0vOhOw.js} +1 -1
  41. package/dist/dashboard/client/assets/{requirementDiagram-UZGBJVZJ-rE40t0IG.js → requirementDiagram-UZGBJVZJ-BrKONIV8.js} +1 -1
  42. package/dist/dashboard/client/assets/{sankeyDiagram-TZEHDZUN-CrgDF_jW.js → sankeyDiagram-TZEHDZUN-IOobtmDc.js} +1 -1
  43. package/dist/dashboard/client/assets/{sequenceDiagram-WL72ISMW-B628IlDW.js → sequenceDiagram-WL72ISMW-Dnb0bOW5.js} +1 -1
  44. package/dist/dashboard/client/assets/{stateDiagram-FKZM4ZOC-C4yb7S9D.js → stateDiagram-FKZM4ZOC-C9-bf7bn.js} +1 -1
  45. package/dist/dashboard/client/assets/stateDiagram-v2-4FDKWEC3-C8Gr4khP.js +1 -0
  46. package/dist/dashboard/client/assets/{timeline-definition-IT6M3QCI-5uLN4f_J.js → timeline-definition-IT6M3QCI-tJogDEHB.js} +1 -1
  47. package/dist/dashboard/client/assets/{treemap-GDKQZRPO-BHXME3bw.js → treemap-GDKQZRPO-DQY6HADq.js} +1 -1
  48. package/dist/dashboard/client/assets/{xychartDiagram-PRI3JC2R-BYTod6eI.js → xychartDiagram-PRI3JC2R-DfxeQmTO.js} +1 -1
  49. package/dist/dashboard/client/index.html +2 -2
  50. package/dist/dashboard/server.js +312 -187
  51. package/dist/index.js +326 -10
  52. package/package.json +2 -2
  53. package/dist/dashboard/client/assets/channel-yW2sWou_.js +0 -1
  54. package/dist/dashboard/client/assets/classDiagram-2ON5EDUG-1pMX5UXO.js +0 -1
  55. package/dist/dashboard/client/assets/classDiagram-v2-WZHVMYZB-1pMX5UXO.js +0 -1
  56. package/dist/dashboard/client/assets/clone-DQwdw3YR.js +0 -1
  57. package/dist/dashboard/client/assets/index-BzQ3i_QR.js +0 -458
  58. package/dist/dashboard/client/assets/index-CGGYXSm-.css +0 -1
  59. package/dist/dashboard/client/assets/stateDiagram-v2-4FDKWEC3-BoFeOfLI.js +0 -1
@@ -18413,7 +18413,7 @@ var require_view = __commonJS({
18413
18413
  var dirname11 = path2.dirname;
18414
18414
  var basename3 = path2.basename;
18415
18415
  var extname = path2.extname;
18416
- var join13 = path2.join;
18416
+ var join14 = path2.join;
18417
18417
  var resolve3 = path2.resolve;
18418
18418
  module.exports = View;
18419
18419
  function View(name, options) {
@@ -18461,12 +18461,12 @@ var require_view = __commonJS({
18461
18461
  };
18462
18462
  View.prototype.resolve = function resolve4(dir, file) {
18463
18463
  var ext = this.ext;
18464
- var path3 = join13(dir, file);
18464
+ var path3 = join14(dir, file);
18465
18465
  var stat = tryStat(path3);
18466
18466
  if (stat && stat.isFile()) {
18467
18467
  return path3;
18468
18468
  }
18469
- path3 = join13(dir, basename3(file, ext), "index" + ext);
18469
+ path3 = join14(dir, basename3(file, ext), "index" + ext);
18470
18470
  stat = tryStat(path3);
18471
18471
  if (stat && stat.isFile()) {
18472
18472
  return path3;
@@ -19099,7 +19099,7 @@ var require_send = __commonJS({
19099
19099
  var Stream = __require("stream");
19100
19100
  var util = __require("util");
19101
19101
  var extname = path2.extname;
19102
- var join13 = path2.join;
19102
+ var join14 = path2.join;
19103
19103
  var normalize = path2.normalize;
19104
19104
  var resolve3 = path2.resolve;
19105
19105
  var sep = path2.sep;
@@ -19318,7 +19318,7 @@ var require_send = __commonJS({
19318
19318
  return res;
19319
19319
  }
19320
19320
  parts = path3.split(sep);
19321
- path3 = normalize(join13(root, path3));
19321
+ path3 = normalize(join14(root, path3));
19322
19322
  } else {
19323
19323
  if (UP_PATH_REGEXP.test(path3)) {
19324
19324
  debug('malicious path "%s"', path3);
@@ -19453,7 +19453,7 @@ var require_send = __commonJS({
19453
19453
  if (err) return self.onStatError(err);
19454
19454
  return self.error(404);
19455
19455
  }
19456
- var p = join13(path3, self._index[i]);
19456
+ var p = join14(path3, self._index[i]);
19457
19457
  debug('stat "%s"', p);
19458
19458
  fs6.stat(p, function(err2, stat) {
19459
19459
  if (err2) return next(err2);
@@ -20592,7 +20592,7 @@ var require_application = __commonJS({
20592
20592
  "../../node_modules/.pnpm/express@4.22.1/node_modules/express/lib/application.js"(exports, module) {
20593
20593
  "use strict";
20594
20594
  var finalhandler = require_finalhandler();
20595
- var Router11 = require_router();
20595
+ var Router12 = require_router();
20596
20596
  var methods = require_methods();
20597
20597
  var middleware = require_init();
20598
20598
  var query = require_query();
@@ -20657,7 +20657,7 @@ var require_application = __commonJS({
20657
20657
  };
20658
20658
  app2.lazyrouter = function lazyrouter() {
20659
20659
  if (!this._router) {
20660
- this._router = new Router11({
20660
+ this._router = new Router12({
20661
20661
  caseSensitive: this.enabled("case sensitive routing"),
20662
20662
  strict: this.enabled("strict routing")
20663
20663
  });
@@ -22521,7 +22521,7 @@ var require_express = __commonJS({
22521
22521
  var mixin = require_merge_descriptors();
22522
22522
  var proto = require_application();
22523
22523
  var Route = require_route();
22524
- var Router11 = require_router();
22524
+ var Router12 = require_router();
22525
22525
  var req = require_request();
22526
22526
  var res = require_response();
22527
22527
  exports = module.exports = createApplication;
@@ -22544,7 +22544,7 @@ var require_express = __commonJS({
22544
22544
  exports.request = req;
22545
22545
  exports.response = res;
22546
22546
  exports.Route = Route;
22547
- exports.Router = Router11;
22547
+ exports.Router = Router12;
22548
22548
  exports.json = bodyParser.json;
22549
22549
  exports.query = require_query();
22550
22550
  exports.raw = bodyParser.raw;
@@ -23179,10 +23179,10 @@ var init_open = __esm({
23179
23179
  });
23180
23180
 
23181
23181
  // src/server/index.ts
23182
- var import_express11 = __toESM(require_express2(), 1);
23182
+ var import_express12 = __toESM(require_express2(), 1);
23183
23183
  import { createServer } from "node:http";
23184
- import { existsSync as existsSync8, readFileSync as readFileSync9, writeFileSync as writeFileSync4, unlinkSync as unlinkSync2, mkdirSync as mkdirSync3 } from "node:fs";
23185
- import { join as join12, dirname as dirname10, resolve as resolve2 } from "node:path";
23184
+ import { existsSync as existsSync9, readFileSync as readFileSync10, writeFileSync as writeFileSync4, unlinkSync as unlinkSync2, mkdirSync as mkdirSync3 } from "node:fs";
23185
+ import { join as join13, dirname as dirname10, resolve as resolve2 } from "node:path";
23186
23186
  import { fileURLToPath as fileURLToPath3 } from "node:url";
23187
23187
  import { randomBytes } from "node:crypto";
23188
23188
  import { Server as SocketIOServer } from "socket.io";
@@ -24008,24 +24008,24 @@ function enrichSession(db, session) {
24008
24008
  let reviewPhaseNumber = 0;
24009
24009
  let reviewPhase = "";
24010
24010
  if (hasReview) {
24011
+ const derived = deriveReviewPhase(db, session.id);
24011
24012
  if (session.workflow_type === "review") {
24012
- reviewPhaseNumber = session.phase_number;
24013
- reviewPhase = session.current_phase;
24013
+ reviewPhaseNumber = Math.max(session.phase_number, derived);
24014
24014
  } else {
24015
- reviewPhaseNumber = deriveReviewPhase(db, session.id);
24016
- reviewPhase = REVIEW_PHASE_NAMES[reviewPhaseNumber - 1] ?? "context";
24015
+ reviewPhaseNumber = derived;
24017
24016
  }
24017
+ reviewPhase = REVIEW_PHASE_NAMES[reviewPhaseNumber - 1] ?? "context";
24018
24018
  }
24019
24019
  let mapPhaseNumber = 0;
24020
24020
  let mapPhase = "";
24021
24021
  if (hasMap) {
24022
+ const derived = deriveMapPhase(db, session.id);
24022
24023
  if (session.workflow_type === "map") {
24023
- mapPhaseNumber = session.phase_number;
24024
- mapPhase = session.current_phase;
24024
+ mapPhaseNumber = Math.max(session.phase_number, derived);
24025
24025
  } else {
24026
- mapPhaseNumber = deriveMapPhase(db, session.id);
24027
- mapPhase = MAP_PHASE_NAMES[mapPhaseNumber - 1] ?? "map-context";
24026
+ mapPhaseNumber = derived;
24028
24027
  }
24028
+ mapPhase = MAP_PHASE_NAMES[mapPhaseNumber - 1] ?? "map-context";
24029
24029
  }
24030
24030
  const latestRound = rounds.length > 0 ? rounds[rounds.length - 1] : null;
24031
24031
  const latestVerdict = latestRound?.verdict ?? null;
@@ -25112,18 +25112,16 @@ var AiCliService = class {
25112
25112
  active: this.activeAdapter?.binary ?? null,
25113
25113
  preferred: this.preference
25114
25114
  };
25115
- for (const entry of this.entries) {
25116
- const { adapter, detection } = entry;
25117
- if (detection.found) {
25118
- console.log(` AI CLI: ${adapter.name} v${detection.version ?? "?"} detected`);
25119
- }
25115
+ const detected = this.entries.filter((e) => e.detection.found).map((e) => `${e.adapter.name} v${e.detection.version ?? "?"}`);
25116
+ if (detected.length > 0) {
25117
+ console.log(` AI CLI detected: ${detected.join(", ")}`);
25120
25118
  }
25121
25119
  if (this.preference === "off") {
25122
- console.log(" AI CLI: Disabled via config (ai_cli: off) \u2014 dashboard will run in read-only mode");
25120
+ console.log(" AI CLI active: off (read-only mode)");
25123
25121
  } else if (this.activeAdapter) {
25124
- console.log(` AI CLI: Using ${this.activeAdapter.name} (preference: ${this.preference})`);
25122
+ console.log(` AI CLI active: ${this.activeAdapter.name} (${this.preference})`);
25125
25123
  } else {
25126
- console.log(" AI CLI: No supported AI CLI found \u2014 dashboard will run in read-only mode");
25124
+ console.log(" AI CLI active: none (read-only mode)");
25127
25125
  }
25128
25126
  }
25129
25127
  /** Returns the status object for the /api/config endpoint. */
@@ -25190,11 +25188,37 @@ function resolveLocalCli() {
25190
25188
  }
25191
25189
 
25192
25190
  // src/server/socket/command-runner.ts
25191
+ function shellSplit(str) {
25192
+ const tokens = [];
25193
+ let current = "";
25194
+ let quote = null;
25195
+ for (let i = 0; i < str.length; i++) {
25196
+ const ch = str[i];
25197
+ if (quote) {
25198
+ if (ch === quote) {
25199
+ quote = null;
25200
+ } else {
25201
+ current += ch;
25202
+ }
25203
+ } else if (ch === '"' || ch === "'") {
25204
+ quote = ch;
25205
+ } else if (/\s/.test(ch)) {
25206
+ if (current) {
25207
+ tokens.push(current);
25208
+ current = "";
25209
+ }
25210
+ } else {
25211
+ current += ch;
25212
+ }
25213
+ }
25214
+ if (current) tokens.push(current);
25215
+ return tokens;
25216
+ }
25193
25217
  var ALLOWED_COMMANDS = /* @__PURE__ */ new Set([
25194
25218
  "progress",
25195
25219
  "state"
25196
25220
  ]);
25197
- var AI_COMMANDS = /* @__PURE__ */ new Set(["map", "review", "translate-review-to-single-human", "address"]);
25221
+ var AI_COMMANDS = /* @__PURE__ */ new Set(["map", "review", "translate-review-to-single-human", "address", "create-reviewer", "sync-reviewers"]);
25198
25222
  var MAX_CONCURRENT = 3;
25199
25223
  var activeCommands = /* @__PURE__ */ new Map();
25200
25224
  function getActiveCommands() {
@@ -25216,7 +25240,7 @@ function registerCommandHandlers(io2, socket, db, ocrDir, aiCliService) {
25216
25240
  }
25217
25241
  const { command } = payload;
25218
25242
  const normalized = command.replace(/^ocr\s+/, "");
25219
- const parts = normalized.split(/\s+/);
25243
+ const parts = shellSplit(normalized);
25220
25244
  const baseCommand = parts[0] ?? "";
25221
25245
  const subArgs = parts.slice(1);
25222
25246
  if (!ALLOWED_COMMANDS.has(baseCommand) && !AI_COMMANDS.has(baseCommand)) {
@@ -25373,34 +25397,68 @@ function spawnAiCommand(io2, _socket, db, ocrDir, executionId, baseCommand, subA
25373
25397
  finishExecution(io2, db, ocrDir, executionId, 1, content);
25374
25398
  return;
25375
25399
  }
25376
- let target = "staged changes";
25377
- let requirements = "";
25378
- const options = [];
25379
- let i = 0;
25380
- while (i < subArgs.length) {
25381
- const arg = subArgs[i] ?? "";
25382
- if (arg === "--fresh") {
25383
- options.push("--fresh");
25384
- i++;
25385
- } else if (arg === "--requirements" && i + 1 < subArgs.length) {
25386
- requirements = subArgs.slice(i + 1).join(" ");
25387
- break;
25388
- } else if (!arg.startsWith("--")) {
25389
- target = arg;
25390
- i++;
25391
- } else {
25392
- i++;
25400
+ const promptLines = [];
25401
+ if (baseCommand === "create-reviewer" || baseCommand === "sync-reviewers") {
25402
+ const argsStr = subArgs.length > 0 ? subArgs.join(" ") : "";
25403
+ promptLines.push(
25404
+ `Follow the instructions below to run the OCR ${baseCommand} workflow.`,
25405
+ "",
25406
+ `Arguments: ${argsStr || "none"}`
25407
+ );
25408
+ } else {
25409
+ let target = "staged changes";
25410
+ let requirements = "";
25411
+ let team = "";
25412
+ const reviewerDescriptions = [];
25413
+ const options = [];
25414
+ let i = 0;
25415
+ while (i < subArgs.length) {
25416
+ const arg = subArgs[i] ?? "";
25417
+ if (arg === "--fresh") {
25418
+ options.push("--fresh");
25419
+ i++;
25420
+ } else if (arg === "--requirements" && i + 1 < subArgs.length) {
25421
+ requirements = subArgs.slice(i + 1).join(" ");
25422
+ break;
25423
+ } else if (arg === "--team" && i + 1 < subArgs.length) {
25424
+ team = subArgs[i + 1] ?? "";
25425
+ i += 2;
25426
+ } else if (arg === "--reviewer" && i + 1 < subArgs.length) {
25427
+ const raw = subArgs[i + 1] ?? "";
25428
+ const countMatch = raw.match(/^(\d+):(.+)$/);
25429
+ if (countMatch) {
25430
+ reviewerDescriptions.push({ description: countMatch[2], count: parseInt(countMatch[1], 10) });
25431
+ } else {
25432
+ reviewerDescriptions.push({ description: raw, count: 1 });
25433
+ }
25434
+ i += 2;
25435
+ } else if (!arg.startsWith("--")) {
25436
+ target = arg;
25437
+ i++;
25438
+ } else {
25439
+ i++;
25440
+ }
25441
+ }
25442
+ const optionsStr = options.length > 0 ? options.join(" ") : "none";
25443
+ promptLines.push(
25444
+ `Follow the instructions below to run the OCR ${baseCommand} workflow.`,
25445
+ "",
25446
+ `Target: ${target}`,
25447
+ `Options: ${optionsStr}`
25448
+ );
25449
+ if (team) {
25450
+ promptLines.push(`Team: ${team}`);
25451
+ }
25452
+ for (const { description, count } of reviewerDescriptions) {
25453
+ if (count > 1) {
25454
+ promptLines.push(`Reviewer (x${count}): ${description}`);
25455
+ } else {
25456
+ promptLines.push(`Reviewer: ${description}`);
25457
+ }
25458
+ }
25459
+ if (requirements) {
25460
+ promptLines.push(`Requirements: ${requirements}`);
25393
25461
  }
25394
- }
25395
- const optionsStr = options.length > 0 ? options.join(" ") : "none";
25396
- const promptLines = [
25397
- `Follow the instructions below to run the OCR ${baseCommand} workflow.`,
25398
- "",
25399
- `Target: ${target}`,
25400
- `Options: ${optionsStr}`
25401
- ];
25402
- if (requirements) {
25403
- promptLines.push(`Requirements: ${requirements}`);
25404
25462
  }
25405
25463
  const localCli = resolveLocalCli();
25406
25464
  if (localCli) {
@@ -25768,10 +25826,77 @@ function createChatRouter(db, ocrDir) {
25768
25826
  return router;
25769
25827
  }
25770
25828
 
25829
+ // src/server/routes/reviewers.ts
25830
+ var import_express11 = __toESM(require_express2(), 1);
25831
+ import { readFileSync as readFileSync5, existsSync as existsSync4, watch } from "node:fs";
25832
+ import { join as join9 } from "node:path";
25833
+ function readReviewersMeta(ocrDir) {
25834
+ const metaPath = join9(ocrDir, "reviewers-meta.json");
25835
+ if (!existsSync4(metaPath)) {
25836
+ return { reviewers: [], defaults: [] };
25837
+ }
25838
+ try {
25839
+ const raw = readFileSync5(metaPath, "utf-8");
25840
+ const meta = JSON.parse(raw);
25841
+ const reviewers = meta.reviewers ?? [];
25842
+ const defaults = reviewers.filter((r) => r.is_default).map((r) => r.id);
25843
+ return { reviewers, defaults };
25844
+ } catch {
25845
+ return { reviewers: [], defaults: [] };
25846
+ }
25847
+ }
25848
+ var VALID_ID_RE = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
25849
+ function createReviewersRouter(ocrDir) {
25850
+ const router = (0, import_express11.Router)();
25851
+ router.get("/", (_req, res) => {
25852
+ res.json(readReviewersMeta(ocrDir));
25853
+ });
25854
+ router.get("/:id/prompt", (req, res) => {
25855
+ const { id } = req.params;
25856
+ if (!id || !VALID_ID_RE.test(id)) {
25857
+ res.status(400).json({ error: "Invalid reviewer ID" });
25858
+ return;
25859
+ }
25860
+ const filePath = join9(ocrDir, "skills", "references", "reviewers", `${id}.md`);
25861
+ if (!existsSync4(filePath)) {
25862
+ res.status(404).json({ error: "Reviewer not found", id });
25863
+ return;
25864
+ }
25865
+ try {
25866
+ const content = readFileSync5(filePath, "utf-8");
25867
+ res.json({ id, content });
25868
+ } catch {
25869
+ res.status(500).json({ error: "Failed to read reviewer file", id });
25870
+ }
25871
+ });
25872
+ return router;
25873
+ }
25874
+ function watchReviewersMeta(ocrDir, io2) {
25875
+ const metaPath = join9(ocrDir, "reviewers-meta.json");
25876
+ let watcher = null;
25877
+ let debounce;
25878
+ try {
25879
+ watcher = watch(metaPath, () => {
25880
+ clearTimeout(debounce);
25881
+ debounce = setTimeout(() => {
25882
+ const data = readReviewersMeta(ocrDir);
25883
+ io2.emit("reviewers:updated", data);
25884
+ }, 200);
25885
+ });
25886
+ watcher.on("error", () => {
25887
+ });
25888
+ } catch {
25889
+ }
25890
+ return () => {
25891
+ clearTimeout(debounce);
25892
+ watcher?.close();
25893
+ };
25894
+ }
25895
+
25771
25896
  // src/server/services/filesystem-sync.ts
25772
- import { readdirSync, readFileSync as readFileSync5, statSync, existsSync as existsSync4 } from "node:fs";
25773
- import { join as join9, basename as basename2, dirname as dirname7, relative } from "node:path";
25774
- import { watch } from "chokidar";
25897
+ import { readdirSync, readFileSync as readFileSync6, statSync, existsSync as existsSync5 } from "node:fs";
25898
+ import { join as join10, basename as basename2, dirname as dirname7, relative } from "node:path";
25899
+ import { watch as watch2 } from "chokidar";
25775
25900
 
25776
25901
  // src/server/services/parsers/reviewer-parser.ts
25777
25902
  var FINDING_HEADING_RE = /^#{2,3}\s+(?:Finding|Issue|Suggestion)\s*(?:\d+)?\s*[:\s]*\s*(.*)/i;
@@ -25952,67 +26077,67 @@ var FilesystemSync = class {
25952
26077
  onSync;
25953
26078
  // ── 6.1: Full Scan ──
25954
26079
  async fullScan() {
25955
- if (!existsSync4(this.sessionsDir)) return;
26080
+ if (!existsSync5(this.sessionsDir)) return;
25956
26081
  const entries = readdirSync(this.sessionsDir, { withFileTypes: true });
25957
26082
  for (const entry of entries) {
25958
26083
  if (!entry.isDirectory()) continue;
25959
26084
  const sessionId = entry.name;
25960
- const sessionDir = join9(this.sessionsDir, sessionId);
26085
+ const sessionDir = join10(this.sessionsDir, sessionId);
25961
26086
  this.syncSession(sessionId, sessionDir);
25962
26087
  }
25963
26088
  }
25964
26089
  syncSession(sessionId, sessionDir) {
25965
26090
  this.ensureSessionRow(sessionId, sessionDir);
25966
- const roundsDir = join9(sessionDir, "rounds");
25967
- if (existsSync4(roundsDir)) {
26091
+ const roundsDir = join10(sessionDir, "rounds");
26092
+ if (existsSync5(roundsDir)) {
25968
26093
  const rounds = readdirSync(roundsDir, { withFileTypes: true });
25969
26094
  for (const roundEntry of rounds) {
25970
26095
  if (!roundEntry.isDirectory()) continue;
25971
26096
  const roundMatch = roundEntry.name.match(/^round-(\d+)$/);
25972
26097
  if (!roundMatch) continue;
25973
26098
  const roundNumber = parseInt(roundMatch[1] ?? "0", 10);
25974
- const roundDir = join9(roundsDir, roundEntry.name);
25975
- const reviewsDir = join9(roundDir, "reviews");
25976
- if (existsSync4(reviewsDir)) {
26099
+ const roundDir = join10(roundsDir, roundEntry.name);
26100
+ const reviewsDir = join10(roundDir, "reviews");
26101
+ if (existsSync5(reviewsDir)) {
25977
26102
  const reviewFiles = readdirSync(reviewsDir).filter((f) => f.endsWith(".md"));
25978
26103
  for (const reviewFile of reviewFiles) {
25979
- const filePath = join9(reviewsDir, reviewFile);
26104
+ const filePath = join10(reviewsDir, reviewFile);
25980
26105
  this.processReviewerOutput(sessionId, roundNumber, filePath, reviewFile);
25981
26106
  }
25982
26107
  }
25983
- const roundMetaPath = join9(roundDir, "round-meta.json");
25984
- if (existsSync4(roundMetaPath)) {
26108
+ const roundMetaPath = join10(roundDir, "round-meta.json");
26109
+ if (existsSync5(roundMetaPath)) {
25985
26110
  this.processRoundMeta(sessionId, roundNumber, roundMetaPath);
25986
26111
  }
25987
- const finalPath = join9(roundDir, "final.md");
25988
- if (existsSync4(finalPath)) {
26112
+ const finalPath = join10(roundDir, "final.md");
26113
+ if (existsSync5(finalPath)) {
25989
26114
  this.processFinalMd(sessionId, roundNumber, finalPath);
25990
26115
  }
25991
- const finalHumanPath = join9(roundDir, "final-human.md");
25992
- if (existsSync4(finalHumanPath)) {
26116
+ const finalHumanPath = join10(roundDir, "final-human.md");
26117
+ if (existsSync5(finalHumanPath)) {
25993
26118
  this.processGenericArtifact(sessionId, "final-human", finalHumanPath, roundNumber);
25994
26119
  }
25995
- const discoursePath = join9(roundDir, "discourse.md");
25996
- if (existsSync4(discoursePath)) {
26120
+ const discoursePath = join10(roundDir, "discourse.md");
26121
+ if (existsSync5(discoursePath)) {
25997
26122
  this.processGenericArtifact(sessionId, "discourse", discoursePath, roundNumber);
25998
26123
  }
25999
26124
  }
26000
26125
  }
26001
- const mapDir = join9(sessionDir, "map", "runs");
26002
- if (existsSync4(mapDir)) {
26126
+ const mapDir = join10(sessionDir, "map", "runs");
26127
+ if (existsSync5(mapDir)) {
26003
26128
  const runs = readdirSync(mapDir, { withFileTypes: true });
26004
26129
  for (const runEntry of runs) {
26005
26130
  if (!runEntry.isDirectory()) continue;
26006
26131
  const runMatch = runEntry.name.match(/^run-(\d+)$/);
26007
26132
  if (!runMatch) continue;
26008
26133
  const runNumber = parseInt(runMatch[1] ?? "0", 10);
26009
- const runDir = join9(mapDir, runEntry.name);
26010
- const mapMetaPath = join9(runDir, "map-meta.json");
26011
- if (existsSync4(mapMetaPath)) {
26134
+ const runDir = join10(mapDir, runEntry.name);
26135
+ const mapMetaPath = join10(runDir, "map-meta.json");
26136
+ if (existsSync5(mapMetaPath)) {
26012
26137
  this.processMapMeta(sessionId, runNumber, mapMetaPath);
26013
26138
  }
26014
- const mapPath = join9(runDir, "map.md");
26015
- if (existsSync4(mapPath)) {
26139
+ const mapPath = join10(runDir, "map.md");
26140
+ if (existsSync5(mapPath)) {
26016
26141
  this.processMapMd(sessionId, runNumber, mapPath);
26017
26142
  }
26018
26143
  const mapArtifacts = [
@@ -26021,8 +26146,8 @@ var FilesystemSync = class {
26021
26146
  ["requirements-mapping.md", "requirements-mapping"]
26022
26147
  ];
26023
26148
  for (const [fileName, artifactType] of mapArtifacts) {
26024
- const filePath = join9(runDir, fileName);
26025
- if (existsSync4(filePath)) {
26149
+ const filePath = join10(runDir, fileName);
26150
+ if (existsSync5(filePath)) {
26026
26151
  this.processGenericArtifact(sessionId, artifactType, filePath, void 0, runNumber);
26027
26152
  }
26028
26153
  }
@@ -26033,8 +26158,8 @@ var FilesystemSync = class {
26033
26158
  ["discovered-standards.md", "discovered-standards"]
26034
26159
  ];
26035
26160
  for (const [fileName, artifactType] of sessionArtifacts) {
26036
- const filePath = join9(sessionDir, fileName);
26037
- if (existsSync4(filePath)) {
26161
+ const filePath = join10(sessionDir, fileName);
26162
+ if (existsSync5(filePath)) {
26038
26163
  this.processGenericArtifact(sessionId, artifactType, filePath);
26039
26164
  }
26040
26165
  }
@@ -26043,17 +26168,17 @@ var FilesystemSync = class {
26043
26168
  ensureSessionRow(sessionId, sessionDir) {
26044
26169
  const branchMatch = sessionId.match(/^\d{4}-\d{2}-\d{2}-(.+)$/);
26045
26170
  const branch = branchMatch?.[1] ?? "unknown";
26046
- const hasRoundsDir = existsSync4(join9(sessionDir, "rounds"));
26047
- const hasMapDir = existsSync4(join9(sessionDir, "map"));
26171
+ const hasRoundsDir = existsSync5(join10(sessionDir, "rounds"));
26172
+ const hasMapDir = existsSync5(join10(sessionDir, "map"));
26048
26173
  const workflowType = hasMapDir && !hasRoundsDir ? "map" : "review";
26049
26174
  let currentRound = 1;
26050
26175
  if (hasRoundsDir) {
26051
- const roundDirs = readdirSync(join9(sessionDir, "rounds")).filter((d) => d.match(/^round-\d+$/));
26176
+ const roundDirs = readdirSync(join10(sessionDir, "rounds")).filter((d) => d.match(/^round-\d+$/));
26052
26177
  currentRound = Math.max(1, roundDirs.length);
26053
26178
  }
26054
26179
  let currentMapRun = 1;
26055
- const mapRunsDir = join9(sessionDir, "map", "runs");
26056
- if (existsSync4(mapRunsDir)) {
26180
+ const mapRunsDir = join10(sessionDir, "map", "runs");
26181
+ if (existsSync5(mapRunsDir)) {
26057
26182
  const runDirs = readdirSync(mapRunsDir).filter((d) => d.match(/^run-\d+$/));
26058
26183
  currentMapRun = Math.max(1, runDirs.length);
26059
26184
  }
@@ -26061,40 +26186,40 @@ var FilesystemSync = class {
26061
26186
  let phaseNumber = 1;
26062
26187
  let status = "active";
26063
26188
  if (workflowType === "review" && hasRoundsDir) {
26064
- const roundDir = join9(sessionDir, "rounds", `round-${currentRound}`);
26065
- if (existsSync4(join9(roundDir, "final.md"))) {
26189
+ const roundDir = join10(sessionDir, "rounds", `round-${currentRound}`);
26190
+ if (existsSync5(join10(roundDir, "final.md"))) {
26066
26191
  phase = "complete";
26067
26192
  phaseNumber = 8;
26068
26193
  status = "closed";
26069
- } else if (existsSync4(join9(roundDir, "discourse.md"))) {
26194
+ } else if (existsSync5(join10(roundDir, "discourse.md"))) {
26070
26195
  phase = "synthesis";
26071
26196
  phaseNumber = 7;
26072
- } else if (existsSync4(join9(roundDir, "reviews")) && readdirSync(join9(roundDir, "reviews")).filter((f) => f.endsWith(".md")).length > 0) {
26197
+ } else if (existsSync5(join10(roundDir, "reviews")) && readdirSync(join10(roundDir, "reviews")).filter((f) => f.endsWith(".md")).length > 0) {
26073
26198
  phase = "reviews";
26074
26199
  phaseNumber = 4;
26075
- } else if (existsSync4(join9(sessionDir, "context.md"))) {
26200
+ } else if (existsSync5(join10(sessionDir, "context.md"))) {
26076
26201
  phase = "analysis";
26077
26202
  phaseNumber = 3;
26078
- } else if (existsSync4(join9(sessionDir, "discovered-standards.md"))) {
26203
+ } else if (existsSync5(join10(sessionDir, "discovered-standards.md"))) {
26079
26204
  phase = "change-context";
26080
26205
  phaseNumber = 2;
26081
26206
  }
26082
26207
  } else if (workflowType === "map" && hasMapDir) {
26083
- const runDir = join9(mapRunsDir, `run-${currentMapRun}`);
26084
- if (existsSync4(join9(runDir, "map.md"))) {
26208
+ const runDir = join10(mapRunsDir, `run-${currentMapRun}`);
26209
+ if (existsSync5(join10(runDir, "map.md"))) {
26085
26210
  phase = "complete";
26086
26211
  phaseNumber = 6;
26087
26212
  status = "closed";
26088
- } else if (existsSync4(join9(runDir, "requirements-mapping.md"))) {
26213
+ } else if (existsSync5(join10(runDir, "requirements-mapping.md"))) {
26089
26214
  phase = "synthesis";
26090
26215
  phaseNumber = 5;
26091
- } else if (existsSync4(join9(runDir, "flow-analysis.md"))) {
26216
+ } else if (existsSync5(join10(runDir, "flow-analysis.md"))) {
26092
26217
  phase = "requirements-mapping";
26093
26218
  phaseNumber = 4;
26094
- } else if (existsSync4(join9(runDir, "topology.md"))) {
26219
+ } else if (existsSync5(join10(runDir, "topology.md"))) {
26095
26220
  phase = "flow-analysis";
26096
26221
  phaseNumber = 3;
26097
- } else if (existsSync4(join9(sessionDir, "discovered-standards.md"))) {
26222
+ } else if (existsSync5(join10(sessionDir, "discovered-standards.md"))) {
26098
26223
  phase = "topology";
26099
26224
  phaseNumber = 2;
26100
26225
  }
@@ -26157,12 +26282,12 @@ var FilesystemSync = class {
26157
26282
  );
26158
26283
  if (existingRun && this.shouldSkip(filePath, existingRun["parsed_at"] ?? null)) return;
26159
26284
  if (existingRun?.["source"] === "orchestrator") {
26160
- const content2 = readFileSync5(filePath, "utf-8");
26285
+ const content2 = readFileSync6(filePath, "utf-8");
26161
26286
  const action2 = this.upsertMarkdownArtifact(sessionId, "map", filePath, content2, void 0);
26162
26287
  this.emitArtifactEvent(action2, { sessionId, artifactType: "map", filePath });
26163
26288
  return;
26164
26289
  }
26165
- const content = readFileSync5(filePath, "utf-8");
26290
+ const content = readFileSync6(filePath, "utf-8");
26166
26291
  const parsed = parseMapMd(content);
26167
26292
  this.db.run(
26168
26293
  `INSERT OR REPLACE INTO map_runs (session_id, run_number, file_count, map_md_path, parsed_at, source)
@@ -26244,10 +26369,10 @@ var FilesystemSync = class {
26244
26369
  }
26245
26370
  const session = queryFirst(
26246
26371
  this.db,
26247
- "SELECT current_phase, workflow_type FROM sessions WHERE id = ?",
26372
+ "SELECT current_phase, phase_number, workflow_type FROM sessions WHERE id = ?",
26248
26373
  [sessionId]
26249
26374
  );
26250
- if (session && session["workflow_type"] === "map" && session["current_phase"] !== "complete") {
26375
+ if (session && session["workflow_type"] === "map" && (session["current_phase"] !== "complete" || session["phase_number"] < 6)) {
26251
26376
  this.db.run(
26252
26377
  `UPDATE sessions SET current_phase = 'complete', phase_number = 6, status = 'closed', updated_at = datetime('now')
26253
26378
  WHERE id = ?`,
@@ -26282,7 +26407,7 @@ var FilesystemSync = class {
26282
26407
  const roundId = roundRow?.["id"];
26283
26408
  if (!roundId) return;
26284
26409
  if (roundRow?.["source"] === "orchestrator") {
26285
- const content2 = readFileSync5(filePath, "utf-8");
26410
+ const content2 = readFileSync6(filePath, "utf-8");
26286
26411
  const action2 = this.upsertMarkdownArtifact(sessionId, "reviewer-output", filePath, content2, roundNumber);
26287
26412
  this.emitArtifactEvent(action2, {
26288
26413
  sessionId,
@@ -26301,7 +26426,7 @@ var FilesystemSync = class {
26301
26426
  [roundId, reviewerType, instanceNumber]
26302
26427
  );
26303
26428
  if (existingOutput && this.shouldSkip(filePath, existingOutput["parsed_at"] ?? null)) return;
26304
- const content = readFileSync5(filePath, "utf-8");
26429
+ const content = readFileSync6(filePath, "utf-8");
26305
26430
  const parsed = parseReviewerOutput(content);
26306
26431
  this.db.run(
26307
26432
  `INSERT OR REPLACE INTO reviewer_outputs (round_id, reviewer_type, instance_number, file_path, finding_count, parsed_at)
@@ -26389,7 +26514,7 @@ var FilesystemSync = class {
26389
26514
  if (existingRound?.["source"] === "orchestrator" && this.shouldSkip(filePath, existingRound["parsed_at"] ?? null)) return;
26390
26515
  let raw;
26391
26516
  try {
26392
- raw = JSON.parse(readFileSync5(filePath, "utf-8"));
26517
+ raw = JSON.parse(readFileSync6(filePath, "utf-8"));
26393
26518
  } catch {
26394
26519
  console.error(`[FilesystemSync] Failed to parse ${filePath}`);
26395
26520
  return;
@@ -26439,7 +26564,7 @@ var FilesystemSync = class {
26439
26564
  const reviewerType = reviewer.type ?? "unknown";
26440
26565
  const instanceNumber = reviewer.instance ?? 1;
26441
26566
  const findings = reviewer.findings ?? [];
26442
- const reviewerMdPath = join9(roundDir, "reviews", `${reviewerType}-${instanceNumber}.md`);
26567
+ const reviewerMdPath = join10(roundDir, "reviews", `${reviewerType}-${instanceNumber}.md`);
26443
26568
  this.db.run(
26444
26569
  `INSERT OR REPLACE INTO reviewer_outputs (round_id, reviewer_type, instance_number, file_path, finding_count, parsed_at)
26445
26570
  VALUES (?, ?, ?, ?, ?, ?)`,
@@ -26537,7 +26662,7 @@ var FilesystemSync = class {
26537
26662
  if (existingRun?.["source"] === "orchestrator" && this.shouldSkip(filePath, existingRun["parsed_at"] ?? null)) return;
26538
26663
  let raw;
26539
26664
  try {
26540
- raw = JSON.parse(readFileSync5(filePath, "utf-8"));
26665
+ raw = JSON.parse(readFileSync6(filePath, "utf-8"));
26541
26666
  } catch {
26542
26667
  console.error(`[FilesystemSync] Failed to parse ${filePath}`);
26543
26668
  return;
@@ -26665,7 +26790,7 @@ var FilesystemSync = class {
26665
26790
  );
26666
26791
  const isOrchestratorSource = existingRound?.["source"] === "orchestrator";
26667
26792
  if (!isOrchestratorSource && existingRound && this.shouldSkip(filePath, existingRound["parsed_at"] ?? null)) return;
26668
- const content = readFileSync5(filePath, "utf-8");
26793
+ const content = readFileSync6(filePath, "utf-8");
26669
26794
  if (isOrchestratorSource) {
26670
26795
  this.db.run(
26671
26796
  `UPDATE review_rounds SET final_md_path = ?, parsed_at = ?
@@ -26708,7 +26833,7 @@ var FilesystemSync = class {
26708
26833
  "SELECT current_phase, phase_number, status FROM sessions WHERE id = ?",
26709
26834
  [sessionId]
26710
26835
  );
26711
- if (session && session["current_phase"] !== "complete") {
26836
+ if (session && (session["current_phase"] !== "complete" || session["phase_number"] < 8)) {
26712
26837
  this.db.run(
26713
26838
  `UPDATE sessions SET current_phase = 'complete', phase_number = 8, status = 'closed', updated_at = datetime('now')
26714
26839
  WHERE id = ?`,
@@ -26738,7 +26863,7 @@ var FilesystemSync = class {
26738
26863
  [sessionId, artifactType, relPath]
26739
26864
  );
26740
26865
  if (existing && this.shouldSkip(filePath, existing["parsed_at"] ?? null)) return;
26741
- const content = readFileSync5(filePath, "utf-8");
26866
+ const content = readFileSync6(filePath, "utf-8");
26742
26867
  const action = this.upsertMarkdownArtifact(sessionId, artifactType, filePath, content, roundNumber);
26743
26868
  this.emitArtifactEvent(action, {
26744
26869
  sessionId,
@@ -26750,7 +26875,7 @@ var FilesystemSync = class {
26750
26875
  // ── 6.6: Chokidar Watcher ──
26751
26876
  startWatching() {
26752
26877
  if (this.watcher) return;
26753
- this.watcher = watch(this.sessionsDir, {
26878
+ this.watcher = watch2(this.sessionsDir, {
26754
26879
  persistent: true,
26755
26880
  ignoreInitial: true,
26756
26881
  depth: 10,
@@ -26797,7 +26922,7 @@ var FilesystemSync = class {
26797
26922
  const parts = relFromSessions.split("/");
26798
26923
  const sessionId = parts[0];
26799
26924
  if (!sessionId) return;
26800
- const sessionDir = join9(this.sessionsDir, sessionId);
26925
+ const sessionDir = join10(this.sessionsDir, sessionId);
26801
26926
  this.ensureSessionRow(sessionId, sessionDir);
26802
26927
  const fileName = basename2(filePath);
26803
26928
  const reviewerMatch = relFromSessions.match(/rounds\/round-(\d+)\/reviews\/(.+\.md)$/);
@@ -26869,8 +26994,8 @@ var FilesystemSync = class {
26869
26994
  };
26870
26995
 
26871
26996
  // src/server/services/db-sync-watcher.ts
26872
- import { existsSync as existsSync5, readFileSync as readFileSync6, statSync as statSync2 } from "node:fs";
26873
- import { watch as watch2 } from "chokidar";
26997
+ import { existsSync as existsSync6, readFileSync as readFileSync7, statSync as statSync2 } from "node:fs";
26998
+ import { watch as watch3 } from "chokidar";
26874
26999
  import initSqlJs3 from "sql.js";
26875
27000
  function col(row, key) {
26876
27001
  return row[key] ?? null;
@@ -26891,7 +27016,7 @@ var DbSyncWatcher = class {
26891
27016
  * Initialize the WASM runtime (called once at startup).
26892
27017
  */
26893
27018
  async init() {
26894
- const wasmBuffer = readFileSync6(locateWasm());
27019
+ const wasmBuffer = readFileSync7(locateWasm());
26895
27020
  this.wasmBinary = wasmBuffer.buffer.slice(
26896
27021
  wasmBuffer.byteOffset,
26897
27022
  wasmBuffer.byteOffset + wasmBuffer.byteLength
@@ -26902,12 +27027,12 @@ var DbSyncWatcher = class {
26902
27027
  * Start watching the DB file for external changes.
26903
27028
  */
26904
27029
  startWatching() {
26905
- if (!existsSync5(this.dbFilePath)) return;
27030
+ if (!existsSync6(this.dbFilePath)) return;
26906
27031
  try {
26907
27032
  this.lastMtime = statSync2(this.dbFilePath).mtimeMs;
26908
27033
  } catch {
26909
27034
  }
26910
- this.watcher = watch2(this.dbFilePath, {
27035
+ this.watcher = watch3(this.dbFilePath, {
26911
27036
  // Also watch WAL/SHM files that SQLite may create
26912
27037
  persistent: true,
26913
27038
  ignoreInitial: true,
@@ -26953,7 +27078,7 @@ var DbSyncWatcher = class {
26953
27078
  * to avoid overwriting CLI changes.
26954
27079
  */
26955
27080
  syncFromDisk() {
26956
- if (!this.SQL || !existsSync5(this.dbFilePath)) return;
27081
+ if (!this.SQL || !existsSync6(this.dbFilePath)) return;
26957
27082
  let currentMtime;
26958
27083
  try {
26959
27084
  currentMtime = statSync2(this.dbFilePath).mtimeMs;
@@ -26964,7 +27089,7 @@ var DbSyncWatcher = class {
26964
27089
  this.lastMtime = currentMtime;
26965
27090
  let diskDb = null;
26966
27091
  try {
26967
- const fileBuffer = readFileSync6(this.dbFilePath);
27092
+ const fileBuffer = readFileSync7(this.dbFilePath);
26968
27093
  diskDb = new this.SQL.Database(fileBuffer);
26969
27094
  this.syncSessions(diskDb);
26970
27095
  this.syncEvents(diskDb);
@@ -27221,17 +27346,17 @@ var DbSyncWatcher = class {
27221
27346
  import { dirname as dirname8 } from "node:path";
27222
27347
 
27223
27348
  // src/server/services/chat-context.ts
27224
- import { readFileSync as readFileSync7, readdirSync as readdirSync2, existsSync as existsSync6 } from "node:fs";
27225
- import { join as join10 } from "node:path";
27349
+ import { readFileSync as readFileSync8, readdirSync as readdirSync2, existsSync as existsSync7 } from "node:fs";
27350
+ import { join as join11 } from "node:path";
27226
27351
  function buildChatContext(ocrDir, target) {
27227
- const sessionsDir = join10(ocrDir, "sessions");
27352
+ const sessionsDir = join11(ocrDir, "sessions");
27228
27353
  if (target.type === "map_run") {
27229
27354
  return buildMapRunContext(sessionsDir, target.sessionId, target.runNumber);
27230
27355
  }
27231
27356
  return buildReviewRoundContext(sessionsDir, target.sessionId, target.roundNumber);
27232
27357
  }
27233
27358
  function buildMapRunContext(sessionsDir, sessionId, runNumber) {
27234
- const mapPath = join10(
27359
+ const mapPath = join11(
27235
27360
  sessionsDir,
27236
27361
  sessionId,
27237
27362
  "map",
@@ -27245,8 +27370,8 @@ function buildMapRunContext(sessionsDir, sessionId, runNumber) {
27245
27370
  "",
27246
27371
  `Below is the Code Review Map that organizes the changeset into reviewable sections:`
27247
27372
  ];
27248
- if (existsSync6(mapPath)) {
27249
- const content = readFileSync7(mapPath, "utf-8");
27373
+ if (existsSync7(mapPath)) {
27374
+ const content = readFileSync8(mapPath, "utf-8");
27250
27375
  parts.push("");
27251
27376
  parts.push("<map>");
27252
27377
  parts.push(content);
@@ -27258,26 +27383,26 @@ function buildMapRunContext(sessionsDir, sessionId, runNumber) {
27258
27383
  return parts.join("\n");
27259
27384
  }
27260
27385
  function buildReviewRoundContext(sessionsDir, sessionId, roundNumber) {
27261
- const roundDir = join10(sessionsDir, sessionId, "rounds", `round-${roundNumber}`);
27262
- const finalPath = join10(roundDir, "final.md");
27263
- const reviewersDir = join10(roundDir, "reviews");
27386
+ const roundDir = join11(sessionsDir, sessionId, "rounds", `round-${roundNumber}`);
27387
+ const finalPath = join11(roundDir, "final.md");
27388
+ const reviewersDir = join11(roundDir, "reviews");
27264
27389
  const parts = [
27265
27390
  `You are an expert code reviewer assisting with a code review session.`,
27266
27391
  `You are looking at review round #${roundNumber} for session "${sessionId}".`,
27267
27392
  "",
27268
27393
  `Below are the review artifacts for this round:`
27269
27394
  ];
27270
- if (existsSync6(finalPath)) {
27271
- const content = readFileSync7(finalPath, "utf-8");
27395
+ if (existsSync7(finalPath)) {
27396
+ const content = readFileSync8(finalPath, "utf-8");
27272
27397
  parts.push("");
27273
27398
  parts.push("<final-synthesis>");
27274
27399
  parts.push(content);
27275
27400
  parts.push("</final-synthesis>");
27276
27401
  }
27277
- if (existsSync6(reviewersDir)) {
27402
+ if (existsSync7(reviewersDir)) {
27278
27403
  const files = readdirSync2(reviewersDir).filter((f) => f.endsWith(".md")).sort();
27279
27404
  for (const file of files) {
27280
- const content = readFileSync7(join10(reviewersDir, file), "utf-8");
27405
+ const content = readFileSync8(join11(reviewersDir, file), "utf-8");
27281
27406
  const reviewerName = file.replace(/\.md$/, "");
27282
27407
  parts.push("");
27283
27408
  parts.push(`<reviewer name="${reviewerName}">`);
@@ -27285,7 +27410,7 @@ function buildReviewRoundContext(sessionsDir, sessionId, roundNumber) {
27285
27410
  parts.push("</reviewer>");
27286
27411
  }
27287
27412
  }
27288
- if (!existsSync6(finalPath) && !existsSync6(reviewersDir)) {
27413
+ if (!existsSync7(finalPath) && !existsSync7(reviewersDir)) {
27289
27414
  parts.push("");
27290
27415
  parts.push("(No review artifacts found on disk for this round.)");
27291
27416
  }
@@ -27590,15 +27715,15 @@ function cleanupAllChats() {
27590
27715
 
27591
27716
  // src/server/socket/post-handler.ts
27592
27717
  import { execFile } from "node:child_process";
27593
- import { existsSync as existsSync7, mkdirSync as mkdirSync2, readFileSync as readFileSync8, unlinkSync, writeFileSync as writeFileSync3 } from "node:fs";
27718
+ import { existsSync as existsSync8, mkdirSync as mkdirSync2, readFileSync as readFileSync9, unlinkSync, writeFileSync as writeFileSync3 } from "node:fs";
27594
27719
  import { tmpdir as tmpdir2 } from "node:os";
27595
- import { join as join11, dirname as dirname9, isAbsolute } from "node:path";
27720
+ import { join as join12, dirname as dirname9, isAbsolute } from "node:path";
27596
27721
  import { randomUUID } from "node:crypto";
27597
27722
  import { promisify } from "node:util";
27598
27723
  var execFileAsync = promisify(execFile);
27599
27724
  function resolveSessionDir(sessionDir, ocrDir) {
27600
27725
  if (isAbsolute(sessionDir)) return sessionDir;
27601
- return join11(dirname9(ocrDir), sessionDir);
27726
+ return join12(dirname9(ocrDir), sessionDir);
27602
27727
  }
27603
27728
  var BRANCH_PREFIXES = [
27604
27729
  "feat",
@@ -27768,19 +27893,19 @@ function registerPostHandlers(io2, socket, db, ocrDir, aiCliService) {
27768
27893
  socket.emit("post:error", { error: "Session not found" });
27769
27894
  return;
27770
27895
  }
27771
- const sessionDir = session.session_dir ? resolveSessionDir(session.session_dir, ocrDir) : join11(ocrDir, "sessions", sessionId);
27772
- const roundDir = join11(sessionDir, "rounds", `round-${roundNumber}`);
27773
- const finalPath = join11(roundDir, "final.md");
27774
- if (!existsSync7(finalPath)) {
27896
+ const sessionDir = session.session_dir ? resolveSessionDir(session.session_dir, ocrDir) : join12(ocrDir, "sessions", sessionId);
27897
+ const roundDir = join12(sessionDir, "rounds", `round-${roundNumber}`);
27898
+ const finalPath = join12(roundDir, "final.md");
27899
+ if (!existsSync8(finalPath)) {
27775
27900
  socket.emit("post:error", { error: "final.md not found for this round" });
27776
27901
  return;
27777
27902
  }
27778
- const humanReviewPath = join11(roundDir, "final-human.md");
27903
+ const humanReviewPath = join12(roundDir, "final-human.md");
27779
27904
  const repoRoot = dirname9(ocrDir);
27780
- const commandMdPath = join11(ocrDir, "commands", "translate-review-to-single-human.md");
27905
+ const commandMdPath = join12(ocrDir, "commands", "translate-review-to-single-human.md");
27781
27906
  let commandContent;
27782
27907
  try {
27783
- commandContent = readFileSync8(commandMdPath, "utf-8");
27908
+ commandContent = readFileSync9(commandMdPath, "utf-8");
27784
27909
  } catch {
27785
27910
  socket.emit("post:error", {
27786
27911
  error: `Command file not found: ${commandMdPath}. Run \`ocr init\` to set up.`
@@ -27856,9 +27981,9 @@ function registerPostHandlers(io2, socket, db, ocrDir, aiCliService) {
27856
27981
  }
27857
27982
  }
27858
27983
  let generatedContent = "";
27859
- if (existsSync7(humanReviewPath)) {
27984
+ if (existsSync8(humanReviewPath)) {
27860
27985
  try {
27861
- generatedContent = readFileSync8(humanReviewPath, "utf-8").trim();
27986
+ generatedContent = readFileSync9(humanReviewPath, "utf-8").trim();
27862
27987
  } catch {
27863
27988
  }
27864
27989
  }
@@ -27933,10 +28058,10 @@ function registerPostHandlers(io2, socket, db, ocrDir, aiCliService) {
27933
28058
  socket.emit("post:save-result", { success: false, error: "Session not found" });
27934
28059
  return;
27935
28060
  }
27936
- const sessionDir = session.session_dir ? resolveSessionDir(session.session_dir, ocrDir) : join11(ocrDir, "sessions", sessionId);
27937
- const roundDir = join11(sessionDir, "rounds", `round-${roundNumber}`);
28061
+ const sessionDir = session.session_dir ? resolveSessionDir(session.session_dir, ocrDir) : join12(ocrDir, "sessions", sessionId);
28062
+ const roundDir = join12(sessionDir, "rounds", `round-${roundNumber}`);
27938
28063
  mkdirSync2(roundDir, { recursive: true });
27939
- const filePath = join11(roundDir, "final-human.md");
28064
+ const filePath = join12(roundDir, "final-human.md");
27940
28065
  writeFileSync3(filePath, content, { mode: 420 });
27941
28066
  saveDb(db, ocrDir);
27942
28067
  socket.emit("post:save-result", { success: true });
@@ -27964,12 +28089,12 @@ function registerPostHandlers(io2, socket, db, ocrDir, aiCliService) {
27964
28089
  );
27965
28090
  tracker.appendOutput(`\u25B8 Posting review to PR #${prNumber}...
27966
28091
  `);
27967
- const tmpDir = join11(tmpdir2(), "ocr-post-comments");
28092
+ const tmpDir = join12(tmpdir2(), "ocr-post-comments");
27968
28093
  try {
27969
28094
  mkdirSync2(tmpDir, { recursive: true, mode: 448 });
27970
28095
  } catch {
27971
28096
  }
27972
- const tmpFile = join11(tmpDir, `${randomUUID()}.md`);
28097
+ const tmpFile = join12(tmpDir, `${randomUUID()}.md`);
27973
28098
  writeFileSync3(tmpFile, content, { mode: 384 });
27974
28099
  const repoRoot = dirname9(ocrDir);
27975
28100
  try {
@@ -28015,9 +28140,14 @@ function cleanupAllPostGenerations() {
28015
28140
  }
28016
28141
 
28017
28142
  // src/server/index.ts
28143
+ import { homedir } from "node:os";
28018
28144
  var __dirname3 = dirname10(fileURLToPath3(import.meta.url));
28145
+ function shortenPath(p) {
28146
+ const home = homedir();
28147
+ return p.startsWith(home) ? "~" + p.slice(home.length) : p;
28148
+ }
28019
28149
  var AUTH_TOKEN = randomBytes(32).toString("hex");
28020
- var app = (0, import_express11.default)();
28150
+ var app = (0, import_express12.default)();
28021
28151
  var httpServer = createServer(app);
28022
28152
  var io = new SocketIOServer(httpServer, {
28023
28153
  cors: {
@@ -28026,7 +28156,7 @@ var io = new SocketIOServer(httpServer, {
28026
28156
  maxHttpBufferSize: 1e6
28027
28157
  // 1 MB — explicit default; review if large payloads are needed
28028
28158
  });
28029
- app.use(import_express11.default.json());
28159
+ app.use(import_express12.default.json());
28030
28160
  if (process.env.NODE_ENV !== "production") {
28031
28161
  app.use((_req, res, next) => {
28032
28162
  const origin = _req.headers.origin;
@@ -28078,12 +28208,12 @@ async function startServer(options = {}) {
28078
28208
  const ocrDir = resolveOcrDir();
28079
28209
  const aiCliService = new AiCliService(ocrDir);
28080
28210
  const db = await openDb(ocrDir);
28081
- const dataDir = join12(ocrDir, "data");
28082
- const pidFilePath = join12(dataDir, "dashboard.pid");
28211
+ const dataDir = join13(ocrDir, "data");
28212
+ const pidFilePath = join13(dataDir, "dashboard.pid");
28083
28213
  mkdirSync3(dataDir, { recursive: true });
28084
- if (existsSync8(pidFilePath)) {
28214
+ if (existsSync9(pidFilePath)) {
28085
28215
  try {
28086
- const oldPid = parseInt(readFileSync9(pidFilePath, "utf-8").trim(), 10);
28216
+ const oldPid = parseInt(readFileSync10(pidFilePath, "utf-8").trim(), 10);
28087
28217
  if (!isNaN(oldPid)) {
28088
28218
  try {
28089
28219
  process.kill(oldPid, 0);
@@ -28123,7 +28253,6 @@ async function startServer(options = {}) {
28123
28253
  process.kill(pid, "SIGTERM");
28124
28254
  }
28125
28255
  killedCount++;
28126
- console.log(`Killed orphaned process (PID ${pid})`);
28127
28256
  setTimeout(() => {
28128
28257
  try {
28129
28258
  process.kill(pid, 0);
@@ -28141,7 +28270,7 @@ async function startServer(options = {}) {
28141
28270
  }
28142
28271
  }
28143
28272
  if (killedCount > 0) {
28144
- console.log(`Killed ${killedCount} orphaned child process(es)`);
28273
+ console.log(` Cleaned up ${killedCount} orphaned process(es)`);
28145
28274
  }
28146
28275
  }
28147
28276
  const staleResult = db.exec(
@@ -28158,7 +28287,7 @@ async function startServer(options = {}) {
28158
28287
  WHERE finished_at IS NULL OR exit_code IS NULL`
28159
28288
  );
28160
28289
  saveDb(db, ocrDir);
28161
- console.log(`Cleaned up ${staleCount} stale command execution(s)`);
28290
+ console.log(` Cleaned up ${staleCount} stale command(s)`);
28162
28291
  }
28163
28292
  app.get("/api/reviews", (_req, res) => {
28164
28293
  try {
@@ -28183,11 +28312,12 @@ async function startServer(options = {}) {
28183
28312
  app.use("/api/commands", createCommandsRouter(db));
28184
28313
  app.use("/api/config", createConfigRouter(ocrDir, aiCliService));
28185
28314
  app.use("/api/sessions", createChatRouter(db, ocrDir));
28186
- const clientDir = join12(__dirname3, "client");
28187
- if (process.env.NODE_ENV === "production" && existsSync8(clientDir)) {
28188
- app.use(import_express11.default.static(clientDir, { index: false }));
28189
- const indexHtmlPath = join12(clientDir, "index.html");
28190
- const rawIndexHtml = existsSync8(indexHtmlPath) ? readFileSync9(indexHtmlPath, "utf-8") : "";
28315
+ app.use("/api/reviewers", createReviewersRouter(ocrDir));
28316
+ const clientDir = join13(__dirname3, "client");
28317
+ if (process.env.NODE_ENV === "production" && existsSync9(clientDir)) {
28318
+ app.use(import_express12.default.static(clientDir, { index: false }));
28319
+ const indexHtmlPath = join13(clientDir, "index.html");
28320
+ const rawIndexHtml = existsSync9(indexHtmlPath) ? readFileSync10(indexHtmlPath, "utf-8") : "";
28191
28321
  const tokenScript = `<script>window.__OCR_TOKEN__=${JSON.stringify(AUTH_TOKEN)};</script>`;
28192
28322
  const injectedIndexHtml = rawIndexHtml.replace(
28193
28323
  "</head>",
@@ -28201,32 +28331,29 @@ async function startServer(options = {}) {
28201
28331
  });
28202
28332
  }
28203
28333
  io.on("connection", (socket) => {
28204
- console.log("Client connected:", socket.id);
28205
28334
  registerSocketHandlers(io, socket);
28206
28335
  registerCommandHandlers(io, socket, db, ocrDir, aiCliService);
28207
28336
  registerChatHandlers(io, socket, db, ocrDir, aiCliService);
28208
28337
  registerPostHandlers(io, socket, db, ocrDir, aiCliService);
28209
- socket.on("disconnect", () => {
28210
- console.log("Client disconnected:", socket.id);
28211
- });
28212
28338
  });
28213
- const dbFilePath = join12(ocrDir, "data", "ocr.db");
28339
+ const dbFilePath = join13(ocrDir, "data", "ocr.db");
28214
28340
  const dbSyncWatcher = new DbSyncWatcher(db, dbFilePath, io, () => {
28215
28341
  saveDb(db, ocrDir);
28216
28342
  });
28217
28343
  await dbSyncWatcher.init();
28218
28344
  dbSyncWatcher.startWatching();
28219
- console.log(`Watching DB: ${dbFilePath}`);
28345
+ console.log(` Watching DB: ${shortenPath(dbFilePath)}`);
28220
28346
  registerSaveHooks(
28221
28347
  () => dbSyncWatcher.syncFromDisk(),
28222
28348
  () => dbSyncWatcher.markOwnWrite()
28223
28349
  );
28224
- const sessionsDir = join12(ocrDir, "sessions");
28350
+ const sessionsDir = join13(ocrDir, "sessions");
28225
28351
  const fsSync = new FilesystemSync(db, sessionsDir, io, () => saveDb(db, ocrDir));
28226
28352
  await fsSync.fullScan();
28227
28353
  saveDb(db, ocrDir);
28228
28354
  fsSync.startWatching();
28229
- console.log(`Watching sessions: ${sessionsDir}`);
28355
+ console.log(` Watching sessions: ${shortenPath(sessionsDir)}`);
28356
+ const stopReviewersWatch = watchReviewersMeta(ocrDir, io);
28230
28357
  await new Promise((resolve3, reject) => {
28231
28358
  httpServer.once("error", (err) => {
28232
28359
  if (err.code === "EADDRINUSE") {
@@ -28238,14 +28365,11 @@ async function startServer(options = {}) {
28238
28365
  }
28239
28366
  });
28240
28367
  httpServer.listen(port, "127.0.0.1", () => {
28241
- console.log(`Dashboard server running on http://localhost:${port}`);
28242
- console.log(`OCR directory: ${ocrDir}`);
28243
- console.log("");
28244
- console.log("=".repeat(60));
28245
- console.log(" AUTH TOKEN (bearer token for API / Socket.IO):");
28246
- console.log(` ${AUTH_TOKEN.slice(0, 8)}...[redacted]`);
28247
- console.log("=".repeat(60));
28248
- console.log("");
28368
+ console.log(` Server: http://localhost:${port}`);
28369
+ console.log(` OCR directory: ${shortenPath(ocrDir)}`);
28370
+ console.log();
28371
+ console.log(` Auth token: ${AUTH_TOKEN.slice(0, 8)}...[redacted]`);
28372
+ console.log();
28249
28373
  resolve3();
28250
28374
  });
28251
28375
  });
@@ -28310,6 +28434,7 @@ async function startServer(options = {}) {
28310
28434
  }
28311
28435
  dbSyncWatcher.stopWatching();
28312
28436
  fsSync.stopWatching();
28437
+ stopReviewersWatch();
28313
28438
  io.close();
28314
28439
  httpServer.close(() => {
28315
28440
  try {