@open-code-review/cli 2.0.0 → 2.2.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 +2 -0
- package/dist/dashboard/client/assets/{_basePickBy-B3ALyupE.js → _basePickBy-BBPb8BJA.js} +1 -1
- package/dist/dashboard/client/assets/{_baseUniq-b2RALAWc.js → _baseUniq-CFHdos6T.js} +1 -1
- package/dist/dashboard/client/assets/{arc-DcSVvhUd.js → arc-BKGGWA2F.js} +1 -1
- package/dist/dashboard/client/assets/{architectureDiagram-VXUJARFQ-BNUlmSCS.js → architectureDiagram-VXUJARFQ-B_ovNjX1.js} +1 -1
- package/dist/dashboard/client/assets/{blockDiagram-VD42YOAC-BmhiQVwa.js → blockDiagram-VD42YOAC-C2M-avVp.js} +1 -1
- package/dist/dashboard/client/assets/{c4Diagram-YG6GDRKO-jyJ3WOv5.js → c4Diagram-YG6GDRKO-BtOBpAzH.js} +1 -1
- package/dist/dashboard/client/assets/channel-rgw7C1e7.js +1 -0
- package/dist/dashboard/client/assets/{chunk-4BX2VUAB-x1dQU_s3.js → chunk-4BX2VUAB-Cz2EbHPl.js} +1 -1
- package/dist/dashboard/client/assets/{chunk-55IACEB6-CwbsE2XQ.js → chunk-55IACEB6-C8xpXw9G.js} +1 -1
- package/dist/dashboard/client/assets/{chunk-B4BG7PRW-BaE7c-ti.js → chunk-B4BG7PRW-BSRfOovX.js} +1 -1
- package/dist/dashboard/client/assets/{chunk-DI55MBZ5-Bw5PUaMK.js → chunk-DI55MBZ5-CEUbYQWn.js} +1 -1
- package/dist/dashboard/client/assets/{chunk-FMBD7UC4-B7cF6P3s.js → chunk-FMBD7UC4-5xWP6GRj.js} +1 -1
- package/dist/dashboard/client/assets/{chunk-QN33PNHL-OY4evNHd.js → chunk-QN33PNHL-DfNCVcy8.js} +1 -1
- package/dist/dashboard/client/assets/{chunk-QZHKN3VN-BpjQwIWz.js → chunk-QZHKN3VN--OdToKKu.js} +1 -1
- package/dist/dashboard/client/assets/{chunk-TZMSLE5B-D8b_Oq9B.js → chunk-TZMSLE5B-B_0K0Qso.js} +1 -1
- package/dist/dashboard/client/assets/classDiagram-2ON5EDUG-DTGi7d9X.js +1 -0
- package/dist/dashboard/client/assets/classDiagram-v2-WZHVMYZB-DTGi7d9X.js +1 -0
- package/dist/dashboard/client/assets/clone-Cz7hswqi.js +1 -0
- package/dist/dashboard/client/assets/{cose-bilkent-S5V4N54A-C-sfP8PN.js → cose-bilkent-S5V4N54A-Cc_Dmnxz.js} +1 -1
- package/dist/dashboard/client/assets/{dagre-6UL2VRFP-Cqfo0NRg.js → dagre-6UL2VRFP-DaAfvUXU.js} +1 -1
- package/dist/dashboard/client/assets/{diagram-PSM6KHXK-BR3ppxqI.js → diagram-PSM6KHXK-7idwN0rC.js} +1 -1
- package/dist/dashboard/client/assets/{diagram-QEK2KX5R-Dvcx6x3R.js → diagram-QEK2KX5R-D9j9H13n.js} +1 -1
- package/dist/dashboard/client/assets/{diagram-S2PKOQOG-DoyBLnVN.js → diagram-S2PKOQOG-SMF5SB0K.js} +1 -1
- package/dist/dashboard/client/assets/{erDiagram-Q2GNP2WA-hy77l1cL.js → erDiagram-Q2GNP2WA-EVJ4Qa2F.js} +1 -1
- package/dist/dashboard/client/assets/{flowDiagram-NV44I4VS-Bz0B1rKM.js → flowDiagram-NV44I4VS-tZ7SFE77.js} +1 -1
- package/dist/dashboard/client/assets/{ganttDiagram-JELNMOA3-CLgrZPoC.js → ganttDiagram-JELNMOA3-DFSqguY7.js} +1 -1
- package/dist/dashboard/client/assets/{gitGraphDiagram-V2S2FVAM-DwJ-1f-v.js → gitGraphDiagram-V2S2FVAM-CqHdP3HE.js} +1 -1
- package/dist/dashboard/client/assets/{graph-DDBMM_t2.js → graph-C0XnkNkk.js} +1 -1
- package/dist/dashboard/client/assets/{index-Cr9yEo_B.js → index-C3NEq704.js} +133 -138
- package/dist/dashboard/client/assets/index-CzxeSSaQ.css +1 -0
- package/dist/dashboard/client/assets/{infoDiagram-HS3SLOUP-Bhn1FmAk.js → infoDiagram-HS3SLOUP-DlXZo9U2.js} +1 -1
- package/dist/dashboard/client/assets/{journeyDiagram-XKPGCS4Q-CzGbjX1y.js → journeyDiagram-XKPGCS4Q-CgC8_7eN.js} +1 -1
- package/dist/dashboard/client/assets/{kanban-definition-3W4ZIXB7-Da77-WYk.js → kanban-definition-3W4ZIXB7-BMAw_jNp.js} +1 -1
- package/dist/dashboard/client/assets/{layout-CVwSB-GS.js → layout-XjM3Q-ka.js} +1 -1
- package/dist/dashboard/client/assets/{linear-CTRAc5Jn.js → linear-CMUrrr1X.js} +1 -1
- package/dist/dashboard/client/assets/{mermaid-renderer-Bjo170ax.js → mermaid-renderer-D2jYNs7K.js} +4 -4
- package/dist/dashboard/client/assets/{mindmap-definition-VGOIOE7T-B55C2odl.js → mindmap-definition-VGOIOE7T-CL4hv-vg.js} +1 -1
- package/dist/dashboard/client/assets/{pieDiagram-ADFJNKIX-5lrQLrSz.js → pieDiagram-ADFJNKIX-DTqv-1h1.js} +1 -1
- package/dist/dashboard/client/assets/{quadrantDiagram-AYHSOK5B-Bg55gC30.js → quadrantDiagram-AYHSOK5B-BpFlSW9N.js} +1 -1
- package/dist/dashboard/client/assets/{requirementDiagram-UZGBJVZJ-CyR4YFJY.js → requirementDiagram-UZGBJVZJ-BqYqqXL4.js} +1 -1
- package/dist/dashboard/client/assets/{sankeyDiagram-TZEHDZUN-BVWKr9_-.js → sankeyDiagram-TZEHDZUN-kEI9kntR.js} +1 -1
- package/dist/dashboard/client/assets/{sequenceDiagram-WL72ISMW-D0AJg_tE.js → sequenceDiagram-WL72ISMW-Cnu_1j-N.js} +1 -1
- package/dist/dashboard/client/assets/{stateDiagram-FKZM4ZOC-BuHpTgim.js → stateDiagram-FKZM4ZOC-BoC-rqoG.js} +1 -1
- package/dist/dashboard/client/assets/stateDiagram-v2-4FDKWEC3-COR3QD3v.js +1 -0
- package/dist/dashboard/client/assets/{timeline-definition-IT6M3QCI-LDhpAmDd.js → timeline-definition-IT6M3QCI-CXMWuzDL.js} +1 -1
- package/dist/dashboard/client/assets/{treemap-GDKQZRPO-Dd4gjvUl.js → treemap-GDKQZRPO-o9ZFgpbJ.js} +1 -1
- package/dist/dashboard/client/assets/{xychartDiagram-PRI3JC2R-B9RDod39.js → xychartDiagram-PRI3JC2R-CfIuUpeA.js} +1 -1
- package/dist/dashboard/client/index.html +2 -2
- package/dist/dashboard/server.js +1175 -450
- package/dist/index.js +1489 -312
- package/dist/lib/db/index.js +666 -48
- package/dist/lib/runtime-config.js +29 -13
- package/dist/lib/state/index.js +2196 -0
- package/package.json +9 -5
- package/dist/dashboard/client/assets/channel-D3J8-GF_.js +0 -1
- package/dist/dashboard/client/assets/classDiagram-2ON5EDUG-tkFUL-1Y.js +0 -1
- package/dist/dashboard/client/assets/classDiagram-v2-WZHVMYZB-tkFUL-1Y.js +0 -1
- package/dist/dashboard/client/assets/clone-CkY5ajLr.js +0 -1
- package/dist/dashboard/client/assets/index-Z1pPudAt.css +0 -1
- package/dist/dashboard/client/assets/stateDiagram-v2-4FDKWEC3-DwAPhteN.js +0 -1
package/dist/index.js
CHANGED
|
@@ -39,6 +39,30 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
39
39
|
mod
|
|
40
40
|
));
|
|
41
41
|
|
|
42
|
+
// src/lib/runtime-checks.ts
|
|
43
|
+
function isSupportedNode(version) {
|
|
44
|
+
const [major = 0, minor = 0] = version.split(".").map((n) => Number.parseInt(n, 10) || 0);
|
|
45
|
+
return major > NODE_FLOOR.major || major === NODE_FLOOR.major && minor >= NODE_FLOOR.minor;
|
|
46
|
+
}
|
|
47
|
+
function nodeVersionGuardMessage(version) {
|
|
48
|
+
return `
|
|
49
|
+
Open Code Review requires Node.js >= ${NODE_FLOOR.major}.${NODE_FLOOR.minor} (it uses Node's built-in SQLite, \`node:sqlite\`).
|
|
50
|
+
You have Node ${version}. Upgrade Node (e.g. \`nvm install 22 && nvm use 22\`) and re-run.
|
|
51
|
+
|
|
52
|
+
`;
|
|
53
|
+
}
|
|
54
|
+
function isSuppressibleSqliteWarning(warning) {
|
|
55
|
+
const message = typeof warning === "string" ? warning : warning?.message;
|
|
56
|
+
return typeof message === "string" && message.includes("SQLite is an experimental feature");
|
|
57
|
+
}
|
|
58
|
+
var NODE_FLOOR;
|
|
59
|
+
var init_runtime_checks = __esm({
|
|
60
|
+
"src/lib/runtime-checks.ts"() {
|
|
61
|
+
"use strict";
|
|
62
|
+
NODE_FLOOR = { major: 22, minor: 5 };
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
|
|
42
66
|
// ../../node_modules/.pnpm/commander@13.1.0/node_modules/commander/lib/error.js
|
|
43
67
|
var require_error = __commonJS({
|
|
44
68
|
"../../node_modules/.pnpm/commander@13.1.0/node_modules/commander/lib/error.js"(exports) {
|
|
@@ -15993,6 +16017,80 @@ var require_emoji_regex2 = __commonJS({
|
|
|
15993
16017
|
}
|
|
15994
16018
|
});
|
|
15995
16019
|
|
|
16020
|
+
// ../shared/platform/src/index.ts
|
|
16021
|
+
import { pathToFileURL } from "node:url";
|
|
16022
|
+
import {
|
|
16023
|
+
execFile,
|
|
16024
|
+
execFileSync,
|
|
16025
|
+
spawn as spawn2
|
|
16026
|
+
} from "node:child_process";
|
|
16027
|
+
import { promisify } from "node:util";
|
|
16028
|
+
async function importModule(absolutePath) {
|
|
16029
|
+
return import(pathToFileURL(absolutePath).href);
|
|
16030
|
+
}
|
|
16031
|
+
function execBinary(binary, args, opts) {
|
|
16032
|
+
return execFileSync(binary, args, {
|
|
16033
|
+
...opts,
|
|
16034
|
+
shell: isWindows
|
|
16035
|
+
});
|
|
16036
|
+
}
|
|
16037
|
+
function isProcessAlive(pid) {
|
|
16038
|
+
try {
|
|
16039
|
+
process.kill(pid, 0);
|
|
16040
|
+
return true;
|
|
16041
|
+
} catch (err) {
|
|
16042
|
+
return !(err instanceof Error && "code" in err && err.code === "ESRCH");
|
|
16043
|
+
}
|
|
16044
|
+
}
|
|
16045
|
+
function defaultIconFor(id, tier) {
|
|
16046
|
+
return BUILTIN_ICON_MAP[id] ?? (tier === "persona" ? "brain" : "user");
|
|
16047
|
+
}
|
|
16048
|
+
function hostCapabilitiesFor(vendor) {
|
|
16049
|
+
return vendor && HOST_CAPABILITIES[vendor] || DEFAULT_HOST_CAPABILITIES;
|
|
16050
|
+
}
|
|
16051
|
+
var execFilePromise, isWindows, BUILTIN_ICON_MAP, DEFAULT_HOST_CAPABILITIES, HOST_CAPABILITIES;
|
|
16052
|
+
var init_src = __esm({
|
|
16053
|
+
"../shared/platform/src/index.ts"() {
|
|
16054
|
+
"use strict";
|
|
16055
|
+
execFilePromise = promisify(execFile);
|
|
16056
|
+
isWindows = process.platform === "win32";
|
|
16057
|
+
BUILTIN_ICON_MAP = {
|
|
16058
|
+
architect: "blocks",
|
|
16059
|
+
fullstack: "layers",
|
|
16060
|
+
reliability: "activity",
|
|
16061
|
+
"staff-engineer": "compass",
|
|
16062
|
+
principal: "crown",
|
|
16063
|
+
frontend: "layout",
|
|
16064
|
+
backend: "server",
|
|
16065
|
+
infrastructure: "cloud",
|
|
16066
|
+
performance: "gauge",
|
|
16067
|
+
accessibility: "accessibility",
|
|
16068
|
+
data: "database",
|
|
16069
|
+
devops: "rocket",
|
|
16070
|
+
dx: "terminal",
|
|
16071
|
+
mobile: "smartphone",
|
|
16072
|
+
security: "shield-alert",
|
|
16073
|
+
quality: "sparkles",
|
|
16074
|
+
testing: "test-tubes",
|
|
16075
|
+
ai: "bot",
|
|
16076
|
+
"docs-writer": "file-text"
|
|
16077
|
+
};
|
|
16078
|
+
DEFAULT_HOST_CAPABILITIES = {
|
|
16079
|
+
subagentSpawn: false,
|
|
16080
|
+
perTaskModel: false
|
|
16081
|
+
};
|
|
16082
|
+
HOST_CAPABILITIES = {
|
|
16083
|
+
// Claude Code: Task tool + per-subagent model frontmatter.
|
|
16084
|
+
claude: { subagentSpawn: true, perTaskModel: true },
|
|
16085
|
+
// OpenCode: `--agent` sub-agent primitive, but no per-task model override.
|
|
16086
|
+
opencode: { subagentSpawn: true, perTaskModel: false },
|
|
16087
|
+
// Gemini CLI / Codex: no in-agent Task primitive → sequential Phase 4.
|
|
16088
|
+
gemini: { subagentSpawn: false, perTaskModel: false },
|
|
16089
|
+
codex: { subagentSpawn: false, perTaskModel: false }
|
|
16090
|
+
};
|
|
16091
|
+
}
|
|
16092
|
+
});
|
|
16093
|
+
|
|
15996
16094
|
// ../../node_modules/.pnpm/yaml@2.8.3/node_modules/yaml/dist/nodes/identity.js
|
|
15997
16095
|
var require_identity = __commonJS({
|
|
15998
16096
|
"../../node_modules/.pnpm/yaml@2.8.3/node_modules/yaml/dist/nodes/identity.js"(exports) {
|
|
@@ -23320,21 +23418,41 @@ var init_result_mapper = __esm({
|
|
|
23320
23418
|
});
|
|
23321
23419
|
|
|
23322
23420
|
// src/lib/db/engine.ts
|
|
23323
|
-
import
|
|
23324
|
-
function
|
|
23325
|
-
if (
|
|
23326
|
-
|
|
23421
|
+
import { createRequire as createRequire2 } from "node:module";
|
|
23422
|
+
function applyEnginePreconditions() {
|
|
23423
|
+
if (_preconditionsApplied) return;
|
|
23424
|
+
_preconditionsApplied = true;
|
|
23425
|
+
const originalEmitWarning = process.emitWarning.bind(process);
|
|
23426
|
+
process.emitWarning = (warning, ...args) => {
|
|
23427
|
+
if (isSuppressibleSqliteWarning(warning)) return;
|
|
23428
|
+
originalEmitWarning(warning, ...args);
|
|
23429
|
+
};
|
|
23430
|
+
}
|
|
23431
|
+
function newDatabase(path2) {
|
|
23432
|
+
if (!_DatabaseSyncCtor) {
|
|
23433
|
+
applyEnginePreconditions();
|
|
23434
|
+
try {
|
|
23435
|
+
_DatabaseSyncCtor = nodeRequire("node:sqlite").DatabaseSync;
|
|
23436
|
+
} catch (e) {
|
|
23437
|
+
if (!isSupportedNode(process.versions.node)) {
|
|
23438
|
+
throw new Error(nodeVersionGuardMessage(process.versions.node).trim());
|
|
23439
|
+
}
|
|
23440
|
+
throw e;
|
|
23441
|
+
}
|
|
23327
23442
|
}
|
|
23328
|
-
|
|
23329
|
-
|
|
23443
|
+
return new _DatabaseSyncCtor(path2);
|
|
23444
|
+
}
|
|
23445
|
+
function isBusyError(e) {
|
|
23446
|
+
const errcode = e?.errcode;
|
|
23447
|
+
return errcode === SQLITE_BUSY || errcode === SQLITE_BUSY_SNAPSHOT;
|
|
23330
23448
|
}
|
|
23331
23449
|
function sleepSync(ms) {
|
|
23332
|
-
Atomics.wait(
|
|
23450
|
+
Atomics.wait(SLEEP_BUF, 0, 0, ms);
|
|
23333
23451
|
}
|
|
23334
23452
|
function probeEngine() {
|
|
23335
23453
|
try {
|
|
23336
|
-
const db =
|
|
23337
|
-
db.
|
|
23454
|
+
const db = newDatabase(":memory:");
|
|
23455
|
+
db.exec("PRAGMA journal_mode = WAL");
|
|
23338
23456
|
db.exec("CREATE TABLE _probe(x); INSERT INTO _probe VALUES (1);");
|
|
23339
23457
|
const row = db.prepare("SELECT sqlite_version() AS v").get();
|
|
23340
23458
|
db.close();
|
|
@@ -23344,33 +23462,47 @@ function probeEngine() {
|
|
|
23344
23462
|
}
|
|
23345
23463
|
}
|
|
23346
23464
|
function openEngine(dbPath) {
|
|
23347
|
-
const native =
|
|
23348
|
-
native.
|
|
23349
|
-
native.
|
|
23350
|
-
native.
|
|
23351
|
-
native.
|
|
23352
|
-
return new
|
|
23353
|
-
}
|
|
23354
|
-
var BUSY_RETRY_ATTEMPTS, BUSY_RETRY_BACKOFF_MS,
|
|
23465
|
+
const native = newDatabase(dbPath);
|
|
23466
|
+
native.exec("PRAGMA journal_mode = WAL");
|
|
23467
|
+
native.exec("PRAGMA foreign_keys = ON");
|
|
23468
|
+
native.exec("PRAGMA busy_timeout = 5000");
|
|
23469
|
+
native.exec("PRAGMA synchronous = NORMAL");
|
|
23470
|
+
return new NodeSqliteAdapter(native);
|
|
23471
|
+
}
|
|
23472
|
+
var SQLITE_BUSY, SQLITE_BUSY_SNAPSHOT, BUSY_RETRY_ATTEMPTS, BUSY_RETRY_BACKOFF_MS, savepointName, nodeRequire, _preconditionsApplied, _DatabaseSyncCtor, SLEEP_BUF, NodeSqliteAdapter;
|
|
23355
23473
|
var init_engine = __esm({
|
|
23356
23474
|
"src/lib/db/engine.ts"() {
|
|
23357
23475
|
"use strict";
|
|
23476
|
+
init_runtime_checks();
|
|
23477
|
+
SQLITE_BUSY = 5;
|
|
23478
|
+
SQLITE_BUSY_SNAPSHOT = 261;
|
|
23358
23479
|
BUSY_RETRY_ATTEMPTS = 5;
|
|
23359
23480
|
BUSY_RETRY_BACKOFF_MS = 50;
|
|
23360
|
-
|
|
23481
|
+
savepointName = (depth) => `ocr_sp_${depth}`;
|
|
23482
|
+
nodeRequire = createRequire2(import.meta.url);
|
|
23483
|
+
_preconditionsApplied = false;
|
|
23484
|
+
SLEEP_BUF = new Int32Array(new SharedArrayBuffer(4));
|
|
23485
|
+
NodeSqliteAdapter = class {
|
|
23361
23486
|
raw;
|
|
23487
|
+
/**
|
|
23488
|
+
* Transaction nesting depth. `node:sqlite` has no transaction helper, so we
|
|
23489
|
+
* drive `BEGIN IMMEDIATE` ourselves and use SAVEPOINTs for nested calls
|
|
23490
|
+
* (better-sqlite3 did this automatically). 0 = no transaction open.
|
|
23491
|
+
*/
|
|
23492
|
+
txnDepth = 0;
|
|
23362
23493
|
constructor(db) {
|
|
23363
23494
|
this.raw = db;
|
|
23364
23495
|
}
|
|
23365
23496
|
exec(sql, params) {
|
|
23366
23497
|
const stmt = this.raw.prepare(sql);
|
|
23367
|
-
|
|
23498
|
+
const cols = stmt.columns();
|
|
23499
|
+
if (cols.length === 0) {
|
|
23368
23500
|
stmt.run(...params ?? []);
|
|
23369
23501
|
return [];
|
|
23370
23502
|
}
|
|
23371
|
-
|
|
23372
|
-
const values = stmt.
|
|
23373
|
-
return values.length > 0 ? [{ columns, values }] : [];
|
|
23503
|
+
stmt.setReturnArrays(true);
|
|
23504
|
+
const values = stmt.all(...params ?? []);
|
|
23505
|
+
return values.length > 0 ? [{ columns: cols.map((c) => c.name), values }] : [];
|
|
23374
23506
|
}
|
|
23375
23507
|
run(sql, params) {
|
|
23376
23508
|
if (params !== void 0) {
|
|
@@ -23383,25 +23515,84 @@ var init_engine = __esm({
|
|
|
23383
23515
|
return this.raw.prepare(sql);
|
|
23384
23516
|
}
|
|
23385
23517
|
transaction(fn) {
|
|
23386
|
-
|
|
23387
|
-
|
|
23518
|
+
return this.txnDepth > 0 ? this.runNested(fn) : this.runOuter(fn);
|
|
23519
|
+
}
|
|
23520
|
+
/**
|
|
23521
|
+
* Nested call: a SAVEPOINT within the outer transaction's write lock. No
|
|
23522
|
+
* busy-retry — the outer transaction already holds the lock. The savepoint
|
|
23523
|
+
* lets the inner block roll back independently while the outer continues.
|
|
23524
|
+
*/
|
|
23525
|
+
runNested(fn) {
|
|
23526
|
+
const name = savepointName(this.txnDepth);
|
|
23527
|
+
this.raw.exec(`SAVEPOINT ${name}`);
|
|
23528
|
+
this.txnDepth++;
|
|
23529
|
+
try {
|
|
23530
|
+
const result = fn();
|
|
23531
|
+
this.raw.exec(`RELEASE ${name}`);
|
|
23532
|
+
return result;
|
|
23533
|
+
} catch (e) {
|
|
23388
23534
|
try {
|
|
23389
|
-
|
|
23535
|
+
this.raw.exec(`ROLLBACK TO ${name}`);
|
|
23536
|
+
this.raw.exec(`RELEASE ${name}`);
|
|
23537
|
+
} catch {
|
|
23538
|
+
}
|
|
23539
|
+
throw e;
|
|
23540
|
+
} finally {
|
|
23541
|
+
this.txnDepth--;
|
|
23542
|
+
}
|
|
23543
|
+
}
|
|
23544
|
+
/**
|
|
23545
|
+
* Outer transaction: `BEGIN IMMEDIATE` acquires the write lock up front so
|
|
23546
|
+
* cross-process writers serialize cleanly under WAL instead of failing late
|
|
23547
|
+
* on upgrade. `busy_timeout` covers most contention; a bounded synchronous
|
|
23548
|
+
* retry absorbs the residual SQLITE_BUSY (another connection holds the lock
|
|
23549
|
+
* past the timeout, or BUSY_SNAPSHOT). Non-busy errors and the final attempt
|
|
23550
|
+
* re-throw so genuine failures propagate.
|
|
23551
|
+
*/
|
|
23552
|
+
runOuter(fn) {
|
|
23553
|
+
for (let attempt = 0; attempt < BUSY_RETRY_ATTEMPTS; attempt++) {
|
|
23554
|
+
try {
|
|
23555
|
+
return this.runOnce(fn);
|
|
23390
23556
|
} catch (e) {
|
|
23391
|
-
if (!isBusyError(e) || attempt
|
|
23557
|
+
if (!isBusyError(e) || attempt === BUSY_RETRY_ATTEMPTS - 1) throw e;
|
|
23392
23558
|
sleepSync(BUSY_RETRY_BACKOFF_MS);
|
|
23393
23559
|
}
|
|
23394
23560
|
}
|
|
23561
|
+
throw new Error("transaction retry budget exhausted");
|
|
23562
|
+
}
|
|
23563
|
+
/** One `BEGIN IMMEDIATE` / `COMMIT` / `ROLLBACK` lifecycle. */
|
|
23564
|
+
runOnce(fn) {
|
|
23565
|
+
this.raw.exec("BEGIN IMMEDIATE");
|
|
23566
|
+
this.txnDepth = 1;
|
|
23567
|
+
try {
|
|
23568
|
+
const result = fn();
|
|
23569
|
+
this.raw.exec("COMMIT");
|
|
23570
|
+
return result;
|
|
23571
|
+
} catch (e) {
|
|
23572
|
+
try {
|
|
23573
|
+
this.raw.exec("ROLLBACK");
|
|
23574
|
+
} catch {
|
|
23575
|
+
}
|
|
23576
|
+
throw e;
|
|
23577
|
+
} finally {
|
|
23578
|
+
this.txnDepth = 0;
|
|
23579
|
+
}
|
|
23395
23580
|
}
|
|
23396
23581
|
pragma(source) {
|
|
23397
|
-
|
|
23582
|
+
this.raw.exec(`PRAGMA ${source}`);
|
|
23583
|
+
return void 0;
|
|
23398
23584
|
}
|
|
23399
23585
|
close() {
|
|
23400
23586
|
try {
|
|
23401
|
-
this.raw.
|
|
23587
|
+
this.raw.exec("PRAGMA wal_checkpoint(TRUNCATE)");
|
|
23402
23588
|
} catch {
|
|
23403
23589
|
}
|
|
23404
|
-
|
|
23590
|
+
try {
|
|
23591
|
+
this.raw.close();
|
|
23592
|
+
} catch (e) {
|
|
23593
|
+
const message = e?.message ?? "";
|
|
23594
|
+
if (!/database is not open/i.test(message)) throw e;
|
|
23595
|
+
}
|
|
23405
23596
|
}
|
|
23406
23597
|
};
|
|
23407
23598
|
}
|
|
@@ -23927,6 +24118,35 @@ var init_migrations = __esm({
|
|
|
23927
24118
|
db.run("DROP INDEX IF EXISTS idx_command_executions_parent;");
|
|
23928
24119
|
db.run("ALTER TABLE command_executions DROP COLUMN parent_id;");
|
|
23929
24120
|
}
|
|
24121
|
+
},
|
|
24122
|
+
{
|
|
24123
|
+
version: 14,
|
|
24124
|
+
description: "Self-heal markdown_artifacts duplication: collapse NULL-round duplicate rows and add a NULL-safe unique index so the dedup bug cannot recur",
|
|
24125
|
+
// The table's `UNIQUE(session_id, artifact_type, round_number, file_path)`
|
|
24126
|
+
// never deduped session-level artifacts because SQLite treats NULL ≠ NULL,
|
|
24127
|
+
// and the writer used `INSERT OR REPLACE` — so every re-parse of a
|
|
24128
|
+
// NULL-round artifact (context.md, map.md, …) appended a duplicate (one
|
|
24129
|
+
// context.md reached 775 identical rows, ~177 MB). The writer is now an
|
|
24130
|
+
// explicit UPDATE-or-INSERT; this migration heals existing DBs and adds a
|
|
24131
|
+
// NULL-collapsing unique index as a DB-level backstop.
|
|
24132
|
+
//
|
|
24133
|
+
// Orphan-row sweep (FK-dangling children from the pre-FK-enforcement era)
|
|
24134
|
+
// is intentionally NOT done here — it needs `PRAGMA foreign_keys = OFF`,
|
|
24135
|
+
// which is a no-op inside the migration transaction. `ocr db doctor --fix`
|
|
24136
|
+
// performs it outside a transaction.
|
|
24137
|
+
run: (db) => {
|
|
24138
|
+
db.run(`
|
|
24139
|
+
DELETE FROM markdown_artifacts
|
|
24140
|
+
WHERE rowid NOT IN (
|
|
24141
|
+
SELECT MAX(rowid) FROM markdown_artifacts
|
|
24142
|
+
GROUP BY session_id, artifact_type, IFNULL(round_number, -1), file_path
|
|
24143
|
+
)
|
|
24144
|
+
`);
|
|
24145
|
+
db.run(`
|
|
24146
|
+
CREATE UNIQUE INDEX IF NOT EXISTS idx_markdown_artifacts_logical
|
|
24147
|
+
ON markdown_artifacts(session_id, artifact_type, IFNULL(round_number, -1), file_path)
|
|
24148
|
+
`);
|
|
24149
|
+
}
|
|
23930
24150
|
}
|
|
23931
24151
|
];
|
|
23932
24152
|
}
|
|
@@ -24055,7 +24275,7 @@ var init_queries = __esm({
|
|
|
24055
24275
|
|
|
24056
24276
|
// src/lib/db/reconcile.ts
|
|
24057
24277
|
import { existsSync as existsSync10 } from "node:fs";
|
|
24058
|
-
import { isAbsolute as isAbsolute2, join as join12, dirname as
|
|
24278
|
+
import { isAbsolute as isAbsolute2, join as join12, dirname as dirname5 } from "node:path";
|
|
24059
24279
|
function hasTerminalArtifactEvent(db, sessionId, workflowType, currentRound, currentMapRun) {
|
|
24060
24280
|
const eventType = workflowType === "map" ? "map_completed" : "round_completed";
|
|
24061
24281
|
const round = workflowType === "map" ? currentMapRun : currentRound;
|
|
@@ -24096,7 +24316,7 @@ function hasInFlightDependents(db, sessionId) {
|
|
|
24096
24316
|
function resolveSessionDir(ocrDir, sessionDir) {
|
|
24097
24317
|
if (!sessionDir) return null;
|
|
24098
24318
|
if (isAbsolute2(sessionDir)) return sessionDir;
|
|
24099
|
-
return join12(
|
|
24319
|
+
return join12(dirname5(ocrDir), sessionDir);
|
|
24100
24320
|
}
|
|
24101
24321
|
function reconcileLegacyState(db, ocrDir, opts = {}) {
|
|
24102
24322
|
const dryRun = opts.dryRun ?? false;
|
|
@@ -24214,7 +24434,7 @@ var init_liveness = __esm({
|
|
|
24214
24434
|
});
|
|
24215
24435
|
|
|
24216
24436
|
// src/lib/state/exit-codes.ts
|
|
24217
|
-
var STATE_EXIT, StateError, CANCELLED_EXIT_CODE, ORPHAN_EXIT_CODE, CASCADE_CLOSE_EXIT_CODE;
|
|
24437
|
+
var STATE_EXIT, StateError, CANCELLED_EXIT_CODE, ORPHAN_EXIT_CODE, CASCADE_CLOSE_EXIT_CODE, WATCHDOG_DEADLINE_EXIT_CODE;
|
|
24218
24438
|
var init_exit_codes = __esm({
|
|
24219
24439
|
"src/lib/state/exit-codes.ts"() {
|
|
24220
24440
|
"use strict";
|
|
@@ -24239,6 +24459,7 @@ var init_exit_codes = __esm({
|
|
|
24239
24459
|
CANCELLED_EXIT_CODE = -2;
|
|
24240
24460
|
ORPHAN_EXIT_CODE = -3;
|
|
24241
24461
|
CASCADE_CLOSE_EXIT_CODE = -4;
|
|
24462
|
+
WATCHDOG_DEADLINE_EXIT_CODE = -5;
|
|
24242
24463
|
}
|
|
24243
24464
|
});
|
|
24244
24465
|
|
|
@@ -24621,24 +24842,431 @@ var init_agent_sessions = __esm({
|
|
|
24621
24842
|
}
|
|
24622
24843
|
});
|
|
24623
24844
|
|
|
24845
|
+
// src/lib/db/maintenance.ts
|
|
24846
|
+
import {
|
|
24847
|
+
existsSync as existsSync11,
|
|
24848
|
+
readdirSync as readdirSync5,
|
|
24849
|
+
statSync,
|
|
24850
|
+
unlinkSync as unlinkSync3,
|
|
24851
|
+
copyFileSync
|
|
24852
|
+
} from "node:fs";
|
|
24853
|
+
import { dirname as dirname6, join as join13, basename as basename7 } from "node:path";
|
|
24854
|
+
function withForeignKeysDisabled(db, fn) {
|
|
24855
|
+
db.pragma("foreign_keys = OFF");
|
|
24856
|
+
try {
|
|
24857
|
+
return fn();
|
|
24858
|
+
} finally {
|
|
24859
|
+
db.pragma("foreign_keys = ON");
|
|
24860
|
+
}
|
|
24861
|
+
}
|
|
24862
|
+
function scalarInt(db, sql) {
|
|
24863
|
+
const r = db.exec(sql);
|
|
24864
|
+
const v = r[0]?.values[0]?.[0];
|
|
24865
|
+
return typeof v === "number" ? v : Number(v ?? 0);
|
|
24866
|
+
}
|
|
24867
|
+
function foreignKeyViolationGroups(db) {
|
|
24868
|
+
const r = db.exec("PRAGMA foreign_key_check");
|
|
24869
|
+
const rows = r[0]?.values ?? [];
|
|
24870
|
+
const counts = /* @__PURE__ */ new Map();
|
|
24871
|
+
for (const row of rows) {
|
|
24872
|
+
const table = String(row[0]);
|
|
24873
|
+
counts.set(table, (counts.get(table) ?? 0) + 1);
|
|
24874
|
+
}
|
|
24875
|
+
return [...counts.entries()].map(([table, count]) => ({ table, count })).sort((a, b) => b.count - a.count);
|
|
24876
|
+
}
|
|
24877
|
+
function scanOrphanTempFiles(dataDir) {
|
|
24878
|
+
let entries;
|
|
24879
|
+
try {
|
|
24880
|
+
entries = readdirSync5(dataDir);
|
|
24881
|
+
} catch {
|
|
24882
|
+
return [];
|
|
24883
|
+
}
|
|
24884
|
+
const out = [];
|
|
24885
|
+
for (const name of entries) {
|
|
24886
|
+
const m = name.match(/^ocr\.db\.(\d+)\.tmp$/);
|
|
24887
|
+
if (!m) continue;
|
|
24888
|
+
const pid = Number(m[1]);
|
|
24889
|
+
let ageMs = 0;
|
|
24890
|
+
try {
|
|
24891
|
+
ageMs = Date.now() - statSync(join13(dataDir, name)).mtimeMs;
|
|
24892
|
+
} catch {
|
|
24893
|
+
continue;
|
|
24894
|
+
}
|
|
24895
|
+
const alive = isProcessAlive(pid);
|
|
24896
|
+
out.push({
|
|
24897
|
+
name,
|
|
24898
|
+
pid,
|
|
24899
|
+
ageMs,
|
|
24900
|
+
// Reapable only when the writer PID is dead AND the file is old enough
|
|
24901
|
+
// that no live mid-write could plausibly own it.
|
|
24902
|
+
reapable: !alive && ageMs > ONE_HOUR_MS
|
|
24903
|
+
});
|
|
24904
|
+
}
|
|
24905
|
+
return out;
|
|
24906
|
+
}
|
|
24907
|
+
function scanBackupFiles(dataDir, dbBase) {
|
|
24908
|
+
let entries;
|
|
24909
|
+
try {
|
|
24910
|
+
entries = readdirSync5(dataDir);
|
|
24911
|
+
} catch {
|
|
24912
|
+
return [];
|
|
24913
|
+
}
|
|
24914
|
+
const out = [];
|
|
24915
|
+
for (const name of entries) {
|
|
24916
|
+
if (!name.startsWith(`${dbBase}.bak`)) continue;
|
|
24917
|
+
try {
|
|
24918
|
+
out.push({ name, sizeBytes: statSync(join13(dataDir, name)).size });
|
|
24919
|
+
} catch {
|
|
24920
|
+
}
|
|
24921
|
+
}
|
|
24922
|
+
return out.sort((a, b) => b.sizeBytes - a.sizeBytes);
|
|
24923
|
+
}
|
|
24924
|
+
function collectDbHealth(db, dbPath) {
|
|
24925
|
+
const dataDir = dirname6(dbPath);
|
|
24926
|
+
const dbBase = basename7(dbPath);
|
|
24927
|
+
const pageSize = scalarInt(db, "PRAGMA page_size");
|
|
24928
|
+
const pageCount = scalarInt(db, "PRAGMA page_count");
|
|
24929
|
+
const freelistCount = scalarInt(db, "PRAGMA freelist_count");
|
|
24930
|
+
const integ = db.exec("PRAGMA integrity_check");
|
|
24931
|
+
const integRows = (integ[0]?.values ?? []).map((v) => String(v[0]));
|
|
24932
|
+
const integrityOk = integRows.length === 1 && integRows[0] === "ok";
|
|
24933
|
+
const allGroups = foreignKeyViolationGroups(db);
|
|
24934
|
+
const fkViolations = allGroups.filter((g) => !PROTECTED_TABLES.has(g.table));
|
|
24935
|
+
const protectedFkViolations = allGroups.filter(
|
|
24936
|
+
(g) => PROTECTED_TABLES.has(g.table)
|
|
24937
|
+
);
|
|
24938
|
+
const fileSizeBytes = existsSync11(dbPath) ? statSync(dbPath).size : 0;
|
|
24939
|
+
return {
|
|
24940
|
+
dbPath,
|
|
24941
|
+
fileSizeBytes,
|
|
24942
|
+
pageSize,
|
|
24943
|
+
pageCount,
|
|
24944
|
+
freelistCount,
|
|
24945
|
+
reclaimableBytes: freelistCount * pageSize,
|
|
24946
|
+
integrityOk,
|
|
24947
|
+
integrityErrors: integrityOk ? [] : integRows,
|
|
24948
|
+
fkViolations,
|
|
24949
|
+
protectedFkViolations,
|
|
24950
|
+
totalFkViolations: allGroups.reduce((n, g) => n + g.count, 0),
|
|
24951
|
+
markdownDuplicateRows: scalarInt(
|
|
24952
|
+
db,
|
|
24953
|
+
`SELECT COALESCE(SUM(cnt - 1), 0) FROM (
|
|
24954
|
+
SELECT COUNT(*) AS cnt FROM markdown_artifacts
|
|
24955
|
+
GROUP BY session_id, artifact_type, IFNULL(round_number, -1), file_path
|
|
24956
|
+
HAVING cnt > 1)`
|
|
24957
|
+
),
|
|
24958
|
+
orphanTempFiles: scanOrphanTempFiles(dataDir),
|
|
24959
|
+
backupFiles: scanBackupFiles(dataDir, dbBase),
|
|
24960
|
+
eventCount: scalarInt(db, "SELECT COUNT(*) FROM orchestration_events"),
|
|
24961
|
+
sessionCount: scalarInt(db, "SELECT COUNT(*) FROM sessions")
|
|
24962
|
+
};
|
|
24963
|
+
}
|
|
24964
|
+
function snapshotDb(db, dbPath, label = "doctor") {
|
|
24965
|
+
try {
|
|
24966
|
+
if (!existsSync11(dbPath) || statSync(dbPath).size === 0) return null;
|
|
24967
|
+
db.pragma("wal_checkpoint(TRUNCATE)");
|
|
24968
|
+
const ts = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
24969
|
+
const bakPath = `${dbPath}.bak.${label}.${ts}`;
|
|
24970
|
+
copyFileSync(dbPath, bakPath);
|
|
24971
|
+
return bakPath;
|
|
24972
|
+
} catch {
|
|
24973
|
+
return null;
|
|
24974
|
+
}
|
|
24975
|
+
}
|
|
24976
|
+
function reapOrphanDbFiles(dataDir) {
|
|
24977
|
+
const reaped = [];
|
|
24978
|
+
for (const f of scanOrphanTempFiles(dataDir)) {
|
|
24979
|
+
if (!f.reapable) continue;
|
|
24980
|
+
try {
|
|
24981
|
+
unlinkSync3(join13(dataDir, f.name));
|
|
24982
|
+
reaped.push(f.name);
|
|
24983
|
+
} catch {
|
|
24984
|
+
}
|
|
24985
|
+
}
|
|
24986
|
+
return reaped;
|
|
24987
|
+
}
|
|
24988
|
+
function reapStaleExecLogs(execLogsDir, maxAgeMs = SEVEN_DAYS_MS) {
|
|
24989
|
+
let entries;
|
|
24990
|
+
try {
|
|
24991
|
+
entries = readdirSync5(execLogsDir);
|
|
24992
|
+
} catch {
|
|
24993
|
+
return [];
|
|
24994
|
+
}
|
|
24995
|
+
const cutoff = Date.now() - maxAgeMs;
|
|
24996
|
+
const reaped = [];
|
|
24997
|
+
for (const name of entries) {
|
|
24998
|
+
if (!name.endsWith(".log")) continue;
|
|
24999
|
+
const full = join13(execLogsDir, name);
|
|
25000
|
+
try {
|
|
25001
|
+
if (statSync(full).mtimeMs > cutoff) continue;
|
|
25002
|
+
unlinkSync3(full);
|
|
25003
|
+
reaped.push(name);
|
|
25004
|
+
} catch {
|
|
25005
|
+
}
|
|
25006
|
+
}
|
|
25007
|
+
return reaped;
|
|
25008
|
+
}
|
|
25009
|
+
function pruneBackups(dataDir, dbPath, opts = {}) {
|
|
25010
|
+
const keep = opts.keep ?? 1;
|
|
25011
|
+
if (!Number.isInteger(keep) || keep < 0) {
|
|
25012
|
+
throw new Error(
|
|
25013
|
+
`pruneBackups: keep must be a non-negative integer (got ${String(keep)})`
|
|
25014
|
+
);
|
|
25015
|
+
}
|
|
25016
|
+
const dryRun = opts.dryRun ?? false;
|
|
25017
|
+
const dbBase = basename7(dbPath);
|
|
25018
|
+
const withMtime = [];
|
|
25019
|
+
for (const file of scanBackupFiles(dataDir, dbBase)) {
|
|
25020
|
+
try {
|
|
25021
|
+
withMtime.push({ file, mtimeMs: statSync(join13(dataDir, file.name)).mtimeMs });
|
|
25022
|
+
} catch {
|
|
25023
|
+
}
|
|
25024
|
+
}
|
|
25025
|
+
withMtime.sort((a, b) => b.mtimeMs - a.mtimeMs);
|
|
25026
|
+
const kept = withMtime.slice(0, keep).map((x) => x.file);
|
|
25027
|
+
const toDelete = withMtime.slice(keep).map((x) => x.file);
|
|
25028
|
+
const deleted = [];
|
|
25029
|
+
if (!dryRun) {
|
|
25030
|
+
for (const b of toDelete) {
|
|
25031
|
+
try {
|
|
25032
|
+
unlinkSync3(join13(dataDir, b.name));
|
|
25033
|
+
deleted.push(b);
|
|
25034
|
+
} catch {
|
|
25035
|
+
}
|
|
25036
|
+
}
|
|
25037
|
+
}
|
|
25038
|
+
const reported = dryRun ? toDelete : deleted;
|
|
25039
|
+
return {
|
|
25040
|
+
dryRun,
|
|
25041
|
+
deleted: reported,
|
|
25042
|
+
kept,
|
|
25043
|
+
reclaimedBytes: reported.reduce((n, b) => n + b.sizeBytes, 0)
|
|
25044
|
+
};
|
|
25045
|
+
}
|
|
25046
|
+
function fixDb(db, dbPath, opts = {}) {
|
|
25047
|
+
const dataDir = dirname6(dbPath);
|
|
25048
|
+
const sizeBeforeBytes = existsSync11(dbPath) ? statSync(dbPath).size : 0;
|
|
25049
|
+
const snapshotPath = opts.snapshot === false ? null : snapshotDb(db, dbPath, "doctor");
|
|
25050
|
+
const fkOrphansDeleted = [];
|
|
25051
|
+
withForeignKeysDisabled(db, () => {
|
|
25052
|
+
db.transaction(() => {
|
|
25053
|
+
for (const sweep of ORPHAN_SWEEPS) {
|
|
25054
|
+
const info = db.prepare(sweep.sql).run();
|
|
25055
|
+
const count = Number(info.changes);
|
|
25056
|
+
if (count > 0) fkOrphansDeleted.push({ table: sweep.table, count });
|
|
25057
|
+
}
|
|
25058
|
+
});
|
|
25059
|
+
});
|
|
25060
|
+
let markdownDupsDeleted = 0;
|
|
25061
|
+
db.transaction(() => {
|
|
25062
|
+
const info = db.prepare(MARKDOWN_DEDUP_SQL).run();
|
|
25063
|
+
markdownDupsDeleted = Number(info.changes);
|
|
25064
|
+
});
|
|
25065
|
+
const tempsReaped = opts.reapTemps === false ? [] : reapOrphanDbFiles(dataDir);
|
|
25066
|
+
let vacuumed = false;
|
|
25067
|
+
if (opts.vacuum !== false) {
|
|
25068
|
+
try {
|
|
25069
|
+
db.pragma("wal_checkpoint(TRUNCATE)");
|
|
25070
|
+
db.run("VACUUM");
|
|
25071
|
+
vacuumed = true;
|
|
25072
|
+
} catch {
|
|
25073
|
+
vacuumed = false;
|
|
25074
|
+
}
|
|
25075
|
+
}
|
|
25076
|
+
const post = collectDbHealth(db, dbPath);
|
|
25077
|
+
return {
|
|
25078
|
+
snapshotPath,
|
|
25079
|
+
fkOrphansDeleted,
|
|
25080
|
+
totalFkOrphansDeleted: fkOrphansDeleted.reduce((n, g) => n + g.count, 0),
|
|
25081
|
+
protectedViolationsRemaining: post.protectedFkViolations,
|
|
25082
|
+
markdownDupsDeleted,
|
|
25083
|
+
tempsReaped,
|
|
25084
|
+
vacuumed,
|
|
25085
|
+
sizeBeforeBytes,
|
|
25086
|
+
sizeAfterBytes: post.fileSizeBytes,
|
|
25087
|
+
integrityOkAfter: post.integrityOk,
|
|
25088
|
+
fkViolationsAfter: post.totalFkViolations
|
|
25089
|
+
};
|
|
25090
|
+
}
|
|
25091
|
+
function vacuumDb(db, dbPath, opts = {}) {
|
|
25092
|
+
const sizeBeforeBytes = existsSync11(dbPath) ? statSync(dbPath).size : 0;
|
|
25093
|
+
const snapshotPath = opts.snapshot === false ? null : snapshotDb(db, dbPath, "vacuum");
|
|
25094
|
+
db.pragma("wal_checkpoint(TRUNCATE)");
|
|
25095
|
+
db.run("VACUUM");
|
|
25096
|
+
db.pragma("wal_checkpoint(TRUNCATE)");
|
|
25097
|
+
const sizeAfterBytes = existsSync11(dbPath) ? statSync(dbPath).size : 0;
|
|
25098
|
+
return {
|
|
25099
|
+
snapshotPath,
|
|
25100
|
+
sizeBeforeBytes,
|
|
25101
|
+
sizeAfterBytes,
|
|
25102
|
+
reclaimedBytes: Math.max(0, sizeBeforeBytes - sizeAfterBytes)
|
|
25103
|
+
};
|
|
25104
|
+
}
|
|
25105
|
+
function countSessionArtifacts(db, sessionId) {
|
|
25106
|
+
const r = db.exec(
|
|
25107
|
+
`SELECT
|
|
25108
|
+
(SELECT COUNT(*) FROM markdown_artifacts WHERE session_id = ?) +
|
|
25109
|
+
(SELECT COUNT(*) FROM review_rounds WHERE session_id = ?) +
|
|
25110
|
+
(SELECT COUNT(*) FROM reviewer_outputs ro JOIN review_rounds rr ON ro.round_id = rr.id WHERE rr.session_id = ?) +
|
|
25111
|
+
(SELECT COUNT(*) FROM review_findings rf JOIN reviewer_outputs ro ON rf.reviewer_output_id = ro.id JOIN review_rounds rr ON ro.round_id = rr.id WHERE rr.session_id = ?) +
|
|
25112
|
+
(SELECT COUNT(*) FROM map_runs WHERE session_id = ?) +
|
|
25113
|
+
(SELECT COUNT(*) FROM chat_conversations WHERE session_id = ?)`,
|
|
25114
|
+
Array(6).fill(sessionId)
|
|
25115
|
+
);
|
|
25116
|
+
const v = r[0]?.values[0]?.[0];
|
|
25117
|
+
return typeof v === "number" ? v : Number(v ?? 0);
|
|
25118
|
+
}
|
|
25119
|
+
function pruneDb(db, dbPath, opts = {}) {
|
|
25120
|
+
const dryRun = opts.dryRun ?? false;
|
|
25121
|
+
const hasBound = opts.olderThanDays !== void 0 || opts.keepSessions !== void 0;
|
|
25122
|
+
if (!hasBound) {
|
|
25123
|
+
return { dryRun, snapshotPath: null, prunedSessions: [], totalArtifactRows: 0 };
|
|
25124
|
+
}
|
|
25125
|
+
const rows = db.exec(
|
|
25126
|
+
`SELECT s.id,
|
|
25127
|
+
(SELECT (julianday('now') - julianday(MAX(e.created_at))) * 86400
|
|
25128
|
+
FROM orchestration_events e WHERE e.session_id = s.id) AS quiet_seconds
|
|
25129
|
+
FROM sessions s
|
|
25130
|
+
WHERE s.status = 'closed'
|
|
25131
|
+
ORDER BY quiet_seconds ASC`
|
|
25132
|
+
);
|
|
25133
|
+
const closed = (rows[0]?.values ?? []).map((v) => ({
|
|
25134
|
+
id: String(v[0]),
|
|
25135
|
+
quietSeconds: typeof v[1] === "number" ? v[1] : Number(v[1] ?? 0)
|
|
25136
|
+
}));
|
|
25137
|
+
const keepN = opts.keepSessions ?? 0;
|
|
25138
|
+
const olderThanSeconds = opts.olderThanDays !== void 0 ? opts.olderThanDays * 86400 : null;
|
|
25139
|
+
const targets = closed.filter((s, idx) => {
|
|
25140
|
+
if (idx < keepN) return false;
|
|
25141
|
+
if (olderThanSeconds !== null && s.quietSeconds < olderThanSeconds)
|
|
25142
|
+
return false;
|
|
25143
|
+
return true;
|
|
25144
|
+
});
|
|
25145
|
+
const prunedSessions = [];
|
|
25146
|
+
for (const t of targets) {
|
|
25147
|
+
const artifactRows = countSessionArtifacts(db, t.id);
|
|
25148
|
+
if (artifactRows === 0) continue;
|
|
25149
|
+
prunedSessions.push({ sessionId: t.id, artifactRows });
|
|
25150
|
+
}
|
|
25151
|
+
if (dryRun || prunedSessions.length === 0) {
|
|
25152
|
+
return {
|
|
25153
|
+
dryRun,
|
|
25154
|
+
snapshotPath: null,
|
|
25155
|
+
prunedSessions,
|
|
25156
|
+
totalArtifactRows: prunedSessions.reduce((n, p) => n + p.artifactRows, 0)
|
|
25157
|
+
};
|
|
25158
|
+
}
|
|
25159
|
+
const snapshotPath = snapshotDb(db, dbPath, "prune");
|
|
25160
|
+
db.transaction(() => {
|
|
25161
|
+
for (const p of prunedSessions) {
|
|
25162
|
+
db.run("DELETE FROM review_rounds WHERE session_id = ?", [p.sessionId]);
|
|
25163
|
+
db.run("DELETE FROM map_runs WHERE session_id = ?", [p.sessionId]);
|
|
25164
|
+
db.run("DELETE FROM markdown_artifacts WHERE session_id = ?", [p.sessionId]);
|
|
25165
|
+
db.run("DELETE FROM chat_conversations WHERE session_id = ?", [p.sessionId]);
|
|
25166
|
+
}
|
|
25167
|
+
});
|
|
25168
|
+
return {
|
|
25169
|
+
dryRun,
|
|
25170
|
+
snapshotPath,
|
|
25171
|
+
prunedSessions,
|
|
25172
|
+
totalArtifactRows: prunedSessions.reduce((n, p) => n + p.artifactRows, 0)
|
|
25173
|
+
};
|
|
25174
|
+
}
|
|
25175
|
+
var PROTECTED_TABLES, ORPHAN_SWEEPS, MARKDOWN_DEDUP_SQL, ONE_HOUR_MS, SEVEN_DAYS_MS;
|
|
25176
|
+
var init_maintenance = __esm({
|
|
25177
|
+
"src/lib/db/maintenance.ts"() {
|
|
25178
|
+
"use strict";
|
|
25179
|
+
init_src();
|
|
25180
|
+
PROTECTED_TABLES = /* @__PURE__ */ new Set([
|
|
25181
|
+
"sessions",
|
|
25182
|
+
"orchestration_events",
|
|
25183
|
+
"agent_sessions",
|
|
25184
|
+
"command_executions",
|
|
25185
|
+
"schema_version"
|
|
25186
|
+
]);
|
|
25187
|
+
ORPHAN_SWEEPS = [
|
|
25188
|
+
// session-rooted parents first
|
|
25189
|
+
{
|
|
25190
|
+
table: "review_rounds",
|
|
25191
|
+
sql: "DELETE FROM review_rounds WHERE session_id NOT IN (SELECT id FROM sessions)"
|
|
25192
|
+
},
|
|
25193
|
+
{
|
|
25194
|
+
table: "map_runs",
|
|
25195
|
+
sql: "DELETE FROM map_runs WHERE session_id NOT IN (SELECT id FROM sessions)"
|
|
25196
|
+
},
|
|
25197
|
+
{
|
|
25198
|
+
table: "markdown_artifacts",
|
|
25199
|
+
sql: "DELETE FROM markdown_artifacts WHERE session_id NOT IN (SELECT id FROM sessions)"
|
|
25200
|
+
},
|
|
25201
|
+
{
|
|
25202
|
+
table: "chat_conversations",
|
|
25203
|
+
sql: "DELETE FROM chat_conversations WHERE session_id NOT IN (SELECT id FROM sessions)"
|
|
25204
|
+
},
|
|
25205
|
+
// second level (pick up parents deleted above)
|
|
25206
|
+
{
|
|
25207
|
+
table: "reviewer_outputs",
|
|
25208
|
+
sql: "DELETE FROM reviewer_outputs WHERE round_id NOT IN (SELECT id FROM review_rounds)"
|
|
25209
|
+
},
|
|
25210
|
+
{
|
|
25211
|
+
table: "map_sections",
|
|
25212
|
+
sql: "DELETE FROM map_sections WHERE map_run_id NOT IN (SELECT id FROM map_runs)"
|
|
25213
|
+
},
|
|
25214
|
+
{
|
|
25215
|
+
table: "chat_messages",
|
|
25216
|
+
sql: "DELETE FROM chat_messages WHERE conversation_id NOT IN (SELECT id FROM chat_conversations)"
|
|
25217
|
+
},
|
|
25218
|
+
{
|
|
25219
|
+
table: "user_round_progress",
|
|
25220
|
+
sql: "DELETE FROM user_round_progress WHERE round_id NOT IN (SELECT id FROM review_rounds)"
|
|
25221
|
+
},
|
|
25222
|
+
// third level
|
|
25223
|
+
{
|
|
25224
|
+
table: "review_findings",
|
|
25225
|
+
sql: "DELETE FROM review_findings WHERE reviewer_output_id NOT IN (SELECT id FROM reviewer_outputs)"
|
|
25226
|
+
},
|
|
25227
|
+
{
|
|
25228
|
+
table: "map_files",
|
|
25229
|
+
sql: "DELETE FROM map_files WHERE section_id NOT IN (SELECT id FROM map_sections)"
|
|
25230
|
+
},
|
|
25231
|
+
// leaves
|
|
25232
|
+
{
|
|
25233
|
+
table: "user_finding_progress",
|
|
25234
|
+
sql: "DELETE FROM user_finding_progress WHERE finding_id NOT IN (SELECT id FROM review_findings)"
|
|
25235
|
+
},
|
|
25236
|
+
{
|
|
25237
|
+
table: "user_file_progress",
|
|
25238
|
+
sql: "DELETE FROM user_file_progress WHERE map_file_id NOT IN (SELECT id FROM map_files)"
|
|
25239
|
+
}
|
|
25240
|
+
];
|
|
25241
|
+
MARKDOWN_DEDUP_SQL = `
|
|
25242
|
+
DELETE FROM markdown_artifacts
|
|
25243
|
+
WHERE rowid NOT IN (
|
|
25244
|
+
SELECT MAX(rowid) FROM markdown_artifacts
|
|
25245
|
+
GROUP BY session_id, artifact_type, IFNULL(round_number, -1), file_path
|
|
25246
|
+
)`;
|
|
25247
|
+
ONE_HOUR_MS = 60 * 60 * 1e3;
|
|
25248
|
+
SEVEN_DAYS_MS = 7 * 24 * 60 * 60 * 1e3;
|
|
25249
|
+
}
|
|
25250
|
+
});
|
|
25251
|
+
|
|
24624
25252
|
// src/lib/db/command-log.ts
|
|
24625
|
-
import { appendFileSync, existsSync as
|
|
24626
|
-
import { dirname as
|
|
25253
|
+
import { appendFileSync, existsSync as existsSync12, mkdirSync as mkdirSync4, readFileSync as readFileSync9, renameSync, writeFileSync as writeFileSync6 } from "node:fs";
|
|
25254
|
+
import { dirname as dirname7, join as join14 } from "node:path";
|
|
24627
25255
|
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
24628
25256
|
function generateCommandUid() {
|
|
24629
25257
|
return randomUUID2();
|
|
24630
25258
|
}
|
|
24631
25259
|
function cacheDir(ocrDir) {
|
|
24632
|
-
return
|
|
25260
|
+
return join14(ocrDir, "data", CACHE_DIR);
|
|
24633
25261
|
}
|
|
24634
25262
|
function commandLogPath(ocrDir) {
|
|
24635
|
-
return
|
|
25263
|
+
return join14(cacheDir(ocrDir), FILENAME);
|
|
24636
25264
|
}
|
|
24637
25265
|
function appendCommandLog(ocrDir, entry) {
|
|
24638
25266
|
try {
|
|
24639
25267
|
const filePath = commandLogPath(ocrDir);
|
|
24640
|
-
const dir =
|
|
24641
|
-
if (!
|
|
25268
|
+
const dir = dirname7(filePath);
|
|
25269
|
+
if (!existsSync12(dir)) mkdirSync4(dir, { recursive: true });
|
|
24642
25270
|
const line = JSON.stringify(entry) + "\n";
|
|
24643
25271
|
appendFileSync(filePath, line, { encoding: "utf-8" });
|
|
24644
25272
|
if (approxLineCount >= 0) approxLineCount++;
|
|
@@ -24648,7 +25276,7 @@ function appendCommandLog(ocrDir, entry) {
|
|
|
24648
25276
|
}
|
|
24649
25277
|
function readCommandLog(ocrDir) {
|
|
24650
25278
|
const filePath = commandLogPath(ocrDir);
|
|
24651
|
-
if (!
|
|
25279
|
+
if (!existsSync12(filePath)) return [];
|
|
24652
25280
|
const content = readFileSync9(filePath, "utf-8");
|
|
24653
25281
|
const entries = [];
|
|
24654
25282
|
for (const line of content.split("\n")) {
|
|
@@ -24734,6 +25362,7 @@ __export(db_exports, {
|
|
|
24734
25362
|
PID_REUSE_GUARD_MS: () => PID_REUSE_GUARD_MS,
|
|
24735
25363
|
STATE_EXIT: () => STATE_EXIT,
|
|
24736
25364
|
StateError: () => StateError,
|
|
25365
|
+
WATCHDOG_DEADLINE_EXIT_CODE: () => WATCHDOG_DEADLINE_EXIT_CODE,
|
|
24737
25366
|
appendCommandLog: () => appendCommandLog,
|
|
24738
25367
|
bindVendorSessionIdOpportunistically: () => bindVendorSessionIdOpportunistically,
|
|
24739
25368
|
bumpAgentSessionHeartbeat: () => bumpAgentSessionHeartbeat,
|
|
@@ -24741,10 +25370,12 @@ __export(db_exports, {
|
|
|
24741
25370
|
cascadeTerminateExecutions: () => cascadeTerminateExecutions,
|
|
24742
25371
|
closeAllDatabases: () => closeAllDatabases,
|
|
24743
25372
|
closeDatabase: () => closeDatabase,
|
|
25373
|
+
collectDbHealth: () => collectDbHealth,
|
|
24744
25374
|
commandLogPath: () => commandLogPath,
|
|
24745
25375
|
commitReasonClose: () => commitReasonClose,
|
|
24746
25376
|
defaultIsAlive: () => defaultIsAlive,
|
|
24747
25377
|
ensureDatabase: () => ensureDatabase,
|
|
25378
|
+
fixDb: () => fixDb,
|
|
24748
25379
|
formatUpgradeNotice: () => formatUpgradeNotice,
|
|
24749
25380
|
generateCommandUid: () => generateCommandUid,
|
|
24750
25381
|
getAgentSession: () => getAgentSession,
|
|
@@ -24756,6 +25387,7 @@ __export(db_exports, {
|
|
|
24756
25387
|
getLatestEventId: () => getLatestEventId,
|
|
24757
25388
|
getSchemaVersion: () => getSchemaVersion,
|
|
24758
25389
|
getSession: () => getSession,
|
|
25390
|
+
hasInFlightDependents: () => hasInFlightDependents,
|
|
24759
25391
|
insertAgentSession: () => insertAgentSession,
|
|
24760
25392
|
insertEvent: () => insertEvent,
|
|
24761
25393
|
insertSession: () => insertSession,
|
|
@@ -24764,7 +25396,12 @@ __export(db_exports, {
|
|
|
24764
25396
|
listAgentSessionsForWorkflow: () => listAgentSessionsForWorkflow,
|
|
24765
25397
|
openDatabase: () => openDatabase,
|
|
24766
25398
|
probeEngine: () => probeEngine,
|
|
25399
|
+
probeWrite: () => probeWrite,
|
|
25400
|
+
pruneBackups: () => pruneBackups,
|
|
25401
|
+
pruneDb: () => pruneDb,
|
|
24767
25402
|
readCommandLog: () => readCommandLog,
|
|
25403
|
+
reapOrphanDbFiles: () => reapOrphanDbFiles,
|
|
25404
|
+
reapStaleExecLogs: () => reapStaleExecLogs,
|
|
24768
25405
|
reconcileLegacyState: () => reconcileLegacyState,
|
|
24769
25406
|
recordVendorSessionIdForExecution: () => recordVendorSessionIdForExecution,
|
|
24770
25407
|
replayCommandLog: () => replayCommandLog,
|
|
@@ -24774,23 +25411,34 @@ __export(db_exports, {
|
|
|
24774
25411
|
runMigrations: () => runMigrations,
|
|
24775
25412
|
setAgentSessionStatus: () => setAgentSessionStatus,
|
|
24776
25413
|
setAgentSessionVendorId: () => setAgentSessionVendorId,
|
|
25414
|
+
snapshotDb: () => snapshotDb,
|
|
24777
25415
|
sqliteUtcMs: () => sqliteUtcMs,
|
|
24778
25416
|
sweepStaleAgentSessions: () => sweepStaleAgentSessions,
|
|
24779
25417
|
sweepStaleSessions: () => sweepStaleSessions,
|
|
24780
25418
|
updateAgentSession: () => updateAgentSession,
|
|
24781
25419
|
updateSession: () => updateSession,
|
|
24782
|
-
|
|
25420
|
+
vacuumDb: () => vacuumDb,
|
|
25421
|
+
walCheckpointTruncate: () => walCheckpointTruncate,
|
|
25422
|
+
withForeignKeysDisabled: () => withForeignKeysDisabled
|
|
24783
25423
|
});
|
|
24784
|
-
import {
|
|
24785
|
-
|
|
25424
|
+
import {
|
|
25425
|
+
existsSync as existsSync13,
|
|
25426
|
+
mkdirSync as mkdirSync5,
|
|
25427
|
+
copyFileSync as copyFileSync2,
|
|
25428
|
+
statSync as statSync2,
|
|
25429
|
+
mkdtempSync,
|
|
25430
|
+
rmSync
|
|
25431
|
+
} from "node:fs";
|
|
25432
|
+
import { tmpdir } from "node:os";
|
|
25433
|
+
import { dirname as dirname8, join as join15 } from "node:path";
|
|
24786
25434
|
function maybeSnapshotBeforeUpgrade(db, dbPath, fromVersion) {
|
|
24787
25435
|
if (fromVersion < 1 || fromVersion >= V2_SCHEMA_VERSION) return null;
|
|
24788
25436
|
const bakPath = `${dbPath}.bak.v${fromVersion}`;
|
|
24789
|
-
if (
|
|
25437
|
+
if (existsSync13(bakPath)) return bakPath;
|
|
24790
25438
|
try {
|
|
24791
|
-
if (!
|
|
25439
|
+
if (!existsSync13(dbPath) || statSync2(dbPath).size === 0) return null;
|
|
24792
25440
|
db.pragma("wal_checkpoint(TRUNCATE)");
|
|
24793
|
-
|
|
25441
|
+
copyFileSync2(dbPath, bakPath);
|
|
24794
25442
|
return bakPath;
|
|
24795
25443
|
} catch {
|
|
24796
25444
|
return null;
|
|
@@ -24823,24 +25471,24 @@ async function openDatabase(dbPath) {
|
|
|
24823
25471
|
if (cached) {
|
|
24824
25472
|
return cached;
|
|
24825
25473
|
}
|
|
24826
|
-
const dir =
|
|
24827
|
-
if (!
|
|
24828
|
-
|
|
25474
|
+
const dir = dirname8(dbPath);
|
|
25475
|
+
if (!existsSync13(dir)) {
|
|
25476
|
+
mkdirSync5(dir, { recursive: true });
|
|
24829
25477
|
}
|
|
24830
25478
|
const db = openEngine(dbPath);
|
|
24831
25479
|
connections.set(dbPath, db);
|
|
24832
25480
|
return db;
|
|
24833
25481
|
}
|
|
24834
25482
|
async function getDb(ocrDir) {
|
|
24835
|
-
const dbPath =
|
|
25483
|
+
const dbPath = join15(ocrDir, "data", "ocr.db");
|
|
24836
25484
|
return openDatabase(dbPath);
|
|
24837
25485
|
}
|
|
24838
25486
|
async function ensureDatabase(ocrDir) {
|
|
24839
|
-
const dataDir =
|
|
24840
|
-
if (!
|
|
24841
|
-
|
|
25487
|
+
const dataDir = join15(ocrDir, "data");
|
|
25488
|
+
if (!existsSync13(dataDir)) {
|
|
25489
|
+
mkdirSync5(dataDir, { recursive: true });
|
|
24842
25490
|
}
|
|
24843
|
-
const dbPath =
|
|
25491
|
+
const dbPath = join15(dataDir, "ocr.db");
|
|
24844
25492
|
const db = await openDatabase(dbPath);
|
|
24845
25493
|
let before = 0;
|
|
24846
25494
|
try {
|
|
@@ -24868,7 +25516,7 @@ async function ensureDatabase(ocrDir) {
|
|
|
24868
25516
|
return db;
|
|
24869
25517
|
}
|
|
24870
25518
|
function walCheckpointTruncate(dbPath) {
|
|
24871
|
-
if (!
|
|
25519
|
+
if (!existsSync13(dbPath)) {
|
|
24872
25520
|
return "skipped";
|
|
24873
25521
|
}
|
|
24874
25522
|
const cached = connections.get(dbPath);
|
|
@@ -24889,7 +25537,7 @@ function walCheckpointTruncate(dbPath) {
|
|
|
24889
25537
|
return "failed";
|
|
24890
25538
|
} finally {
|
|
24891
25539
|
try {
|
|
24892
|
-
transient?.
|
|
25540
|
+
transient?.close();
|
|
24893
25541
|
} catch {
|
|
24894
25542
|
}
|
|
24895
25543
|
}
|
|
@@ -24907,6 +25555,41 @@ function closeAllDatabases() {
|
|
|
24907
25555
|
connections.delete(path2);
|
|
24908
25556
|
}
|
|
24909
25557
|
}
|
|
25558
|
+
function probeWrite() {
|
|
25559
|
+
let dir;
|
|
25560
|
+
try {
|
|
25561
|
+
dir = mkdtempSync(join15(tmpdir(), "ocr-probe-"));
|
|
25562
|
+
const db = openEngine(join15(dir, "probe.db"));
|
|
25563
|
+
try {
|
|
25564
|
+
db.run("CREATE TABLE _probe_write (id INTEGER PRIMARY KEY, v TEXT)");
|
|
25565
|
+
db.transaction(() => {
|
|
25566
|
+
db.run("INSERT INTO _probe_write (v) VALUES (?)", ["written-in-txn"]);
|
|
25567
|
+
});
|
|
25568
|
+
const value = db.exec("SELECT v FROM _probe_write")[0]?.values[0]?.[0];
|
|
25569
|
+
if (value !== "written-in-txn") {
|
|
25570
|
+
return { ok: false, error: `unexpected probe value: ${String(value)}` };
|
|
25571
|
+
}
|
|
25572
|
+
return { ok: true };
|
|
25573
|
+
} finally {
|
|
25574
|
+
db.close();
|
|
25575
|
+
}
|
|
25576
|
+
} catch (e) {
|
|
25577
|
+
return { ok: false, error: e instanceof Error ? e.message : String(e) };
|
|
25578
|
+
} finally {
|
|
25579
|
+
if (dir) rmDirBestEffort(dir);
|
|
25580
|
+
}
|
|
25581
|
+
}
|
|
25582
|
+
function rmDirBestEffort(dir) {
|
|
25583
|
+
for (let attempt = 0; attempt < 3; attempt++) {
|
|
25584
|
+
try {
|
|
25585
|
+
rmSync(dir, { recursive: true, force: true });
|
|
25586
|
+
return;
|
|
25587
|
+
} catch {
|
|
25588
|
+
if (attempt === 2) return;
|
|
25589
|
+
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 10);
|
|
25590
|
+
}
|
|
25591
|
+
}
|
|
25592
|
+
}
|
|
24910
25593
|
var V2_SCHEMA_VERSION, connections;
|
|
24911
25594
|
var init_db = __esm({
|
|
24912
25595
|
"src/lib/db/index.ts"() {
|
|
@@ -24922,6 +25605,7 @@ var init_db = __esm({
|
|
|
24922
25605
|
init_result_mapper();
|
|
24923
25606
|
init_engine();
|
|
24924
25607
|
init_reconcile();
|
|
25608
|
+
init_maintenance();
|
|
24925
25609
|
init_migrations();
|
|
24926
25610
|
init_command_log();
|
|
24927
25611
|
V2_SCHEMA_VERSION = 12;
|
|
@@ -24929,6 +25613,13 @@ var init_db = __esm({
|
|
|
24929
25613
|
}
|
|
24930
25614
|
});
|
|
24931
25615
|
|
|
25616
|
+
// src/lib/runtime-guard.ts
|
|
25617
|
+
init_runtime_checks();
|
|
25618
|
+
if (!isSupportedNode(process.versions.node)) {
|
|
25619
|
+
process.stderr.write(nodeVersionGuardMessage(process.versions.node));
|
|
25620
|
+
process.exit(1);
|
|
25621
|
+
}
|
|
25622
|
+
|
|
24932
25623
|
// ../../node_modules/.pnpm/commander@13.1.0/node_modules/commander/esm.mjs
|
|
24933
25624
|
var import_index = __toESM(require_commander(), 1);
|
|
24934
25625
|
var {
|
|
@@ -28445,6 +29136,8 @@ function ora(options) {
|
|
|
28445
29136
|
}
|
|
28446
29137
|
|
|
28447
29138
|
// src/lib/config.ts
|
|
29139
|
+
init_src();
|
|
29140
|
+
init_src();
|
|
28448
29141
|
var AI_TOOLS = [
|
|
28449
29142
|
{
|
|
28450
29143
|
id: "amazon-q",
|
|
@@ -28468,7 +29161,9 @@ var AI_TOOLS = [
|
|
|
28468
29161
|
configDir: ".claude",
|
|
28469
29162
|
commandsDir: ".claude/commands",
|
|
28470
29163
|
skillsDir: ".claude/skills",
|
|
28471
|
-
commandStrategy: "subdirectory"
|
|
29164
|
+
commandStrategy: "subdirectory",
|
|
29165
|
+
instructionFiles: [{ path: "CLAUDE.md", format: "markdown" }],
|
|
29166
|
+
vendorBinary: "claude"
|
|
28472
29167
|
},
|
|
28473
29168
|
{
|
|
28474
29169
|
id: "cline",
|
|
@@ -28484,7 +29179,9 @@ var AI_TOOLS = [
|
|
|
28484
29179
|
configDir: ".codex",
|
|
28485
29180
|
commandsDir: ".codex/commands",
|
|
28486
29181
|
skillsDir: ".codex/skills",
|
|
28487
|
-
commandStrategy: "subdirectory"
|
|
29182
|
+
commandStrategy: "subdirectory",
|
|
29183
|
+
// Codex reads AGENTS.md natively — no extra instruction file.
|
|
29184
|
+
vendorBinary: "codex"
|
|
28488
29185
|
},
|
|
28489
29186
|
{
|
|
28490
29187
|
id: "continue",
|
|
@@ -28508,7 +29205,9 @@ var AI_TOOLS = [
|
|
|
28508
29205
|
configDir: ".gemini",
|
|
28509
29206
|
commandsDir: ".gemini/commands",
|
|
28510
29207
|
skillsDir: ".gemini/skills",
|
|
28511
|
-
commandStrategy: "subdirectory"
|
|
29208
|
+
commandStrategy: "subdirectory",
|
|
29209
|
+
instructionFiles: [{ path: "GEMINI.md", format: "markdown" }],
|
|
29210
|
+
vendorBinary: "gemini"
|
|
28512
29211
|
},
|
|
28513
29212
|
{
|
|
28514
29213
|
id: "github-copilot",
|
|
@@ -28516,7 +29215,10 @@ var AI_TOOLS = [
|
|
|
28516
29215
|
configDir: ".github",
|
|
28517
29216
|
commandsDir: ".github/commands",
|
|
28518
29217
|
skillsDir: ".github/skills",
|
|
28519
|
-
commandStrategy: "subdirectory"
|
|
29218
|
+
commandStrategy: "subdirectory",
|
|
29219
|
+
instructionFiles: [
|
|
29220
|
+
{ path: ".github/copilot-instructions.md", format: "markdown" }
|
|
29221
|
+
]
|
|
28520
29222
|
},
|
|
28521
29223
|
{
|
|
28522
29224
|
id: "kilo-code",
|
|
@@ -28532,7 +29234,9 @@ var AI_TOOLS = [
|
|
|
28532
29234
|
configDir: ".opencode",
|
|
28533
29235
|
commandsDir: ".opencode/commands",
|
|
28534
29236
|
skillsDir: ".opencode/skills",
|
|
28535
|
-
commandStrategy: "subdirectory"
|
|
29237
|
+
commandStrategy: "subdirectory",
|
|
29238
|
+
// OpenCode reads AGENTS.md natively — no extra instruction file.
|
|
29239
|
+
vendorBinary: "opencode"
|
|
28536
29240
|
},
|
|
28537
29241
|
{
|
|
28538
29242
|
id: "qoder",
|
|
@@ -28556,9 +29260,16 @@ var AI_TOOLS = [
|
|
|
28556
29260
|
configDir: ".windsurf",
|
|
28557
29261
|
commandsDir: ".windsurf/workflows",
|
|
28558
29262
|
skillsDir: ".windsurf/skills",
|
|
28559
|
-
commandStrategy: "flat-prefixed"
|
|
29263
|
+
commandStrategy: "flat-prefixed",
|
|
29264
|
+
instructionFiles: [{ path: ".windsurfrules", format: "plaintext" }]
|
|
28560
29265
|
}
|
|
28561
29266
|
];
|
|
29267
|
+
function getToolById(id) {
|
|
29268
|
+
return AI_TOOLS.find((tool) => tool.id === id);
|
|
29269
|
+
}
|
|
29270
|
+
function getHostCapabilities(id) {
|
|
29271
|
+
return hostCapabilitiesFor(id);
|
|
29272
|
+
}
|
|
28562
29273
|
function getToolIds() {
|
|
28563
29274
|
return AI_TOOLS.map((tool) => tool.id);
|
|
28564
29275
|
}
|
|
@@ -28619,11 +29330,11 @@ function ensureGitignore(ocrDir) {
|
|
|
28619
29330
|
const gitignorePath = join(ocrDir, ".gitignore");
|
|
28620
29331
|
const block = buildManagedBlock();
|
|
28621
29332
|
let content = existsSync(gitignorePath) ? readFileSync2(gitignorePath, "utf-8") : "";
|
|
28622
|
-
const
|
|
29333
|
+
const blockRegex2 = new RegExp(
|
|
28623
29334
|
`${escapeRegex(START_MARKER)}[\\s\\S]*?${escapeRegex(END_MARKER)}\\n?`,
|
|
28624
29335
|
"g"
|
|
28625
29336
|
);
|
|
28626
|
-
if (
|
|
29337
|
+
if (blockRegex2.test(content)) {
|
|
28627
29338
|
content = content.replace(
|
|
28628
29339
|
new RegExp(
|
|
28629
29340
|
`${escapeRegex(START_MARKER)}[\\s\\S]*?${escapeRegex(END_MARKER)}\\n?`,
|
|
@@ -28813,6 +29524,7 @@ function resolveTeamComposition(team, override) {
|
|
|
28813
29524
|
}
|
|
28814
29525
|
|
|
28815
29526
|
// src/lib/installer.ts
|
|
29527
|
+
init_src();
|
|
28816
29528
|
var require2 = createRequire(import.meta.url);
|
|
28817
29529
|
function ensureDir(dir) {
|
|
28818
29530
|
if (!existsSync3(dir)) {
|
|
@@ -28921,27 +29633,6 @@ function installCommandsForTool(tool, commandsSource, targetDir) {
|
|
|
28921
29633
|
return false;
|
|
28922
29634
|
}
|
|
28923
29635
|
}
|
|
28924
|
-
var BUILTIN_ICON_MAP = {
|
|
28925
|
-
architect: "blocks",
|
|
28926
|
-
fullstack: "layers",
|
|
28927
|
-
reliability: "activity",
|
|
28928
|
-
"staff-engineer": "compass",
|
|
28929
|
-
principal: "crown",
|
|
28930
|
-
frontend: "layout",
|
|
28931
|
-
backend: "server",
|
|
28932
|
-
infrastructure: "cloud",
|
|
28933
|
-
performance: "gauge",
|
|
28934
|
-
accessibility: "accessibility",
|
|
28935
|
-
data: "database",
|
|
28936
|
-
devops: "rocket",
|
|
28937
|
-
dx: "terminal",
|
|
28938
|
-
mobile: "smartphone",
|
|
28939
|
-
security: "shield-alert",
|
|
28940
|
-
quality: "sparkles",
|
|
28941
|
-
testing: "test-tubes",
|
|
28942
|
-
ai: "bot",
|
|
28943
|
-
"docs-writer": "file-text"
|
|
28944
|
-
};
|
|
28945
29636
|
var HOLISTIC_IDS = /* @__PURE__ */ new Set(["architect", "fullstack", "reliability", "staff-engineer", "principal"]);
|
|
28946
29637
|
var SPECIALIST_IDS = /* @__PURE__ */ new Set([
|
|
28947
29638
|
"frontend",
|
|
@@ -29044,7 +29735,7 @@ function generateReviewersMeta(reviewersDir, configPath) {
|
|
|
29044
29735
|
id,
|
|
29045
29736
|
name: extractReviewerName(content),
|
|
29046
29737
|
tier,
|
|
29047
|
-
icon:
|
|
29738
|
+
icon: defaultIconFor(id, tier),
|
|
29048
29739
|
description: extractReviewerDescription(content),
|
|
29049
29740
|
focus_areas: extractFocusAreas(content),
|
|
29050
29741
|
is_default: defaultTeamIds.has(id),
|
|
@@ -29145,7 +29836,9 @@ function installForTool(tool, targetDir) {
|
|
|
29145
29836
|
if (meta) {
|
|
29146
29837
|
writeFileSync3(metaPath, JSON.stringify(meta, null, 2) + "\n");
|
|
29147
29838
|
}
|
|
29148
|
-
} catch {
|
|
29839
|
+
} catch (err) {
|
|
29840
|
+
const msg = err instanceof Error ? err.message : "unknown error";
|
|
29841
|
+
warnings.push(`Could not generate reviewers-meta.json: ${msg}`);
|
|
29149
29842
|
}
|
|
29150
29843
|
const commandsOk = installCommandsForTool(tool, commandsSource, targetDir);
|
|
29151
29844
|
if (!commandsOk) {
|
|
@@ -29179,12 +29872,14 @@ function detectInstalledTools(targetDir, tools) {
|
|
|
29179
29872
|
}
|
|
29180
29873
|
|
|
29181
29874
|
// src/lib/injector.ts
|
|
29182
|
-
import { existsSync as existsSync4, readFileSync as readFileSync5, writeFileSync as writeFileSync4 } from "node:fs";
|
|
29183
|
-
import { join as join4 } from "node:path";
|
|
29184
|
-
var
|
|
29185
|
-
var
|
|
29186
|
-
|
|
29187
|
-
|
|
29875
|
+
import { existsSync as existsSync4, mkdirSync as mkdirSync2, readFileSync as readFileSync5, writeFileSync as writeFileSync4 } from "node:fs";
|
|
29876
|
+
import { dirname as dirname2, join as join4 } from "node:path";
|
|
29877
|
+
var AGENTS_MD = { path: "AGENTS.md", format: "markdown" };
|
|
29878
|
+
var MARKERS = {
|
|
29879
|
+
markdown: { start: "<!-- OCR:START -->", end: "<!-- OCR:END -->" },
|
|
29880
|
+
plaintext: { start: "# OCR:START", end: "# OCR:END" }
|
|
29881
|
+
};
|
|
29882
|
+
var OCR_INSTRUCTION_BODY = `## Open Code Review Instructions
|
|
29188
29883
|
|
|
29189
29884
|
These instructions are for AI assistants handling code review in this project.
|
|
29190
29885
|
|
|
@@ -29200,37 +29895,95 @@ Use \`.ocr/skills/SKILL.md\` to learn:
|
|
|
29200
29895
|
- Available reviewer personas and their focus areas
|
|
29201
29896
|
- Session management and output format
|
|
29202
29897
|
|
|
29203
|
-
Keep this managed block so \`ocr init\` can refresh the instructions
|
|
29204
|
-
|
|
29205
|
-
|
|
29206
|
-
|
|
29898
|
+
Keep this managed block so \`ocr init\` can refresh the instructions.`;
|
|
29899
|
+
function buildBlock(format) {
|
|
29900
|
+
const { start, end } = MARKERS[format];
|
|
29901
|
+
return `${start}
|
|
29902
|
+
${OCR_INSTRUCTION_BODY}
|
|
29903
|
+
${end}`;
|
|
29904
|
+
}
|
|
29905
|
+
function escapeRegex2(str) {
|
|
29906
|
+
return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
29907
|
+
}
|
|
29908
|
+
function blockRegex(format) {
|
|
29909
|
+
const { start, end } = MARKERS[format];
|
|
29910
|
+
return new RegExp(
|
|
29911
|
+
`${escapeRegex2(start)}[\\s\\S]*?${escapeRegex2(end)}\\n?`,
|
|
29912
|
+
"g"
|
|
29913
|
+
);
|
|
29914
|
+
}
|
|
29915
|
+
function injectOcrInstructions(filePath, format = "markdown") {
|
|
29207
29916
|
try {
|
|
29917
|
+
mkdirSync2(dirname2(filePath), { recursive: true });
|
|
29208
29918
|
let content = existsSync4(filePath) ? readFileSync5(filePath, "utf-8") : "";
|
|
29209
|
-
|
|
29210
|
-
`${escapeRegex2(START_MARKER2)}[\\s\\S]*?${escapeRegex2(END_MARKER2)}\\n?`,
|
|
29211
|
-
"g"
|
|
29212
|
-
);
|
|
29213
|
-
content = content.replace(regex2, "");
|
|
29919
|
+
content = content.replace(blockRegex(format), "");
|
|
29214
29920
|
content = content.trim();
|
|
29215
29921
|
if (content.length > 0) {
|
|
29216
29922
|
content += "\n\n";
|
|
29217
29923
|
}
|
|
29218
|
-
content +=
|
|
29924
|
+
content += buildBlock(format) + "\n";
|
|
29219
29925
|
writeFileSync4(filePath, content);
|
|
29220
29926
|
return true;
|
|
29221
29927
|
} catch {
|
|
29222
29928
|
return false;
|
|
29223
29929
|
}
|
|
29224
29930
|
}
|
|
29225
|
-
function
|
|
29226
|
-
|
|
29931
|
+
function resolveTargets(selectedTools) {
|
|
29932
|
+
const targets = /* @__PURE__ */ new Map();
|
|
29933
|
+
targets.set(AGENTS_MD.path, AGENTS_MD);
|
|
29934
|
+
for (const tool of selectedTools) {
|
|
29935
|
+
for (const file of tool.instructionFiles ?? []) {
|
|
29936
|
+
targets.set(file.path, file);
|
|
29937
|
+
}
|
|
29938
|
+
}
|
|
29939
|
+
return [...targets.values()];
|
|
29227
29940
|
}
|
|
29228
|
-
function
|
|
29229
|
-
|
|
29230
|
-
|
|
29231
|
-
|
|
29232
|
-
const
|
|
29233
|
-
|
|
29941
|
+
function plannedInstructionFiles(selectedTools) {
|
|
29942
|
+
return resolveTargets(selectedTools).map((t) => t.path);
|
|
29943
|
+
}
|
|
29944
|
+
function injectIntoProjectFiles(targetDir, selectedTools) {
|
|
29945
|
+
const written = [];
|
|
29946
|
+
const failed = [];
|
|
29947
|
+
for (const target of resolveTargets(selectedTools)) {
|
|
29948
|
+
const ok = injectOcrInstructions(join4(targetDir, target.path), target.format);
|
|
29949
|
+
(ok ? written : failed).push(target.path);
|
|
29950
|
+
}
|
|
29951
|
+
return { written, failed };
|
|
29952
|
+
}
|
|
29953
|
+
function hasOcrInstructions(filePath) {
|
|
29954
|
+
if (!existsSync4(filePath)) {
|
|
29955
|
+
return false;
|
|
29956
|
+
}
|
|
29957
|
+
const content = readFileSync5(filePath, "utf-8");
|
|
29958
|
+
return Object.values(MARKERS).some(
|
|
29959
|
+
(m) => content.includes(m.start) && content.includes(m.end)
|
|
29960
|
+
);
|
|
29961
|
+
}
|
|
29962
|
+
function findStaleInstructionFiles(targetDir, writtenPaths) {
|
|
29963
|
+
const written = new Set(writtenPaths);
|
|
29964
|
+
const candidates = /* @__PURE__ */ new Set();
|
|
29965
|
+
for (const tool of AI_TOOLS) {
|
|
29966
|
+
for (const file of tool.instructionFiles ?? []) {
|
|
29967
|
+
candidates.add(file.path);
|
|
29968
|
+
}
|
|
29969
|
+
}
|
|
29970
|
+
const stale = [];
|
|
29971
|
+
for (const path2 of candidates) {
|
|
29972
|
+
if (written.has(path2)) continue;
|
|
29973
|
+
if (hasOcrInstructions(join4(targetDir, path2))) {
|
|
29974
|
+
stale.push(path2);
|
|
29975
|
+
}
|
|
29976
|
+
}
|
|
29977
|
+
return stale;
|
|
29978
|
+
}
|
|
29979
|
+
function formatStaleWarnings(stale, mode) {
|
|
29980
|
+
if (mode === "dry-run") {
|
|
29981
|
+
return stale.map((path2) => `${path2} (stale OCR block \u2014 left untouched)`);
|
|
29982
|
+
}
|
|
29983
|
+
const owner = mode === "init" ? "installed" : "configured";
|
|
29984
|
+
return stale.map(
|
|
29985
|
+
(path2) => `${path2} still has an OCR block but no ${owner} tool uses it \u2014 remove it manually if unneeded.`
|
|
29986
|
+
);
|
|
29234
29987
|
}
|
|
29235
29988
|
|
|
29236
29989
|
// src/lib/banner.ts
|
|
@@ -29333,29 +30086,10 @@ ${hint}
|
|
|
29333
30086
|
}
|
|
29334
30087
|
|
|
29335
30088
|
// src/lib/version.ts
|
|
29336
|
-
var CLI_VERSION = true ? "2.
|
|
29337
|
-
|
|
29338
|
-
// ../shared/platform/src/index.ts
|
|
29339
|
-
import { pathToFileURL } from "node:url";
|
|
29340
|
-
import {
|
|
29341
|
-
execFile,
|
|
29342
|
-
execFileSync,
|
|
29343
|
-
spawn as spawn2
|
|
29344
|
-
} from "node:child_process";
|
|
29345
|
-
import { promisify } from "node:util";
|
|
29346
|
-
var execFilePromise = promisify(execFile);
|
|
29347
|
-
var isWindows = process.platform === "win32";
|
|
29348
|
-
async function importModule(absolutePath) {
|
|
29349
|
-
return import(pathToFileURL(absolutePath).href);
|
|
29350
|
-
}
|
|
29351
|
-
function execBinary(binary, args, opts) {
|
|
29352
|
-
return execFileSync(binary, args, {
|
|
29353
|
-
...opts,
|
|
29354
|
-
shell: isWindows
|
|
29355
|
-
});
|
|
29356
|
-
}
|
|
30089
|
+
var CLI_VERSION = true ? "2.2.0" : createRequire(import.meta.url)("../../package.json").version;
|
|
29357
30090
|
|
|
29358
30091
|
// src/lib/deps.ts
|
|
30092
|
+
init_src();
|
|
29359
30093
|
var CATEGORY_ORDER = ["core", "ai-cli", "github"];
|
|
29360
30094
|
var CATEGORY_INFO = {
|
|
29361
30095
|
core: { label: "Core", hint: "" },
|
|
@@ -29527,7 +30261,7 @@ function printCapabilities(result) {
|
|
|
29527
30261
|
}
|
|
29528
30262
|
|
|
29529
30263
|
// src/commands/init.ts
|
|
29530
|
-
var initCommand = new Command("init").description("Set up OCR for AI coding environments").option("-t, --tools <tools>", 'Comma-separated tool IDs or "all"').option("--no-inject", "Skip injecting instructions into AGENTS.md
|
|
30264
|
+
var initCommand = new Command("init").description("Set up OCR for AI coding environments").option("-t, --tools <tools>", 'Comma-separated tool IDs or "all"').option("--no-inject", "Skip injecting instructions into project instruction files (AGENTS.md + each tool's native file)").action(async (options) => {
|
|
29531
30265
|
printBanner();
|
|
29532
30266
|
const depResult = checkDependencies();
|
|
29533
30267
|
printDepChecks(depResult);
|
|
@@ -29618,17 +30352,19 @@ var initCommand = new Command("init").description("Set up OCR for AI coding envi
|
|
|
29618
30352
|
const injectSpinner = ora(
|
|
29619
30353
|
"Injecting OCR instructions into project files..."
|
|
29620
30354
|
).start();
|
|
29621
|
-
const
|
|
30355
|
+
const installedTools = successful.map((r) => r.tool);
|
|
30356
|
+
const injectResults = injectIntoProjectFiles(targetDir, installedTools);
|
|
29622
30357
|
injectSpinner.stop();
|
|
29623
|
-
if (injectResults.
|
|
30358
|
+
if (injectResults.written.length > 0) {
|
|
29624
30359
|
console.log(source_default.green("\u2713 OCR instructions injected"));
|
|
29625
|
-
|
|
29626
|
-
console.log(` ${source_default.green("\u2713")}
|
|
29627
|
-
}
|
|
29628
|
-
if (injectResults.claudeMd) {
|
|
29629
|
-
console.log(` ${source_default.green("\u2713")} CLAUDE.md`);
|
|
30360
|
+
for (const path2 of injectResults.written) {
|
|
30361
|
+
console.log(` ${source_default.green("\u2713")} ${path2}`);
|
|
29630
30362
|
}
|
|
29631
30363
|
}
|
|
30364
|
+
const stale = findStaleInstructionFiles(targetDir, injectResults.written);
|
|
30365
|
+
for (const warning of formatStaleWarnings(stale, "init")) {
|
|
30366
|
+
console.log(source_default.yellow(` \u26A0 ${warning}`));
|
|
30367
|
+
}
|
|
29632
30368
|
}
|
|
29633
30369
|
console.log();
|
|
29634
30370
|
console.log(source_default.bold("Next steps:"));
|
|
@@ -29816,10 +30552,10 @@ var ReaddirpStream = class extends Readable {
|
|
|
29816
30552
|
}
|
|
29817
30553
|
async _formatEntry(dirent, path2) {
|
|
29818
30554
|
let entry;
|
|
29819
|
-
const
|
|
30555
|
+
const basename9 = this._isDirent ? dirent.name : dirent;
|
|
29820
30556
|
try {
|
|
29821
|
-
const fullPath = presolve(pjoin(path2,
|
|
29822
|
-
entry = { path: prelative(this._root, fullPath), fullPath, basename:
|
|
30557
|
+
const fullPath = presolve(pjoin(path2, basename9));
|
|
30558
|
+
entry = { path: prelative(this._root, fullPath), fullPath, basename: basename9 };
|
|
29823
30559
|
entry[this._statsProp] = this._isDirent ? dirent : await this._stat(fullPath);
|
|
29824
30560
|
} catch (err) {
|
|
29825
30561
|
this._onError(err);
|
|
@@ -30358,9 +31094,9 @@ var NodeFsHandler = class {
|
|
|
30358
31094
|
_watchWithNodeFs(path2, listener) {
|
|
30359
31095
|
const opts = this.fsw.options;
|
|
30360
31096
|
const directory = sysPath.dirname(path2);
|
|
30361
|
-
const
|
|
31097
|
+
const basename9 = sysPath.basename(path2);
|
|
30362
31098
|
const parent = this.fsw._getWatchedDir(directory);
|
|
30363
|
-
parent.add(
|
|
31099
|
+
parent.add(basename9);
|
|
30364
31100
|
const absolutePath = sysPath.resolve(path2);
|
|
30365
31101
|
const options = {
|
|
30366
31102
|
persistent: opts.persistent
|
|
@@ -30370,7 +31106,7 @@ var NodeFsHandler = class {
|
|
|
30370
31106
|
let closer;
|
|
30371
31107
|
if (opts.usePolling) {
|
|
30372
31108
|
const enableBin = opts.interval !== opts.binaryInterval;
|
|
30373
|
-
options.interval = enableBin && isBinaryPath(
|
|
31109
|
+
options.interval = enableBin && isBinaryPath(basename9) ? opts.binaryInterval : opts.interval;
|
|
30374
31110
|
closer = setFsWatchFileListener(path2, absolutePath, options, {
|
|
30375
31111
|
listener,
|
|
30376
31112
|
rawEmitter: this.fsw._emitRaw
|
|
@@ -30392,11 +31128,11 @@ var NodeFsHandler = class {
|
|
|
30392
31128
|
if (this.fsw.closed) {
|
|
30393
31129
|
return;
|
|
30394
31130
|
}
|
|
30395
|
-
const
|
|
30396
|
-
const
|
|
30397
|
-
const parent = this.fsw._getWatchedDir(
|
|
31131
|
+
const dirname10 = sysPath.dirname(file);
|
|
31132
|
+
const basename9 = sysPath.basename(file);
|
|
31133
|
+
const parent = this.fsw._getWatchedDir(dirname10);
|
|
30398
31134
|
let prevStats = stats;
|
|
30399
|
-
if (parent.has(
|
|
31135
|
+
if (parent.has(basename9))
|
|
30400
31136
|
return;
|
|
30401
31137
|
const listener = async (path2, newStats) => {
|
|
30402
31138
|
if (!this.fsw._throttle(THROTTLE_MODE_WATCH, file, 5))
|
|
@@ -30421,9 +31157,9 @@ var NodeFsHandler = class {
|
|
|
30421
31157
|
prevStats = newStats2;
|
|
30422
31158
|
}
|
|
30423
31159
|
} catch (error) {
|
|
30424
|
-
this.fsw._remove(
|
|
31160
|
+
this.fsw._remove(dirname10, basename9);
|
|
30425
31161
|
}
|
|
30426
|
-
} else if (parent.has(
|
|
31162
|
+
} else if (parent.has(basename9)) {
|
|
30427
31163
|
const at = newStats.atimeMs;
|
|
30428
31164
|
const mt = newStats.mtimeMs;
|
|
30429
31165
|
if (!at || at <= mt || mt !== prevStats.mtimeMs) {
|
|
@@ -31354,8 +32090,8 @@ function watch(paths, options = {}) {
|
|
|
31354
32090
|
}
|
|
31355
32091
|
|
|
31356
32092
|
// src/commands/progress.ts
|
|
31357
|
-
import { existsSync as
|
|
31358
|
-
import { join as
|
|
32093
|
+
import { existsSync as existsSync14, readdirSync as readdirSync6, statSync as statSync3 } from "node:fs";
|
|
32094
|
+
import { join as join16, basename as basename8 } from "node:path";
|
|
31359
32095
|
|
|
31360
32096
|
// ../../node_modules/.pnpm/log-update@7.0.2/node_modules/log-update/index.js
|
|
31361
32097
|
import process12 from "node:process";
|
|
@@ -32223,7 +32959,7 @@ var log_update_default = logUpdate;
|
|
|
32223
32959
|
var logUpdateStderr = createLogUpdate(process12.stderr);
|
|
32224
32960
|
|
|
32225
32961
|
// src/lib/guards.ts
|
|
32226
|
-
import { existsSync as existsSync6, mkdirSync as
|
|
32962
|
+
import { existsSync as existsSync6, mkdirSync as mkdirSync3 } from "node:fs";
|
|
32227
32963
|
import { join as join8 } from "node:path";
|
|
32228
32964
|
function checkOcrSetup(targetDir) {
|
|
32229
32965
|
const ocrDir = join8(targetDir, ".ocr");
|
|
@@ -32269,7 +33005,7 @@ function requireOcrSetup(targetDir) {
|
|
|
32269
33005
|
function ensureSessionsDir(targetDir) {
|
|
32270
33006
|
const sessionsDir = join8(targetDir, ".ocr", "sessions");
|
|
32271
33007
|
if (!existsSync6(sessionsDir)) {
|
|
32272
|
-
|
|
33008
|
+
mkdirSync3(sessionsDir, { recursive: true });
|
|
32273
33009
|
}
|
|
32274
33010
|
return sessionsDir;
|
|
32275
33011
|
}
|
|
@@ -32991,15 +33727,15 @@ function debounce(fn, delay) {
|
|
|
32991
33727
|
};
|
|
32992
33728
|
}
|
|
32993
33729
|
function findLatestActiveSession(sessionsDir) {
|
|
32994
|
-
if (!
|
|
33730
|
+
if (!existsSync14(sessionsDir)) {
|
|
32995
33731
|
return null;
|
|
32996
33732
|
}
|
|
32997
|
-
const sessions =
|
|
32998
|
-
const sessionPath =
|
|
32999
|
-
return
|
|
33733
|
+
const sessions = readdirSync6(sessionsDir).filter((name) => {
|
|
33734
|
+
const sessionPath = join16(sessionsDir, name);
|
|
33735
|
+
return statSync3(sessionPath).isDirectory();
|
|
33000
33736
|
}).sort().reverse();
|
|
33001
33737
|
for (const session of sessions) {
|
|
33002
|
-
const sessionPath =
|
|
33738
|
+
const sessionPath = join16(sessionsDir, session);
|
|
33003
33739
|
if (isSessionActive(sessionPath)) {
|
|
33004
33740
|
return session;
|
|
33005
33741
|
}
|
|
@@ -33014,8 +33750,8 @@ function getStrategyForSession(sessionPath, explicitWorkflow) {
|
|
|
33014
33750
|
return getStrategy(workflowType) ?? null;
|
|
33015
33751
|
}
|
|
33016
33752
|
async function initProgressDb(ocrDir) {
|
|
33017
|
-
const dbPath =
|
|
33018
|
-
if (!
|
|
33753
|
+
const dbPath = join16(ocrDir, "data", "ocr.db");
|
|
33754
|
+
if (!existsSync14(dbPath)) {
|
|
33019
33755
|
return;
|
|
33020
33756
|
}
|
|
33021
33757
|
try {
|
|
@@ -33040,11 +33776,11 @@ var progressCommand = new Command("progress").description("Watch real-time progr
|
|
|
33040
33776
|
const targetDir = process.cwd();
|
|
33041
33777
|
requireOcrSetup(targetDir);
|
|
33042
33778
|
const sessionsDir = ensureSessionsDir(targetDir);
|
|
33043
|
-
const ocrDir =
|
|
33779
|
+
const ocrDir = join16(targetDir, ".ocr");
|
|
33044
33780
|
await initProgressDb(ocrDir);
|
|
33045
33781
|
if (options.session) {
|
|
33046
|
-
const sessionPath =
|
|
33047
|
-
if (!
|
|
33782
|
+
const sessionPath = join16(sessionsDir, options.session);
|
|
33783
|
+
if (!existsSync14(sessionPath)) {
|
|
33048
33784
|
console.error(source_default.red(`Session not found: ${options.session}`));
|
|
33049
33785
|
process.exit(1);
|
|
33050
33786
|
}
|
|
@@ -33105,7 +33841,7 @@ var progressCommand = new Command("progress").description("Watch real-time progr
|
|
|
33105
33841
|
return;
|
|
33106
33842
|
}
|
|
33107
33843
|
let currentSession = findLatestActiveSession(sessionsDir);
|
|
33108
|
-
let currentSessionPath = currentSession ?
|
|
33844
|
+
let currentSessionPath = currentSession ? join16(sessionsDir, currentSession) : null;
|
|
33109
33845
|
let sessionWatcher = null;
|
|
33110
33846
|
const preservedStartTimes = {
|
|
33111
33847
|
review: void 0,
|
|
@@ -33113,11 +33849,11 @@ var progressCommand = new Command("progress").description("Watch real-time progr
|
|
|
33113
33849
|
};
|
|
33114
33850
|
let currentStrategy = null;
|
|
33115
33851
|
const updateDisplayImpl = () => {
|
|
33116
|
-
if (!currentSessionPath || !
|
|
33852
|
+
if (!currentSessionPath || !existsSync14(currentSessionPath) || !isSessionActive(currentSessionPath)) {
|
|
33117
33853
|
const latestActive = findLatestActiveSession(sessionsDir);
|
|
33118
33854
|
if (latestActive && latestActive !== currentSession) {
|
|
33119
33855
|
currentSession = latestActive;
|
|
33120
|
-
currentSessionPath =
|
|
33856
|
+
currentSessionPath = join16(sessionsDir, latestActive);
|
|
33121
33857
|
preservedStartTimes.review = void 0;
|
|
33122
33858
|
preservedStartTimes.map = void 0;
|
|
33123
33859
|
currentStrategy = null;
|
|
@@ -33130,7 +33866,7 @@ var progressCommand = new Command("progress").description("Watch real-time progr
|
|
|
33130
33866
|
currentStrategy = null;
|
|
33131
33867
|
}
|
|
33132
33868
|
}
|
|
33133
|
-
if (currentSessionPath &&
|
|
33869
|
+
if (currentSessionPath && existsSync14(currentSessionPath)) {
|
|
33134
33870
|
if (!options.workflow) {
|
|
33135
33871
|
const activeWorkflows = detectActiveWorkflows(currentSessionPath);
|
|
33136
33872
|
if (activeWorkflows.length > 1) {
|
|
@@ -33184,17 +33920,17 @@ var progressCommand = new Command("progress").description("Watch real-time progr
|
|
|
33184
33920
|
watchSession(currentSessionPath);
|
|
33185
33921
|
}
|
|
33186
33922
|
const timerInterval = setInterval(updateDisplay, 1e3);
|
|
33187
|
-
const watchDir =
|
|
33923
|
+
const watchDir = existsSync14(ocrDir) ? ocrDir : targetDir;
|
|
33188
33924
|
const dirWatcher = watch(watchDir, {
|
|
33189
33925
|
persistent: true,
|
|
33190
33926
|
ignoreInitial: true,
|
|
33191
33927
|
depth: 3
|
|
33192
33928
|
});
|
|
33193
33929
|
dirWatcher.on("addDir", (dirPath) => {
|
|
33194
|
-
const parentDir =
|
|
33195
|
-
const isDirectChild = parentDir.endsWith("sessions") || parentDir.endsWith(
|
|
33930
|
+
const parentDir = join16(dirPath, "..");
|
|
33931
|
+
const isDirectChild = parentDir.endsWith("sessions") || parentDir.endsWith(join16(".ocr", "sessions"));
|
|
33196
33932
|
if (isDirectChild && !dirPath.endsWith("sessions")) {
|
|
33197
|
-
const newSession =
|
|
33933
|
+
const newSession = basename8(dirPath);
|
|
33198
33934
|
currentSession = newSession;
|
|
33199
33935
|
currentSessionPath = dirPath;
|
|
33200
33936
|
preservedStartTimes.review = void 0;
|
|
@@ -33233,7 +33969,7 @@ function renderGenericWaiting() {
|
|
|
33233
33969
|
}
|
|
33234
33970
|
function renderCombinedProgress(sessionPath, preservedStartTimes, ocrDir) {
|
|
33235
33971
|
const lines = [];
|
|
33236
|
-
const session =
|
|
33972
|
+
const session = basename8(sessionPath);
|
|
33237
33973
|
lines.push("");
|
|
33238
33974
|
lines.push(
|
|
33239
33975
|
source_default.bold.white(" Open Code Review") + source_default.yellow(" \xB7 Parallel Workflows")
|
|
@@ -33294,21 +34030,21 @@ function renderCombinedProgress(sessionPath, preservedStartTimes, ocrDir) {
|
|
|
33294
34030
|
}
|
|
33295
34031
|
|
|
33296
34032
|
// src/commands/state.ts
|
|
33297
|
-
import { existsSync as
|
|
33298
|
-
import { join as
|
|
34033
|
+
import { existsSync as existsSync16, mkdirSync as mkdirSync7, readFileSync as readFileSync11 } from "node:fs";
|
|
34034
|
+
import { join as join18 } from "node:path";
|
|
33299
34035
|
|
|
33300
34036
|
// src/lib/state/index.ts
|
|
33301
34037
|
init_db();
|
|
33302
34038
|
init_exit_codes();
|
|
33303
34039
|
import {
|
|
33304
|
-
existsSync as
|
|
33305
|
-
mkdirSync as
|
|
33306
|
-
readdirSync as
|
|
34040
|
+
existsSync as existsSync15,
|
|
34041
|
+
mkdirSync as mkdirSync6,
|
|
34042
|
+
readdirSync as readdirSync7,
|
|
33307
34043
|
readFileSync as readFileSync10,
|
|
33308
|
-
statSync as
|
|
34044
|
+
statSync as statSync4,
|
|
33309
34045
|
writeFileSync as writeFileSync7
|
|
33310
34046
|
} from "node:fs";
|
|
33311
|
-
import { join as
|
|
34047
|
+
import { join as join17 } from "node:path";
|
|
33312
34048
|
|
|
33313
34049
|
// src/lib/state/phase-graph.ts
|
|
33314
34050
|
init_exit_codes();
|
|
@@ -33616,9 +34352,9 @@ function deriveNextRound(db, sessionId, fallbackRound) {
|
|
|
33616
34352
|
}
|
|
33617
34353
|
function hasArtifacts(dir) {
|
|
33618
34354
|
try {
|
|
33619
|
-
for (const entry of
|
|
34355
|
+
for (const entry of readdirSync7(dir, { withFileTypes: true })) {
|
|
33620
34356
|
if (entry.isDirectory()) {
|
|
33621
|
-
if (hasArtifacts(
|
|
34357
|
+
if (hasArtifacts(join17(dir, entry.name))) return true;
|
|
33622
34358
|
} else if (/\.(md|json)$/.test(entry.name)) {
|
|
33623
34359
|
return true;
|
|
33624
34360
|
}
|
|
@@ -33629,7 +34365,7 @@ function hasArtifacts(dir) {
|
|
|
33629
34365
|
}
|
|
33630
34366
|
function readJsonFromSource(params) {
|
|
33631
34367
|
if (params.source === "file") {
|
|
33632
|
-
if (!
|
|
34368
|
+
if (!existsSync15(params.filePath)) {
|
|
33633
34369
|
throw new StateError(STATE_EXIT.NOT_FOUND, `File not found: ${params.filePath}`);
|
|
33634
34370
|
}
|
|
33635
34371
|
return readFileSync10(params.filePath, "utf-8");
|
|
@@ -33956,7 +34692,7 @@ async function stateCompleteRound(params) {
|
|
|
33956
34692
|
}
|
|
33957
34693
|
const resolved = resolveSession(db, params.sessionId);
|
|
33958
34694
|
const roundNumber = params.round ?? resolved.current_round;
|
|
33959
|
-
const roundMetaPath =
|
|
34695
|
+
const roundMetaPath = join17(
|
|
33960
34696
|
resolved.session_dir,
|
|
33961
34697
|
"rounds",
|
|
33962
34698
|
`round-${roundNumber}`,
|
|
@@ -33977,8 +34713,8 @@ async function stateCompleteRound(params) {
|
|
|
33977
34713
|
);
|
|
33978
34714
|
}
|
|
33979
34715
|
if (params.requireFinal) {
|
|
33980
|
-
const finalPath =
|
|
33981
|
-
if (!
|
|
34716
|
+
const finalPath = join17(resolved.session_dir, "rounds", `round-${roundNumber}`, "final.md");
|
|
34717
|
+
if (!existsSync15(finalPath)) {
|
|
33982
34718
|
throw new StateError(
|
|
33983
34719
|
STATE_EXIT.INVARIANT_UNMET,
|
|
33984
34720
|
`Cannot complete round: --require-final set but ${finalPath} is missing.`
|
|
@@ -33987,8 +34723,8 @@ async function stateCompleteRound(params) {
|
|
|
33987
34723
|
}
|
|
33988
34724
|
let metaPath;
|
|
33989
34725
|
if (params.source === "stdin") {
|
|
33990
|
-
const roundDir =
|
|
33991
|
-
|
|
34726
|
+
const roundDir = join17(resolved.session_dir, "rounds", `round-${roundNumber}`);
|
|
34727
|
+
mkdirSync6(roundDir, { recursive: true });
|
|
33992
34728
|
metaPath = roundMetaPath;
|
|
33993
34729
|
writeFileSync7(metaPath, JSON.stringify(meta, null, 2));
|
|
33994
34730
|
}
|
|
@@ -34042,7 +34778,7 @@ async function stateCompleteMap(params) {
|
|
|
34042
34778
|
}
|
|
34043
34779
|
const resolved = resolveSession(db, params.sessionId);
|
|
34044
34780
|
const mapRunNumber = params.mapRun ?? resolved.current_map_run;
|
|
34045
|
-
const mapMetaPath =
|
|
34781
|
+
const mapMetaPath = join17(
|
|
34046
34782
|
resolved.session_dir,
|
|
34047
34783
|
"map",
|
|
34048
34784
|
"runs",
|
|
@@ -34065,8 +34801,8 @@ async function stateCompleteMap(params) {
|
|
|
34065
34801
|
}
|
|
34066
34802
|
let metaPath;
|
|
34067
34803
|
if (params.source === "stdin") {
|
|
34068
|
-
const runDir =
|
|
34069
|
-
|
|
34804
|
+
const runDir = join17(resolved.session_dir, "map", "runs", `run-${mapRunNumber}`);
|
|
34805
|
+
mkdirSync6(runDir, { recursive: true });
|
|
34070
34806
|
metaPath = mapMetaPath;
|
|
34071
34807
|
writeFileSync7(metaPath, JSON.stringify(meta, null, 2));
|
|
34072
34808
|
}
|
|
@@ -34151,17 +34887,17 @@ async function stateStatus(ocrDir, sessionId) {
|
|
|
34151
34887
|
}
|
|
34152
34888
|
async function stateSync(ocrDir) {
|
|
34153
34889
|
const db = await ensureDatabase(ocrDir);
|
|
34154
|
-
const sessionsRoot =
|
|
34155
|
-
if (!
|
|
34890
|
+
const sessionsRoot = join17(ocrDir, "sessions");
|
|
34891
|
+
if (!existsSync15(sessionsRoot)) {
|
|
34156
34892
|
return 0;
|
|
34157
34893
|
}
|
|
34158
|
-
const entries =
|
|
34159
|
-
const fullPath =
|
|
34160
|
-
return
|
|
34894
|
+
const entries = readdirSync7(sessionsRoot).filter((name) => {
|
|
34895
|
+
const fullPath = join17(sessionsRoot, name);
|
|
34896
|
+
return statSync4(fullPath).isDirectory();
|
|
34161
34897
|
});
|
|
34162
34898
|
let synced = 0;
|
|
34163
34899
|
for (const dirName of entries) {
|
|
34164
|
-
const dirPath =
|
|
34900
|
+
const dirPath = join17(sessionsRoot, dirName);
|
|
34165
34901
|
const existing = getSession(db, dirName);
|
|
34166
34902
|
if (existing) {
|
|
34167
34903
|
continue;
|
|
@@ -34169,8 +34905,8 @@ async function stateSync(ocrDir) {
|
|
|
34169
34905
|
if (!hasArtifacts(dirPath)) {
|
|
34170
34906
|
continue;
|
|
34171
34907
|
}
|
|
34172
|
-
const hasRoundsDir =
|
|
34173
|
-
const hasMapDir =
|
|
34908
|
+
const hasRoundsDir = existsSync15(join17(dirPath, "rounds"));
|
|
34909
|
+
const hasMapDir = existsSync15(join17(dirPath, "map"));
|
|
34174
34910
|
const workflowType = hasMapDir && !hasRoundsDir ? "map" : "review";
|
|
34175
34911
|
const branchMatch = dirName.match(/^\d{4}-\d{2}-\d{2}-(.+)$/);
|
|
34176
34912
|
const branch = branchMatch?.[1] ?? dirName;
|
|
@@ -34179,14 +34915,14 @@ async function stateSync(ocrDir) {
|
|
|
34179
34915
|
let inferredRound = 1;
|
|
34180
34916
|
let inferredMapRun = 1;
|
|
34181
34917
|
if (workflowType === "review") {
|
|
34182
|
-
const roundsDir =
|
|
34183
|
-
if (
|
|
34184
|
-
const roundDirs =
|
|
34918
|
+
const roundsDir = join17(dirPath, "rounds");
|
|
34919
|
+
if (existsSync15(roundsDir)) {
|
|
34920
|
+
const roundDirs = readdirSync7(roundsDir).filter((d) => /^round-\d+$/.test(d)).map((d) => parseInt(d.replace("round-", ""), 10)).filter((n) => Number.isFinite(n)).sort((a, b) => a - b);
|
|
34185
34921
|
const latestRoundNum = roundDirs[roundDirs.length - 1];
|
|
34186
34922
|
if (latestRoundNum !== void 0) {
|
|
34187
34923
|
inferredRound = latestRoundNum;
|
|
34188
|
-
if (
|
|
34189
|
-
|
|
34924
|
+
if (existsSync15(
|
|
34925
|
+
join17(roundsDir, `round-${latestRoundNum}`, "final.md")
|
|
34190
34926
|
)) {
|
|
34191
34927
|
inferredPhase = "complete";
|
|
34192
34928
|
inferredPhaseNumber = 8;
|
|
@@ -34194,13 +34930,13 @@ async function stateSync(ocrDir) {
|
|
|
34194
34930
|
}
|
|
34195
34931
|
}
|
|
34196
34932
|
} else if (workflowType === "map") {
|
|
34197
|
-
const runsDir =
|
|
34198
|
-
if (
|
|
34199
|
-
const runDirs =
|
|
34933
|
+
const runsDir = join17(dirPath, "map", "runs");
|
|
34934
|
+
if (existsSync15(runsDir)) {
|
|
34935
|
+
const runDirs = readdirSync7(runsDir).filter((d) => /^run-\d+$/.test(d)).map((d) => parseInt(d.replace("run-", ""), 10)).filter((n) => Number.isFinite(n)).sort((a, b) => a - b);
|
|
34200
34936
|
const latestRunNum = runDirs[runDirs.length - 1];
|
|
34201
34937
|
if (latestRunNum !== void 0) {
|
|
34202
34938
|
inferredMapRun = latestRunNum;
|
|
34203
|
-
if (
|
|
34939
|
+
if (existsSync15(join17(runsDir, `run-${latestRunNum}`, "map.md"))) {
|
|
34204
34940
|
inferredPhase = "complete";
|
|
34205
34941
|
inferredPhaseNumber = 6;
|
|
34206
34942
|
}
|
|
@@ -34238,7 +34974,7 @@ init_command_log();
|
|
|
34238
34974
|
init_db();
|
|
34239
34975
|
init_db();
|
|
34240
34976
|
function readDashboardSpawnMarker(ocrDir) {
|
|
34241
|
-
const path2 =
|
|
34977
|
+
const path2 = join18(ocrDir, "data", "dashboard-active-spawn.json");
|
|
34242
34978
|
let raw;
|
|
34243
34979
|
try {
|
|
34244
34980
|
raw = readFileSync11(path2, "utf-8");
|
|
@@ -34303,7 +35039,7 @@ async function linkDashboardInvocation(ocrDir, sessionId, explicitUid, label) {
|
|
|
34303
35039
|
var showSubcommand = new Command("show").description("Show current session state").option("--session-id <id>", "Session ID (defaults to latest active)").option("--json", "Output as JSON").action(async (options) => {
|
|
34304
35040
|
const targetDir = process.cwd();
|
|
34305
35041
|
requireOcrSetup(targetDir);
|
|
34306
|
-
const ocrDir =
|
|
35042
|
+
const ocrDir = join18(targetDir, ".ocr");
|
|
34307
35043
|
try {
|
|
34308
35044
|
const result = await stateShow(ocrDir, options.sessionId);
|
|
34309
35045
|
if (!result) {
|
|
@@ -34372,7 +35108,7 @@ var showSubcommand = new Command("show").description("Show current session state
|
|
|
34372
35108
|
var syncSubcommand = new Command("sync").description("Rebuild session state from filesystem artifacts").action(async () => {
|
|
34373
35109
|
const targetDir = process.cwd();
|
|
34374
35110
|
requireOcrSetup(targetDir);
|
|
34375
|
-
const ocrDir =
|
|
35111
|
+
const ocrDir = join18(targetDir, ".ocr");
|
|
34376
35112
|
try {
|
|
34377
35113
|
const synced = await stateSync(ocrDir);
|
|
34378
35114
|
console.log(`Synced ${synced} session${synced !== 1 ? "s" : ""} from filesystem.`);
|
|
@@ -34399,7 +35135,7 @@ var reconcileSubcommand = new Command("reconcile").description(
|
|
|
34399
35135
|
).option("--dry-run", "Print the repair plan without writing anything").option("--json", "Output the result as JSON").action(async (options) => {
|
|
34400
35136
|
const targetDir = process.cwd();
|
|
34401
35137
|
requireOcrSetup(targetDir);
|
|
34402
|
-
const ocrDir =
|
|
35138
|
+
const ocrDir = join18(targetDir, ".ocr");
|
|
34403
35139
|
try {
|
|
34404
35140
|
const db = await ensureDatabase(ocrDir);
|
|
34405
35141
|
const result = reconcileLegacyState(db, ocrDir, { dryRun: options.dryRun });
|
|
@@ -34458,9 +35194,9 @@ var beginSubcommand = new Command("begin").description("Start or resume a workfl
|
|
|
34458
35194
|
async (options) => {
|
|
34459
35195
|
const targetDir = process.cwd();
|
|
34460
35196
|
requireOcrSetup(targetDir);
|
|
34461
|
-
const ocrDir =
|
|
34462
|
-
const sessionDir = options.sessionDir ??
|
|
34463
|
-
if (!
|
|
35197
|
+
const ocrDir = join18(targetDir, ".ocr");
|
|
35198
|
+
const sessionDir = options.sessionDir ?? join18(ocrDir, "sessions", options.sessionId);
|
|
35199
|
+
if (!existsSync16(sessionDir)) mkdirSync7(sessionDir, { recursive: true });
|
|
34464
35200
|
try {
|
|
34465
35201
|
const result = await stateBegin({
|
|
34466
35202
|
sessionId: options.sessionId,
|
|
@@ -34482,7 +35218,7 @@ var advanceSubcommand = new Command("advance").description("Advance the workflow
|
|
|
34482
35218
|
async (options) => {
|
|
34483
35219
|
const targetDir = process.cwd();
|
|
34484
35220
|
requireOcrSetup(targetDir);
|
|
34485
|
-
const ocrDir =
|
|
35221
|
+
const ocrDir = join18(targetDir, ".ocr");
|
|
34486
35222
|
try {
|
|
34487
35223
|
const { id: sessionId } = await resolveActiveSession(ocrDir, options.sessionId);
|
|
34488
35224
|
await stateAdvance({
|
|
@@ -34502,7 +35238,7 @@ var completeRoundSubcommand = new Command("complete-round").description("Atomica
|
|
|
34502
35238
|
async (options) => {
|
|
34503
35239
|
const targetDir = process.cwd();
|
|
34504
35240
|
requireOcrSetup(targetDir);
|
|
34505
|
-
const ocrDir =
|
|
35241
|
+
const ocrDir = join18(targetDir, ".ocr");
|
|
34506
35242
|
try {
|
|
34507
35243
|
const base = options.stdin ? { source: "stdin", data: await readStdin() } : options.file ? { source: "file", filePath: options.file } : (() => {
|
|
34508
35244
|
throw new StateError(STATE_EXIT.USAGE, "Provide --stdin or --file with round metadata");
|
|
@@ -34526,7 +35262,7 @@ var completeMapSubcommand = new Command("complete-map").description("Atomically
|
|
|
34526
35262
|
async (options) => {
|
|
34527
35263
|
const targetDir = process.cwd();
|
|
34528
35264
|
requireOcrSetup(targetDir);
|
|
34529
|
-
const ocrDir =
|
|
35265
|
+
const ocrDir = join18(targetDir, ".ocr");
|
|
34530
35266
|
try {
|
|
34531
35267
|
const base = options.stdin ? { source: "stdin", data: await readStdin() } : options.file ? { source: "file", filePath: options.file } : (() => {
|
|
34532
35268
|
throw new StateError(STATE_EXIT.USAGE, "Provide --stdin or --file with map metadata");
|
|
@@ -34548,7 +35284,7 @@ var completeMapSubcommand = new Command("complete-map").description("Atomically
|
|
|
34548
35284
|
var finishSubcommand = new Command("finish").description("Close a workflow (refuses unless the current round/run is complete)").option("--session-id <id>", "Session ID (auto-detects active if omitted)").option("--abort", "Abandon the session \u2014 records a distinct, non-success terminal").action(async (options) => {
|
|
34549
35285
|
const targetDir = process.cwd();
|
|
34550
35286
|
requireOcrSetup(targetDir);
|
|
34551
|
-
const ocrDir =
|
|
35287
|
+
const ocrDir = join18(targetDir, ".ocr");
|
|
34552
35288
|
try {
|
|
34553
35289
|
const { id: sessionId } = await resolveActiveSession(ocrDir, options.sessionId);
|
|
34554
35290
|
await stateClose({ sessionId, ocrDir, abort: options.abort });
|
|
@@ -34560,7 +35296,7 @@ var finishSubcommand = new Command("finish").description("Close a workflow (refu
|
|
|
34560
35296
|
var statusSubcommand = new Command("status").description("Report whether a session is complete and, if not, what's missing").option("--session-id <id>", "Session ID (auto-detects active if omitted)").option("--json", "Output the result as JSON").action(async (options) => {
|
|
34561
35297
|
const targetDir = process.cwd();
|
|
34562
35298
|
requireOcrSetup(targetDir);
|
|
34563
|
-
const ocrDir =
|
|
35299
|
+
const ocrDir = join18(targetDir, ".ocr");
|
|
34564
35300
|
try {
|
|
34565
35301
|
const result = await stateStatus(ocrDir, options.sessionId);
|
|
34566
35302
|
if (options.json) {
|
|
@@ -34589,44 +35325,50 @@ var stateCommand = new Command("state").description("Manage OCR session state").
|
|
|
34589
35325
|
|
|
34590
35326
|
// src/commands/session.ts
|
|
34591
35327
|
import { randomUUID as randomUUID3 } from "node:crypto";
|
|
34592
|
-
import { join as
|
|
35328
|
+
import { join as join20 } from "node:path";
|
|
34593
35329
|
init_db();
|
|
34594
35330
|
|
|
34595
35331
|
// src/lib/runtime-config.ts
|
|
34596
|
-
import { existsSync as
|
|
34597
|
-
import { join as
|
|
35332
|
+
import { existsSync as existsSync17, readFileSync as readFileSync12 } from "node:fs";
|
|
35333
|
+
import { join as join19 } from "node:path";
|
|
34598
35334
|
var DEFAULT_AGENT_HEARTBEAT_SECONDS = 60;
|
|
34599
|
-
function
|
|
34600
|
-
const configPath =
|
|
34601
|
-
if (!
|
|
34602
|
-
return DEFAULT_AGENT_HEARTBEAT_SECONDS;
|
|
34603
|
-
}
|
|
35335
|
+
function readRuntimePositiveInt(ocrDir, key, defaultValue) {
|
|
35336
|
+
const configPath = join19(ocrDir, "config.yaml");
|
|
35337
|
+
if (!existsSync17(configPath)) return defaultValue;
|
|
34604
35338
|
let content;
|
|
34605
35339
|
try {
|
|
34606
35340
|
content = readFileSync12(configPath, "utf-8");
|
|
34607
35341
|
} catch {
|
|
34608
|
-
return
|
|
35342
|
+
return defaultValue;
|
|
34609
35343
|
}
|
|
34610
35344
|
const blockMatch = content.match(
|
|
34611
|
-
|
|
35345
|
+
new RegExp(
|
|
35346
|
+
String.raw`^runtime:\s*\n(?:\s+[^\n]*\n)*?\s+${key}:\s*([^\s#\n]+)`,
|
|
35347
|
+
"m"
|
|
35348
|
+
)
|
|
34612
35349
|
);
|
|
34613
35350
|
const inlineMatch = content.match(
|
|
34614
|
-
|
|
35351
|
+
new RegExp(String.raw`^runtime:\s*\{[^}]*\b${key}:\s*([^\s,}]+)`, "m")
|
|
34615
35352
|
);
|
|
34616
35353
|
const raw = blockMatch?.[1] ?? inlineMatch?.[1];
|
|
34617
|
-
if (!raw)
|
|
34618
|
-
return DEFAULT_AGENT_HEARTBEAT_SECONDS;
|
|
34619
|
-
}
|
|
35354
|
+
if (!raw) return defaultValue;
|
|
34620
35355
|
const parsed = Number(raw);
|
|
34621
35356
|
if (!Number.isFinite(parsed) || parsed <= 0 || !Number.isInteger(parsed)) {
|
|
34622
35357
|
process.stderr.write(
|
|
34623
|
-
`[ocr] runtime
|
|
35358
|
+
`[ocr] runtime.${key} is not a positive integer (got "${raw}"); falling back to ${defaultValue}.
|
|
34624
35359
|
`
|
|
34625
35360
|
);
|
|
34626
|
-
return
|
|
35361
|
+
return defaultValue;
|
|
34627
35362
|
}
|
|
34628
35363
|
return parsed;
|
|
34629
35364
|
}
|
|
35365
|
+
function getAgentHeartbeatSeconds(ocrDir) {
|
|
35366
|
+
return readRuntimePositiveInt(
|
|
35367
|
+
ocrDir,
|
|
35368
|
+
"agent_heartbeat_seconds",
|
|
35369
|
+
DEFAULT_AGENT_HEARTBEAT_SECONDS
|
|
35370
|
+
);
|
|
35371
|
+
}
|
|
34630
35372
|
|
|
34631
35373
|
// src/commands/session.ts
|
|
34632
35374
|
var TERMINAL_STATUSES = /* @__PURE__ */ new Set([
|
|
@@ -34642,7 +35384,7 @@ function fail(message) {
|
|
|
34642
35384
|
async function setup() {
|
|
34643
35385
|
const targetDir = process.cwd();
|
|
34644
35386
|
requireOcrSetup(targetDir);
|
|
34645
|
-
const ocrDir =
|
|
35387
|
+
const ocrDir = join20(targetDir, ".ocr");
|
|
34646
35388
|
return { ocrDir };
|
|
34647
35389
|
}
|
|
34648
35390
|
var startInstanceSubcommand = new Command("start-instance").description("Journal a new agent-CLI process spawned for the active review").option("--workflow <id>", "Workflow session id (auto-detects active if omitted)").option("--persona <name>", "Reviewer persona, e.g. 'principal'").option("--instance <number>", "Instance index within (workflow, persona)", parseInt).option("--name <name>", "Human-friendly name (default: '{persona}-{instance}')").requiredOption("--vendor <vendor>", "Underlying CLI vendor (e.g. 'claude', 'opencode')").option("--model <id>", "Resolved model id passed to the CLI's --model flag").option("--phase <phase>", "Workflow phase this instance is doing").option("--pid <pid>", "Process id of the spawned process", parseInt).option("--note <text>", "Free-form note to attach").action(
|
|
@@ -34777,6 +35519,7 @@ var listSubcommand = new Command("list").description("List agent sessions for a
|
|
|
34777
35519
|
var sessionCommand = new Command("session").description("Manage agent-CLI session lifecycle journal").addCommand(startInstanceSubcommand).addCommand(bindVendorIdSubcommand).addCommand(beatSubcommand).addCommand(endInstanceSubcommand).addCommand(listSubcommand);
|
|
34778
35520
|
|
|
34779
35521
|
// src/lib/models.ts
|
|
35522
|
+
init_src();
|
|
34780
35523
|
var BUNDLED_CLAUDE_MODELS = [
|
|
34781
35524
|
{ id: "claude-opus-4-7", displayName: "Claude Opus 4.7" },
|
|
34782
35525
|
{ id: "claude-sonnet-4-6", displayName: "Claude Sonnet 4.6" },
|
|
@@ -34897,8 +35640,8 @@ var modelsCommand = new Command("models").description("Inspect models available
|
|
|
34897
35640
|
|
|
34898
35641
|
// src/commands/team.ts
|
|
34899
35642
|
var import_yaml2 = __toESM(require_dist(), 1);
|
|
34900
|
-
import { existsSync as
|
|
34901
|
-
import { join as
|
|
35643
|
+
import { existsSync as existsSync18, readFileSync as readFileSync13, writeFileSync as writeFileSync8 } from "node:fs";
|
|
35644
|
+
import { join as join21 } from "node:path";
|
|
34902
35645
|
async function readStdin2() {
|
|
34903
35646
|
const chunks = [];
|
|
34904
35647
|
for await (const chunk of process.stdin) {
|
|
@@ -34957,7 +35700,7 @@ var resolveSubcommand = new Command("resolve").description("Resolve and print th
|
|
|
34957
35700
|
async (options) => {
|
|
34958
35701
|
const targetDir = process.cwd();
|
|
34959
35702
|
requireOcrSetup(targetDir);
|
|
34960
|
-
const ocrDir =
|
|
35703
|
+
const ocrDir = join21(targetDir, ".ocr");
|
|
34961
35704
|
try {
|
|
34962
35705
|
const { team } = loadTeamConfig(ocrDir);
|
|
34963
35706
|
let override;
|
|
@@ -34996,8 +35739,8 @@ var setSubcommand = new Command("set").description("Persist a new default_team c
|
|
|
34996
35739
|
}
|
|
34997
35740
|
const targetDir = process.cwd();
|
|
34998
35741
|
requireOcrSetup(targetDir);
|
|
34999
|
-
const ocrDir =
|
|
35000
|
-
const configPath =
|
|
35742
|
+
const ocrDir = join21(targetDir, ".ocr");
|
|
35743
|
+
const configPath = join21(ocrDir, "config.yaml");
|
|
35001
35744
|
try {
|
|
35002
35745
|
const raw = await readStdin2();
|
|
35003
35746
|
const team = parseSessionOverride(raw);
|
|
@@ -35007,12 +35750,12 @@ var setSubcommand = new Command("set").description("Persist a new default_team c
|
|
|
35007
35750
|
list.push(inst);
|
|
35008
35751
|
byPersona.set(inst.persona, list);
|
|
35009
35752
|
}
|
|
35010
|
-
const doc =
|
|
35753
|
+
const doc = existsSync18(configPath) ? (0, import_yaml2.parseDocument)(readFileSync13(configPath, "utf-8")) : new import_yaml2.Document({});
|
|
35011
35754
|
applyDefaultTeamSurgically(doc, byPersona);
|
|
35012
35755
|
const yamlOutput = doc.toString({ lineWidth: 0 });
|
|
35013
35756
|
writeFileSync8(configPath, yamlOutput, "utf-8");
|
|
35014
|
-
const reviewersDir =
|
|
35015
|
-
const metaPath =
|
|
35757
|
+
const reviewersDir = join21(ocrDir, "skills", "references", "reviewers");
|
|
35758
|
+
const metaPath = join21(ocrDir, "reviewers-meta.json");
|
|
35016
35759
|
let metaWritten = false;
|
|
35017
35760
|
try {
|
|
35018
35761
|
const meta = generateReviewersMeta(reviewersDir, configPath);
|
|
@@ -35108,7 +35851,7 @@ var teamCommand = new Command("team").description("Resolve and persist team comp
|
|
|
35108
35851
|
|
|
35109
35852
|
// src/commands/review.ts
|
|
35110
35853
|
import { spawn as spawn3 } from "node:child_process";
|
|
35111
|
-
import { join as
|
|
35854
|
+
import { join as join22 } from "node:path";
|
|
35112
35855
|
init_db();
|
|
35113
35856
|
|
|
35114
35857
|
// src/lib/vendor-resume.ts
|
|
@@ -35147,7 +35890,7 @@ var reviewCommand = new Command("review").description("Run or resume an OCR revi
|
|
|
35147
35890
|
}
|
|
35148
35891
|
const targetDir = process.cwd();
|
|
35149
35892
|
requireOcrSetup(targetDir);
|
|
35150
|
-
const ocrDir =
|
|
35893
|
+
const ocrDir = join22(targetDir, ".ocr");
|
|
35151
35894
|
const db = await ensureDatabase(ocrDir);
|
|
35152
35895
|
const session = getSession(db, options.resume);
|
|
35153
35896
|
if (!session) {
|
|
@@ -35189,23 +35932,23 @@ var reviewCommand = new Command("review").description("Run or resume an OCR revi
|
|
|
35189
35932
|
});
|
|
35190
35933
|
|
|
35191
35934
|
// src/commands/update.ts
|
|
35192
|
-
import { existsSync as
|
|
35193
|
-
import { join as
|
|
35935
|
+
import { existsSync as existsSync19 } from "node:fs";
|
|
35936
|
+
import { join as join23 } from "node:path";
|
|
35194
35937
|
function detectConfiguredTools(targetDir) {
|
|
35195
35938
|
return AI_TOOLS.filter((tool) => {
|
|
35196
35939
|
if (tool.commandStrategy === "subdirectory") {
|
|
35197
|
-
const ocrDir =
|
|
35198
|
-
return
|
|
35940
|
+
const ocrDir = join23(targetDir, tool.commandsDir, "ocr");
|
|
35941
|
+
return existsSync19(ocrDir);
|
|
35199
35942
|
} else {
|
|
35200
|
-
const reviewCmd =
|
|
35201
|
-
return
|
|
35943
|
+
const reviewCmd = join23(targetDir, tool.commandsDir, "ocr-review.md");
|
|
35944
|
+
return existsSync19(reviewCmd);
|
|
35202
35945
|
}
|
|
35203
35946
|
});
|
|
35204
35947
|
}
|
|
35205
35948
|
var updateCommand = new Command("update").description("Update OCR assets after package upgrade").option("--commands", "Update only commands/workflows").option(
|
|
35206
35949
|
"--skills",
|
|
35207
35950
|
"Update only skills (includes templates, references, assets)"
|
|
35208
|
-
).option("--inject", "Update only AGENTS.md
|
|
35951
|
+
).option("--inject", "Update only instruction-file injection (AGENTS.md + each tool's native file)").option("--dry-run", "Preview changes without modifying files").action(async (options) => {
|
|
35209
35952
|
const targetDir = process.cwd();
|
|
35210
35953
|
requireOcrSetup(targetDir);
|
|
35211
35954
|
console.log();
|
|
@@ -35272,7 +36015,7 @@ var updateCommand = new Command("update").description("Update OCR assets after p
|
|
|
35272
36015
|
const result = installForTool(tool, targetDir);
|
|
35273
36016
|
results.push(result);
|
|
35274
36017
|
}
|
|
35275
|
-
ensureGitignore(
|
|
36018
|
+
ensureGitignore(join23(targetDir, ".ocr"));
|
|
35276
36019
|
spinner.stop();
|
|
35277
36020
|
const successful = results.filter((r) => r.success);
|
|
35278
36021
|
const failed = results.filter((r) => !r.success);
|
|
@@ -35306,30 +36049,34 @@ var updateCommand = new Command("update").description("Update OCR assets after p
|
|
|
35306
36049
|
}
|
|
35307
36050
|
}
|
|
35308
36051
|
if (updateInject) {
|
|
36052
|
+
const planned = plannedInstructionFiles(toolsToUpdate);
|
|
35309
36053
|
if (options.dryRun) {
|
|
35310
36054
|
console.log(source_default.dim(" Would update:"));
|
|
35311
|
-
|
|
35312
|
-
|
|
36055
|
+
for (const path2 of planned) {
|
|
36056
|
+
const verb = existsSync19(join23(targetDir, path2)) ? "update" : "create";
|
|
36057
|
+
console.log(source_default.dim(` \u2022 ${path2} (${verb} OCR managed block)`));
|
|
35313
36058
|
}
|
|
35314
|
-
|
|
35315
|
-
|
|
36059
|
+
const staleDry = findStaleInstructionFiles(targetDir, planned);
|
|
36060
|
+
for (const warning of formatStaleWarnings(staleDry, "dry-run")) {
|
|
36061
|
+
console.log(source_default.dim(` \u2022 ${warning}`));
|
|
35316
36062
|
}
|
|
35317
36063
|
console.log();
|
|
35318
36064
|
} else {
|
|
35319
|
-
const spinner = ora("Updating
|
|
35320
|
-
const injectResults = injectIntoProjectFiles(targetDir);
|
|
36065
|
+
const spinner = ora("Updating instruction files...").start();
|
|
36066
|
+
const injectResults = injectIntoProjectFiles(targetDir, toolsToUpdate);
|
|
35321
36067
|
spinner.stop();
|
|
35322
|
-
if (injectResults.
|
|
36068
|
+
if (injectResults.written.length > 0) {
|
|
35323
36069
|
console.log(source_default.green(" \u2713 Instructions updated"));
|
|
35324
|
-
|
|
35325
|
-
console.log(` ${source_default.green("\u2713")}
|
|
35326
|
-
}
|
|
35327
|
-
if (injectResults.claudeMd) {
|
|
35328
|
-
console.log(` ${source_default.green("\u2713")} CLAUDE.md`);
|
|
36070
|
+
for (const path2 of injectResults.written) {
|
|
36071
|
+
console.log(` ${source_default.green("\u2713")} ${path2}`);
|
|
35329
36072
|
}
|
|
35330
36073
|
} else {
|
|
35331
36074
|
console.log(source_default.dim(" No instruction files to update"));
|
|
35332
36075
|
}
|
|
36076
|
+
const stale = findStaleInstructionFiles(targetDir, injectResults.written);
|
|
36077
|
+
for (const warning of formatStaleWarnings(stale, "update")) {
|
|
36078
|
+
console.log(source_default.yellow(` \u26A0 ${warning}`));
|
|
36079
|
+
}
|
|
35333
36080
|
console.log();
|
|
35334
36081
|
}
|
|
35335
36082
|
}
|
|
@@ -35343,14 +36090,15 @@ var updateCommand = new Command("update").description("Update OCR assets after p
|
|
|
35343
36090
|
});
|
|
35344
36091
|
|
|
35345
36092
|
// src/commands/dashboard.ts
|
|
35346
|
-
import { existsSync as
|
|
35347
|
-
import { join as
|
|
36093
|
+
import { existsSync as existsSync20 } from "node:fs";
|
|
36094
|
+
import { join as join24, dirname as dirname9 } from "node:path";
|
|
35348
36095
|
import { fileURLToPath } from "node:url";
|
|
36096
|
+
init_src();
|
|
35349
36097
|
init_db();
|
|
35350
36098
|
var __filename = fileURLToPath(import.meta.url);
|
|
35351
|
-
var __dirname =
|
|
36099
|
+
var __dirname = dirname9(__filename);
|
|
35352
36100
|
function resolveServerPath() {
|
|
35353
|
-
return
|
|
36101
|
+
return join24(__dirname, "dashboard", "server.js");
|
|
35354
36102
|
}
|
|
35355
36103
|
var dashboardCommand = new Command("dashboard").description("Start the OCR dashboard web interface").option("-p, --port <port>", "Port to run the server on", "4173").option("--no-open", "Don't open the browser automatically").action(
|
|
35356
36104
|
async (options) => {
|
|
@@ -35361,7 +36109,7 @@ var dashboardCommand = new Command("dashboard").description("Start the OCR dashb
|
|
|
35361
36109
|
console.error(source_default.red(`Error: Invalid port "${options.port}". Must be 1-65535.`));
|
|
35362
36110
|
process.exit(1);
|
|
35363
36111
|
}
|
|
35364
|
-
const ocrDir =
|
|
36112
|
+
const ocrDir = join24(targetDir, ".ocr");
|
|
35365
36113
|
try {
|
|
35366
36114
|
await ensureDatabase(ocrDir);
|
|
35367
36115
|
closeAllDatabases();
|
|
@@ -35375,7 +36123,7 @@ var dashboardCommand = new Command("dashboard").description("Start the OCR dashb
|
|
|
35375
36123
|
process.exit(1);
|
|
35376
36124
|
}
|
|
35377
36125
|
const serverPath = resolveServerPath();
|
|
35378
|
-
if (!
|
|
36126
|
+
if (!existsSync20(serverPath)) {
|
|
35379
36127
|
console.error(source_default.red("Error: Dashboard server bundle not found."));
|
|
35380
36128
|
console.error(
|
|
35381
36129
|
source_default.dim(` Expected at: ${serverPath}`)
|
|
@@ -35409,11 +36157,53 @@ var dashboardCommand = new Command("dashboard").description("Start the OCR dashb
|
|
|
35409
36157
|
);
|
|
35410
36158
|
|
|
35411
36159
|
// src/commands/doctor.ts
|
|
35412
|
-
import { existsSync as
|
|
35413
|
-
import { join as
|
|
36160
|
+
import { existsSync as existsSync21 } from "node:fs";
|
|
36161
|
+
import { join as join25 } from "node:path";
|
|
35414
36162
|
init_db();
|
|
35415
|
-
|
|
36163
|
+
function printStorageEngine(probeWriteEnabled) {
|
|
36164
|
+
console.log();
|
|
36165
|
+
console.log(source_default.bold(" Storage Engine"));
|
|
36166
|
+
console.log();
|
|
36167
|
+
const engine = probeEngine();
|
|
36168
|
+
if (!engine.ok) {
|
|
36169
|
+
console.log(` ${source_default.red("\u2717")} node:sqlite unavailable`);
|
|
36170
|
+
console.log(` ${source_default.dim(engine.error)}`);
|
|
36171
|
+
console.log(
|
|
36172
|
+
` ${source_default.dim(
|
|
36173
|
+
"OCR requires Node >= 22.5 (node:sqlite). Upgrade Node, then re-run `ocr doctor`."
|
|
36174
|
+
)}`
|
|
36175
|
+
);
|
|
36176
|
+
return false;
|
|
36177
|
+
}
|
|
36178
|
+
console.log(
|
|
36179
|
+
` ${source_default.green("\u2713")} node:sqlite (SQLite ${engine.version}, WAL)`
|
|
36180
|
+
);
|
|
36181
|
+
if (probeWriteEnabled) {
|
|
36182
|
+
const write = probeWrite();
|
|
36183
|
+
if (!write.ok) {
|
|
36184
|
+
console.log(` ${source_default.red("\u2717")} write probe failed`);
|
|
36185
|
+
console.log(` ${source_default.dim(write.error)}`);
|
|
36186
|
+
return false;
|
|
36187
|
+
}
|
|
36188
|
+
console.log(
|
|
36189
|
+
` ${source_default.green("\u2713")} write probe (on-disk WAL transaction round-trip)`
|
|
36190
|
+
);
|
|
36191
|
+
}
|
|
36192
|
+
return true;
|
|
36193
|
+
}
|
|
36194
|
+
var doctorCommand = new Command("doctor").description("Check OCR installation and verify all dependencies").option(
|
|
36195
|
+
"--probe-write",
|
|
36196
|
+
"additionally exercise an on-disk WAL transaction round-trip (used by the release install gate)"
|
|
36197
|
+
).option(
|
|
36198
|
+
"--engine-only",
|
|
36199
|
+
"check ONLY the storage engine and exit on its result \u2014 skips project/tool checks (used by the release install gate, which runs from a non-initialized dir with no AI tools)"
|
|
36200
|
+
).action((options) => {
|
|
35416
36201
|
printHeader();
|
|
36202
|
+
if (options.engineOnly) {
|
|
36203
|
+
const ok = printStorageEngine(options.probeWrite ?? false);
|
|
36204
|
+
console.log();
|
|
36205
|
+
process.exit(ok ? 0 : 1);
|
|
36206
|
+
}
|
|
35417
36207
|
const targetDir = process.cwd();
|
|
35418
36208
|
let hasIssues = false;
|
|
35419
36209
|
const depResult = checkDependencies();
|
|
@@ -35425,10 +36215,10 @@ var doctorCommand = new Command("doctor").description("Check OCR installation an
|
|
|
35425
36215
|
console.log(source_default.bold(" OCR Installation"));
|
|
35426
36216
|
console.log();
|
|
35427
36217
|
const ocrStatus = checkOcrSetup(targetDir);
|
|
35428
|
-
const configPath =
|
|
35429
|
-
const dbPath =
|
|
35430
|
-
const hasConfig =
|
|
35431
|
-
const hasDb =
|
|
36218
|
+
const configPath = join25(targetDir, ".ocr", "config.yaml");
|
|
36219
|
+
const dbPath = join25(targetDir, ".ocr", "data", "ocr.db");
|
|
36220
|
+
const hasConfig = existsSync21(configPath);
|
|
36221
|
+
const hasDb = existsSync21(dbPath);
|
|
35432
36222
|
const ocrChecks = [
|
|
35433
36223
|
{ label: ".ocr/skills/", ok: ocrStatus.hasSkills },
|
|
35434
36224
|
{ label: ".ocr/sessions/", ok: ocrStatus.hasSessions },
|
|
@@ -35450,25 +36240,8 @@ var doctorCommand = new Command("doctor").description("Check OCR installation an
|
|
|
35450
36240
|
if (!ocrStatus.valid) {
|
|
35451
36241
|
hasIssues = true;
|
|
35452
36242
|
}
|
|
35453
|
-
|
|
35454
|
-
console.log(source_default.bold(" Storage Engine"));
|
|
35455
|
-
console.log();
|
|
35456
|
-
const engine = probeEngine();
|
|
35457
|
-
if (engine.ok) {
|
|
35458
|
-
console.log(
|
|
35459
|
-
` ${source_default.green("\u2713")} better-sqlite3 (SQLite ${engine.version}, WAL)`
|
|
35460
|
-
);
|
|
35461
|
-
} else {
|
|
36243
|
+
if (!printStorageEngine(options.probeWrite ?? false)) {
|
|
35462
36244
|
hasIssues = true;
|
|
35463
|
-
console.log(
|
|
35464
|
-
` ${source_default.red("\u2717")} better-sqlite3 failed to load`
|
|
35465
|
-
);
|
|
35466
|
-
console.log(` ${source_default.dim(engine.error)}`);
|
|
35467
|
-
console.log(
|
|
35468
|
-
` ${source_default.dim(
|
|
35469
|
-
"Reinstall the CLI; if it persists your platform may need build tools (python3 + a C++ toolchain) or lacks a prebuilt binary."
|
|
35470
|
-
)}`
|
|
35471
|
-
);
|
|
35472
36245
|
}
|
|
35473
36246
|
console.log();
|
|
35474
36247
|
printCapabilities(depResult);
|
|
@@ -35521,9 +36294,331 @@ var doctorCommand = new Command("doctor").description("Check OCR installation an
|
|
|
35521
36294
|
console.log();
|
|
35522
36295
|
});
|
|
35523
36296
|
|
|
36297
|
+
// src/commands/db.ts
|
|
36298
|
+
import { existsSync as existsSync22, readFileSync as readFileSync14 } from "node:fs";
|
|
36299
|
+
import { join as join26 } from "node:path";
|
|
36300
|
+
init_src();
|
|
36301
|
+
init_db();
|
|
36302
|
+
function fail4(message) {
|
|
36303
|
+
console.error(source_default.red(`Error: ${message}`));
|
|
36304
|
+
process.exit(1);
|
|
36305
|
+
}
|
|
36306
|
+
function resolveOcrDir() {
|
|
36307
|
+
const targetDir = process.cwd();
|
|
36308
|
+
requireOcrSetup(targetDir);
|
|
36309
|
+
return join26(targetDir, ".ocr");
|
|
36310
|
+
}
|
|
36311
|
+
function dbPathFor(ocrDir) {
|
|
36312
|
+
return join26(ocrDir, "data", "ocr.db");
|
|
36313
|
+
}
|
|
36314
|
+
function formatBytes(n) {
|
|
36315
|
+
if (n < 1024) return `${n} B`;
|
|
36316
|
+
const units = ["KB", "MB", "GB", "TB"];
|
|
36317
|
+
let v = n / 1024;
|
|
36318
|
+
let i = 0;
|
|
36319
|
+
while (v >= 1024 && i < units.length - 1) {
|
|
36320
|
+
v /= 1024;
|
|
36321
|
+
i++;
|
|
36322
|
+
}
|
|
36323
|
+
return `${v.toFixed(v >= 100 ? 0 : 1)} ${units[i]}`;
|
|
36324
|
+
}
|
|
36325
|
+
function liveDashboardPid(ocrDir) {
|
|
36326
|
+
const pidFile = join26(ocrDir, "data", "dashboard.pid");
|
|
36327
|
+
if (!existsSync22(pidFile)) return null;
|
|
36328
|
+
try {
|
|
36329
|
+
const pid = parseInt(readFileSync14(pidFile, "utf-8").trim(), 10);
|
|
36330
|
+
if (!Number.isNaN(pid) && isProcessAlive(pid)) return pid;
|
|
36331
|
+
} catch {
|
|
36332
|
+
}
|
|
36333
|
+
return null;
|
|
36334
|
+
}
|
|
36335
|
+
function guardExclusive(ocrDir, force, op) {
|
|
36336
|
+
const pid = liveDashboardPid(ocrDir);
|
|
36337
|
+
if (pid !== null && !force) {
|
|
36338
|
+
fail4(
|
|
36339
|
+
`a dashboard appears to be running (PID ${pid}); ${op} needs exclusive access to the database.
|
|
36340
|
+
Stop it first, or pass --force to proceed anyway.`
|
|
36341
|
+
);
|
|
36342
|
+
}
|
|
36343
|
+
}
|
|
36344
|
+
function printHealth(report) {
|
|
36345
|
+
console.log();
|
|
36346
|
+
console.log(source_default.bold(" Database Health"));
|
|
36347
|
+
console.log();
|
|
36348
|
+
console.log(` File: ${report.dbPath}`);
|
|
36349
|
+
console.log(` Size: ${formatBytes(report.fileSizeBytes)}`);
|
|
36350
|
+
if (report.reclaimableBytes > 0) {
|
|
36351
|
+
console.log(
|
|
36352
|
+
` Reclaimable: ${source_default.yellow(formatBytes(report.reclaimableBytes))} ` + source_default.dim(`(${report.freelistCount} free pages \u2014 run \`ocr db vacuum\`)`)
|
|
36353
|
+
);
|
|
36354
|
+
}
|
|
36355
|
+
console.log(
|
|
36356
|
+
` Records: ${report.sessionCount} session(s), ${report.eventCount} event(s)`
|
|
36357
|
+
);
|
|
36358
|
+
console.log();
|
|
36359
|
+
const ok = (s) => ` ${source_default.green("\u2713")} ${s}`;
|
|
36360
|
+
const bad = (s) => ` ${source_default.red("\u2717")} ${s}`;
|
|
36361
|
+
console.log(
|
|
36362
|
+
report.integrityOk ? ok("integrity_check: ok") : bad(`integrity_check: ${report.integrityErrors.length} error(s)`)
|
|
36363
|
+
);
|
|
36364
|
+
if (!report.integrityOk) {
|
|
36365
|
+
for (const e of report.integrityErrors.slice(0, 5)) {
|
|
36366
|
+
console.log(` ${source_default.dim(e)}`);
|
|
36367
|
+
}
|
|
36368
|
+
}
|
|
36369
|
+
const fkTotal = report.fkViolations.reduce((n, g) => n + g.count, 0) + report.protectedFkViolations.reduce((n, g) => n + g.count, 0);
|
|
36370
|
+
if (fkTotal === 0) {
|
|
36371
|
+
console.log(ok("foreign_key_check: 0 violations"));
|
|
36372
|
+
} else {
|
|
36373
|
+
console.log(bad(`foreign_key_check: ${fkTotal} violation(s)`));
|
|
36374
|
+
for (const g of report.fkViolations) {
|
|
36375
|
+
console.log(` ${source_default.dim(`${g.table}: ${g.count} orphan(s)`)}`);
|
|
36376
|
+
}
|
|
36377
|
+
for (const g of report.protectedFkViolations) {
|
|
36378
|
+
console.log(
|
|
36379
|
+
` ${source_default.yellow(`${g.table}: ${g.count} (protected \u2014 manual review)`)}`
|
|
36380
|
+
);
|
|
36381
|
+
}
|
|
36382
|
+
}
|
|
36383
|
+
if (report.markdownDuplicateRows === 0) {
|
|
36384
|
+
console.log(ok("markdown_artifacts: no duplicates"));
|
|
36385
|
+
} else {
|
|
36386
|
+
console.log(
|
|
36387
|
+
bad(`markdown_artifacts: ${report.markdownDuplicateRows} duplicate row(s)`)
|
|
36388
|
+
);
|
|
36389
|
+
}
|
|
36390
|
+
const reapable = report.orphanTempFiles.filter((f) => f.reapable);
|
|
36391
|
+
if (report.orphanTempFiles.length > 0) {
|
|
36392
|
+
console.log(
|
|
36393
|
+
` ${reapable.length > 0 ? source_default.yellow("\u26A0") : source_default.dim("\xB7")} orphan temp files: ${report.orphanTempFiles.length} (${reapable.length} reapable)`
|
|
36394
|
+
);
|
|
36395
|
+
}
|
|
36396
|
+
if (report.backupFiles.length > 0) {
|
|
36397
|
+
const total = report.backupFiles.reduce((n, b) => n + b.sizeBytes, 0);
|
|
36398
|
+
console.log(
|
|
36399
|
+
` ${source_default.dim("\xB7")} backups: ${report.backupFiles.length} (${formatBytes(total)})`
|
|
36400
|
+
);
|
|
36401
|
+
}
|
|
36402
|
+
console.log();
|
|
36403
|
+
}
|
|
36404
|
+
function needsFix(report) {
|
|
36405
|
+
return !report.integrityOk || report.fkViolations.length > 0 || report.markdownDuplicateRows > 0 || report.orphanTempFiles.some((f) => f.reapable) || report.reclaimableBytes > 0;
|
|
36406
|
+
}
|
|
36407
|
+
var doctorSubcommand = new Command("doctor").description("Report database health; --fix repairs orphans/dupes and VACUUMs").option("--fix", "apply repairs: FK-orphan sweep, dedup, temp reap, VACUUM").option("--no-snapshot", "skip the pre-fix snapshot (with --fix)").option("--force", "proceed even if a live dashboard owns the database").option("--json", "emit the health report as JSON (implies no --fix)").action(
|
|
36408
|
+
async (options) => {
|
|
36409
|
+
const ocrDir = resolveOcrDir();
|
|
36410
|
+
const dbPath = dbPathFor(ocrDir);
|
|
36411
|
+
const db = await ensureDatabase(ocrDir);
|
|
36412
|
+
if (options.json) {
|
|
36413
|
+
console.log(JSON.stringify(collectDbHealth(db, dbPath), null, 2));
|
|
36414
|
+
return;
|
|
36415
|
+
}
|
|
36416
|
+
const before = collectDbHealth(db, dbPath);
|
|
36417
|
+
printHealth(before);
|
|
36418
|
+
if (!options.fix) {
|
|
36419
|
+
if (needsFix(before)) {
|
|
36420
|
+
console.log(
|
|
36421
|
+
source_default.dim(" Run `ocr db doctor --fix` to repair the issues above.")
|
|
36422
|
+
);
|
|
36423
|
+
console.log();
|
|
36424
|
+
} else {
|
|
36425
|
+
console.log(source_default.green(" \u2713 Database is healthy"));
|
|
36426
|
+
console.log();
|
|
36427
|
+
}
|
|
36428
|
+
return;
|
|
36429
|
+
}
|
|
36430
|
+
guardExclusive(ocrDir, options.force ?? false, "doctor --fix");
|
|
36431
|
+
const result = fixDb(db, dbPath, { snapshot: options.snapshot !== false });
|
|
36432
|
+
console.log(source_default.bold(" Repairs applied"));
|
|
36433
|
+
console.log();
|
|
36434
|
+
if (result.snapshotPath) {
|
|
36435
|
+
console.log(` ${source_default.dim("snapshot:")} ${result.snapshotPath}`);
|
|
36436
|
+
}
|
|
36437
|
+
if (result.totalFkOrphansDeleted > 0) {
|
|
36438
|
+
console.log(
|
|
36439
|
+
` ${source_default.green("\u2713")} swept ${result.totalFkOrphansDeleted} FK-orphan row(s)`
|
|
36440
|
+
);
|
|
36441
|
+
for (const g of result.fkOrphansDeleted) {
|
|
36442
|
+
console.log(` ${source_default.dim(`${g.table}: ${g.count}`)}`);
|
|
36443
|
+
}
|
|
36444
|
+
}
|
|
36445
|
+
if (result.markdownDupsDeleted > 0) {
|
|
36446
|
+
console.log(
|
|
36447
|
+
` ${source_default.green("\u2713")} removed ${result.markdownDupsDeleted} duplicate markdown row(s)`
|
|
36448
|
+
);
|
|
36449
|
+
}
|
|
36450
|
+
if (result.tempsReaped.length > 0) {
|
|
36451
|
+
console.log(
|
|
36452
|
+
` ${source_default.green("\u2713")} reaped ${result.tempsReaped.length} orphan temp file(s)`
|
|
36453
|
+
);
|
|
36454
|
+
}
|
|
36455
|
+
if (result.vacuumed) {
|
|
36456
|
+
const saved = result.sizeBeforeBytes - result.sizeAfterBytes;
|
|
36457
|
+
console.log(
|
|
36458
|
+
` ${source_default.green("\u2713")} VACUUM: ${formatBytes(result.sizeBeforeBytes)} \u2192 ${formatBytes(result.sizeAfterBytes)} ` + source_default.dim(`(reclaimed ${formatBytes(Math.max(0, saved))})`)
|
|
36459
|
+
);
|
|
36460
|
+
}
|
|
36461
|
+
console.log();
|
|
36462
|
+
if (result.protectedViolationsRemaining.length > 0) {
|
|
36463
|
+
console.log(
|
|
36464
|
+
source_default.yellow(
|
|
36465
|
+
" \u26A0 Violations remain in protected (system-of-record) tables:"
|
|
36466
|
+
)
|
|
36467
|
+
);
|
|
36468
|
+
for (const g of result.protectedViolationsRemaining) {
|
|
36469
|
+
console.log(` ${source_default.yellow(`${g.table}: ${g.count}`)}`);
|
|
36470
|
+
}
|
|
36471
|
+
console.log();
|
|
36472
|
+
}
|
|
36473
|
+
if (result.integrityOkAfter && result.fkViolationsAfter === 0) {
|
|
36474
|
+
console.log(source_default.green(" \u2713 Database repaired and healthy"));
|
|
36475
|
+
} else {
|
|
36476
|
+
console.log(
|
|
36477
|
+
source_default.red(
|
|
36478
|
+
` \u2717 Post-fix check: integrity ${result.integrityOkAfter ? "ok" : "FAILED"}, ${result.fkViolationsAfter} FK violation(s) remaining`
|
|
36479
|
+
)
|
|
36480
|
+
);
|
|
36481
|
+
process.exitCode = 1;
|
|
36482
|
+
}
|
|
36483
|
+
console.log();
|
|
36484
|
+
}
|
|
36485
|
+
);
|
|
36486
|
+
var vacuumSubcommand = new Command("vacuum").description("Checkpoint the WAL and VACUUM the database (snapshot-first)").option("--no-snapshot", "skip the pre-vacuum snapshot").option("--force", "proceed even if a live dashboard owns the database").action(async (options) => {
|
|
36487
|
+
const ocrDir = resolveOcrDir();
|
|
36488
|
+
const dbPath = dbPathFor(ocrDir);
|
|
36489
|
+
guardExclusive(ocrDir, options.force ?? false, "vacuum");
|
|
36490
|
+
const db = await ensureDatabase(ocrDir);
|
|
36491
|
+
const result = vacuumDb(db, dbPath, { snapshot: options.snapshot !== false });
|
|
36492
|
+
console.log();
|
|
36493
|
+
if (result.snapshotPath) {
|
|
36494
|
+
console.log(` ${source_default.dim("snapshot:")} ${result.snapshotPath}`);
|
|
36495
|
+
}
|
|
36496
|
+
console.log(
|
|
36497
|
+
` ${source_default.green("\u2713")} VACUUM: ${formatBytes(result.sizeBeforeBytes)} \u2192 ${formatBytes(result.sizeAfterBytes)} ` + source_default.dim(`(reclaimed ${formatBytes(result.reclaimedBytes)})`)
|
|
36498
|
+
);
|
|
36499
|
+
console.log();
|
|
36500
|
+
});
|
|
36501
|
+
var pruneSubcommand = new Command("prune").description(
|
|
36502
|
+
"Drop derived artifacts of old CLOSED sessions (events + sessions kept)"
|
|
36503
|
+
).option(
|
|
36504
|
+
"--keep-sessions <n>",
|
|
36505
|
+
"protect the N most-recently-active closed sessions",
|
|
36506
|
+
(v) => parseInt(v, 10)
|
|
36507
|
+
).option(
|
|
36508
|
+
"--older-than <days>",
|
|
36509
|
+
"only prune closed sessions quiet for more than D days",
|
|
36510
|
+
(v) => parseInt(v, 10)
|
|
36511
|
+
).option("--dry-run", "show what would be pruned without deleting").option("--force", "proceed even if a live dashboard owns the database").action(
|
|
36512
|
+
async (options) => {
|
|
36513
|
+
const ocrDir = resolveOcrDir();
|
|
36514
|
+
const dbPath = dbPathFor(ocrDir);
|
|
36515
|
+
if (options.keepSessions === void 0 && options.olderThan === void 0) {
|
|
36516
|
+
fail4(
|
|
36517
|
+
"prune needs a bound: pass --older-than <days> and/or --keep-sessions <n>."
|
|
36518
|
+
);
|
|
36519
|
+
}
|
|
36520
|
+
if (!options.dryRun) {
|
|
36521
|
+
guardExclusive(ocrDir, options.force ?? false, "prune");
|
|
36522
|
+
}
|
|
36523
|
+
const db = await ensureDatabase(ocrDir);
|
|
36524
|
+
const result = pruneDb(db, dbPath, {
|
|
36525
|
+
keepSessions: options.keepSessions,
|
|
36526
|
+
olderThanDays: options.olderThan,
|
|
36527
|
+
dryRun: options.dryRun ?? false
|
|
36528
|
+
});
|
|
36529
|
+
console.log();
|
|
36530
|
+
if (result.prunedSessions.length === 0) {
|
|
36531
|
+
console.log(source_default.green(" \u2713 Nothing to prune"));
|
|
36532
|
+
console.log();
|
|
36533
|
+
return;
|
|
36534
|
+
}
|
|
36535
|
+
const verb = result.dryRun ? "Would prune" : "Pruned";
|
|
36536
|
+
console.log(
|
|
36537
|
+
source_default.bold(
|
|
36538
|
+
` ${verb} ${result.totalArtifactRows} artifact row(s) across ${result.prunedSessions.length} session(s)`
|
|
36539
|
+
)
|
|
36540
|
+
);
|
|
36541
|
+
console.log();
|
|
36542
|
+
for (const p of result.prunedSessions.slice(0, 20)) {
|
|
36543
|
+
console.log(
|
|
36544
|
+
` ${source_default.dim("\xB7")} ${p.sessionId} ${source_default.dim(`(${p.artifactRows} rows)`)}`
|
|
36545
|
+
);
|
|
36546
|
+
}
|
|
36547
|
+
if (result.prunedSessions.length > 20) {
|
|
36548
|
+
console.log(
|
|
36549
|
+
` ${source_default.dim(`\u2026 and ${result.prunedSessions.length - 20} more`)}`
|
|
36550
|
+
);
|
|
36551
|
+
}
|
|
36552
|
+
console.log();
|
|
36553
|
+
if (result.snapshotPath) {
|
|
36554
|
+
console.log(` ${source_default.dim("snapshot:")} ${result.snapshotPath}`);
|
|
36555
|
+
}
|
|
36556
|
+
console.log(
|
|
36557
|
+
source_default.dim(
|
|
36558
|
+
result.dryRun ? " Re-run without --dry-run to apply. Events + session rows are always kept." : " Events + session rows were kept; sessions remain fully auditable."
|
|
36559
|
+
)
|
|
36560
|
+
);
|
|
36561
|
+
console.log();
|
|
36562
|
+
}
|
|
36563
|
+
);
|
|
36564
|
+
function validatePruneBackupsOptions(options) {
|
|
36565
|
+
if (!Number.isInteger(options.keep) || options.keep < 0) {
|
|
36566
|
+
return `--keep must be a non-negative integer (got "${String(options.keep)}").`;
|
|
36567
|
+
}
|
|
36568
|
+
if (options.keep === 0 && !options.force && !options.dryRun) {
|
|
36569
|
+
return "--keep 0 removes every backup (including any just-written snapshot). Re-run with --dry-run to preview, or --force to confirm.";
|
|
36570
|
+
}
|
|
36571
|
+
return null;
|
|
36572
|
+
}
|
|
36573
|
+
var pruneBackupsSubcommand = new Command("prune-backups").description("Delete old ocr.db.bak.* snapshots, keeping the most recent few").option(
|
|
36574
|
+
"--keep <n>",
|
|
36575
|
+
"retain the N most-recent backups (default 1; 0 removes all, requires --force)",
|
|
36576
|
+
// Raw conversion only — `Number('oops')` is NaN and flows into
|
|
36577
|
+
// validatePruneBackupsOptions, the single validation home. (parseInt would
|
|
36578
|
+
// also silently accept "3abc" → 3; Number rejects it as NaN.)
|
|
36579
|
+
(v) => Number(v),
|
|
36580
|
+
1
|
|
36581
|
+
).option("--force", "permit --keep 0 (removing the last backup / safety net)").option("--dry-run", "show what would be deleted without deleting").action(async (options) => {
|
|
36582
|
+
const ocrDir = resolveOcrDir();
|
|
36583
|
+
const dataDir = join26(ocrDir, "data");
|
|
36584
|
+
const invalid = validatePruneBackupsOptions(options);
|
|
36585
|
+
if (invalid !== null) {
|
|
36586
|
+
fail4(invalid);
|
|
36587
|
+
}
|
|
36588
|
+
const result = pruneBackups(dataDir, dbPathFor(ocrDir), {
|
|
36589
|
+
keep: options.keep,
|
|
36590
|
+
dryRun: options.dryRun ?? false
|
|
36591
|
+
});
|
|
36592
|
+
console.log();
|
|
36593
|
+
if (result.deleted.length === 0) {
|
|
36594
|
+
console.log(source_default.green(" \u2713 No backups to remove"));
|
|
36595
|
+
console.log();
|
|
36596
|
+
return;
|
|
36597
|
+
}
|
|
36598
|
+
const verb = result.dryRun ? "Would delete" : "Deleted";
|
|
36599
|
+
console.log(
|
|
36600
|
+
source_default.bold(
|
|
36601
|
+
` ${verb} ${result.deleted.length} backup(s) \u2014 ${formatBytes(result.reclaimedBytes)}`
|
|
36602
|
+
)
|
|
36603
|
+
);
|
|
36604
|
+
console.log();
|
|
36605
|
+
for (const b of result.deleted) {
|
|
36606
|
+
console.log(` ${source_default.dim("\xB7")} ${b.name} ${source_default.dim(`(${formatBytes(b.sizeBytes)})`)}`);
|
|
36607
|
+
}
|
|
36608
|
+
if (result.kept.length > 0) {
|
|
36609
|
+
console.log();
|
|
36610
|
+
console.log(
|
|
36611
|
+
source_default.dim(` Kept ${result.kept.length} most-recent backup(s) as a safety net.`)
|
|
36612
|
+
);
|
|
36613
|
+
}
|
|
36614
|
+
console.log();
|
|
36615
|
+
});
|
|
36616
|
+
var dbCommand = new Command("db").description("Inspect and maintain the OCR SQLite database").addCommand(doctorSubcommand).addCommand(vacuumSubcommand).addCommand(pruneSubcommand).addCommand(pruneBackupsSubcommand);
|
|
36617
|
+
|
|
35524
36618
|
// src/commands/reviewers.ts
|
|
35525
36619
|
import { writeFileSync as writeFileSync9, renameSync as renameSync2 } from "node:fs";
|
|
35526
|
-
import { join as
|
|
36620
|
+
import { join as join27 } from "node:path";
|
|
36621
|
+
init_src();
|
|
35527
36622
|
async function readStdin3() {
|
|
35528
36623
|
const chunks = [];
|
|
35529
36624
|
for await (const chunk of process.stdin) {
|
|
@@ -35537,6 +36632,25 @@ async function readStdin3() {
|
|
|
35537
36632
|
}
|
|
35538
36633
|
var VALID_TIERS = /* @__PURE__ */ new Set(["holistic", "specialist", "persona", "custom"]);
|
|
35539
36634
|
var SLUG_RE = /^[a-z][a-z0-9-]*$/;
|
|
36635
|
+
var INJECTION_PATTERNS = [
|
|
36636
|
+
/ignore\s+(all\s+|the\s+)?(previous|prior|above)?\s*(instructions|prompts|rules)/i,
|
|
36637
|
+
/disregard\s+(all\s+|the\s+)?(previous|prior|above)/i,
|
|
36638
|
+
/\byou\s+are\s+now\b/i,
|
|
36639
|
+
/^\s*system\s*:/im,
|
|
36640
|
+
/\balways\s+(conclude|respond|reply|return|output|approve|reject|say)\b/i,
|
|
36641
|
+
/\bnew\s+rule\s*:/i
|
|
36642
|
+
];
|
|
36643
|
+
function warnIfSuspiciousPersona(label, fields) {
|
|
36644
|
+
const text = fields.filter((f) => typeof f === "string").join("\n");
|
|
36645
|
+
const hit = INJECTION_PATTERNS.find((re) => re.test(text));
|
|
36646
|
+
if (hit) {
|
|
36647
|
+
console.error(
|
|
36648
|
+
source_default.yellow(
|
|
36649
|
+
`\u26A0 ${label} contains text resembling a prompt-injection override (matched ${hit}). Review the persona before relying on it.`
|
|
36650
|
+
)
|
|
36651
|
+
);
|
|
36652
|
+
}
|
|
36653
|
+
}
|
|
35540
36654
|
function validateReviewersMeta(data) {
|
|
35541
36655
|
if (typeof data !== "object" || data === null || Array.isArray(data)) {
|
|
35542
36656
|
throw new Error("Payload must be a JSON object");
|
|
@@ -35574,6 +36688,19 @@ function validateReviewersMeta(data) {
|
|
|
35574
36688
|
if (!Array.isArray(r.focus_areas)) {
|
|
35575
36689
|
throw new Error(`${prefix}.focus_areas must be an array`);
|
|
35576
36690
|
}
|
|
36691
|
+
if (r.icon !== void 0 && typeof r.icon !== "string") {
|
|
36692
|
+
throw new Error(`${prefix}.icon must be a string if provided (got ${JSON.stringify(r.icon)})`);
|
|
36693
|
+
}
|
|
36694
|
+
if (typeof r.icon !== "string" || r.icon.length === 0) {
|
|
36695
|
+
r.icon = defaultIconFor(r.id, r.tier);
|
|
36696
|
+
}
|
|
36697
|
+
warnIfSuspiciousPersona(`${prefix} ("${r.name}")`, [
|
|
36698
|
+
r.name,
|
|
36699
|
+
r.description,
|
|
36700
|
+
...Array.isArray(r.focus_areas) ? r.focus_areas : [],
|
|
36701
|
+
r.known_for,
|
|
36702
|
+
r.philosophy
|
|
36703
|
+
]);
|
|
35577
36704
|
if (r.known_for !== void 0 && typeof r.known_for !== "string") {
|
|
35578
36705
|
throw new Error(`${prefix}.known_for must be a string if provided`);
|
|
35579
36706
|
}
|
|
@@ -35586,17 +36713,17 @@ function validateReviewersMeta(data) {
|
|
|
35586
36713
|
var syncSubcommand2 = new Command("sync").description("Sync reviewers-meta.json from reviewer markdown files or structured JSON").option("--stdin", "Read reviewers JSON from stdin (for AI-invoked sync)").action(async (options) => {
|
|
35587
36714
|
const targetDir = process.cwd();
|
|
35588
36715
|
requireOcrSetup(targetDir);
|
|
35589
|
-
const ocrDir =
|
|
36716
|
+
const ocrDir = join27(targetDir, ".ocr");
|
|
35590
36717
|
if (!options.stdin) {
|
|
35591
36718
|
try {
|
|
35592
|
-
const reviewersDir =
|
|
35593
|
-
const configPath =
|
|
36719
|
+
const reviewersDir = join27(ocrDir, "skills", "references", "reviewers");
|
|
36720
|
+
const configPath = join27(ocrDir, "config.yaml");
|
|
35594
36721
|
const meta = generateReviewersMeta(reviewersDir, configPath);
|
|
35595
36722
|
if (!meta || meta.reviewers.length === 0) {
|
|
35596
36723
|
console.error(source_default.yellow("No reviewer files found in .ocr/skills/references/reviewers/"));
|
|
35597
36724
|
process.exit(1);
|
|
35598
36725
|
}
|
|
35599
|
-
const metaPath =
|
|
36726
|
+
const metaPath = join27(ocrDir, "reviewers-meta.json");
|
|
35600
36727
|
const tmpPath = metaPath + ".tmp";
|
|
35601
36728
|
writeFileSync9(tmpPath, JSON.stringify(meta, null, 2) + "\n");
|
|
35602
36729
|
renameSync2(tmpPath, metaPath);
|
|
@@ -35626,7 +36753,7 @@ var syncSubcommand2 = new Command("sync").description("Sync reviewers-meta.json
|
|
|
35626
36753
|
throw new Error("Invalid JSON on stdin");
|
|
35627
36754
|
}
|
|
35628
36755
|
const meta = validateReviewersMeta(parsed);
|
|
35629
|
-
const metaPath =
|
|
36756
|
+
const metaPath = join27(ocrDir, "reviewers-meta.json");
|
|
35630
36757
|
const tmpPath = metaPath + ".tmp";
|
|
35631
36758
|
writeFileSync9(tmpPath, JSON.stringify(meta, null, 2) + "\n");
|
|
35632
36759
|
renameSync2(tmpPath, metaPath);
|
|
@@ -35652,26 +36779,74 @@ var syncSubcommand2 = new Command("sync").description("Sync reviewers-meta.json
|
|
|
35652
36779
|
});
|
|
35653
36780
|
var reviewersCommand = new Command("reviewers").description("Manage OCR reviewer metadata").addCommand(syncSubcommand2);
|
|
35654
36781
|
|
|
36782
|
+
// src/commands/host.ts
|
|
36783
|
+
function describeRow(id) {
|
|
36784
|
+
const tool = getToolById(id);
|
|
36785
|
+
const caps = getHostCapabilities(id);
|
|
36786
|
+
return {
|
|
36787
|
+
id,
|
|
36788
|
+
name: tool?.name ?? id,
|
|
36789
|
+
subagentSpawn: caps.subagentSpawn,
|
|
36790
|
+
perTaskModel: caps.perTaskModel,
|
|
36791
|
+
phase4: caps.subagentSpawn ? "parallel-subagents" : "sequential"
|
|
36792
|
+
};
|
|
36793
|
+
}
|
|
36794
|
+
var capabilitiesSubcommand = new Command("capabilities").description("Print host (AI CLI) Phase-4 capabilities").option("--tool <id>", "Show capabilities for a single tool id").option("--json", "Output JSON").action((options) => {
|
|
36795
|
+
if (options.tool) {
|
|
36796
|
+
const id = options.tool.trim().toLowerCase();
|
|
36797
|
+
if (!getToolIds().includes(id)) {
|
|
36798
|
+
console.error(
|
|
36799
|
+
source_default.red(
|
|
36800
|
+
`Error: unknown tool id "${options.tool}". Valid ids: ${getToolIds().join(", ")}`
|
|
36801
|
+
)
|
|
36802
|
+
);
|
|
36803
|
+
process.exit(1);
|
|
36804
|
+
}
|
|
36805
|
+
const row = describeRow(id);
|
|
36806
|
+
if (options.json) {
|
|
36807
|
+
console.log(JSON.stringify(row, null, 2));
|
|
36808
|
+
} else {
|
|
36809
|
+
printRows([row]);
|
|
36810
|
+
}
|
|
36811
|
+
return;
|
|
36812
|
+
}
|
|
36813
|
+
const rows = AI_TOOLS.map((t) => describeRow(t.id));
|
|
36814
|
+
if (options.json) {
|
|
36815
|
+
console.log(JSON.stringify(rows, null, 2));
|
|
36816
|
+
} else {
|
|
36817
|
+
printRows(rows);
|
|
36818
|
+
}
|
|
36819
|
+
});
|
|
36820
|
+
function printRows(rows) {
|
|
36821
|
+
const yn = (v) => v ? source_default.green("yes") : source_default.dim("no");
|
|
36822
|
+
for (const row of rows) {
|
|
36823
|
+
console.log(
|
|
36824
|
+
`${source_default.bold(row.name.padEnd(20))} subagentSpawn=${yn(row.subagentSpawn)} perTaskModel=${yn(row.perTaskModel)} \u2192 ${source_default.cyan(row.phase4)}`
|
|
36825
|
+
);
|
|
36826
|
+
}
|
|
36827
|
+
}
|
|
36828
|
+
var hostCommand = new Command("host").description("Inspect host (AI CLI) capabilities").addCommand(capabilitiesSubcommand);
|
|
36829
|
+
|
|
35655
36830
|
// src/lib/update-check.ts
|
|
35656
36831
|
import { homedir } from "node:os";
|
|
35657
|
-
import { join as
|
|
35658
|
-
import { readFileSync as
|
|
36832
|
+
import { join as join28 } from "node:path";
|
|
36833
|
+
import { readFileSync as readFileSync15, writeFileSync as writeFileSync10, mkdirSync as mkdirSync8 } from "node:fs";
|
|
35659
36834
|
var PACKAGE_NAME = "@open-code-review/cli";
|
|
35660
36835
|
var REGISTRY_URL = `https://registry.npmjs.org/${PACKAGE_NAME}/latest`;
|
|
35661
|
-
var CACHE_DIR2 =
|
|
35662
|
-
var CACHE_FILE =
|
|
36836
|
+
var CACHE_DIR2 = join28(homedir(), ".ocr");
|
|
36837
|
+
var CACHE_FILE = join28(CACHE_DIR2, "update-check.json");
|
|
35663
36838
|
var CHECK_INTERVAL_MS = 4 * 60 * 60 * 1e3;
|
|
35664
36839
|
var FETCH_TIMEOUT_MS = 3e3;
|
|
35665
36840
|
function readCache(cacheFile) {
|
|
35666
36841
|
try {
|
|
35667
|
-
return JSON.parse(
|
|
36842
|
+
return JSON.parse(readFileSync15(cacheFile, "utf-8"));
|
|
35668
36843
|
} catch {
|
|
35669
36844
|
return null;
|
|
35670
36845
|
}
|
|
35671
36846
|
}
|
|
35672
36847
|
function writeCache(cacheFile, cache) {
|
|
35673
36848
|
try {
|
|
35674
|
-
|
|
36849
|
+
mkdirSync8(join28(cacheFile, ".."), { recursive: true });
|
|
35675
36850
|
writeFileSync10(cacheFile, JSON.stringify(cache));
|
|
35676
36851
|
} catch {
|
|
35677
36852
|
}
|
|
@@ -35692,7 +36867,7 @@ async function checkForUpdate(currentVersion, options) {
|
|
|
35692
36867
|
if (process.env.CI || process.env.OCR_NO_UPDATE_CHECK) {
|
|
35693
36868
|
return null;
|
|
35694
36869
|
}
|
|
35695
|
-
const cacheFile =
|
|
36870
|
+
const cacheFile = join28(options?.cacheDir ?? CACHE_DIR2, "update-check.json");
|
|
35696
36871
|
try {
|
|
35697
36872
|
const cache = readCache(cacheFile);
|
|
35698
36873
|
if (cache && Date.now() - cache.lastCheck < CHECK_INTERVAL_MS) {
|
|
@@ -35751,7 +36926,9 @@ program2.addCommand(reviewCommand);
|
|
|
35751
36926
|
program2.addCommand(updateCommand);
|
|
35752
36927
|
program2.addCommand(dashboardCommand);
|
|
35753
36928
|
program2.addCommand(doctorCommand);
|
|
36929
|
+
program2.addCommand(dbCommand);
|
|
35754
36930
|
program2.addCommand(reviewersCommand);
|
|
36931
|
+
program2.addCommand(hostCommand);
|
|
35755
36932
|
await program2.parseAsync();
|
|
35756
36933
|
if (subcommand && HUMAN_COMMANDS.has(subcommand)) {
|
|
35757
36934
|
const drift = checkLocalArtifactVersion(process.cwd(), CLI_VERSION);
|