@open-code-review/cli 1.5.1 → 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.
- package/README.md +67 -121
- package/dist/dashboard/client/assets/{_basePickBy-BJKCdvle.js → _basePickBy-DbLJVCA4.js} +1 -1
- package/dist/dashboard/client/assets/{_baseUniq-L_sxIO0r.js → _baseUniq-IXEG0cJJ.js} +1 -1
- package/dist/dashboard/client/assets/{arc-tqAEcLt5.js → arc-lsKxmOJY.js} +1 -1
- package/dist/dashboard/client/assets/{architectureDiagram-VXUJARFQ-CrKQo6Ye.js → architectureDiagram-VXUJARFQ-DfMlzFJX.js} +1 -1
- package/dist/dashboard/client/assets/{blockDiagram-VD42YOAC-DXOc89nw.js → blockDiagram-VD42YOAC-bSpnd26J.js} +1 -1
- package/dist/dashboard/client/assets/{c4Diagram-YG6GDRKO-Ba-jYbw0.js → c4Diagram-YG6GDRKO-DPYmVhCZ.js} +1 -1
- package/dist/dashboard/client/assets/channel-C--wY_Wd.js +1 -0
- package/dist/dashboard/client/assets/{chunk-4BX2VUAB-D1G3HCqL.js → chunk-4BX2VUAB-CI9zC4lV.js} +1 -1
- package/dist/dashboard/client/assets/{chunk-55IACEB6-FI7g4AjR.js → chunk-55IACEB6-BqUdJdx5.js} +1 -1
- package/dist/dashboard/client/assets/{chunk-B4BG7PRW-DhEGFGWs.js → chunk-B4BG7PRW-DymQrTp-.js} +1 -1
- package/dist/dashboard/client/assets/{chunk-DI55MBZ5-Da3-6ZE4.js → chunk-DI55MBZ5-lZ_9LKGJ.js} +1 -1
- package/dist/dashboard/client/assets/{chunk-FMBD7UC4-D0QLOjiy.js → chunk-FMBD7UC4-DC5rgLNm.js} +1 -1
- package/dist/dashboard/client/assets/{chunk-QN33PNHL-WkfgpbLo.js → chunk-QN33PNHL-BrygpHrX.js} +1 -1
- package/dist/dashboard/client/assets/{chunk-QZHKN3VN-Bqn0IO1w.js → chunk-QZHKN3VN-CWJqBdNg.js} +1 -1
- package/dist/dashboard/client/assets/{chunk-TZMSLE5B-CC_K_BeL.js → chunk-TZMSLE5B-BACgM5pG.js} +1 -1
- package/dist/dashboard/client/assets/classDiagram-2ON5EDUG-DoxmMlnf.js +1 -0
- package/dist/dashboard/client/assets/classDiagram-v2-WZHVMYZB-DoxmMlnf.js +1 -0
- package/dist/dashboard/client/assets/clone-BgvweD4v.js +1 -0
- package/dist/dashboard/client/assets/{cose-bilkent-S5V4N54A-D8urqxIF.js → cose-bilkent-S5V4N54A-BYvGIfo0.js} +1 -1
- package/dist/dashboard/client/assets/{dagre-6UL2VRFP-w2xS0ztU.js → dagre-6UL2VRFP-B1rZyiLJ.js} +1 -1
- package/dist/dashboard/client/assets/{diagram-PSM6KHXK-DlOtv6zO.js → diagram-PSM6KHXK-Dvl5dQMd.js} +1 -1
- package/dist/dashboard/client/assets/{diagram-QEK2KX5R-EpxsVLZY.js → diagram-QEK2KX5R-Cmntmhht.js} +1 -1
- package/dist/dashboard/client/assets/{diagram-S2PKOQOG-kmITzl42.js → diagram-S2PKOQOG-BqZcpG85.js} +1 -1
- package/dist/dashboard/client/assets/{erDiagram-Q2GNP2WA-Bvyepu_Z.js → erDiagram-Q2GNP2WA-Cw7BALso.js} +1 -1
- package/dist/dashboard/client/assets/{flowDiagram-NV44I4VS-BokLAZN0.js → flowDiagram-NV44I4VS-B_amTHzQ.js} +1 -1
- package/dist/dashboard/client/assets/{ganttDiagram-JELNMOA3-i5ZSGuTN.js → ganttDiagram-JELNMOA3-B1j2-sTo.js} +1 -1
- package/dist/dashboard/client/assets/{gitGraphDiagram-V2S2FVAM-CIayQ8P9.js → gitGraphDiagram-V2S2FVAM-D5BkfAMt.js} +1 -1
- package/dist/dashboard/client/assets/{graph-C3ouLF2F.js → graph-B_v15DHv.js} +1 -1
- package/dist/dashboard/client/assets/index-UkJZZdYD.js +548 -0
- package/dist/dashboard/client/assets/index-Zl---B_3.css +1 -0
- package/dist/dashboard/client/assets/{infoDiagram-HS3SLOUP-wxe8NO00.js → infoDiagram-HS3SLOUP-C4dtIkj3.js} +1 -1
- package/dist/dashboard/client/assets/{journeyDiagram-XKPGCS4Q-BeHCbOFN.js → journeyDiagram-XKPGCS4Q-hha4Am8v.js} +1 -1
- package/dist/dashboard/client/assets/{kanban-definition-3W4ZIXB7-DxUlb4wo.js → kanban-definition-3W4ZIXB7-1EY8l7Ng.js} +1 -1
- package/dist/dashboard/client/assets/{layout-CYsQ5kjv.js → layout-7SmAbjFT.js} +1 -1
- package/dist/dashboard/client/assets/{linear-ByuMiLUn.js → linear-BfjSBezh.js} +1 -1
- package/dist/dashboard/client/assets/{mermaid-renderer-cx-n1jFM.js → mermaid-renderer-PPIt-kY4.js} +4 -4
- package/dist/dashboard/client/assets/{mindmap-definition-VGOIOE7T-CI5zvW3G.js → mindmap-definition-VGOIOE7T-BFpjN9LY.js} +1 -1
- package/dist/dashboard/client/assets/{pieDiagram-ADFJNKIX-lC7QV-4L.js → pieDiagram-ADFJNKIX-GBbQtDBQ.js} +1 -1
- package/dist/dashboard/client/assets/{quadrantDiagram-AYHSOK5B-DI7Bn_fF.js → quadrantDiagram-AYHSOK5B-Dm0vOhOw.js} +1 -1
- package/dist/dashboard/client/assets/{requirementDiagram-UZGBJVZJ-BVuFGUp6.js → requirementDiagram-UZGBJVZJ-BrKONIV8.js} +1 -1
- package/dist/dashboard/client/assets/{sankeyDiagram-TZEHDZUN-C-3hBPRk.js → sankeyDiagram-TZEHDZUN-IOobtmDc.js} +1 -1
- package/dist/dashboard/client/assets/{sequenceDiagram-WL72ISMW-CLS6xCbv.js → sequenceDiagram-WL72ISMW-Dnb0bOW5.js} +1 -1
- package/dist/dashboard/client/assets/{stateDiagram-FKZM4ZOC-XOLrkoEE.js → stateDiagram-FKZM4ZOC-C9-bf7bn.js} +1 -1
- package/dist/dashboard/client/assets/stateDiagram-v2-4FDKWEC3-C8Gr4khP.js +1 -0
- package/dist/dashboard/client/assets/{timeline-definition-IT6M3QCI-N9m6IkH5.js → timeline-definition-IT6M3QCI-tJogDEHB.js} +1 -1
- package/dist/dashboard/client/assets/{treemap-GDKQZRPO-ayvdfxB1.js → treemap-GDKQZRPO-DQY6HADq.js} +1 -1
- package/dist/dashboard/client/assets/{xychartDiagram-PRI3JC2R-CUmVEVIH.js → xychartDiagram-PRI3JC2R-DfxeQmTO.js} +1 -1
- package/dist/dashboard/client/index.html +2 -2
- package/dist/dashboard/server.js +810 -206
- package/dist/index.js +810 -15
- package/dist/lib/db/index.js +522 -0
- package/package.json +2 -2
- package/dist/dashboard/client/assets/channel-OmrThJE3.js +0 -1
- package/dist/dashboard/client/assets/classDiagram-2ON5EDUG-Dg5ffKNR.js +0 -1
- package/dist/dashboard/client/assets/classDiagram-v2-WZHVMYZB-Dg5ffKNR.js +0 -1
- package/dist/dashboard/client/assets/clone-CKI4Qu1i.js +0 -1
- package/dist/dashboard/client/assets/index-CPEavIIM.css +0 -1
- package/dist/dashboard/client/assets/index-icxlpW-l.js +0 -456
- package/dist/dashboard/client/assets/stateDiagram-v2-4FDKWEC3-Cy33HZ1p.js +0 -1
package/dist/dashboard/server.js
CHANGED
|
@@ -18410,10 +18410,10 @@ var require_view = __commonJS({
|
|
|
18410
18410
|
var debug = require_src()("express:view");
|
|
18411
18411
|
var path2 = __require("path");
|
|
18412
18412
|
var fs6 = __require("fs");
|
|
18413
|
-
var
|
|
18413
|
+
var dirname11 = path2.dirname;
|
|
18414
18414
|
var basename3 = path2.basename;
|
|
18415
18415
|
var extname = path2.extname;
|
|
18416
|
-
var
|
|
18416
|
+
var join14 = path2.join;
|
|
18417
18417
|
var resolve3 = path2.resolve;
|
|
18418
18418
|
module.exports = View;
|
|
18419
18419
|
function View(name, options) {
|
|
@@ -18449,7 +18449,7 @@ var require_view = __commonJS({
|
|
|
18449
18449
|
for (var i = 0; i < roots.length && !path3; i++) {
|
|
18450
18450
|
var root = roots[i];
|
|
18451
18451
|
var loc = resolve3(root, name);
|
|
18452
|
-
var dir =
|
|
18452
|
+
var dir = dirname11(loc);
|
|
18453
18453
|
var file = basename3(loc);
|
|
18454
18454
|
path3 = this.resolve(dir, file);
|
|
18455
18455
|
}
|
|
@@ -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 =
|
|
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 =
|
|
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
|
|
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(
|
|
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 =
|
|
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
|
|
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
|
|
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
|
|
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 =
|
|
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
|
|
23182
|
+
var import_express12 = __toESM(require_express2(), 1);
|
|
23183
23183
|
import { createServer } from "node:http";
|
|
23184
|
-
import { existsSync as
|
|
23185
|
-
import { join as
|
|
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";
|
|
@@ -23440,6 +23440,30 @@ var MIGRATIONS = [
|
|
|
23440
23440
|
CREATE INDEX idx_events_session ON orchestration_events(session_id);
|
|
23441
23441
|
CREATE INDEX idx_events_type ON orchestration_events(event_type);
|
|
23442
23442
|
`
|
|
23443
|
+
},
|
|
23444
|
+
{
|
|
23445
|
+
version: 6,
|
|
23446
|
+
description: "Add orchestrator-first columns to review_rounds for round-meta.json support",
|
|
23447
|
+
sql: `
|
|
23448
|
+
ALTER TABLE review_rounds ADD COLUMN source TEXT DEFAULT NULL;
|
|
23449
|
+
ALTER TABLE review_rounds ADD COLUMN reviewer_count INTEGER DEFAULT 0;
|
|
23450
|
+
ALTER TABLE review_rounds ADD COLUMN total_finding_count INTEGER DEFAULT 0;
|
|
23451
|
+
`
|
|
23452
|
+
},
|
|
23453
|
+
{
|
|
23454
|
+
version: 7,
|
|
23455
|
+
description: "Add category column to review_findings for blocker/should_fix/suggestion classification",
|
|
23456
|
+
sql: `
|
|
23457
|
+
ALTER TABLE review_findings ADD COLUMN category TEXT DEFAULT NULL;
|
|
23458
|
+
`
|
|
23459
|
+
},
|
|
23460
|
+
{
|
|
23461
|
+
version: 8,
|
|
23462
|
+
description: "Add orchestrator-first columns to map_runs for map-meta.json support",
|
|
23463
|
+
sql: `
|
|
23464
|
+
ALTER TABLE map_runs ADD COLUMN source TEXT DEFAULT NULL;
|
|
23465
|
+
ALTER TABLE map_runs ADD COLUMN section_count INTEGER DEFAULT 0;
|
|
23466
|
+
`
|
|
23443
23467
|
}
|
|
23444
23468
|
];
|
|
23445
23469
|
function ensureSchemaVersionTable(db) {
|
|
@@ -23984,24 +24008,24 @@ function enrichSession(db, session) {
|
|
|
23984
24008
|
let reviewPhaseNumber = 0;
|
|
23985
24009
|
let reviewPhase = "";
|
|
23986
24010
|
if (hasReview) {
|
|
24011
|
+
const derived = deriveReviewPhase(db, session.id);
|
|
23987
24012
|
if (session.workflow_type === "review") {
|
|
23988
|
-
reviewPhaseNumber = session.phase_number;
|
|
23989
|
-
reviewPhase = session.current_phase;
|
|
24013
|
+
reviewPhaseNumber = Math.max(session.phase_number, derived);
|
|
23990
24014
|
} else {
|
|
23991
|
-
reviewPhaseNumber =
|
|
23992
|
-
reviewPhase = REVIEW_PHASE_NAMES[reviewPhaseNumber - 1] ?? "context";
|
|
24015
|
+
reviewPhaseNumber = derived;
|
|
23993
24016
|
}
|
|
24017
|
+
reviewPhase = REVIEW_PHASE_NAMES[reviewPhaseNumber - 1] ?? "context";
|
|
23994
24018
|
}
|
|
23995
24019
|
let mapPhaseNumber = 0;
|
|
23996
24020
|
let mapPhase = "";
|
|
23997
24021
|
if (hasMap) {
|
|
24022
|
+
const derived = deriveMapPhase(db, session.id);
|
|
23998
24023
|
if (session.workflow_type === "map") {
|
|
23999
|
-
mapPhaseNumber = session.phase_number;
|
|
24000
|
-
mapPhase = session.current_phase;
|
|
24024
|
+
mapPhaseNumber = Math.max(session.phase_number, derived);
|
|
24001
24025
|
} else {
|
|
24002
|
-
mapPhaseNumber =
|
|
24003
|
-
mapPhase = MAP_PHASE_NAMES[mapPhaseNumber - 1] ?? "map-context";
|
|
24026
|
+
mapPhaseNumber = derived;
|
|
24004
24027
|
}
|
|
24028
|
+
mapPhase = MAP_PHASE_NAMES[mapPhaseNumber - 1] ?? "map-context";
|
|
24005
24029
|
}
|
|
24006
24030
|
const latestRound = rounds.length > 0 ? rounds[rounds.length - 1] : null;
|
|
24007
24031
|
const latestVerdict = latestRound?.verdict ?? null;
|
|
@@ -25166,11 +25190,37 @@ function resolveLocalCli() {
|
|
|
25166
25190
|
}
|
|
25167
25191
|
|
|
25168
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
|
+
}
|
|
25169
25219
|
var ALLOWED_COMMANDS = /* @__PURE__ */ new Set([
|
|
25170
25220
|
"progress",
|
|
25171
25221
|
"state"
|
|
25172
25222
|
]);
|
|
25173
|
-
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"]);
|
|
25174
25224
|
var MAX_CONCURRENT = 3;
|
|
25175
25225
|
var activeCommands = /* @__PURE__ */ new Map();
|
|
25176
25226
|
function getActiveCommands() {
|
|
@@ -25192,7 +25242,7 @@ function registerCommandHandlers(io2, socket, db, ocrDir, aiCliService) {
|
|
|
25192
25242
|
}
|
|
25193
25243
|
const { command } = payload;
|
|
25194
25244
|
const normalized = command.replace(/^ocr\s+/, "");
|
|
25195
|
-
const parts = normalized
|
|
25245
|
+
const parts = shellSplit(normalized);
|
|
25196
25246
|
const baseCommand = parts[0] ?? "";
|
|
25197
25247
|
const subArgs = parts.slice(1);
|
|
25198
25248
|
if (!ALLOWED_COMMANDS.has(baseCommand) && !AI_COMMANDS.has(baseCommand)) {
|
|
@@ -25349,34 +25399,68 @@ function spawnAiCommand(io2, _socket, db, ocrDir, executionId, baseCommand, subA
|
|
|
25349
25399
|
finishExecution(io2, db, ocrDir, executionId, 1, content);
|
|
25350
25400
|
return;
|
|
25351
25401
|
}
|
|
25352
|
-
|
|
25353
|
-
|
|
25354
|
-
|
|
25355
|
-
|
|
25356
|
-
|
|
25357
|
-
|
|
25358
|
-
|
|
25359
|
-
|
|
25360
|
-
|
|
25361
|
-
|
|
25362
|
-
|
|
25363
|
-
|
|
25364
|
-
|
|
25365
|
-
|
|
25366
|
-
|
|
25367
|
-
|
|
25368
|
-
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}`);
|
|
25369
25463
|
}
|
|
25370
|
-
}
|
|
25371
|
-
const optionsStr = options.length > 0 ? options.join(" ") : "none";
|
|
25372
|
-
const promptLines = [
|
|
25373
|
-
`Follow the instructions below to run the OCR ${baseCommand} workflow.`,
|
|
25374
|
-
"",
|
|
25375
|
-
`Target: ${target}`,
|
|
25376
|
-
`Options: ${optionsStr}`
|
|
25377
|
-
];
|
|
25378
|
-
if (requirements) {
|
|
25379
|
-
promptLines.push(`Requirements: ${requirements}`);
|
|
25380
25464
|
}
|
|
25381
25465
|
const localCli = resolveLocalCli();
|
|
25382
25466
|
if (localCli) {
|
|
@@ -25744,10 +25828,77 @@ function createChatRouter(db, ocrDir) {
|
|
|
25744
25828
|
return router;
|
|
25745
25829
|
}
|
|
25746
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
|
+
|
|
25747
25898
|
// src/server/services/filesystem-sync.ts
|
|
25748
|
-
import { readdirSync, readFileSync as
|
|
25749
|
-
import { join as
|
|
25750
|
-
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";
|
|
25751
25902
|
|
|
25752
25903
|
// src/server/services/parsers/reviewer-parser.ts
|
|
25753
25904
|
var FINDING_HEADING_RE = /^#{2,3}\s+(?:Finding|Issue|Suggestion)\s*(?:\d+)?\s*[:\s]*\s*(.*)/i;
|
|
@@ -25838,9 +25989,9 @@ function finalizeFinding(partial, summaryLines) {
|
|
|
25838
25989
|
|
|
25839
25990
|
// src/server/services/parsers/final-parser.ts
|
|
25840
25991
|
var VERDICT_RE = /^\*?\*?\s*(?:##\s*)?Verdict\s*\*?\*?\s*:?\s*\*?\*?\s*(.*)/im;
|
|
25841
|
-
var BLOCKERS_RE =
|
|
25842
|
-
var SHOULD_FIX_RE =
|
|
25843
|
-
var SUGGESTIONS_RE =
|
|
25992
|
+
var BLOCKERS_RE = /^\*\*Blockers?\*\*\s*:?\s*(\d+)/im;
|
|
25993
|
+
var SHOULD_FIX_RE = /^\*\*Should\s*Fix\*\*\s*:?\s*(\d+)/im;
|
|
25994
|
+
var SUGGESTIONS_RE = /^\*\*Suggestions?\*\*\s*:?\s*(\d+)/im;
|
|
25844
25995
|
function parseFinalMd(content) {
|
|
25845
25996
|
let verdict = null;
|
|
25846
25997
|
const verdictMatch = content.match(VERDICT_RE);
|
|
@@ -25854,25 +26005,39 @@ function parseFinalMd(content) {
|
|
|
25854
26005
|
let shouldFixCount = shouldFixMatch ? parseInt(shouldFixMatch[1] ?? "0", 10) : 0;
|
|
25855
26006
|
let suggestionCount = suggestionsMatch ? parseInt(suggestionsMatch[1] ?? "0", 10) : 0;
|
|
25856
26007
|
if (!blockerMatch) {
|
|
25857
|
-
blockerCount =
|
|
26008
|
+
blockerCount = countSectionItems(content, /^##\s+Blockers?\b/im);
|
|
25858
26009
|
}
|
|
25859
26010
|
if (!shouldFixMatch) {
|
|
25860
|
-
shouldFixCount =
|
|
26011
|
+
shouldFixCount = countSectionItems(content, /^##\s+Should\s*Fix\b/im);
|
|
25861
26012
|
}
|
|
25862
26013
|
if (!suggestionsMatch) {
|
|
25863
|
-
suggestionCount =
|
|
26014
|
+
suggestionCount = countSectionItems(content, /^##\s+Suggestions?\b/im);
|
|
25864
26015
|
}
|
|
25865
26016
|
return { verdict, blockerCount, shouldFixCount, suggestionCount };
|
|
25866
26017
|
}
|
|
25867
|
-
function
|
|
26018
|
+
function countSectionItems(content, sectionRe) {
|
|
25868
26019
|
const match = content.match(sectionRe);
|
|
25869
|
-
if (!match?.index) return 0;
|
|
26020
|
+
if (!match?.index && match?.index !== 0) return 0;
|
|
25870
26021
|
const afterSection = content.slice(match.index + (match[0]?.length ?? 0));
|
|
25871
26022
|
const lines = afterSection.split("\n");
|
|
25872
26023
|
let count = 0;
|
|
25873
26024
|
for (const line of lines) {
|
|
25874
|
-
|
|
25875
|
-
if (
|
|
26025
|
+
const trimmed = line.trim();
|
|
26026
|
+
if (/^##\s+[^#]/.test(trimmed)) break;
|
|
26027
|
+
if (/^---+\s*$/.test(trimmed)) break;
|
|
26028
|
+
if (/^###\s+\d+\./.test(trimmed)) {
|
|
26029
|
+
count++;
|
|
26030
|
+
continue;
|
|
26031
|
+
}
|
|
26032
|
+
if (/^###\s+[^\w\s#]/.test(trimmed)) {
|
|
26033
|
+
count++;
|
|
26034
|
+
continue;
|
|
26035
|
+
}
|
|
26036
|
+
if (/^-\s+\S/.test(trimmed)) {
|
|
26037
|
+
if (/^-\s+(?:none\b|no\s|n\/a\b)/i.test(trimmed)) continue;
|
|
26038
|
+
count++;
|
|
26039
|
+
continue;
|
|
26040
|
+
}
|
|
25876
26041
|
}
|
|
25877
26042
|
return count;
|
|
25878
26043
|
}
|
|
@@ -25913,60 +26078,68 @@ var FilesystemSync = class {
|
|
|
25913
26078
|
debounceTimers = /* @__PURE__ */ new Map();
|
|
25914
26079
|
onSync;
|
|
25915
26080
|
// ── 6.1: Full Scan ──
|
|
25916
|
-
fullScan() {
|
|
25917
|
-
if (!
|
|
26081
|
+
async fullScan() {
|
|
26082
|
+
if (!existsSync5(this.sessionsDir)) return;
|
|
25918
26083
|
const entries = readdirSync(this.sessionsDir, { withFileTypes: true });
|
|
25919
26084
|
for (const entry of entries) {
|
|
25920
26085
|
if (!entry.isDirectory()) continue;
|
|
25921
26086
|
const sessionId = entry.name;
|
|
25922
|
-
const sessionDir =
|
|
26087
|
+
const sessionDir = join10(this.sessionsDir, sessionId);
|
|
25923
26088
|
this.syncSession(sessionId, sessionDir);
|
|
25924
26089
|
}
|
|
25925
26090
|
}
|
|
25926
26091
|
syncSession(sessionId, sessionDir) {
|
|
25927
26092
|
this.ensureSessionRow(sessionId, sessionDir);
|
|
25928
|
-
const roundsDir =
|
|
25929
|
-
if (
|
|
26093
|
+
const roundsDir = join10(sessionDir, "rounds");
|
|
26094
|
+
if (existsSync5(roundsDir)) {
|
|
25930
26095
|
const rounds = readdirSync(roundsDir, { withFileTypes: true });
|
|
25931
26096
|
for (const roundEntry of rounds) {
|
|
25932
26097
|
if (!roundEntry.isDirectory()) continue;
|
|
25933
26098
|
const roundMatch = roundEntry.name.match(/^round-(\d+)$/);
|
|
25934
26099
|
if (!roundMatch) continue;
|
|
25935
26100
|
const roundNumber = parseInt(roundMatch[1] ?? "0", 10);
|
|
25936
|
-
const roundDir =
|
|
25937
|
-
const reviewsDir =
|
|
25938
|
-
if (
|
|
26101
|
+
const roundDir = join10(roundsDir, roundEntry.name);
|
|
26102
|
+
const reviewsDir = join10(roundDir, "reviews");
|
|
26103
|
+
if (existsSync5(reviewsDir)) {
|
|
25939
26104
|
const reviewFiles = readdirSync(reviewsDir).filter((f) => f.endsWith(".md"));
|
|
25940
26105
|
for (const reviewFile of reviewFiles) {
|
|
25941
|
-
const filePath =
|
|
26106
|
+
const filePath = join10(reviewsDir, reviewFile);
|
|
25942
26107
|
this.processReviewerOutput(sessionId, roundNumber, filePath, reviewFile);
|
|
25943
26108
|
}
|
|
25944
26109
|
}
|
|
25945
|
-
const
|
|
25946
|
-
if (
|
|
26110
|
+
const roundMetaPath = join10(roundDir, "round-meta.json");
|
|
26111
|
+
if (existsSync5(roundMetaPath)) {
|
|
26112
|
+
this.processRoundMeta(sessionId, roundNumber, roundMetaPath);
|
|
26113
|
+
}
|
|
26114
|
+
const finalPath = join10(roundDir, "final.md");
|
|
26115
|
+
if (existsSync5(finalPath)) {
|
|
25947
26116
|
this.processFinalMd(sessionId, roundNumber, finalPath);
|
|
25948
26117
|
}
|
|
25949
|
-
const finalHumanPath =
|
|
25950
|
-
if (
|
|
26118
|
+
const finalHumanPath = join10(roundDir, "final-human.md");
|
|
26119
|
+
if (existsSync5(finalHumanPath)) {
|
|
25951
26120
|
this.processGenericArtifact(sessionId, "final-human", finalHumanPath, roundNumber);
|
|
25952
26121
|
}
|
|
25953
|
-
const discoursePath =
|
|
25954
|
-
if (
|
|
26122
|
+
const discoursePath = join10(roundDir, "discourse.md");
|
|
26123
|
+
if (existsSync5(discoursePath)) {
|
|
25955
26124
|
this.processGenericArtifact(sessionId, "discourse", discoursePath, roundNumber);
|
|
25956
26125
|
}
|
|
25957
26126
|
}
|
|
25958
26127
|
}
|
|
25959
|
-
const mapDir =
|
|
25960
|
-
if (
|
|
26128
|
+
const mapDir = join10(sessionDir, "map", "runs");
|
|
26129
|
+
if (existsSync5(mapDir)) {
|
|
25961
26130
|
const runs = readdirSync(mapDir, { withFileTypes: true });
|
|
25962
26131
|
for (const runEntry of runs) {
|
|
25963
26132
|
if (!runEntry.isDirectory()) continue;
|
|
25964
26133
|
const runMatch = runEntry.name.match(/^run-(\d+)$/);
|
|
25965
26134
|
if (!runMatch) continue;
|
|
25966
26135
|
const runNumber = parseInt(runMatch[1] ?? "0", 10);
|
|
25967
|
-
const runDir =
|
|
25968
|
-
const
|
|
25969
|
-
if (
|
|
26136
|
+
const runDir = join10(mapDir, runEntry.name);
|
|
26137
|
+
const mapMetaPath = join10(runDir, "map-meta.json");
|
|
26138
|
+
if (existsSync5(mapMetaPath)) {
|
|
26139
|
+
this.processMapMeta(sessionId, runNumber, mapMetaPath);
|
|
26140
|
+
}
|
|
26141
|
+
const mapPath = join10(runDir, "map.md");
|
|
26142
|
+
if (existsSync5(mapPath)) {
|
|
25970
26143
|
this.processMapMd(sessionId, runNumber, mapPath);
|
|
25971
26144
|
}
|
|
25972
26145
|
const mapArtifacts = [
|
|
@@ -25975,8 +26148,8 @@ var FilesystemSync = class {
|
|
|
25975
26148
|
["requirements-mapping.md", "requirements-mapping"]
|
|
25976
26149
|
];
|
|
25977
26150
|
for (const [fileName, artifactType] of mapArtifacts) {
|
|
25978
|
-
const filePath =
|
|
25979
|
-
if (
|
|
26151
|
+
const filePath = join10(runDir, fileName);
|
|
26152
|
+
if (existsSync5(filePath)) {
|
|
25980
26153
|
this.processGenericArtifact(sessionId, artifactType, filePath, void 0, runNumber);
|
|
25981
26154
|
}
|
|
25982
26155
|
}
|
|
@@ -25987,8 +26160,8 @@ var FilesystemSync = class {
|
|
|
25987
26160
|
["discovered-standards.md", "discovered-standards"]
|
|
25988
26161
|
];
|
|
25989
26162
|
for (const [fileName, artifactType] of sessionArtifacts) {
|
|
25990
|
-
const filePath =
|
|
25991
|
-
if (
|
|
26163
|
+
const filePath = join10(sessionDir, fileName);
|
|
26164
|
+
if (existsSync5(filePath)) {
|
|
25992
26165
|
this.processGenericArtifact(sessionId, artifactType, filePath);
|
|
25993
26166
|
}
|
|
25994
26167
|
}
|
|
@@ -25997,17 +26170,17 @@ var FilesystemSync = class {
|
|
|
25997
26170
|
ensureSessionRow(sessionId, sessionDir) {
|
|
25998
26171
|
const branchMatch = sessionId.match(/^\d{4}-\d{2}-\d{2}-(.+)$/);
|
|
25999
26172
|
const branch = branchMatch?.[1] ?? "unknown";
|
|
26000
|
-
const hasRoundsDir =
|
|
26001
|
-
const hasMapDir =
|
|
26173
|
+
const hasRoundsDir = existsSync5(join10(sessionDir, "rounds"));
|
|
26174
|
+
const hasMapDir = existsSync5(join10(sessionDir, "map"));
|
|
26002
26175
|
const workflowType = hasMapDir && !hasRoundsDir ? "map" : "review";
|
|
26003
26176
|
let currentRound = 1;
|
|
26004
26177
|
if (hasRoundsDir) {
|
|
26005
|
-
const roundDirs = readdirSync(
|
|
26178
|
+
const roundDirs = readdirSync(join10(sessionDir, "rounds")).filter((d) => d.match(/^round-\d+$/));
|
|
26006
26179
|
currentRound = Math.max(1, roundDirs.length);
|
|
26007
26180
|
}
|
|
26008
26181
|
let currentMapRun = 1;
|
|
26009
|
-
const mapRunsDir =
|
|
26010
|
-
if (
|
|
26182
|
+
const mapRunsDir = join10(sessionDir, "map", "runs");
|
|
26183
|
+
if (existsSync5(mapRunsDir)) {
|
|
26011
26184
|
const runDirs = readdirSync(mapRunsDir).filter((d) => d.match(/^run-\d+$/));
|
|
26012
26185
|
currentMapRun = Math.max(1, runDirs.length);
|
|
26013
26186
|
}
|
|
@@ -26015,40 +26188,40 @@ var FilesystemSync = class {
|
|
|
26015
26188
|
let phaseNumber = 1;
|
|
26016
26189
|
let status = "active";
|
|
26017
26190
|
if (workflowType === "review" && hasRoundsDir) {
|
|
26018
|
-
const roundDir =
|
|
26019
|
-
if (
|
|
26191
|
+
const roundDir = join10(sessionDir, "rounds", `round-${currentRound}`);
|
|
26192
|
+
if (existsSync5(join10(roundDir, "final.md"))) {
|
|
26020
26193
|
phase = "complete";
|
|
26021
26194
|
phaseNumber = 8;
|
|
26022
26195
|
status = "closed";
|
|
26023
|
-
} else if (
|
|
26196
|
+
} else if (existsSync5(join10(roundDir, "discourse.md"))) {
|
|
26024
26197
|
phase = "synthesis";
|
|
26025
26198
|
phaseNumber = 7;
|
|
26026
|
-
} else if (
|
|
26199
|
+
} else if (existsSync5(join10(roundDir, "reviews")) && readdirSync(join10(roundDir, "reviews")).filter((f) => f.endsWith(".md")).length > 0) {
|
|
26027
26200
|
phase = "reviews";
|
|
26028
26201
|
phaseNumber = 4;
|
|
26029
|
-
} else if (
|
|
26202
|
+
} else if (existsSync5(join10(sessionDir, "context.md"))) {
|
|
26030
26203
|
phase = "analysis";
|
|
26031
26204
|
phaseNumber = 3;
|
|
26032
|
-
} else if (
|
|
26205
|
+
} else if (existsSync5(join10(sessionDir, "discovered-standards.md"))) {
|
|
26033
26206
|
phase = "change-context";
|
|
26034
26207
|
phaseNumber = 2;
|
|
26035
26208
|
}
|
|
26036
26209
|
} else if (workflowType === "map" && hasMapDir) {
|
|
26037
|
-
const runDir =
|
|
26038
|
-
if (
|
|
26210
|
+
const runDir = join10(mapRunsDir, `run-${currentMapRun}`);
|
|
26211
|
+
if (existsSync5(join10(runDir, "map.md"))) {
|
|
26039
26212
|
phase = "complete";
|
|
26040
26213
|
phaseNumber = 6;
|
|
26041
26214
|
status = "closed";
|
|
26042
|
-
} else if (
|
|
26215
|
+
} else if (existsSync5(join10(runDir, "requirements-mapping.md"))) {
|
|
26043
26216
|
phase = "synthesis";
|
|
26044
26217
|
phaseNumber = 5;
|
|
26045
|
-
} else if (
|
|
26218
|
+
} else if (existsSync5(join10(runDir, "flow-analysis.md"))) {
|
|
26046
26219
|
phase = "requirements-mapping";
|
|
26047
26220
|
phaseNumber = 4;
|
|
26048
|
-
} else if (
|
|
26221
|
+
} else if (existsSync5(join10(runDir, "topology.md"))) {
|
|
26049
26222
|
phase = "flow-analysis";
|
|
26050
26223
|
phaseNumber = 3;
|
|
26051
|
-
} else if (
|
|
26224
|
+
} else if (existsSync5(join10(sessionDir, "discovered-standards.md"))) {
|
|
26052
26225
|
phase = "topology";
|
|
26053
26226
|
phaseNumber = 2;
|
|
26054
26227
|
}
|
|
@@ -26106,15 +26279,21 @@ var FilesystemSync = class {
|
|
|
26106
26279
|
processMapMd(sessionId, runNumber, filePath) {
|
|
26107
26280
|
const existingRun = queryFirst(
|
|
26108
26281
|
this.db,
|
|
26109
|
-
"SELECT id, parsed_at FROM map_runs WHERE session_id = ? AND run_number = ?",
|
|
26282
|
+
"SELECT id, parsed_at, source FROM map_runs WHERE session_id = ? AND run_number = ?",
|
|
26110
26283
|
[sessionId, runNumber]
|
|
26111
26284
|
);
|
|
26112
26285
|
if (existingRun && this.shouldSkip(filePath, existingRun["parsed_at"] ?? null)) return;
|
|
26113
|
-
|
|
26286
|
+
if (existingRun?.["source"] === "orchestrator") {
|
|
26287
|
+
const content2 = readFileSync6(filePath, "utf-8");
|
|
26288
|
+
const action2 = this.upsertMarkdownArtifact(sessionId, "map", filePath, content2, void 0);
|
|
26289
|
+
this.emitArtifactEvent(action2, { sessionId, artifactType: "map", filePath });
|
|
26290
|
+
return;
|
|
26291
|
+
}
|
|
26292
|
+
const content = readFileSync6(filePath, "utf-8");
|
|
26114
26293
|
const parsed = parseMapMd(content);
|
|
26115
26294
|
this.db.run(
|
|
26116
|
-
`INSERT OR REPLACE INTO map_runs (session_id, run_number, file_count, map_md_path, parsed_at)
|
|
26117
|
-
VALUES (?, ?, ?, ?,
|
|
26295
|
+
`INSERT OR REPLACE INTO map_runs (session_id, run_number, file_count, map_md_path, parsed_at, source)
|
|
26296
|
+
VALUES (?, ?, ?, ?, ?, 'parser')`,
|
|
26118
26297
|
[sessionId, runNumber, parsed.sections.reduce((sum, s) => sum + s.files.length, 0), filePath, sqlNow()]
|
|
26119
26298
|
);
|
|
26120
26299
|
const runRow = queryFirst(
|
|
@@ -26192,10 +26371,10 @@ var FilesystemSync = class {
|
|
|
26192
26371
|
}
|
|
26193
26372
|
const session = queryFirst(
|
|
26194
26373
|
this.db,
|
|
26195
|
-
"SELECT current_phase, workflow_type FROM sessions WHERE id = ?",
|
|
26374
|
+
"SELECT current_phase, phase_number, workflow_type FROM sessions WHERE id = ?",
|
|
26196
26375
|
[sessionId]
|
|
26197
26376
|
);
|
|
26198
|
-
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)) {
|
|
26199
26378
|
this.db.run(
|
|
26200
26379
|
`UPDATE sessions SET current_phase = 'complete', phase_number = 6, status = 'closed', updated_at = datetime('now')
|
|
26201
26380
|
WHERE id = ?`,
|
|
@@ -26224,11 +26403,22 @@ var FilesystemSync = class {
|
|
|
26224
26403
|
);
|
|
26225
26404
|
const roundRow = queryFirst(
|
|
26226
26405
|
this.db,
|
|
26227
|
-
"SELECT id FROM review_rounds WHERE session_id = ? AND round_number = ?",
|
|
26406
|
+
"SELECT id, source FROM review_rounds WHERE session_id = ? AND round_number = ?",
|
|
26228
26407
|
[sessionId, roundNumber]
|
|
26229
26408
|
);
|
|
26230
26409
|
const roundId = roundRow?.["id"];
|
|
26231
26410
|
if (!roundId) return;
|
|
26411
|
+
if (roundRow?.["source"] === "orchestrator") {
|
|
26412
|
+
const content2 = readFileSync6(filePath, "utf-8");
|
|
26413
|
+
const action2 = this.upsertMarkdownArtifact(sessionId, "reviewer-output", filePath, content2, roundNumber);
|
|
26414
|
+
this.emitArtifactEvent(action2, {
|
|
26415
|
+
sessionId,
|
|
26416
|
+
artifactType: "reviewer-output",
|
|
26417
|
+
roundNumber,
|
|
26418
|
+
filePath
|
|
26419
|
+
});
|
|
26420
|
+
return;
|
|
26421
|
+
}
|
|
26232
26422
|
const nameMatch = fileName.replace(/\.md$/, "").match(/^(.+?)-(\d+)$/);
|
|
26233
26423
|
const reviewerType = nameMatch?.[1] ?? fileName.replace(/\.md$/, "");
|
|
26234
26424
|
const instanceNumber = nameMatch?.[2] ? parseInt(nameMatch[2], 10) : 1;
|
|
@@ -26238,7 +26428,7 @@ var FilesystemSync = class {
|
|
|
26238
26428
|
[roundId, reviewerType, instanceNumber]
|
|
26239
26429
|
);
|
|
26240
26430
|
if (existingOutput && this.shouldSkip(filePath, existingOutput["parsed_at"] ?? null)) return;
|
|
26241
|
-
const content =
|
|
26431
|
+
const content = readFileSync6(filePath, "utf-8");
|
|
26242
26432
|
const parsed = parseReviewerOutput(content);
|
|
26243
26433
|
this.db.run(
|
|
26244
26434
|
`INSERT OR REPLACE INTO reviewer_outputs (round_id, reviewer_type, instance_number, file_path, finding_count, parsed_at)
|
|
@@ -26311,8 +26501,8 @@ var FilesystemSync = class {
|
|
|
26311
26501
|
filePath
|
|
26312
26502
|
});
|
|
26313
26503
|
}
|
|
26314
|
-
// ── 6.
|
|
26315
|
-
|
|
26504
|
+
// ── 6.3b: Round Meta (Orchestrator-First) ──
|
|
26505
|
+
processRoundMeta(sessionId, roundNumber, filePath) {
|
|
26316
26506
|
this.db.run(
|
|
26317
26507
|
`INSERT OR IGNORE INTO review_rounds (session_id, round_number)
|
|
26318
26508
|
VALUES (?, ?)`,
|
|
@@ -26320,46 +26510,332 @@ var FilesystemSync = class {
|
|
|
26320
26510
|
);
|
|
26321
26511
|
const existingRound = queryFirst(
|
|
26322
26512
|
this.db,
|
|
26323
|
-
"SELECT parsed_at FROM review_rounds WHERE session_id = ? AND round_number = ?",
|
|
26513
|
+
"SELECT parsed_at, source FROM review_rounds WHERE session_id = ? AND round_number = ?",
|
|
26324
26514
|
[sessionId, roundNumber]
|
|
26325
26515
|
);
|
|
26326
|
-
if (existingRound && this.shouldSkip(filePath, existingRound["parsed_at"] ?? null)) return;
|
|
26327
|
-
|
|
26328
|
-
|
|
26516
|
+
if (existingRound?.["source"] === "orchestrator" && this.shouldSkip(filePath, existingRound["parsed_at"] ?? null)) return;
|
|
26517
|
+
let raw;
|
|
26518
|
+
try {
|
|
26519
|
+
raw = JSON.parse(readFileSync6(filePath, "utf-8"));
|
|
26520
|
+
} catch {
|
|
26521
|
+
console.error(`[FilesystemSync] Failed to parse ${filePath}`);
|
|
26522
|
+
return;
|
|
26523
|
+
}
|
|
26524
|
+
const meta = raw;
|
|
26525
|
+
if (meta.schema_version !== 1 || !meta.verdict || !Array.isArray(meta.reviewers)) {
|
|
26526
|
+
console.error(`[FilesystemSync] Invalid round-meta.json at ${filePath}`);
|
|
26527
|
+
return;
|
|
26528
|
+
}
|
|
26529
|
+
const allFindings = meta.reviewers.flatMap((r) => r.findings ?? []);
|
|
26530
|
+
const blockerCount = allFindings.filter((f) => f.category === "blocker").length;
|
|
26531
|
+
const shouldFixCount = allFindings.filter((f) => f.category === "should_fix").length;
|
|
26532
|
+
const suggestionCount = allFindings.filter((f) => f.category === "suggestion").length;
|
|
26533
|
+
const reviewerCount = meta.reviewers.length;
|
|
26534
|
+
const totalFindingCount = allFindings.length;
|
|
26535
|
+
this.db.run("BEGIN TRANSACTION");
|
|
26536
|
+
try {
|
|
26537
|
+
this.db.run(
|
|
26538
|
+
`UPDATE review_rounds
|
|
26539
|
+
SET verdict = ?, blocker_count = ?, suggestion_count = ?, should_fix_count = ?,
|
|
26540
|
+
reviewer_count = ?, total_finding_count = ?, source = 'orchestrator', parsed_at = ?
|
|
26541
|
+
WHERE session_id = ? AND round_number = ?`,
|
|
26542
|
+
[
|
|
26543
|
+
meta.verdict,
|
|
26544
|
+
blockerCount,
|
|
26545
|
+
suggestionCount,
|
|
26546
|
+
shouldFixCount,
|
|
26547
|
+
reviewerCount,
|
|
26548
|
+
totalFindingCount,
|
|
26549
|
+
sqlNow(),
|
|
26550
|
+
sessionId,
|
|
26551
|
+
roundNumber
|
|
26552
|
+
]
|
|
26553
|
+
);
|
|
26554
|
+
const roundRow = queryFirst(
|
|
26555
|
+
this.db,
|
|
26556
|
+
"SELECT id FROM review_rounds WHERE session_id = ? AND round_number = ?",
|
|
26557
|
+
[sessionId, roundNumber]
|
|
26558
|
+
);
|
|
26559
|
+
const roundId = roundRow?.["id"];
|
|
26560
|
+
if (!roundId) {
|
|
26561
|
+
this.db.run("COMMIT");
|
|
26562
|
+
return;
|
|
26563
|
+
}
|
|
26564
|
+
const roundDir = dirname7(filePath);
|
|
26565
|
+
for (const reviewer of meta.reviewers) {
|
|
26566
|
+
const reviewerType = reviewer.type ?? "unknown";
|
|
26567
|
+
const instanceNumber = reviewer.instance ?? 1;
|
|
26568
|
+
const findings = reviewer.findings ?? [];
|
|
26569
|
+
const reviewerMdPath = join10(roundDir, "reviews", `${reviewerType}-${instanceNumber}.md`);
|
|
26570
|
+
this.db.run(
|
|
26571
|
+
`INSERT OR REPLACE INTO reviewer_outputs (round_id, reviewer_type, instance_number, file_path, finding_count, parsed_at)
|
|
26572
|
+
VALUES (?, ?, ?, ?, ?, ?)`,
|
|
26573
|
+
[roundId, reviewerType, instanceNumber, reviewerMdPath, findings.length, sqlNow()]
|
|
26574
|
+
);
|
|
26575
|
+
const outputRow = queryFirst(
|
|
26576
|
+
this.db,
|
|
26577
|
+
"SELECT id FROM reviewer_outputs WHERE round_id = ? AND reviewer_type = ? AND instance_number = ?",
|
|
26578
|
+
[roundId, reviewerType, instanceNumber]
|
|
26579
|
+
);
|
|
26580
|
+
const outputId = outputRow?.["id"];
|
|
26581
|
+
if (!outputId) continue;
|
|
26582
|
+
const stashedFindingProgress = /* @__PURE__ */ new Map();
|
|
26583
|
+
const findingProgressResult = this.db.exec(
|
|
26584
|
+
`SELECT rf.title, rf.severity, rf.file_path, ufp.status, ufp.updated_at
|
|
26585
|
+
FROM user_finding_progress ufp
|
|
26586
|
+
JOIN review_findings rf ON rf.id = ufp.finding_id
|
|
26587
|
+
WHERE rf.reviewer_output_id = ?`,
|
|
26588
|
+
[outputId]
|
|
26589
|
+
);
|
|
26590
|
+
if (findingProgressResult[0]) {
|
|
26591
|
+
for (const row of findingProgressResult[0].values) {
|
|
26592
|
+
const key = `${row[0]}|${row[1]}|${row[2]}`;
|
|
26593
|
+
stashedFindingProgress.set(key, {
|
|
26594
|
+
status: row[3],
|
|
26595
|
+
updatedAt: row[4]
|
|
26596
|
+
});
|
|
26597
|
+
}
|
|
26598
|
+
}
|
|
26599
|
+
this.db.run("DELETE FROM review_findings WHERE reviewer_output_id = ?", [outputId]);
|
|
26600
|
+
for (const finding of findings) {
|
|
26601
|
+
this.db.run(
|
|
26602
|
+
`INSERT INTO review_findings (reviewer_output_id, title, severity, file_path, line_start, line_end, summary, is_blocker, parsed_at)
|
|
26603
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
26604
|
+
[
|
|
26605
|
+
outputId,
|
|
26606
|
+
finding.title ?? "",
|
|
26607
|
+
finding.severity ?? "info",
|
|
26608
|
+
finding.file_path ?? null,
|
|
26609
|
+
finding.line_start ?? null,
|
|
26610
|
+
finding.line_end ?? null,
|
|
26611
|
+
finding.summary ?? null,
|
|
26612
|
+
finding.category === "blocker" ? 1 : 0,
|
|
26613
|
+
sqlNow()
|
|
26614
|
+
]
|
|
26615
|
+
);
|
|
26616
|
+
const key = `${finding.title ?? ""}|${finding.severity ?? "info"}|${finding.file_path ?? ""}`;
|
|
26617
|
+
const stashed = stashedFindingProgress.get(key);
|
|
26618
|
+
if (stashed) {
|
|
26619
|
+
const newFindingRow = queryFirst(
|
|
26620
|
+
this.db,
|
|
26621
|
+
"SELECT id FROM review_findings WHERE reviewer_output_id = ? AND title = ? AND severity = ? AND file_path IS ?",
|
|
26622
|
+
[outputId, finding.title ?? "", finding.severity ?? "info", finding.file_path ?? null]
|
|
26623
|
+
);
|
|
26624
|
+
if (newFindingRow) {
|
|
26625
|
+
this.db.run(
|
|
26626
|
+
`INSERT OR REPLACE INTO user_finding_progress (finding_id, status, updated_at)
|
|
26627
|
+
VALUES (?, ?, ?)`,
|
|
26628
|
+
[newFindingRow["id"], stashed.status, stashed.updatedAt]
|
|
26629
|
+
);
|
|
26630
|
+
}
|
|
26631
|
+
}
|
|
26632
|
+
}
|
|
26633
|
+
}
|
|
26634
|
+
this.db.run("COMMIT");
|
|
26635
|
+
} catch (err) {
|
|
26636
|
+
this.db.run("ROLLBACK");
|
|
26637
|
+
console.error(`[FilesystemSync] Error in processRoundMeta for ${filePath}:`, err);
|
|
26638
|
+
return;
|
|
26639
|
+
}
|
|
26640
|
+
this.io?.to(`session:${sessionId}`).emit("round:updated", {
|
|
26641
|
+
sessionId,
|
|
26642
|
+
roundNumber,
|
|
26643
|
+
verdict: meta.verdict,
|
|
26644
|
+
blockerCount,
|
|
26645
|
+
shouldFixCount,
|
|
26646
|
+
suggestionCount,
|
|
26647
|
+
reviewerCount,
|
|
26648
|
+
totalFindingCount,
|
|
26649
|
+
source: "orchestrator"
|
|
26650
|
+
});
|
|
26651
|
+
}
|
|
26652
|
+
// ── 6.2b: Map-meta.json Integration ──
|
|
26653
|
+
processMapMeta(sessionId, runNumber, filePath) {
|
|
26329
26654
|
this.db.run(
|
|
26330
|
-
`
|
|
26331
|
-
|
|
26332
|
-
[
|
|
26333
|
-
parsed.verdict,
|
|
26334
|
-
parsed.blockerCount,
|
|
26335
|
-
parsed.suggestionCount,
|
|
26336
|
-
parsed.shouldFixCount,
|
|
26337
|
-
filePath,
|
|
26338
|
-
sqlNow(),
|
|
26339
|
-
sessionId,
|
|
26340
|
-
roundNumber
|
|
26341
|
-
]
|
|
26655
|
+
`INSERT OR IGNORE INTO map_runs (session_id, run_number)
|
|
26656
|
+
VALUES (?, ?)`,
|
|
26657
|
+
[sessionId, runNumber]
|
|
26342
26658
|
);
|
|
26343
|
-
const
|
|
26659
|
+
const existingRun = queryFirst(
|
|
26344
26660
|
this.db,
|
|
26345
|
-
|
|
26346
|
-
|
|
26347
|
-
|
|
26348
|
-
|
|
26661
|
+
"SELECT id, parsed_at, source FROM map_runs WHERE session_id = ? AND run_number = ?",
|
|
26662
|
+
[sessionId, runNumber]
|
|
26663
|
+
);
|
|
26664
|
+
if (existingRun?.["source"] === "orchestrator" && this.shouldSkip(filePath, existingRun["parsed_at"] ?? null)) return;
|
|
26665
|
+
let raw;
|
|
26666
|
+
try {
|
|
26667
|
+
raw = JSON.parse(readFileSync6(filePath, "utf-8"));
|
|
26668
|
+
} catch {
|
|
26669
|
+
console.error(`[FilesystemSync] Failed to parse ${filePath}`);
|
|
26670
|
+
return;
|
|
26671
|
+
}
|
|
26672
|
+
const meta = raw;
|
|
26673
|
+
if (meta.schema_version !== 1 || !Array.isArray(meta.sections)) {
|
|
26674
|
+
console.error(`[FilesystemSync] Invalid map-meta.json at ${filePath}`);
|
|
26675
|
+
return;
|
|
26676
|
+
}
|
|
26677
|
+
const sectionCount = meta.sections.length;
|
|
26678
|
+
const fileCount = meta.sections.reduce((sum, s) => sum + (s.files?.length ?? 0), 0);
|
|
26679
|
+
this.db.run("BEGIN TRANSACTION");
|
|
26680
|
+
try {
|
|
26681
|
+
this.db.run(
|
|
26682
|
+
`UPDATE map_runs
|
|
26683
|
+
SET file_count = ?, section_count = ?, source = 'orchestrator', parsed_at = ?
|
|
26684
|
+
WHERE session_id = ? AND run_number = ?`,
|
|
26685
|
+
[fileCount, sectionCount, sqlNow(), sessionId, runNumber]
|
|
26686
|
+
);
|
|
26687
|
+
const runRow = queryFirst(
|
|
26688
|
+
this.db,
|
|
26689
|
+
"SELECT id FROM map_runs WHERE session_id = ? AND run_number = ?",
|
|
26690
|
+
[sessionId, runNumber]
|
|
26691
|
+
);
|
|
26692
|
+
const mapRunId = runRow?.["id"];
|
|
26693
|
+
if (!mapRunId) {
|
|
26694
|
+
this.db.run("COMMIT");
|
|
26695
|
+
return;
|
|
26696
|
+
}
|
|
26697
|
+
const stashedFileProgress = /* @__PURE__ */ new Map();
|
|
26698
|
+
const progressResult = this.db.exec(
|
|
26699
|
+
`SELECT mf.file_path, ufp.is_reviewed, ufp.reviewed_at
|
|
26700
|
+
FROM user_file_progress ufp
|
|
26701
|
+
JOIN map_files mf ON mf.id = ufp.map_file_id
|
|
26702
|
+
JOIN map_sections ms ON ms.id = mf.section_id
|
|
26703
|
+
WHERE ms.map_run_id = ?`,
|
|
26704
|
+
[mapRunId]
|
|
26705
|
+
);
|
|
26706
|
+
if (progressResult[0]) {
|
|
26707
|
+
for (const row of progressResult[0].values) {
|
|
26708
|
+
const fp = row[0];
|
|
26709
|
+
stashedFileProgress.set(fp, {
|
|
26710
|
+
isReviewed: row[1],
|
|
26711
|
+
reviewedAt: row[2]
|
|
26712
|
+
});
|
|
26713
|
+
}
|
|
26714
|
+
}
|
|
26715
|
+
const oldSections = this.db.exec(
|
|
26716
|
+
"SELECT id FROM map_sections WHERE map_run_id = ?",
|
|
26717
|
+
[mapRunId]
|
|
26718
|
+
);
|
|
26719
|
+
if (oldSections[0]) {
|
|
26720
|
+
for (const row of oldSections[0].values) {
|
|
26721
|
+
this.db.run("DELETE FROM map_files WHERE section_id = ?", [row[0]]);
|
|
26722
|
+
}
|
|
26723
|
+
}
|
|
26724
|
+
this.db.run("DELETE FROM map_sections WHERE map_run_id = ?", [mapRunId]);
|
|
26725
|
+
for (const section of meta.sections) {
|
|
26726
|
+
const sectionNumber = section.section_number ?? 0;
|
|
26727
|
+
const title = section.title ?? "Untitled";
|
|
26728
|
+
const description = section.description ?? null;
|
|
26729
|
+
const files = section.files ?? [];
|
|
26730
|
+
this.db.run(
|
|
26731
|
+
`INSERT OR REPLACE INTO map_sections (map_run_id, section_number, title, description, file_count, display_order)
|
|
26732
|
+
VALUES (?, ?, ?, ?, ?, ?)`,
|
|
26733
|
+
[mapRunId, sectionNumber, title, description, files.length, sectionNumber]
|
|
26734
|
+
);
|
|
26735
|
+
const sectionRow = queryFirst(
|
|
26736
|
+
this.db,
|
|
26737
|
+
"SELECT id FROM map_sections WHERE map_run_id = ? AND section_number = ?",
|
|
26738
|
+
[mapRunId, sectionNumber]
|
|
26739
|
+
);
|
|
26740
|
+
const sectionId = sectionRow?.["id"];
|
|
26741
|
+
if (!sectionId) continue;
|
|
26742
|
+
for (let fi = 0; fi < files.length; fi++) {
|
|
26743
|
+
const file = files[fi];
|
|
26744
|
+
if (!file) continue;
|
|
26745
|
+
this.db.run(
|
|
26746
|
+
`INSERT OR REPLACE INTO map_files (section_id, file_path, role, lines_added, lines_deleted, display_order)
|
|
26747
|
+
VALUES (?, ?, ?, ?, ?, ?)`,
|
|
26748
|
+
[sectionId, file.file_path ?? "", file.role ?? null, file.lines_added ?? 0, file.lines_deleted ?? 0, fi]
|
|
26749
|
+
);
|
|
26750
|
+
const stashed = stashedFileProgress.get(file.file_path ?? "");
|
|
26751
|
+
if (stashed) {
|
|
26752
|
+
const newFileRow = queryFirst(
|
|
26753
|
+
this.db,
|
|
26754
|
+
"SELECT id FROM map_files WHERE section_id = ? AND file_path = ?",
|
|
26755
|
+
[sectionId, file.file_path ?? ""]
|
|
26756
|
+
);
|
|
26757
|
+
if (newFileRow) {
|
|
26758
|
+
this.db.run(
|
|
26759
|
+
`INSERT OR REPLACE INTO user_file_progress (map_file_id, is_reviewed, reviewed_at)
|
|
26760
|
+
VALUES (?, ?, ?)`,
|
|
26761
|
+
[newFileRow["id"], stashed.isReviewed, stashed.reviewedAt]
|
|
26762
|
+
);
|
|
26763
|
+
}
|
|
26764
|
+
}
|
|
26765
|
+
}
|
|
26766
|
+
}
|
|
26767
|
+
this.db.run("COMMIT");
|
|
26768
|
+
} catch (err) {
|
|
26769
|
+
this.db.run("ROLLBACK");
|
|
26770
|
+
console.error(`[FilesystemSync] Error in processMapMeta for ${filePath}:`, err);
|
|
26771
|
+
return;
|
|
26772
|
+
}
|
|
26773
|
+
this.io?.to(`session:${sessionId}`).emit("map:updated", {
|
|
26774
|
+
sessionId,
|
|
26775
|
+
runNumber,
|
|
26776
|
+
fileCount,
|
|
26777
|
+
sectionCount,
|
|
26778
|
+
source: "orchestrator"
|
|
26779
|
+
});
|
|
26780
|
+
}
|
|
26781
|
+
// ── 6.4: Final.md Integration ──
|
|
26782
|
+
processFinalMd(sessionId, roundNumber, filePath) {
|
|
26783
|
+
this.db.run(
|
|
26784
|
+
`INSERT OR IGNORE INTO review_rounds (session_id, round_number)
|
|
26785
|
+
VALUES (?, ?)`,
|
|
26786
|
+
[sessionId, roundNumber]
|
|
26787
|
+
);
|
|
26788
|
+
const existingRound = queryFirst(
|
|
26789
|
+
this.db,
|
|
26790
|
+
"SELECT parsed_at, source FROM review_rounds WHERE session_id = ? AND round_number = ?",
|
|
26349
26791
|
[sessionId, roundNumber]
|
|
26350
26792
|
);
|
|
26351
|
-
|
|
26793
|
+
const isOrchestratorSource = existingRound?.["source"] === "orchestrator";
|
|
26794
|
+
if (!isOrchestratorSource && existingRound && this.shouldSkip(filePath, existingRound["parsed_at"] ?? null)) return;
|
|
26795
|
+
const content = readFileSync6(filePath, "utf-8");
|
|
26796
|
+
if (isOrchestratorSource) {
|
|
26352
26797
|
this.db.run(
|
|
26353
|
-
|
|
26354
|
-
|
|
26798
|
+
`UPDATE review_rounds SET final_md_path = ?, parsed_at = ?
|
|
26799
|
+
WHERE session_id = ? AND round_number = ?`,
|
|
26800
|
+
[filePath, sqlNow(), sessionId, roundNumber]
|
|
26801
|
+
);
|
|
26802
|
+
} else {
|
|
26803
|
+
const parsed = parseFinalMd(content);
|
|
26804
|
+
this.db.run(
|
|
26805
|
+
`UPDATE review_rounds SET verdict = ?, blocker_count = ?, suggestion_count = ?, should_fix_count = ?, final_md_path = ?, parsed_at = ?, source = 'parser'
|
|
26806
|
+
WHERE session_id = ? AND round_number = ?`,
|
|
26807
|
+
[
|
|
26808
|
+
parsed.verdict,
|
|
26809
|
+
parsed.blockerCount,
|
|
26810
|
+
parsed.suggestionCount,
|
|
26811
|
+
parsed.shouldFixCount,
|
|
26812
|
+
filePath,
|
|
26813
|
+
sqlNow(),
|
|
26814
|
+
sessionId,
|
|
26815
|
+
roundNumber
|
|
26816
|
+
]
|
|
26817
|
+
);
|
|
26818
|
+
const actualBlockers = queryScalar(
|
|
26819
|
+
this.db,
|
|
26820
|
+
`SELECT COUNT(*) FROM review_findings rf
|
|
26821
|
+
JOIN reviewer_outputs ro ON rf.reviewer_output_id = ro.id
|
|
26822
|
+
WHERE ro.round_id = (SELECT id FROM review_rounds WHERE session_id = ? AND round_number = ?)
|
|
26823
|
+
AND rf.is_blocker = 1`,
|
|
26824
|
+
[sessionId, roundNumber]
|
|
26355
26825
|
);
|
|
26826
|
+
if (actualBlockers !== null && actualBlockers !== parsed.blockerCount) {
|
|
26827
|
+
this.db.run(
|
|
26828
|
+
"UPDATE review_rounds SET blocker_count = ? WHERE session_id = ? AND round_number = ?",
|
|
26829
|
+
[actualBlockers, sessionId, roundNumber]
|
|
26830
|
+
);
|
|
26831
|
+
}
|
|
26356
26832
|
}
|
|
26357
26833
|
const session = queryFirst(
|
|
26358
26834
|
this.db,
|
|
26359
26835
|
"SELECT current_phase, phase_number, status FROM sessions WHERE id = ?",
|
|
26360
26836
|
[sessionId]
|
|
26361
26837
|
);
|
|
26362
|
-
if (session && session["current_phase"] !== "complete") {
|
|
26838
|
+
if (session && (session["current_phase"] !== "complete" || session["phase_number"] < 8)) {
|
|
26363
26839
|
this.db.run(
|
|
26364
26840
|
`UPDATE sessions SET current_phase = 'complete', phase_number = 8, status = 'closed', updated_at = datetime('now')
|
|
26365
26841
|
WHERE id = ?`,
|
|
@@ -26389,7 +26865,7 @@ var FilesystemSync = class {
|
|
|
26389
26865
|
[sessionId, artifactType, relPath]
|
|
26390
26866
|
);
|
|
26391
26867
|
if (existing && this.shouldSkip(filePath, existing["parsed_at"] ?? null)) return;
|
|
26392
|
-
const content =
|
|
26868
|
+
const content = readFileSync6(filePath, "utf-8");
|
|
26393
26869
|
const action = this.upsertMarkdownArtifact(sessionId, artifactType, filePath, content, roundNumber);
|
|
26394
26870
|
this.emitArtifactEvent(action, {
|
|
26395
26871
|
sessionId,
|
|
@@ -26401,7 +26877,7 @@ var FilesystemSync = class {
|
|
|
26401
26877
|
// ── 6.6: Chokidar Watcher ──
|
|
26402
26878
|
startWatching() {
|
|
26403
26879
|
if (this.watcher) return;
|
|
26404
|
-
this.watcher =
|
|
26880
|
+
this.watcher = watch2(this.sessionsDir, {
|
|
26405
26881
|
persistent: true,
|
|
26406
26882
|
ignoreInitial: true,
|
|
26407
26883
|
depth: 10,
|
|
@@ -26427,7 +26903,7 @@ var FilesystemSync = class {
|
|
|
26427
26903
|
this.debounceTimers.clear();
|
|
26428
26904
|
}
|
|
26429
26905
|
handleFileChange(filePath) {
|
|
26430
|
-
if (!filePath.endsWith(".md")) return;
|
|
26906
|
+
if (!filePath.endsWith(".md") && !filePath.endsWith(".json")) return;
|
|
26431
26907
|
const existing = this.debounceTimers.get(filePath);
|
|
26432
26908
|
if (existing) clearTimeout(existing);
|
|
26433
26909
|
this.debounceTimers.set(
|
|
@@ -26448,7 +26924,7 @@ var FilesystemSync = class {
|
|
|
26448
26924
|
const parts = relFromSessions.split("/");
|
|
26449
26925
|
const sessionId = parts[0];
|
|
26450
26926
|
if (!sessionId) return;
|
|
26451
|
-
const sessionDir =
|
|
26927
|
+
const sessionDir = join10(this.sessionsDir, sessionId);
|
|
26452
26928
|
this.ensureSessionRow(sessionId, sessionDir);
|
|
26453
26929
|
const fileName = basename2(filePath);
|
|
26454
26930
|
const reviewerMatch = relFromSessions.match(/rounds\/round-(\d+)\/reviews\/(.+\.md)$/);
|
|
@@ -26457,6 +26933,12 @@ var FilesystemSync = class {
|
|
|
26457
26933
|
this.processReviewerOutput(sessionId, roundNumber, filePath, reviewerMatch[2] ?? "");
|
|
26458
26934
|
return;
|
|
26459
26935
|
}
|
|
26936
|
+
const roundMetaMatch = relFromSessions.match(/rounds\/round-(\d+)\/round-meta\.json$/);
|
|
26937
|
+
if (roundMetaMatch) {
|
|
26938
|
+
const roundNumber = parseInt(roundMetaMatch[1] ?? "0", 10);
|
|
26939
|
+
this.processRoundMeta(sessionId, roundNumber, filePath);
|
|
26940
|
+
return;
|
|
26941
|
+
}
|
|
26460
26942
|
const finalMatch = relFromSessions.match(/rounds\/round-(\d+)\/final\.md$/);
|
|
26461
26943
|
if (finalMatch) {
|
|
26462
26944
|
const roundNumber = parseInt(finalMatch[1] ?? "0", 10);
|
|
@@ -26475,6 +26957,12 @@ var FilesystemSync = class {
|
|
|
26475
26957
|
this.processGenericArtifact(sessionId, "discourse", filePath, roundNumber);
|
|
26476
26958
|
return;
|
|
26477
26959
|
}
|
|
26960
|
+
const mapMetaMatch = relFromSessions.match(/map\/runs\/run-(\d+)\/map-meta\.json$/);
|
|
26961
|
+
if (mapMetaMatch) {
|
|
26962
|
+
const runNumber = parseInt(mapMetaMatch[1] ?? "0", 10);
|
|
26963
|
+
this.processMapMeta(sessionId, runNumber, filePath);
|
|
26964
|
+
return;
|
|
26965
|
+
}
|
|
26478
26966
|
const mapMatch = relFromSessions.match(/map\/runs\/run-(\d+)\/map\.md$/);
|
|
26479
26967
|
if (mapMatch) {
|
|
26480
26968
|
const runNumber = parseInt(mapMatch[1] ?? "0", 10);
|
|
@@ -26508,8 +26996,8 @@ var FilesystemSync = class {
|
|
|
26508
26996
|
};
|
|
26509
26997
|
|
|
26510
26998
|
// src/server/services/db-sync-watcher.ts
|
|
26511
|
-
import { existsSync as
|
|
26512
|
-
import { watch as
|
|
26999
|
+
import { existsSync as existsSync6, readFileSync as readFileSync7, statSync as statSync2 } from "node:fs";
|
|
27000
|
+
import { watch as watch3 } from "chokidar";
|
|
26513
27001
|
import initSqlJs3 from "sql.js";
|
|
26514
27002
|
function col(row, key) {
|
|
26515
27003
|
return row[key] ?? null;
|
|
@@ -26530,7 +27018,7 @@ var DbSyncWatcher = class {
|
|
|
26530
27018
|
* Initialize the WASM runtime (called once at startup).
|
|
26531
27019
|
*/
|
|
26532
27020
|
async init() {
|
|
26533
|
-
const wasmBuffer =
|
|
27021
|
+
const wasmBuffer = readFileSync7(locateWasm());
|
|
26534
27022
|
this.wasmBinary = wasmBuffer.buffer.slice(
|
|
26535
27023
|
wasmBuffer.byteOffset,
|
|
26536
27024
|
wasmBuffer.byteOffset + wasmBuffer.byteLength
|
|
@@ -26541,12 +27029,12 @@ var DbSyncWatcher = class {
|
|
|
26541
27029
|
* Start watching the DB file for external changes.
|
|
26542
27030
|
*/
|
|
26543
27031
|
startWatching() {
|
|
26544
|
-
if (!
|
|
27032
|
+
if (!existsSync6(this.dbFilePath)) return;
|
|
26545
27033
|
try {
|
|
26546
27034
|
this.lastMtime = statSync2(this.dbFilePath).mtimeMs;
|
|
26547
27035
|
} catch {
|
|
26548
27036
|
}
|
|
26549
|
-
this.watcher =
|
|
27037
|
+
this.watcher = watch3(this.dbFilePath, {
|
|
26550
27038
|
// Also watch WAL/SHM files that SQLite may create
|
|
26551
27039
|
persistent: true,
|
|
26552
27040
|
ignoreInitial: true,
|
|
@@ -26592,7 +27080,7 @@ var DbSyncWatcher = class {
|
|
|
26592
27080
|
* to avoid overwriting CLI changes.
|
|
26593
27081
|
*/
|
|
26594
27082
|
syncFromDisk() {
|
|
26595
|
-
if (!this.SQL || !
|
|
27083
|
+
if (!this.SQL || !existsSync6(this.dbFilePath)) return;
|
|
26596
27084
|
let currentMtime;
|
|
26597
27085
|
try {
|
|
26598
27086
|
currentMtime = statSync2(this.dbFilePath).mtimeMs;
|
|
@@ -26603,7 +27091,7 @@ var DbSyncWatcher = class {
|
|
|
26603
27091
|
this.lastMtime = currentMtime;
|
|
26604
27092
|
let diskDb = null;
|
|
26605
27093
|
try {
|
|
26606
|
-
const fileBuffer =
|
|
27094
|
+
const fileBuffer = readFileSync7(this.dbFilePath);
|
|
26607
27095
|
diskDb = new this.SQL.Database(fileBuffer);
|
|
26608
27096
|
this.syncSessions(diskDb);
|
|
26609
27097
|
this.syncEvents(diskDb);
|
|
@@ -26698,7 +27186,7 @@ var DbSyncWatcher = class {
|
|
|
26698
27186
|
const diskEvents = resultToRows(
|
|
26699
27187
|
diskDb.exec("SELECT * FROM orchestration_events ORDER BY id ASC")
|
|
26700
27188
|
);
|
|
26701
|
-
|
|
27189
|
+
const newEvents = [];
|
|
26702
27190
|
const affectedSessions = /* @__PURE__ */ new Set();
|
|
26703
27191
|
for (const row of diskEvents) {
|
|
26704
27192
|
const eventId = col(row, "id");
|
|
@@ -26722,15 +27210,128 @@ var DbSyncWatcher = class {
|
|
|
26722
27210
|
col(row, "created_at")
|
|
26723
27211
|
]
|
|
26724
27212
|
);
|
|
26725
|
-
|
|
27213
|
+
newEvents.push(row);
|
|
26726
27214
|
affectedSessions.add(sessionId);
|
|
26727
27215
|
}
|
|
26728
|
-
|
|
27216
|
+
for (const row of newEvents) {
|
|
27217
|
+
const eventType = col(row, "event_type");
|
|
27218
|
+
const sessionId = col(row, "session_id");
|
|
27219
|
+
const metadataStr = col(row, "metadata");
|
|
27220
|
+
if (eventType === "round_completed") {
|
|
27221
|
+
const roundNumber = col(row, "round");
|
|
27222
|
+
if (sessionId && roundNumber && metadataStr) {
|
|
27223
|
+
this.processRoundCompletedEvent(sessionId, roundNumber, metadataStr);
|
|
27224
|
+
}
|
|
27225
|
+
} else if (eventType === "map_completed") {
|
|
27226
|
+
const runNumber = col(row, "round");
|
|
27227
|
+
if (sessionId && runNumber && metadataStr) {
|
|
27228
|
+
this.processMapCompletedEvent(sessionId, runNumber, metadataStr);
|
|
27229
|
+
}
|
|
27230
|
+
}
|
|
27231
|
+
}
|
|
27232
|
+
if (newEvents.length > 0) {
|
|
26729
27233
|
for (const sessionId of affectedSessions) {
|
|
26730
|
-
this.io.emit("session:events", { session_id: sessionId });
|
|
27234
|
+
this.io.to(`session:${sessionId}`).emit("session:events", { session_id: sessionId });
|
|
26731
27235
|
}
|
|
26732
27236
|
}
|
|
26733
27237
|
}
|
|
27238
|
+
/**
|
|
27239
|
+
* Process a `round_completed` orchestration event.
|
|
27240
|
+
* Upserts review_rounds with orchestrator data for real-time dashboard updates.
|
|
27241
|
+
* Idempotent — skips if round already has source='orchestrator'.
|
|
27242
|
+
*/
|
|
27243
|
+
processRoundCompletedEvent(sessionId, roundNumber, metadataStr) {
|
|
27244
|
+
let metadata;
|
|
27245
|
+
try {
|
|
27246
|
+
metadata = JSON.parse(metadataStr);
|
|
27247
|
+
} catch {
|
|
27248
|
+
return;
|
|
27249
|
+
}
|
|
27250
|
+
const existing = this.db.exec(
|
|
27251
|
+
"SELECT source FROM review_rounds WHERE session_id = ? AND round_number = ?",
|
|
27252
|
+
[sessionId, roundNumber]
|
|
27253
|
+
);
|
|
27254
|
+
const rows = resultToRows(existing);
|
|
27255
|
+
if (rows.length > 0 && col(rows[0], "source") === "orchestrator") {
|
|
27256
|
+
return;
|
|
27257
|
+
}
|
|
27258
|
+
this.db.run(
|
|
27259
|
+
`INSERT OR IGNORE INTO review_rounds (session_id, round_number)
|
|
27260
|
+
VALUES (?, ?)`,
|
|
27261
|
+
[sessionId, roundNumber]
|
|
27262
|
+
);
|
|
27263
|
+
this.db.run(
|
|
27264
|
+
`UPDATE review_rounds
|
|
27265
|
+
SET verdict = ?, blocker_count = ?, suggestion_count = ?, should_fix_count = ?,
|
|
27266
|
+
reviewer_count = ?, total_finding_count = ?, source = 'orchestrator',
|
|
27267
|
+
parsed_at = datetime('now')
|
|
27268
|
+
WHERE session_id = ? AND round_number = ?`,
|
|
27269
|
+
[
|
|
27270
|
+
metadata.verdict ?? null,
|
|
27271
|
+
metadata.blocker_count ?? 0,
|
|
27272
|
+
metadata.suggestion_count ?? 0,
|
|
27273
|
+
metadata.should_fix_count ?? 0,
|
|
27274
|
+
metadata.reviewer_count ?? 0,
|
|
27275
|
+
metadata.total_finding_count ?? 0,
|
|
27276
|
+
sessionId,
|
|
27277
|
+
roundNumber
|
|
27278
|
+
]
|
|
27279
|
+
);
|
|
27280
|
+
this.io.to(`session:${sessionId}`).emit("round:updated", {
|
|
27281
|
+
sessionId,
|
|
27282
|
+
roundNumber,
|
|
27283
|
+
verdict: metadata.verdict,
|
|
27284
|
+
blockerCount: metadata.blocker_count,
|
|
27285
|
+
shouldFixCount: metadata.should_fix_count,
|
|
27286
|
+
suggestionCount: metadata.suggestion_count,
|
|
27287
|
+
source: "orchestrator"
|
|
27288
|
+
});
|
|
27289
|
+
}
|
|
27290
|
+
/**
|
|
27291
|
+
* Process a `map_completed` orchestration event.
|
|
27292
|
+
* Upserts map_runs with orchestrator data for real-time dashboard updates.
|
|
27293
|
+
* Idempotent — skips if run already has source='orchestrator'.
|
|
27294
|
+
*/
|
|
27295
|
+
processMapCompletedEvent(sessionId, runNumber, metadataStr) {
|
|
27296
|
+
let metadata;
|
|
27297
|
+
try {
|
|
27298
|
+
metadata = JSON.parse(metadataStr);
|
|
27299
|
+
} catch {
|
|
27300
|
+
return;
|
|
27301
|
+
}
|
|
27302
|
+
const existing = this.db.exec(
|
|
27303
|
+
"SELECT source FROM map_runs WHERE session_id = ? AND run_number = ?",
|
|
27304
|
+
[sessionId, runNumber]
|
|
27305
|
+
);
|
|
27306
|
+
const rows = resultToRows(existing);
|
|
27307
|
+
if (rows.length > 0 && col(rows[0], "source") === "orchestrator") {
|
|
27308
|
+
return;
|
|
27309
|
+
}
|
|
27310
|
+
this.db.run(
|
|
27311
|
+
`INSERT OR IGNORE INTO map_runs (session_id, run_number)
|
|
27312
|
+
VALUES (?, ?)`,
|
|
27313
|
+
[sessionId, runNumber]
|
|
27314
|
+
);
|
|
27315
|
+
this.db.run(
|
|
27316
|
+
`UPDATE map_runs
|
|
27317
|
+
SET file_count = ?, section_count = ?, source = 'orchestrator',
|
|
27318
|
+
parsed_at = datetime('now')
|
|
27319
|
+
WHERE session_id = ? AND run_number = ?`,
|
|
27320
|
+
[
|
|
27321
|
+
metadata.file_count ?? 0,
|
|
27322
|
+
metadata.section_count ?? 0,
|
|
27323
|
+
sessionId,
|
|
27324
|
+
runNumber
|
|
27325
|
+
]
|
|
27326
|
+
);
|
|
27327
|
+
this.io.to(`session:${sessionId}`).emit("map:updated", {
|
|
27328
|
+
sessionId,
|
|
27329
|
+
runNumber,
|
|
27330
|
+
fileCount: metadata.file_count,
|
|
27331
|
+
sectionCount: metadata.section_count,
|
|
27332
|
+
source: "orchestrator"
|
|
27333
|
+
});
|
|
27334
|
+
}
|
|
26734
27335
|
/**
|
|
26735
27336
|
* Record current mtime after the dashboard writes to disk.
|
|
26736
27337
|
* Called automatically via registered save hooks after saveDb().
|
|
@@ -26744,20 +27345,20 @@ var DbSyncWatcher = class {
|
|
|
26744
27345
|
};
|
|
26745
27346
|
|
|
26746
27347
|
// src/server/socket/chat-handler.ts
|
|
26747
|
-
import { dirname as
|
|
27348
|
+
import { dirname as dirname8 } from "node:path";
|
|
26748
27349
|
|
|
26749
27350
|
// src/server/services/chat-context.ts
|
|
26750
|
-
import { readFileSync as
|
|
26751
|
-
import { join as
|
|
27351
|
+
import { readFileSync as readFileSync8, readdirSync as readdirSync2, existsSync as existsSync7 } from "node:fs";
|
|
27352
|
+
import { join as join11 } from "node:path";
|
|
26752
27353
|
function buildChatContext(ocrDir, target) {
|
|
26753
|
-
const sessionsDir =
|
|
27354
|
+
const sessionsDir = join11(ocrDir, "sessions");
|
|
26754
27355
|
if (target.type === "map_run") {
|
|
26755
27356
|
return buildMapRunContext(sessionsDir, target.sessionId, target.runNumber);
|
|
26756
27357
|
}
|
|
26757
27358
|
return buildReviewRoundContext(sessionsDir, target.sessionId, target.roundNumber);
|
|
26758
27359
|
}
|
|
26759
27360
|
function buildMapRunContext(sessionsDir, sessionId, runNumber) {
|
|
26760
|
-
const mapPath =
|
|
27361
|
+
const mapPath = join11(
|
|
26761
27362
|
sessionsDir,
|
|
26762
27363
|
sessionId,
|
|
26763
27364
|
"map",
|
|
@@ -26771,8 +27372,8 @@ function buildMapRunContext(sessionsDir, sessionId, runNumber) {
|
|
|
26771
27372
|
"",
|
|
26772
27373
|
`Below is the Code Review Map that organizes the changeset into reviewable sections:`
|
|
26773
27374
|
];
|
|
26774
|
-
if (
|
|
26775
|
-
const content =
|
|
27375
|
+
if (existsSync7(mapPath)) {
|
|
27376
|
+
const content = readFileSync8(mapPath, "utf-8");
|
|
26776
27377
|
parts.push("");
|
|
26777
27378
|
parts.push("<map>");
|
|
26778
27379
|
parts.push(content);
|
|
@@ -26784,26 +27385,26 @@ function buildMapRunContext(sessionsDir, sessionId, runNumber) {
|
|
|
26784
27385
|
return parts.join("\n");
|
|
26785
27386
|
}
|
|
26786
27387
|
function buildReviewRoundContext(sessionsDir, sessionId, roundNumber) {
|
|
26787
|
-
const roundDir =
|
|
26788
|
-
const finalPath =
|
|
26789
|
-
const reviewersDir =
|
|
27388
|
+
const roundDir = join11(sessionsDir, sessionId, "rounds", `round-${roundNumber}`);
|
|
27389
|
+
const finalPath = join11(roundDir, "final.md");
|
|
27390
|
+
const reviewersDir = join11(roundDir, "reviews");
|
|
26790
27391
|
const parts = [
|
|
26791
27392
|
`You are an expert code reviewer assisting with a code review session.`,
|
|
26792
27393
|
`You are looking at review round #${roundNumber} for session "${sessionId}".`,
|
|
26793
27394
|
"",
|
|
26794
27395
|
`Below are the review artifacts for this round:`
|
|
26795
27396
|
];
|
|
26796
|
-
if (
|
|
26797
|
-
const content =
|
|
27397
|
+
if (existsSync7(finalPath)) {
|
|
27398
|
+
const content = readFileSync8(finalPath, "utf-8");
|
|
26798
27399
|
parts.push("");
|
|
26799
27400
|
parts.push("<final-synthesis>");
|
|
26800
27401
|
parts.push(content);
|
|
26801
27402
|
parts.push("</final-synthesis>");
|
|
26802
27403
|
}
|
|
26803
|
-
if (
|
|
27404
|
+
if (existsSync7(reviewersDir)) {
|
|
26804
27405
|
const files = readdirSync2(reviewersDir).filter((f) => f.endsWith(".md")).sort();
|
|
26805
27406
|
for (const file of files) {
|
|
26806
|
-
const content =
|
|
27407
|
+
const content = readFileSync8(join11(reviewersDir, file), "utf-8");
|
|
26807
27408
|
const reviewerName = file.replace(/\.md$/, "");
|
|
26808
27409
|
parts.push("");
|
|
26809
27410
|
parts.push(`<reviewer name="${reviewerName}">`);
|
|
@@ -26811,7 +27412,7 @@ function buildReviewRoundContext(sessionsDir, sessionId, roundNumber) {
|
|
|
26811
27412
|
parts.push("</reviewer>");
|
|
26812
27413
|
}
|
|
26813
27414
|
}
|
|
26814
|
-
if (!
|
|
27415
|
+
if (!existsSync7(finalPath) && !existsSync7(reviewersDir)) {
|
|
26815
27416
|
parts.push("");
|
|
26816
27417
|
parts.push("(No review artifacts found on disk for this round.)");
|
|
26817
27418
|
}
|
|
@@ -26939,7 +27540,7 @@ User: ${message}`;
|
|
|
26939
27540
|
});
|
|
26940
27541
|
return;
|
|
26941
27542
|
}
|
|
26942
|
-
const repoRoot =
|
|
27543
|
+
const repoRoot = dirname8(ocrDir);
|
|
26943
27544
|
const spawnResult = adapter.spawn({
|
|
26944
27545
|
prompt,
|
|
26945
27546
|
cwd: repoRoot,
|
|
@@ -27116,15 +27717,15 @@ function cleanupAllChats() {
|
|
|
27116
27717
|
|
|
27117
27718
|
// src/server/socket/post-handler.ts
|
|
27118
27719
|
import { execFile } from "node:child_process";
|
|
27119
|
-
import { existsSync as
|
|
27720
|
+
import { existsSync as existsSync8, mkdirSync as mkdirSync2, readFileSync as readFileSync9, unlinkSync, writeFileSync as writeFileSync3 } from "node:fs";
|
|
27120
27721
|
import { tmpdir as tmpdir2 } from "node:os";
|
|
27121
|
-
import { join as
|
|
27722
|
+
import { join as join12, dirname as dirname9, isAbsolute } from "node:path";
|
|
27122
27723
|
import { randomUUID } from "node:crypto";
|
|
27123
27724
|
import { promisify } from "node:util";
|
|
27124
27725
|
var execFileAsync = promisify(execFile);
|
|
27125
27726
|
function resolveSessionDir(sessionDir, ocrDir) {
|
|
27126
27727
|
if (isAbsolute(sessionDir)) return sessionDir;
|
|
27127
|
-
return
|
|
27728
|
+
return join12(dirname9(ocrDir), sessionDir);
|
|
27128
27729
|
}
|
|
27129
27730
|
var BRANCH_PREFIXES = [
|
|
27130
27731
|
"feat",
|
|
@@ -27193,7 +27794,7 @@ function registerPostHandlers(io2, socket, db, ocrDir, aiCliService) {
|
|
|
27193
27794
|
return;
|
|
27194
27795
|
}
|
|
27195
27796
|
const branch = session.branch;
|
|
27196
|
-
const repoRoot =
|
|
27797
|
+
const repoRoot = dirname9(ocrDir);
|
|
27197
27798
|
try {
|
|
27198
27799
|
await execFileAsync("gh", ["auth", "status"], { env: cleanEnv(), cwd: repoRoot });
|
|
27199
27800
|
} catch {
|
|
@@ -27294,19 +27895,19 @@ function registerPostHandlers(io2, socket, db, ocrDir, aiCliService) {
|
|
|
27294
27895
|
socket.emit("post:error", { error: "Session not found" });
|
|
27295
27896
|
return;
|
|
27296
27897
|
}
|
|
27297
|
-
const sessionDir = session.session_dir ? resolveSessionDir(session.session_dir, ocrDir) :
|
|
27298
|
-
const roundDir =
|
|
27299
|
-
const finalPath =
|
|
27300
|
-
if (!
|
|
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)) {
|
|
27301
27902
|
socket.emit("post:error", { error: "final.md not found for this round" });
|
|
27302
27903
|
return;
|
|
27303
27904
|
}
|
|
27304
|
-
const humanReviewPath =
|
|
27305
|
-
const repoRoot =
|
|
27306
|
-
const commandMdPath =
|
|
27905
|
+
const humanReviewPath = join12(roundDir, "final-human.md");
|
|
27906
|
+
const repoRoot = dirname9(ocrDir);
|
|
27907
|
+
const commandMdPath = join12(ocrDir, "commands", "translate-review-to-single-human.md");
|
|
27307
27908
|
let commandContent;
|
|
27308
27909
|
try {
|
|
27309
|
-
commandContent =
|
|
27910
|
+
commandContent = readFileSync9(commandMdPath, "utf-8");
|
|
27310
27911
|
} catch {
|
|
27311
27912
|
socket.emit("post:error", {
|
|
27312
27913
|
error: `Command file not found: ${commandMdPath}. Run \`ocr init\` to set up.`
|
|
@@ -27382,9 +27983,9 @@ function registerPostHandlers(io2, socket, db, ocrDir, aiCliService) {
|
|
|
27382
27983
|
}
|
|
27383
27984
|
}
|
|
27384
27985
|
let generatedContent = "";
|
|
27385
|
-
if (
|
|
27986
|
+
if (existsSync8(humanReviewPath)) {
|
|
27386
27987
|
try {
|
|
27387
|
-
generatedContent =
|
|
27988
|
+
generatedContent = readFileSync9(humanReviewPath, "utf-8").trim();
|
|
27388
27989
|
} catch {
|
|
27389
27990
|
}
|
|
27390
27991
|
}
|
|
@@ -27459,10 +28060,10 @@ function registerPostHandlers(io2, socket, db, ocrDir, aiCliService) {
|
|
|
27459
28060
|
socket.emit("post:save-result", { success: false, error: "Session not found" });
|
|
27460
28061
|
return;
|
|
27461
28062
|
}
|
|
27462
|
-
const sessionDir = session.session_dir ? resolveSessionDir(session.session_dir, ocrDir) :
|
|
27463
|
-
const roundDir =
|
|
28063
|
+
const sessionDir = session.session_dir ? resolveSessionDir(session.session_dir, ocrDir) : join12(ocrDir, "sessions", sessionId);
|
|
28064
|
+
const roundDir = join12(sessionDir, "rounds", `round-${roundNumber}`);
|
|
27464
28065
|
mkdirSync2(roundDir, { recursive: true });
|
|
27465
|
-
const filePath =
|
|
28066
|
+
const filePath = join12(roundDir, "final-human.md");
|
|
27466
28067
|
writeFileSync3(filePath, content, { mode: 420 });
|
|
27467
28068
|
saveDb(db, ocrDir);
|
|
27468
28069
|
socket.emit("post:save-result", { success: true });
|
|
@@ -27490,14 +28091,14 @@ function registerPostHandlers(io2, socket, db, ocrDir, aiCliService) {
|
|
|
27490
28091
|
);
|
|
27491
28092
|
tracker.appendOutput(`\u25B8 Posting review to PR #${prNumber}...
|
|
27492
28093
|
`);
|
|
27493
|
-
const tmpDir =
|
|
28094
|
+
const tmpDir = join12(tmpdir2(), "ocr-post-comments");
|
|
27494
28095
|
try {
|
|
27495
28096
|
mkdirSync2(tmpDir, { recursive: true, mode: 448 });
|
|
27496
28097
|
} catch {
|
|
27497
28098
|
}
|
|
27498
|
-
const tmpFile =
|
|
28099
|
+
const tmpFile = join12(tmpDir, `${randomUUID()}.md`);
|
|
27499
28100
|
writeFileSync3(tmpFile, content, { mode: 384 });
|
|
27500
|
-
const repoRoot =
|
|
28101
|
+
const repoRoot = dirname9(ocrDir);
|
|
27501
28102
|
try {
|
|
27502
28103
|
const { stdout } = await execFileAsync(
|
|
27503
28104
|
"gh",
|
|
@@ -27541,9 +28142,9 @@ function cleanupAllPostGenerations() {
|
|
|
27541
28142
|
}
|
|
27542
28143
|
|
|
27543
28144
|
// src/server/index.ts
|
|
27544
|
-
var __dirname3 =
|
|
28145
|
+
var __dirname3 = dirname10(fileURLToPath3(import.meta.url));
|
|
27545
28146
|
var AUTH_TOKEN = randomBytes(32).toString("hex");
|
|
27546
|
-
var app = (0,
|
|
28147
|
+
var app = (0, import_express12.default)();
|
|
27547
28148
|
var httpServer = createServer(app);
|
|
27548
28149
|
var io = new SocketIOServer(httpServer, {
|
|
27549
28150
|
cors: {
|
|
@@ -27552,7 +28153,7 @@ var io = new SocketIOServer(httpServer, {
|
|
|
27552
28153
|
maxHttpBufferSize: 1e6
|
|
27553
28154
|
// 1 MB — explicit default; review if large payloads are needed
|
|
27554
28155
|
});
|
|
27555
|
-
app.use(
|
|
28156
|
+
app.use(import_express12.default.json());
|
|
27556
28157
|
if (process.env.NODE_ENV !== "production") {
|
|
27557
28158
|
app.use((_req, res, next) => {
|
|
27558
28159
|
const origin = _req.headers.origin;
|
|
@@ -27604,12 +28205,12 @@ async function startServer(options = {}) {
|
|
|
27604
28205
|
const ocrDir = resolveOcrDir();
|
|
27605
28206
|
const aiCliService = new AiCliService(ocrDir);
|
|
27606
28207
|
const db = await openDb(ocrDir);
|
|
27607
|
-
const dataDir =
|
|
27608
|
-
const pidFilePath =
|
|
28208
|
+
const dataDir = join13(ocrDir, "data");
|
|
28209
|
+
const pidFilePath = join13(dataDir, "dashboard.pid");
|
|
27609
28210
|
mkdirSync3(dataDir, { recursive: true });
|
|
27610
|
-
if (
|
|
28211
|
+
if (existsSync9(pidFilePath)) {
|
|
27611
28212
|
try {
|
|
27612
|
-
const oldPid = parseInt(
|
|
28213
|
+
const oldPid = parseInt(readFileSync10(pidFilePath, "utf-8").trim(), 10);
|
|
27613
28214
|
if (!isNaN(oldPid)) {
|
|
27614
28215
|
try {
|
|
27615
28216
|
process.kill(oldPid, 0);
|
|
@@ -27709,11 +28310,12 @@ async function startServer(options = {}) {
|
|
|
27709
28310
|
app.use("/api/commands", createCommandsRouter(db));
|
|
27710
28311
|
app.use("/api/config", createConfigRouter(ocrDir, aiCliService));
|
|
27711
28312
|
app.use("/api/sessions", createChatRouter(db, ocrDir));
|
|
27712
|
-
|
|
27713
|
-
|
|
27714
|
-
|
|
27715
|
-
|
|
27716
|
-
const
|
|
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") : "";
|
|
27717
28319
|
const tokenScript = `<script>window.__OCR_TOKEN__=${JSON.stringify(AUTH_TOKEN)};</script>`;
|
|
27718
28320
|
const injectedIndexHtml = rawIndexHtml.replace(
|
|
27719
28321
|
"</head>",
|
|
@@ -27736,7 +28338,7 @@ async function startServer(options = {}) {
|
|
|
27736
28338
|
console.log("Client disconnected:", socket.id);
|
|
27737
28339
|
});
|
|
27738
28340
|
});
|
|
27739
|
-
const dbFilePath =
|
|
28341
|
+
const dbFilePath = join13(ocrDir, "data", "ocr.db");
|
|
27740
28342
|
const dbSyncWatcher = new DbSyncWatcher(db, dbFilePath, io, () => {
|
|
27741
28343
|
saveDb(db, ocrDir);
|
|
27742
28344
|
});
|
|
@@ -27747,12 +28349,13 @@ async function startServer(options = {}) {
|
|
|
27747
28349
|
() => dbSyncWatcher.syncFromDisk(),
|
|
27748
28350
|
() => dbSyncWatcher.markOwnWrite()
|
|
27749
28351
|
);
|
|
27750
|
-
const sessionsDir =
|
|
28352
|
+
const sessionsDir = join13(ocrDir, "sessions");
|
|
27751
28353
|
const fsSync = new FilesystemSync(db, sessionsDir, io, () => saveDb(db, ocrDir));
|
|
27752
28354
|
await fsSync.fullScan();
|
|
27753
28355
|
saveDb(db, ocrDir);
|
|
27754
28356
|
fsSync.startWatching();
|
|
27755
28357
|
console.log(`Watching sessions: ${sessionsDir}`);
|
|
28358
|
+
const stopReviewersWatch = watchReviewersMeta(ocrDir, io);
|
|
27756
28359
|
await new Promise((resolve3, reject) => {
|
|
27757
28360
|
httpServer.once("error", (err) => {
|
|
27758
28361
|
if (err.code === "EADDRINUSE") {
|
|
@@ -27836,6 +28439,7 @@ async function startServer(options = {}) {
|
|
|
27836
28439
|
}
|
|
27837
28440
|
dbSyncWatcher.stopWatching();
|
|
27838
28441
|
fsSync.stopWatching();
|
|
28442
|
+
stopReviewersWatch();
|
|
27839
28443
|
io.close();
|
|
27840
28444
|
httpServer.close(() => {
|
|
27841
28445
|
try {
|