@open-code-review/cli 2.1.0 → 2.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/dashboard/client/assets/{_basePickBy-B3ALyupE.js → _basePickBy-BAlGnwHG.js} +1 -1
- package/dist/dashboard/client/assets/{_baseUniq-b2RALAWc.js → _baseUniq-CoauyOeL.js} +1 -1
- package/dist/dashboard/client/assets/{arc-DcSVvhUd.js → arc-DtS0aHfP.js} +1 -1
- package/dist/dashboard/client/assets/{architectureDiagram-VXUJARFQ-BNUlmSCS.js → architectureDiagram-VXUJARFQ-CnWmtRTh.js} +1 -1
- package/dist/dashboard/client/assets/{blockDiagram-VD42YOAC-BmhiQVwa.js → blockDiagram-VD42YOAC-DgPp4oGV.js} +1 -1
- package/dist/dashboard/client/assets/{c4Diagram-YG6GDRKO-jyJ3WOv5.js → c4Diagram-YG6GDRKO--LV4qQaE.js} +1 -1
- package/dist/dashboard/client/assets/channel-BU2129fl.js +1 -0
- package/dist/dashboard/client/assets/{chunk-4BX2VUAB-x1dQU_s3.js → chunk-4BX2VUAB-BRglpc7Z.js} +1 -1
- package/dist/dashboard/client/assets/{chunk-55IACEB6-CwbsE2XQ.js → chunk-55IACEB6-Bgx06_CV.js} +1 -1
- package/dist/dashboard/client/assets/{chunk-B4BG7PRW-BaE7c-ti.js → chunk-B4BG7PRW-D6HN3Yiy.js} +1 -1
- package/dist/dashboard/client/assets/{chunk-DI55MBZ5-Bw5PUaMK.js → chunk-DI55MBZ5-NH9EgN9T.js} +1 -1
- package/dist/dashboard/client/assets/{chunk-FMBD7UC4-B7cF6P3s.js → chunk-FMBD7UC4-xriO6WNP.js} +1 -1
- package/dist/dashboard/client/assets/{chunk-QN33PNHL-OY4evNHd.js → chunk-QN33PNHL-CV1h6_Zl.js} +1 -1
- package/dist/dashboard/client/assets/{chunk-QZHKN3VN-BpjQwIWz.js → chunk-QZHKN3VN-CV4VzxNq.js} +1 -1
- package/dist/dashboard/client/assets/{chunk-TZMSLE5B-D8b_Oq9B.js → chunk-TZMSLE5B-isdklocW.js} +1 -1
- package/dist/dashboard/client/assets/classDiagram-2ON5EDUG-CVftFGiR.js +1 -0
- package/dist/dashboard/client/assets/classDiagram-v2-WZHVMYZB-CVftFGiR.js +1 -0
- package/dist/dashboard/client/assets/clone-DC6LEEC5.js +1 -0
- package/dist/dashboard/client/assets/{cose-bilkent-S5V4N54A-C-sfP8PN.js → cose-bilkent-S5V4N54A-CCzlFSJf.js} +1 -1
- package/dist/dashboard/client/assets/{dagre-6UL2VRFP-Cqfo0NRg.js → dagre-6UL2VRFP-DVN3PkjZ.js} +1 -1
- package/dist/dashboard/client/assets/{diagram-PSM6KHXK-BR3ppxqI.js → diagram-PSM6KHXK-SzJVoSsb.js} +1 -1
- package/dist/dashboard/client/assets/{diagram-QEK2KX5R-Dvcx6x3R.js → diagram-QEK2KX5R-CgGn7ts-.js} +1 -1
- package/dist/dashboard/client/assets/{diagram-S2PKOQOG-DoyBLnVN.js → diagram-S2PKOQOG-Bz1ukSx8.js} +1 -1
- package/dist/dashboard/client/assets/{erDiagram-Q2GNP2WA-hy77l1cL.js → erDiagram-Q2GNP2WA-CpstUTMZ.js} +1 -1
- package/dist/dashboard/client/assets/{flowDiagram-NV44I4VS-Bz0B1rKM.js → flowDiagram-NV44I4VS-aYVydGhp.js} +1 -1
- package/dist/dashboard/client/assets/{ganttDiagram-JELNMOA3-CLgrZPoC.js → ganttDiagram-JELNMOA3-Cb2DUSRk.js} +1 -1
- package/dist/dashboard/client/assets/{gitGraphDiagram-V2S2FVAM-DwJ-1f-v.js → gitGraphDiagram-V2S2FVAM-BUOnwA2w.js} +1 -1
- package/dist/dashboard/client/assets/{graph-DDBMM_t2.js → graph-4X5ddhLp.js} +1 -1
- package/dist/dashboard/client/assets/index-CKWqYAfu.js +581 -0
- package/dist/dashboard/client/assets/index-CzxeSSaQ.css +1 -0
- package/dist/dashboard/client/assets/{infoDiagram-HS3SLOUP-Bhn1FmAk.js → infoDiagram-HS3SLOUP-BlMqcrwm.js} +1 -1
- package/dist/dashboard/client/assets/{journeyDiagram-XKPGCS4Q-CzGbjX1y.js → journeyDiagram-XKPGCS4Q-DF2ew7ju.js} +1 -1
- package/dist/dashboard/client/assets/{kanban-definition-3W4ZIXB7-Da77-WYk.js → kanban-definition-3W4ZIXB7-BKQMx0-n.js} +1 -1
- package/dist/dashboard/client/assets/{layout-CVwSB-GS.js → layout-DNcn2g9w.js} +1 -1
- package/dist/dashboard/client/assets/{linear-CTRAc5Jn.js → linear-Bqy9gvqb.js} +1 -1
- package/dist/dashboard/client/assets/{mermaid-renderer-Bjo170ax.js → mermaid-renderer-dJ71wgld.js} +4 -4
- package/dist/dashboard/client/assets/{mindmap-definition-VGOIOE7T-B55C2odl.js → mindmap-definition-VGOIOE7T-BARc8sqJ.js} +1 -1
- package/dist/dashboard/client/assets/{pieDiagram-ADFJNKIX-5lrQLrSz.js → pieDiagram-ADFJNKIX-CULlNZTd.js} +1 -1
- package/dist/dashboard/client/assets/{quadrantDiagram-AYHSOK5B-Bg55gC30.js → quadrantDiagram-AYHSOK5B-BJEZPVe9.js} +1 -1
- package/dist/dashboard/client/assets/{requirementDiagram-UZGBJVZJ-CyR4YFJY.js → requirementDiagram-UZGBJVZJ-BhMsmUIs.js} +1 -1
- package/dist/dashboard/client/assets/{sankeyDiagram-TZEHDZUN-BVWKr9_-.js → sankeyDiagram-TZEHDZUN-BYbNgogG.js} +1 -1
- package/dist/dashboard/client/assets/{sequenceDiagram-WL72ISMW-D0AJg_tE.js → sequenceDiagram-WL72ISMW-MoM_NwWk.js} +1 -1
- package/dist/dashboard/client/assets/{stateDiagram-FKZM4ZOC-BuHpTgim.js → stateDiagram-FKZM4ZOC-ditrlbM3.js} +1 -1
- package/dist/dashboard/client/assets/stateDiagram-v2-4FDKWEC3-SqoG2LCn.js +1 -0
- package/dist/dashboard/client/assets/{timeline-definition-IT6M3QCI-LDhpAmDd.js → timeline-definition-IT6M3QCI-DOAJyjuz.js} +1 -1
- package/dist/dashboard/client/assets/{treemap-GDKQZRPO-Dd4gjvUl.js → treemap-GDKQZRPO-BBJkjnJl.js} +1 -1
- package/dist/dashboard/client/assets/{xychartDiagram-PRI3JC2R-B9RDod39.js → xychartDiagram-PRI3JC2R-CPW4s5vm.js} +1 -1
- package/dist/dashboard/client/index.html +2 -2
- package/dist/dashboard/server.js +1188 -579
- package/dist/index.js +1395 -335
- package/dist/lib/db/index.js +485 -24
- package/dist/lib/models.js +125 -50
- package/dist/lib/runtime-config.js +29 -13
- package/dist/lib/state/index.js +2196 -0
- package/package.json +8 -2
- 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-Cr9yEo_B.js +0 -576
- 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
|
@@ -16017,6 +16017,86 @@ var require_emoji_regex2 = __commonJS({
|
|
|
16017
16017
|
}
|
|
16018
16018
|
});
|
|
16019
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
|
+
async function execBinaryAsync(binary, args, opts) {
|
|
16038
|
+
return execFilePromise(binary, args, {
|
|
16039
|
+
...opts,
|
|
16040
|
+
shell: isWindows
|
|
16041
|
+
});
|
|
16042
|
+
}
|
|
16043
|
+
function isProcessAlive(pid) {
|
|
16044
|
+
try {
|
|
16045
|
+
process.kill(pid, 0);
|
|
16046
|
+
return true;
|
|
16047
|
+
} catch (err) {
|
|
16048
|
+
return !(err instanceof Error && "code" in err && err.code === "ESRCH");
|
|
16049
|
+
}
|
|
16050
|
+
}
|
|
16051
|
+
function defaultIconFor(id, tier) {
|
|
16052
|
+
return BUILTIN_ICON_MAP[id] ?? (tier === "persona" ? "brain" : "user");
|
|
16053
|
+
}
|
|
16054
|
+
function hostCapabilitiesFor(vendor) {
|
|
16055
|
+
return vendor && HOST_CAPABILITIES[vendor] || DEFAULT_HOST_CAPABILITIES;
|
|
16056
|
+
}
|
|
16057
|
+
var execFilePromise, isWindows, BUILTIN_ICON_MAP, DEFAULT_HOST_CAPABILITIES, HOST_CAPABILITIES;
|
|
16058
|
+
var init_src = __esm({
|
|
16059
|
+
"../shared/platform/src/index.ts"() {
|
|
16060
|
+
"use strict";
|
|
16061
|
+
execFilePromise = promisify(execFile);
|
|
16062
|
+
isWindows = process.platform === "win32";
|
|
16063
|
+
BUILTIN_ICON_MAP = {
|
|
16064
|
+
architect: "blocks",
|
|
16065
|
+
fullstack: "layers",
|
|
16066
|
+
reliability: "activity",
|
|
16067
|
+
"staff-engineer": "compass",
|
|
16068
|
+
principal: "crown",
|
|
16069
|
+
frontend: "layout",
|
|
16070
|
+
backend: "server",
|
|
16071
|
+
infrastructure: "cloud",
|
|
16072
|
+
performance: "gauge",
|
|
16073
|
+
accessibility: "accessibility",
|
|
16074
|
+
data: "database",
|
|
16075
|
+
devops: "rocket",
|
|
16076
|
+
dx: "terminal",
|
|
16077
|
+
mobile: "smartphone",
|
|
16078
|
+
security: "shield-alert",
|
|
16079
|
+
quality: "sparkles",
|
|
16080
|
+
testing: "test-tubes",
|
|
16081
|
+
ai: "bot",
|
|
16082
|
+
"docs-writer": "file-text"
|
|
16083
|
+
};
|
|
16084
|
+
DEFAULT_HOST_CAPABILITIES = {
|
|
16085
|
+
subagentSpawn: false,
|
|
16086
|
+
perTaskModel: false
|
|
16087
|
+
};
|
|
16088
|
+
HOST_CAPABILITIES = {
|
|
16089
|
+
// Claude Code: Task tool + per-subagent model frontmatter.
|
|
16090
|
+
claude: { subagentSpawn: true, perTaskModel: true },
|
|
16091
|
+
// OpenCode: `--agent` sub-agent primitive, but no per-task model override.
|
|
16092
|
+
opencode: { subagentSpawn: true, perTaskModel: false },
|
|
16093
|
+
// Gemini CLI / Codex: no in-agent Task primitive → sequential Phase 4.
|
|
16094
|
+
gemini: { subagentSpawn: false, perTaskModel: false },
|
|
16095
|
+
codex: { subagentSpawn: false, perTaskModel: false }
|
|
16096
|
+
};
|
|
16097
|
+
}
|
|
16098
|
+
});
|
|
16099
|
+
|
|
16020
16100
|
// ../../node_modules/.pnpm/yaml@2.8.3/node_modules/yaml/dist/nodes/identity.js
|
|
16021
16101
|
var require_identity = __commonJS({
|
|
16022
16102
|
"../../node_modules/.pnpm/yaml@2.8.3/node_modules/yaml/dist/nodes/identity.js"(exports) {
|
|
@@ -24044,6 +24124,35 @@ var init_migrations = __esm({
|
|
|
24044
24124
|
db.run("DROP INDEX IF EXISTS idx_command_executions_parent;");
|
|
24045
24125
|
db.run("ALTER TABLE command_executions DROP COLUMN parent_id;");
|
|
24046
24126
|
}
|
|
24127
|
+
},
|
|
24128
|
+
{
|
|
24129
|
+
version: 14,
|
|
24130
|
+
description: "Self-heal markdown_artifacts duplication: collapse NULL-round duplicate rows and add a NULL-safe unique index so the dedup bug cannot recur",
|
|
24131
|
+
// The table's `UNIQUE(session_id, artifact_type, round_number, file_path)`
|
|
24132
|
+
// never deduped session-level artifacts because SQLite treats NULL ≠ NULL,
|
|
24133
|
+
// and the writer used `INSERT OR REPLACE` — so every re-parse of a
|
|
24134
|
+
// NULL-round artifact (context.md, map.md, …) appended a duplicate (one
|
|
24135
|
+
// context.md reached 775 identical rows, ~177 MB). The writer is now an
|
|
24136
|
+
// explicit UPDATE-or-INSERT; this migration heals existing DBs and adds a
|
|
24137
|
+
// NULL-collapsing unique index as a DB-level backstop.
|
|
24138
|
+
//
|
|
24139
|
+
// Orphan-row sweep (FK-dangling children from the pre-FK-enforcement era)
|
|
24140
|
+
// is intentionally NOT done here — it needs `PRAGMA foreign_keys = OFF`,
|
|
24141
|
+
// which is a no-op inside the migration transaction. `ocr db doctor --fix`
|
|
24142
|
+
// performs it outside a transaction.
|
|
24143
|
+
run: (db) => {
|
|
24144
|
+
db.run(`
|
|
24145
|
+
DELETE FROM markdown_artifacts
|
|
24146
|
+
WHERE rowid NOT IN (
|
|
24147
|
+
SELECT MAX(rowid) FROM markdown_artifacts
|
|
24148
|
+
GROUP BY session_id, artifact_type, IFNULL(round_number, -1), file_path
|
|
24149
|
+
)
|
|
24150
|
+
`);
|
|
24151
|
+
db.run(`
|
|
24152
|
+
CREATE UNIQUE INDEX IF NOT EXISTS idx_markdown_artifacts_logical
|
|
24153
|
+
ON markdown_artifacts(session_id, artifact_type, IFNULL(round_number, -1), file_path)
|
|
24154
|
+
`);
|
|
24155
|
+
}
|
|
24047
24156
|
}
|
|
24048
24157
|
];
|
|
24049
24158
|
}
|
|
@@ -24172,7 +24281,7 @@ var init_queries = __esm({
|
|
|
24172
24281
|
|
|
24173
24282
|
// src/lib/db/reconcile.ts
|
|
24174
24283
|
import { existsSync as existsSync10 } from "node:fs";
|
|
24175
|
-
import { isAbsolute as isAbsolute2, join as join12, dirname as
|
|
24284
|
+
import { isAbsolute as isAbsolute2, join as join12, dirname as dirname5 } from "node:path";
|
|
24176
24285
|
function hasTerminalArtifactEvent(db, sessionId, workflowType, currentRound, currentMapRun) {
|
|
24177
24286
|
const eventType = workflowType === "map" ? "map_completed" : "round_completed";
|
|
24178
24287
|
const round = workflowType === "map" ? currentMapRun : currentRound;
|
|
@@ -24213,7 +24322,7 @@ function hasInFlightDependents(db, sessionId) {
|
|
|
24213
24322
|
function resolveSessionDir(ocrDir, sessionDir) {
|
|
24214
24323
|
if (!sessionDir) return null;
|
|
24215
24324
|
if (isAbsolute2(sessionDir)) return sessionDir;
|
|
24216
|
-
return join12(
|
|
24325
|
+
return join12(dirname5(ocrDir), sessionDir);
|
|
24217
24326
|
}
|
|
24218
24327
|
function reconcileLegacyState(db, ocrDir, opts = {}) {
|
|
24219
24328
|
const dryRun = opts.dryRun ?? false;
|
|
@@ -24331,7 +24440,7 @@ var init_liveness = __esm({
|
|
|
24331
24440
|
});
|
|
24332
24441
|
|
|
24333
24442
|
// src/lib/state/exit-codes.ts
|
|
24334
|
-
var STATE_EXIT, StateError, CANCELLED_EXIT_CODE, ORPHAN_EXIT_CODE, CASCADE_CLOSE_EXIT_CODE;
|
|
24443
|
+
var STATE_EXIT, StateError, CANCELLED_EXIT_CODE, ORPHAN_EXIT_CODE, CASCADE_CLOSE_EXIT_CODE, WATCHDOG_DEADLINE_EXIT_CODE;
|
|
24335
24444
|
var init_exit_codes = __esm({
|
|
24336
24445
|
"src/lib/state/exit-codes.ts"() {
|
|
24337
24446
|
"use strict";
|
|
@@ -24356,6 +24465,7 @@ var init_exit_codes = __esm({
|
|
|
24356
24465
|
CANCELLED_EXIT_CODE = -2;
|
|
24357
24466
|
ORPHAN_EXIT_CODE = -3;
|
|
24358
24467
|
CASCADE_CLOSE_EXIT_CODE = -4;
|
|
24468
|
+
WATCHDOG_DEADLINE_EXIT_CODE = -5;
|
|
24359
24469
|
}
|
|
24360
24470
|
});
|
|
24361
24471
|
|
|
@@ -24738,24 +24848,431 @@ var init_agent_sessions = __esm({
|
|
|
24738
24848
|
}
|
|
24739
24849
|
});
|
|
24740
24850
|
|
|
24851
|
+
// src/lib/db/maintenance.ts
|
|
24852
|
+
import {
|
|
24853
|
+
existsSync as existsSync11,
|
|
24854
|
+
readdirSync as readdirSync5,
|
|
24855
|
+
statSync,
|
|
24856
|
+
unlinkSync as unlinkSync3,
|
|
24857
|
+
copyFileSync
|
|
24858
|
+
} from "node:fs";
|
|
24859
|
+
import { dirname as dirname6, join as join13, basename as basename7 } from "node:path";
|
|
24860
|
+
function withForeignKeysDisabled(db, fn) {
|
|
24861
|
+
db.pragma("foreign_keys = OFF");
|
|
24862
|
+
try {
|
|
24863
|
+
return fn();
|
|
24864
|
+
} finally {
|
|
24865
|
+
db.pragma("foreign_keys = ON");
|
|
24866
|
+
}
|
|
24867
|
+
}
|
|
24868
|
+
function scalarInt(db, sql) {
|
|
24869
|
+
const r = db.exec(sql);
|
|
24870
|
+
const v = r[0]?.values[0]?.[0];
|
|
24871
|
+
return typeof v === "number" ? v : Number(v ?? 0);
|
|
24872
|
+
}
|
|
24873
|
+
function foreignKeyViolationGroups(db) {
|
|
24874
|
+
const r = db.exec("PRAGMA foreign_key_check");
|
|
24875
|
+
const rows = r[0]?.values ?? [];
|
|
24876
|
+
const counts = /* @__PURE__ */ new Map();
|
|
24877
|
+
for (const row of rows) {
|
|
24878
|
+
const table = String(row[0]);
|
|
24879
|
+
counts.set(table, (counts.get(table) ?? 0) + 1);
|
|
24880
|
+
}
|
|
24881
|
+
return [...counts.entries()].map(([table, count]) => ({ table, count })).sort((a, b) => b.count - a.count);
|
|
24882
|
+
}
|
|
24883
|
+
function scanOrphanTempFiles(dataDir) {
|
|
24884
|
+
let entries;
|
|
24885
|
+
try {
|
|
24886
|
+
entries = readdirSync5(dataDir);
|
|
24887
|
+
} catch {
|
|
24888
|
+
return [];
|
|
24889
|
+
}
|
|
24890
|
+
const out = [];
|
|
24891
|
+
for (const name of entries) {
|
|
24892
|
+
const m = name.match(/^ocr\.db\.(\d+)\.tmp$/);
|
|
24893
|
+
if (!m) continue;
|
|
24894
|
+
const pid = Number(m[1]);
|
|
24895
|
+
let ageMs = 0;
|
|
24896
|
+
try {
|
|
24897
|
+
ageMs = Date.now() - statSync(join13(dataDir, name)).mtimeMs;
|
|
24898
|
+
} catch {
|
|
24899
|
+
continue;
|
|
24900
|
+
}
|
|
24901
|
+
const alive = isProcessAlive(pid);
|
|
24902
|
+
out.push({
|
|
24903
|
+
name,
|
|
24904
|
+
pid,
|
|
24905
|
+
ageMs,
|
|
24906
|
+
// Reapable only when the writer PID is dead AND the file is old enough
|
|
24907
|
+
// that no live mid-write could plausibly own it.
|
|
24908
|
+
reapable: !alive && ageMs > ONE_HOUR_MS
|
|
24909
|
+
});
|
|
24910
|
+
}
|
|
24911
|
+
return out;
|
|
24912
|
+
}
|
|
24913
|
+
function scanBackupFiles(dataDir, dbBase) {
|
|
24914
|
+
let entries;
|
|
24915
|
+
try {
|
|
24916
|
+
entries = readdirSync5(dataDir);
|
|
24917
|
+
} catch {
|
|
24918
|
+
return [];
|
|
24919
|
+
}
|
|
24920
|
+
const out = [];
|
|
24921
|
+
for (const name of entries) {
|
|
24922
|
+
if (!name.startsWith(`${dbBase}.bak`)) continue;
|
|
24923
|
+
try {
|
|
24924
|
+
out.push({ name, sizeBytes: statSync(join13(dataDir, name)).size });
|
|
24925
|
+
} catch {
|
|
24926
|
+
}
|
|
24927
|
+
}
|
|
24928
|
+
return out.sort((a, b) => b.sizeBytes - a.sizeBytes);
|
|
24929
|
+
}
|
|
24930
|
+
function collectDbHealth(db, dbPath) {
|
|
24931
|
+
const dataDir = dirname6(dbPath);
|
|
24932
|
+
const dbBase = basename7(dbPath);
|
|
24933
|
+
const pageSize = scalarInt(db, "PRAGMA page_size");
|
|
24934
|
+
const pageCount = scalarInt(db, "PRAGMA page_count");
|
|
24935
|
+
const freelistCount = scalarInt(db, "PRAGMA freelist_count");
|
|
24936
|
+
const integ = db.exec("PRAGMA integrity_check");
|
|
24937
|
+
const integRows = (integ[0]?.values ?? []).map((v) => String(v[0]));
|
|
24938
|
+
const integrityOk = integRows.length === 1 && integRows[0] === "ok";
|
|
24939
|
+
const allGroups = foreignKeyViolationGroups(db);
|
|
24940
|
+
const fkViolations = allGroups.filter((g) => !PROTECTED_TABLES.has(g.table));
|
|
24941
|
+
const protectedFkViolations = allGroups.filter(
|
|
24942
|
+
(g) => PROTECTED_TABLES.has(g.table)
|
|
24943
|
+
);
|
|
24944
|
+
const fileSizeBytes = existsSync11(dbPath) ? statSync(dbPath).size : 0;
|
|
24945
|
+
return {
|
|
24946
|
+
dbPath,
|
|
24947
|
+
fileSizeBytes,
|
|
24948
|
+
pageSize,
|
|
24949
|
+
pageCount,
|
|
24950
|
+
freelistCount,
|
|
24951
|
+
reclaimableBytes: freelistCount * pageSize,
|
|
24952
|
+
integrityOk,
|
|
24953
|
+
integrityErrors: integrityOk ? [] : integRows,
|
|
24954
|
+
fkViolations,
|
|
24955
|
+
protectedFkViolations,
|
|
24956
|
+
totalFkViolations: allGroups.reduce((n, g) => n + g.count, 0),
|
|
24957
|
+
markdownDuplicateRows: scalarInt(
|
|
24958
|
+
db,
|
|
24959
|
+
`SELECT COALESCE(SUM(cnt - 1), 0) FROM (
|
|
24960
|
+
SELECT COUNT(*) AS cnt FROM markdown_artifacts
|
|
24961
|
+
GROUP BY session_id, artifact_type, IFNULL(round_number, -1), file_path
|
|
24962
|
+
HAVING cnt > 1)`
|
|
24963
|
+
),
|
|
24964
|
+
orphanTempFiles: scanOrphanTempFiles(dataDir),
|
|
24965
|
+
backupFiles: scanBackupFiles(dataDir, dbBase),
|
|
24966
|
+
eventCount: scalarInt(db, "SELECT COUNT(*) FROM orchestration_events"),
|
|
24967
|
+
sessionCount: scalarInt(db, "SELECT COUNT(*) FROM sessions")
|
|
24968
|
+
};
|
|
24969
|
+
}
|
|
24970
|
+
function snapshotDb(db, dbPath, label = "doctor") {
|
|
24971
|
+
try {
|
|
24972
|
+
if (!existsSync11(dbPath) || statSync(dbPath).size === 0) return null;
|
|
24973
|
+
db.pragma("wal_checkpoint(TRUNCATE)");
|
|
24974
|
+
const ts = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
24975
|
+
const bakPath = `${dbPath}.bak.${label}.${ts}`;
|
|
24976
|
+
copyFileSync(dbPath, bakPath);
|
|
24977
|
+
return bakPath;
|
|
24978
|
+
} catch {
|
|
24979
|
+
return null;
|
|
24980
|
+
}
|
|
24981
|
+
}
|
|
24982
|
+
function reapOrphanDbFiles(dataDir) {
|
|
24983
|
+
const reaped = [];
|
|
24984
|
+
for (const f of scanOrphanTempFiles(dataDir)) {
|
|
24985
|
+
if (!f.reapable) continue;
|
|
24986
|
+
try {
|
|
24987
|
+
unlinkSync3(join13(dataDir, f.name));
|
|
24988
|
+
reaped.push(f.name);
|
|
24989
|
+
} catch {
|
|
24990
|
+
}
|
|
24991
|
+
}
|
|
24992
|
+
return reaped;
|
|
24993
|
+
}
|
|
24994
|
+
function reapStaleExecLogs(execLogsDir, maxAgeMs = SEVEN_DAYS_MS) {
|
|
24995
|
+
let entries;
|
|
24996
|
+
try {
|
|
24997
|
+
entries = readdirSync5(execLogsDir);
|
|
24998
|
+
} catch {
|
|
24999
|
+
return [];
|
|
25000
|
+
}
|
|
25001
|
+
const cutoff = Date.now() - maxAgeMs;
|
|
25002
|
+
const reaped = [];
|
|
25003
|
+
for (const name of entries) {
|
|
25004
|
+
if (!name.endsWith(".log")) continue;
|
|
25005
|
+
const full = join13(execLogsDir, name);
|
|
25006
|
+
try {
|
|
25007
|
+
if (statSync(full).mtimeMs > cutoff) continue;
|
|
25008
|
+
unlinkSync3(full);
|
|
25009
|
+
reaped.push(name);
|
|
25010
|
+
} catch {
|
|
25011
|
+
}
|
|
25012
|
+
}
|
|
25013
|
+
return reaped;
|
|
25014
|
+
}
|
|
25015
|
+
function pruneBackups(dataDir, dbPath, opts = {}) {
|
|
25016
|
+
const keep = opts.keep ?? 1;
|
|
25017
|
+
if (!Number.isInteger(keep) || keep < 0) {
|
|
25018
|
+
throw new Error(
|
|
25019
|
+
`pruneBackups: keep must be a non-negative integer (got ${String(keep)})`
|
|
25020
|
+
);
|
|
25021
|
+
}
|
|
25022
|
+
const dryRun = opts.dryRun ?? false;
|
|
25023
|
+
const dbBase = basename7(dbPath);
|
|
25024
|
+
const withMtime = [];
|
|
25025
|
+
for (const file of scanBackupFiles(dataDir, dbBase)) {
|
|
25026
|
+
try {
|
|
25027
|
+
withMtime.push({ file, mtimeMs: statSync(join13(dataDir, file.name)).mtimeMs });
|
|
25028
|
+
} catch {
|
|
25029
|
+
}
|
|
25030
|
+
}
|
|
25031
|
+
withMtime.sort((a, b) => b.mtimeMs - a.mtimeMs);
|
|
25032
|
+
const kept = withMtime.slice(0, keep).map((x) => x.file);
|
|
25033
|
+
const toDelete = withMtime.slice(keep).map((x) => x.file);
|
|
25034
|
+
const deleted = [];
|
|
25035
|
+
if (!dryRun) {
|
|
25036
|
+
for (const b of toDelete) {
|
|
25037
|
+
try {
|
|
25038
|
+
unlinkSync3(join13(dataDir, b.name));
|
|
25039
|
+
deleted.push(b);
|
|
25040
|
+
} catch {
|
|
25041
|
+
}
|
|
25042
|
+
}
|
|
25043
|
+
}
|
|
25044
|
+
const reported = dryRun ? toDelete : deleted;
|
|
25045
|
+
return {
|
|
25046
|
+
dryRun,
|
|
25047
|
+
deleted: reported,
|
|
25048
|
+
kept,
|
|
25049
|
+
reclaimedBytes: reported.reduce((n, b) => n + b.sizeBytes, 0)
|
|
25050
|
+
};
|
|
25051
|
+
}
|
|
25052
|
+
function fixDb(db, dbPath, opts = {}) {
|
|
25053
|
+
const dataDir = dirname6(dbPath);
|
|
25054
|
+
const sizeBeforeBytes = existsSync11(dbPath) ? statSync(dbPath).size : 0;
|
|
25055
|
+
const snapshotPath = opts.snapshot === false ? null : snapshotDb(db, dbPath, "doctor");
|
|
25056
|
+
const fkOrphansDeleted = [];
|
|
25057
|
+
withForeignKeysDisabled(db, () => {
|
|
25058
|
+
db.transaction(() => {
|
|
25059
|
+
for (const sweep of ORPHAN_SWEEPS) {
|
|
25060
|
+
const info = db.prepare(sweep.sql).run();
|
|
25061
|
+
const count = Number(info.changes);
|
|
25062
|
+
if (count > 0) fkOrphansDeleted.push({ table: sweep.table, count });
|
|
25063
|
+
}
|
|
25064
|
+
});
|
|
25065
|
+
});
|
|
25066
|
+
let markdownDupsDeleted = 0;
|
|
25067
|
+
db.transaction(() => {
|
|
25068
|
+
const info = db.prepare(MARKDOWN_DEDUP_SQL).run();
|
|
25069
|
+
markdownDupsDeleted = Number(info.changes);
|
|
25070
|
+
});
|
|
25071
|
+
const tempsReaped = opts.reapTemps === false ? [] : reapOrphanDbFiles(dataDir);
|
|
25072
|
+
let vacuumed = false;
|
|
25073
|
+
if (opts.vacuum !== false) {
|
|
25074
|
+
try {
|
|
25075
|
+
db.pragma("wal_checkpoint(TRUNCATE)");
|
|
25076
|
+
db.run("VACUUM");
|
|
25077
|
+
vacuumed = true;
|
|
25078
|
+
} catch {
|
|
25079
|
+
vacuumed = false;
|
|
25080
|
+
}
|
|
25081
|
+
}
|
|
25082
|
+
const post = collectDbHealth(db, dbPath);
|
|
25083
|
+
return {
|
|
25084
|
+
snapshotPath,
|
|
25085
|
+
fkOrphansDeleted,
|
|
25086
|
+
totalFkOrphansDeleted: fkOrphansDeleted.reduce((n, g) => n + g.count, 0),
|
|
25087
|
+
protectedViolationsRemaining: post.protectedFkViolations,
|
|
25088
|
+
markdownDupsDeleted,
|
|
25089
|
+
tempsReaped,
|
|
25090
|
+
vacuumed,
|
|
25091
|
+
sizeBeforeBytes,
|
|
25092
|
+
sizeAfterBytes: post.fileSizeBytes,
|
|
25093
|
+
integrityOkAfter: post.integrityOk,
|
|
25094
|
+
fkViolationsAfter: post.totalFkViolations
|
|
25095
|
+
};
|
|
25096
|
+
}
|
|
25097
|
+
function vacuumDb(db, dbPath, opts = {}) {
|
|
25098
|
+
const sizeBeforeBytes = existsSync11(dbPath) ? statSync(dbPath).size : 0;
|
|
25099
|
+
const snapshotPath = opts.snapshot === false ? null : snapshotDb(db, dbPath, "vacuum");
|
|
25100
|
+
db.pragma("wal_checkpoint(TRUNCATE)");
|
|
25101
|
+
db.run("VACUUM");
|
|
25102
|
+
db.pragma("wal_checkpoint(TRUNCATE)");
|
|
25103
|
+
const sizeAfterBytes = existsSync11(dbPath) ? statSync(dbPath).size : 0;
|
|
25104
|
+
return {
|
|
25105
|
+
snapshotPath,
|
|
25106
|
+
sizeBeforeBytes,
|
|
25107
|
+
sizeAfterBytes,
|
|
25108
|
+
reclaimedBytes: Math.max(0, sizeBeforeBytes - sizeAfterBytes)
|
|
25109
|
+
};
|
|
25110
|
+
}
|
|
25111
|
+
function countSessionArtifacts(db, sessionId) {
|
|
25112
|
+
const r = db.exec(
|
|
25113
|
+
`SELECT
|
|
25114
|
+
(SELECT COUNT(*) FROM markdown_artifacts WHERE session_id = ?) +
|
|
25115
|
+
(SELECT COUNT(*) FROM review_rounds WHERE session_id = ?) +
|
|
25116
|
+
(SELECT COUNT(*) FROM reviewer_outputs ro JOIN review_rounds rr ON ro.round_id = rr.id WHERE rr.session_id = ?) +
|
|
25117
|
+
(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 = ?) +
|
|
25118
|
+
(SELECT COUNT(*) FROM map_runs WHERE session_id = ?) +
|
|
25119
|
+
(SELECT COUNT(*) FROM chat_conversations WHERE session_id = ?)`,
|
|
25120
|
+
Array(6).fill(sessionId)
|
|
25121
|
+
);
|
|
25122
|
+
const v = r[0]?.values[0]?.[0];
|
|
25123
|
+
return typeof v === "number" ? v : Number(v ?? 0);
|
|
25124
|
+
}
|
|
25125
|
+
function pruneDb(db, dbPath, opts = {}) {
|
|
25126
|
+
const dryRun = opts.dryRun ?? false;
|
|
25127
|
+
const hasBound = opts.olderThanDays !== void 0 || opts.keepSessions !== void 0;
|
|
25128
|
+
if (!hasBound) {
|
|
25129
|
+
return { dryRun, snapshotPath: null, prunedSessions: [], totalArtifactRows: 0 };
|
|
25130
|
+
}
|
|
25131
|
+
const rows = db.exec(
|
|
25132
|
+
`SELECT s.id,
|
|
25133
|
+
(SELECT (julianday('now') - julianday(MAX(e.created_at))) * 86400
|
|
25134
|
+
FROM orchestration_events e WHERE e.session_id = s.id) AS quiet_seconds
|
|
25135
|
+
FROM sessions s
|
|
25136
|
+
WHERE s.status = 'closed'
|
|
25137
|
+
ORDER BY quiet_seconds ASC`
|
|
25138
|
+
);
|
|
25139
|
+
const closed = (rows[0]?.values ?? []).map((v) => ({
|
|
25140
|
+
id: String(v[0]),
|
|
25141
|
+
quietSeconds: typeof v[1] === "number" ? v[1] : Number(v[1] ?? 0)
|
|
25142
|
+
}));
|
|
25143
|
+
const keepN = opts.keepSessions ?? 0;
|
|
25144
|
+
const olderThanSeconds = opts.olderThanDays !== void 0 ? opts.olderThanDays * 86400 : null;
|
|
25145
|
+
const targets = closed.filter((s, idx) => {
|
|
25146
|
+
if (idx < keepN) return false;
|
|
25147
|
+
if (olderThanSeconds !== null && s.quietSeconds < olderThanSeconds)
|
|
25148
|
+
return false;
|
|
25149
|
+
return true;
|
|
25150
|
+
});
|
|
25151
|
+
const prunedSessions = [];
|
|
25152
|
+
for (const t of targets) {
|
|
25153
|
+
const artifactRows = countSessionArtifacts(db, t.id);
|
|
25154
|
+
if (artifactRows === 0) continue;
|
|
25155
|
+
prunedSessions.push({ sessionId: t.id, artifactRows });
|
|
25156
|
+
}
|
|
25157
|
+
if (dryRun || prunedSessions.length === 0) {
|
|
25158
|
+
return {
|
|
25159
|
+
dryRun,
|
|
25160
|
+
snapshotPath: null,
|
|
25161
|
+
prunedSessions,
|
|
25162
|
+
totalArtifactRows: prunedSessions.reduce((n, p) => n + p.artifactRows, 0)
|
|
25163
|
+
};
|
|
25164
|
+
}
|
|
25165
|
+
const snapshotPath = snapshotDb(db, dbPath, "prune");
|
|
25166
|
+
db.transaction(() => {
|
|
25167
|
+
for (const p of prunedSessions) {
|
|
25168
|
+
db.run("DELETE FROM review_rounds WHERE session_id = ?", [p.sessionId]);
|
|
25169
|
+
db.run("DELETE FROM map_runs WHERE session_id = ?", [p.sessionId]);
|
|
25170
|
+
db.run("DELETE FROM markdown_artifacts WHERE session_id = ?", [p.sessionId]);
|
|
25171
|
+
db.run("DELETE FROM chat_conversations WHERE session_id = ?", [p.sessionId]);
|
|
25172
|
+
}
|
|
25173
|
+
});
|
|
25174
|
+
return {
|
|
25175
|
+
dryRun,
|
|
25176
|
+
snapshotPath,
|
|
25177
|
+
prunedSessions,
|
|
25178
|
+
totalArtifactRows: prunedSessions.reduce((n, p) => n + p.artifactRows, 0)
|
|
25179
|
+
};
|
|
25180
|
+
}
|
|
25181
|
+
var PROTECTED_TABLES, ORPHAN_SWEEPS, MARKDOWN_DEDUP_SQL, ONE_HOUR_MS, SEVEN_DAYS_MS;
|
|
25182
|
+
var init_maintenance = __esm({
|
|
25183
|
+
"src/lib/db/maintenance.ts"() {
|
|
25184
|
+
"use strict";
|
|
25185
|
+
init_src();
|
|
25186
|
+
PROTECTED_TABLES = /* @__PURE__ */ new Set([
|
|
25187
|
+
"sessions",
|
|
25188
|
+
"orchestration_events",
|
|
25189
|
+
"agent_sessions",
|
|
25190
|
+
"command_executions",
|
|
25191
|
+
"schema_version"
|
|
25192
|
+
]);
|
|
25193
|
+
ORPHAN_SWEEPS = [
|
|
25194
|
+
// session-rooted parents first
|
|
25195
|
+
{
|
|
25196
|
+
table: "review_rounds",
|
|
25197
|
+
sql: "DELETE FROM review_rounds WHERE session_id NOT IN (SELECT id FROM sessions)"
|
|
25198
|
+
},
|
|
25199
|
+
{
|
|
25200
|
+
table: "map_runs",
|
|
25201
|
+
sql: "DELETE FROM map_runs WHERE session_id NOT IN (SELECT id FROM sessions)"
|
|
25202
|
+
},
|
|
25203
|
+
{
|
|
25204
|
+
table: "markdown_artifacts",
|
|
25205
|
+
sql: "DELETE FROM markdown_artifacts WHERE session_id NOT IN (SELECT id FROM sessions)"
|
|
25206
|
+
},
|
|
25207
|
+
{
|
|
25208
|
+
table: "chat_conversations",
|
|
25209
|
+
sql: "DELETE FROM chat_conversations WHERE session_id NOT IN (SELECT id FROM sessions)"
|
|
25210
|
+
},
|
|
25211
|
+
// second level (pick up parents deleted above)
|
|
25212
|
+
{
|
|
25213
|
+
table: "reviewer_outputs",
|
|
25214
|
+
sql: "DELETE FROM reviewer_outputs WHERE round_id NOT IN (SELECT id FROM review_rounds)"
|
|
25215
|
+
},
|
|
25216
|
+
{
|
|
25217
|
+
table: "map_sections",
|
|
25218
|
+
sql: "DELETE FROM map_sections WHERE map_run_id NOT IN (SELECT id FROM map_runs)"
|
|
25219
|
+
},
|
|
25220
|
+
{
|
|
25221
|
+
table: "chat_messages",
|
|
25222
|
+
sql: "DELETE FROM chat_messages WHERE conversation_id NOT IN (SELECT id FROM chat_conversations)"
|
|
25223
|
+
},
|
|
25224
|
+
{
|
|
25225
|
+
table: "user_round_progress",
|
|
25226
|
+
sql: "DELETE FROM user_round_progress WHERE round_id NOT IN (SELECT id FROM review_rounds)"
|
|
25227
|
+
},
|
|
25228
|
+
// third level
|
|
25229
|
+
{
|
|
25230
|
+
table: "review_findings",
|
|
25231
|
+
sql: "DELETE FROM review_findings WHERE reviewer_output_id NOT IN (SELECT id FROM reviewer_outputs)"
|
|
25232
|
+
},
|
|
25233
|
+
{
|
|
25234
|
+
table: "map_files",
|
|
25235
|
+
sql: "DELETE FROM map_files WHERE section_id NOT IN (SELECT id FROM map_sections)"
|
|
25236
|
+
},
|
|
25237
|
+
// leaves
|
|
25238
|
+
{
|
|
25239
|
+
table: "user_finding_progress",
|
|
25240
|
+
sql: "DELETE FROM user_finding_progress WHERE finding_id NOT IN (SELECT id FROM review_findings)"
|
|
25241
|
+
},
|
|
25242
|
+
{
|
|
25243
|
+
table: "user_file_progress",
|
|
25244
|
+
sql: "DELETE FROM user_file_progress WHERE map_file_id NOT IN (SELECT id FROM map_files)"
|
|
25245
|
+
}
|
|
25246
|
+
];
|
|
25247
|
+
MARKDOWN_DEDUP_SQL = `
|
|
25248
|
+
DELETE FROM markdown_artifacts
|
|
25249
|
+
WHERE rowid NOT IN (
|
|
25250
|
+
SELECT MAX(rowid) FROM markdown_artifacts
|
|
25251
|
+
GROUP BY session_id, artifact_type, IFNULL(round_number, -1), file_path
|
|
25252
|
+
)`;
|
|
25253
|
+
ONE_HOUR_MS = 60 * 60 * 1e3;
|
|
25254
|
+
SEVEN_DAYS_MS = 7 * 24 * 60 * 60 * 1e3;
|
|
25255
|
+
}
|
|
25256
|
+
});
|
|
25257
|
+
|
|
24741
25258
|
// src/lib/db/command-log.ts
|
|
24742
|
-
import { appendFileSync, existsSync as
|
|
24743
|
-
import { dirname as
|
|
25259
|
+
import { appendFileSync, existsSync as existsSync12, mkdirSync as mkdirSync4, readFileSync as readFileSync9, renameSync, writeFileSync as writeFileSync6 } from "node:fs";
|
|
25260
|
+
import { dirname as dirname7, join as join14 } from "node:path";
|
|
24744
25261
|
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
24745
25262
|
function generateCommandUid() {
|
|
24746
25263
|
return randomUUID2();
|
|
24747
25264
|
}
|
|
24748
25265
|
function cacheDir(ocrDir) {
|
|
24749
|
-
return
|
|
25266
|
+
return join14(ocrDir, "data", CACHE_DIR);
|
|
24750
25267
|
}
|
|
24751
25268
|
function commandLogPath(ocrDir) {
|
|
24752
|
-
return
|
|
25269
|
+
return join14(cacheDir(ocrDir), FILENAME);
|
|
24753
25270
|
}
|
|
24754
25271
|
function appendCommandLog(ocrDir, entry) {
|
|
24755
25272
|
try {
|
|
24756
25273
|
const filePath = commandLogPath(ocrDir);
|
|
24757
|
-
const dir =
|
|
24758
|
-
if (!
|
|
25274
|
+
const dir = dirname7(filePath);
|
|
25275
|
+
if (!existsSync12(dir)) mkdirSync4(dir, { recursive: true });
|
|
24759
25276
|
const line = JSON.stringify(entry) + "\n";
|
|
24760
25277
|
appendFileSync(filePath, line, { encoding: "utf-8" });
|
|
24761
25278
|
if (approxLineCount >= 0) approxLineCount++;
|
|
@@ -24765,7 +25282,7 @@ function appendCommandLog(ocrDir, entry) {
|
|
|
24765
25282
|
}
|
|
24766
25283
|
function readCommandLog(ocrDir) {
|
|
24767
25284
|
const filePath = commandLogPath(ocrDir);
|
|
24768
|
-
if (!
|
|
25285
|
+
if (!existsSync12(filePath)) return [];
|
|
24769
25286
|
const content = readFileSync9(filePath, "utf-8");
|
|
24770
25287
|
const entries = [];
|
|
24771
25288
|
for (const line of content.split("\n")) {
|
|
@@ -24851,6 +25368,7 @@ __export(db_exports, {
|
|
|
24851
25368
|
PID_REUSE_GUARD_MS: () => PID_REUSE_GUARD_MS,
|
|
24852
25369
|
STATE_EXIT: () => STATE_EXIT,
|
|
24853
25370
|
StateError: () => StateError,
|
|
25371
|
+
WATCHDOG_DEADLINE_EXIT_CODE: () => WATCHDOG_DEADLINE_EXIT_CODE,
|
|
24854
25372
|
appendCommandLog: () => appendCommandLog,
|
|
24855
25373
|
bindVendorSessionIdOpportunistically: () => bindVendorSessionIdOpportunistically,
|
|
24856
25374
|
bumpAgentSessionHeartbeat: () => bumpAgentSessionHeartbeat,
|
|
@@ -24858,10 +25376,12 @@ __export(db_exports, {
|
|
|
24858
25376
|
cascadeTerminateExecutions: () => cascadeTerminateExecutions,
|
|
24859
25377
|
closeAllDatabases: () => closeAllDatabases,
|
|
24860
25378
|
closeDatabase: () => closeDatabase,
|
|
25379
|
+
collectDbHealth: () => collectDbHealth,
|
|
24861
25380
|
commandLogPath: () => commandLogPath,
|
|
24862
25381
|
commitReasonClose: () => commitReasonClose,
|
|
24863
25382
|
defaultIsAlive: () => defaultIsAlive,
|
|
24864
25383
|
ensureDatabase: () => ensureDatabase,
|
|
25384
|
+
fixDb: () => fixDb,
|
|
24865
25385
|
formatUpgradeNotice: () => formatUpgradeNotice,
|
|
24866
25386
|
generateCommandUid: () => generateCommandUid,
|
|
24867
25387
|
getAgentSession: () => getAgentSession,
|
|
@@ -24873,6 +25393,7 @@ __export(db_exports, {
|
|
|
24873
25393
|
getLatestEventId: () => getLatestEventId,
|
|
24874
25394
|
getSchemaVersion: () => getSchemaVersion,
|
|
24875
25395
|
getSession: () => getSession,
|
|
25396
|
+
hasInFlightDependents: () => hasInFlightDependents,
|
|
24876
25397
|
insertAgentSession: () => insertAgentSession,
|
|
24877
25398
|
insertEvent: () => insertEvent,
|
|
24878
25399
|
insertSession: () => insertSession,
|
|
@@ -24882,7 +25403,11 @@ __export(db_exports, {
|
|
|
24882
25403
|
openDatabase: () => openDatabase,
|
|
24883
25404
|
probeEngine: () => probeEngine,
|
|
24884
25405
|
probeWrite: () => probeWrite,
|
|
25406
|
+
pruneBackups: () => pruneBackups,
|
|
25407
|
+
pruneDb: () => pruneDb,
|
|
24885
25408
|
readCommandLog: () => readCommandLog,
|
|
25409
|
+
reapOrphanDbFiles: () => reapOrphanDbFiles,
|
|
25410
|
+
reapStaleExecLogs: () => reapStaleExecLogs,
|
|
24886
25411
|
reconcileLegacyState: () => reconcileLegacyState,
|
|
24887
25412
|
recordVendorSessionIdForExecution: () => recordVendorSessionIdForExecution,
|
|
24888
25413
|
replayCommandLog: () => replayCommandLog,
|
|
@@ -24892,31 +25417,34 @@ __export(db_exports, {
|
|
|
24892
25417
|
runMigrations: () => runMigrations,
|
|
24893
25418
|
setAgentSessionStatus: () => setAgentSessionStatus,
|
|
24894
25419
|
setAgentSessionVendorId: () => setAgentSessionVendorId,
|
|
25420
|
+
snapshotDb: () => snapshotDb,
|
|
24895
25421
|
sqliteUtcMs: () => sqliteUtcMs,
|
|
24896
25422
|
sweepStaleAgentSessions: () => sweepStaleAgentSessions,
|
|
24897
25423
|
sweepStaleSessions: () => sweepStaleSessions,
|
|
24898
25424
|
updateAgentSession: () => updateAgentSession,
|
|
24899
25425
|
updateSession: () => updateSession,
|
|
24900
|
-
|
|
25426
|
+
vacuumDb: () => vacuumDb,
|
|
25427
|
+
walCheckpointTruncate: () => walCheckpointTruncate,
|
|
25428
|
+
withForeignKeysDisabled: () => withForeignKeysDisabled
|
|
24901
25429
|
});
|
|
24902
25430
|
import {
|
|
24903
|
-
existsSync as
|
|
24904
|
-
mkdirSync as
|
|
24905
|
-
copyFileSync,
|
|
24906
|
-
statSync,
|
|
25431
|
+
existsSync as existsSync13,
|
|
25432
|
+
mkdirSync as mkdirSync5,
|
|
25433
|
+
copyFileSync as copyFileSync2,
|
|
25434
|
+
statSync as statSync2,
|
|
24907
25435
|
mkdtempSync,
|
|
24908
25436
|
rmSync
|
|
24909
25437
|
} from "node:fs";
|
|
24910
25438
|
import { tmpdir } from "node:os";
|
|
24911
|
-
import { dirname as
|
|
25439
|
+
import { dirname as dirname8, join as join15 } from "node:path";
|
|
24912
25440
|
function maybeSnapshotBeforeUpgrade(db, dbPath, fromVersion) {
|
|
24913
25441
|
if (fromVersion < 1 || fromVersion >= V2_SCHEMA_VERSION) return null;
|
|
24914
25442
|
const bakPath = `${dbPath}.bak.v${fromVersion}`;
|
|
24915
|
-
if (
|
|
25443
|
+
if (existsSync13(bakPath)) return bakPath;
|
|
24916
25444
|
try {
|
|
24917
|
-
if (!
|
|
25445
|
+
if (!existsSync13(dbPath) || statSync2(dbPath).size === 0) return null;
|
|
24918
25446
|
db.pragma("wal_checkpoint(TRUNCATE)");
|
|
24919
|
-
|
|
25447
|
+
copyFileSync2(dbPath, bakPath);
|
|
24920
25448
|
return bakPath;
|
|
24921
25449
|
} catch {
|
|
24922
25450
|
return null;
|
|
@@ -24949,24 +25477,24 @@ async function openDatabase(dbPath) {
|
|
|
24949
25477
|
if (cached) {
|
|
24950
25478
|
return cached;
|
|
24951
25479
|
}
|
|
24952
|
-
const dir =
|
|
24953
|
-
if (!
|
|
24954
|
-
|
|
25480
|
+
const dir = dirname8(dbPath);
|
|
25481
|
+
if (!existsSync13(dir)) {
|
|
25482
|
+
mkdirSync5(dir, { recursive: true });
|
|
24955
25483
|
}
|
|
24956
25484
|
const db = openEngine(dbPath);
|
|
24957
25485
|
connections.set(dbPath, db);
|
|
24958
25486
|
return db;
|
|
24959
25487
|
}
|
|
24960
25488
|
async function getDb(ocrDir) {
|
|
24961
|
-
const dbPath =
|
|
25489
|
+
const dbPath = join15(ocrDir, "data", "ocr.db");
|
|
24962
25490
|
return openDatabase(dbPath);
|
|
24963
25491
|
}
|
|
24964
25492
|
async function ensureDatabase(ocrDir) {
|
|
24965
|
-
const dataDir =
|
|
24966
|
-
if (!
|
|
24967
|
-
|
|
25493
|
+
const dataDir = join15(ocrDir, "data");
|
|
25494
|
+
if (!existsSync13(dataDir)) {
|
|
25495
|
+
mkdirSync5(dataDir, { recursive: true });
|
|
24968
25496
|
}
|
|
24969
|
-
const dbPath =
|
|
25497
|
+
const dbPath = join15(dataDir, "ocr.db");
|
|
24970
25498
|
const db = await openDatabase(dbPath);
|
|
24971
25499
|
let before = 0;
|
|
24972
25500
|
try {
|
|
@@ -24994,7 +25522,7 @@ async function ensureDatabase(ocrDir) {
|
|
|
24994
25522
|
return db;
|
|
24995
25523
|
}
|
|
24996
25524
|
function walCheckpointTruncate(dbPath) {
|
|
24997
|
-
if (!
|
|
25525
|
+
if (!existsSync13(dbPath)) {
|
|
24998
25526
|
return "skipped";
|
|
24999
25527
|
}
|
|
25000
25528
|
const cached = connections.get(dbPath);
|
|
@@ -25036,8 +25564,8 @@ function closeAllDatabases() {
|
|
|
25036
25564
|
function probeWrite() {
|
|
25037
25565
|
let dir;
|
|
25038
25566
|
try {
|
|
25039
|
-
dir = mkdtempSync(
|
|
25040
|
-
const db = openEngine(
|
|
25567
|
+
dir = mkdtempSync(join15(tmpdir(), "ocr-probe-"));
|
|
25568
|
+
const db = openEngine(join15(dir, "probe.db"));
|
|
25041
25569
|
try {
|
|
25042
25570
|
db.run("CREATE TABLE _probe_write (id INTEGER PRIMARY KEY, v TEXT)");
|
|
25043
25571
|
db.transaction(() => {
|
|
@@ -25083,6 +25611,7 @@ var init_db = __esm({
|
|
|
25083
25611
|
init_result_mapper();
|
|
25084
25612
|
init_engine();
|
|
25085
25613
|
init_reconcile();
|
|
25614
|
+
init_maintenance();
|
|
25086
25615
|
init_migrations();
|
|
25087
25616
|
init_command_log();
|
|
25088
25617
|
V2_SCHEMA_VERSION = 12;
|
|
@@ -28613,6 +29142,8 @@ function ora(options) {
|
|
|
28613
29142
|
}
|
|
28614
29143
|
|
|
28615
29144
|
// src/lib/config.ts
|
|
29145
|
+
init_src();
|
|
29146
|
+
init_src();
|
|
28616
29147
|
var AI_TOOLS = [
|
|
28617
29148
|
{
|
|
28618
29149
|
id: "amazon-q",
|
|
@@ -28636,7 +29167,9 @@ var AI_TOOLS = [
|
|
|
28636
29167
|
configDir: ".claude",
|
|
28637
29168
|
commandsDir: ".claude/commands",
|
|
28638
29169
|
skillsDir: ".claude/skills",
|
|
28639
|
-
commandStrategy: "subdirectory"
|
|
29170
|
+
commandStrategy: "subdirectory",
|
|
29171
|
+
instructionFiles: [{ path: "CLAUDE.md", format: "markdown" }],
|
|
29172
|
+
vendorBinary: "claude"
|
|
28640
29173
|
},
|
|
28641
29174
|
{
|
|
28642
29175
|
id: "cline",
|
|
@@ -28652,7 +29185,9 @@ var AI_TOOLS = [
|
|
|
28652
29185
|
configDir: ".codex",
|
|
28653
29186
|
commandsDir: ".codex/commands",
|
|
28654
29187
|
skillsDir: ".codex/skills",
|
|
28655
|
-
commandStrategy: "subdirectory"
|
|
29188
|
+
commandStrategy: "subdirectory",
|
|
29189
|
+
// Codex reads AGENTS.md natively — no extra instruction file.
|
|
29190
|
+
vendorBinary: "codex"
|
|
28656
29191
|
},
|
|
28657
29192
|
{
|
|
28658
29193
|
id: "continue",
|
|
@@ -28676,7 +29211,9 @@ var AI_TOOLS = [
|
|
|
28676
29211
|
configDir: ".gemini",
|
|
28677
29212
|
commandsDir: ".gemini/commands",
|
|
28678
29213
|
skillsDir: ".gemini/skills",
|
|
28679
|
-
commandStrategy: "subdirectory"
|
|
29214
|
+
commandStrategy: "subdirectory",
|
|
29215
|
+
instructionFiles: [{ path: "GEMINI.md", format: "markdown" }],
|
|
29216
|
+
vendorBinary: "gemini"
|
|
28680
29217
|
},
|
|
28681
29218
|
{
|
|
28682
29219
|
id: "github-copilot",
|
|
@@ -28684,7 +29221,10 @@ var AI_TOOLS = [
|
|
|
28684
29221
|
configDir: ".github",
|
|
28685
29222
|
commandsDir: ".github/commands",
|
|
28686
29223
|
skillsDir: ".github/skills",
|
|
28687
|
-
commandStrategy: "subdirectory"
|
|
29224
|
+
commandStrategy: "subdirectory",
|
|
29225
|
+
instructionFiles: [
|
|
29226
|
+
{ path: ".github/copilot-instructions.md", format: "markdown" }
|
|
29227
|
+
]
|
|
28688
29228
|
},
|
|
28689
29229
|
{
|
|
28690
29230
|
id: "kilo-code",
|
|
@@ -28700,7 +29240,9 @@ var AI_TOOLS = [
|
|
|
28700
29240
|
configDir: ".opencode",
|
|
28701
29241
|
commandsDir: ".opencode/commands",
|
|
28702
29242
|
skillsDir: ".opencode/skills",
|
|
28703
|
-
commandStrategy: "subdirectory"
|
|
29243
|
+
commandStrategy: "subdirectory",
|
|
29244
|
+
// OpenCode reads AGENTS.md natively — no extra instruction file.
|
|
29245
|
+
vendorBinary: "opencode"
|
|
28704
29246
|
},
|
|
28705
29247
|
{
|
|
28706
29248
|
id: "qoder",
|
|
@@ -28724,9 +29266,16 @@ var AI_TOOLS = [
|
|
|
28724
29266
|
configDir: ".windsurf",
|
|
28725
29267
|
commandsDir: ".windsurf/workflows",
|
|
28726
29268
|
skillsDir: ".windsurf/skills",
|
|
28727
|
-
commandStrategy: "flat-prefixed"
|
|
29269
|
+
commandStrategy: "flat-prefixed",
|
|
29270
|
+
instructionFiles: [{ path: ".windsurfrules", format: "plaintext" }]
|
|
28728
29271
|
}
|
|
28729
29272
|
];
|
|
29273
|
+
function getToolById(id) {
|
|
29274
|
+
return AI_TOOLS.find((tool) => tool.id === id);
|
|
29275
|
+
}
|
|
29276
|
+
function getHostCapabilities(id) {
|
|
29277
|
+
return hostCapabilitiesFor(id);
|
|
29278
|
+
}
|
|
28730
29279
|
function getToolIds() {
|
|
28731
29280
|
return AI_TOOLS.map((tool) => tool.id);
|
|
28732
29281
|
}
|
|
@@ -28787,11 +29336,11 @@ function ensureGitignore(ocrDir) {
|
|
|
28787
29336
|
const gitignorePath = join(ocrDir, ".gitignore");
|
|
28788
29337
|
const block = buildManagedBlock();
|
|
28789
29338
|
let content = existsSync(gitignorePath) ? readFileSync2(gitignorePath, "utf-8") : "";
|
|
28790
|
-
const
|
|
29339
|
+
const blockRegex2 = new RegExp(
|
|
28791
29340
|
`${escapeRegex(START_MARKER)}[\\s\\S]*?${escapeRegex(END_MARKER)}\\n?`,
|
|
28792
29341
|
"g"
|
|
28793
29342
|
);
|
|
28794
|
-
if (
|
|
29343
|
+
if (blockRegex2.test(content)) {
|
|
28795
29344
|
content = content.replace(
|
|
28796
29345
|
new RegExp(
|
|
28797
29346
|
`${escapeRegex(START_MARKER)}[\\s\\S]*?${escapeRegex(END_MARKER)}\\n?`,
|
|
@@ -28981,6 +29530,7 @@ function resolveTeamComposition(team, override) {
|
|
|
28981
29530
|
}
|
|
28982
29531
|
|
|
28983
29532
|
// src/lib/installer.ts
|
|
29533
|
+
init_src();
|
|
28984
29534
|
var require2 = createRequire(import.meta.url);
|
|
28985
29535
|
function ensureDir(dir) {
|
|
28986
29536
|
if (!existsSync3(dir)) {
|
|
@@ -29089,27 +29639,6 @@ function installCommandsForTool(tool, commandsSource, targetDir) {
|
|
|
29089
29639
|
return false;
|
|
29090
29640
|
}
|
|
29091
29641
|
}
|
|
29092
|
-
var BUILTIN_ICON_MAP = {
|
|
29093
|
-
architect: "blocks",
|
|
29094
|
-
fullstack: "layers",
|
|
29095
|
-
reliability: "activity",
|
|
29096
|
-
"staff-engineer": "compass",
|
|
29097
|
-
principal: "crown",
|
|
29098
|
-
frontend: "layout",
|
|
29099
|
-
backend: "server",
|
|
29100
|
-
infrastructure: "cloud",
|
|
29101
|
-
performance: "gauge",
|
|
29102
|
-
accessibility: "accessibility",
|
|
29103
|
-
data: "database",
|
|
29104
|
-
devops: "rocket",
|
|
29105
|
-
dx: "terminal",
|
|
29106
|
-
mobile: "smartphone",
|
|
29107
|
-
security: "shield-alert",
|
|
29108
|
-
quality: "sparkles",
|
|
29109
|
-
testing: "test-tubes",
|
|
29110
|
-
ai: "bot",
|
|
29111
|
-
"docs-writer": "file-text"
|
|
29112
|
-
};
|
|
29113
29642
|
var HOLISTIC_IDS = /* @__PURE__ */ new Set(["architect", "fullstack", "reliability", "staff-engineer", "principal"]);
|
|
29114
29643
|
var SPECIALIST_IDS = /* @__PURE__ */ new Set([
|
|
29115
29644
|
"frontend",
|
|
@@ -29212,7 +29741,7 @@ function generateReviewersMeta(reviewersDir, configPath) {
|
|
|
29212
29741
|
id,
|
|
29213
29742
|
name: extractReviewerName(content),
|
|
29214
29743
|
tier,
|
|
29215
|
-
icon:
|
|
29744
|
+
icon: defaultIconFor(id, tier),
|
|
29216
29745
|
description: extractReviewerDescription(content),
|
|
29217
29746
|
focus_areas: extractFocusAreas(content),
|
|
29218
29747
|
is_default: defaultTeamIds.has(id),
|
|
@@ -29313,7 +29842,9 @@ function installForTool(tool, targetDir) {
|
|
|
29313
29842
|
if (meta) {
|
|
29314
29843
|
writeFileSync3(metaPath, JSON.stringify(meta, null, 2) + "\n");
|
|
29315
29844
|
}
|
|
29316
|
-
} catch {
|
|
29845
|
+
} catch (err) {
|
|
29846
|
+
const msg = err instanceof Error ? err.message : "unknown error";
|
|
29847
|
+
warnings.push(`Could not generate reviewers-meta.json: ${msg}`);
|
|
29317
29848
|
}
|
|
29318
29849
|
const commandsOk = installCommandsForTool(tool, commandsSource, targetDir);
|
|
29319
29850
|
if (!commandsOk) {
|
|
@@ -29347,12 +29878,14 @@ function detectInstalledTools(targetDir, tools) {
|
|
|
29347
29878
|
}
|
|
29348
29879
|
|
|
29349
29880
|
// src/lib/injector.ts
|
|
29350
|
-
import { existsSync as existsSync4, readFileSync as readFileSync5, writeFileSync as writeFileSync4 } from "node:fs";
|
|
29351
|
-
import { join as join4 } from "node:path";
|
|
29352
|
-
var
|
|
29353
|
-
var
|
|
29354
|
-
|
|
29355
|
-
|
|
29881
|
+
import { existsSync as existsSync4, mkdirSync as mkdirSync2, readFileSync as readFileSync5, writeFileSync as writeFileSync4 } from "node:fs";
|
|
29882
|
+
import { dirname as dirname2, join as join4 } from "node:path";
|
|
29883
|
+
var AGENTS_MD = { path: "AGENTS.md", format: "markdown" };
|
|
29884
|
+
var MARKERS = {
|
|
29885
|
+
markdown: { start: "<!-- OCR:START -->", end: "<!-- OCR:END -->" },
|
|
29886
|
+
plaintext: { start: "# OCR:START", end: "# OCR:END" }
|
|
29887
|
+
};
|
|
29888
|
+
var OCR_INSTRUCTION_BODY = `## Open Code Review Instructions
|
|
29356
29889
|
|
|
29357
29890
|
These instructions are for AI assistants handling code review in this project.
|
|
29358
29891
|
|
|
@@ -29368,37 +29901,95 @@ Use \`.ocr/skills/SKILL.md\` to learn:
|
|
|
29368
29901
|
- Available reviewer personas and their focus areas
|
|
29369
29902
|
- Session management and output format
|
|
29370
29903
|
|
|
29371
|
-
Keep this managed block so \`ocr init\` can refresh the instructions
|
|
29372
|
-
|
|
29373
|
-
|
|
29374
|
-
|
|
29904
|
+
Keep this managed block so \`ocr init\` can refresh the instructions.`;
|
|
29905
|
+
function buildBlock(format) {
|
|
29906
|
+
const { start, end } = MARKERS[format];
|
|
29907
|
+
return `${start}
|
|
29908
|
+
${OCR_INSTRUCTION_BODY}
|
|
29909
|
+
${end}`;
|
|
29910
|
+
}
|
|
29911
|
+
function escapeRegex2(str) {
|
|
29912
|
+
return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
29913
|
+
}
|
|
29914
|
+
function blockRegex(format) {
|
|
29915
|
+
const { start, end } = MARKERS[format];
|
|
29916
|
+
return new RegExp(
|
|
29917
|
+
`${escapeRegex2(start)}[\\s\\S]*?${escapeRegex2(end)}\\n?`,
|
|
29918
|
+
"g"
|
|
29919
|
+
);
|
|
29920
|
+
}
|
|
29921
|
+
function injectOcrInstructions(filePath, format = "markdown") {
|
|
29375
29922
|
try {
|
|
29923
|
+
mkdirSync2(dirname2(filePath), { recursive: true });
|
|
29376
29924
|
let content = existsSync4(filePath) ? readFileSync5(filePath, "utf-8") : "";
|
|
29377
|
-
|
|
29378
|
-
`${escapeRegex2(START_MARKER2)}[\\s\\S]*?${escapeRegex2(END_MARKER2)}\\n?`,
|
|
29379
|
-
"g"
|
|
29380
|
-
);
|
|
29381
|
-
content = content.replace(regex2, "");
|
|
29925
|
+
content = content.replace(blockRegex(format), "");
|
|
29382
29926
|
content = content.trim();
|
|
29383
29927
|
if (content.length > 0) {
|
|
29384
29928
|
content += "\n\n";
|
|
29385
29929
|
}
|
|
29386
|
-
content +=
|
|
29930
|
+
content += buildBlock(format) + "\n";
|
|
29387
29931
|
writeFileSync4(filePath, content);
|
|
29388
29932
|
return true;
|
|
29389
29933
|
} catch {
|
|
29390
29934
|
return false;
|
|
29391
29935
|
}
|
|
29392
29936
|
}
|
|
29393
|
-
function
|
|
29394
|
-
|
|
29937
|
+
function resolveTargets(selectedTools) {
|
|
29938
|
+
const targets = /* @__PURE__ */ new Map();
|
|
29939
|
+
targets.set(AGENTS_MD.path, AGENTS_MD);
|
|
29940
|
+
for (const tool of selectedTools) {
|
|
29941
|
+
for (const file of tool.instructionFiles ?? []) {
|
|
29942
|
+
targets.set(file.path, file);
|
|
29943
|
+
}
|
|
29944
|
+
}
|
|
29945
|
+
return [...targets.values()];
|
|
29946
|
+
}
|
|
29947
|
+
function plannedInstructionFiles(selectedTools) {
|
|
29948
|
+
return resolveTargets(selectedTools).map((t) => t.path);
|
|
29949
|
+
}
|
|
29950
|
+
function injectIntoProjectFiles(targetDir, selectedTools) {
|
|
29951
|
+
const written = [];
|
|
29952
|
+
const failed = [];
|
|
29953
|
+
for (const target of resolveTargets(selectedTools)) {
|
|
29954
|
+
const ok = injectOcrInstructions(join4(targetDir, target.path), target.format);
|
|
29955
|
+
(ok ? written : failed).push(target.path);
|
|
29956
|
+
}
|
|
29957
|
+
return { written, failed };
|
|
29395
29958
|
}
|
|
29396
|
-
function
|
|
29397
|
-
|
|
29398
|
-
|
|
29399
|
-
|
|
29400
|
-
const
|
|
29401
|
-
return
|
|
29959
|
+
function hasOcrInstructions(filePath) {
|
|
29960
|
+
if (!existsSync4(filePath)) {
|
|
29961
|
+
return false;
|
|
29962
|
+
}
|
|
29963
|
+
const content = readFileSync5(filePath, "utf-8");
|
|
29964
|
+
return Object.values(MARKERS).some(
|
|
29965
|
+
(m) => content.includes(m.start) && content.includes(m.end)
|
|
29966
|
+
);
|
|
29967
|
+
}
|
|
29968
|
+
function findStaleInstructionFiles(targetDir, writtenPaths) {
|
|
29969
|
+
const written = new Set(writtenPaths);
|
|
29970
|
+
const candidates = /* @__PURE__ */ new Set();
|
|
29971
|
+
for (const tool of AI_TOOLS) {
|
|
29972
|
+
for (const file of tool.instructionFiles ?? []) {
|
|
29973
|
+
candidates.add(file.path);
|
|
29974
|
+
}
|
|
29975
|
+
}
|
|
29976
|
+
const stale = [];
|
|
29977
|
+
for (const path2 of candidates) {
|
|
29978
|
+
if (written.has(path2)) continue;
|
|
29979
|
+
if (hasOcrInstructions(join4(targetDir, path2))) {
|
|
29980
|
+
stale.push(path2);
|
|
29981
|
+
}
|
|
29982
|
+
}
|
|
29983
|
+
return stale;
|
|
29984
|
+
}
|
|
29985
|
+
function formatStaleWarnings(stale, mode) {
|
|
29986
|
+
if (mode === "dry-run") {
|
|
29987
|
+
return stale.map((path2) => `${path2} (stale OCR block \u2014 left untouched)`);
|
|
29988
|
+
}
|
|
29989
|
+
const owner = mode === "init" ? "installed" : "configured";
|
|
29990
|
+
return stale.map(
|
|
29991
|
+
(path2) => `${path2} still has an OCR block but no ${owner} tool uses it \u2014 remove it manually if unneeded.`
|
|
29992
|
+
);
|
|
29402
29993
|
}
|
|
29403
29994
|
|
|
29404
29995
|
// src/lib/banner.ts
|
|
@@ -29501,29 +30092,10 @@ ${hint}
|
|
|
29501
30092
|
}
|
|
29502
30093
|
|
|
29503
30094
|
// src/lib/version.ts
|
|
29504
|
-
var CLI_VERSION = true ? "2.1
|
|
29505
|
-
|
|
29506
|
-
// ../shared/platform/src/index.ts
|
|
29507
|
-
import { pathToFileURL } from "node:url";
|
|
29508
|
-
import {
|
|
29509
|
-
execFile,
|
|
29510
|
-
execFileSync,
|
|
29511
|
-
spawn as spawn2
|
|
29512
|
-
} from "node:child_process";
|
|
29513
|
-
import { promisify } from "node:util";
|
|
29514
|
-
var execFilePromise = promisify(execFile);
|
|
29515
|
-
var isWindows = process.platform === "win32";
|
|
29516
|
-
async function importModule(absolutePath) {
|
|
29517
|
-
return import(pathToFileURL(absolutePath).href);
|
|
29518
|
-
}
|
|
29519
|
-
function execBinary(binary, args, opts) {
|
|
29520
|
-
return execFileSync(binary, args, {
|
|
29521
|
-
...opts,
|
|
29522
|
-
shell: isWindows
|
|
29523
|
-
});
|
|
29524
|
-
}
|
|
30095
|
+
var CLI_VERSION = true ? "2.2.1" : createRequire(import.meta.url)("../../package.json").version;
|
|
29525
30096
|
|
|
29526
30097
|
// src/lib/deps.ts
|
|
30098
|
+
init_src();
|
|
29527
30099
|
var CATEGORY_ORDER = ["core", "ai-cli", "github"];
|
|
29528
30100
|
var CATEGORY_INFO = {
|
|
29529
30101
|
core: { label: "Core", hint: "" },
|
|
@@ -29695,7 +30267,7 @@ function printCapabilities(result) {
|
|
|
29695
30267
|
}
|
|
29696
30268
|
|
|
29697
30269
|
// src/commands/init.ts
|
|
29698
|
-
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
|
|
30270
|
+
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) => {
|
|
29699
30271
|
printBanner();
|
|
29700
30272
|
const depResult = checkDependencies();
|
|
29701
30273
|
printDepChecks(depResult);
|
|
@@ -29786,17 +30358,19 @@ var initCommand = new Command("init").description("Set up OCR for AI coding envi
|
|
|
29786
30358
|
const injectSpinner = ora(
|
|
29787
30359
|
"Injecting OCR instructions into project files..."
|
|
29788
30360
|
).start();
|
|
29789
|
-
const
|
|
30361
|
+
const installedTools = successful.map((r) => r.tool);
|
|
30362
|
+
const injectResults = injectIntoProjectFiles(targetDir, installedTools);
|
|
29790
30363
|
injectSpinner.stop();
|
|
29791
|
-
if (injectResults.
|
|
30364
|
+
if (injectResults.written.length > 0) {
|
|
29792
30365
|
console.log(source_default.green("\u2713 OCR instructions injected"));
|
|
29793
|
-
|
|
29794
|
-
console.log(` ${source_default.green("\u2713")}
|
|
29795
|
-
}
|
|
29796
|
-
if (injectResults.claudeMd) {
|
|
29797
|
-
console.log(` ${source_default.green("\u2713")} CLAUDE.md`);
|
|
30366
|
+
for (const path2 of injectResults.written) {
|
|
30367
|
+
console.log(` ${source_default.green("\u2713")} ${path2}`);
|
|
29798
30368
|
}
|
|
29799
30369
|
}
|
|
30370
|
+
const stale = findStaleInstructionFiles(targetDir, injectResults.written);
|
|
30371
|
+
for (const warning of formatStaleWarnings(stale, "init")) {
|
|
30372
|
+
console.log(source_default.yellow(` \u26A0 ${warning}`));
|
|
30373
|
+
}
|
|
29800
30374
|
}
|
|
29801
30375
|
console.log();
|
|
29802
30376
|
console.log(source_default.bold("Next steps:"));
|
|
@@ -29984,10 +30558,10 @@ var ReaddirpStream = class extends Readable {
|
|
|
29984
30558
|
}
|
|
29985
30559
|
async _formatEntry(dirent, path2) {
|
|
29986
30560
|
let entry;
|
|
29987
|
-
const
|
|
30561
|
+
const basename9 = this._isDirent ? dirent.name : dirent;
|
|
29988
30562
|
try {
|
|
29989
|
-
const fullPath = presolve(pjoin(path2,
|
|
29990
|
-
entry = { path: prelative(this._root, fullPath), fullPath, basename:
|
|
30563
|
+
const fullPath = presolve(pjoin(path2, basename9));
|
|
30564
|
+
entry = { path: prelative(this._root, fullPath), fullPath, basename: basename9 };
|
|
29991
30565
|
entry[this._statsProp] = this._isDirent ? dirent : await this._stat(fullPath);
|
|
29992
30566
|
} catch (err) {
|
|
29993
30567
|
this._onError(err);
|
|
@@ -30526,9 +31100,9 @@ var NodeFsHandler = class {
|
|
|
30526
31100
|
_watchWithNodeFs(path2, listener) {
|
|
30527
31101
|
const opts = this.fsw.options;
|
|
30528
31102
|
const directory = sysPath.dirname(path2);
|
|
30529
|
-
const
|
|
31103
|
+
const basename9 = sysPath.basename(path2);
|
|
30530
31104
|
const parent = this.fsw._getWatchedDir(directory);
|
|
30531
|
-
parent.add(
|
|
31105
|
+
parent.add(basename9);
|
|
30532
31106
|
const absolutePath = sysPath.resolve(path2);
|
|
30533
31107
|
const options = {
|
|
30534
31108
|
persistent: opts.persistent
|
|
@@ -30538,7 +31112,7 @@ var NodeFsHandler = class {
|
|
|
30538
31112
|
let closer;
|
|
30539
31113
|
if (opts.usePolling) {
|
|
30540
31114
|
const enableBin = opts.interval !== opts.binaryInterval;
|
|
30541
|
-
options.interval = enableBin && isBinaryPath(
|
|
31115
|
+
options.interval = enableBin && isBinaryPath(basename9) ? opts.binaryInterval : opts.interval;
|
|
30542
31116
|
closer = setFsWatchFileListener(path2, absolutePath, options, {
|
|
30543
31117
|
listener,
|
|
30544
31118
|
rawEmitter: this.fsw._emitRaw
|
|
@@ -30560,11 +31134,11 @@ var NodeFsHandler = class {
|
|
|
30560
31134
|
if (this.fsw.closed) {
|
|
30561
31135
|
return;
|
|
30562
31136
|
}
|
|
30563
|
-
const
|
|
30564
|
-
const
|
|
30565
|
-
const parent = this.fsw._getWatchedDir(
|
|
31137
|
+
const dirname10 = sysPath.dirname(file);
|
|
31138
|
+
const basename9 = sysPath.basename(file);
|
|
31139
|
+
const parent = this.fsw._getWatchedDir(dirname10);
|
|
30566
31140
|
let prevStats = stats;
|
|
30567
|
-
if (parent.has(
|
|
31141
|
+
if (parent.has(basename9))
|
|
30568
31142
|
return;
|
|
30569
31143
|
const listener = async (path2, newStats) => {
|
|
30570
31144
|
if (!this.fsw._throttle(THROTTLE_MODE_WATCH, file, 5))
|
|
@@ -30589,9 +31163,9 @@ var NodeFsHandler = class {
|
|
|
30589
31163
|
prevStats = newStats2;
|
|
30590
31164
|
}
|
|
30591
31165
|
} catch (error) {
|
|
30592
|
-
this.fsw._remove(
|
|
31166
|
+
this.fsw._remove(dirname10, basename9);
|
|
30593
31167
|
}
|
|
30594
|
-
} else if (parent.has(
|
|
31168
|
+
} else if (parent.has(basename9)) {
|
|
30595
31169
|
const at = newStats.atimeMs;
|
|
30596
31170
|
const mt = newStats.mtimeMs;
|
|
30597
31171
|
if (!at || at <= mt || mt !== prevStats.mtimeMs) {
|
|
@@ -31522,8 +32096,8 @@ function watch(paths, options = {}) {
|
|
|
31522
32096
|
}
|
|
31523
32097
|
|
|
31524
32098
|
// src/commands/progress.ts
|
|
31525
|
-
import { existsSync as
|
|
31526
|
-
import { join as
|
|
32099
|
+
import { existsSync as existsSync14, readdirSync as readdirSync6, statSync as statSync3 } from "node:fs";
|
|
32100
|
+
import { join as join16, basename as basename8 } from "node:path";
|
|
31527
32101
|
|
|
31528
32102
|
// ../../node_modules/.pnpm/log-update@7.0.2/node_modules/log-update/index.js
|
|
31529
32103
|
import process12 from "node:process";
|
|
@@ -32391,7 +32965,7 @@ var log_update_default = logUpdate;
|
|
|
32391
32965
|
var logUpdateStderr = createLogUpdate(process12.stderr);
|
|
32392
32966
|
|
|
32393
32967
|
// src/lib/guards.ts
|
|
32394
|
-
import { existsSync as existsSync6, mkdirSync as
|
|
32968
|
+
import { existsSync as existsSync6, mkdirSync as mkdirSync3 } from "node:fs";
|
|
32395
32969
|
import { join as join8 } from "node:path";
|
|
32396
32970
|
function checkOcrSetup(targetDir) {
|
|
32397
32971
|
const ocrDir = join8(targetDir, ".ocr");
|
|
@@ -32437,7 +33011,7 @@ function requireOcrSetup(targetDir) {
|
|
|
32437
33011
|
function ensureSessionsDir(targetDir) {
|
|
32438
33012
|
const sessionsDir = join8(targetDir, ".ocr", "sessions");
|
|
32439
33013
|
if (!existsSync6(sessionsDir)) {
|
|
32440
|
-
|
|
33014
|
+
mkdirSync3(sessionsDir, { recursive: true });
|
|
32441
33015
|
}
|
|
32442
33016
|
return sessionsDir;
|
|
32443
33017
|
}
|
|
@@ -33159,15 +33733,15 @@ function debounce(fn, delay) {
|
|
|
33159
33733
|
};
|
|
33160
33734
|
}
|
|
33161
33735
|
function findLatestActiveSession(sessionsDir) {
|
|
33162
|
-
if (!
|
|
33736
|
+
if (!existsSync14(sessionsDir)) {
|
|
33163
33737
|
return null;
|
|
33164
33738
|
}
|
|
33165
|
-
const sessions =
|
|
33166
|
-
const sessionPath =
|
|
33167
|
-
return
|
|
33739
|
+
const sessions = readdirSync6(sessionsDir).filter((name) => {
|
|
33740
|
+
const sessionPath = join16(sessionsDir, name);
|
|
33741
|
+
return statSync3(sessionPath).isDirectory();
|
|
33168
33742
|
}).sort().reverse();
|
|
33169
33743
|
for (const session of sessions) {
|
|
33170
|
-
const sessionPath =
|
|
33744
|
+
const sessionPath = join16(sessionsDir, session);
|
|
33171
33745
|
if (isSessionActive(sessionPath)) {
|
|
33172
33746
|
return session;
|
|
33173
33747
|
}
|
|
@@ -33182,8 +33756,8 @@ function getStrategyForSession(sessionPath, explicitWorkflow) {
|
|
|
33182
33756
|
return getStrategy(workflowType) ?? null;
|
|
33183
33757
|
}
|
|
33184
33758
|
async function initProgressDb(ocrDir) {
|
|
33185
|
-
const dbPath =
|
|
33186
|
-
if (!
|
|
33759
|
+
const dbPath = join16(ocrDir, "data", "ocr.db");
|
|
33760
|
+
if (!existsSync14(dbPath)) {
|
|
33187
33761
|
return;
|
|
33188
33762
|
}
|
|
33189
33763
|
try {
|
|
@@ -33208,11 +33782,11 @@ var progressCommand = new Command("progress").description("Watch real-time progr
|
|
|
33208
33782
|
const targetDir = process.cwd();
|
|
33209
33783
|
requireOcrSetup(targetDir);
|
|
33210
33784
|
const sessionsDir = ensureSessionsDir(targetDir);
|
|
33211
|
-
const ocrDir =
|
|
33785
|
+
const ocrDir = join16(targetDir, ".ocr");
|
|
33212
33786
|
await initProgressDb(ocrDir);
|
|
33213
33787
|
if (options.session) {
|
|
33214
|
-
const sessionPath =
|
|
33215
|
-
if (!
|
|
33788
|
+
const sessionPath = join16(sessionsDir, options.session);
|
|
33789
|
+
if (!existsSync14(sessionPath)) {
|
|
33216
33790
|
console.error(source_default.red(`Session not found: ${options.session}`));
|
|
33217
33791
|
process.exit(1);
|
|
33218
33792
|
}
|
|
@@ -33273,7 +33847,7 @@ var progressCommand = new Command("progress").description("Watch real-time progr
|
|
|
33273
33847
|
return;
|
|
33274
33848
|
}
|
|
33275
33849
|
let currentSession = findLatestActiveSession(sessionsDir);
|
|
33276
|
-
let currentSessionPath = currentSession ?
|
|
33850
|
+
let currentSessionPath = currentSession ? join16(sessionsDir, currentSession) : null;
|
|
33277
33851
|
let sessionWatcher = null;
|
|
33278
33852
|
const preservedStartTimes = {
|
|
33279
33853
|
review: void 0,
|
|
@@ -33281,11 +33855,11 @@ var progressCommand = new Command("progress").description("Watch real-time progr
|
|
|
33281
33855
|
};
|
|
33282
33856
|
let currentStrategy = null;
|
|
33283
33857
|
const updateDisplayImpl = () => {
|
|
33284
|
-
if (!currentSessionPath || !
|
|
33858
|
+
if (!currentSessionPath || !existsSync14(currentSessionPath) || !isSessionActive(currentSessionPath)) {
|
|
33285
33859
|
const latestActive = findLatestActiveSession(sessionsDir);
|
|
33286
33860
|
if (latestActive && latestActive !== currentSession) {
|
|
33287
33861
|
currentSession = latestActive;
|
|
33288
|
-
currentSessionPath =
|
|
33862
|
+
currentSessionPath = join16(sessionsDir, latestActive);
|
|
33289
33863
|
preservedStartTimes.review = void 0;
|
|
33290
33864
|
preservedStartTimes.map = void 0;
|
|
33291
33865
|
currentStrategy = null;
|
|
@@ -33298,7 +33872,7 @@ var progressCommand = new Command("progress").description("Watch real-time progr
|
|
|
33298
33872
|
currentStrategy = null;
|
|
33299
33873
|
}
|
|
33300
33874
|
}
|
|
33301
|
-
if (currentSessionPath &&
|
|
33875
|
+
if (currentSessionPath && existsSync14(currentSessionPath)) {
|
|
33302
33876
|
if (!options.workflow) {
|
|
33303
33877
|
const activeWorkflows = detectActiveWorkflows(currentSessionPath);
|
|
33304
33878
|
if (activeWorkflows.length > 1) {
|
|
@@ -33352,17 +33926,17 @@ var progressCommand = new Command("progress").description("Watch real-time progr
|
|
|
33352
33926
|
watchSession(currentSessionPath);
|
|
33353
33927
|
}
|
|
33354
33928
|
const timerInterval = setInterval(updateDisplay, 1e3);
|
|
33355
|
-
const watchDir =
|
|
33929
|
+
const watchDir = existsSync14(ocrDir) ? ocrDir : targetDir;
|
|
33356
33930
|
const dirWatcher = watch(watchDir, {
|
|
33357
33931
|
persistent: true,
|
|
33358
33932
|
ignoreInitial: true,
|
|
33359
33933
|
depth: 3
|
|
33360
33934
|
});
|
|
33361
33935
|
dirWatcher.on("addDir", (dirPath) => {
|
|
33362
|
-
const parentDir =
|
|
33363
|
-
const isDirectChild = parentDir.endsWith("sessions") || parentDir.endsWith(
|
|
33936
|
+
const parentDir = join16(dirPath, "..");
|
|
33937
|
+
const isDirectChild = parentDir.endsWith("sessions") || parentDir.endsWith(join16(".ocr", "sessions"));
|
|
33364
33938
|
if (isDirectChild && !dirPath.endsWith("sessions")) {
|
|
33365
|
-
const newSession =
|
|
33939
|
+
const newSession = basename8(dirPath);
|
|
33366
33940
|
currentSession = newSession;
|
|
33367
33941
|
currentSessionPath = dirPath;
|
|
33368
33942
|
preservedStartTimes.review = void 0;
|
|
@@ -33401,7 +33975,7 @@ function renderGenericWaiting() {
|
|
|
33401
33975
|
}
|
|
33402
33976
|
function renderCombinedProgress(sessionPath, preservedStartTimes, ocrDir) {
|
|
33403
33977
|
const lines = [];
|
|
33404
|
-
const session =
|
|
33978
|
+
const session = basename8(sessionPath);
|
|
33405
33979
|
lines.push("");
|
|
33406
33980
|
lines.push(
|
|
33407
33981
|
source_default.bold.white(" Open Code Review") + source_default.yellow(" \xB7 Parallel Workflows")
|
|
@@ -33462,21 +34036,21 @@ function renderCombinedProgress(sessionPath, preservedStartTimes, ocrDir) {
|
|
|
33462
34036
|
}
|
|
33463
34037
|
|
|
33464
34038
|
// src/commands/state.ts
|
|
33465
|
-
import { existsSync as
|
|
33466
|
-
import { join as
|
|
34039
|
+
import { existsSync as existsSync16, mkdirSync as mkdirSync7, readFileSync as readFileSync11 } from "node:fs";
|
|
34040
|
+
import { join as join18 } from "node:path";
|
|
33467
34041
|
|
|
33468
34042
|
// src/lib/state/index.ts
|
|
33469
34043
|
init_db();
|
|
33470
34044
|
init_exit_codes();
|
|
33471
34045
|
import {
|
|
33472
|
-
existsSync as
|
|
33473
|
-
mkdirSync as
|
|
33474
|
-
readdirSync as
|
|
34046
|
+
existsSync as existsSync15,
|
|
34047
|
+
mkdirSync as mkdirSync6,
|
|
34048
|
+
readdirSync as readdirSync7,
|
|
33475
34049
|
readFileSync as readFileSync10,
|
|
33476
|
-
statSync as
|
|
34050
|
+
statSync as statSync4,
|
|
33477
34051
|
writeFileSync as writeFileSync7
|
|
33478
34052
|
} from "node:fs";
|
|
33479
|
-
import { join as
|
|
34053
|
+
import { join as join17 } from "node:path";
|
|
33480
34054
|
|
|
33481
34055
|
// src/lib/state/phase-graph.ts
|
|
33482
34056
|
init_exit_codes();
|
|
@@ -33784,9 +34358,9 @@ function deriveNextRound(db, sessionId, fallbackRound) {
|
|
|
33784
34358
|
}
|
|
33785
34359
|
function hasArtifacts(dir) {
|
|
33786
34360
|
try {
|
|
33787
|
-
for (const entry of
|
|
34361
|
+
for (const entry of readdirSync7(dir, { withFileTypes: true })) {
|
|
33788
34362
|
if (entry.isDirectory()) {
|
|
33789
|
-
if (hasArtifacts(
|
|
34363
|
+
if (hasArtifacts(join17(dir, entry.name))) return true;
|
|
33790
34364
|
} else if (/\.(md|json)$/.test(entry.name)) {
|
|
33791
34365
|
return true;
|
|
33792
34366
|
}
|
|
@@ -33797,7 +34371,7 @@ function hasArtifacts(dir) {
|
|
|
33797
34371
|
}
|
|
33798
34372
|
function readJsonFromSource(params) {
|
|
33799
34373
|
if (params.source === "file") {
|
|
33800
|
-
if (!
|
|
34374
|
+
if (!existsSync15(params.filePath)) {
|
|
33801
34375
|
throw new StateError(STATE_EXIT.NOT_FOUND, `File not found: ${params.filePath}`);
|
|
33802
34376
|
}
|
|
33803
34377
|
return readFileSync10(params.filePath, "utf-8");
|
|
@@ -34124,7 +34698,7 @@ async function stateCompleteRound(params) {
|
|
|
34124
34698
|
}
|
|
34125
34699
|
const resolved = resolveSession(db, params.sessionId);
|
|
34126
34700
|
const roundNumber = params.round ?? resolved.current_round;
|
|
34127
|
-
const roundMetaPath =
|
|
34701
|
+
const roundMetaPath = join17(
|
|
34128
34702
|
resolved.session_dir,
|
|
34129
34703
|
"rounds",
|
|
34130
34704
|
`round-${roundNumber}`,
|
|
@@ -34145,8 +34719,8 @@ async function stateCompleteRound(params) {
|
|
|
34145
34719
|
);
|
|
34146
34720
|
}
|
|
34147
34721
|
if (params.requireFinal) {
|
|
34148
|
-
const finalPath =
|
|
34149
|
-
if (!
|
|
34722
|
+
const finalPath = join17(resolved.session_dir, "rounds", `round-${roundNumber}`, "final.md");
|
|
34723
|
+
if (!existsSync15(finalPath)) {
|
|
34150
34724
|
throw new StateError(
|
|
34151
34725
|
STATE_EXIT.INVARIANT_UNMET,
|
|
34152
34726
|
`Cannot complete round: --require-final set but ${finalPath} is missing.`
|
|
@@ -34155,8 +34729,8 @@ async function stateCompleteRound(params) {
|
|
|
34155
34729
|
}
|
|
34156
34730
|
let metaPath;
|
|
34157
34731
|
if (params.source === "stdin") {
|
|
34158
|
-
const roundDir =
|
|
34159
|
-
|
|
34732
|
+
const roundDir = join17(resolved.session_dir, "rounds", `round-${roundNumber}`);
|
|
34733
|
+
mkdirSync6(roundDir, { recursive: true });
|
|
34160
34734
|
metaPath = roundMetaPath;
|
|
34161
34735
|
writeFileSync7(metaPath, JSON.stringify(meta, null, 2));
|
|
34162
34736
|
}
|
|
@@ -34210,7 +34784,7 @@ async function stateCompleteMap(params) {
|
|
|
34210
34784
|
}
|
|
34211
34785
|
const resolved = resolveSession(db, params.sessionId);
|
|
34212
34786
|
const mapRunNumber = params.mapRun ?? resolved.current_map_run;
|
|
34213
|
-
const mapMetaPath =
|
|
34787
|
+
const mapMetaPath = join17(
|
|
34214
34788
|
resolved.session_dir,
|
|
34215
34789
|
"map",
|
|
34216
34790
|
"runs",
|
|
@@ -34233,8 +34807,8 @@ async function stateCompleteMap(params) {
|
|
|
34233
34807
|
}
|
|
34234
34808
|
let metaPath;
|
|
34235
34809
|
if (params.source === "stdin") {
|
|
34236
|
-
const runDir =
|
|
34237
|
-
|
|
34810
|
+
const runDir = join17(resolved.session_dir, "map", "runs", `run-${mapRunNumber}`);
|
|
34811
|
+
mkdirSync6(runDir, { recursive: true });
|
|
34238
34812
|
metaPath = mapMetaPath;
|
|
34239
34813
|
writeFileSync7(metaPath, JSON.stringify(meta, null, 2));
|
|
34240
34814
|
}
|
|
@@ -34319,17 +34893,17 @@ async function stateStatus(ocrDir, sessionId) {
|
|
|
34319
34893
|
}
|
|
34320
34894
|
async function stateSync(ocrDir) {
|
|
34321
34895
|
const db = await ensureDatabase(ocrDir);
|
|
34322
|
-
const sessionsRoot =
|
|
34323
|
-
if (!
|
|
34896
|
+
const sessionsRoot = join17(ocrDir, "sessions");
|
|
34897
|
+
if (!existsSync15(sessionsRoot)) {
|
|
34324
34898
|
return 0;
|
|
34325
34899
|
}
|
|
34326
|
-
const entries =
|
|
34327
|
-
const fullPath =
|
|
34328
|
-
return
|
|
34900
|
+
const entries = readdirSync7(sessionsRoot).filter((name) => {
|
|
34901
|
+
const fullPath = join17(sessionsRoot, name);
|
|
34902
|
+
return statSync4(fullPath).isDirectory();
|
|
34329
34903
|
});
|
|
34330
34904
|
let synced = 0;
|
|
34331
34905
|
for (const dirName of entries) {
|
|
34332
|
-
const dirPath =
|
|
34906
|
+
const dirPath = join17(sessionsRoot, dirName);
|
|
34333
34907
|
const existing = getSession(db, dirName);
|
|
34334
34908
|
if (existing) {
|
|
34335
34909
|
continue;
|
|
@@ -34337,8 +34911,8 @@ async function stateSync(ocrDir) {
|
|
|
34337
34911
|
if (!hasArtifacts(dirPath)) {
|
|
34338
34912
|
continue;
|
|
34339
34913
|
}
|
|
34340
|
-
const hasRoundsDir =
|
|
34341
|
-
const hasMapDir =
|
|
34914
|
+
const hasRoundsDir = existsSync15(join17(dirPath, "rounds"));
|
|
34915
|
+
const hasMapDir = existsSync15(join17(dirPath, "map"));
|
|
34342
34916
|
const workflowType = hasMapDir && !hasRoundsDir ? "map" : "review";
|
|
34343
34917
|
const branchMatch = dirName.match(/^\d{4}-\d{2}-\d{2}-(.+)$/);
|
|
34344
34918
|
const branch = branchMatch?.[1] ?? dirName;
|
|
@@ -34347,14 +34921,14 @@ async function stateSync(ocrDir) {
|
|
|
34347
34921
|
let inferredRound = 1;
|
|
34348
34922
|
let inferredMapRun = 1;
|
|
34349
34923
|
if (workflowType === "review") {
|
|
34350
|
-
const roundsDir =
|
|
34351
|
-
if (
|
|
34352
|
-
const roundDirs =
|
|
34924
|
+
const roundsDir = join17(dirPath, "rounds");
|
|
34925
|
+
if (existsSync15(roundsDir)) {
|
|
34926
|
+
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);
|
|
34353
34927
|
const latestRoundNum = roundDirs[roundDirs.length - 1];
|
|
34354
34928
|
if (latestRoundNum !== void 0) {
|
|
34355
34929
|
inferredRound = latestRoundNum;
|
|
34356
|
-
if (
|
|
34357
|
-
|
|
34930
|
+
if (existsSync15(
|
|
34931
|
+
join17(roundsDir, `round-${latestRoundNum}`, "final.md")
|
|
34358
34932
|
)) {
|
|
34359
34933
|
inferredPhase = "complete";
|
|
34360
34934
|
inferredPhaseNumber = 8;
|
|
@@ -34362,13 +34936,13 @@ async function stateSync(ocrDir) {
|
|
|
34362
34936
|
}
|
|
34363
34937
|
}
|
|
34364
34938
|
} else if (workflowType === "map") {
|
|
34365
|
-
const runsDir =
|
|
34366
|
-
if (
|
|
34367
|
-
const runDirs =
|
|
34939
|
+
const runsDir = join17(dirPath, "map", "runs");
|
|
34940
|
+
if (existsSync15(runsDir)) {
|
|
34941
|
+
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);
|
|
34368
34942
|
const latestRunNum = runDirs[runDirs.length - 1];
|
|
34369
34943
|
if (latestRunNum !== void 0) {
|
|
34370
34944
|
inferredMapRun = latestRunNum;
|
|
34371
|
-
if (
|
|
34945
|
+
if (existsSync15(join17(runsDir, `run-${latestRunNum}`, "map.md"))) {
|
|
34372
34946
|
inferredPhase = "complete";
|
|
34373
34947
|
inferredPhaseNumber = 6;
|
|
34374
34948
|
}
|
|
@@ -34406,7 +34980,7 @@ init_command_log();
|
|
|
34406
34980
|
init_db();
|
|
34407
34981
|
init_db();
|
|
34408
34982
|
function readDashboardSpawnMarker(ocrDir) {
|
|
34409
|
-
const path2 =
|
|
34983
|
+
const path2 = join18(ocrDir, "data", "dashboard-active-spawn.json");
|
|
34410
34984
|
let raw;
|
|
34411
34985
|
try {
|
|
34412
34986
|
raw = readFileSync11(path2, "utf-8");
|
|
@@ -34471,7 +35045,7 @@ async function linkDashboardInvocation(ocrDir, sessionId, explicitUid, label) {
|
|
|
34471
35045
|
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) => {
|
|
34472
35046
|
const targetDir = process.cwd();
|
|
34473
35047
|
requireOcrSetup(targetDir);
|
|
34474
|
-
const ocrDir =
|
|
35048
|
+
const ocrDir = join18(targetDir, ".ocr");
|
|
34475
35049
|
try {
|
|
34476
35050
|
const result = await stateShow(ocrDir, options.sessionId);
|
|
34477
35051
|
if (!result) {
|
|
@@ -34540,7 +35114,7 @@ var showSubcommand = new Command("show").description("Show current session state
|
|
|
34540
35114
|
var syncSubcommand = new Command("sync").description("Rebuild session state from filesystem artifacts").action(async () => {
|
|
34541
35115
|
const targetDir = process.cwd();
|
|
34542
35116
|
requireOcrSetup(targetDir);
|
|
34543
|
-
const ocrDir =
|
|
35117
|
+
const ocrDir = join18(targetDir, ".ocr");
|
|
34544
35118
|
try {
|
|
34545
35119
|
const synced = await stateSync(ocrDir);
|
|
34546
35120
|
console.log(`Synced ${synced} session${synced !== 1 ? "s" : ""} from filesystem.`);
|
|
@@ -34567,7 +35141,7 @@ var reconcileSubcommand = new Command("reconcile").description(
|
|
|
34567
35141
|
).option("--dry-run", "Print the repair plan without writing anything").option("--json", "Output the result as JSON").action(async (options) => {
|
|
34568
35142
|
const targetDir = process.cwd();
|
|
34569
35143
|
requireOcrSetup(targetDir);
|
|
34570
|
-
const ocrDir =
|
|
35144
|
+
const ocrDir = join18(targetDir, ".ocr");
|
|
34571
35145
|
try {
|
|
34572
35146
|
const db = await ensureDatabase(ocrDir);
|
|
34573
35147
|
const result = reconcileLegacyState(db, ocrDir, { dryRun: options.dryRun });
|
|
@@ -34626,9 +35200,9 @@ var beginSubcommand = new Command("begin").description("Start or resume a workfl
|
|
|
34626
35200
|
async (options) => {
|
|
34627
35201
|
const targetDir = process.cwd();
|
|
34628
35202
|
requireOcrSetup(targetDir);
|
|
34629
|
-
const ocrDir =
|
|
34630
|
-
const sessionDir = options.sessionDir ??
|
|
34631
|
-
if (!
|
|
35203
|
+
const ocrDir = join18(targetDir, ".ocr");
|
|
35204
|
+
const sessionDir = options.sessionDir ?? join18(ocrDir, "sessions", options.sessionId);
|
|
35205
|
+
if (!existsSync16(sessionDir)) mkdirSync7(sessionDir, { recursive: true });
|
|
34632
35206
|
try {
|
|
34633
35207
|
const result = await stateBegin({
|
|
34634
35208
|
sessionId: options.sessionId,
|
|
@@ -34650,7 +35224,7 @@ var advanceSubcommand = new Command("advance").description("Advance the workflow
|
|
|
34650
35224
|
async (options) => {
|
|
34651
35225
|
const targetDir = process.cwd();
|
|
34652
35226
|
requireOcrSetup(targetDir);
|
|
34653
|
-
const ocrDir =
|
|
35227
|
+
const ocrDir = join18(targetDir, ".ocr");
|
|
34654
35228
|
try {
|
|
34655
35229
|
const { id: sessionId } = await resolveActiveSession(ocrDir, options.sessionId);
|
|
34656
35230
|
await stateAdvance({
|
|
@@ -34670,7 +35244,7 @@ var completeRoundSubcommand = new Command("complete-round").description("Atomica
|
|
|
34670
35244
|
async (options) => {
|
|
34671
35245
|
const targetDir = process.cwd();
|
|
34672
35246
|
requireOcrSetup(targetDir);
|
|
34673
|
-
const ocrDir =
|
|
35247
|
+
const ocrDir = join18(targetDir, ".ocr");
|
|
34674
35248
|
try {
|
|
34675
35249
|
const base = options.stdin ? { source: "stdin", data: await readStdin() } : options.file ? { source: "file", filePath: options.file } : (() => {
|
|
34676
35250
|
throw new StateError(STATE_EXIT.USAGE, "Provide --stdin or --file with round metadata");
|
|
@@ -34694,7 +35268,7 @@ var completeMapSubcommand = new Command("complete-map").description("Atomically
|
|
|
34694
35268
|
async (options) => {
|
|
34695
35269
|
const targetDir = process.cwd();
|
|
34696
35270
|
requireOcrSetup(targetDir);
|
|
34697
|
-
const ocrDir =
|
|
35271
|
+
const ocrDir = join18(targetDir, ".ocr");
|
|
34698
35272
|
try {
|
|
34699
35273
|
const base = options.stdin ? { source: "stdin", data: await readStdin() } : options.file ? { source: "file", filePath: options.file } : (() => {
|
|
34700
35274
|
throw new StateError(STATE_EXIT.USAGE, "Provide --stdin or --file with map metadata");
|
|
@@ -34716,7 +35290,7 @@ var completeMapSubcommand = new Command("complete-map").description("Atomically
|
|
|
34716
35290
|
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) => {
|
|
34717
35291
|
const targetDir = process.cwd();
|
|
34718
35292
|
requireOcrSetup(targetDir);
|
|
34719
|
-
const ocrDir =
|
|
35293
|
+
const ocrDir = join18(targetDir, ".ocr");
|
|
34720
35294
|
try {
|
|
34721
35295
|
const { id: sessionId } = await resolveActiveSession(ocrDir, options.sessionId);
|
|
34722
35296
|
await stateClose({ sessionId, ocrDir, abort: options.abort });
|
|
@@ -34728,7 +35302,7 @@ var finishSubcommand = new Command("finish").description("Close a workflow (refu
|
|
|
34728
35302
|
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) => {
|
|
34729
35303
|
const targetDir = process.cwd();
|
|
34730
35304
|
requireOcrSetup(targetDir);
|
|
34731
|
-
const ocrDir =
|
|
35305
|
+
const ocrDir = join18(targetDir, ".ocr");
|
|
34732
35306
|
try {
|
|
34733
35307
|
const result = await stateStatus(ocrDir, options.sessionId);
|
|
34734
35308
|
if (options.json) {
|
|
@@ -34757,44 +35331,50 @@ var stateCommand = new Command("state").description("Manage OCR session state").
|
|
|
34757
35331
|
|
|
34758
35332
|
// src/commands/session.ts
|
|
34759
35333
|
import { randomUUID as randomUUID3 } from "node:crypto";
|
|
34760
|
-
import { join as
|
|
35334
|
+
import { join as join20 } from "node:path";
|
|
34761
35335
|
init_db();
|
|
34762
35336
|
|
|
34763
35337
|
// src/lib/runtime-config.ts
|
|
34764
|
-
import { existsSync as
|
|
34765
|
-
import { join as
|
|
35338
|
+
import { existsSync as existsSync17, readFileSync as readFileSync12 } from "node:fs";
|
|
35339
|
+
import { join as join19 } from "node:path";
|
|
34766
35340
|
var DEFAULT_AGENT_HEARTBEAT_SECONDS = 60;
|
|
34767
|
-
function
|
|
34768
|
-
const configPath =
|
|
34769
|
-
if (!
|
|
34770
|
-
return DEFAULT_AGENT_HEARTBEAT_SECONDS;
|
|
34771
|
-
}
|
|
35341
|
+
function readRuntimePositiveInt(ocrDir, key, defaultValue) {
|
|
35342
|
+
const configPath = join19(ocrDir, "config.yaml");
|
|
35343
|
+
if (!existsSync17(configPath)) return defaultValue;
|
|
34772
35344
|
let content;
|
|
34773
35345
|
try {
|
|
34774
35346
|
content = readFileSync12(configPath, "utf-8");
|
|
34775
35347
|
} catch {
|
|
34776
|
-
return
|
|
35348
|
+
return defaultValue;
|
|
34777
35349
|
}
|
|
34778
35350
|
const blockMatch = content.match(
|
|
34779
|
-
|
|
35351
|
+
new RegExp(
|
|
35352
|
+
String.raw`^runtime:\s*\n(?:\s+[^\n]*\n)*?\s+${key}:\s*([^\s#\n]+)`,
|
|
35353
|
+
"m"
|
|
35354
|
+
)
|
|
34780
35355
|
);
|
|
34781
35356
|
const inlineMatch = content.match(
|
|
34782
|
-
|
|
35357
|
+
new RegExp(String.raw`^runtime:\s*\{[^}]*\b${key}:\s*([^\s,}]+)`, "m")
|
|
34783
35358
|
);
|
|
34784
35359
|
const raw = blockMatch?.[1] ?? inlineMatch?.[1];
|
|
34785
|
-
if (!raw)
|
|
34786
|
-
return DEFAULT_AGENT_HEARTBEAT_SECONDS;
|
|
34787
|
-
}
|
|
35360
|
+
if (!raw) return defaultValue;
|
|
34788
35361
|
const parsed = Number(raw);
|
|
34789
35362
|
if (!Number.isFinite(parsed) || parsed <= 0 || !Number.isInteger(parsed)) {
|
|
34790
35363
|
process.stderr.write(
|
|
34791
|
-
`[ocr] runtime
|
|
35364
|
+
`[ocr] runtime.${key} is not a positive integer (got "${raw}"); falling back to ${defaultValue}.
|
|
34792
35365
|
`
|
|
34793
35366
|
);
|
|
34794
|
-
return
|
|
35367
|
+
return defaultValue;
|
|
34795
35368
|
}
|
|
34796
35369
|
return parsed;
|
|
34797
35370
|
}
|
|
35371
|
+
function getAgentHeartbeatSeconds(ocrDir) {
|
|
35372
|
+
return readRuntimePositiveInt(
|
|
35373
|
+
ocrDir,
|
|
35374
|
+
"agent_heartbeat_seconds",
|
|
35375
|
+
DEFAULT_AGENT_HEARTBEAT_SECONDS
|
|
35376
|
+
);
|
|
35377
|
+
}
|
|
34798
35378
|
|
|
34799
35379
|
// src/commands/session.ts
|
|
34800
35380
|
var TERMINAL_STATUSES = /* @__PURE__ */ new Set([
|
|
@@ -34810,7 +35390,7 @@ function fail(message) {
|
|
|
34810
35390
|
async function setup() {
|
|
34811
35391
|
const targetDir = process.cwd();
|
|
34812
35392
|
requireOcrSetup(targetDir);
|
|
34813
|
-
const ocrDir =
|
|
35393
|
+
const ocrDir = join20(targetDir, ".ocr");
|
|
34814
35394
|
return { ocrDir };
|
|
34815
35395
|
}
|
|
34816
35396
|
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(
|
|
@@ -34945,23 +35525,63 @@ var listSubcommand = new Command("list").description("List agent sessions for a
|
|
|
34945
35525
|
var sessionCommand = new Command("session").description("Manage agent-CLI session lifecycle journal").addCommand(startInstanceSubcommand).addCommand(bindVendorIdSubcommand).addCommand(beatSubcommand).addCommand(endInstanceSubcommand).addCommand(listSubcommand);
|
|
34946
35526
|
|
|
34947
35527
|
// src/lib/models.ts
|
|
34948
|
-
|
|
34949
|
-
|
|
34950
|
-
|
|
34951
|
-
|
|
34952
|
-
|
|
34953
|
-
|
|
34954
|
-
|
|
34955
|
-
|
|
34956
|
-
|
|
34957
|
-
|
|
34958
|
-
|
|
34959
|
-
|
|
35528
|
+
init_src();
|
|
35529
|
+
function parseOpenCodeModelList(stdout) {
|
|
35530
|
+
const models = [];
|
|
35531
|
+
for (const rawLine of stdout.split(/\r?\n/)) {
|
|
35532
|
+
const line = rawLine.trim();
|
|
35533
|
+
if (!/^[^\s:]+\/\S+$/.test(line)) continue;
|
|
35534
|
+
const provider = line.slice(0, line.indexOf("/"));
|
|
35535
|
+
models.push({ id: line, provider });
|
|
35536
|
+
}
|
|
35537
|
+
return models.length > 0 ? models : null;
|
|
35538
|
+
}
|
|
35539
|
+
var VENDOR_MODEL_STRATEGIES = {
|
|
35540
|
+
claude: {
|
|
35541
|
+
displayName: "Claude Code",
|
|
35542
|
+
native: {
|
|
35543
|
+
// Verified against Claude Code 2.1.x: the CLI has no model-listing
|
|
35544
|
+
// subcommand (`claude models --json` → "unknown option"). Revisit if
|
|
35545
|
+
// a future release adds one.
|
|
35546
|
+
unavailableReason: "Claude Code does not provide a model-listing command; showing its documented model aliases instead"
|
|
35547
|
+
},
|
|
35548
|
+
// Vendor-documented aliases that always track the latest generation —
|
|
35549
|
+
// dated ids here would go stale by construction (the exact bug class of
|
|
35550
|
+
// issue #39). Pinned dated ids remain available via free-text entry.
|
|
35551
|
+
bundled: [
|
|
35552
|
+
{ id: "opus", displayName: "Claude Opus (latest)" },
|
|
35553
|
+
{ id: "sonnet", displayName: "Claude Sonnet (latest)" },
|
|
35554
|
+
{ id: "haiku", displayName: "Claude Haiku (latest)" }
|
|
35555
|
+
]
|
|
35556
|
+
},
|
|
35557
|
+
opencode: {
|
|
35558
|
+
displayName: "OpenCode",
|
|
35559
|
+
native: {
|
|
35560
|
+
// Plain `opencode models` — newline-delimited ids. (`--json` is not a
|
|
35561
|
+
// real flag, and `--verbose` interleaves JSON metadata blocks that
|
|
35562
|
+
// defeat line parsing.)
|
|
35563
|
+
args: ["models"],
|
|
35564
|
+
parse: parseOpenCodeModelList
|
|
35565
|
+
},
|
|
35566
|
+
bundled: [
|
|
35567
|
+
{ id: "anthropic/claude-opus-4-8", provider: "anthropic" },
|
|
35568
|
+
{ id: "anthropic/claude-sonnet-4-6", provider: "anthropic" },
|
|
35569
|
+
{ id: "anthropic/claude-haiku-4-5", provider: "anthropic" }
|
|
35570
|
+
]
|
|
35571
|
+
}
|
|
35572
|
+
};
|
|
35573
|
+
var SUPPORTED_VENDORS = Object.keys(
|
|
35574
|
+
VENDOR_MODEL_STRATEGIES
|
|
35575
|
+
);
|
|
35576
|
+
function isModelVendor(value) {
|
|
35577
|
+
return Object.hasOwn(VENDOR_MODEL_STRATEGIES, value);
|
|
35578
|
+
}
|
|
35579
|
+
async function detectActiveVendor() {
|
|
35580
|
+
for (const vendor of SUPPORTED_VENDORS) {
|
|
34960
35581
|
try {
|
|
34961
|
-
|
|
35582
|
+
await execBinaryAsync(vendor, ["--version"], {
|
|
34962
35583
|
encoding: "utf-8",
|
|
34963
|
-
timeout: 3e3
|
|
34964
|
-
stdio: ["ignore", "pipe", "ignore"]
|
|
35584
|
+
timeout: 3e3
|
|
34965
35585
|
});
|
|
34966
35586
|
return vendor;
|
|
34967
35587
|
} catch {
|
|
@@ -34969,68 +35589,97 @@ function detectActiveVendor() {
|
|
|
34969
35589
|
}
|
|
34970
35590
|
return null;
|
|
34971
35591
|
}
|
|
34972
|
-
function
|
|
35592
|
+
function describeProbeFailure(vendor, args, err) {
|
|
35593
|
+
const command = `${vendor} ${args.join(" ")}`;
|
|
35594
|
+
const e = err;
|
|
35595
|
+
if (e.code === "ENOENT") {
|
|
35596
|
+
return `\`${vendor}\` is not installed or not on PATH`;
|
|
35597
|
+
}
|
|
35598
|
+
if (e.killed) {
|
|
35599
|
+
return `\`${command}\` timed out or exceeded output limits`;
|
|
35600
|
+
}
|
|
35601
|
+
const stderr = typeof e.stderr === "string" ? e.stderr.trim() : "";
|
|
35602
|
+
const firstLine = (stderr.split(/\r?\n/)[0] ?? "").replace(/\u001b\[[0-9;]*[A-Za-z]/g, "").replace(/[\u0000-\u001f\u007f]/g, "").slice(0, 200);
|
|
35603
|
+
const detail = firstLine ? `: ${firstLine}` : "";
|
|
35604
|
+
const exit = typeof e.code === "number" ? ` with exit code ${e.code}` : "";
|
|
35605
|
+
return `\`${command}\` failed${exit}${detail}`;
|
|
35606
|
+
}
|
|
35607
|
+
async function tryNativeEnumeration(vendor, probe) {
|
|
35608
|
+
let stdout;
|
|
34973
35609
|
try {
|
|
34974
|
-
const
|
|
35610
|
+
const result = await execBinaryAsync(vendor, probe.args, {
|
|
34975
35611
|
encoding: "utf-8",
|
|
34976
|
-
timeout: 5e3
|
|
34977
|
-
stdio: ["ignore", "pipe", "ignore"]
|
|
35612
|
+
timeout: 5e3
|
|
34978
35613
|
});
|
|
34979
|
-
|
|
34980
|
-
|
|
34981
|
-
|
|
34982
|
-
for (const item of parsed) {
|
|
34983
|
-
if (typeof item === "string") {
|
|
34984
|
-
models.push({ id: item });
|
|
34985
|
-
} else if (typeof item === "object" && item !== null && "id" in item && typeof item.id === "string") {
|
|
34986
|
-
const obj = item;
|
|
34987
|
-
const desc = { id: obj.id };
|
|
34988
|
-
if (typeof obj.displayName === "string") desc.displayName = obj.displayName;
|
|
34989
|
-
if (typeof obj.provider === "string") desc.provider = obj.provider;
|
|
34990
|
-
if (Array.isArray(obj.tags)) {
|
|
34991
|
-
desc.tags = obj.tags.filter((t) => typeof t === "string");
|
|
34992
|
-
}
|
|
34993
|
-
models.push(desc);
|
|
34994
|
-
}
|
|
34995
|
-
}
|
|
34996
|
-
return models.length > 0 ? models : null;
|
|
34997
|
-
} catch {
|
|
34998
|
-
return null;
|
|
35614
|
+
stdout = result.stdout;
|
|
35615
|
+
} catch (err) {
|
|
35616
|
+
return { models: null, reason: describeProbeFailure(vendor, probe.args, err) };
|
|
34999
35617
|
}
|
|
35618
|
+
const models = probe.parse(stdout);
|
|
35619
|
+
if (!models) {
|
|
35620
|
+
return {
|
|
35621
|
+
models: null,
|
|
35622
|
+
reason: `\`${vendor} ${probe.args.join(" ")}\` output did not contain any model identifiers`
|
|
35623
|
+
};
|
|
35624
|
+
}
|
|
35625
|
+
return { models };
|
|
35000
35626
|
}
|
|
35001
|
-
|
|
35002
|
-
|
|
35003
|
-
|
|
35004
|
-
|
|
35005
|
-
|
|
35006
|
-
|
|
35007
|
-
|
|
35008
|
-
|
|
35627
|
+
var SUCCESS_TTL_MS = 6e4;
|
|
35628
|
+
var FAILURE_TTL_MS = 1e4;
|
|
35629
|
+
var cache = /* @__PURE__ */ new Map();
|
|
35630
|
+
async function listModelsForVendor(vendor) {
|
|
35631
|
+
const cached = cache.get(vendor);
|
|
35632
|
+
if (cached && cached.expiresAt > Date.now()) {
|
|
35633
|
+
return cached.result;
|
|
35634
|
+
}
|
|
35635
|
+
const strategy = VENDOR_MODEL_STRATEGIES[vendor];
|
|
35636
|
+
if (!strategy) {
|
|
35637
|
+
throw new Error(`Unknown vendor: ${vendor}`);
|
|
35638
|
+
}
|
|
35639
|
+
let result;
|
|
35640
|
+
if ("unavailableReason" in strategy.native) {
|
|
35641
|
+
result = {
|
|
35642
|
+
vendor,
|
|
35643
|
+
source: "bundled",
|
|
35644
|
+
models: strategy.bundled,
|
|
35645
|
+
nativeUnavailableReason: strategy.native.unavailableReason
|
|
35646
|
+
};
|
|
35647
|
+
} else {
|
|
35648
|
+
const native = await tryNativeEnumeration(vendor, strategy.native);
|
|
35649
|
+
result = native.models ? { vendor, source: "native", models: native.models } : {
|
|
35650
|
+
vendor,
|
|
35651
|
+
source: "bundled",
|
|
35652
|
+
models: strategy.bundled,
|
|
35653
|
+
nativeUnavailableReason: native.reason
|
|
35654
|
+
};
|
|
35009
35655
|
}
|
|
35010
|
-
|
|
35656
|
+
const ttl = result.source === "native" ? SUCCESS_TTL_MS : FAILURE_TTL_MS;
|
|
35657
|
+
cache.set(vendor, { result, expiresAt: Date.now() + ttl });
|
|
35658
|
+
return result;
|
|
35011
35659
|
}
|
|
35012
35660
|
|
|
35013
35661
|
// src/commands/models.ts
|
|
35014
|
-
var
|
|
35015
|
-
|
|
35016
|
-
"Override autodetection (claude | opencode)"
|
|
35017
|
-
).option("--json", "Emit JSON for programmatic consumption").action(async (options) => {
|
|
35662
|
+
var vendorList = SUPPORTED_VENDORS.join(" | ");
|
|
35663
|
+
var listSubcommand2 = new Command("list").description("List models the active AI CLI is willing to accept").option("--vendor <vendor>", `Override autodetection (${vendorList})`).option("--json", "Emit JSON for programmatic consumption").action(async (options) => {
|
|
35018
35664
|
let vendor;
|
|
35019
35665
|
if (options.vendor) {
|
|
35020
|
-
|
|
35666
|
+
const requested = options.vendor.toLowerCase();
|
|
35667
|
+
if (!isModelVendor(requested)) {
|
|
35021
35668
|
console.error(
|
|
35022
35669
|
source_default.red(
|
|
35023
|
-
`Invalid --vendor: "${options.vendor}". Must be
|
|
35670
|
+
`Invalid --vendor: "${options.vendor}". Must be one of: ${vendorList}.`
|
|
35024
35671
|
)
|
|
35025
35672
|
);
|
|
35026
35673
|
process.exit(1);
|
|
35027
35674
|
}
|
|
35028
|
-
vendor =
|
|
35675
|
+
vendor = requested;
|
|
35029
35676
|
} else {
|
|
35030
|
-
vendor = detectActiveVendor();
|
|
35677
|
+
vendor = await detectActiveVendor();
|
|
35031
35678
|
if (!vendor) {
|
|
35032
35679
|
if (options.json) {
|
|
35033
|
-
console.log(
|
|
35680
|
+
console.log(
|
|
35681
|
+
JSON.stringify({ vendor: null, source: null, models: [] }, null, 2)
|
|
35682
|
+
);
|
|
35034
35683
|
return;
|
|
35035
35684
|
}
|
|
35036
35685
|
console.error(
|
|
@@ -35041,16 +35690,18 @@ var listSubcommand2 = new Command("list").description("List models the active AI
|
|
|
35041
35690
|
process.exit(1);
|
|
35042
35691
|
}
|
|
35043
35692
|
}
|
|
35044
|
-
const
|
|
35693
|
+
const result = await listModelsForVendor(vendor);
|
|
35045
35694
|
if (options.json) {
|
|
35046
|
-
console.log(JSON.stringify(
|
|
35695
|
+
console.log(JSON.stringify(result, null, 2));
|
|
35047
35696
|
return;
|
|
35048
35697
|
}
|
|
35698
|
+
const { source, models, nativeUnavailableReason } = result;
|
|
35049
35699
|
console.log(source_default.bold(`Models for ${vendor} (${source})`));
|
|
35050
35700
|
if (source === "bundled") {
|
|
35701
|
+
const reason = nativeUnavailableReason ? ` \u2014 ${nativeUnavailableReason}` : "";
|
|
35051
35702
|
console.log(
|
|
35052
35703
|
source_default.dim(
|
|
35053
|
-
|
|
35704
|
+
` Note: bundled fallback list${reason}. Free-text input is always accepted.`
|
|
35054
35705
|
)
|
|
35055
35706
|
);
|
|
35056
35707
|
}
|
|
@@ -35065,8 +35716,8 @@ var modelsCommand = new Command("models").description("Inspect models available
|
|
|
35065
35716
|
|
|
35066
35717
|
// src/commands/team.ts
|
|
35067
35718
|
var import_yaml2 = __toESM(require_dist(), 1);
|
|
35068
|
-
import { existsSync as
|
|
35069
|
-
import { join as
|
|
35719
|
+
import { existsSync as existsSync18, readFileSync as readFileSync13, writeFileSync as writeFileSync8 } from "node:fs";
|
|
35720
|
+
import { join as join21 } from "node:path";
|
|
35070
35721
|
async function readStdin2() {
|
|
35071
35722
|
const chunks = [];
|
|
35072
35723
|
for await (const chunk of process.stdin) {
|
|
@@ -35125,7 +35776,7 @@ var resolveSubcommand = new Command("resolve").description("Resolve and print th
|
|
|
35125
35776
|
async (options) => {
|
|
35126
35777
|
const targetDir = process.cwd();
|
|
35127
35778
|
requireOcrSetup(targetDir);
|
|
35128
|
-
const ocrDir =
|
|
35779
|
+
const ocrDir = join21(targetDir, ".ocr");
|
|
35129
35780
|
try {
|
|
35130
35781
|
const { team } = loadTeamConfig(ocrDir);
|
|
35131
35782
|
let override;
|
|
@@ -35164,8 +35815,8 @@ var setSubcommand = new Command("set").description("Persist a new default_team c
|
|
|
35164
35815
|
}
|
|
35165
35816
|
const targetDir = process.cwd();
|
|
35166
35817
|
requireOcrSetup(targetDir);
|
|
35167
|
-
const ocrDir =
|
|
35168
|
-
const configPath =
|
|
35818
|
+
const ocrDir = join21(targetDir, ".ocr");
|
|
35819
|
+
const configPath = join21(ocrDir, "config.yaml");
|
|
35169
35820
|
try {
|
|
35170
35821
|
const raw = await readStdin2();
|
|
35171
35822
|
const team = parseSessionOverride(raw);
|
|
@@ -35175,12 +35826,12 @@ var setSubcommand = new Command("set").description("Persist a new default_team c
|
|
|
35175
35826
|
list.push(inst);
|
|
35176
35827
|
byPersona.set(inst.persona, list);
|
|
35177
35828
|
}
|
|
35178
|
-
const doc =
|
|
35829
|
+
const doc = existsSync18(configPath) ? (0, import_yaml2.parseDocument)(readFileSync13(configPath, "utf-8")) : new import_yaml2.Document({});
|
|
35179
35830
|
applyDefaultTeamSurgically(doc, byPersona);
|
|
35180
35831
|
const yamlOutput = doc.toString({ lineWidth: 0 });
|
|
35181
35832
|
writeFileSync8(configPath, yamlOutput, "utf-8");
|
|
35182
|
-
const reviewersDir =
|
|
35183
|
-
const metaPath =
|
|
35833
|
+
const reviewersDir = join21(ocrDir, "skills", "references", "reviewers");
|
|
35834
|
+
const metaPath = join21(ocrDir, "reviewers-meta.json");
|
|
35184
35835
|
let metaWritten = false;
|
|
35185
35836
|
try {
|
|
35186
35837
|
const meta = generateReviewersMeta(reviewersDir, configPath);
|
|
@@ -35276,7 +35927,7 @@ var teamCommand = new Command("team").description("Resolve and persist team comp
|
|
|
35276
35927
|
|
|
35277
35928
|
// src/commands/review.ts
|
|
35278
35929
|
import { spawn as spawn3 } from "node:child_process";
|
|
35279
|
-
import { join as
|
|
35930
|
+
import { join as join22 } from "node:path";
|
|
35280
35931
|
init_db();
|
|
35281
35932
|
|
|
35282
35933
|
// src/lib/vendor-resume.ts
|
|
@@ -35315,7 +35966,7 @@ var reviewCommand = new Command("review").description("Run or resume an OCR revi
|
|
|
35315
35966
|
}
|
|
35316
35967
|
const targetDir = process.cwd();
|
|
35317
35968
|
requireOcrSetup(targetDir);
|
|
35318
|
-
const ocrDir =
|
|
35969
|
+
const ocrDir = join22(targetDir, ".ocr");
|
|
35319
35970
|
const db = await ensureDatabase(ocrDir);
|
|
35320
35971
|
const session = getSession(db, options.resume);
|
|
35321
35972
|
if (!session) {
|
|
@@ -35357,23 +36008,23 @@ var reviewCommand = new Command("review").description("Run or resume an OCR revi
|
|
|
35357
36008
|
});
|
|
35358
36009
|
|
|
35359
36010
|
// src/commands/update.ts
|
|
35360
|
-
import { existsSync as
|
|
35361
|
-
import { join as
|
|
36011
|
+
import { existsSync as existsSync19 } from "node:fs";
|
|
36012
|
+
import { join as join23 } from "node:path";
|
|
35362
36013
|
function detectConfiguredTools(targetDir) {
|
|
35363
36014
|
return AI_TOOLS.filter((tool) => {
|
|
35364
36015
|
if (tool.commandStrategy === "subdirectory") {
|
|
35365
|
-
const ocrDir =
|
|
35366
|
-
return
|
|
36016
|
+
const ocrDir = join23(targetDir, tool.commandsDir, "ocr");
|
|
36017
|
+
return existsSync19(ocrDir);
|
|
35367
36018
|
} else {
|
|
35368
|
-
const reviewCmd =
|
|
35369
|
-
return
|
|
36019
|
+
const reviewCmd = join23(targetDir, tool.commandsDir, "ocr-review.md");
|
|
36020
|
+
return existsSync19(reviewCmd);
|
|
35370
36021
|
}
|
|
35371
36022
|
});
|
|
35372
36023
|
}
|
|
35373
36024
|
var updateCommand = new Command("update").description("Update OCR assets after package upgrade").option("--commands", "Update only commands/workflows").option(
|
|
35374
36025
|
"--skills",
|
|
35375
36026
|
"Update only skills (includes templates, references, assets)"
|
|
35376
|
-
).option("--inject", "Update only AGENTS.md
|
|
36027
|
+
).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) => {
|
|
35377
36028
|
const targetDir = process.cwd();
|
|
35378
36029
|
requireOcrSetup(targetDir);
|
|
35379
36030
|
console.log();
|
|
@@ -35440,7 +36091,7 @@ var updateCommand = new Command("update").description("Update OCR assets after p
|
|
|
35440
36091
|
const result = installForTool(tool, targetDir);
|
|
35441
36092
|
results.push(result);
|
|
35442
36093
|
}
|
|
35443
|
-
ensureGitignore(
|
|
36094
|
+
ensureGitignore(join23(targetDir, ".ocr"));
|
|
35444
36095
|
spinner.stop();
|
|
35445
36096
|
const successful = results.filter((r) => r.success);
|
|
35446
36097
|
const failed = results.filter((r) => !r.success);
|
|
@@ -35474,30 +36125,34 @@ var updateCommand = new Command("update").description("Update OCR assets after p
|
|
|
35474
36125
|
}
|
|
35475
36126
|
}
|
|
35476
36127
|
if (updateInject) {
|
|
36128
|
+
const planned = plannedInstructionFiles(toolsToUpdate);
|
|
35477
36129
|
if (options.dryRun) {
|
|
35478
36130
|
console.log(source_default.dim(" Would update:"));
|
|
35479
|
-
|
|
35480
|
-
|
|
36131
|
+
for (const path2 of planned) {
|
|
36132
|
+
const verb = existsSync19(join23(targetDir, path2)) ? "update" : "create";
|
|
36133
|
+
console.log(source_default.dim(` \u2022 ${path2} (${verb} OCR managed block)`));
|
|
35481
36134
|
}
|
|
35482
|
-
|
|
35483
|
-
|
|
36135
|
+
const staleDry = findStaleInstructionFiles(targetDir, planned);
|
|
36136
|
+
for (const warning of formatStaleWarnings(staleDry, "dry-run")) {
|
|
36137
|
+
console.log(source_default.dim(` \u2022 ${warning}`));
|
|
35484
36138
|
}
|
|
35485
36139
|
console.log();
|
|
35486
36140
|
} else {
|
|
35487
|
-
const spinner = ora("Updating
|
|
35488
|
-
const injectResults = injectIntoProjectFiles(targetDir);
|
|
36141
|
+
const spinner = ora("Updating instruction files...").start();
|
|
36142
|
+
const injectResults = injectIntoProjectFiles(targetDir, toolsToUpdate);
|
|
35489
36143
|
spinner.stop();
|
|
35490
|
-
if (injectResults.
|
|
36144
|
+
if (injectResults.written.length > 0) {
|
|
35491
36145
|
console.log(source_default.green(" \u2713 Instructions updated"));
|
|
35492
|
-
|
|
35493
|
-
console.log(` ${source_default.green("\u2713")}
|
|
35494
|
-
}
|
|
35495
|
-
if (injectResults.claudeMd) {
|
|
35496
|
-
console.log(` ${source_default.green("\u2713")} CLAUDE.md`);
|
|
36146
|
+
for (const path2 of injectResults.written) {
|
|
36147
|
+
console.log(` ${source_default.green("\u2713")} ${path2}`);
|
|
35497
36148
|
}
|
|
35498
36149
|
} else {
|
|
35499
36150
|
console.log(source_default.dim(" No instruction files to update"));
|
|
35500
36151
|
}
|
|
36152
|
+
const stale = findStaleInstructionFiles(targetDir, injectResults.written);
|
|
36153
|
+
for (const warning of formatStaleWarnings(stale, "update")) {
|
|
36154
|
+
console.log(source_default.yellow(` \u26A0 ${warning}`));
|
|
36155
|
+
}
|
|
35501
36156
|
console.log();
|
|
35502
36157
|
}
|
|
35503
36158
|
}
|
|
@@ -35511,14 +36166,15 @@ var updateCommand = new Command("update").description("Update OCR assets after p
|
|
|
35511
36166
|
});
|
|
35512
36167
|
|
|
35513
36168
|
// src/commands/dashboard.ts
|
|
35514
|
-
import { existsSync as
|
|
35515
|
-
import { join as
|
|
36169
|
+
import { existsSync as existsSync20 } from "node:fs";
|
|
36170
|
+
import { join as join24, dirname as dirname9 } from "node:path";
|
|
35516
36171
|
import { fileURLToPath } from "node:url";
|
|
36172
|
+
init_src();
|
|
35517
36173
|
init_db();
|
|
35518
36174
|
var __filename = fileURLToPath(import.meta.url);
|
|
35519
|
-
var __dirname =
|
|
36175
|
+
var __dirname = dirname9(__filename);
|
|
35520
36176
|
function resolveServerPath() {
|
|
35521
|
-
return
|
|
36177
|
+
return join24(__dirname, "dashboard", "server.js");
|
|
35522
36178
|
}
|
|
35523
36179
|
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(
|
|
35524
36180
|
async (options) => {
|
|
@@ -35529,7 +36185,7 @@ var dashboardCommand = new Command("dashboard").description("Start the OCR dashb
|
|
|
35529
36185
|
console.error(source_default.red(`Error: Invalid port "${options.port}". Must be 1-65535.`));
|
|
35530
36186
|
process.exit(1);
|
|
35531
36187
|
}
|
|
35532
|
-
const ocrDir =
|
|
36188
|
+
const ocrDir = join24(targetDir, ".ocr");
|
|
35533
36189
|
try {
|
|
35534
36190
|
await ensureDatabase(ocrDir);
|
|
35535
36191
|
closeAllDatabases();
|
|
@@ -35543,7 +36199,7 @@ var dashboardCommand = new Command("dashboard").description("Start the OCR dashb
|
|
|
35543
36199
|
process.exit(1);
|
|
35544
36200
|
}
|
|
35545
36201
|
const serverPath = resolveServerPath();
|
|
35546
|
-
if (!
|
|
36202
|
+
if (!existsSync20(serverPath)) {
|
|
35547
36203
|
console.error(source_default.red("Error: Dashboard server bundle not found."));
|
|
35548
36204
|
console.error(
|
|
35549
36205
|
source_default.dim(` Expected at: ${serverPath}`)
|
|
@@ -35577,8 +36233,8 @@ var dashboardCommand = new Command("dashboard").description("Start the OCR dashb
|
|
|
35577
36233
|
);
|
|
35578
36234
|
|
|
35579
36235
|
// src/commands/doctor.ts
|
|
35580
|
-
import { existsSync as
|
|
35581
|
-
import { join as
|
|
36236
|
+
import { existsSync as existsSync21 } from "node:fs";
|
|
36237
|
+
import { join as join25 } from "node:path";
|
|
35582
36238
|
init_db();
|
|
35583
36239
|
function printStorageEngine(probeWriteEnabled) {
|
|
35584
36240
|
console.log();
|
|
@@ -35635,10 +36291,10 @@ var doctorCommand = new Command("doctor").description("Check OCR installation an
|
|
|
35635
36291
|
console.log(source_default.bold(" OCR Installation"));
|
|
35636
36292
|
console.log();
|
|
35637
36293
|
const ocrStatus = checkOcrSetup(targetDir);
|
|
35638
|
-
const configPath =
|
|
35639
|
-
const dbPath =
|
|
35640
|
-
const hasConfig =
|
|
35641
|
-
const hasDb =
|
|
36294
|
+
const configPath = join25(targetDir, ".ocr", "config.yaml");
|
|
36295
|
+
const dbPath = join25(targetDir, ".ocr", "data", "ocr.db");
|
|
36296
|
+
const hasConfig = existsSync21(configPath);
|
|
36297
|
+
const hasDb = existsSync21(dbPath);
|
|
35642
36298
|
const ocrChecks = [
|
|
35643
36299
|
{ label: ".ocr/skills/", ok: ocrStatus.hasSkills },
|
|
35644
36300
|
{ label: ".ocr/sessions/", ok: ocrStatus.hasSessions },
|
|
@@ -35714,9 +36370,331 @@ var doctorCommand = new Command("doctor").description("Check OCR installation an
|
|
|
35714
36370
|
console.log();
|
|
35715
36371
|
});
|
|
35716
36372
|
|
|
36373
|
+
// src/commands/db.ts
|
|
36374
|
+
import { existsSync as existsSync22, readFileSync as readFileSync14 } from "node:fs";
|
|
36375
|
+
import { join as join26 } from "node:path";
|
|
36376
|
+
init_src();
|
|
36377
|
+
init_db();
|
|
36378
|
+
function fail4(message) {
|
|
36379
|
+
console.error(source_default.red(`Error: ${message}`));
|
|
36380
|
+
process.exit(1);
|
|
36381
|
+
}
|
|
36382
|
+
function resolveOcrDir() {
|
|
36383
|
+
const targetDir = process.cwd();
|
|
36384
|
+
requireOcrSetup(targetDir);
|
|
36385
|
+
return join26(targetDir, ".ocr");
|
|
36386
|
+
}
|
|
36387
|
+
function dbPathFor(ocrDir) {
|
|
36388
|
+
return join26(ocrDir, "data", "ocr.db");
|
|
36389
|
+
}
|
|
36390
|
+
function formatBytes(n) {
|
|
36391
|
+
if (n < 1024) return `${n} B`;
|
|
36392
|
+
const units = ["KB", "MB", "GB", "TB"];
|
|
36393
|
+
let v = n / 1024;
|
|
36394
|
+
let i = 0;
|
|
36395
|
+
while (v >= 1024 && i < units.length - 1) {
|
|
36396
|
+
v /= 1024;
|
|
36397
|
+
i++;
|
|
36398
|
+
}
|
|
36399
|
+
return `${v.toFixed(v >= 100 ? 0 : 1)} ${units[i]}`;
|
|
36400
|
+
}
|
|
36401
|
+
function liveDashboardPid(ocrDir) {
|
|
36402
|
+
const pidFile = join26(ocrDir, "data", "dashboard.pid");
|
|
36403
|
+
if (!existsSync22(pidFile)) return null;
|
|
36404
|
+
try {
|
|
36405
|
+
const pid = parseInt(readFileSync14(pidFile, "utf-8").trim(), 10);
|
|
36406
|
+
if (!Number.isNaN(pid) && isProcessAlive(pid)) return pid;
|
|
36407
|
+
} catch {
|
|
36408
|
+
}
|
|
36409
|
+
return null;
|
|
36410
|
+
}
|
|
36411
|
+
function guardExclusive(ocrDir, force, op) {
|
|
36412
|
+
const pid = liveDashboardPid(ocrDir);
|
|
36413
|
+
if (pid !== null && !force) {
|
|
36414
|
+
fail4(
|
|
36415
|
+
`a dashboard appears to be running (PID ${pid}); ${op} needs exclusive access to the database.
|
|
36416
|
+
Stop it first, or pass --force to proceed anyway.`
|
|
36417
|
+
);
|
|
36418
|
+
}
|
|
36419
|
+
}
|
|
36420
|
+
function printHealth(report) {
|
|
36421
|
+
console.log();
|
|
36422
|
+
console.log(source_default.bold(" Database Health"));
|
|
36423
|
+
console.log();
|
|
36424
|
+
console.log(` File: ${report.dbPath}`);
|
|
36425
|
+
console.log(` Size: ${formatBytes(report.fileSizeBytes)}`);
|
|
36426
|
+
if (report.reclaimableBytes > 0) {
|
|
36427
|
+
console.log(
|
|
36428
|
+
` Reclaimable: ${source_default.yellow(formatBytes(report.reclaimableBytes))} ` + source_default.dim(`(${report.freelistCount} free pages \u2014 run \`ocr db vacuum\`)`)
|
|
36429
|
+
);
|
|
36430
|
+
}
|
|
36431
|
+
console.log(
|
|
36432
|
+
` Records: ${report.sessionCount} session(s), ${report.eventCount} event(s)`
|
|
36433
|
+
);
|
|
36434
|
+
console.log();
|
|
36435
|
+
const ok = (s) => ` ${source_default.green("\u2713")} ${s}`;
|
|
36436
|
+
const bad = (s) => ` ${source_default.red("\u2717")} ${s}`;
|
|
36437
|
+
console.log(
|
|
36438
|
+
report.integrityOk ? ok("integrity_check: ok") : bad(`integrity_check: ${report.integrityErrors.length} error(s)`)
|
|
36439
|
+
);
|
|
36440
|
+
if (!report.integrityOk) {
|
|
36441
|
+
for (const e of report.integrityErrors.slice(0, 5)) {
|
|
36442
|
+
console.log(` ${source_default.dim(e)}`);
|
|
36443
|
+
}
|
|
36444
|
+
}
|
|
36445
|
+
const fkTotal = report.fkViolations.reduce((n, g) => n + g.count, 0) + report.protectedFkViolations.reduce((n, g) => n + g.count, 0);
|
|
36446
|
+
if (fkTotal === 0) {
|
|
36447
|
+
console.log(ok("foreign_key_check: 0 violations"));
|
|
36448
|
+
} else {
|
|
36449
|
+
console.log(bad(`foreign_key_check: ${fkTotal} violation(s)`));
|
|
36450
|
+
for (const g of report.fkViolations) {
|
|
36451
|
+
console.log(` ${source_default.dim(`${g.table}: ${g.count} orphan(s)`)}`);
|
|
36452
|
+
}
|
|
36453
|
+
for (const g of report.protectedFkViolations) {
|
|
36454
|
+
console.log(
|
|
36455
|
+
` ${source_default.yellow(`${g.table}: ${g.count} (protected \u2014 manual review)`)}`
|
|
36456
|
+
);
|
|
36457
|
+
}
|
|
36458
|
+
}
|
|
36459
|
+
if (report.markdownDuplicateRows === 0) {
|
|
36460
|
+
console.log(ok("markdown_artifacts: no duplicates"));
|
|
36461
|
+
} else {
|
|
36462
|
+
console.log(
|
|
36463
|
+
bad(`markdown_artifacts: ${report.markdownDuplicateRows} duplicate row(s)`)
|
|
36464
|
+
);
|
|
36465
|
+
}
|
|
36466
|
+
const reapable = report.orphanTempFiles.filter((f) => f.reapable);
|
|
36467
|
+
if (report.orphanTempFiles.length > 0) {
|
|
36468
|
+
console.log(
|
|
36469
|
+
` ${reapable.length > 0 ? source_default.yellow("\u26A0") : source_default.dim("\xB7")} orphan temp files: ${report.orphanTempFiles.length} (${reapable.length} reapable)`
|
|
36470
|
+
);
|
|
36471
|
+
}
|
|
36472
|
+
if (report.backupFiles.length > 0) {
|
|
36473
|
+
const total = report.backupFiles.reduce((n, b) => n + b.sizeBytes, 0);
|
|
36474
|
+
console.log(
|
|
36475
|
+
` ${source_default.dim("\xB7")} backups: ${report.backupFiles.length} (${formatBytes(total)})`
|
|
36476
|
+
);
|
|
36477
|
+
}
|
|
36478
|
+
console.log();
|
|
36479
|
+
}
|
|
36480
|
+
function needsFix(report) {
|
|
36481
|
+
return !report.integrityOk || report.fkViolations.length > 0 || report.markdownDuplicateRows > 0 || report.orphanTempFiles.some((f) => f.reapable) || report.reclaimableBytes > 0;
|
|
36482
|
+
}
|
|
36483
|
+
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(
|
|
36484
|
+
async (options) => {
|
|
36485
|
+
const ocrDir = resolveOcrDir();
|
|
36486
|
+
const dbPath = dbPathFor(ocrDir);
|
|
36487
|
+
const db = await ensureDatabase(ocrDir);
|
|
36488
|
+
if (options.json) {
|
|
36489
|
+
console.log(JSON.stringify(collectDbHealth(db, dbPath), null, 2));
|
|
36490
|
+
return;
|
|
36491
|
+
}
|
|
36492
|
+
const before = collectDbHealth(db, dbPath);
|
|
36493
|
+
printHealth(before);
|
|
36494
|
+
if (!options.fix) {
|
|
36495
|
+
if (needsFix(before)) {
|
|
36496
|
+
console.log(
|
|
36497
|
+
source_default.dim(" Run `ocr db doctor --fix` to repair the issues above.")
|
|
36498
|
+
);
|
|
36499
|
+
console.log();
|
|
36500
|
+
} else {
|
|
36501
|
+
console.log(source_default.green(" \u2713 Database is healthy"));
|
|
36502
|
+
console.log();
|
|
36503
|
+
}
|
|
36504
|
+
return;
|
|
36505
|
+
}
|
|
36506
|
+
guardExclusive(ocrDir, options.force ?? false, "doctor --fix");
|
|
36507
|
+
const result = fixDb(db, dbPath, { snapshot: options.snapshot !== false });
|
|
36508
|
+
console.log(source_default.bold(" Repairs applied"));
|
|
36509
|
+
console.log();
|
|
36510
|
+
if (result.snapshotPath) {
|
|
36511
|
+
console.log(` ${source_default.dim("snapshot:")} ${result.snapshotPath}`);
|
|
36512
|
+
}
|
|
36513
|
+
if (result.totalFkOrphansDeleted > 0) {
|
|
36514
|
+
console.log(
|
|
36515
|
+
` ${source_default.green("\u2713")} swept ${result.totalFkOrphansDeleted} FK-orphan row(s)`
|
|
36516
|
+
);
|
|
36517
|
+
for (const g of result.fkOrphansDeleted) {
|
|
36518
|
+
console.log(` ${source_default.dim(`${g.table}: ${g.count}`)}`);
|
|
36519
|
+
}
|
|
36520
|
+
}
|
|
36521
|
+
if (result.markdownDupsDeleted > 0) {
|
|
36522
|
+
console.log(
|
|
36523
|
+
` ${source_default.green("\u2713")} removed ${result.markdownDupsDeleted} duplicate markdown row(s)`
|
|
36524
|
+
);
|
|
36525
|
+
}
|
|
36526
|
+
if (result.tempsReaped.length > 0) {
|
|
36527
|
+
console.log(
|
|
36528
|
+
` ${source_default.green("\u2713")} reaped ${result.tempsReaped.length} orphan temp file(s)`
|
|
36529
|
+
);
|
|
36530
|
+
}
|
|
36531
|
+
if (result.vacuumed) {
|
|
36532
|
+
const saved = result.sizeBeforeBytes - result.sizeAfterBytes;
|
|
36533
|
+
console.log(
|
|
36534
|
+
` ${source_default.green("\u2713")} VACUUM: ${formatBytes(result.sizeBeforeBytes)} \u2192 ${formatBytes(result.sizeAfterBytes)} ` + source_default.dim(`(reclaimed ${formatBytes(Math.max(0, saved))})`)
|
|
36535
|
+
);
|
|
36536
|
+
}
|
|
36537
|
+
console.log();
|
|
36538
|
+
if (result.protectedViolationsRemaining.length > 0) {
|
|
36539
|
+
console.log(
|
|
36540
|
+
source_default.yellow(
|
|
36541
|
+
" \u26A0 Violations remain in protected (system-of-record) tables:"
|
|
36542
|
+
)
|
|
36543
|
+
);
|
|
36544
|
+
for (const g of result.protectedViolationsRemaining) {
|
|
36545
|
+
console.log(` ${source_default.yellow(`${g.table}: ${g.count}`)}`);
|
|
36546
|
+
}
|
|
36547
|
+
console.log();
|
|
36548
|
+
}
|
|
36549
|
+
if (result.integrityOkAfter && result.fkViolationsAfter === 0) {
|
|
36550
|
+
console.log(source_default.green(" \u2713 Database repaired and healthy"));
|
|
36551
|
+
} else {
|
|
36552
|
+
console.log(
|
|
36553
|
+
source_default.red(
|
|
36554
|
+
` \u2717 Post-fix check: integrity ${result.integrityOkAfter ? "ok" : "FAILED"}, ${result.fkViolationsAfter} FK violation(s) remaining`
|
|
36555
|
+
)
|
|
36556
|
+
);
|
|
36557
|
+
process.exitCode = 1;
|
|
36558
|
+
}
|
|
36559
|
+
console.log();
|
|
36560
|
+
}
|
|
36561
|
+
);
|
|
36562
|
+
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) => {
|
|
36563
|
+
const ocrDir = resolveOcrDir();
|
|
36564
|
+
const dbPath = dbPathFor(ocrDir);
|
|
36565
|
+
guardExclusive(ocrDir, options.force ?? false, "vacuum");
|
|
36566
|
+
const db = await ensureDatabase(ocrDir);
|
|
36567
|
+
const result = vacuumDb(db, dbPath, { snapshot: options.snapshot !== false });
|
|
36568
|
+
console.log();
|
|
36569
|
+
if (result.snapshotPath) {
|
|
36570
|
+
console.log(` ${source_default.dim("snapshot:")} ${result.snapshotPath}`);
|
|
36571
|
+
}
|
|
36572
|
+
console.log(
|
|
36573
|
+
` ${source_default.green("\u2713")} VACUUM: ${formatBytes(result.sizeBeforeBytes)} \u2192 ${formatBytes(result.sizeAfterBytes)} ` + source_default.dim(`(reclaimed ${formatBytes(result.reclaimedBytes)})`)
|
|
36574
|
+
);
|
|
36575
|
+
console.log();
|
|
36576
|
+
});
|
|
36577
|
+
var pruneSubcommand = new Command("prune").description(
|
|
36578
|
+
"Drop derived artifacts of old CLOSED sessions (events + sessions kept)"
|
|
36579
|
+
).option(
|
|
36580
|
+
"--keep-sessions <n>",
|
|
36581
|
+
"protect the N most-recently-active closed sessions",
|
|
36582
|
+
(v) => parseInt(v, 10)
|
|
36583
|
+
).option(
|
|
36584
|
+
"--older-than <days>",
|
|
36585
|
+
"only prune closed sessions quiet for more than D days",
|
|
36586
|
+
(v) => parseInt(v, 10)
|
|
36587
|
+
).option("--dry-run", "show what would be pruned without deleting").option("--force", "proceed even if a live dashboard owns the database").action(
|
|
36588
|
+
async (options) => {
|
|
36589
|
+
const ocrDir = resolveOcrDir();
|
|
36590
|
+
const dbPath = dbPathFor(ocrDir);
|
|
36591
|
+
if (options.keepSessions === void 0 && options.olderThan === void 0) {
|
|
36592
|
+
fail4(
|
|
36593
|
+
"prune needs a bound: pass --older-than <days> and/or --keep-sessions <n>."
|
|
36594
|
+
);
|
|
36595
|
+
}
|
|
36596
|
+
if (!options.dryRun) {
|
|
36597
|
+
guardExclusive(ocrDir, options.force ?? false, "prune");
|
|
36598
|
+
}
|
|
36599
|
+
const db = await ensureDatabase(ocrDir);
|
|
36600
|
+
const result = pruneDb(db, dbPath, {
|
|
36601
|
+
keepSessions: options.keepSessions,
|
|
36602
|
+
olderThanDays: options.olderThan,
|
|
36603
|
+
dryRun: options.dryRun ?? false
|
|
36604
|
+
});
|
|
36605
|
+
console.log();
|
|
36606
|
+
if (result.prunedSessions.length === 0) {
|
|
36607
|
+
console.log(source_default.green(" \u2713 Nothing to prune"));
|
|
36608
|
+
console.log();
|
|
36609
|
+
return;
|
|
36610
|
+
}
|
|
36611
|
+
const verb = result.dryRun ? "Would prune" : "Pruned";
|
|
36612
|
+
console.log(
|
|
36613
|
+
source_default.bold(
|
|
36614
|
+
` ${verb} ${result.totalArtifactRows} artifact row(s) across ${result.prunedSessions.length} session(s)`
|
|
36615
|
+
)
|
|
36616
|
+
);
|
|
36617
|
+
console.log();
|
|
36618
|
+
for (const p of result.prunedSessions.slice(0, 20)) {
|
|
36619
|
+
console.log(
|
|
36620
|
+
` ${source_default.dim("\xB7")} ${p.sessionId} ${source_default.dim(`(${p.artifactRows} rows)`)}`
|
|
36621
|
+
);
|
|
36622
|
+
}
|
|
36623
|
+
if (result.prunedSessions.length > 20) {
|
|
36624
|
+
console.log(
|
|
36625
|
+
` ${source_default.dim(`\u2026 and ${result.prunedSessions.length - 20} more`)}`
|
|
36626
|
+
);
|
|
36627
|
+
}
|
|
36628
|
+
console.log();
|
|
36629
|
+
if (result.snapshotPath) {
|
|
36630
|
+
console.log(` ${source_default.dim("snapshot:")} ${result.snapshotPath}`);
|
|
36631
|
+
}
|
|
36632
|
+
console.log(
|
|
36633
|
+
source_default.dim(
|
|
36634
|
+
result.dryRun ? " Re-run without --dry-run to apply. Events + session rows are always kept." : " Events + session rows were kept; sessions remain fully auditable."
|
|
36635
|
+
)
|
|
36636
|
+
);
|
|
36637
|
+
console.log();
|
|
36638
|
+
}
|
|
36639
|
+
);
|
|
36640
|
+
function validatePruneBackupsOptions(options) {
|
|
36641
|
+
if (!Number.isInteger(options.keep) || options.keep < 0) {
|
|
36642
|
+
return `--keep must be a non-negative integer (got "${String(options.keep)}").`;
|
|
36643
|
+
}
|
|
36644
|
+
if (options.keep === 0 && !options.force && !options.dryRun) {
|
|
36645
|
+
return "--keep 0 removes every backup (including any just-written snapshot). Re-run with --dry-run to preview, or --force to confirm.";
|
|
36646
|
+
}
|
|
36647
|
+
return null;
|
|
36648
|
+
}
|
|
36649
|
+
var pruneBackupsSubcommand = new Command("prune-backups").description("Delete old ocr.db.bak.* snapshots, keeping the most recent few").option(
|
|
36650
|
+
"--keep <n>",
|
|
36651
|
+
"retain the N most-recent backups (default 1; 0 removes all, requires --force)",
|
|
36652
|
+
// Raw conversion only — `Number('oops')` is NaN and flows into
|
|
36653
|
+
// validatePruneBackupsOptions, the single validation home. (parseInt would
|
|
36654
|
+
// also silently accept "3abc" → 3; Number rejects it as NaN.)
|
|
36655
|
+
(v) => Number(v),
|
|
36656
|
+
1
|
|
36657
|
+
).option("--force", "permit --keep 0 (removing the last backup / safety net)").option("--dry-run", "show what would be deleted without deleting").action(async (options) => {
|
|
36658
|
+
const ocrDir = resolveOcrDir();
|
|
36659
|
+
const dataDir = join26(ocrDir, "data");
|
|
36660
|
+
const invalid = validatePruneBackupsOptions(options);
|
|
36661
|
+
if (invalid !== null) {
|
|
36662
|
+
fail4(invalid);
|
|
36663
|
+
}
|
|
36664
|
+
const result = pruneBackups(dataDir, dbPathFor(ocrDir), {
|
|
36665
|
+
keep: options.keep,
|
|
36666
|
+
dryRun: options.dryRun ?? false
|
|
36667
|
+
});
|
|
36668
|
+
console.log();
|
|
36669
|
+
if (result.deleted.length === 0) {
|
|
36670
|
+
console.log(source_default.green(" \u2713 No backups to remove"));
|
|
36671
|
+
console.log();
|
|
36672
|
+
return;
|
|
36673
|
+
}
|
|
36674
|
+
const verb = result.dryRun ? "Would delete" : "Deleted";
|
|
36675
|
+
console.log(
|
|
36676
|
+
source_default.bold(
|
|
36677
|
+
` ${verb} ${result.deleted.length} backup(s) \u2014 ${formatBytes(result.reclaimedBytes)}`
|
|
36678
|
+
)
|
|
36679
|
+
);
|
|
36680
|
+
console.log();
|
|
36681
|
+
for (const b of result.deleted) {
|
|
36682
|
+
console.log(` ${source_default.dim("\xB7")} ${b.name} ${source_default.dim(`(${formatBytes(b.sizeBytes)})`)}`);
|
|
36683
|
+
}
|
|
36684
|
+
if (result.kept.length > 0) {
|
|
36685
|
+
console.log();
|
|
36686
|
+
console.log(
|
|
36687
|
+
source_default.dim(` Kept ${result.kept.length} most-recent backup(s) as a safety net.`)
|
|
36688
|
+
);
|
|
36689
|
+
}
|
|
36690
|
+
console.log();
|
|
36691
|
+
});
|
|
36692
|
+
var dbCommand = new Command("db").description("Inspect and maintain the OCR SQLite database").addCommand(doctorSubcommand).addCommand(vacuumSubcommand).addCommand(pruneSubcommand).addCommand(pruneBackupsSubcommand);
|
|
36693
|
+
|
|
35717
36694
|
// src/commands/reviewers.ts
|
|
35718
36695
|
import { writeFileSync as writeFileSync9, renameSync as renameSync2 } from "node:fs";
|
|
35719
|
-
import { join as
|
|
36696
|
+
import { join as join27 } from "node:path";
|
|
36697
|
+
init_src();
|
|
35720
36698
|
async function readStdin3() {
|
|
35721
36699
|
const chunks = [];
|
|
35722
36700
|
for await (const chunk of process.stdin) {
|
|
@@ -35730,6 +36708,25 @@ async function readStdin3() {
|
|
|
35730
36708
|
}
|
|
35731
36709
|
var VALID_TIERS = /* @__PURE__ */ new Set(["holistic", "specialist", "persona", "custom"]);
|
|
35732
36710
|
var SLUG_RE = /^[a-z][a-z0-9-]*$/;
|
|
36711
|
+
var INJECTION_PATTERNS = [
|
|
36712
|
+
/ignore\s+(all\s+|the\s+)?(previous|prior|above)?\s*(instructions|prompts|rules)/i,
|
|
36713
|
+
/disregard\s+(all\s+|the\s+)?(previous|prior|above)/i,
|
|
36714
|
+
/\byou\s+are\s+now\b/i,
|
|
36715
|
+
/^\s*system\s*:/im,
|
|
36716
|
+
/\balways\s+(conclude|respond|reply|return|output|approve|reject|say)\b/i,
|
|
36717
|
+
/\bnew\s+rule\s*:/i
|
|
36718
|
+
];
|
|
36719
|
+
function warnIfSuspiciousPersona(label, fields) {
|
|
36720
|
+
const text = fields.filter((f) => typeof f === "string").join("\n");
|
|
36721
|
+
const hit = INJECTION_PATTERNS.find((re) => re.test(text));
|
|
36722
|
+
if (hit) {
|
|
36723
|
+
console.error(
|
|
36724
|
+
source_default.yellow(
|
|
36725
|
+
`\u26A0 ${label} contains text resembling a prompt-injection override (matched ${hit}). Review the persona before relying on it.`
|
|
36726
|
+
)
|
|
36727
|
+
);
|
|
36728
|
+
}
|
|
36729
|
+
}
|
|
35733
36730
|
function validateReviewersMeta(data) {
|
|
35734
36731
|
if (typeof data !== "object" || data === null || Array.isArray(data)) {
|
|
35735
36732
|
throw new Error("Payload must be a JSON object");
|
|
@@ -35767,6 +36764,19 @@ function validateReviewersMeta(data) {
|
|
|
35767
36764
|
if (!Array.isArray(r.focus_areas)) {
|
|
35768
36765
|
throw new Error(`${prefix}.focus_areas must be an array`);
|
|
35769
36766
|
}
|
|
36767
|
+
if (r.icon !== void 0 && typeof r.icon !== "string") {
|
|
36768
|
+
throw new Error(`${prefix}.icon must be a string if provided (got ${JSON.stringify(r.icon)})`);
|
|
36769
|
+
}
|
|
36770
|
+
if (typeof r.icon !== "string" || r.icon.length === 0) {
|
|
36771
|
+
r.icon = defaultIconFor(r.id, r.tier);
|
|
36772
|
+
}
|
|
36773
|
+
warnIfSuspiciousPersona(`${prefix} ("${r.name}")`, [
|
|
36774
|
+
r.name,
|
|
36775
|
+
r.description,
|
|
36776
|
+
...Array.isArray(r.focus_areas) ? r.focus_areas : [],
|
|
36777
|
+
r.known_for,
|
|
36778
|
+
r.philosophy
|
|
36779
|
+
]);
|
|
35770
36780
|
if (r.known_for !== void 0 && typeof r.known_for !== "string") {
|
|
35771
36781
|
throw new Error(`${prefix}.known_for must be a string if provided`);
|
|
35772
36782
|
}
|
|
@@ -35779,17 +36789,17 @@ function validateReviewersMeta(data) {
|
|
|
35779
36789
|
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) => {
|
|
35780
36790
|
const targetDir = process.cwd();
|
|
35781
36791
|
requireOcrSetup(targetDir);
|
|
35782
|
-
const ocrDir =
|
|
36792
|
+
const ocrDir = join27(targetDir, ".ocr");
|
|
35783
36793
|
if (!options.stdin) {
|
|
35784
36794
|
try {
|
|
35785
|
-
const reviewersDir =
|
|
35786
|
-
const configPath =
|
|
36795
|
+
const reviewersDir = join27(ocrDir, "skills", "references", "reviewers");
|
|
36796
|
+
const configPath = join27(ocrDir, "config.yaml");
|
|
35787
36797
|
const meta = generateReviewersMeta(reviewersDir, configPath);
|
|
35788
36798
|
if (!meta || meta.reviewers.length === 0) {
|
|
35789
36799
|
console.error(source_default.yellow("No reviewer files found in .ocr/skills/references/reviewers/"));
|
|
35790
36800
|
process.exit(1);
|
|
35791
36801
|
}
|
|
35792
|
-
const metaPath =
|
|
36802
|
+
const metaPath = join27(ocrDir, "reviewers-meta.json");
|
|
35793
36803
|
const tmpPath = metaPath + ".tmp";
|
|
35794
36804
|
writeFileSync9(tmpPath, JSON.stringify(meta, null, 2) + "\n");
|
|
35795
36805
|
renameSync2(tmpPath, metaPath);
|
|
@@ -35819,7 +36829,7 @@ var syncSubcommand2 = new Command("sync").description("Sync reviewers-meta.json
|
|
|
35819
36829
|
throw new Error("Invalid JSON on stdin");
|
|
35820
36830
|
}
|
|
35821
36831
|
const meta = validateReviewersMeta(parsed);
|
|
35822
|
-
const metaPath =
|
|
36832
|
+
const metaPath = join27(ocrDir, "reviewers-meta.json");
|
|
35823
36833
|
const tmpPath = metaPath + ".tmp";
|
|
35824
36834
|
writeFileSync9(tmpPath, JSON.stringify(meta, null, 2) + "\n");
|
|
35825
36835
|
renameSync2(tmpPath, metaPath);
|
|
@@ -35845,27 +36855,75 @@ var syncSubcommand2 = new Command("sync").description("Sync reviewers-meta.json
|
|
|
35845
36855
|
});
|
|
35846
36856
|
var reviewersCommand = new Command("reviewers").description("Manage OCR reviewer metadata").addCommand(syncSubcommand2);
|
|
35847
36857
|
|
|
36858
|
+
// src/commands/host.ts
|
|
36859
|
+
function describeRow(id) {
|
|
36860
|
+
const tool = getToolById(id);
|
|
36861
|
+
const caps = getHostCapabilities(id);
|
|
36862
|
+
return {
|
|
36863
|
+
id,
|
|
36864
|
+
name: tool?.name ?? id,
|
|
36865
|
+
subagentSpawn: caps.subagentSpawn,
|
|
36866
|
+
perTaskModel: caps.perTaskModel,
|
|
36867
|
+
phase4: caps.subagentSpawn ? "parallel-subagents" : "sequential"
|
|
36868
|
+
};
|
|
36869
|
+
}
|
|
36870
|
+
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) => {
|
|
36871
|
+
if (options.tool) {
|
|
36872
|
+
const id = options.tool.trim().toLowerCase();
|
|
36873
|
+
if (!getToolIds().includes(id)) {
|
|
36874
|
+
console.error(
|
|
36875
|
+
source_default.red(
|
|
36876
|
+
`Error: unknown tool id "${options.tool}". Valid ids: ${getToolIds().join(", ")}`
|
|
36877
|
+
)
|
|
36878
|
+
);
|
|
36879
|
+
process.exit(1);
|
|
36880
|
+
}
|
|
36881
|
+
const row = describeRow(id);
|
|
36882
|
+
if (options.json) {
|
|
36883
|
+
console.log(JSON.stringify(row, null, 2));
|
|
36884
|
+
} else {
|
|
36885
|
+
printRows([row]);
|
|
36886
|
+
}
|
|
36887
|
+
return;
|
|
36888
|
+
}
|
|
36889
|
+
const rows = AI_TOOLS.map((t) => describeRow(t.id));
|
|
36890
|
+
if (options.json) {
|
|
36891
|
+
console.log(JSON.stringify(rows, null, 2));
|
|
36892
|
+
} else {
|
|
36893
|
+
printRows(rows);
|
|
36894
|
+
}
|
|
36895
|
+
});
|
|
36896
|
+
function printRows(rows) {
|
|
36897
|
+
const yn = (v) => v ? source_default.green("yes") : source_default.dim("no");
|
|
36898
|
+
for (const row of rows) {
|
|
36899
|
+
console.log(
|
|
36900
|
+
`${source_default.bold(row.name.padEnd(20))} subagentSpawn=${yn(row.subagentSpawn)} perTaskModel=${yn(row.perTaskModel)} \u2192 ${source_default.cyan(row.phase4)}`
|
|
36901
|
+
);
|
|
36902
|
+
}
|
|
36903
|
+
}
|
|
36904
|
+
var hostCommand = new Command("host").description("Inspect host (AI CLI) capabilities").addCommand(capabilitiesSubcommand);
|
|
36905
|
+
|
|
35848
36906
|
// src/lib/update-check.ts
|
|
35849
36907
|
import { homedir } from "node:os";
|
|
35850
|
-
import { join as
|
|
35851
|
-
import { readFileSync as
|
|
36908
|
+
import { join as join28 } from "node:path";
|
|
36909
|
+
import { readFileSync as readFileSync15, writeFileSync as writeFileSync10, mkdirSync as mkdirSync8 } from "node:fs";
|
|
35852
36910
|
var PACKAGE_NAME = "@open-code-review/cli";
|
|
35853
36911
|
var REGISTRY_URL = `https://registry.npmjs.org/${PACKAGE_NAME}/latest`;
|
|
35854
|
-
var CACHE_DIR2 =
|
|
35855
|
-
var CACHE_FILE =
|
|
36912
|
+
var CACHE_DIR2 = join28(homedir(), ".ocr");
|
|
36913
|
+
var CACHE_FILE = join28(CACHE_DIR2, "update-check.json");
|
|
35856
36914
|
var CHECK_INTERVAL_MS = 4 * 60 * 60 * 1e3;
|
|
35857
36915
|
var FETCH_TIMEOUT_MS = 3e3;
|
|
35858
36916
|
function readCache(cacheFile) {
|
|
35859
36917
|
try {
|
|
35860
|
-
return JSON.parse(
|
|
36918
|
+
return JSON.parse(readFileSync15(cacheFile, "utf-8"));
|
|
35861
36919
|
} catch {
|
|
35862
36920
|
return null;
|
|
35863
36921
|
}
|
|
35864
36922
|
}
|
|
35865
|
-
function writeCache(cacheFile,
|
|
36923
|
+
function writeCache(cacheFile, cache2) {
|
|
35866
36924
|
try {
|
|
35867
|
-
|
|
35868
|
-
writeFileSync10(cacheFile, JSON.stringify(
|
|
36925
|
+
mkdirSync8(join28(cacheFile, ".."), { recursive: true });
|
|
36926
|
+
writeFileSync10(cacheFile, JSON.stringify(cache2));
|
|
35869
36927
|
} catch {
|
|
35870
36928
|
}
|
|
35871
36929
|
}
|
|
@@ -35885,16 +36943,16 @@ async function checkForUpdate(currentVersion, options) {
|
|
|
35885
36943
|
if (process.env.CI || process.env.OCR_NO_UPDATE_CHECK) {
|
|
35886
36944
|
return null;
|
|
35887
36945
|
}
|
|
35888
|
-
const cacheFile =
|
|
36946
|
+
const cacheFile = join28(options?.cacheDir ?? CACHE_DIR2, "update-check.json");
|
|
35889
36947
|
try {
|
|
35890
|
-
const
|
|
35891
|
-
if (
|
|
35892
|
-
if (!
|
|
35893
|
-
if (!isNewer(
|
|
36948
|
+
const cache2 = readCache(cacheFile);
|
|
36949
|
+
if (cache2 && Date.now() - cache2.lastCheck < CHECK_INTERVAL_MS) {
|
|
36950
|
+
if (!cache2.latestVersion) return null;
|
|
36951
|
+
if (!isNewer(cache2.latestVersion, currentVersion)) return null;
|
|
35894
36952
|
return {
|
|
35895
36953
|
updateAvailable: true,
|
|
35896
36954
|
currentVersion,
|
|
35897
|
-
latestVersion:
|
|
36955
|
+
latestVersion: cache2.latestVersion,
|
|
35898
36956
|
updateCommand: detectUpdateCommand()
|
|
35899
36957
|
};
|
|
35900
36958
|
}
|
|
@@ -35944,7 +37002,9 @@ program2.addCommand(reviewCommand);
|
|
|
35944
37002
|
program2.addCommand(updateCommand);
|
|
35945
37003
|
program2.addCommand(dashboardCommand);
|
|
35946
37004
|
program2.addCommand(doctorCommand);
|
|
37005
|
+
program2.addCommand(dbCommand);
|
|
35947
37006
|
program2.addCommand(reviewersCommand);
|
|
37007
|
+
program2.addCommand(hostCommand);
|
|
35948
37008
|
await program2.parseAsync();
|
|
35949
37009
|
if (subcommand && HUMAN_COMMANDS.has(subcommand)) {
|
|
35950
37010
|
const drift = checkLocalArtifactVersion(process.cwd(), CLI_VERSION);
|