@open-code-review/cli 1.9.0 → 1.10.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/dashboard/server.js +426 -214
- package/dist/index.js +290 -123
- package/dist/lib/db/index.js +128 -17
- package/package.json +2 -2
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 dirname12 = path2.dirname;
|
|
18414
18414
|
var basename3 = path2.basename;
|
|
18415
18415
|
var extname = path2.extname;
|
|
18416
|
-
var
|
|
18416
|
+
var join15 = 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 = dirname12(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 = join15(dir, file);
|
|
18465
18465
|
var stat = tryStat(path3);
|
|
18466
18466
|
if (stat && stat.isFile()) {
|
|
18467
18467
|
return path3;
|
|
18468
18468
|
}
|
|
18469
|
-
path3 =
|
|
18469
|
+
path3 = join15(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 join15 = 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(join15(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 = join15(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);
|
|
@@ -23181,8 +23181,8 @@ var init_open = __esm({
|
|
|
23181
23181
|
// src/server/index.ts
|
|
23182
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 existsSync10, readFileSync as readFileSync11, writeFileSync as writeFileSync5, unlinkSync as unlinkSync2, mkdirSync as mkdirSync4 } from "node:fs";
|
|
23185
|
+
import { join as join14, dirname as dirname11, 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";
|
|
@@ -23208,12 +23208,12 @@ function resolveOcrDir(startDir) {
|
|
|
23208
23208
|
}
|
|
23209
23209
|
|
|
23210
23210
|
// src/server/db.ts
|
|
23211
|
-
import { existsSync as
|
|
23212
|
-
import { join as
|
|
23211
|
+
import { existsSync as existsSync3, readFileSync as readFileSync2, renameSync as renameSync2, writeFileSync as writeFileSync2, mkdirSync as mkdirSync2 } from "node:fs";
|
|
23212
|
+
import { join as join4, dirname as dirname4 } from "node:path";
|
|
23213
23213
|
import initSqlJs2 from "sql.js";
|
|
23214
23214
|
|
|
23215
23215
|
// ../cli/src/lib/db/index.ts
|
|
23216
|
-
import { dirname as
|
|
23216
|
+
import { dirname as dirname3, join as join3 } from "node:path";
|
|
23217
23217
|
import { createRequire } from "node:module";
|
|
23218
23218
|
import initSqlJs from "sql.js";
|
|
23219
23219
|
|
|
@@ -23464,6 +23464,14 @@ var MIGRATIONS = [
|
|
|
23464
23464
|
ALTER TABLE map_runs ADD COLUMN source TEXT DEFAULT NULL;
|
|
23465
23465
|
ALTER TABLE map_runs ADD COLUMN section_count INTEGER DEFAULT 0;
|
|
23466
23466
|
`
|
|
23467
|
+
},
|
|
23468
|
+
{
|
|
23469
|
+
version: 9,
|
|
23470
|
+
description: "Add uid column to command_executions for JSONL-backed recovery",
|
|
23471
|
+
sql: `
|
|
23472
|
+
ALTER TABLE command_executions ADD COLUMN uid TEXT;
|
|
23473
|
+
CREATE UNIQUE INDEX idx_command_executions_uid ON command_executions(uid);
|
|
23474
|
+
`
|
|
23467
23475
|
}
|
|
23468
23476
|
];
|
|
23469
23477
|
function ensureSchemaVersionTable(db) {
|
|
@@ -23526,11 +23534,108 @@ function resultToRow(result) {
|
|
|
23526
23534
|
return rows[0];
|
|
23527
23535
|
}
|
|
23528
23536
|
|
|
23537
|
+
// ../cli/src/lib/db/command-log.ts
|
|
23538
|
+
import { appendFileSync, existsSync as existsSync2, mkdirSync, readFileSync, renameSync, writeFileSync } from "node:fs";
|
|
23539
|
+
import { dirname as dirname2, join as join2 } from "node:path";
|
|
23540
|
+
import { randomUUID } from "node:crypto";
|
|
23541
|
+
var CACHE_DIR = ".cache";
|
|
23542
|
+
var FILENAME = "command-history.jsonl";
|
|
23543
|
+
var MAX_LINES = 5e3;
|
|
23544
|
+
var KEEP_LINES = 4e3;
|
|
23545
|
+
var approxLineCount = -1;
|
|
23546
|
+
function generateCommandUid() {
|
|
23547
|
+
return randomUUID();
|
|
23548
|
+
}
|
|
23549
|
+
function cacheDir(ocrDir) {
|
|
23550
|
+
return join2(ocrDir, "data", CACHE_DIR);
|
|
23551
|
+
}
|
|
23552
|
+
function commandLogPath(ocrDir) {
|
|
23553
|
+
return join2(cacheDir(ocrDir), FILENAME);
|
|
23554
|
+
}
|
|
23555
|
+
function appendCommandLog(ocrDir, entry) {
|
|
23556
|
+
try {
|
|
23557
|
+
const filePath = commandLogPath(ocrDir);
|
|
23558
|
+
const dir = dirname2(filePath);
|
|
23559
|
+
if (!existsSync2(dir)) mkdirSync(dir, { recursive: true });
|
|
23560
|
+
const line = JSON.stringify(entry) + "\n";
|
|
23561
|
+
appendFileSync(filePath, line, { encoding: "utf-8" });
|
|
23562
|
+
if (approxLineCount >= 0) approxLineCount++;
|
|
23563
|
+
rotateIfNeeded(filePath);
|
|
23564
|
+
} catch {
|
|
23565
|
+
}
|
|
23566
|
+
}
|
|
23567
|
+
function readCommandLog(ocrDir) {
|
|
23568
|
+
const filePath = commandLogPath(ocrDir);
|
|
23569
|
+
if (!existsSync2(filePath)) return [];
|
|
23570
|
+
const content = readFileSync(filePath, "utf-8");
|
|
23571
|
+
const entries = [];
|
|
23572
|
+
for (const line of content.split("\n")) {
|
|
23573
|
+
if (!line.trim()) continue;
|
|
23574
|
+
try {
|
|
23575
|
+
entries.push(JSON.parse(line));
|
|
23576
|
+
} catch {
|
|
23577
|
+
}
|
|
23578
|
+
}
|
|
23579
|
+
return entries;
|
|
23580
|
+
}
|
|
23581
|
+
function replayCommandLog(db, ocrDir) {
|
|
23582
|
+
const entries = readCommandLog(ocrDir);
|
|
23583
|
+
if (entries.length === 0) return 0;
|
|
23584
|
+
const latest = /* @__PURE__ */ new Map();
|
|
23585
|
+
for (const entry of entries) {
|
|
23586
|
+
if (!entry.uid || !entry.command || !entry.started_at) continue;
|
|
23587
|
+
const existing = latest.get(entry.uid);
|
|
23588
|
+
if (!existing || entry.event !== "start") {
|
|
23589
|
+
latest.set(entry.uid, entry);
|
|
23590
|
+
}
|
|
23591
|
+
}
|
|
23592
|
+
let imported = 0;
|
|
23593
|
+
for (const entry of latest.values()) {
|
|
23594
|
+
if (entry.event === "start" && !entry.finished_at) continue;
|
|
23595
|
+
const existing = db.exec(
|
|
23596
|
+
"SELECT COUNT(*) as c FROM command_executions WHERE uid = ?",
|
|
23597
|
+
[entry.uid]
|
|
23598
|
+
);
|
|
23599
|
+
if ((existing[0]?.values[0]?.[0] ?? 0) > 0) continue;
|
|
23600
|
+
db.run(
|
|
23601
|
+
`INSERT INTO command_executions
|
|
23602
|
+
(uid, command, args, exit_code, started_at, finished_at, pid, is_detached)
|
|
23603
|
+
VALUES (?, ?, ?, ?, ?, ?, NULL, ?)`,
|
|
23604
|
+
[
|
|
23605
|
+
entry.uid,
|
|
23606
|
+
entry.command,
|
|
23607
|
+
entry.args,
|
|
23608
|
+
entry.exit_code,
|
|
23609
|
+
entry.started_at,
|
|
23610
|
+
entry.finished_at,
|
|
23611
|
+
entry.is_detached
|
|
23612
|
+
]
|
|
23613
|
+
);
|
|
23614
|
+
imported++;
|
|
23615
|
+
}
|
|
23616
|
+
return imported;
|
|
23617
|
+
}
|
|
23618
|
+
function rotateIfNeeded(filePath) {
|
|
23619
|
+
try {
|
|
23620
|
+
if (approxLineCount >= 0 && approxLineCount <= MAX_LINES) return;
|
|
23621
|
+
const content = readFileSync(filePath, "utf-8");
|
|
23622
|
+
const lines = content.split("\n").filter((l) => l.trim());
|
|
23623
|
+
approxLineCount = lines.length;
|
|
23624
|
+
if (approxLineCount <= MAX_LINES) return;
|
|
23625
|
+
const kept = lines.slice(lines.length - KEEP_LINES);
|
|
23626
|
+
const tmpPath = `${filePath}.${process.pid}.tmp`;
|
|
23627
|
+
writeFileSync(tmpPath, kept.join("\n") + "\n", { encoding: "utf-8" });
|
|
23628
|
+
renameSync(tmpPath, filePath);
|
|
23629
|
+
approxLineCount = KEEP_LINES;
|
|
23630
|
+
} catch {
|
|
23631
|
+
}
|
|
23632
|
+
}
|
|
23633
|
+
|
|
23529
23634
|
// ../cli/src/lib/db/index.ts
|
|
23530
23635
|
function locateWasm() {
|
|
23531
23636
|
const require2 = createRequire(import.meta.url);
|
|
23532
23637
|
const sqlJsPath = require2.resolve("sql.js");
|
|
23533
|
-
return
|
|
23638
|
+
return join3(dirname3(sqlJsPath), "sql-wasm.wasm");
|
|
23534
23639
|
}
|
|
23535
23640
|
function applyPragmas(db) {
|
|
23536
23641
|
db.run("PRAGMA foreign_keys = ON;");
|
|
@@ -23548,24 +23653,24 @@ function registerSaveHooks(preSave, postSave) {
|
|
|
23548
23653
|
postSaveHook = postSave;
|
|
23549
23654
|
}
|
|
23550
23655
|
async function openDb(ocrDir) {
|
|
23551
|
-
const dbPath =
|
|
23656
|
+
const dbPath = join4(ocrDir, "data", "ocr.db");
|
|
23552
23657
|
if (cachedDb && cachedDbPath === dbPath) {
|
|
23553
23658
|
return cachedDb;
|
|
23554
23659
|
}
|
|
23555
|
-
const wasmBuffer =
|
|
23660
|
+
const wasmBuffer = readFileSync2(locateWasm());
|
|
23556
23661
|
const wasmBinary = wasmBuffer.buffer.slice(
|
|
23557
23662
|
wasmBuffer.byteOffset,
|
|
23558
23663
|
wasmBuffer.byteOffset + wasmBuffer.byteLength
|
|
23559
23664
|
);
|
|
23560
23665
|
const SQL = await initSqlJs2({ wasmBinary });
|
|
23561
23666
|
let db;
|
|
23562
|
-
if (
|
|
23563
|
-
const fileBuffer =
|
|
23667
|
+
if (existsSync3(dbPath)) {
|
|
23668
|
+
const fileBuffer = readFileSync2(dbPath);
|
|
23564
23669
|
db = new SQL.Database(fileBuffer);
|
|
23565
23670
|
} else {
|
|
23566
|
-
const dataDir =
|
|
23567
|
-
if (!
|
|
23568
|
-
|
|
23671
|
+
const dataDir = dirname4(dbPath);
|
|
23672
|
+
if (!existsSync3(dataDir)) {
|
|
23673
|
+
mkdirSync2(dataDir, { recursive: true });
|
|
23569
23674
|
}
|
|
23570
23675
|
db = new SQL.Database();
|
|
23571
23676
|
}
|
|
@@ -23577,15 +23682,15 @@ async function openDb(ocrDir) {
|
|
|
23577
23682
|
}
|
|
23578
23683
|
function saveDb(db, ocrDir) {
|
|
23579
23684
|
preSaveHook?.();
|
|
23580
|
-
const dbPath =
|
|
23685
|
+
const dbPath = join4(ocrDir, "data", "ocr.db");
|
|
23581
23686
|
const data = db.export();
|
|
23582
|
-
const dir =
|
|
23583
|
-
if (!
|
|
23584
|
-
|
|
23687
|
+
const dir = dirname4(dbPath);
|
|
23688
|
+
if (!existsSync3(dir)) {
|
|
23689
|
+
mkdirSync2(dir, { recursive: true });
|
|
23585
23690
|
}
|
|
23586
|
-
const tmpPath = dbPath
|
|
23587
|
-
|
|
23588
|
-
|
|
23691
|
+
const tmpPath = `${dbPath}.${process.pid}.tmp`;
|
|
23692
|
+
writeFileSync2(tmpPath, Buffer.from(data));
|
|
23693
|
+
renameSync2(tmpPath, dbPath);
|
|
23589
23694
|
postSaveHook?.();
|
|
23590
23695
|
}
|
|
23591
23696
|
function closeDb() {
|
|
@@ -24798,12 +24903,12 @@ var import_express8 = __toESM(require_express2(), 1);
|
|
|
24798
24903
|
|
|
24799
24904
|
// src/server/socket/command-runner.ts
|
|
24800
24905
|
import { spawn as spawn3 } from "node:child_process";
|
|
24801
|
-
import { readFileSync as
|
|
24802
|
-
import { dirname as
|
|
24906
|
+
import { readFileSync as readFileSync4 } from "node:fs";
|
|
24907
|
+
import { dirname as dirname6, join as join8 } from "node:path";
|
|
24803
24908
|
|
|
24804
24909
|
// src/server/services/ai-cli/index.ts
|
|
24805
|
-
import { readFileSync as
|
|
24806
|
-
import { join as
|
|
24910
|
+
import { readFileSync as readFileSync3 } from "node:fs";
|
|
24911
|
+
import { join as join6 } from "node:path";
|
|
24807
24912
|
|
|
24808
24913
|
// src/server/services/ai-cli/claude-adapter.ts
|
|
24809
24914
|
import { execFileSync } from "node:child_process";
|
|
@@ -24811,7 +24916,7 @@ import { spawn } from "node:child_process";
|
|
|
24811
24916
|
|
|
24812
24917
|
// src/server/services/ai-cli/helpers.ts
|
|
24813
24918
|
import { tmpdir } from "node:os";
|
|
24814
|
-
import { join as
|
|
24919
|
+
import { join as join5 } from "node:path";
|
|
24815
24920
|
function formatToolDetail(tool, input) {
|
|
24816
24921
|
switch (tool) {
|
|
24817
24922
|
case "Read":
|
|
@@ -24847,7 +24952,7 @@ function extractAssistantText(parsed) {
|
|
|
24847
24952
|
}
|
|
24848
24953
|
return text;
|
|
24849
24954
|
}
|
|
24850
|
-
var TEMP_BASE =
|
|
24955
|
+
var TEMP_BASE = join5(tmpdir(), "ocr-ai-prompts");
|
|
24851
24956
|
|
|
24852
24957
|
// src/server/socket/env.ts
|
|
24853
24958
|
var ENV_ALLOWLIST = [
|
|
@@ -25081,8 +25186,8 @@ function extractToolInput(part) {
|
|
|
25081
25186
|
// src/server/services/ai-cli/index.ts
|
|
25082
25187
|
function readAiCliPreference(ocrDir) {
|
|
25083
25188
|
try {
|
|
25084
|
-
const configPath =
|
|
25085
|
-
const content =
|
|
25189
|
+
const configPath = join6(ocrDir, "config.yaml");
|
|
25190
|
+
const content = readFileSync3(configPath, "utf-8");
|
|
25086
25191
|
const match = content.match(/^\s*ai_cli:\s*(\S+)/m);
|
|
25087
25192
|
const value = match?.[1] ?? "auto";
|
|
25088
25193
|
if (value === "claude" || value === "opencode" || value === "off") return value;
|
|
@@ -25163,24 +25268,24 @@ var AiCliService = class {
|
|
|
25163
25268
|
};
|
|
25164
25269
|
|
|
25165
25270
|
// src/server/socket/cli-resolver.ts
|
|
25166
|
-
import { existsSync as
|
|
25167
|
-
import { dirname as
|
|
25271
|
+
import { existsSync as existsSync4 } from "node:fs";
|
|
25272
|
+
import { dirname as dirname5, join as join7 } from "node:path";
|
|
25168
25273
|
import { fileURLToPath } from "node:url";
|
|
25169
|
-
var __dirname =
|
|
25274
|
+
var __dirname = dirname5(fileURLToPath(import.meta.url));
|
|
25170
25275
|
function resolveLocalCli() {
|
|
25171
|
-
const parentDir =
|
|
25172
|
-
const bundledCli =
|
|
25173
|
-
if (
|
|
25276
|
+
const parentDir = join7(__dirname, "..");
|
|
25277
|
+
const bundledCli = join7(parentDir, "index.js");
|
|
25278
|
+
if (existsSync4(bundledCli) && existsSync4(join7(parentDir, "dashboard", "server.js"))) {
|
|
25174
25279
|
return bundledCli;
|
|
25175
25280
|
}
|
|
25176
25281
|
let dir = __dirname;
|
|
25177
25282
|
for (let i = 0; i < 8; i++) {
|
|
25178
|
-
if (
|
|
25179
|
-
const candidate =
|
|
25180
|
-
if (
|
|
25283
|
+
if (existsSync4(join7(dir, "nx.json"))) {
|
|
25284
|
+
const candidate = join7(dir, "packages", "cli", "dist", "index.js");
|
|
25285
|
+
if (existsSync4(candidate)) return candidate;
|
|
25181
25286
|
break;
|
|
25182
25287
|
}
|
|
25183
|
-
const parent =
|
|
25288
|
+
const parent = dirname5(dir);
|
|
25184
25289
|
if (parent === dir) break;
|
|
25185
25290
|
dir = parent;
|
|
25186
25291
|
}
|
|
@@ -25267,17 +25372,34 @@ function registerCommandHandlers(io2, socket, db, ocrDir, aiCliService) {
|
|
|
25267
25372
|
return;
|
|
25268
25373
|
}
|
|
25269
25374
|
const startedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
25375
|
+
const uid = generateCommandUid();
|
|
25376
|
+
const argsJson = JSON.stringify(subArgs);
|
|
25270
25377
|
db.run(
|
|
25271
|
-
`INSERT INTO command_executions (command, args, started_at)
|
|
25272
|
-
VALUES (?, ?, ?)`,
|
|
25273
|
-
[command,
|
|
25378
|
+
`INSERT INTO command_executions (uid, command, args, started_at)
|
|
25379
|
+
VALUES (?, ?, ?, ?)`,
|
|
25380
|
+
[uid, command, argsJson, startedAt]
|
|
25274
25381
|
);
|
|
25275
25382
|
const idResult = db.exec("SELECT last_insert_rowid() as id");
|
|
25276
25383
|
const executionId = idResult[0]?.values[0]?.[0] ?? 0;
|
|
25384
|
+
appendCommandLog(ocrDir, {
|
|
25385
|
+
v: 1,
|
|
25386
|
+
uid,
|
|
25387
|
+
db_id: executionId,
|
|
25388
|
+
command,
|
|
25389
|
+
args: argsJson,
|
|
25390
|
+
exit_code: null,
|
|
25391
|
+
started_at: startedAt,
|
|
25392
|
+
finished_at: null,
|
|
25393
|
+
is_detached: AI_COMMANDS.has(baseCommand) ? 1 : 0,
|
|
25394
|
+
event: "start",
|
|
25395
|
+
writer: "dashboard"
|
|
25396
|
+
});
|
|
25277
25397
|
const isAi = AI_COMMANDS.has(baseCommand);
|
|
25278
25398
|
const entry = {
|
|
25279
25399
|
process: null,
|
|
25280
25400
|
executionId,
|
|
25401
|
+
uid,
|
|
25402
|
+
argsJson,
|
|
25281
25403
|
outputBuffer: "",
|
|
25282
25404
|
commandStr: command,
|
|
25283
25405
|
startedAt,
|
|
@@ -25344,7 +25466,7 @@ function registerCommandHandlers(io2, socket, db, ocrDir, aiCliService) {
|
|
|
25344
25466
|
}
|
|
25345
25467
|
function spawnCliCommand(io2, db, ocrDir, executionId, baseCommand, subArgs, entry) {
|
|
25346
25468
|
const localCli = resolveLocalCli();
|
|
25347
|
-
const repoRoot =
|
|
25469
|
+
const repoRoot = dirname6(ocrDir);
|
|
25348
25470
|
const proc = localCli ? spawn3("node", [localCli, baseCommand, ...subArgs], {
|
|
25349
25471
|
cwd: repoRoot,
|
|
25350
25472
|
env: cleanEnv()
|
|
@@ -25386,10 +25508,10 @@ function spawnAiCommand(io2, _socket, db, ocrDir, executionId, baseCommand, subA
|
|
|
25386
25508
|
finishExecution(io2, db, ocrDir, executionId, 1, content);
|
|
25387
25509
|
return;
|
|
25388
25510
|
}
|
|
25389
|
-
const commandMdPath =
|
|
25511
|
+
const commandMdPath = join8(ocrDir, "commands", `${baseCommand}.md`);
|
|
25390
25512
|
let commandContent;
|
|
25391
25513
|
try {
|
|
25392
|
-
commandContent =
|
|
25514
|
+
commandContent = readFileSync4(commandMdPath, "utf-8");
|
|
25393
25515
|
} catch {
|
|
25394
25516
|
const content = `Error: Could not read command file at ${commandMdPath}
|
|
25395
25517
|
`;
|
|
@@ -25483,7 +25605,7 @@ function spawnAiCommand(io2, _socket, db, ocrDir, executionId, baseCommand, subA
|
|
|
25483
25605
|
}
|
|
25484
25606
|
promptLines.push("", "---", "", commandContent);
|
|
25485
25607
|
const prompt = promptLines.join("\n");
|
|
25486
|
-
const repoRoot =
|
|
25608
|
+
const repoRoot = dirname6(ocrDir);
|
|
25487
25609
|
const { process: proc, detached } = adapter.spawn({ mode: "workflow", prompt, cwd: repoRoot });
|
|
25488
25610
|
entry.process = proc;
|
|
25489
25611
|
entry.detached = detached;
|
|
@@ -25597,6 +25719,7 @@ ${stderrBuffer}`;
|
|
|
25597
25719
|
}
|
|
25598
25720
|
function finishExecution(io2, db, ocrDir, executionId, code, output) {
|
|
25599
25721
|
const finishedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
25722
|
+
const entry = activeCommands.get(executionId);
|
|
25600
25723
|
db.run(
|
|
25601
25724
|
`UPDATE command_executions
|
|
25602
25725
|
SET exit_code = ?, finished_at = ?, output = ?, pid = NULL
|
|
@@ -25604,6 +25727,21 @@ function finishExecution(io2, db, ocrDir, executionId, code, output) {
|
|
|
25604
25727
|
[code, finishedAt, output, executionId]
|
|
25605
25728
|
);
|
|
25606
25729
|
saveDb(db, ocrDir);
|
|
25730
|
+
if (entry?.uid) {
|
|
25731
|
+
appendCommandLog(ocrDir, {
|
|
25732
|
+
v: 1,
|
|
25733
|
+
uid: entry.uid,
|
|
25734
|
+
db_id: executionId,
|
|
25735
|
+
command: entry.commandStr,
|
|
25736
|
+
args: entry.argsJson ?? null,
|
|
25737
|
+
exit_code: code,
|
|
25738
|
+
started_at: entry.startedAt,
|
|
25739
|
+
finished_at: finishedAt,
|
|
25740
|
+
is_detached: entry.detached ? 1 : 0,
|
|
25741
|
+
event: code === -2 ? "cancel" : "finish",
|
|
25742
|
+
writer: "dashboard"
|
|
25743
|
+
});
|
|
25744
|
+
}
|
|
25607
25745
|
io2.emit("command:finished", {
|
|
25608
25746
|
execution_id: executionId,
|
|
25609
25747
|
exitCode: code,
|
|
@@ -25672,9 +25810,9 @@ function createCommandsRouter(db) {
|
|
|
25672
25810
|
|
|
25673
25811
|
// src/server/routes/config.ts
|
|
25674
25812
|
var import_express9 = __toESM(require_express2(), 1);
|
|
25675
|
-
import { readFileSync as
|
|
25813
|
+
import { readFileSync as readFileSync5, writeFileSync as writeFileSync3 } from "node:fs";
|
|
25676
25814
|
import { execFileSync as execFileSync3 } from "node:child_process";
|
|
25677
|
-
import { join as
|
|
25815
|
+
import { join as join9, dirname as dirname7, basename } from "node:path";
|
|
25678
25816
|
var VALID_IDES = ["vscode", "cursor", "windsurf", "jetbrains", "sublime"];
|
|
25679
25817
|
function detectIde() {
|
|
25680
25818
|
const termProgram = process.env.TERM_PROGRAM?.toLowerCase() ?? "";
|
|
@@ -25691,8 +25829,8 @@ function detectIde() {
|
|
|
25691
25829
|
}
|
|
25692
25830
|
function readIdeFromConfig(ocrDir) {
|
|
25693
25831
|
try {
|
|
25694
|
-
const configPath =
|
|
25695
|
-
const content =
|
|
25832
|
+
const configPath = join9(ocrDir, "config.yaml");
|
|
25833
|
+
const content = readFileSync5(configPath, "utf-8");
|
|
25696
25834
|
const match = content.match(/^\s*ide:\s*(\S+)/m);
|
|
25697
25835
|
return match?.[1] ?? "auto";
|
|
25698
25836
|
} catch {
|
|
@@ -25719,7 +25857,7 @@ function detectGitBranch(cwd) {
|
|
|
25719
25857
|
}
|
|
25720
25858
|
function createConfigRouter(ocrDir, aiCliService) {
|
|
25721
25859
|
const router = (0, import_express9.Router)();
|
|
25722
|
-
const projectRoot =
|
|
25860
|
+
const projectRoot = dirname7(ocrDir);
|
|
25723
25861
|
const workspaceName = basename(projectRoot);
|
|
25724
25862
|
const gitBranch = detectGitBranch(projectRoot);
|
|
25725
25863
|
router.get("/", (_req, res) => {
|
|
@@ -25738,8 +25876,8 @@ function createConfigRouter(ocrDir, aiCliService) {
|
|
|
25738
25876
|
return;
|
|
25739
25877
|
}
|
|
25740
25878
|
try {
|
|
25741
|
-
const configPath =
|
|
25742
|
-
let content =
|
|
25879
|
+
const configPath = join9(ocrDir, "config.yaml");
|
|
25880
|
+
let content = readFileSync5(configPath, "utf-8");
|
|
25743
25881
|
if (content.match(/^\s*ide:\s*\S+/m)) {
|
|
25744
25882
|
content = content.replace(/^(\s*ide:\s*)\S+/m, `$1${ide}`);
|
|
25745
25883
|
} else if (content.includes("dashboard:")) {
|
|
@@ -25751,7 +25889,7 @@ dashboard:
|
|
|
25751
25889
|
ide: ${ide}
|
|
25752
25890
|
`;
|
|
25753
25891
|
}
|
|
25754
|
-
|
|
25892
|
+
writeFileSync3(configPath, content);
|
|
25755
25893
|
res.json({ ide });
|
|
25756
25894
|
} catch (err) {
|
|
25757
25895
|
console.error("Failed to update config:", err);
|
|
@@ -25828,15 +25966,15 @@ function createChatRouter(db, ocrDir) {
|
|
|
25828
25966
|
|
|
25829
25967
|
// src/server/routes/reviewers.ts
|
|
25830
25968
|
var import_express11 = __toESM(require_express2(), 1);
|
|
25831
|
-
import { readFileSync as
|
|
25832
|
-
import { join as
|
|
25969
|
+
import { readFileSync as readFileSync6, existsSync as existsSync5, watch } from "node:fs";
|
|
25970
|
+
import { join as join10 } from "node:path";
|
|
25833
25971
|
function readReviewersMeta(ocrDir) {
|
|
25834
|
-
const metaPath =
|
|
25835
|
-
if (!
|
|
25972
|
+
const metaPath = join10(ocrDir, "reviewers-meta.json");
|
|
25973
|
+
if (!existsSync5(metaPath)) {
|
|
25836
25974
|
return { reviewers: [], defaults: [] };
|
|
25837
25975
|
}
|
|
25838
25976
|
try {
|
|
25839
|
-
const raw =
|
|
25977
|
+
const raw = readFileSync6(metaPath, "utf-8");
|
|
25840
25978
|
const meta = JSON.parse(raw);
|
|
25841
25979
|
const reviewers = meta.reviewers ?? [];
|
|
25842
25980
|
const defaults = reviewers.filter((r) => r.is_default).map((r) => r.id);
|
|
@@ -25857,13 +25995,13 @@ function createReviewersRouter(ocrDir) {
|
|
|
25857
25995
|
res.status(400).json({ error: "Invalid reviewer ID" });
|
|
25858
25996
|
return;
|
|
25859
25997
|
}
|
|
25860
|
-
const filePath =
|
|
25861
|
-
if (!
|
|
25998
|
+
const filePath = join10(ocrDir, "skills", "references", "reviewers", `${id}.md`);
|
|
25999
|
+
if (!existsSync5(filePath)) {
|
|
25862
26000
|
res.status(404).json({ error: "Reviewer not found", id });
|
|
25863
26001
|
return;
|
|
25864
26002
|
}
|
|
25865
26003
|
try {
|
|
25866
|
-
const content =
|
|
26004
|
+
const content = readFileSync6(filePath, "utf-8");
|
|
25867
26005
|
res.json({ id, content });
|
|
25868
26006
|
} catch {
|
|
25869
26007
|
res.status(500).json({ error: "Failed to read reviewer file", id });
|
|
@@ -25872,7 +26010,7 @@ function createReviewersRouter(ocrDir) {
|
|
|
25872
26010
|
return router;
|
|
25873
26011
|
}
|
|
25874
26012
|
function watchReviewersMeta(ocrDir, io2) {
|
|
25875
|
-
const metaPath =
|
|
26013
|
+
const metaPath = join10(ocrDir, "reviewers-meta.json");
|
|
25876
26014
|
let watcher = null;
|
|
25877
26015
|
let debounce;
|
|
25878
26016
|
try {
|
|
@@ -25894,8 +26032,8 @@ function watchReviewersMeta(ocrDir, io2) {
|
|
|
25894
26032
|
}
|
|
25895
26033
|
|
|
25896
26034
|
// src/server/services/filesystem-sync.ts
|
|
25897
|
-
import { readdirSync, readFileSync as
|
|
25898
|
-
import { join as
|
|
26035
|
+
import { readdirSync, readFileSync as readFileSync7, statSync, existsSync as existsSync6 } from "node:fs";
|
|
26036
|
+
import { join as join11, basename as basename2, dirname as dirname8, relative } from "node:path";
|
|
25899
26037
|
import { watch as watch2 } from "chokidar";
|
|
25900
26038
|
|
|
25901
26039
|
// src/server/services/parsers/reviewer-parser.ts
|
|
@@ -26077,67 +26215,67 @@ var FilesystemSync = class {
|
|
|
26077
26215
|
onSync;
|
|
26078
26216
|
// ── 6.1: Full Scan ──
|
|
26079
26217
|
async fullScan() {
|
|
26080
|
-
if (!
|
|
26218
|
+
if (!existsSync6(this.sessionsDir)) return;
|
|
26081
26219
|
const entries = readdirSync(this.sessionsDir, { withFileTypes: true });
|
|
26082
26220
|
for (const entry of entries) {
|
|
26083
26221
|
if (!entry.isDirectory()) continue;
|
|
26084
26222
|
const sessionId = entry.name;
|
|
26085
|
-
const sessionDir =
|
|
26223
|
+
const sessionDir = join11(this.sessionsDir, sessionId);
|
|
26086
26224
|
this.syncSession(sessionId, sessionDir);
|
|
26087
26225
|
}
|
|
26088
26226
|
}
|
|
26089
26227
|
syncSession(sessionId, sessionDir) {
|
|
26090
26228
|
this.ensureSessionRow(sessionId, sessionDir);
|
|
26091
|
-
const roundsDir =
|
|
26092
|
-
if (
|
|
26229
|
+
const roundsDir = join11(sessionDir, "rounds");
|
|
26230
|
+
if (existsSync6(roundsDir)) {
|
|
26093
26231
|
const rounds = readdirSync(roundsDir, { withFileTypes: true });
|
|
26094
26232
|
for (const roundEntry of rounds) {
|
|
26095
26233
|
if (!roundEntry.isDirectory()) continue;
|
|
26096
26234
|
const roundMatch = roundEntry.name.match(/^round-(\d+)$/);
|
|
26097
26235
|
if (!roundMatch) continue;
|
|
26098
26236
|
const roundNumber = parseInt(roundMatch[1] ?? "0", 10);
|
|
26099
|
-
const roundDir =
|
|
26100
|
-
const reviewsDir =
|
|
26101
|
-
if (
|
|
26237
|
+
const roundDir = join11(roundsDir, roundEntry.name);
|
|
26238
|
+
const reviewsDir = join11(roundDir, "reviews");
|
|
26239
|
+
if (existsSync6(reviewsDir)) {
|
|
26102
26240
|
const reviewFiles = readdirSync(reviewsDir).filter((f) => f.endsWith(".md"));
|
|
26103
26241
|
for (const reviewFile of reviewFiles) {
|
|
26104
|
-
const filePath =
|
|
26242
|
+
const filePath = join11(reviewsDir, reviewFile);
|
|
26105
26243
|
this.processReviewerOutput(sessionId, roundNumber, filePath, reviewFile);
|
|
26106
26244
|
}
|
|
26107
26245
|
}
|
|
26108
|
-
const roundMetaPath =
|
|
26109
|
-
if (
|
|
26246
|
+
const roundMetaPath = join11(roundDir, "round-meta.json");
|
|
26247
|
+
if (existsSync6(roundMetaPath)) {
|
|
26110
26248
|
this.processRoundMeta(sessionId, roundNumber, roundMetaPath);
|
|
26111
26249
|
}
|
|
26112
|
-
const finalPath =
|
|
26113
|
-
if (
|
|
26250
|
+
const finalPath = join11(roundDir, "final.md");
|
|
26251
|
+
if (existsSync6(finalPath)) {
|
|
26114
26252
|
this.processFinalMd(sessionId, roundNumber, finalPath);
|
|
26115
26253
|
}
|
|
26116
|
-
const finalHumanPath =
|
|
26117
|
-
if (
|
|
26254
|
+
const finalHumanPath = join11(roundDir, "final-human.md");
|
|
26255
|
+
if (existsSync6(finalHumanPath)) {
|
|
26118
26256
|
this.processGenericArtifact(sessionId, "final-human", finalHumanPath, roundNumber);
|
|
26119
26257
|
}
|
|
26120
|
-
const discoursePath =
|
|
26121
|
-
if (
|
|
26258
|
+
const discoursePath = join11(roundDir, "discourse.md");
|
|
26259
|
+
if (existsSync6(discoursePath)) {
|
|
26122
26260
|
this.processGenericArtifact(sessionId, "discourse", discoursePath, roundNumber);
|
|
26123
26261
|
}
|
|
26124
26262
|
}
|
|
26125
26263
|
}
|
|
26126
|
-
const mapDir =
|
|
26127
|
-
if (
|
|
26264
|
+
const mapDir = join11(sessionDir, "map", "runs");
|
|
26265
|
+
if (existsSync6(mapDir)) {
|
|
26128
26266
|
const runs = readdirSync(mapDir, { withFileTypes: true });
|
|
26129
26267
|
for (const runEntry of runs) {
|
|
26130
26268
|
if (!runEntry.isDirectory()) continue;
|
|
26131
26269
|
const runMatch = runEntry.name.match(/^run-(\d+)$/);
|
|
26132
26270
|
if (!runMatch) continue;
|
|
26133
26271
|
const runNumber = parseInt(runMatch[1] ?? "0", 10);
|
|
26134
|
-
const runDir =
|
|
26135
|
-
const mapMetaPath =
|
|
26136
|
-
if (
|
|
26272
|
+
const runDir = join11(mapDir, runEntry.name);
|
|
26273
|
+
const mapMetaPath = join11(runDir, "map-meta.json");
|
|
26274
|
+
if (existsSync6(mapMetaPath)) {
|
|
26137
26275
|
this.processMapMeta(sessionId, runNumber, mapMetaPath);
|
|
26138
26276
|
}
|
|
26139
|
-
const mapPath =
|
|
26140
|
-
if (
|
|
26277
|
+
const mapPath = join11(runDir, "map.md");
|
|
26278
|
+
if (existsSync6(mapPath)) {
|
|
26141
26279
|
this.processMapMd(sessionId, runNumber, mapPath);
|
|
26142
26280
|
}
|
|
26143
26281
|
const mapArtifacts = [
|
|
@@ -26146,8 +26284,8 @@ var FilesystemSync = class {
|
|
|
26146
26284
|
["requirements-mapping.md", "requirements-mapping"]
|
|
26147
26285
|
];
|
|
26148
26286
|
for (const [fileName, artifactType] of mapArtifacts) {
|
|
26149
|
-
const filePath =
|
|
26150
|
-
if (
|
|
26287
|
+
const filePath = join11(runDir, fileName);
|
|
26288
|
+
if (existsSync6(filePath)) {
|
|
26151
26289
|
this.processGenericArtifact(sessionId, artifactType, filePath, void 0, runNumber);
|
|
26152
26290
|
}
|
|
26153
26291
|
}
|
|
@@ -26158,8 +26296,8 @@ var FilesystemSync = class {
|
|
|
26158
26296
|
["discovered-standards.md", "discovered-standards"]
|
|
26159
26297
|
];
|
|
26160
26298
|
for (const [fileName, artifactType] of sessionArtifacts) {
|
|
26161
|
-
const filePath =
|
|
26162
|
-
if (
|
|
26299
|
+
const filePath = join11(sessionDir, fileName);
|
|
26300
|
+
if (existsSync6(filePath)) {
|
|
26163
26301
|
this.processGenericArtifact(sessionId, artifactType, filePath);
|
|
26164
26302
|
}
|
|
26165
26303
|
}
|
|
@@ -26168,58 +26306,58 @@ var FilesystemSync = class {
|
|
|
26168
26306
|
ensureSessionRow(sessionId, sessionDir) {
|
|
26169
26307
|
const branchMatch = sessionId.match(/^\d{4}-\d{2}-\d{2}-(.+)$/);
|
|
26170
26308
|
const branch = branchMatch?.[1] ?? "unknown";
|
|
26171
|
-
const hasRoundsDir =
|
|
26172
|
-
const hasMapDir =
|
|
26309
|
+
const hasRoundsDir = existsSync6(join11(sessionDir, "rounds"));
|
|
26310
|
+
const hasMapDir = existsSync6(join11(sessionDir, "map"));
|
|
26173
26311
|
const workflowType = hasMapDir && !hasRoundsDir ? "map" : "review";
|
|
26174
26312
|
let currentRound = 1;
|
|
26175
26313
|
if (hasRoundsDir) {
|
|
26176
|
-
const roundDirs = readdirSync(
|
|
26314
|
+
const roundDirs = readdirSync(join11(sessionDir, "rounds")).filter((d) => d.match(/^round-\d+$/));
|
|
26177
26315
|
currentRound = Math.max(1, roundDirs.length);
|
|
26178
26316
|
}
|
|
26179
26317
|
let currentMapRun = 1;
|
|
26180
|
-
const mapRunsDir =
|
|
26181
|
-
if (
|
|
26318
|
+
const mapRunsDir = join11(sessionDir, "map", "runs");
|
|
26319
|
+
if (existsSync6(mapRunsDir)) {
|
|
26182
26320
|
const runDirs = readdirSync(mapRunsDir).filter((d) => d.match(/^run-\d+$/));
|
|
26183
26321
|
currentMapRun = Math.max(1, runDirs.length);
|
|
26184
26322
|
}
|
|
26185
26323
|
let phase = "context";
|
|
26186
26324
|
let phaseNumber = 1;
|
|
26187
|
-
let status = "
|
|
26325
|
+
let status = "closed";
|
|
26188
26326
|
if (workflowType === "review" && hasRoundsDir) {
|
|
26189
|
-
const roundDir =
|
|
26190
|
-
if (
|
|
26327
|
+
const roundDir = join11(sessionDir, "rounds", `round-${currentRound}`);
|
|
26328
|
+
if (existsSync6(join11(roundDir, "final.md"))) {
|
|
26191
26329
|
phase = "complete";
|
|
26192
26330
|
phaseNumber = 8;
|
|
26193
26331
|
status = "closed";
|
|
26194
|
-
} else if (
|
|
26332
|
+
} else if (existsSync6(join11(roundDir, "discourse.md"))) {
|
|
26195
26333
|
phase = "synthesis";
|
|
26196
26334
|
phaseNumber = 7;
|
|
26197
|
-
} else if (
|
|
26335
|
+
} else if (existsSync6(join11(roundDir, "reviews")) && readdirSync(join11(roundDir, "reviews")).filter((f) => f.endsWith(".md")).length > 0) {
|
|
26198
26336
|
phase = "reviews";
|
|
26199
26337
|
phaseNumber = 4;
|
|
26200
|
-
} else if (
|
|
26338
|
+
} else if (existsSync6(join11(sessionDir, "context.md"))) {
|
|
26201
26339
|
phase = "analysis";
|
|
26202
26340
|
phaseNumber = 3;
|
|
26203
|
-
} else if (
|
|
26341
|
+
} else if (existsSync6(join11(sessionDir, "discovered-standards.md"))) {
|
|
26204
26342
|
phase = "change-context";
|
|
26205
26343
|
phaseNumber = 2;
|
|
26206
26344
|
}
|
|
26207
26345
|
} else if (workflowType === "map" && hasMapDir) {
|
|
26208
|
-
const runDir =
|
|
26209
|
-
if (
|
|
26346
|
+
const runDir = join11(mapRunsDir, `run-${currentMapRun}`);
|
|
26347
|
+
if (existsSync6(join11(runDir, "map.md"))) {
|
|
26210
26348
|
phase = "complete";
|
|
26211
26349
|
phaseNumber = 6;
|
|
26212
26350
|
status = "closed";
|
|
26213
|
-
} else if (
|
|
26351
|
+
} else if (existsSync6(join11(runDir, "requirements-mapping.md"))) {
|
|
26214
26352
|
phase = "synthesis";
|
|
26215
26353
|
phaseNumber = 5;
|
|
26216
|
-
} else if (
|
|
26354
|
+
} else if (existsSync6(join11(runDir, "flow-analysis.md"))) {
|
|
26217
26355
|
phase = "requirements-mapping";
|
|
26218
26356
|
phaseNumber = 4;
|
|
26219
|
-
} else if (
|
|
26357
|
+
} else if (existsSync6(join11(runDir, "topology.md"))) {
|
|
26220
26358
|
phase = "flow-analysis";
|
|
26221
26359
|
phaseNumber = 3;
|
|
26222
|
-
} else if (
|
|
26360
|
+
} else if (existsSync6(join11(sessionDir, "discovered-standards.md"))) {
|
|
26223
26361
|
phase = "topology";
|
|
26224
26362
|
phaseNumber = 2;
|
|
26225
26363
|
}
|
|
@@ -26232,6 +26370,7 @@ var FilesystemSync = class {
|
|
|
26232
26370
|
[currentRound, currentMapRun, sessionId]
|
|
26233
26371
|
);
|
|
26234
26372
|
} else {
|
|
26373
|
+
if (!this.hasArtifacts(sessionDir)) return;
|
|
26235
26374
|
this.db.run(
|
|
26236
26375
|
`INSERT INTO sessions (id, branch, workflow_type, current_phase, phase_number, current_round, current_map_run, session_dir, status)
|
|
26237
26376
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
@@ -26241,6 +26380,21 @@ var FilesystemSync = class {
|
|
|
26241
26380
|
}
|
|
26242
26381
|
this.onSync?.();
|
|
26243
26382
|
}
|
|
26383
|
+
// ── Artifact Check ──
|
|
26384
|
+
/** Returns true if the directory contains at least one .md or .json file (recursively). */
|
|
26385
|
+
hasArtifacts(dir) {
|
|
26386
|
+
try {
|
|
26387
|
+
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
|
26388
|
+
if (entry.isDirectory()) {
|
|
26389
|
+
if (this.hasArtifacts(join11(dir, entry.name))) return true;
|
|
26390
|
+
} else if (/\.(md|json)$/.test(entry.name)) {
|
|
26391
|
+
return true;
|
|
26392
|
+
}
|
|
26393
|
+
}
|
|
26394
|
+
} catch {
|
|
26395
|
+
}
|
|
26396
|
+
return false;
|
|
26397
|
+
}
|
|
26244
26398
|
// ── Mtime Skip Check ──
|
|
26245
26399
|
shouldSkip(filePath, existingParsedAt) {
|
|
26246
26400
|
if (!existingParsedAt) return false;
|
|
@@ -26282,12 +26436,12 @@ var FilesystemSync = class {
|
|
|
26282
26436
|
);
|
|
26283
26437
|
if (existingRun && this.shouldSkip(filePath, existingRun["parsed_at"] ?? null)) return;
|
|
26284
26438
|
if (existingRun?.["source"] === "orchestrator") {
|
|
26285
|
-
const content2 =
|
|
26439
|
+
const content2 = readFileSync7(filePath, "utf-8");
|
|
26286
26440
|
const action2 = this.upsertMarkdownArtifact(sessionId, "map", filePath, content2, void 0);
|
|
26287
26441
|
this.emitArtifactEvent(action2, { sessionId, artifactType: "map", filePath });
|
|
26288
26442
|
return;
|
|
26289
26443
|
}
|
|
26290
|
-
const content =
|
|
26444
|
+
const content = readFileSync7(filePath, "utf-8");
|
|
26291
26445
|
const parsed = parseMapMd(content);
|
|
26292
26446
|
this.db.run(
|
|
26293
26447
|
`INSERT OR REPLACE INTO map_runs (session_id, run_number, file_count, map_md_path, parsed_at, source)
|
|
@@ -26407,7 +26561,7 @@ var FilesystemSync = class {
|
|
|
26407
26561
|
const roundId = roundRow?.["id"];
|
|
26408
26562
|
if (!roundId) return;
|
|
26409
26563
|
if (roundRow?.["source"] === "orchestrator") {
|
|
26410
|
-
const content2 =
|
|
26564
|
+
const content2 = readFileSync7(filePath, "utf-8");
|
|
26411
26565
|
const action2 = this.upsertMarkdownArtifact(sessionId, "reviewer-output", filePath, content2, roundNumber);
|
|
26412
26566
|
this.emitArtifactEvent(action2, {
|
|
26413
26567
|
sessionId,
|
|
@@ -26426,7 +26580,7 @@ var FilesystemSync = class {
|
|
|
26426
26580
|
[roundId, reviewerType, instanceNumber]
|
|
26427
26581
|
);
|
|
26428
26582
|
if (existingOutput && this.shouldSkip(filePath, existingOutput["parsed_at"] ?? null)) return;
|
|
26429
|
-
const content =
|
|
26583
|
+
const content = readFileSync7(filePath, "utf-8");
|
|
26430
26584
|
const parsed = parseReviewerOutput(content);
|
|
26431
26585
|
this.db.run(
|
|
26432
26586
|
`INSERT OR REPLACE INTO reviewer_outputs (round_id, reviewer_type, instance_number, file_path, finding_count, parsed_at)
|
|
@@ -26514,7 +26668,7 @@ var FilesystemSync = class {
|
|
|
26514
26668
|
if (existingRound?.["source"] === "orchestrator" && this.shouldSkip(filePath, existingRound["parsed_at"] ?? null)) return;
|
|
26515
26669
|
let raw;
|
|
26516
26670
|
try {
|
|
26517
|
-
raw = JSON.parse(
|
|
26671
|
+
raw = JSON.parse(readFileSync7(filePath, "utf-8"));
|
|
26518
26672
|
} catch {
|
|
26519
26673
|
console.error(`[FilesystemSync] Failed to parse ${filePath}`);
|
|
26520
26674
|
return;
|
|
@@ -26560,12 +26714,12 @@ var FilesystemSync = class {
|
|
|
26560
26714
|
this.db.run("COMMIT");
|
|
26561
26715
|
return;
|
|
26562
26716
|
}
|
|
26563
|
-
const roundDir =
|
|
26717
|
+
const roundDir = dirname8(filePath);
|
|
26564
26718
|
for (const reviewer of meta.reviewers) {
|
|
26565
26719
|
const reviewerType = reviewer.type ?? "unknown";
|
|
26566
26720
|
const instanceNumber = reviewer.instance ?? 1;
|
|
26567
26721
|
const findings = reviewer.findings ?? [];
|
|
26568
|
-
const reviewerMdPath =
|
|
26722
|
+
const reviewerMdPath = join11(roundDir, "reviews", `${reviewerType}-${instanceNumber}.md`);
|
|
26569
26723
|
this.db.run(
|
|
26570
26724
|
`INSERT OR REPLACE INTO reviewer_outputs (round_id, reviewer_type, instance_number, file_path, finding_count, parsed_at)
|
|
26571
26725
|
VALUES (?, ?, ?, ?, ?, ?)`,
|
|
@@ -26663,7 +26817,7 @@ var FilesystemSync = class {
|
|
|
26663
26817
|
if (existingRun?.["source"] === "orchestrator" && this.shouldSkip(filePath, existingRun["parsed_at"] ?? null)) return;
|
|
26664
26818
|
let raw;
|
|
26665
26819
|
try {
|
|
26666
|
-
raw = JSON.parse(
|
|
26820
|
+
raw = JSON.parse(readFileSync7(filePath, "utf-8"));
|
|
26667
26821
|
} catch {
|
|
26668
26822
|
console.error(`[FilesystemSync] Failed to parse ${filePath}`);
|
|
26669
26823
|
return;
|
|
@@ -26791,7 +26945,7 @@ var FilesystemSync = class {
|
|
|
26791
26945
|
);
|
|
26792
26946
|
const isOrchestratorSource = existingRound?.["source"] === "orchestrator";
|
|
26793
26947
|
if (!isOrchestratorSource && existingRound && this.shouldSkip(filePath, existingRound["parsed_at"] ?? null)) return;
|
|
26794
|
-
const content =
|
|
26948
|
+
const content = readFileSync7(filePath, "utf-8");
|
|
26795
26949
|
if (isOrchestratorSource) {
|
|
26796
26950
|
this.db.run(
|
|
26797
26951
|
`UPDATE review_rounds SET final_md_path = ?, parsed_at = ?
|
|
@@ -26864,7 +27018,7 @@ var FilesystemSync = class {
|
|
|
26864
27018
|
[sessionId, artifactType, relPath]
|
|
26865
27019
|
);
|
|
26866
27020
|
if (existing && this.shouldSkip(filePath, existing["parsed_at"] ?? null)) return;
|
|
26867
|
-
const content =
|
|
27021
|
+
const content = readFileSync7(filePath, "utf-8");
|
|
26868
27022
|
const action = this.upsertMarkdownArtifact(sessionId, artifactType, filePath, content, roundNumber);
|
|
26869
27023
|
this.emitArtifactEvent(action, {
|
|
26870
27024
|
sessionId,
|
|
@@ -26923,7 +27077,7 @@ var FilesystemSync = class {
|
|
|
26923
27077
|
const parts = relFromSessions.split("/");
|
|
26924
27078
|
const sessionId = parts[0];
|
|
26925
27079
|
if (!sessionId) return;
|
|
26926
|
-
const sessionDir =
|
|
27080
|
+
const sessionDir = join11(this.sessionsDir, sessionId);
|
|
26927
27081
|
this.ensureSessionRow(sessionId, sessionDir);
|
|
26928
27082
|
const fileName = basename2(filePath);
|
|
26929
27083
|
const reviewerMatch = relFromSessions.match(/rounds\/round-(\d+)\/reviews\/(.+\.md)$/);
|
|
@@ -26995,7 +27149,7 @@ var FilesystemSync = class {
|
|
|
26995
27149
|
};
|
|
26996
27150
|
|
|
26997
27151
|
// src/server/services/db-sync-watcher.ts
|
|
26998
|
-
import { existsSync as
|
|
27152
|
+
import { existsSync as existsSync7, readFileSync as readFileSync8, statSync as statSync2 } from "node:fs";
|
|
26999
27153
|
import { watch as watch3 } from "chokidar";
|
|
27000
27154
|
import initSqlJs3 from "sql.js";
|
|
27001
27155
|
function col(row, key) {
|
|
@@ -27017,7 +27171,7 @@ var DbSyncWatcher = class {
|
|
|
27017
27171
|
* Initialize the WASM runtime (called once at startup).
|
|
27018
27172
|
*/
|
|
27019
27173
|
async init() {
|
|
27020
|
-
const wasmBuffer =
|
|
27174
|
+
const wasmBuffer = readFileSync8(locateWasm());
|
|
27021
27175
|
this.wasmBinary = wasmBuffer.buffer.slice(
|
|
27022
27176
|
wasmBuffer.byteOffset,
|
|
27023
27177
|
wasmBuffer.byteOffset + wasmBuffer.byteLength
|
|
@@ -27028,7 +27182,7 @@ var DbSyncWatcher = class {
|
|
|
27028
27182
|
* Start watching the DB file for external changes.
|
|
27029
27183
|
*/
|
|
27030
27184
|
startWatching() {
|
|
27031
|
-
if (!
|
|
27185
|
+
if (!existsSync7(this.dbFilePath)) return;
|
|
27032
27186
|
try {
|
|
27033
27187
|
this.lastMtime = statSync2(this.dbFilePath).mtimeMs;
|
|
27034
27188
|
} catch {
|
|
@@ -27079,7 +27233,7 @@ var DbSyncWatcher = class {
|
|
|
27079
27233
|
* to avoid overwriting CLI changes.
|
|
27080
27234
|
*/
|
|
27081
27235
|
syncFromDisk() {
|
|
27082
|
-
if (!this.SQL || !
|
|
27236
|
+
if (!this.SQL || !existsSync7(this.dbFilePath)) return;
|
|
27083
27237
|
let currentMtime;
|
|
27084
27238
|
try {
|
|
27085
27239
|
currentMtime = statSync2(this.dbFilePath).mtimeMs;
|
|
@@ -27090,7 +27244,7 @@ var DbSyncWatcher = class {
|
|
|
27090
27244
|
this.lastMtime = currentMtime;
|
|
27091
27245
|
let diskDb = null;
|
|
27092
27246
|
try {
|
|
27093
|
-
const fileBuffer =
|
|
27247
|
+
const fileBuffer = readFileSync8(this.dbFilePath);
|
|
27094
27248
|
diskDb = new this.SQL.Database(fileBuffer);
|
|
27095
27249
|
this.syncSessions(diskDb);
|
|
27096
27250
|
this.syncEvents(diskDb);
|
|
@@ -27344,20 +27498,20 @@ var DbSyncWatcher = class {
|
|
|
27344
27498
|
};
|
|
27345
27499
|
|
|
27346
27500
|
// src/server/socket/chat-handler.ts
|
|
27347
|
-
import { dirname as
|
|
27501
|
+
import { dirname as dirname9 } from "node:path";
|
|
27348
27502
|
|
|
27349
27503
|
// src/server/services/chat-context.ts
|
|
27350
|
-
import { readFileSync as
|
|
27351
|
-
import { join as
|
|
27504
|
+
import { readFileSync as readFileSync9, readdirSync as readdirSync2, existsSync as existsSync8 } from "node:fs";
|
|
27505
|
+
import { join as join12 } from "node:path";
|
|
27352
27506
|
function buildChatContext(ocrDir, target) {
|
|
27353
|
-
const sessionsDir =
|
|
27507
|
+
const sessionsDir = join12(ocrDir, "sessions");
|
|
27354
27508
|
if (target.type === "map_run") {
|
|
27355
27509
|
return buildMapRunContext(sessionsDir, target.sessionId, target.runNumber);
|
|
27356
27510
|
}
|
|
27357
27511
|
return buildReviewRoundContext(sessionsDir, target.sessionId, target.roundNumber);
|
|
27358
27512
|
}
|
|
27359
27513
|
function buildMapRunContext(sessionsDir, sessionId, runNumber) {
|
|
27360
|
-
const mapPath =
|
|
27514
|
+
const mapPath = join12(
|
|
27361
27515
|
sessionsDir,
|
|
27362
27516
|
sessionId,
|
|
27363
27517
|
"map",
|
|
@@ -27371,8 +27525,8 @@ function buildMapRunContext(sessionsDir, sessionId, runNumber) {
|
|
|
27371
27525
|
"",
|
|
27372
27526
|
`Below is the Code Review Map that organizes the changeset into reviewable sections:`
|
|
27373
27527
|
];
|
|
27374
|
-
if (
|
|
27375
|
-
const content =
|
|
27528
|
+
if (existsSync8(mapPath)) {
|
|
27529
|
+
const content = readFileSync9(mapPath, "utf-8");
|
|
27376
27530
|
parts.push("");
|
|
27377
27531
|
parts.push("<map>");
|
|
27378
27532
|
parts.push(content);
|
|
@@ -27384,26 +27538,26 @@ function buildMapRunContext(sessionsDir, sessionId, runNumber) {
|
|
|
27384
27538
|
return parts.join("\n");
|
|
27385
27539
|
}
|
|
27386
27540
|
function buildReviewRoundContext(sessionsDir, sessionId, roundNumber) {
|
|
27387
|
-
const roundDir =
|
|
27388
|
-
const finalPath =
|
|
27389
|
-
const reviewersDir =
|
|
27541
|
+
const roundDir = join12(sessionsDir, sessionId, "rounds", `round-${roundNumber}`);
|
|
27542
|
+
const finalPath = join12(roundDir, "final.md");
|
|
27543
|
+
const reviewersDir = join12(roundDir, "reviews");
|
|
27390
27544
|
const parts = [
|
|
27391
27545
|
`You are an expert code reviewer assisting with a code review session.`,
|
|
27392
27546
|
`You are looking at review round #${roundNumber} for session "${sessionId}".`,
|
|
27393
27547
|
"",
|
|
27394
27548
|
`Below are the review artifacts for this round:`
|
|
27395
27549
|
];
|
|
27396
|
-
if (
|
|
27397
|
-
const content =
|
|
27550
|
+
if (existsSync8(finalPath)) {
|
|
27551
|
+
const content = readFileSync9(finalPath, "utf-8");
|
|
27398
27552
|
parts.push("");
|
|
27399
27553
|
parts.push("<final-synthesis>");
|
|
27400
27554
|
parts.push(content);
|
|
27401
27555
|
parts.push("</final-synthesis>");
|
|
27402
27556
|
}
|
|
27403
|
-
if (
|
|
27557
|
+
if (existsSync8(reviewersDir)) {
|
|
27404
27558
|
const files = readdirSync2(reviewersDir).filter((f) => f.endsWith(".md")).sort();
|
|
27405
27559
|
for (const file of files) {
|
|
27406
|
-
const content =
|
|
27560
|
+
const content = readFileSync9(join12(reviewersDir, file), "utf-8");
|
|
27407
27561
|
const reviewerName = file.replace(/\.md$/, "");
|
|
27408
27562
|
parts.push("");
|
|
27409
27563
|
parts.push(`<reviewer name="${reviewerName}">`);
|
|
@@ -27411,7 +27565,7 @@ function buildReviewRoundContext(sessionsDir, sessionId, roundNumber) {
|
|
|
27411
27565
|
parts.push("</reviewer>");
|
|
27412
27566
|
}
|
|
27413
27567
|
}
|
|
27414
|
-
if (!
|
|
27568
|
+
if (!existsSync8(finalPath) && !existsSync8(reviewersDir)) {
|
|
27415
27569
|
parts.push("");
|
|
27416
27570
|
parts.push("(No review artifacts found on disk for this round.)");
|
|
27417
27571
|
}
|
|
@@ -27421,13 +27575,26 @@ function buildReviewRoundContext(sessionsDir, sessionId, roundNumber) {
|
|
|
27421
27575
|
// src/server/socket/execution-tracker.ts
|
|
27422
27576
|
function startTrackedExecution(io2, db, ocrDir, command, args = []) {
|
|
27423
27577
|
const startedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
27578
|
+
const uid = generateCommandUid();
|
|
27579
|
+
const argsJson = JSON.stringify(args);
|
|
27424
27580
|
db.run(
|
|
27425
|
-
`INSERT INTO command_executions (command, args, started_at)
|
|
27426
|
-
VALUES (?, ?, ?)`,
|
|
27427
|
-
[command,
|
|
27581
|
+
`INSERT INTO command_executions (uid, command, args, started_at)
|
|
27582
|
+
VALUES (?, ?, ?, ?)`,
|
|
27583
|
+
[uid, command, argsJson, startedAt]
|
|
27428
27584
|
);
|
|
27429
27585
|
const idResult = db.exec("SELECT last_insert_rowid() as id");
|
|
27430
27586
|
const executionId = idResult[0]?.values[0]?.[0] ?? 0;
|
|
27587
|
+
const baseLogEntry = {
|
|
27588
|
+
v: 1,
|
|
27589
|
+
uid,
|
|
27590
|
+
db_id: executionId,
|
|
27591
|
+
command,
|
|
27592
|
+
args: argsJson,
|
|
27593
|
+
started_at: startedAt,
|
|
27594
|
+
is_detached: 0,
|
|
27595
|
+
writer: "dashboard"
|
|
27596
|
+
};
|
|
27597
|
+
appendCommandLog(ocrDir, { ...baseLogEntry, event: "start", exit_code: null, finished_at: null });
|
|
27431
27598
|
io2.emit("command:started", {
|
|
27432
27599
|
execution_id: executionId,
|
|
27433
27600
|
command,
|
|
@@ -27435,6 +27602,7 @@ function startTrackedExecution(io2, db, ocrDir, command, args = []) {
|
|
|
27435
27602
|
started_at: startedAt
|
|
27436
27603
|
});
|
|
27437
27604
|
let outputBuffer = "";
|
|
27605
|
+
let trackedIsDetached = 0;
|
|
27438
27606
|
return {
|
|
27439
27607
|
executionId,
|
|
27440
27608
|
appendOutput(content) {
|
|
@@ -27442,9 +27610,10 @@ function startTrackedExecution(io2, db, ocrDir, command, args = []) {
|
|
|
27442
27610
|
io2.emit("command:output", { execution_id: executionId, content });
|
|
27443
27611
|
},
|
|
27444
27612
|
setPid(pid, isDetached) {
|
|
27613
|
+
trackedIsDetached = isDetached ? 1 : 0;
|
|
27445
27614
|
db.run(
|
|
27446
27615
|
"UPDATE command_executions SET pid = ?, is_detached = ? WHERE id = ?",
|
|
27447
|
-
[pid,
|
|
27616
|
+
[pid, trackedIsDetached, executionId]
|
|
27448
27617
|
);
|
|
27449
27618
|
},
|
|
27450
27619
|
finish(exitCode) {
|
|
@@ -27456,6 +27625,13 @@ function startTrackedExecution(io2, db, ocrDir, command, args = []) {
|
|
|
27456
27625
|
[exitCode, finishedAt, outputBuffer, executionId]
|
|
27457
27626
|
);
|
|
27458
27627
|
saveDb(db, ocrDir);
|
|
27628
|
+
appendCommandLog(ocrDir, {
|
|
27629
|
+
...baseLogEntry,
|
|
27630
|
+
is_detached: trackedIsDetached,
|
|
27631
|
+
event: exitCode === -2 ? "cancel" : "finish",
|
|
27632
|
+
exit_code: exitCode,
|
|
27633
|
+
finished_at: finishedAt
|
|
27634
|
+
});
|
|
27459
27635
|
io2.emit("command:finished", {
|
|
27460
27636
|
execution_id: executionId,
|
|
27461
27637
|
exitCode,
|
|
@@ -27539,7 +27715,7 @@ User: ${message}`;
|
|
|
27539
27715
|
});
|
|
27540
27716
|
return;
|
|
27541
27717
|
}
|
|
27542
|
-
const repoRoot =
|
|
27718
|
+
const repoRoot = dirname9(ocrDir);
|
|
27543
27719
|
const spawnResult = adapter.spawn({
|
|
27544
27720
|
prompt,
|
|
27545
27721
|
cwd: repoRoot,
|
|
@@ -27716,15 +27892,15 @@ function cleanupAllChats() {
|
|
|
27716
27892
|
|
|
27717
27893
|
// src/server/socket/post-handler.ts
|
|
27718
27894
|
import { execFile } from "node:child_process";
|
|
27719
|
-
import { existsSync as
|
|
27895
|
+
import { existsSync as existsSync9, mkdirSync as mkdirSync3, readFileSync as readFileSync10, unlinkSync, writeFileSync as writeFileSync4 } from "node:fs";
|
|
27720
27896
|
import { tmpdir as tmpdir2 } from "node:os";
|
|
27721
|
-
import { join as
|
|
27722
|
-
import { randomUUID } from "node:crypto";
|
|
27897
|
+
import { join as join13, dirname as dirname10, isAbsolute } from "node:path";
|
|
27898
|
+
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
27723
27899
|
import { promisify } from "node:util";
|
|
27724
27900
|
var execFileAsync = promisify(execFile);
|
|
27725
27901
|
function resolveSessionDir(sessionDir, ocrDir) {
|
|
27726
27902
|
if (isAbsolute(sessionDir)) return sessionDir;
|
|
27727
|
-
return
|
|
27903
|
+
return join13(dirname10(ocrDir), sessionDir);
|
|
27728
27904
|
}
|
|
27729
27905
|
var BRANCH_PREFIXES = [
|
|
27730
27906
|
"feat",
|
|
@@ -27793,7 +27969,7 @@ function registerPostHandlers(io2, socket, db, ocrDir, aiCliService) {
|
|
|
27793
27969
|
return;
|
|
27794
27970
|
}
|
|
27795
27971
|
const branch = session.branch;
|
|
27796
|
-
const repoRoot =
|
|
27972
|
+
const repoRoot = dirname10(ocrDir);
|
|
27797
27973
|
try {
|
|
27798
27974
|
await execFileAsync("gh", ["auth", "status"], { env: cleanEnv(), cwd: repoRoot });
|
|
27799
27975
|
} catch {
|
|
@@ -27894,19 +28070,19 @@ function registerPostHandlers(io2, socket, db, ocrDir, aiCliService) {
|
|
|
27894
28070
|
socket.emit("post:error", { error: "Session not found" });
|
|
27895
28071
|
return;
|
|
27896
28072
|
}
|
|
27897
|
-
const sessionDir = session.session_dir ? resolveSessionDir(session.session_dir, ocrDir) :
|
|
27898
|
-
const roundDir =
|
|
27899
|
-
const finalPath =
|
|
27900
|
-
if (!
|
|
28073
|
+
const sessionDir = session.session_dir ? resolveSessionDir(session.session_dir, ocrDir) : join13(ocrDir, "sessions", sessionId);
|
|
28074
|
+
const roundDir = join13(sessionDir, "rounds", `round-${roundNumber}`);
|
|
28075
|
+
const finalPath = join13(roundDir, "final.md");
|
|
28076
|
+
if (!existsSync9(finalPath)) {
|
|
27901
28077
|
socket.emit("post:error", { error: "final.md not found for this round" });
|
|
27902
28078
|
return;
|
|
27903
28079
|
}
|
|
27904
|
-
const humanReviewPath =
|
|
27905
|
-
const repoRoot =
|
|
27906
|
-
const commandMdPath =
|
|
28080
|
+
const humanReviewPath = join13(roundDir, "final-human.md");
|
|
28081
|
+
const repoRoot = dirname10(ocrDir);
|
|
28082
|
+
const commandMdPath = join13(ocrDir, "commands", "translate-review-to-single-human.md");
|
|
27907
28083
|
let commandContent;
|
|
27908
28084
|
try {
|
|
27909
|
-
commandContent =
|
|
28085
|
+
commandContent = readFileSync10(commandMdPath, "utf-8");
|
|
27910
28086
|
} catch {
|
|
27911
28087
|
socket.emit("post:error", {
|
|
27912
28088
|
error: `Command file not found: ${commandMdPath}. Run \`ocr init\` to set up.`
|
|
@@ -27982,9 +28158,9 @@ function registerPostHandlers(io2, socket, db, ocrDir, aiCliService) {
|
|
|
27982
28158
|
}
|
|
27983
28159
|
}
|
|
27984
28160
|
let generatedContent = "";
|
|
27985
|
-
if (
|
|
28161
|
+
if (existsSync9(humanReviewPath)) {
|
|
27986
28162
|
try {
|
|
27987
|
-
generatedContent =
|
|
28163
|
+
generatedContent = readFileSync10(humanReviewPath, "utf-8").trim();
|
|
27988
28164
|
} catch {
|
|
27989
28165
|
}
|
|
27990
28166
|
}
|
|
@@ -28059,11 +28235,11 @@ function registerPostHandlers(io2, socket, db, ocrDir, aiCliService) {
|
|
|
28059
28235
|
socket.emit("post:save-result", { success: false, error: "Session not found" });
|
|
28060
28236
|
return;
|
|
28061
28237
|
}
|
|
28062
|
-
const sessionDir = session.session_dir ? resolveSessionDir(session.session_dir, ocrDir) :
|
|
28063
|
-
const roundDir =
|
|
28064
|
-
|
|
28065
|
-
const filePath =
|
|
28066
|
-
|
|
28238
|
+
const sessionDir = session.session_dir ? resolveSessionDir(session.session_dir, ocrDir) : join13(ocrDir, "sessions", sessionId);
|
|
28239
|
+
const roundDir = join13(sessionDir, "rounds", `round-${roundNumber}`);
|
|
28240
|
+
mkdirSync3(roundDir, { recursive: true });
|
|
28241
|
+
const filePath = join13(roundDir, "final-human.md");
|
|
28242
|
+
writeFileSync4(filePath, content, { mode: 420 });
|
|
28067
28243
|
saveDb(db, ocrDir);
|
|
28068
28244
|
socket.emit("post:save-result", { success: true });
|
|
28069
28245
|
} catch (err) {
|
|
@@ -28090,14 +28266,14 @@ function registerPostHandlers(io2, socket, db, ocrDir, aiCliService) {
|
|
|
28090
28266
|
);
|
|
28091
28267
|
tracker.appendOutput(`\u25B8 Posting review to PR #${prNumber}...
|
|
28092
28268
|
`);
|
|
28093
|
-
const tmpDir =
|
|
28269
|
+
const tmpDir = join13(tmpdir2(), "ocr-post-comments");
|
|
28094
28270
|
try {
|
|
28095
|
-
|
|
28271
|
+
mkdirSync3(tmpDir, { recursive: true, mode: 448 });
|
|
28096
28272
|
} catch {
|
|
28097
28273
|
}
|
|
28098
|
-
const tmpFile =
|
|
28099
|
-
|
|
28100
|
-
const repoRoot =
|
|
28274
|
+
const tmpFile = join13(tmpDir, `${randomUUID2()}.md`);
|
|
28275
|
+
writeFileSync4(tmpFile, content, { mode: 384 });
|
|
28276
|
+
const repoRoot = dirname10(ocrDir);
|
|
28101
28277
|
try {
|
|
28102
28278
|
const { stdout } = await execFileAsync(
|
|
28103
28279
|
"gh",
|
|
@@ -28142,17 +28318,21 @@ function cleanupAllPostGenerations() {
|
|
|
28142
28318
|
|
|
28143
28319
|
// src/server/index.ts
|
|
28144
28320
|
import { homedir } from "node:os";
|
|
28145
|
-
var __dirname3 =
|
|
28321
|
+
var __dirname3 = dirname11(fileURLToPath3(import.meta.url));
|
|
28146
28322
|
function shortenPath(p) {
|
|
28147
28323
|
const home = homedir();
|
|
28148
28324
|
return p.startsWith(home) ? "~" + p.slice(home.length) : p;
|
|
28149
28325
|
}
|
|
28326
|
+
function isLocalhostOrigin(origin) {
|
|
28327
|
+
if (!origin) return false;
|
|
28328
|
+
return /^https?:\/\/(localhost|127\.0\.0\.1)(:\d+)?$/.test(origin);
|
|
28329
|
+
}
|
|
28150
28330
|
var AUTH_TOKEN = randomBytes(32).toString("hex");
|
|
28151
28331
|
var app = (0, import_express12.default)();
|
|
28152
28332
|
var httpServer = createServer(app);
|
|
28153
28333
|
var io = new SocketIOServer(httpServer, {
|
|
28154
28334
|
cors: {
|
|
28155
|
-
origin: process.env.NODE_ENV !== "production" ?
|
|
28335
|
+
origin: process.env.NODE_ENV !== "production" ? (origin, cb) => cb(null, !origin || isLocalhostOrigin(origin)) : false
|
|
28156
28336
|
},
|
|
28157
28337
|
maxHttpBufferSize: 1e6
|
|
28158
28338
|
// 1 MB — explicit default; review if large payloads are needed
|
|
@@ -28161,8 +28341,7 @@ app.use(import_express12.default.json());
|
|
|
28161
28341
|
if (process.env.NODE_ENV !== "production") {
|
|
28162
28342
|
app.use((_req, res, next) => {
|
|
28163
28343
|
const origin = _req.headers.origin;
|
|
28164
|
-
|
|
28165
|
-
if (origin && allowed.includes(origin)) {
|
|
28344
|
+
if (origin && isLocalhostOrigin(origin)) {
|
|
28166
28345
|
res.header("Access-Control-Allow-Origin", origin);
|
|
28167
28346
|
}
|
|
28168
28347
|
res.header("Access-Control-Allow-Methods", "GET, POST, PATCH, DELETE, OPTIONS");
|
|
@@ -28193,8 +28372,7 @@ io.use((socket, next) => {
|
|
|
28193
28372
|
if (process.env.NODE_ENV !== "production") {
|
|
28194
28373
|
app.get("/auth/token", (req, res) => {
|
|
28195
28374
|
const origin = req.headers.origin;
|
|
28196
|
-
|
|
28197
|
-
if (origin && !allowed.includes(origin)) {
|
|
28375
|
+
if (origin && !isLocalhostOrigin(origin)) {
|
|
28198
28376
|
res.status(403).json({ error: "Forbidden: invalid origin" });
|
|
28199
28377
|
return;
|
|
28200
28378
|
}
|
|
@@ -28209,12 +28387,12 @@ async function startServer(options = {}) {
|
|
|
28209
28387
|
const ocrDir = resolveOcrDir();
|
|
28210
28388
|
const aiCliService = new AiCliService(ocrDir);
|
|
28211
28389
|
const db = await openDb(ocrDir);
|
|
28212
|
-
const dataDir =
|
|
28213
|
-
const pidFilePath =
|
|
28214
|
-
|
|
28215
|
-
if (
|
|
28390
|
+
const dataDir = join14(ocrDir, "data");
|
|
28391
|
+
const pidFilePath = join14(dataDir, "dashboard.pid");
|
|
28392
|
+
mkdirSync4(dataDir, { recursive: true });
|
|
28393
|
+
if (existsSync10(pidFilePath)) {
|
|
28216
28394
|
try {
|
|
28217
|
-
const oldPid = parseInt(
|
|
28395
|
+
const oldPid = parseInt(readFileSync11(pidFilePath, "utf-8").trim(), 10);
|
|
28218
28396
|
if (!isNaN(oldPid)) {
|
|
28219
28397
|
try {
|
|
28220
28398
|
process.kill(oldPid, 0);
|
|
@@ -28227,7 +28405,16 @@ async function startServer(options = {}) {
|
|
|
28227
28405
|
} catch {
|
|
28228
28406
|
}
|
|
28229
28407
|
}
|
|
28230
|
-
|
|
28408
|
+
writeFileSync5(pidFilePath, String(process.pid), { mode: 384 });
|
|
28409
|
+
const cmdCountResult = db.exec("SELECT COUNT(*) as c FROM command_executions");
|
|
28410
|
+
const totalCmds = cmdCountResult[0]?.values[0]?.[0] ?? 0;
|
|
28411
|
+
if (totalCmds === 0) {
|
|
28412
|
+
const recovered = replayCommandLog(db, ocrDir);
|
|
28413
|
+
if (recovered > 0) {
|
|
28414
|
+
saveDb(db, ocrDir);
|
|
28415
|
+
console.log(` Recovered ${recovered} command(s) from JSONL backup`);
|
|
28416
|
+
}
|
|
28417
|
+
}
|
|
28231
28418
|
const orphanResult = db.exec(
|
|
28232
28419
|
`SELECT id, pid, is_detached, started_at FROM command_executions
|
|
28233
28420
|
WHERE pid IS NOT NULL AND finished_at IS NULL`
|
|
@@ -28314,11 +28501,11 @@ async function startServer(options = {}) {
|
|
|
28314
28501
|
app.use("/api/config", createConfigRouter(ocrDir, aiCliService));
|
|
28315
28502
|
app.use("/api/sessions", createChatRouter(db, ocrDir));
|
|
28316
28503
|
app.use("/api/reviewers", createReviewersRouter(ocrDir));
|
|
28317
|
-
const clientDir =
|
|
28318
|
-
if (process.env.NODE_ENV === "production" &&
|
|
28504
|
+
const clientDir = join14(__dirname3, "client");
|
|
28505
|
+
if (process.env.NODE_ENV === "production" && existsSync10(clientDir)) {
|
|
28319
28506
|
app.use(import_express12.default.static(clientDir, { index: false }));
|
|
28320
|
-
const indexHtmlPath =
|
|
28321
|
-
const rawIndexHtml =
|
|
28507
|
+
const indexHtmlPath = join14(clientDir, "index.html");
|
|
28508
|
+
const rawIndexHtml = existsSync10(indexHtmlPath) ? readFileSync11(indexHtmlPath, "utf-8") : "";
|
|
28322
28509
|
const tokenScript = `<script>window.__OCR_TOKEN__=${JSON.stringify(AUTH_TOKEN)};</script>`;
|
|
28323
28510
|
const injectedIndexHtml = rawIndexHtml.replace(
|
|
28324
28511
|
"</head>",
|
|
@@ -28337,7 +28524,7 @@ async function startServer(options = {}) {
|
|
|
28337
28524
|
registerChatHandlers(io, socket, db, ocrDir, aiCliService);
|
|
28338
28525
|
registerPostHandlers(io, socket, db, ocrDir, aiCliService);
|
|
28339
28526
|
});
|
|
28340
|
-
const dbFilePath =
|
|
28527
|
+
const dbFilePath = join14(ocrDir, "data", "ocr.db");
|
|
28341
28528
|
const dbSyncWatcher = new DbSyncWatcher(db, dbFilePath, io, () => {
|
|
28342
28529
|
saveDb(db, ocrDir);
|
|
28343
28530
|
});
|
|
@@ -28348,36 +28535,57 @@ async function startServer(options = {}) {
|
|
|
28348
28535
|
() => dbSyncWatcher.syncFromDisk(),
|
|
28349
28536
|
() => dbSyncWatcher.markOwnWrite()
|
|
28350
28537
|
);
|
|
28351
|
-
const sessionsDir =
|
|
28538
|
+
const sessionsDir = join14(ocrDir, "sessions");
|
|
28352
28539
|
const fsSync = new FilesystemSync(db, sessionsDir, io, () => saveDb(db, ocrDir));
|
|
28353
28540
|
await fsSync.fullScan();
|
|
28354
28541
|
saveDb(db, ocrDir);
|
|
28355
28542
|
fsSync.startWatching();
|
|
28356
28543
|
console.log(` Watching sessions: ${shortenPath(sessionsDir)}`);
|
|
28357
28544
|
const stopReviewersWatch = watchReviewersMeta(ocrDir, io);
|
|
28358
|
-
|
|
28359
|
-
|
|
28360
|
-
|
|
28361
|
-
|
|
28362
|
-
|
|
28363
|
-
));
|
|
28545
|
+
const MAX_PORT_ATTEMPTS = 10;
|
|
28546
|
+
let actualPort = port;
|
|
28547
|
+
for (let attempt = 0; attempt < MAX_PORT_ATTEMPTS; attempt++) {
|
|
28548
|
+
try {
|
|
28549
|
+
await new Promise((resolve3, reject) => {
|
|
28550
|
+
const onError = (err) => reject(err);
|
|
28551
|
+
httpServer.once("error", onError);
|
|
28552
|
+
httpServer.listen(actualPort, "127.0.0.1", () => {
|
|
28553
|
+
httpServer.removeListener("error", onError);
|
|
28554
|
+
resolve3();
|
|
28555
|
+
});
|
|
28556
|
+
});
|
|
28557
|
+
break;
|
|
28558
|
+
} catch (err) {
|
|
28559
|
+
const nodeErr = err;
|
|
28560
|
+
if (nodeErr.code === "EADDRINUSE") {
|
|
28561
|
+
httpServer.close();
|
|
28562
|
+
if (attempt < MAX_PORT_ATTEMPTS - 1) {
|
|
28563
|
+
console.log(` Port ${actualPort} in use, trying ${actualPort + 1}...`);
|
|
28564
|
+
actualPort++;
|
|
28565
|
+
} else {
|
|
28566
|
+
throw new Error(
|
|
28567
|
+
`Could not find an available port (tried ${port}\u2013${actualPort}). Stop other processes or set PORT explicitly.`
|
|
28568
|
+
);
|
|
28569
|
+
}
|
|
28364
28570
|
} else {
|
|
28365
|
-
|
|
28571
|
+
throw err;
|
|
28366
28572
|
}
|
|
28367
|
-
}
|
|
28368
|
-
|
|
28369
|
-
|
|
28370
|
-
|
|
28371
|
-
|
|
28372
|
-
|
|
28373
|
-
|
|
28374
|
-
|
|
28375
|
-
|
|
28376
|
-
|
|
28573
|
+
}
|
|
28574
|
+
}
|
|
28575
|
+
if (actualPort !== port) {
|
|
28576
|
+
console.log(` Note: using port ${actualPort} (${port} was in use)`);
|
|
28577
|
+
}
|
|
28578
|
+
const portFilePath = join14(dataDir, "server-port");
|
|
28579
|
+
writeFileSync5(portFilePath, String(actualPort), { mode: 384 });
|
|
28580
|
+
console.log(` Server: http://localhost:${actualPort}`);
|
|
28581
|
+
console.log(` OCR directory: ${shortenPath(ocrDir)}`);
|
|
28582
|
+
console.log();
|
|
28583
|
+
console.log(` Auth token: ${AUTH_TOKEN.slice(0, 8)}...[redacted]`);
|
|
28584
|
+
console.log();
|
|
28377
28585
|
if (options.open) {
|
|
28378
28586
|
try {
|
|
28379
28587
|
const { default: openBrowser } = await Promise.resolve().then(() => (init_open(), open_exports));
|
|
28380
|
-
await openBrowser(`http://localhost:${
|
|
28588
|
+
await openBrowser(`http://localhost:${actualPort}`);
|
|
28381
28589
|
} catch {
|
|
28382
28590
|
}
|
|
28383
28591
|
}
|
|
@@ -28387,6 +28595,10 @@ async function startServer(options = {}) {
|
|
|
28387
28595
|
unlinkSync2(pidFilePath);
|
|
28388
28596
|
} catch {
|
|
28389
28597
|
}
|
|
28598
|
+
try {
|
|
28599
|
+
unlinkSync2(portFilePath);
|
|
28600
|
+
} catch {
|
|
28601
|
+
}
|
|
28390
28602
|
try {
|
|
28391
28603
|
const activeResult = db.exec(
|
|
28392
28604
|
"SELECT id, pid, is_detached FROM command_executions WHERE pid IS NOT NULL AND finished_at IS NULL"
|