@open-code-review/cli 1.6.0 → 1.7.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 +292 -162
  51. package/dist/index.js +295 -8
  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;
@@ -25190,11 +25190,37 @@ function resolveLocalCli() {
25190
25190
  }
25191
25191
 
25192
25192
  // src/server/socket/command-runner.ts
25193
+ function shellSplit(str) {
25194
+ const tokens = [];
25195
+ let current = "";
25196
+ let quote = null;
25197
+ for (let i = 0; i < str.length; i++) {
25198
+ const ch = str[i];
25199
+ if (quote) {
25200
+ if (ch === quote) {
25201
+ quote = null;
25202
+ } else {
25203
+ current += ch;
25204
+ }
25205
+ } else if (ch === '"' || ch === "'") {
25206
+ quote = ch;
25207
+ } else if (/\s/.test(ch)) {
25208
+ if (current) {
25209
+ tokens.push(current);
25210
+ current = "";
25211
+ }
25212
+ } else {
25213
+ current += ch;
25214
+ }
25215
+ }
25216
+ if (current) tokens.push(current);
25217
+ return tokens;
25218
+ }
25193
25219
  var ALLOWED_COMMANDS = /* @__PURE__ */ new Set([
25194
25220
  "progress",
25195
25221
  "state"
25196
25222
  ]);
25197
- var AI_COMMANDS = /* @__PURE__ */ new Set(["map", "review", "translate-review-to-single-human", "address"]);
25223
+ var AI_COMMANDS = /* @__PURE__ */ new Set(["map", "review", "translate-review-to-single-human", "address", "create-reviewer", "sync-reviewers"]);
25198
25224
  var MAX_CONCURRENT = 3;
25199
25225
  var activeCommands = /* @__PURE__ */ new Map();
25200
25226
  function getActiveCommands() {
@@ -25216,7 +25242,7 @@ function registerCommandHandlers(io2, socket, db, ocrDir, aiCliService) {
25216
25242
  }
25217
25243
  const { command } = payload;
25218
25244
  const normalized = command.replace(/^ocr\s+/, "");
25219
- const parts = normalized.split(/\s+/);
25245
+ const parts = shellSplit(normalized);
25220
25246
  const baseCommand = parts[0] ?? "";
25221
25247
  const subArgs = parts.slice(1);
25222
25248
  if (!ALLOWED_COMMANDS.has(baseCommand) && !AI_COMMANDS.has(baseCommand)) {
@@ -25373,34 +25399,68 @@ function spawnAiCommand(io2, _socket, db, ocrDir, executionId, baseCommand, subA
25373
25399
  finishExecution(io2, db, ocrDir, executionId, 1, content);
25374
25400
  return;
25375
25401
  }
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++;
25402
+ const promptLines = [];
25403
+ if (baseCommand === "create-reviewer" || baseCommand === "sync-reviewers") {
25404
+ const argsStr = subArgs.length > 0 ? subArgs.join(" ") : "";
25405
+ promptLines.push(
25406
+ `Follow the instructions below to run the OCR ${baseCommand} workflow.`,
25407
+ "",
25408
+ `Arguments: ${argsStr || "none"}`
25409
+ );
25410
+ } else {
25411
+ let target = "staged changes";
25412
+ let requirements = "";
25413
+ let team = "";
25414
+ const reviewerDescriptions = [];
25415
+ const options = [];
25416
+ let i = 0;
25417
+ while (i < subArgs.length) {
25418
+ const arg = subArgs[i] ?? "";
25419
+ if (arg === "--fresh") {
25420
+ options.push("--fresh");
25421
+ i++;
25422
+ } else if (arg === "--requirements" && i + 1 < subArgs.length) {
25423
+ requirements = subArgs.slice(i + 1).join(" ");
25424
+ break;
25425
+ } else if (arg === "--team" && i + 1 < subArgs.length) {
25426
+ team = subArgs[i + 1] ?? "";
25427
+ i += 2;
25428
+ } else if (arg === "--reviewer" && i + 1 < subArgs.length) {
25429
+ const raw = subArgs[i + 1] ?? "";
25430
+ const countMatch = raw.match(/^(\d+):(.+)$/);
25431
+ if (countMatch) {
25432
+ reviewerDescriptions.push({ description: countMatch[2], count: parseInt(countMatch[1], 10) });
25433
+ } else {
25434
+ reviewerDescriptions.push({ description: raw, count: 1 });
25435
+ }
25436
+ i += 2;
25437
+ } else if (!arg.startsWith("--")) {
25438
+ target = arg;
25439
+ i++;
25440
+ } else {
25441
+ i++;
25442
+ }
25443
+ }
25444
+ const optionsStr = options.length > 0 ? options.join(" ") : "none";
25445
+ promptLines.push(
25446
+ `Follow the instructions below to run the OCR ${baseCommand} workflow.`,
25447
+ "",
25448
+ `Target: ${target}`,
25449
+ `Options: ${optionsStr}`
25450
+ );
25451
+ if (team) {
25452
+ promptLines.push(`Team: ${team}`);
25453
+ }
25454
+ for (const { description, count } of reviewerDescriptions) {
25455
+ if (count > 1) {
25456
+ promptLines.push(`Reviewer (x${count}): ${description}`);
25457
+ } else {
25458
+ promptLines.push(`Reviewer: ${description}`);
25459
+ }
25460
+ }
25461
+ if (requirements) {
25462
+ promptLines.push(`Requirements: ${requirements}`);
25393
25463
  }
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
25464
  }
25405
25465
  const localCli = resolveLocalCli();
25406
25466
  if (localCli) {
@@ -25768,10 +25828,77 @@ function createChatRouter(db, ocrDir) {
25768
25828
  return router;
25769
25829
  }
25770
25830
 
25831
+ // src/server/routes/reviewers.ts
25832
+ var import_express11 = __toESM(require_express2(), 1);
25833
+ import { readFileSync as readFileSync5, existsSync as existsSync4, watch } from "node:fs";
25834
+ import { join as join9 } from "node:path";
25835
+ function readReviewersMeta(ocrDir) {
25836
+ const metaPath = join9(ocrDir, "reviewers-meta.json");
25837
+ if (!existsSync4(metaPath)) {
25838
+ return { reviewers: [], defaults: [] };
25839
+ }
25840
+ try {
25841
+ const raw = readFileSync5(metaPath, "utf-8");
25842
+ const meta = JSON.parse(raw);
25843
+ const reviewers = meta.reviewers ?? [];
25844
+ const defaults = reviewers.filter((r) => r.is_default).map((r) => r.id);
25845
+ return { reviewers, defaults };
25846
+ } catch {
25847
+ return { reviewers: [], defaults: [] };
25848
+ }
25849
+ }
25850
+ var VALID_ID_RE = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
25851
+ function createReviewersRouter(ocrDir) {
25852
+ const router = (0, import_express11.Router)();
25853
+ router.get("/", (_req, res) => {
25854
+ res.json(readReviewersMeta(ocrDir));
25855
+ });
25856
+ router.get("/:id/prompt", (req, res) => {
25857
+ const { id } = req.params;
25858
+ if (!id || !VALID_ID_RE.test(id)) {
25859
+ res.status(400).json({ error: "Invalid reviewer ID" });
25860
+ return;
25861
+ }
25862
+ const filePath = join9(ocrDir, "skills", "references", "reviewers", `${id}.md`);
25863
+ if (!existsSync4(filePath)) {
25864
+ res.status(404).json({ error: "Reviewer not found", id });
25865
+ return;
25866
+ }
25867
+ try {
25868
+ const content = readFileSync5(filePath, "utf-8");
25869
+ res.json({ id, content });
25870
+ } catch {
25871
+ res.status(500).json({ error: "Failed to read reviewer file", id });
25872
+ }
25873
+ });
25874
+ return router;
25875
+ }
25876
+ function watchReviewersMeta(ocrDir, io2) {
25877
+ const metaPath = join9(ocrDir, "reviewers-meta.json");
25878
+ let watcher = null;
25879
+ let debounce;
25880
+ try {
25881
+ watcher = watch(metaPath, () => {
25882
+ clearTimeout(debounce);
25883
+ debounce = setTimeout(() => {
25884
+ const data = readReviewersMeta(ocrDir);
25885
+ io2.emit("reviewers:updated", data);
25886
+ }, 200);
25887
+ });
25888
+ watcher.on("error", () => {
25889
+ });
25890
+ } catch {
25891
+ }
25892
+ return () => {
25893
+ clearTimeout(debounce);
25894
+ watcher?.close();
25895
+ };
25896
+ }
25897
+
25771
25898
  // 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";
25899
+ import { readdirSync, readFileSync as readFileSync6, statSync, existsSync as existsSync5 } from "node:fs";
25900
+ import { join as join10, basename as basename2, dirname as dirname7, relative } from "node:path";
25901
+ import { watch as watch2 } from "chokidar";
25775
25902
 
25776
25903
  // src/server/services/parsers/reviewer-parser.ts
25777
25904
  var FINDING_HEADING_RE = /^#{2,3}\s+(?:Finding|Issue|Suggestion)\s*(?:\d+)?\s*[:\s]*\s*(.*)/i;
@@ -25952,67 +26079,67 @@ var FilesystemSync = class {
25952
26079
  onSync;
25953
26080
  // ── 6.1: Full Scan ──
25954
26081
  async fullScan() {
25955
- if (!existsSync4(this.sessionsDir)) return;
26082
+ if (!existsSync5(this.sessionsDir)) return;
25956
26083
  const entries = readdirSync(this.sessionsDir, { withFileTypes: true });
25957
26084
  for (const entry of entries) {
25958
26085
  if (!entry.isDirectory()) continue;
25959
26086
  const sessionId = entry.name;
25960
- const sessionDir = join9(this.sessionsDir, sessionId);
26087
+ const sessionDir = join10(this.sessionsDir, sessionId);
25961
26088
  this.syncSession(sessionId, sessionDir);
25962
26089
  }
25963
26090
  }
25964
26091
  syncSession(sessionId, sessionDir) {
25965
26092
  this.ensureSessionRow(sessionId, sessionDir);
25966
- const roundsDir = join9(sessionDir, "rounds");
25967
- if (existsSync4(roundsDir)) {
26093
+ const roundsDir = join10(sessionDir, "rounds");
26094
+ if (existsSync5(roundsDir)) {
25968
26095
  const rounds = readdirSync(roundsDir, { withFileTypes: true });
25969
26096
  for (const roundEntry of rounds) {
25970
26097
  if (!roundEntry.isDirectory()) continue;
25971
26098
  const roundMatch = roundEntry.name.match(/^round-(\d+)$/);
25972
26099
  if (!roundMatch) continue;
25973
26100
  const roundNumber = parseInt(roundMatch[1] ?? "0", 10);
25974
- const roundDir = join9(roundsDir, roundEntry.name);
25975
- const reviewsDir = join9(roundDir, "reviews");
25976
- if (existsSync4(reviewsDir)) {
26101
+ const roundDir = join10(roundsDir, roundEntry.name);
26102
+ const reviewsDir = join10(roundDir, "reviews");
26103
+ if (existsSync5(reviewsDir)) {
25977
26104
  const reviewFiles = readdirSync(reviewsDir).filter((f) => f.endsWith(".md"));
25978
26105
  for (const reviewFile of reviewFiles) {
25979
- const filePath = join9(reviewsDir, reviewFile);
26106
+ const filePath = join10(reviewsDir, reviewFile);
25980
26107
  this.processReviewerOutput(sessionId, roundNumber, filePath, reviewFile);
25981
26108
  }
25982
26109
  }
25983
- const roundMetaPath = join9(roundDir, "round-meta.json");
25984
- if (existsSync4(roundMetaPath)) {
26110
+ const roundMetaPath = join10(roundDir, "round-meta.json");
26111
+ if (existsSync5(roundMetaPath)) {
25985
26112
  this.processRoundMeta(sessionId, roundNumber, roundMetaPath);
25986
26113
  }
25987
- const finalPath = join9(roundDir, "final.md");
25988
- if (existsSync4(finalPath)) {
26114
+ const finalPath = join10(roundDir, "final.md");
26115
+ if (existsSync5(finalPath)) {
25989
26116
  this.processFinalMd(sessionId, roundNumber, finalPath);
25990
26117
  }
25991
- const finalHumanPath = join9(roundDir, "final-human.md");
25992
- if (existsSync4(finalHumanPath)) {
26118
+ const finalHumanPath = join10(roundDir, "final-human.md");
26119
+ if (existsSync5(finalHumanPath)) {
25993
26120
  this.processGenericArtifact(sessionId, "final-human", finalHumanPath, roundNumber);
25994
26121
  }
25995
- const discoursePath = join9(roundDir, "discourse.md");
25996
- if (existsSync4(discoursePath)) {
26122
+ const discoursePath = join10(roundDir, "discourse.md");
26123
+ if (existsSync5(discoursePath)) {
25997
26124
  this.processGenericArtifact(sessionId, "discourse", discoursePath, roundNumber);
25998
26125
  }
25999
26126
  }
26000
26127
  }
26001
- const mapDir = join9(sessionDir, "map", "runs");
26002
- if (existsSync4(mapDir)) {
26128
+ const mapDir = join10(sessionDir, "map", "runs");
26129
+ if (existsSync5(mapDir)) {
26003
26130
  const runs = readdirSync(mapDir, { withFileTypes: true });
26004
26131
  for (const runEntry of runs) {
26005
26132
  if (!runEntry.isDirectory()) continue;
26006
26133
  const runMatch = runEntry.name.match(/^run-(\d+)$/);
26007
26134
  if (!runMatch) continue;
26008
26135
  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)) {
26136
+ const runDir = join10(mapDir, runEntry.name);
26137
+ const mapMetaPath = join10(runDir, "map-meta.json");
26138
+ if (existsSync5(mapMetaPath)) {
26012
26139
  this.processMapMeta(sessionId, runNumber, mapMetaPath);
26013
26140
  }
26014
- const mapPath = join9(runDir, "map.md");
26015
- if (existsSync4(mapPath)) {
26141
+ const mapPath = join10(runDir, "map.md");
26142
+ if (existsSync5(mapPath)) {
26016
26143
  this.processMapMd(sessionId, runNumber, mapPath);
26017
26144
  }
26018
26145
  const mapArtifacts = [
@@ -26021,8 +26148,8 @@ var FilesystemSync = class {
26021
26148
  ["requirements-mapping.md", "requirements-mapping"]
26022
26149
  ];
26023
26150
  for (const [fileName, artifactType] of mapArtifacts) {
26024
- const filePath = join9(runDir, fileName);
26025
- if (existsSync4(filePath)) {
26151
+ const filePath = join10(runDir, fileName);
26152
+ if (existsSync5(filePath)) {
26026
26153
  this.processGenericArtifact(sessionId, artifactType, filePath, void 0, runNumber);
26027
26154
  }
26028
26155
  }
@@ -26033,8 +26160,8 @@ var FilesystemSync = class {
26033
26160
  ["discovered-standards.md", "discovered-standards"]
26034
26161
  ];
26035
26162
  for (const [fileName, artifactType] of sessionArtifacts) {
26036
- const filePath = join9(sessionDir, fileName);
26037
- if (existsSync4(filePath)) {
26163
+ const filePath = join10(sessionDir, fileName);
26164
+ if (existsSync5(filePath)) {
26038
26165
  this.processGenericArtifact(sessionId, artifactType, filePath);
26039
26166
  }
26040
26167
  }
@@ -26043,17 +26170,17 @@ var FilesystemSync = class {
26043
26170
  ensureSessionRow(sessionId, sessionDir) {
26044
26171
  const branchMatch = sessionId.match(/^\d{4}-\d{2}-\d{2}-(.+)$/);
26045
26172
  const branch = branchMatch?.[1] ?? "unknown";
26046
- const hasRoundsDir = existsSync4(join9(sessionDir, "rounds"));
26047
- const hasMapDir = existsSync4(join9(sessionDir, "map"));
26173
+ const hasRoundsDir = existsSync5(join10(sessionDir, "rounds"));
26174
+ const hasMapDir = existsSync5(join10(sessionDir, "map"));
26048
26175
  const workflowType = hasMapDir && !hasRoundsDir ? "map" : "review";
26049
26176
  let currentRound = 1;
26050
26177
  if (hasRoundsDir) {
26051
- const roundDirs = readdirSync(join9(sessionDir, "rounds")).filter((d) => d.match(/^round-\d+$/));
26178
+ const roundDirs = readdirSync(join10(sessionDir, "rounds")).filter((d) => d.match(/^round-\d+$/));
26052
26179
  currentRound = Math.max(1, roundDirs.length);
26053
26180
  }
26054
26181
  let currentMapRun = 1;
26055
- const mapRunsDir = join9(sessionDir, "map", "runs");
26056
- if (existsSync4(mapRunsDir)) {
26182
+ const mapRunsDir = join10(sessionDir, "map", "runs");
26183
+ if (existsSync5(mapRunsDir)) {
26057
26184
  const runDirs = readdirSync(mapRunsDir).filter((d) => d.match(/^run-\d+$/));
26058
26185
  currentMapRun = Math.max(1, runDirs.length);
26059
26186
  }
@@ -26061,40 +26188,40 @@ var FilesystemSync = class {
26061
26188
  let phaseNumber = 1;
26062
26189
  let status = "active";
26063
26190
  if (workflowType === "review" && hasRoundsDir) {
26064
- const roundDir = join9(sessionDir, "rounds", `round-${currentRound}`);
26065
- if (existsSync4(join9(roundDir, "final.md"))) {
26191
+ const roundDir = join10(sessionDir, "rounds", `round-${currentRound}`);
26192
+ if (existsSync5(join10(roundDir, "final.md"))) {
26066
26193
  phase = "complete";
26067
26194
  phaseNumber = 8;
26068
26195
  status = "closed";
26069
- } else if (existsSync4(join9(roundDir, "discourse.md"))) {
26196
+ } else if (existsSync5(join10(roundDir, "discourse.md"))) {
26070
26197
  phase = "synthesis";
26071
26198
  phaseNumber = 7;
26072
- } else if (existsSync4(join9(roundDir, "reviews")) && readdirSync(join9(roundDir, "reviews")).filter((f) => f.endsWith(".md")).length > 0) {
26199
+ } else if (existsSync5(join10(roundDir, "reviews")) && readdirSync(join10(roundDir, "reviews")).filter((f) => f.endsWith(".md")).length > 0) {
26073
26200
  phase = "reviews";
26074
26201
  phaseNumber = 4;
26075
- } else if (existsSync4(join9(sessionDir, "context.md"))) {
26202
+ } else if (existsSync5(join10(sessionDir, "context.md"))) {
26076
26203
  phase = "analysis";
26077
26204
  phaseNumber = 3;
26078
- } else if (existsSync4(join9(sessionDir, "discovered-standards.md"))) {
26205
+ } else if (existsSync5(join10(sessionDir, "discovered-standards.md"))) {
26079
26206
  phase = "change-context";
26080
26207
  phaseNumber = 2;
26081
26208
  }
26082
26209
  } else if (workflowType === "map" && hasMapDir) {
26083
- const runDir = join9(mapRunsDir, `run-${currentMapRun}`);
26084
- if (existsSync4(join9(runDir, "map.md"))) {
26210
+ const runDir = join10(mapRunsDir, `run-${currentMapRun}`);
26211
+ if (existsSync5(join10(runDir, "map.md"))) {
26085
26212
  phase = "complete";
26086
26213
  phaseNumber = 6;
26087
26214
  status = "closed";
26088
- } else if (existsSync4(join9(runDir, "requirements-mapping.md"))) {
26215
+ } else if (existsSync5(join10(runDir, "requirements-mapping.md"))) {
26089
26216
  phase = "synthesis";
26090
26217
  phaseNumber = 5;
26091
- } else if (existsSync4(join9(runDir, "flow-analysis.md"))) {
26218
+ } else if (existsSync5(join10(runDir, "flow-analysis.md"))) {
26092
26219
  phase = "requirements-mapping";
26093
26220
  phaseNumber = 4;
26094
- } else if (existsSync4(join9(runDir, "topology.md"))) {
26221
+ } else if (existsSync5(join10(runDir, "topology.md"))) {
26095
26222
  phase = "flow-analysis";
26096
26223
  phaseNumber = 3;
26097
- } else if (existsSync4(join9(sessionDir, "discovered-standards.md"))) {
26224
+ } else if (existsSync5(join10(sessionDir, "discovered-standards.md"))) {
26098
26225
  phase = "topology";
26099
26226
  phaseNumber = 2;
26100
26227
  }
@@ -26157,12 +26284,12 @@ var FilesystemSync = class {
26157
26284
  );
26158
26285
  if (existingRun && this.shouldSkip(filePath, existingRun["parsed_at"] ?? null)) return;
26159
26286
  if (existingRun?.["source"] === "orchestrator") {
26160
- const content2 = readFileSync5(filePath, "utf-8");
26287
+ const content2 = readFileSync6(filePath, "utf-8");
26161
26288
  const action2 = this.upsertMarkdownArtifact(sessionId, "map", filePath, content2, void 0);
26162
26289
  this.emitArtifactEvent(action2, { sessionId, artifactType: "map", filePath });
26163
26290
  return;
26164
26291
  }
26165
- const content = readFileSync5(filePath, "utf-8");
26292
+ const content = readFileSync6(filePath, "utf-8");
26166
26293
  const parsed = parseMapMd(content);
26167
26294
  this.db.run(
26168
26295
  `INSERT OR REPLACE INTO map_runs (session_id, run_number, file_count, map_md_path, parsed_at, source)
@@ -26244,10 +26371,10 @@ var FilesystemSync = class {
26244
26371
  }
26245
26372
  const session = queryFirst(
26246
26373
  this.db,
26247
- "SELECT current_phase, workflow_type FROM sessions WHERE id = ?",
26374
+ "SELECT current_phase, phase_number, workflow_type FROM sessions WHERE id = ?",
26248
26375
  [sessionId]
26249
26376
  );
26250
- if (session && session["workflow_type"] === "map" && session["current_phase"] !== "complete") {
26377
+ if (session && session["workflow_type"] === "map" && (session["current_phase"] !== "complete" || session["phase_number"] < 6)) {
26251
26378
  this.db.run(
26252
26379
  `UPDATE sessions SET current_phase = 'complete', phase_number = 6, status = 'closed', updated_at = datetime('now')
26253
26380
  WHERE id = ?`,
@@ -26282,7 +26409,7 @@ var FilesystemSync = class {
26282
26409
  const roundId = roundRow?.["id"];
26283
26410
  if (!roundId) return;
26284
26411
  if (roundRow?.["source"] === "orchestrator") {
26285
- const content2 = readFileSync5(filePath, "utf-8");
26412
+ const content2 = readFileSync6(filePath, "utf-8");
26286
26413
  const action2 = this.upsertMarkdownArtifact(sessionId, "reviewer-output", filePath, content2, roundNumber);
26287
26414
  this.emitArtifactEvent(action2, {
26288
26415
  sessionId,
@@ -26301,7 +26428,7 @@ var FilesystemSync = class {
26301
26428
  [roundId, reviewerType, instanceNumber]
26302
26429
  );
26303
26430
  if (existingOutput && this.shouldSkip(filePath, existingOutput["parsed_at"] ?? null)) return;
26304
- const content = readFileSync5(filePath, "utf-8");
26431
+ const content = readFileSync6(filePath, "utf-8");
26305
26432
  const parsed = parseReviewerOutput(content);
26306
26433
  this.db.run(
26307
26434
  `INSERT OR REPLACE INTO reviewer_outputs (round_id, reviewer_type, instance_number, file_path, finding_count, parsed_at)
@@ -26389,7 +26516,7 @@ var FilesystemSync = class {
26389
26516
  if (existingRound?.["source"] === "orchestrator" && this.shouldSkip(filePath, existingRound["parsed_at"] ?? null)) return;
26390
26517
  let raw;
26391
26518
  try {
26392
- raw = JSON.parse(readFileSync5(filePath, "utf-8"));
26519
+ raw = JSON.parse(readFileSync6(filePath, "utf-8"));
26393
26520
  } catch {
26394
26521
  console.error(`[FilesystemSync] Failed to parse ${filePath}`);
26395
26522
  return;
@@ -26439,7 +26566,7 @@ var FilesystemSync = class {
26439
26566
  const reviewerType = reviewer.type ?? "unknown";
26440
26567
  const instanceNumber = reviewer.instance ?? 1;
26441
26568
  const findings = reviewer.findings ?? [];
26442
- const reviewerMdPath = join9(roundDir, "reviews", `${reviewerType}-${instanceNumber}.md`);
26569
+ const reviewerMdPath = join10(roundDir, "reviews", `${reviewerType}-${instanceNumber}.md`);
26443
26570
  this.db.run(
26444
26571
  `INSERT OR REPLACE INTO reviewer_outputs (round_id, reviewer_type, instance_number, file_path, finding_count, parsed_at)
26445
26572
  VALUES (?, ?, ?, ?, ?, ?)`,
@@ -26537,7 +26664,7 @@ var FilesystemSync = class {
26537
26664
  if (existingRun?.["source"] === "orchestrator" && this.shouldSkip(filePath, existingRun["parsed_at"] ?? null)) return;
26538
26665
  let raw;
26539
26666
  try {
26540
- raw = JSON.parse(readFileSync5(filePath, "utf-8"));
26667
+ raw = JSON.parse(readFileSync6(filePath, "utf-8"));
26541
26668
  } catch {
26542
26669
  console.error(`[FilesystemSync] Failed to parse ${filePath}`);
26543
26670
  return;
@@ -26665,7 +26792,7 @@ var FilesystemSync = class {
26665
26792
  );
26666
26793
  const isOrchestratorSource = existingRound?.["source"] === "orchestrator";
26667
26794
  if (!isOrchestratorSource && existingRound && this.shouldSkip(filePath, existingRound["parsed_at"] ?? null)) return;
26668
- const content = readFileSync5(filePath, "utf-8");
26795
+ const content = readFileSync6(filePath, "utf-8");
26669
26796
  if (isOrchestratorSource) {
26670
26797
  this.db.run(
26671
26798
  `UPDATE review_rounds SET final_md_path = ?, parsed_at = ?
@@ -26708,7 +26835,7 @@ var FilesystemSync = class {
26708
26835
  "SELECT current_phase, phase_number, status FROM sessions WHERE id = ?",
26709
26836
  [sessionId]
26710
26837
  );
26711
- if (session && session["current_phase"] !== "complete") {
26838
+ if (session && (session["current_phase"] !== "complete" || session["phase_number"] < 8)) {
26712
26839
  this.db.run(
26713
26840
  `UPDATE sessions SET current_phase = 'complete', phase_number = 8, status = 'closed', updated_at = datetime('now')
26714
26841
  WHERE id = ?`,
@@ -26738,7 +26865,7 @@ var FilesystemSync = class {
26738
26865
  [sessionId, artifactType, relPath]
26739
26866
  );
26740
26867
  if (existing && this.shouldSkip(filePath, existing["parsed_at"] ?? null)) return;
26741
- const content = readFileSync5(filePath, "utf-8");
26868
+ const content = readFileSync6(filePath, "utf-8");
26742
26869
  const action = this.upsertMarkdownArtifact(sessionId, artifactType, filePath, content, roundNumber);
26743
26870
  this.emitArtifactEvent(action, {
26744
26871
  sessionId,
@@ -26750,7 +26877,7 @@ var FilesystemSync = class {
26750
26877
  // ── 6.6: Chokidar Watcher ──
26751
26878
  startWatching() {
26752
26879
  if (this.watcher) return;
26753
- this.watcher = watch(this.sessionsDir, {
26880
+ this.watcher = watch2(this.sessionsDir, {
26754
26881
  persistent: true,
26755
26882
  ignoreInitial: true,
26756
26883
  depth: 10,
@@ -26797,7 +26924,7 @@ var FilesystemSync = class {
26797
26924
  const parts = relFromSessions.split("/");
26798
26925
  const sessionId = parts[0];
26799
26926
  if (!sessionId) return;
26800
- const sessionDir = join9(this.sessionsDir, sessionId);
26927
+ const sessionDir = join10(this.sessionsDir, sessionId);
26801
26928
  this.ensureSessionRow(sessionId, sessionDir);
26802
26929
  const fileName = basename2(filePath);
26803
26930
  const reviewerMatch = relFromSessions.match(/rounds\/round-(\d+)\/reviews\/(.+\.md)$/);
@@ -26869,8 +26996,8 @@ var FilesystemSync = class {
26869
26996
  };
26870
26997
 
26871
26998
  // 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";
26999
+ import { existsSync as existsSync6, readFileSync as readFileSync7, statSync as statSync2 } from "node:fs";
27000
+ import { watch as watch3 } from "chokidar";
26874
27001
  import initSqlJs3 from "sql.js";
26875
27002
  function col(row, key) {
26876
27003
  return row[key] ?? null;
@@ -26891,7 +27018,7 @@ var DbSyncWatcher = class {
26891
27018
  * Initialize the WASM runtime (called once at startup).
26892
27019
  */
26893
27020
  async init() {
26894
- const wasmBuffer = readFileSync6(locateWasm());
27021
+ const wasmBuffer = readFileSync7(locateWasm());
26895
27022
  this.wasmBinary = wasmBuffer.buffer.slice(
26896
27023
  wasmBuffer.byteOffset,
26897
27024
  wasmBuffer.byteOffset + wasmBuffer.byteLength
@@ -26902,12 +27029,12 @@ var DbSyncWatcher = class {
26902
27029
  * Start watching the DB file for external changes.
26903
27030
  */
26904
27031
  startWatching() {
26905
- if (!existsSync5(this.dbFilePath)) return;
27032
+ if (!existsSync6(this.dbFilePath)) return;
26906
27033
  try {
26907
27034
  this.lastMtime = statSync2(this.dbFilePath).mtimeMs;
26908
27035
  } catch {
26909
27036
  }
26910
- this.watcher = watch2(this.dbFilePath, {
27037
+ this.watcher = watch3(this.dbFilePath, {
26911
27038
  // Also watch WAL/SHM files that SQLite may create
26912
27039
  persistent: true,
26913
27040
  ignoreInitial: true,
@@ -26953,7 +27080,7 @@ var DbSyncWatcher = class {
26953
27080
  * to avoid overwriting CLI changes.
26954
27081
  */
26955
27082
  syncFromDisk() {
26956
- if (!this.SQL || !existsSync5(this.dbFilePath)) return;
27083
+ if (!this.SQL || !existsSync6(this.dbFilePath)) return;
26957
27084
  let currentMtime;
26958
27085
  try {
26959
27086
  currentMtime = statSync2(this.dbFilePath).mtimeMs;
@@ -26964,7 +27091,7 @@ var DbSyncWatcher = class {
26964
27091
  this.lastMtime = currentMtime;
26965
27092
  let diskDb = null;
26966
27093
  try {
26967
- const fileBuffer = readFileSync6(this.dbFilePath);
27094
+ const fileBuffer = readFileSync7(this.dbFilePath);
26968
27095
  diskDb = new this.SQL.Database(fileBuffer);
26969
27096
  this.syncSessions(diskDb);
26970
27097
  this.syncEvents(diskDb);
@@ -27221,17 +27348,17 @@ var DbSyncWatcher = class {
27221
27348
  import { dirname as dirname8 } from "node:path";
27222
27349
 
27223
27350
  // 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";
27351
+ import { readFileSync as readFileSync8, readdirSync as readdirSync2, existsSync as existsSync7 } from "node:fs";
27352
+ import { join as join11 } from "node:path";
27226
27353
  function buildChatContext(ocrDir, target) {
27227
- const sessionsDir = join10(ocrDir, "sessions");
27354
+ const sessionsDir = join11(ocrDir, "sessions");
27228
27355
  if (target.type === "map_run") {
27229
27356
  return buildMapRunContext(sessionsDir, target.sessionId, target.runNumber);
27230
27357
  }
27231
27358
  return buildReviewRoundContext(sessionsDir, target.sessionId, target.roundNumber);
27232
27359
  }
27233
27360
  function buildMapRunContext(sessionsDir, sessionId, runNumber) {
27234
- const mapPath = join10(
27361
+ const mapPath = join11(
27235
27362
  sessionsDir,
27236
27363
  sessionId,
27237
27364
  "map",
@@ -27245,8 +27372,8 @@ function buildMapRunContext(sessionsDir, sessionId, runNumber) {
27245
27372
  "",
27246
27373
  `Below is the Code Review Map that organizes the changeset into reviewable sections:`
27247
27374
  ];
27248
- if (existsSync6(mapPath)) {
27249
- const content = readFileSync7(mapPath, "utf-8");
27375
+ if (existsSync7(mapPath)) {
27376
+ const content = readFileSync8(mapPath, "utf-8");
27250
27377
  parts.push("");
27251
27378
  parts.push("<map>");
27252
27379
  parts.push(content);
@@ -27258,26 +27385,26 @@ function buildMapRunContext(sessionsDir, sessionId, runNumber) {
27258
27385
  return parts.join("\n");
27259
27386
  }
27260
27387
  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");
27388
+ const roundDir = join11(sessionsDir, sessionId, "rounds", `round-${roundNumber}`);
27389
+ const finalPath = join11(roundDir, "final.md");
27390
+ const reviewersDir = join11(roundDir, "reviews");
27264
27391
  const parts = [
27265
27392
  `You are an expert code reviewer assisting with a code review session.`,
27266
27393
  `You are looking at review round #${roundNumber} for session "${sessionId}".`,
27267
27394
  "",
27268
27395
  `Below are the review artifacts for this round:`
27269
27396
  ];
27270
- if (existsSync6(finalPath)) {
27271
- const content = readFileSync7(finalPath, "utf-8");
27397
+ if (existsSync7(finalPath)) {
27398
+ const content = readFileSync8(finalPath, "utf-8");
27272
27399
  parts.push("");
27273
27400
  parts.push("<final-synthesis>");
27274
27401
  parts.push(content);
27275
27402
  parts.push("</final-synthesis>");
27276
27403
  }
27277
- if (existsSync6(reviewersDir)) {
27404
+ if (existsSync7(reviewersDir)) {
27278
27405
  const files = readdirSync2(reviewersDir).filter((f) => f.endsWith(".md")).sort();
27279
27406
  for (const file of files) {
27280
- const content = readFileSync7(join10(reviewersDir, file), "utf-8");
27407
+ const content = readFileSync8(join11(reviewersDir, file), "utf-8");
27281
27408
  const reviewerName = file.replace(/\.md$/, "");
27282
27409
  parts.push("");
27283
27410
  parts.push(`<reviewer name="${reviewerName}">`);
@@ -27285,7 +27412,7 @@ function buildReviewRoundContext(sessionsDir, sessionId, roundNumber) {
27285
27412
  parts.push("</reviewer>");
27286
27413
  }
27287
27414
  }
27288
- if (!existsSync6(finalPath) && !existsSync6(reviewersDir)) {
27415
+ if (!existsSync7(finalPath) && !existsSync7(reviewersDir)) {
27289
27416
  parts.push("");
27290
27417
  parts.push("(No review artifacts found on disk for this round.)");
27291
27418
  }
@@ -27590,15 +27717,15 @@ function cleanupAllChats() {
27590
27717
 
27591
27718
  // src/server/socket/post-handler.ts
27592
27719
  import { execFile } from "node:child_process";
27593
- import { existsSync as existsSync7, mkdirSync as mkdirSync2, readFileSync as readFileSync8, unlinkSync, writeFileSync as writeFileSync3 } from "node:fs";
27720
+ import { existsSync as existsSync8, mkdirSync as mkdirSync2, readFileSync as readFileSync9, unlinkSync, writeFileSync as writeFileSync3 } from "node:fs";
27594
27721
  import { tmpdir as tmpdir2 } from "node:os";
27595
- import { join as join11, dirname as dirname9, isAbsolute } from "node:path";
27722
+ import { join as join12, dirname as dirname9, isAbsolute } from "node:path";
27596
27723
  import { randomUUID } from "node:crypto";
27597
27724
  import { promisify } from "node:util";
27598
27725
  var execFileAsync = promisify(execFile);
27599
27726
  function resolveSessionDir(sessionDir, ocrDir) {
27600
27727
  if (isAbsolute(sessionDir)) return sessionDir;
27601
- return join11(dirname9(ocrDir), sessionDir);
27728
+ return join12(dirname9(ocrDir), sessionDir);
27602
27729
  }
27603
27730
  var BRANCH_PREFIXES = [
27604
27731
  "feat",
@@ -27768,19 +27895,19 @@ function registerPostHandlers(io2, socket, db, ocrDir, aiCliService) {
27768
27895
  socket.emit("post:error", { error: "Session not found" });
27769
27896
  return;
27770
27897
  }
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)) {
27898
+ const sessionDir = session.session_dir ? resolveSessionDir(session.session_dir, ocrDir) : join12(ocrDir, "sessions", sessionId);
27899
+ const roundDir = join12(sessionDir, "rounds", `round-${roundNumber}`);
27900
+ const finalPath = join12(roundDir, "final.md");
27901
+ if (!existsSync8(finalPath)) {
27775
27902
  socket.emit("post:error", { error: "final.md not found for this round" });
27776
27903
  return;
27777
27904
  }
27778
- const humanReviewPath = join11(roundDir, "final-human.md");
27905
+ const humanReviewPath = join12(roundDir, "final-human.md");
27779
27906
  const repoRoot = dirname9(ocrDir);
27780
- const commandMdPath = join11(ocrDir, "commands", "translate-review-to-single-human.md");
27907
+ const commandMdPath = join12(ocrDir, "commands", "translate-review-to-single-human.md");
27781
27908
  let commandContent;
27782
27909
  try {
27783
- commandContent = readFileSync8(commandMdPath, "utf-8");
27910
+ commandContent = readFileSync9(commandMdPath, "utf-8");
27784
27911
  } catch {
27785
27912
  socket.emit("post:error", {
27786
27913
  error: `Command file not found: ${commandMdPath}. Run \`ocr init\` to set up.`
@@ -27856,9 +27983,9 @@ function registerPostHandlers(io2, socket, db, ocrDir, aiCliService) {
27856
27983
  }
27857
27984
  }
27858
27985
  let generatedContent = "";
27859
- if (existsSync7(humanReviewPath)) {
27986
+ if (existsSync8(humanReviewPath)) {
27860
27987
  try {
27861
- generatedContent = readFileSync8(humanReviewPath, "utf-8").trim();
27988
+ generatedContent = readFileSync9(humanReviewPath, "utf-8").trim();
27862
27989
  } catch {
27863
27990
  }
27864
27991
  }
@@ -27933,10 +28060,10 @@ function registerPostHandlers(io2, socket, db, ocrDir, aiCliService) {
27933
28060
  socket.emit("post:save-result", { success: false, error: "Session not found" });
27934
28061
  return;
27935
28062
  }
27936
- const sessionDir = session.session_dir ? resolveSessionDir(session.session_dir, ocrDir) : join11(ocrDir, "sessions", sessionId);
27937
- const roundDir = join11(sessionDir, "rounds", `round-${roundNumber}`);
28063
+ const sessionDir = session.session_dir ? resolveSessionDir(session.session_dir, ocrDir) : join12(ocrDir, "sessions", sessionId);
28064
+ const roundDir = join12(sessionDir, "rounds", `round-${roundNumber}`);
27938
28065
  mkdirSync2(roundDir, { recursive: true });
27939
- const filePath = join11(roundDir, "final-human.md");
28066
+ const filePath = join12(roundDir, "final-human.md");
27940
28067
  writeFileSync3(filePath, content, { mode: 420 });
27941
28068
  saveDb(db, ocrDir);
27942
28069
  socket.emit("post:save-result", { success: true });
@@ -27964,12 +28091,12 @@ function registerPostHandlers(io2, socket, db, ocrDir, aiCliService) {
27964
28091
  );
27965
28092
  tracker.appendOutput(`\u25B8 Posting review to PR #${prNumber}...
27966
28093
  `);
27967
- const tmpDir = join11(tmpdir2(), "ocr-post-comments");
28094
+ const tmpDir = join12(tmpdir2(), "ocr-post-comments");
27968
28095
  try {
27969
28096
  mkdirSync2(tmpDir, { recursive: true, mode: 448 });
27970
28097
  } catch {
27971
28098
  }
27972
- const tmpFile = join11(tmpDir, `${randomUUID()}.md`);
28099
+ const tmpFile = join12(tmpDir, `${randomUUID()}.md`);
27973
28100
  writeFileSync3(tmpFile, content, { mode: 384 });
27974
28101
  const repoRoot = dirname9(ocrDir);
27975
28102
  try {
@@ -28017,7 +28144,7 @@ function cleanupAllPostGenerations() {
28017
28144
  // src/server/index.ts
28018
28145
  var __dirname3 = dirname10(fileURLToPath3(import.meta.url));
28019
28146
  var AUTH_TOKEN = randomBytes(32).toString("hex");
28020
- var app = (0, import_express11.default)();
28147
+ var app = (0, import_express12.default)();
28021
28148
  var httpServer = createServer(app);
28022
28149
  var io = new SocketIOServer(httpServer, {
28023
28150
  cors: {
@@ -28026,7 +28153,7 @@ var io = new SocketIOServer(httpServer, {
28026
28153
  maxHttpBufferSize: 1e6
28027
28154
  // 1 MB — explicit default; review if large payloads are needed
28028
28155
  });
28029
- app.use(import_express11.default.json());
28156
+ app.use(import_express12.default.json());
28030
28157
  if (process.env.NODE_ENV !== "production") {
28031
28158
  app.use((_req, res, next) => {
28032
28159
  const origin = _req.headers.origin;
@@ -28078,12 +28205,12 @@ async function startServer(options = {}) {
28078
28205
  const ocrDir = resolveOcrDir();
28079
28206
  const aiCliService = new AiCliService(ocrDir);
28080
28207
  const db = await openDb(ocrDir);
28081
- const dataDir = join12(ocrDir, "data");
28082
- const pidFilePath = join12(dataDir, "dashboard.pid");
28208
+ const dataDir = join13(ocrDir, "data");
28209
+ const pidFilePath = join13(dataDir, "dashboard.pid");
28083
28210
  mkdirSync3(dataDir, { recursive: true });
28084
- if (existsSync8(pidFilePath)) {
28211
+ if (existsSync9(pidFilePath)) {
28085
28212
  try {
28086
- const oldPid = parseInt(readFileSync9(pidFilePath, "utf-8").trim(), 10);
28213
+ const oldPid = parseInt(readFileSync10(pidFilePath, "utf-8").trim(), 10);
28087
28214
  if (!isNaN(oldPid)) {
28088
28215
  try {
28089
28216
  process.kill(oldPid, 0);
@@ -28183,11 +28310,12 @@ async function startServer(options = {}) {
28183
28310
  app.use("/api/commands", createCommandsRouter(db));
28184
28311
  app.use("/api/config", createConfigRouter(ocrDir, aiCliService));
28185
28312
  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") : "";
28313
+ app.use("/api/reviewers", createReviewersRouter(ocrDir));
28314
+ const clientDir = join13(__dirname3, "client");
28315
+ if (process.env.NODE_ENV === "production" && existsSync9(clientDir)) {
28316
+ app.use(import_express12.default.static(clientDir, { index: false }));
28317
+ const indexHtmlPath = join13(clientDir, "index.html");
28318
+ const rawIndexHtml = existsSync9(indexHtmlPath) ? readFileSync10(indexHtmlPath, "utf-8") : "";
28191
28319
  const tokenScript = `<script>window.__OCR_TOKEN__=${JSON.stringify(AUTH_TOKEN)};</script>`;
28192
28320
  const injectedIndexHtml = rawIndexHtml.replace(
28193
28321
  "</head>",
@@ -28210,7 +28338,7 @@ async function startServer(options = {}) {
28210
28338
  console.log("Client disconnected:", socket.id);
28211
28339
  });
28212
28340
  });
28213
- const dbFilePath = join12(ocrDir, "data", "ocr.db");
28341
+ const dbFilePath = join13(ocrDir, "data", "ocr.db");
28214
28342
  const dbSyncWatcher = new DbSyncWatcher(db, dbFilePath, io, () => {
28215
28343
  saveDb(db, ocrDir);
28216
28344
  });
@@ -28221,12 +28349,13 @@ async function startServer(options = {}) {
28221
28349
  () => dbSyncWatcher.syncFromDisk(),
28222
28350
  () => dbSyncWatcher.markOwnWrite()
28223
28351
  );
28224
- const sessionsDir = join12(ocrDir, "sessions");
28352
+ const sessionsDir = join13(ocrDir, "sessions");
28225
28353
  const fsSync = new FilesystemSync(db, sessionsDir, io, () => saveDb(db, ocrDir));
28226
28354
  await fsSync.fullScan();
28227
28355
  saveDb(db, ocrDir);
28228
28356
  fsSync.startWatching();
28229
28357
  console.log(`Watching sessions: ${sessionsDir}`);
28358
+ const stopReviewersWatch = watchReviewersMeta(ocrDir, io);
28230
28359
  await new Promise((resolve3, reject) => {
28231
28360
  httpServer.once("error", (err) => {
28232
28361
  if (err.code === "EADDRINUSE") {
@@ -28310,6 +28439,7 @@ async function startServer(options = {}) {
28310
28439
  }
28311
28440
  dbSyncWatcher.stopWatching();
28312
28441
  fsSync.stopWatching();
28442
+ stopReviewersWatch();
28313
28443
  io.close();
28314
28444
  httpServer.close(() => {
28315
28445
  try {