@open-code-review/cli 2.1.0 → 2.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/dashboard/client/assets/{_basePickBy-B3ALyupE.js → _basePickBy-BBPb8BJA.js} +1 -1
- package/dist/dashboard/client/assets/{_baseUniq-b2RALAWc.js → _baseUniq-CFHdos6T.js} +1 -1
- package/dist/dashboard/client/assets/{arc-DcSVvhUd.js → arc-BKGGWA2F.js} +1 -1
- package/dist/dashboard/client/assets/{architectureDiagram-VXUJARFQ-BNUlmSCS.js → architectureDiagram-VXUJARFQ-B_ovNjX1.js} +1 -1
- package/dist/dashboard/client/assets/{blockDiagram-VD42YOAC-BmhiQVwa.js → blockDiagram-VD42YOAC-C2M-avVp.js} +1 -1
- package/dist/dashboard/client/assets/{c4Diagram-YG6GDRKO-jyJ3WOv5.js → c4Diagram-YG6GDRKO-BtOBpAzH.js} +1 -1
- package/dist/dashboard/client/assets/channel-rgw7C1e7.js +1 -0
- package/dist/dashboard/client/assets/{chunk-4BX2VUAB-x1dQU_s3.js → chunk-4BX2VUAB-Cz2EbHPl.js} +1 -1
- package/dist/dashboard/client/assets/{chunk-55IACEB6-CwbsE2XQ.js → chunk-55IACEB6-C8xpXw9G.js} +1 -1
- package/dist/dashboard/client/assets/{chunk-B4BG7PRW-BaE7c-ti.js → chunk-B4BG7PRW-BSRfOovX.js} +1 -1
- package/dist/dashboard/client/assets/{chunk-DI55MBZ5-Bw5PUaMK.js → chunk-DI55MBZ5-CEUbYQWn.js} +1 -1
- package/dist/dashboard/client/assets/{chunk-FMBD7UC4-B7cF6P3s.js → chunk-FMBD7UC4-5xWP6GRj.js} +1 -1
- package/dist/dashboard/client/assets/{chunk-QN33PNHL-OY4evNHd.js → chunk-QN33PNHL-DfNCVcy8.js} +1 -1
- package/dist/dashboard/client/assets/{chunk-QZHKN3VN-BpjQwIWz.js → chunk-QZHKN3VN--OdToKKu.js} +1 -1
- package/dist/dashboard/client/assets/{chunk-TZMSLE5B-D8b_Oq9B.js → chunk-TZMSLE5B-B_0K0Qso.js} +1 -1
- package/dist/dashboard/client/assets/classDiagram-2ON5EDUG-DTGi7d9X.js +1 -0
- package/dist/dashboard/client/assets/classDiagram-v2-WZHVMYZB-DTGi7d9X.js +1 -0
- package/dist/dashboard/client/assets/clone-Cz7hswqi.js +1 -0
- package/dist/dashboard/client/assets/{cose-bilkent-S5V4N54A-C-sfP8PN.js → cose-bilkent-S5V4N54A-Cc_Dmnxz.js} +1 -1
- package/dist/dashboard/client/assets/{dagre-6UL2VRFP-Cqfo0NRg.js → dagre-6UL2VRFP-DaAfvUXU.js} +1 -1
- package/dist/dashboard/client/assets/{diagram-PSM6KHXK-BR3ppxqI.js → diagram-PSM6KHXK-7idwN0rC.js} +1 -1
- package/dist/dashboard/client/assets/{diagram-QEK2KX5R-Dvcx6x3R.js → diagram-QEK2KX5R-D9j9H13n.js} +1 -1
- package/dist/dashboard/client/assets/{diagram-S2PKOQOG-DoyBLnVN.js → diagram-S2PKOQOG-SMF5SB0K.js} +1 -1
- package/dist/dashboard/client/assets/{erDiagram-Q2GNP2WA-hy77l1cL.js → erDiagram-Q2GNP2WA-EVJ4Qa2F.js} +1 -1
- package/dist/dashboard/client/assets/{flowDiagram-NV44I4VS-Bz0B1rKM.js → flowDiagram-NV44I4VS-tZ7SFE77.js} +1 -1
- package/dist/dashboard/client/assets/{ganttDiagram-JELNMOA3-CLgrZPoC.js → ganttDiagram-JELNMOA3-DFSqguY7.js} +1 -1
- package/dist/dashboard/client/assets/{gitGraphDiagram-V2S2FVAM-DwJ-1f-v.js → gitGraphDiagram-V2S2FVAM-CqHdP3HE.js} +1 -1
- package/dist/dashboard/client/assets/{graph-DDBMM_t2.js → graph-C0XnkNkk.js} +1 -1
- package/dist/dashboard/client/assets/{index-Cr9yEo_B.js → index-C3NEq704.js} +133 -138
- package/dist/dashboard/client/assets/index-CzxeSSaQ.css +1 -0
- package/dist/dashboard/client/assets/{infoDiagram-HS3SLOUP-Bhn1FmAk.js → infoDiagram-HS3SLOUP-DlXZo9U2.js} +1 -1
- package/dist/dashboard/client/assets/{journeyDiagram-XKPGCS4Q-CzGbjX1y.js → journeyDiagram-XKPGCS4Q-CgC8_7eN.js} +1 -1
- package/dist/dashboard/client/assets/{kanban-definition-3W4ZIXB7-Da77-WYk.js → kanban-definition-3W4ZIXB7-BMAw_jNp.js} +1 -1
- package/dist/dashboard/client/assets/{layout-CVwSB-GS.js → layout-XjM3Q-ka.js} +1 -1
- package/dist/dashboard/client/assets/{linear-CTRAc5Jn.js → linear-CMUrrr1X.js} +1 -1
- package/dist/dashboard/client/assets/{mermaid-renderer-Bjo170ax.js → mermaid-renderer-D2jYNs7K.js} +4 -4
- package/dist/dashboard/client/assets/{mindmap-definition-VGOIOE7T-B55C2odl.js → mindmap-definition-VGOIOE7T-CL4hv-vg.js} +1 -1
- package/dist/dashboard/client/assets/{pieDiagram-ADFJNKIX-5lrQLrSz.js → pieDiagram-ADFJNKIX-DTqv-1h1.js} +1 -1
- package/dist/dashboard/client/assets/{quadrantDiagram-AYHSOK5B-Bg55gC30.js → quadrantDiagram-AYHSOK5B-BpFlSW9N.js} +1 -1
- package/dist/dashboard/client/assets/{requirementDiagram-UZGBJVZJ-CyR4YFJY.js → requirementDiagram-UZGBJVZJ-BqYqqXL4.js} +1 -1
- package/dist/dashboard/client/assets/{sankeyDiagram-TZEHDZUN-BVWKr9_-.js → sankeyDiagram-TZEHDZUN-kEI9kntR.js} +1 -1
- package/dist/dashboard/client/assets/{sequenceDiagram-WL72ISMW-D0AJg_tE.js → sequenceDiagram-WL72ISMW-Cnu_1j-N.js} +1 -1
- package/dist/dashboard/client/assets/{stateDiagram-FKZM4ZOC-BuHpTgim.js → stateDiagram-FKZM4ZOC-BoC-rqoG.js} +1 -1
- package/dist/dashboard/client/assets/stateDiagram-v2-4FDKWEC3-COR3QD3v.js +1 -0
- package/dist/dashboard/client/assets/{timeline-definition-IT6M3QCI-LDhpAmDd.js → timeline-definition-IT6M3QCI-CXMWuzDL.js} +1 -1
- package/dist/dashboard/client/assets/{treemap-GDKQZRPO-Dd4gjvUl.js → treemap-GDKQZRPO-o9ZFgpbJ.js} +1 -1
- package/dist/dashboard/client/assets/{xychartDiagram-PRI3JC2R-B9RDod39.js → xychartDiagram-PRI3JC2R-CfIuUpeA.js} +1 -1
- package/dist/dashboard/client/index.html +2 -2
- package/dist/dashboard/server.js +1031 -426
- package/dist/index.js +1252 -268
- package/dist/lib/db/index.js +485 -24
- 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-Z1pPudAt.css +0 -1
- package/dist/dashboard/client/assets/stateDiagram-v2-4FDKWEC3-DwAPhteN.js +0 -1
package/dist/index.js
CHANGED
|
@@ -16017,6 +16017,80 @@ 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
|
+
function isProcessAlive(pid) {
|
|
16038
|
+
try {
|
|
16039
|
+
process.kill(pid, 0);
|
|
16040
|
+
return true;
|
|
16041
|
+
} catch (err) {
|
|
16042
|
+
return !(err instanceof Error && "code" in err && err.code === "ESRCH");
|
|
16043
|
+
}
|
|
16044
|
+
}
|
|
16045
|
+
function defaultIconFor(id, tier) {
|
|
16046
|
+
return BUILTIN_ICON_MAP[id] ?? (tier === "persona" ? "brain" : "user");
|
|
16047
|
+
}
|
|
16048
|
+
function hostCapabilitiesFor(vendor) {
|
|
16049
|
+
return vendor && HOST_CAPABILITIES[vendor] || DEFAULT_HOST_CAPABILITIES;
|
|
16050
|
+
}
|
|
16051
|
+
var execFilePromise, isWindows, BUILTIN_ICON_MAP, DEFAULT_HOST_CAPABILITIES, HOST_CAPABILITIES;
|
|
16052
|
+
var init_src = __esm({
|
|
16053
|
+
"../shared/platform/src/index.ts"() {
|
|
16054
|
+
"use strict";
|
|
16055
|
+
execFilePromise = promisify(execFile);
|
|
16056
|
+
isWindows = process.platform === "win32";
|
|
16057
|
+
BUILTIN_ICON_MAP = {
|
|
16058
|
+
architect: "blocks",
|
|
16059
|
+
fullstack: "layers",
|
|
16060
|
+
reliability: "activity",
|
|
16061
|
+
"staff-engineer": "compass",
|
|
16062
|
+
principal: "crown",
|
|
16063
|
+
frontend: "layout",
|
|
16064
|
+
backend: "server",
|
|
16065
|
+
infrastructure: "cloud",
|
|
16066
|
+
performance: "gauge",
|
|
16067
|
+
accessibility: "accessibility",
|
|
16068
|
+
data: "database",
|
|
16069
|
+
devops: "rocket",
|
|
16070
|
+
dx: "terminal",
|
|
16071
|
+
mobile: "smartphone",
|
|
16072
|
+
security: "shield-alert",
|
|
16073
|
+
quality: "sparkles",
|
|
16074
|
+
testing: "test-tubes",
|
|
16075
|
+
ai: "bot",
|
|
16076
|
+
"docs-writer": "file-text"
|
|
16077
|
+
};
|
|
16078
|
+
DEFAULT_HOST_CAPABILITIES = {
|
|
16079
|
+
subagentSpawn: false,
|
|
16080
|
+
perTaskModel: false
|
|
16081
|
+
};
|
|
16082
|
+
HOST_CAPABILITIES = {
|
|
16083
|
+
// Claude Code: Task tool + per-subagent model frontmatter.
|
|
16084
|
+
claude: { subagentSpawn: true, perTaskModel: true },
|
|
16085
|
+
// OpenCode: `--agent` sub-agent primitive, but no per-task model override.
|
|
16086
|
+
opencode: { subagentSpawn: true, perTaskModel: false },
|
|
16087
|
+
// Gemini CLI / Codex: no in-agent Task primitive → sequential Phase 4.
|
|
16088
|
+
gemini: { subagentSpawn: false, perTaskModel: false },
|
|
16089
|
+
codex: { subagentSpawn: false, perTaskModel: false }
|
|
16090
|
+
};
|
|
16091
|
+
}
|
|
16092
|
+
});
|
|
16093
|
+
|
|
16020
16094
|
// ../../node_modules/.pnpm/yaml@2.8.3/node_modules/yaml/dist/nodes/identity.js
|
|
16021
16095
|
var require_identity = __commonJS({
|
|
16022
16096
|
"../../node_modules/.pnpm/yaml@2.8.3/node_modules/yaml/dist/nodes/identity.js"(exports) {
|
|
@@ -24044,6 +24118,35 @@ var init_migrations = __esm({
|
|
|
24044
24118
|
db.run("DROP INDEX IF EXISTS idx_command_executions_parent;");
|
|
24045
24119
|
db.run("ALTER TABLE command_executions DROP COLUMN parent_id;");
|
|
24046
24120
|
}
|
|
24121
|
+
},
|
|
24122
|
+
{
|
|
24123
|
+
version: 14,
|
|
24124
|
+
description: "Self-heal markdown_artifacts duplication: collapse NULL-round duplicate rows and add a NULL-safe unique index so the dedup bug cannot recur",
|
|
24125
|
+
// The table's `UNIQUE(session_id, artifact_type, round_number, file_path)`
|
|
24126
|
+
// never deduped session-level artifacts because SQLite treats NULL ≠ NULL,
|
|
24127
|
+
// and the writer used `INSERT OR REPLACE` — so every re-parse of a
|
|
24128
|
+
// NULL-round artifact (context.md, map.md, …) appended a duplicate (one
|
|
24129
|
+
// context.md reached 775 identical rows, ~177 MB). The writer is now an
|
|
24130
|
+
// explicit UPDATE-or-INSERT; this migration heals existing DBs and adds a
|
|
24131
|
+
// NULL-collapsing unique index as a DB-level backstop.
|
|
24132
|
+
//
|
|
24133
|
+
// Orphan-row sweep (FK-dangling children from the pre-FK-enforcement era)
|
|
24134
|
+
// is intentionally NOT done here — it needs `PRAGMA foreign_keys = OFF`,
|
|
24135
|
+
// which is a no-op inside the migration transaction. `ocr db doctor --fix`
|
|
24136
|
+
// performs it outside a transaction.
|
|
24137
|
+
run: (db) => {
|
|
24138
|
+
db.run(`
|
|
24139
|
+
DELETE FROM markdown_artifacts
|
|
24140
|
+
WHERE rowid NOT IN (
|
|
24141
|
+
SELECT MAX(rowid) FROM markdown_artifacts
|
|
24142
|
+
GROUP BY session_id, artifact_type, IFNULL(round_number, -1), file_path
|
|
24143
|
+
)
|
|
24144
|
+
`);
|
|
24145
|
+
db.run(`
|
|
24146
|
+
CREATE UNIQUE INDEX IF NOT EXISTS idx_markdown_artifacts_logical
|
|
24147
|
+
ON markdown_artifacts(session_id, artifact_type, IFNULL(round_number, -1), file_path)
|
|
24148
|
+
`);
|
|
24149
|
+
}
|
|
24047
24150
|
}
|
|
24048
24151
|
];
|
|
24049
24152
|
}
|
|
@@ -24172,7 +24275,7 @@ var init_queries = __esm({
|
|
|
24172
24275
|
|
|
24173
24276
|
// src/lib/db/reconcile.ts
|
|
24174
24277
|
import { existsSync as existsSync10 } from "node:fs";
|
|
24175
|
-
import { isAbsolute as isAbsolute2, join as join12, dirname as
|
|
24278
|
+
import { isAbsolute as isAbsolute2, join as join12, dirname as dirname5 } from "node:path";
|
|
24176
24279
|
function hasTerminalArtifactEvent(db, sessionId, workflowType, currentRound, currentMapRun) {
|
|
24177
24280
|
const eventType = workflowType === "map" ? "map_completed" : "round_completed";
|
|
24178
24281
|
const round = workflowType === "map" ? currentMapRun : currentRound;
|
|
@@ -24213,7 +24316,7 @@ function hasInFlightDependents(db, sessionId) {
|
|
|
24213
24316
|
function resolveSessionDir(ocrDir, sessionDir) {
|
|
24214
24317
|
if (!sessionDir) return null;
|
|
24215
24318
|
if (isAbsolute2(sessionDir)) return sessionDir;
|
|
24216
|
-
return join12(
|
|
24319
|
+
return join12(dirname5(ocrDir), sessionDir);
|
|
24217
24320
|
}
|
|
24218
24321
|
function reconcileLegacyState(db, ocrDir, opts = {}) {
|
|
24219
24322
|
const dryRun = opts.dryRun ?? false;
|
|
@@ -24331,7 +24434,7 @@ var init_liveness = __esm({
|
|
|
24331
24434
|
});
|
|
24332
24435
|
|
|
24333
24436
|
// src/lib/state/exit-codes.ts
|
|
24334
|
-
var STATE_EXIT, StateError, CANCELLED_EXIT_CODE, ORPHAN_EXIT_CODE, CASCADE_CLOSE_EXIT_CODE;
|
|
24437
|
+
var STATE_EXIT, StateError, CANCELLED_EXIT_CODE, ORPHAN_EXIT_CODE, CASCADE_CLOSE_EXIT_CODE, WATCHDOG_DEADLINE_EXIT_CODE;
|
|
24335
24438
|
var init_exit_codes = __esm({
|
|
24336
24439
|
"src/lib/state/exit-codes.ts"() {
|
|
24337
24440
|
"use strict";
|
|
@@ -24356,6 +24459,7 @@ var init_exit_codes = __esm({
|
|
|
24356
24459
|
CANCELLED_EXIT_CODE = -2;
|
|
24357
24460
|
ORPHAN_EXIT_CODE = -3;
|
|
24358
24461
|
CASCADE_CLOSE_EXIT_CODE = -4;
|
|
24462
|
+
WATCHDOG_DEADLINE_EXIT_CODE = -5;
|
|
24359
24463
|
}
|
|
24360
24464
|
});
|
|
24361
24465
|
|
|
@@ -24738,24 +24842,431 @@ var init_agent_sessions = __esm({
|
|
|
24738
24842
|
}
|
|
24739
24843
|
});
|
|
24740
24844
|
|
|
24845
|
+
// src/lib/db/maintenance.ts
|
|
24846
|
+
import {
|
|
24847
|
+
existsSync as existsSync11,
|
|
24848
|
+
readdirSync as readdirSync5,
|
|
24849
|
+
statSync,
|
|
24850
|
+
unlinkSync as unlinkSync3,
|
|
24851
|
+
copyFileSync
|
|
24852
|
+
} from "node:fs";
|
|
24853
|
+
import { dirname as dirname6, join as join13, basename as basename7 } from "node:path";
|
|
24854
|
+
function withForeignKeysDisabled(db, fn) {
|
|
24855
|
+
db.pragma("foreign_keys = OFF");
|
|
24856
|
+
try {
|
|
24857
|
+
return fn();
|
|
24858
|
+
} finally {
|
|
24859
|
+
db.pragma("foreign_keys = ON");
|
|
24860
|
+
}
|
|
24861
|
+
}
|
|
24862
|
+
function scalarInt(db, sql) {
|
|
24863
|
+
const r = db.exec(sql);
|
|
24864
|
+
const v = r[0]?.values[0]?.[0];
|
|
24865
|
+
return typeof v === "number" ? v : Number(v ?? 0);
|
|
24866
|
+
}
|
|
24867
|
+
function foreignKeyViolationGroups(db) {
|
|
24868
|
+
const r = db.exec("PRAGMA foreign_key_check");
|
|
24869
|
+
const rows = r[0]?.values ?? [];
|
|
24870
|
+
const counts = /* @__PURE__ */ new Map();
|
|
24871
|
+
for (const row of rows) {
|
|
24872
|
+
const table = String(row[0]);
|
|
24873
|
+
counts.set(table, (counts.get(table) ?? 0) + 1);
|
|
24874
|
+
}
|
|
24875
|
+
return [...counts.entries()].map(([table, count]) => ({ table, count })).sort((a, b) => b.count - a.count);
|
|
24876
|
+
}
|
|
24877
|
+
function scanOrphanTempFiles(dataDir) {
|
|
24878
|
+
let entries;
|
|
24879
|
+
try {
|
|
24880
|
+
entries = readdirSync5(dataDir);
|
|
24881
|
+
} catch {
|
|
24882
|
+
return [];
|
|
24883
|
+
}
|
|
24884
|
+
const out = [];
|
|
24885
|
+
for (const name of entries) {
|
|
24886
|
+
const m = name.match(/^ocr\.db\.(\d+)\.tmp$/);
|
|
24887
|
+
if (!m) continue;
|
|
24888
|
+
const pid = Number(m[1]);
|
|
24889
|
+
let ageMs = 0;
|
|
24890
|
+
try {
|
|
24891
|
+
ageMs = Date.now() - statSync(join13(dataDir, name)).mtimeMs;
|
|
24892
|
+
} catch {
|
|
24893
|
+
continue;
|
|
24894
|
+
}
|
|
24895
|
+
const alive = isProcessAlive(pid);
|
|
24896
|
+
out.push({
|
|
24897
|
+
name,
|
|
24898
|
+
pid,
|
|
24899
|
+
ageMs,
|
|
24900
|
+
// Reapable only when the writer PID is dead AND the file is old enough
|
|
24901
|
+
// that no live mid-write could plausibly own it.
|
|
24902
|
+
reapable: !alive && ageMs > ONE_HOUR_MS
|
|
24903
|
+
});
|
|
24904
|
+
}
|
|
24905
|
+
return out;
|
|
24906
|
+
}
|
|
24907
|
+
function scanBackupFiles(dataDir, dbBase) {
|
|
24908
|
+
let entries;
|
|
24909
|
+
try {
|
|
24910
|
+
entries = readdirSync5(dataDir);
|
|
24911
|
+
} catch {
|
|
24912
|
+
return [];
|
|
24913
|
+
}
|
|
24914
|
+
const out = [];
|
|
24915
|
+
for (const name of entries) {
|
|
24916
|
+
if (!name.startsWith(`${dbBase}.bak`)) continue;
|
|
24917
|
+
try {
|
|
24918
|
+
out.push({ name, sizeBytes: statSync(join13(dataDir, name)).size });
|
|
24919
|
+
} catch {
|
|
24920
|
+
}
|
|
24921
|
+
}
|
|
24922
|
+
return out.sort((a, b) => b.sizeBytes - a.sizeBytes);
|
|
24923
|
+
}
|
|
24924
|
+
function collectDbHealth(db, dbPath) {
|
|
24925
|
+
const dataDir = dirname6(dbPath);
|
|
24926
|
+
const dbBase = basename7(dbPath);
|
|
24927
|
+
const pageSize = scalarInt(db, "PRAGMA page_size");
|
|
24928
|
+
const pageCount = scalarInt(db, "PRAGMA page_count");
|
|
24929
|
+
const freelistCount = scalarInt(db, "PRAGMA freelist_count");
|
|
24930
|
+
const integ = db.exec("PRAGMA integrity_check");
|
|
24931
|
+
const integRows = (integ[0]?.values ?? []).map((v) => String(v[0]));
|
|
24932
|
+
const integrityOk = integRows.length === 1 && integRows[0] === "ok";
|
|
24933
|
+
const allGroups = foreignKeyViolationGroups(db);
|
|
24934
|
+
const fkViolations = allGroups.filter((g) => !PROTECTED_TABLES.has(g.table));
|
|
24935
|
+
const protectedFkViolations = allGroups.filter(
|
|
24936
|
+
(g) => PROTECTED_TABLES.has(g.table)
|
|
24937
|
+
);
|
|
24938
|
+
const fileSizeBytes = existsSync11(dbPath) ? statSync(dbPath).size : 0;
|
|
24939
|
+
return {
|
|
24940
|
+
dbPath,
|
|
24941
|
+
fileSizeBytes,
|
|
24942
|
+
pageSize,
|
|
24943
|
+
pageCount,
|
|
24944
|
+
freelistCount,
|
|
24945
|
+
reclaimableBytes: freelistCount * pageSize,
|
|
24946
|
+
integrityOk,
|
|
24947
|
+
integrityErrors: integrityOk ? [] : integRows,
|
|
24948
|
+
fkViolations,
|
|
24949
|
+
protectedFkViolations,
|
|
24950
|
+
totalFkViolations: allGroups.reduce((n, g) => n + g.count, 0),
|
|
24951
|
+
markdownDuplicateRows: scalarInt(
|
|
24952
|
+
db,
|
|
24953
|
+
`SELECT COALESCE(SUM(cnt - 1), 0) FROM (
|
|
24954
|
+
SELECT COUNT(*) AS cnt FROM markdown_artifacts
|
|
24955
|
+
GROUP BY session_id, artifact_type, IFNULL(round_number, -1), file_path
|
|
24956
|
+
HAVING cnt > 1)`
|
|
24957
|
+
),
|
|
24958
|
+
orphanTempFiles: scanOrphanTempFiles(dataDir),
|
|
24959
|
+
backupFiles: scanBackupFiles(dataDir, dbBase),
|
|
24960
|
+
eventCount: scalarInt(db, "SELECT COUNT(*) FROM orchestration_events"),
|
|
24961
|
+
sessionCount: scalarInt(db, "SELECT COUNT(*) FROM sessions")
|
|
24962
|
+
};
|
|
24963
|
+
}
|
|
24964
|
+
function snapshotDb(db, dbPath, label = "doctor") {
|
|
24965
|
+
try {
|
|
24966
|
+
if (!existsSync11(dbPath) || statSync(dbPath).size === 0) return null;
|
|
24967
|
+
db.pragma("wal_checkpoint(TRUNCATE)");
|
|
24968
|
+
const ts = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
24969
|
+
const bakPath = `${dbPath}.bak.${label}.${ts}`;
|
|
24970
|
+
copyFileSync(dbPath, bakPath);
|
|
24971
|
+
return bakPath;
|
|
24972
|
+
} catch {
|
|
24973
|
+
return null;
|
|
24974
|
+
}
|
|
24975
|
+
}
|
|
24976
|
+
function reapOrphanDbFiles(dataDir) {
|
|
24977
|
+
const reaped = [];
|
|
24978
|
+
for (const f of scanOrphanTempFiles(dataDir)) {
|
|
24979
|
+
if (!f.reapable) continue;
|
|
24980
|
+
try {
|
|
24981
|
+
unlinkSync3(join13(dataDir, f.name));
|
|
24982
|
+
reaped.push(f.name);
|
|
24983
|
+
} catch {
|
|
24984
|
+
}
|
|
24985
|
+
}
|
|
24986
|
+
return reaped;
|
|
24987
|
+
}
|
|
24988
|
+
function reapStaleExecLogs(execLogsDir, maxAgeMs = SEVEN_DAYS_MS) {
|
|
24989
|
+
let entries;
|
|
24990
|
+
try {
|
|
24991
|
+
entries = readdirSync5(execLogsDir);
|
|
24992
|
+
} catch {
|
|
24993
|
+
return [];
|
|
24994
|
+
}
|
|
24995
|
+
const cutoff = Date.now() - maxAgeMs;
|
|
24996
|
+
const reaped = [];
|
|
24997
|
+
for (const name of entries) {
|
|
24998
|
+
if (!name.endsWith(".log")) continue;
|
|
24999
|
+
const full = join13(execLogsDir, name);
|
|
25000
|
+
try {
|
|
25001
|
+
if (statSync(full).mtimeMs > cutoff) continue;
|
|
25002
|
+
unlinkSync3(full);
|
|
25003
|
+
reaped.push(name);
|
|
25004
|
+
} catch {
|
|
25005
|
+
}
|
|
25006
|
+
}
|
|
25007
|
+
return reaped;
|
|
25008
|
+
}
|
|
25009
|
+
function pruneBackups(dataDir, dbPath, opts = {}) {
|
|
25010
|
+
const keep = opts.keep ?? 1;
|
|
25011
|
+
if (!Number.isInteger(keep) || keep < 0) {
|
|
25012
|
+
throw new Error(
|
|
25013
|
+
`pruneBackups: keep must be a non-negative integer (got ${String(keep)})`
|
|
25014
|
+
);
|
|
25015
|
+
}
|
|
25016
|
+
const dryRun = opts.dryRun ?? false;
|
|
25017
|
+
const dbBase = basename7(dbPath);
|
|
25018
|
+
const withMtime = [];
|
|
25019
|
+
for (const file of scanBackupFiles(dataDir, dbBase)) {
|
|
25020
|
+
try {
|
|
25021
|
+
withMtime.push({ file, mtimeMs: statSync(join13(dataDir, file.name)).mtimeMs });
|
|
25022
|
+
} catch {
|
|
25023
|
+
}
|
|
25024
|
+
}
|
|
25025
|
+
withMtime.sort((a, b) => b.mtimeMs - a.mtimeMs);
|
|
25026
|
+
const kept = withMtime.slice(0, keep).map((x) => x.file);
|
|
25027
|
+
const toDelete = withMtime.slice(keep).map((x) => x.file);
|
|
25028
|
+
const deleted = [];
|
|
25029
|
+
if (!dryRun) {
|
|
25030
|
+
for (const b of toDelete) {
|
|
25031
|
+
try {
|
|
25032
|
+
unlinkSync3(join13(dataDir, b.name));
|
|
25033
|
+
deleted.push(b);
|
|
25034
|
+
} catch {
|
|
25035
|
+
}
|
|
25036
|
+
}
|
|
25037
|
+
}
|
|
25038
|
+
const reported = dryRun ? toDelete : deleted;
|
|
25039
|
+
return {
|
|
25040
|
+
dryRun,
|
|
25041
|
+
deleted: reported,
|
|
25042
|
+
kept,
|
|
25043
|
+
reclaimedBytes: reported.reduce((n, b) => n + b.sizeBytes, 0)
|
|
25044
|
+
};
|
|
25045
|
+
}
|
|
25046
|
+
function fixDb(db, dbPath, opts = {}) {
|
|
25047
|
+
const dataDir = dirname6(dbPath);
|
|
25048
|
+
const sizeBeforeBytes = existsSync11(dbPath) ? statSync(dbPath).size : 0;
|
|
25049
|
+
const snapshotPath = opts.snapshot === false ? null : snapshotDb(db, dbPath, "doctor");
|
|
25050
|
+
const fkOrphansDeleted = [];
|
|
25051
|
+
withForeignKeysDisabled(db, () => {
|
|
25052
|
+
db.transaction(() => {
|
|
25053
|
+
for (const sweep of ORPHAN_SWEEPS) {
|
|
25054
|
+
const info = db.prepare(sweep.sql).run();
|
|
25055
|
+
const count = Number(info.changes);
|
|
25056
|
+
if (count > 0) fkOrphansDeleted.push({ table: sweep.table, count });
|
|
25057
|
+
}
|
|
25058
|
+
});
|
|
25059
|
+
});
|
|
25060
|
+
let markdownDupsDeleted = 0;
|
|
25061
|
+
db.transaction(() => {
|
|
25062
|
+
const info = db.prepare(MARKDOWN_DEDUP_SQL).run();
|
|
25063
|
+
markdownDupsDeleted = Number(info.changes);
|
|
25064
|
+
});
|
|
25065
|
+
const tempsReaped = opts.reapTemps === false ? [] : reapOrphanDbFiles(dataDir);
|
|
25066
|
+
let vacuumed = false;
|
|
25067
|
+
if (opts.vacuum !== false) {
|
|
25068
|
+
try {
|
|
25069
|
+
db.pragma("wal_checkpoint(TRUNCATE)");
|
|
25070
|
+
db.run("VACUUM");
|
|
25071
|
+
vacuumed = true;
|
|
25072
|
+
} catch {
|
|
25073
|
+
vacuumed = false;
|
|
25074
|
+
}
|
|
25075
|
+
}
|
|
25076
|
+
const post = collectDbHealth(db, dbPath);
|
|
25077
|
+
return {
|
|
25078
|
+
snapshotPath,
|
|
25079
|
+
fkOrphansDeleted,
|
|
25080
|
+
totalFkOrphansDeleted: fkOrphansDeleted.reduce((n, g) => n + g.count, 0),
|
|
25081
|
+
protectedViolationsRemaining: post.protectedFkViolations,
|
|
25082
|
+
markdownDupsDeleted,
|
|
25083
|
+
tempsReaped,
|
|
25084
|
+
vacuumed,
|
|
25085
|
+
sizeBeforeBytes,
|
|
25086
|
+
sizeAfterBytes: post.fileSizeBytes,
|
|
25087
|
+
integrityOkAfter: post.integrityOk,
|
|
25088
|
+
fkViolationsAfter: post.totalFkViolations
|
|
25089
|
+
};
|
|
25090
|
+
}
|
|
25091
|
+
function vacuumDb(db, dbPath, opts = {}) {
|
|
25092
|
+
const sizeBeforeBytes = existsSync11(dbPath) ? statSync(dbPath).size : 0;
|
|
25093
|
+
const snapshotPath = opts.snapshot === false ? null : snapshotDb(db, dbPath, "vacuum");
|
|
25094
|
+
db.pragma("wal_checkpoint(TRUNCATE)");
|
|
25095
|
+
db.run("VACUUM");
|
|
25096
|
+
db.pragma("wal_checkpoint(TRUNCATE)");
|
|
25097
|
+
const sizeAfterBytes = existsSync11(dbPath) ? statSync(dbPath).size : 0;
|
|
25098
|
+
return {
|
|
25099
|
+
snapshotPath,
|
|
25100
|
+
sizeBeforeBytes,
|
|
25101
|
+
sizeAfterBytes,
|
|
25102
|
+
reclaimedBytes: Math.max(0, sizeBeforeBytes - sizeAfterBytes)
|
|
25103
|
+
};
|
|
25104
|
+
}
|
|
25105
|
+
function countSessionArtifacts(db, sessionId) {
|
|
25106
|
+
const r = db.exec(
|
|
25107
|
+
`SELECT
|
|
25108
|
+
(SELECT COUNT(*) FROM markdown_artifacts WHERE session_id = ?) +
|
|
25109
|
+
(SELECT COUNT(*) FROM review_rounds WHERE session_id = ?) +
|
|
25110
|
+
(SELECT COUNT(*) FROM reviewer_outputs ro JOIN review_rounds rr ON ro.round_id = rr.id WHERE rr.session_id = ?) +
|
|
25111
|
+
(SELECT COUNT(*) FROM review_findings rf JOIN reviewer_outputs ro ON rf.reviewer_output_id = ro.id JOIN review_rounds rr ON ro.round_id = rr.id WHERE rr.session_id = ?) +
|
|
25112
|
+
(SELECT COUNT(*) FROM map_runs WHERE session_id = ?) +
|
|
25113
|
+
(SELECT COUNT(*) FROM chat_conversations WHERE session_id = ?)`,
|
|
25114
|
+
Array(6).fill(sessionId)
|
|
25115
|
+
);
|
|
25116
|
+
const v = r[0]?.values[0]?.[0];
|
|
25117
|
+
return typeof v === "number" ? v : Number(v ?? 0);
|
|
25118
|
+
}
|
|
25119
|
+
function pruneDb(db, dbPath, opts = {}) {
|
|
25120
|
+
const dryRun = opts.dryRun ?? false;
|
|
25121
|
+
const hasBound = opts.olderThanDays !== void 0 || opts.keepSessions !== void 0;
|
|
25122
|
+
if (!hasBound) {
|
|
25123
|
+
return { dryRun, snapshotPath: null, prunedSessions: [], totalArtifactRows: 0 };
|
|
25124
|
+
}
|
|
25125
|
+
const rows = db.exec(
|
|
25126
|
+
`SELECT s.id,
|
|
25127
|
+
(SELECT (julianday('now') - julianday(MAX(e.created_at))) * 86400
|
|
25128
|
+
FROM orchestration_events e WHERE e.session_id = s.id) AS quiet_seconds
|
|
25129
|
+
FROM sessions s
|
|
25130
|
+
WHERE s.status = 'closed'
|
|
25131
|
+
ORDER BY quiet_seconds ASC`
|
|
25132
|
+
);
|
|
25133
|
+
const closed = (rows[0]?.values ?? []).map((v) => ({
|
|
25134
|
+
id: String(v[0]),
|
|
25135
|
+
quietSeconds: typeof v[1] === "number" ? v[1] : Number(v[1] ?? 0)
|
|
25136
|
+
}));
|
|
25137
|
+
const keepN = opts.keepSessions ?? 0;
|
|
25138
|
+
const olderThanSeconds = opts.olderThanDays !== void 0 ? opts.olderThanDays * 86400 : null;
|
|
25139
|
+
const targets = closed.filter((s, idx) => {
|
|
25140
|
+
if (idx < keepN) return false;
|
|
25141
|
+
if (olderThanSeconds !== null && s.quietSeconds < olderThanSeconds)
|
|
25142
|
+
return false;
|
|
25143
|
+
return true;
|
|
25144
|
+
});
|
|
25145
|
+
const prunedSessions = [];
|
|
25146
|
+
for (const t of targets) {
|
|
25147
|
+
const artifactRows = countSessionArtifacts(db, t.id);
|
|
25148
|
+
if (artifactRows === 0) continue;
|
|
25149
|
+
prunedSessions.push({ sessionId: t.id, artifactRows });
|
|
25150
|
+
}
|
|
25151
|
+
if (dryRun || prunedSessions.length === 0) {
|
|
25152
|
+
return {
|
|
25153
|
+
dryRun,
|
|
25154
|
+
snapshotPath: null,
|
|
25155
|
+
prunedSessions,
|
|
25156
|
+
totalArtifactRows: prunedSessions.reduce((n, p) => n + p.artifactRows, 0)
|
|
25157
|
+
};
|
|
25158
|
+
}
|
|
25159
|
+
const snapshotPath = snapshotDb(db, dbPath, "prune");
|
|
25160
|
+
db.transaction(() => {
|
|
25161
|
+
for (const p of prunedSessions) {
|
|
25162
|
+
db.run("DELETE FROM review_rounds WHERE session_id = ?", [p.sessionId]);
|
|
25163
|
+
db.run("DELETE FROM map_runs WHERE session_id = ?", [p.sessionId]);
|
|
25164
|
+
db.run("DELETE FROM markdown_artifacts WHERE session_id = ?", [p.sessionId]);
|
|
25165
|
+
db.run("DELETE FROM chat_conversations WHERE session_id = ?", [p.sessionId]);
|
|
25166
|
+
}
|
|
25167
|
+
});
|
|
25168
|
+
return {
|
|
25169
|
+
dryRun,
|
|
25170
|
+
snapshotPath,
|
|
25171
|
+
prunedSessions,
|
|
25172
|
+
totalArtifactRows: prunedSessions.reduce((n, p) => n + p.artifactRows, 0)
|
|
25173
|
+
};
|
|
25174
|
+
}
|
|
25175
|
+
var PROTECTED_TABLES, ORPHAN_SWEEPS, MARKDOWN_DEDUP_SQL, ONE_HOUR_MS, SEVEN_DAYS_MS;
|
|
25176
|
+
var init_maintenance = __esm({
|
|
25177
|
+
"src/lib/db/maintenance.ts"() {
|
|
25178
|
+
"use strict";
|
|
25179
|
+
init_src();
|
|
25180
|
+
PROTECTED_TABLES = /* @__PURE__ */ new Set([
|
|
25181
|
+
"sessions",
|
|
25182
|
+
"orchestration_events",
|
|
25183
|
+
"agent_sessions",
|
|
25184
|
+
"command_executions",
|
|
25185
|
+
"schema_version"
|
|
25186
|
+
]);
|
|
25187
|
+
ORPHAN_SWEEPS = [
|
|
25188
|
+
// session-rooted parents first
|
|
25189
|
+
{
|
|
25190
|
+
table: "review_rounds",
|
|
25191
|
+
sql: "DELETE FROM review_rounds WHERE session_id NOT IN (SELECT id FROM sessions)"
|
|
25192
|
+
},
|
|
25193
|
+
{
|
|
25194
|
+
table: "map_runs",
|
|
25195
|
+
sql: "DELETE FROM map_runs WHERE session_id NOT IN (SELECT id FROM sessions)"
|
|
25196
|
+
},
|
|
25197
|
+
{
|
|
25198
|
+
table: "markdown_artifacts",
|
|
25199
|
+
sql: "DELETE FROM markdown_artifacts WHERE session_id NOT IN (SELECT id FROM sessions)"
|
|
25200
|
+
},
|
|
25201
|
+
{
|
|
25202
|
+
table: "chat_conversations",
|
|
25203
|
+
sql: "DELETE FROM chat_conversations WHERE session_id NOT IN (SELECT id FROM sessions)"
|
|
25204
|
+
},
|
|
25205
|
+
// second level (pick up parents deleted above)
|
|
25206
|
+
{
|
|
25207
|
+
table: "reviewer_outputs",
|
|
25208
|
+
sql: "DELETE FROM reviewer_outputs WHERE round_id NOT IN (SELECT id FROM review_rounds)"
|
|
25209
|
+
},
|
|
25210
|
+
{
|
|
25211
|
+
table: "map_sections",
|
|
25212
|
+
sql: "DELETE FROM map_sections WHERE map_run_id NOT IN (SELECT id FROM map_runs)"
|
|
25213
|
+
},
|
|
25214
|
+
{
|
|
25215
|
+
table: "chat_messages",
|
|
25216
|
+
sql: "DELETE FROM chat_messages WHERE conversation_id NOT IN (SELECT id FROM chat_conversations)"
|
|
25217
|
+
},
|
|
25218
|
+
{
|
|
25219
|
+
table: "user_round_progress",
|
|
25220
|
+
sql: "DELETE FROM user_round_progress WHERE round_id NOT IN (SELECT id FROM review_rounds)"
|
|
25221
|
+
},
|
|
25222
|
+
// third level
|
|
25223
|
+
{
|
|
25224
|
+
table: "review_findings",
|
|
25225
|
+
sql: "DELETE FROM review_findings WHERE reviewer_output_id NOT IN (SELECT id FROM reviewer_outputs)"
|
|
25226
|
+
},
|
|
25227
|
+
{
|
|
25228
|
+
table: "map_files",
|
|
25229
|
+
sql: "DELETE FROM map_files WHERE section_id NOT IN (SELECT id FROM map_sections)"
|
|
25230
|
+
},
|
|
25231
|
+
// leaves
|
|
25232
|
+
{
|
|
25233
|
+
table: "user_finding_progress",
|
|
25234
|
+
sql: "DELETE FROM user_finding_progress WHERE finding_id NOT IN (SELECT id FROM review_findings)"
|
|
25235
|
+
},
|
|
25236
|
+
{
|
|
25237
|
+
table: "user_file_progress",
|
|
25238
|
+
sql: "DELETE FROM user_file_progress WHERE map_file_id NOT IN (SELECT id FROM map_files)"
|
|
25239
|
+
}
|
|
25240
|
+
];
|
|
25241
|
+
MARKDOWN_DEDUP_SQL = `
|
|
25242
|
+
DELETE FROM markdown_artifacts
|
|
25243
|
+
WHERE rowid NOT IN (
|
|
25244
|
+
SELECT MAX(rowid) FROM markdown_artifacts
|
|
25245
|
+
GROUP BY session_id, artifact_type, IFNULL(round_number, -1), file_path
|
|
25246
|
+
)`;
|
|
25247
|
+
ONE_HOUR_MS = 60 * 60 * 1e3;
|
|
25248
|
+
SEVEN_DAYS_MS = 7 * 24 * 60 * 60 * 1e3;
|
|
25249
|
+
}
|
|
25250
|
+
});
|
|
25251
|
+
|
|
24741
25252
|
// src/lib/db/command-log.ts
|
|
24742
|
-
import { appendFileSync, existsSync as
|
|
24743
|
-
import { dirname as
|
|
25253
|
+
import { appendFileSync, existsSync as existsSync12, mkdirSync as mkdirSync4, readFileSync as readFileSync9, renameSync, writeFileSync as writeFileSync6 } from "node:fs";
|
|
25254
|
+
import { dirname as dirname7, join as join14 } from "node:path";
|
|
24744
25255
|
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
24745
25256
|
function generateCommandUid() {
|
|
24746
25257
|
return randomUUID2();
|
|
24747
25258
|
}
|
|
24748
25259
|
function cacheDir(ocrDir) {
|
|
24749
|
-
return
|
|
25260
|
+
return join14(ocrDir, "data", CACHE_DIR);
|
|
24750
25261
|
}
|
|
24751
25262
|
function commandLogPath(ocrDir) {
|
|
24752
|
-
return
|
|
25263
|
+
return join14(cacheDir(ocrDir), FILENAME);
|
|
24753
25264
|
}
|
|
24754
25265
|
function appendCommandLog(ocrDir, entry) {
|
|
24755
25266
|
try {
|
|
24756
25267
|
const filePath = commandLogPath(ocrDir);
|
|
24757
|
-
const dir =
|
|
24758
|
-
if (!
|
|
25268
|
+
const dir = dirname7(filePath);
|
|
25269
|
+
if (!existsSync12(dir)) mkdirSync4(dir, { recursive: true });
|
|
24759
25270
|
const line = JSON.stringify(entry) + "\n";
|
|
24760
25271
|
appendFileSync(filePath, line, { encoding: "utf-8" });
|
|
24761
25272
|
if (approxLineCount >= 0) approxLineCount++;
|
|
@@ -24765,7 +25276,7 @@ function appendCommandLog(ocrDir, entry) {
|
|
|
24765
25276
|
}
|
|
24766
25277
|
function readCommandLog(ocrDir) {
|
|
24767
25278
|
const filePath = commandLogPath(ocrDir);
|
|
24768
|
-
if (!
|
|
25279
|
+
if (!existsSync12(filePath)) return [];
|
|
24769
25280
|
const content = readFileSync9(filePath, "utf-8");
|
|
24770
25281
|
const entries = [];
|
|
24771
25282
|
for (const line of content.split("\n")) {
|
|
@@ -24851,6 +25362,7 @@ __export(db_exports, {
|
|
|
24851
25362
|
PID_REUSE_GUARD_MS: () => PID_REUSE_GUARD_MS,
|
|
24852
25363
|
STATE_EXIT: () => STATE_EXIT,
|
|
24853
25364
|
StateError: () => StateError,
|
|
25365
|
+
WATCHDOG_DEADLINE_EXIT_CODE: () => WATCHDOG_DEADLINE_EXIT_CODE,
|
|
24854
25366
|
appendCommandLog: () => appendCommandLog,
|
|
24855
25367
|
bindVendorSessionIdOpportunistically: () => bindVendorSessionIdOpportunistically,
|
|
24856
25368
|
bumpAgentSessionHeartbeat: () => bumpAgentSessionHeartbeat,
|
|
@@ -24858,10 +25370,12 @@ __export(db_exports, {
|
|
|
24858
25370
|
cascadeTerminateExecutions: () => cascadeTerminateExecutions,
|
|
24859
25371
|
closeAllDatabases: () => closeAllDatabases,
|
|
24860
25372
|
closeDatabase: () => closeDatabase,
|
|
25373
|
+
collectDbHealth: () => collectDbHealth,
|
|
24861
25374
|
commandLogPath: () => commandLogPath,
|
|
24862
25375
|
commitReasonClose: () => commitReasonClose,
|
|
24863
25376
|
defaultIsAlive: () => defaultIsAlive,
|
|
24864
25377
|
ensureDatabase: () => ensureDatabase,
|
|
25378
|
+
fixDb: () => fixDb,
|
|
24865
25379
|
formatUpgradeNotice: () => formatUpgradeNotice,
|
|
24866
25380
|
generateCommandUid: () => generateCommandUid,
|
|
24867
25381
|
getAgentSession: () => getAgentSession,
|
|
@@ -24873,6 +25387,7 @@ __export(db_exports, {
|
|
|
24873
25387
|
getLatestEventId: () => getLatestEventId,
|
|
24874
25388
|
getSchemaVersion: () => getSchemaVersion,
|
|
24875
25389
|
getSession: () => getSession,
|
|
25390
|
+
hasInFlightDependents: () => hasInFlightDependents,
|
|
24876
25391
|
insertAgentSession: () => insertAgentSession,
|
|
24877
25392
|
insertEvent: () => insertEvent,
|
|
24878
25393
|
insertSession: () => insertSession,
|
|
@@ -24882,7 +25397,11 @@ __export(db_exports, {
|
|
|
24882
25397
|
openDatabase: () => openDatabase,
|
|
24883
25398
|
probeEngine: () => probeEngine,
|
|
24884
25399
|
probeWrite: () => probeWrite,
|
|
25400
|
+
pruneBackups: () => pruneBackups,
|
|
25401
|
+
pruneDb: () => pruneDb,
|
|
24885
25402
|
readCommandLog: () => readCommandLog,
|
|
25403
|
+
reapOrphanDbFiles: () => reapOrphanDbFiles,
|
|
25404
|
+
reapStaleExecLogs: () => reapStaleExecLogs,
|
|
24886
25405
|
reconcileLegacyState: () => reconcileLegacyState,
|
|
24887
25406
|
recordVendorSessionIdForExecution: () => recordVendorSessionIdForExecution,
|
|
24888
25407
|
replayCommandLog: () => replayCommandLog,
|
|
@@ -24892,31 +25411,34 @@ __export(db_exports, {
|
|
|
24892
25411
|
runMigrations: () => runMigrations,
|
|
24893
25412
|
setAgentSessionStatus: () => setAgentSessionStatus,
|
|
24894
25413
|
setAgentSessionVendorId: () => setAgentSessionVendorId,
|
|
25414
|
+
snapshotDb: () => snapshotDb,
|
|
24895
25415
|
sqliteUtcMs: () => sqliteUtcMs,
|
|
24896
25416
|
sweepStaleAgentSessions: () => sweepStaleAgentSessions,
|
|
24897
25417
|
sweepStaleSessions: () => sweepStaleSessions,
|
|
24898
25418
|
updateAgentSession: () => updateAgentSession,
|
|
24899
25419
|
updateSession: () => updateSession,
|
|
24900
|
-
|
|
25420
|
+
vacuumDb: () => vacuumDb,
|
|
25421
|
+
walCheckpointTruncate: () => walCheckpointTruncate,
|
|
25422
|
+
withForeignKeysDisabled: () => withForeignKeysDisabled
|
|
24901
25423
|
});
|
|
24902
25424
|
import {
|
|
24903
|
-
existsSync as
|
|
24904
|
-
mkdirSync as
|
|
24905
|
-
copyFileSync,
|
|
24906
|
-
statSync,
|
|
25425
|
+
existsSync as existsSync13,
|
|
25426
|
+
mkdirSync as mkdirSync5,
|
|
25427
|
+
copyFileSync as copyFileSync2,
|
|
25428
|
+
statSync as statSync2,
|
|
24907
25429
|
mkdtempSync,
|
|
24908
25430
|
rmSync
|
|
24909
25431
|
} from "node:fs";
|
|
24910
25432
|
import { tmpdir } from "node:os";
|
|
24911
|
-
import { dirname as
|
|
25433
|
+
import { dirname as dirname8, join as join15 } from "node:path";
|
|
24912
25434
|
function maybeSnapshotBeforeUpgrade(db, dbPath, fromVersion) {
|
|
24913
25435
|
if (fromVersion < 1 || fromVersion >= V2_SCHEMA_VERSION) return null;
|
|
24914
25436
|
const bakPath = `${dbPath}.bak.v${fromVersion}`;
|
|
24915
|
-
if (
|
|
25437
|
+
if (existsSync13(bakPath)) return bakPath;
|
|
24916
25438
|
try {
|
|
24917
|
-
if (!
|
|
25439
|
+
if (!existsSync13(dbPath) || statSync2(dbPath).size === 0) return null;
|
|
24918
25440
|
db.pragma("wal_checkpoint(TRUNCATE)");
|
|
24919
|
-
|
|
25441
|
+
copyFileSync2(dbPath, bakPath);
|
|
24920
25442
|
return bakPath;
|
|
24921
25443
|
} catch {
|
|
24922
25444
|
return null;
|
|
@@ -24949,24 +25471,24 @@ async function openDatabase(dbPath) {
|
|
|
24949
25471
|
if (cached) {
|
|
24950
25472
|
return cached;
|
|
24951
25473
|
}
|
|
24952
|
-
const dir =
|
|
24953
|
-
if (!
|
|
24954
|
-
|
|
25474
|
+
const dir = dirname8(dbPath);
|
|
25475
|
+
if (!existsSync13(dir)) {
|
|
25476
|
+
mkdirSync5(dir, { recursive: true });
|
|
24955
25477
|
}
|
|
24956
25478
|
const db = openEngine(dbPath);
|
|
24957
25479
|
connections.set(dbPath, db);
|
|
24958
25480
|
return db;
|
|
24959
25481
|
}
|
|
24960
25482
|
async function getDb(ocrDir) {
|
|
24961
|
-
const dbPath =
|
|
25483
|
+
const dbPath = join15(ocrDir, "data", "ocr.db");
|
|
24962
25484
|
return openDatabase(dbPath);
|
|
24963
25485
|
}
|
|
24964
25486
|
async function ensureDatabase(ocrDir) {
|
|
24965
|
-
const dataDir =
|
|
24966
|
-
if (!
|
|
24967
|
-
|
|
25487
|
+
const dataDir = join15(ocrDir, "data");
|
|
25488
|
+
if (!existsSync13(dataDir)) {
|
|
25489
|
+
mkdirSync5(dataDir, { recursive: true });
|
|
24968
25490
|
}
|
|
24969
|
-
const dbPath =
|
|
25491
|
+
const dbPath = join15(dataDir, "ocr.db");
|
|
24970
25492
|
const db = await openDatabase(dbPath);
|
|
24971
25493
|
let before = 0;
|
|
24972
25494
|
try {
|
|
@@ -24994,7 +25516,7 @@ async function ensureDatabase(ocrDir) {
|
|
|
24994
25516
|
return db;
|
|
24995
25517
|
}
|
|
24996
25518
|
function walCheckpointTruncate(dbPath) {
|
|
24997
|
-
if (!
|
|
25519
|
+
if (!existsSync13(dbPath)) {
|
|
24998
25520
|
return "skipped";
|
|
24999
25521
|
}
|
|
25000
25522
|
const cached = connections.get(dbPath);
|
|
@@ -25036,8 +25558,8 @@ function closeAllDatabases() {
|
|
|
25036
25558
|
function probeWrite() {
|
|
25037
25559
|
let dir;
|
|
25038
25560
|
try {
|
|
25039
|
-
dir = mkdtempSync(
|
|
25040
|
-
const db = openEngine(
|
|
25561
|
+
dir = mkdtempSync(join15(tmpdir(), "ocr-probe-"));
|
|
25562
|
+
const db = openEngine(join15(dir, "probe.db"));
|
|
25041
25563
|
try {
|
|
25042
25564
|
db.run("CREATE TABLE _probe_write (id INTEGER PRIMARY KEY, v TEXT)");
|
|
25043
25565
|
db.transaction(() => {
|
|
@@ -25083,6 +25605,7 @@ var init_db = __esm({
|
|
|
25083
25605
|
init_result_mapper();
|
|
25084
25606
|
init_engine();
|
|
25085
25607
|
init_reconcile();
|
|
25608
|
+
init_maintenance();
|
|
25086
25609
|
init_migrations();
|
|
25087
25610
|
init_command_log();
|
|
25088
25611
|
V2_SCHEMA_VERSION = 12;
|
|
@@ -28613,6 +29136,8 @@ function ora(options) {
|
|
|
28613
29136
|
}
|
|
28614
29137
|
|
|
28615
29138
|
// src/lib/config.ts
|
|
29139
|
+
init_src();
|
|
29140
|
+
init_src();
|
|
28616
29141
|
var AI_TOOLS = [
|
|
28617
29142
|
{
|
|
28618
29143
|
id: "amazon-q",
|
|
@@ -28636,7 +29161,9 @@ var AI_TOOLS = [
|
|
|
28636
29161
|
configDir: ".claude",
|
|
28637
29162
|
commandsDir: ".claude/commands",
|
|
28638
29163
|
skillsDir: ".claude/skills",
|
|
28639
|
-
commandStrategy: "subdirectory"
|
|
29164
|
+
commandStrategy: "subdirectory",
|
|
29165
|
+
instructionFiles: [{ path: "CLAUDE.md", format: "markdown" }],
|
|
29166
|
+
vendorBinary: "claude"
|
|
28640
29167
|
},
|
|
28641
29168
|
{
|
|
28642
29169
|
id: "cline",
|
|
@@ -28652,7 +29179,9 @@ var AI_TOOLS = [
|
|
|
28652
29179
|
configDir: ".codex",
|
|
28653
29180
|
commandsDir: ".codex/commands",
|
|
28654
29181
|
skillsDir: ".codex/skills",
|
|
28655
|
-
commandStrategy: "subdirectory"
|
|
29182
|
+
commandStrategy: "subdirectory",
|
|
29183
|
+
// Codex reads AGENTS.md natively — no extra instruction file.
|
|
29184
|
+
vendorBinary: "codex"
|
|
28656
29185
|
},
|
|
28657
29186
|
{
|
|
28658
29187
|
id: "continue",
|
|
@@ -28676,7 +29205,9 @@ var AI_TOOLS = [
|
|
|
28676
29205
|
configDir: ".gemini",
|
|
28677
29206
|
commandsDir: ".gemini/commands",
|
|
28678
29207
|
skillsDir: ".gemini/skills",
|
|
28679
|
-
commandStrategy: "subdirectory"
|
|
29208
|
+
commandStrategy: "subdirectory",
|
|
29209
|
+
instructionFiles: [{ path: "GEMINI.md", format: "markdown" }],
|
|
29210
|
+
vendorBinary: "gemini"
|
|
28680
29211
|
},
|
|
28681
29212
|
{
|
|
28682
29213
|
id: "github-copilot",
|
|
@@ -28684,7 +29215,10 @@ var AI_TOOLS = [
|
|
|
28684
29215
|
configDir: ".github",
|
|
28685
29216
|
commandsDir: ".github/commands",
|
|
28686
29217
|
skillsDir: ".github/skills",
|
|
28687
|
-
commandStrategy: "subdirectory"
|
|
29218
|
+
commandStrategy: "subdirectory",
|
|
29219
|
+
instructionFiles: [
|
|
29220
|
+
{ path: ".github/copilot-instructions.md", format: "markdown" }
|
|
29221
|
+
]
|
|
28688
29222
|
},
|
|
28689
29223
|
{
|
|
28690
29224
|
id: "kilo-code",
|
|
@@ -28700,7 +29234,9 @@ var AI_TOOLS = [
|
|
|
28700
29234
|
configDir: ".opencode",
|
|
28701
29235
|
commandsDir: ".opencode/commands",
|
|
28702
29236
|
skillsDir: ".opencode/skills",
|
|
28703
|
-
commandStrategy: "subdirectory"
|
|
29237
|
+
commandStrategy: "subdirectory",
|
|
29238
|
+
// OpenCode reads AGENTS.md natively — no extra instruction file.
|
|
29239
|
+
vendorBinary: "opencode"
|
|
28704
29240
|
},
|
|
28705
29241
|
{
|
|
28706
29242
|
id: "qoder",
|
|
@@ -28724,9 +29260,16 @@ var AI_TOOLS = [
|
|
|
28724
29260
|
configDir: ".windsurf",
|
|
28725
29261
|
commandsDir: ".windsurf/workflows",
|
|
28726
29262
|
skillsDir: ".windsurf/skills",
|
|
28727
|
-
commandStrategy: "flat-prefixed"
|
|
29263
|
+
commandStrategy: "flat-prefixed",
|
|
29264
|
+
instructionFiles: [{ path: ".windsurfrules", format: "plaintext" }]
|
|
28728
29265
|
}
|
|
28729
29266
|
];
|
|
29267
|
+
function getToolById(id) {
|
|
29268
|
+
return AI_TOOLS.find((tool) => tool.id === id);
|
|
29269
|
+
}
|
|
29270
|
+
function getHostCapabilities(id) {
|
|
29271
|
+
return hostCapabilitiesFor(id);
|
|
29272
|
+
}
|
|
28730
29273
|
function getToolIds() {
|
|
28731
29274
|
return AI_TOOLS.map((tool) => tool.id);
|
|
28732
29275
|
}
|
|
@@ -28787,11 +29330,11 @@ function ensureGitignore(ocrDir) {
|
|
|
28787
29330
|
const gitignorePath = join(ocrDir, ".gitignore");
|
|
28788
29331
|
const block = buildManagedBlock();
|
|
28789
29332
|
let content = existsSync(gitignorePath) ? readFileSync2(gitignorePath, "utf-8") : "";
|
|
28790
|
-
const
|
|
29333
|
+
const blockRegex2 = new RegExp(
|
|
28791
29334
|
`${escapeRegex(START_MARKER)}[\\s\\S]*?${escapeRegex(END_MARKER)}\\n?`,
|
|
28792
29335
|
"g"
|
|
28793
29336
|
);
|
|
28794
|
-
if (
|
|
29337
|
+
if (blockRegex2.test(content)) {
|
|
28795
29338
|
content = content.replace(
|
|
28796
29339
|
new RegExp(
|
|
28797
29340
|
`${escapeRegex(START_MARKER)}[\\s\\S]*?${escapeRegex(END_MARKER)}\\n?`,
|
|
@@ -28981,6 +29524,7 @@ function resolveTeamComposition(team, override) {
|
|
|
28981
29524
|
}
|
|
28982
29525
|
|
|
28983
29526
|
// src/lib/installer.ts
|
|
29527
|
+
init_src();
|
|
28984
29528
|
var require2 = createRequire(import.meta.url);
|
|
28985
29529
|
function ensureDir(dir) {
|
|
28986
29530
|
if (!existsSync3(dir)) {
|
|
@@ -29089,27 +29633,6 @@ function installCommandsForTool(tool, commandsSource, targetDir) {
|
|
|
29089
29633
|
return false;
|
|
29090
29634
|
}
|
|
29091
29635
|
}
|
|
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
29636
|
var HOLISTIC_IDS = /* @__PURE__ */ new Set(["architect", "fullstack", "reliability", "staff-engineer", "principal"]);
|
|
29114
29637
|
var SPECIALIST_IDS = /* @__PURE__ */ new Set([
|
|
29115
29638
|
"frontend",
|
|
@@ -29212,7 +29735,7 @@ function generateReviewersMeta(reviewersDir, configPath) {
|
|
|
29212
29735
|
id,
|
|
29213
29736
|
name: extractReviewerName(content),
|
|
29214
29737
|
tier,
|
|
29215
|
-
icon:
|
|
29738
|
+
icon: defaultIconFor(id, tier),
|
|
29216
29739
|
description: extractReviewerDescription(content),
|
|
29217
29740
|
focus_areas: extractFocusAreas(content),
|
|
29218
29741
|
is_default: defaultTeamIds.has(id),
|
|
@@ -29313,7 +29836,9 @@ function installForTool(tool, targetDir) {
|
|
|
29313
29836
|
if (meta) {
|
|
29314
29837
|
writeFileSync3(metaPath, JSON.stringify(meta, null, 2) + "\n");
|
|
29315
29838
|
}
|
|
29316
|
-
} catch {
|
|
29839
|
+
} catch (err) {
|
|
29840
|
+
const msg = err instanceof Error ? err.message : "unknown error";
|
|
29841
|
+
warnings.push(`Could not generate reviewers-meta.json: ${msg}`);
|
|
29317
29842
|
}
|
|
29318
29843
|
const commandsOk = installCommandsForTool(tool, commandsSource, targetDir);
|
|
29319
29844
|
if (!commandsOk) {
|
|
@@ -29347,12 +29872,14 @@ function detectInstalledTools(targetDir, tools) {
|
|
|
29347
29872
|
}
|
|
29348
29873
|
|
|
29349
29874
|
// 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
|
-
|
|
29875
|
+
import { existsSync as existsSync4, mkdirSync as mkdirSync2, readFileSync as readFileSync5, writeFileSync as writeFileSync4 } from "node:fs";
|
|
29876
|
+
import { dirname as dirname2, join as join4 } from "node:path";
|
|
29877
|
+
var AGENTS_MD = { path: "AGENTS.md", format: "markdown" };
|
|
29878
|
+
var MARKERS = {
|
|
29879
|
+
markdown: { start: "<!-- OCR:START -->", end: "<!-- OCR:END -->" },
|
|
29880
|
+
plaintext: { start: "# OCR:START", end: "# OCR:END" }
|
|
29881
|
+
};
|
|
29882
|
+
var OCR_INSTRUCTION_BODY = `## Open Code Review Instructions
|
|
29356
29883
|
|
|
29357
29884
|
These instructions are for AI assistants handling code review in this project.
|
|
29358
29885
|
|
|
@@ -29368,37 +29895,95 @@ Use \`.ocr/skills/SKILL.md\` to learn:
|
|
|
29368
29895
|
- Available reviewer personas and their focus areas
|
|
29369
29896
|
- Session management and output format
|
|
29370
29897
|
|
|
29371
|
-
Keep this managed block so \`ocr init\` can refresh the instructions
|
|
29372
|
-
|
|
29373
|
-
|
|
29374
|
-
|
|
29898
|
+
Keep this managed block so \`ocr init\` can refresh the instructions.`;
|
|
29899
|
+
function buildBlock(format) {
|
|
29900
|
+
const { start, end } = MARKERS[format];
|
|
29901
|
+
return `${start}
|
|
29902
|
+
${OCR_INSTRUCTION_BODY}
|
|
29903
|
+
${end}`;
|
|
29904
|
+
}
|
|
29905
|
+
function escapeRegex2(str) {
|
|
29906
|
+
return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
29907
|
+
}
|
|
29908
|
+
function blockRegex(format) {
|
|
29909
|
+
const { start, end } = MARKERS[format];
|
|
29910
|
+
return new RegExp(
|
|
29911
|
+
`${escapeRegex2(start)}[\\s\\S]*?${escapeRegex2(end)}\\n?`,
|
|
29912
|
+
"g"
|
|
29913
|
+
);
|
|
29914
|
+
}
|
|
29915
|
+
function injectOcrInstructions(filePath, format = "markdown") {
|
|
29375
29916
|
try {
|
|
29917
|
+
mkdirSync2(dirname2(filePath), { recursive: true });
|
|
29376
29918
|
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, "");
|
|
29919
|
+
content = content.replace(blockRegex(format), "");
|
|
29382
29920
|
content = content.trim();
|
|
29383
29921
|
if (content.length > 0) {
|
|
29384
29922
|
content += "\n\n";
|
|
29385
29923
|
}
|
|
29386
|
-
content +=
|
|
29924
|
+
content += buildBlock(format) + "\n";
|
|
29387
29925
|
writeFileSync4(filePath, content);
|
|
29388
29926
|
return true;
|
|
29389
29927
|
} catch {
|
|
29390
29928
|
return false;
|
|
29391
29929
|
}
|
|
29392
29930
|
}
|
|
29393
|
-
function
|
|
29394
|
-
|
|
29931
|
+
function resolveTargets(selectedTools) {
|
|
29932
|
+
const targets = /* @__PURE__ */ new Map();
|
|
29933
|
+
targets.set(AGENTS_MD.path, AGENTS_MD);
|
|
29934
|
+
for (const tool of selectedTools) {
|
|
29935
|
+
for (const file of tool.instructionFiles ?? []) {
|
|
29936
|
+
targets.set(file.path, file);
|
|
29937
|
+
}
|
|
29938
|
+
}
|
|
29939
|
+
return [...targets.values()];
|
|
29940
|
+
}
|
|
29941
|
+
function plannedInstructionFiles(selectedTools) {
|
|
29942
|
+
return resolveTargets(selectedTools).map((t) => t.path);
|
|
29395
29943
|
}
|
|
29396
|
-
function injectIntoProjectFiles(targetDir) {
|
|
29397
|
-
const
|
|
29398
|
-
const
|
|
29399
|
-
const
|
|
29400
|
-
|
|
29401
|
-
|
|
29944
|
+
function injectIntoProjectFiles(targetDir, selectedTools) {
|
|
29945
|
+
const written = [];
|
|
29946
|
+
const failed = [];
|
|
29947
|
+
for (const target of resolveTargets(selectedTools)) {
|
|
29948
|
+
const ok = injectOcrInstructions(join4(targetDir, target.path), target.format);
|
|
29949
|
+
(ok ? written : failed).push(target.path);
|
|
29950
|
+
}
|
|
29951
|
+
return { written, failed };
|
|
29952
|
+
}
|
|
29953
|
+
function hasOcrInstructions(filePath) {
|
|
29954
|
+
if (!existsSync4(filePath)) {
|
|
29955
|
+
return false;
|
|
29956
|
+
}
|
|
29957
|
+
const content = readFileSync5(filePath, "utf-8");
|
|
29958
|
+
return Object.values(MARKERS).some(
|
|
29959
|
+
(m) => content.includes(m.start) && content.includes(m.end)
|
|
29960
|
+
);
|
|
29961
|
+
}
|
|
29962
|
+
function findStaleInstructionFiles(targetDir, writtenPaths) {
|
|
29963
|
+
const written = new Set(writtenPaths);
|
|
29964
|
+
const candidates = /* @__PURE__ */ new Set();
|
|
29965
|
+
for (const tool of AI_TOOLS) {
|
|
29966
|
+
for (const file of tool.instructionFiles ?? []) {
|
|
29967
|
+
candidates.add(file.path);
|
|
29968
|
+
}
|
|
29969
|
+
}
|
|
29970
|
+
const stale = [];
|
|
29971
|
+
for (const path2 of candidates) {
|
|
29972
|
+
if (written.has(path2)) continue;
|
|
29973
|
+
if (hasOcrInstructions(join4(targetDir, path2))) {
|
|
29974
|
+
stale.push(path2);
|
|
29975
|
+
}
|
|
29976
|
+
}
|
|
29977
|
+
return stale;
|
|
29978
|
+
}
|
|
29979
|
+
function formatStaleWarnings(stale, mode) {
|
|
29980
|
+
if (mode === "dry-run") {
|
|
29981
|
+
return stale.map((path2) => `${path2} (stale OCR block \u2014 left untouched)`);
|
|
29982
|
+
}
|
|
29983
|
+
const owner = mode === "init" ? "installed" : "configured";
|
|
29984
|
+
return stale.map(
|
|
29985
|
+
(path2) => `${path2} still has an OCR block but no ${owner} tool uses it \u2014 remove it manually if unneeded.`
|
|
29986
|
+
);
|
|
29402
29987
|
}
|
|
29403
29988
|
|
|
29404
29989
|
// src/lib/banner.ts
|
|
@@ -29501,29 +30086,10 @@ ${hint}
|
|
|
29501
30086
|
}
|
|
29502
30087
|
|
|
29503
30088
|
// src/lib/version.ts
|
|
29504
|
-
var CLI_VERSION = true ? "2.
|
|
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
|
-
}
|
|
30089
|
+
var CLI_VERSION = true ? "2.2.0" : createRequire(import.meta.url)("../../package.json").version;
|
|
29525
30090
|
|
|
29526
30091
|
// src/lib/deps.ts
|
|
30092
|
+
init_src();
|
|
29527
30093
|
var CATEGORY_ORDER = ["core", "ai-cli", "github"];
|
|
29528
30094
|
var CATEGORY_INFO = {
|
|
29529
30095
|
core: { label: "Core", hint: "" },
|
|
@@ -29695,7 +30261,7 @@ function printCapabilities(result) {
|
|
|
29695
30261
|
}
|
|
29696
30262
|
|
|
29697
30263
|
// 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
|
|
30264
|
+
var initCommand = new Command("init").description("Set up OCR for AI coding environments").option("-t, --tools <tools>", 'Comma-separated tool IDs or "all"').option("--no-inject", "Skip injecting instructions into project instruction files (AGENTS.md + each tool's native file)").action(async (options) => {
|
|
29699
30265
|
printBanner();
|
|
29700
30266
|
const depResult = checkDependencies();
|
|
29701
30267
|
printDepChecks(depResult);
|
|
@@ -29786,17 +30352,19 @@ var initCommand = new Command("init").description("Set up OCR for AI coding envi
|
|
|
29786
30352
|
const injectSpinner = ora(
|
|
29787
30353
|
"Injecting OCR instructions into project files..."
|
|
29788
30354
|
).start();
|
|
29789
|
-
const
|
|
30355
|
+
const installedTools = successful.map((r) => r.tool);
|
|
30356
|
+
const injectResults = injectIntoProjectFiles(targetDir, installedTools);
|
|
29790
30357
|
injectSpinner.stop();
|
|
29791
|
-
if (injectResults.
|
|
30358
|
+
if (injectResults.written.length > 0) {
|
|
29792
30359
|
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`);
|
|
30360
|
+
for (const path2 of injectResults.written) {
|
|
30361
|
+
console.log(` ${source_default.green("\u2713")} ${path2}`);
|
|
29798
30362
|
}
|
|
29799
30363
|
}
|
|
30364
|
+
const stale = findStaleInstructionFiles(targetDir, injectResults.written);
|
|
30365
|
+
for (const warning of formatStaleWarnings(stale, "init")) {
|
|
30366
|
+
console.log(source_default.yellow(` \u26A0 ${warning}`));
|
|
30367
|
+
}
|
|
29800
30368
|
}
|
|
29801
30369
|
console.log();
|
|
29802
30370
|
console.log(source_default.bold("Next steps:"));
|
|
@@ -29984,10 +30552,10 @@ var ReaddirpStream = class extends Readable {
|
|
|
29984
30552
|
}
|
|
29985
30553
|
async _formatEntry(dirent, path2) {
|
|
29986
30554
|
let entry;
|
|
29987
|
-
const
|
|
30555
|
+
const basename9 = this._isDirent ? dirent.name : dirent;
|
|
29988
30556
|
try {
|
|
29989
|
-
const fullPath = presolve(pjoin(path2,
|
|
29990
|
-
entry = { path: prelative(this._root, fullPath), fullPath, basename:
|
|
30557
|
+
const fullPath = presolve(pjoin(path2, basename9));
|
|
30558
|
+
entry = { path: prelative(this._root, fullPath), fullPath, basename: basename9 };
|
|
29991
30559
|
entry[this._statsProp] = this._isDirent ? dirent : await this._stat(fullPath);
|
|
29992
30560
|
} catch (err) {
|
|
29993
30561
|
this._onError(err);
|
|
@@ -30526,9 +31094,9 @@ var NodeFsHandler = class {
|
|
|
30526
31094
|
_watchWithNodeFs(path2, listener) {
|
|
30527
31095
|
const opts = this.fsw.options;
|
|
30528
31096
|
const directory = sysPath.dirname(path2);
|
|
30529
|
-
const
|
|
31097
|
+
const basename9 = sysPath.basename(path2);
|
|
30530
31098
|
const parent = this.fsw._getWatchedDir(directory);
|
|
30531
|
-
parent.add(
|
|
31099
|
+
parent.add(basename9);
|
|
30532
31100
|
const absolutePath = sysPath.resolve(path2);
|
|
30533
31101
|
const options = {
|
|
30534
31102
|
persistent: opts.persistent
|
|
@@ -30538,7 +31106,7 @@ var NodeFsHandler = class {
|
|
|
30538
31106
|
let closer;
|
|
30539
31107
|
if (opts.usePolling) {
|
|
30540
31108
|
const enableBin = opts.interval !== opts.binaryInterval;
|
|
30541
|
-
options.interval = enableBin && isBinaryPath(
|
|
31109
|
+
options.interval = enableBin && isBinaryPath(basename9) ? opts.binaryInterval : opts.interval;
|
|
30542
31110
|
closer = setFsWatchFileListener(path2, absolutePath, options, {
|
|
30543
31111
|
listener,
|
|
30544
31112
|
rawEmitter: this.fsw._emitRaw
|
|
@@ -30560,11 +31128,11 @@ var NodeFsHandler = class {
|
|
|
30560
31128
|
if (this.fsw.closed) {
|
|
30561
31129
|
return;
|
|
30562
31130
|
}
|
|
30563
|
-
const
|
|
30564
|
-
const
|
|
30565
|
-
const parent = this.fsw._getWatchedDir(
|
|
31131
|
+
const dirname10 = sysPath.dirname(file);
|
|
31132
|
+
const basename9 = sysPath.basename(file);
|
|
31133
|
+
const parent = this.fsw._getWatchedDir(dirname10);
|
|
30566
31134
|
let prevStats = stats;
|
|
30567
|
-
if (parent.has(
|
|
31135
|
+
if (parent.has(basename9))
|
|
30568
31136
|
return;
|
|
30569
31137
|
const listener = async (path2, newStats) => {
|
|
30570
31138
|
if (!this.fsw._throttle(THROTTLE_MODE_WATCH, file, 5))
|
|
@@ -30589,9 +31157,9 @@ var NodeFsHandler = class {
|
|
|
30589
31157
|
prevStats = newStats2;
|
|
30590
31158
|
}
|
|
30591
31159
|
} catch (error) {
|
|
30592
|
-
this.fsw._remove(
|
|
31160
|
+
this.fsw._remove(dirname10, basename9);
|
|
30593
31161
|
}
|
|
30594
|
-
} else if (parent.has(
|
|
31162
|
+
} else if (parent.has(basename9)) {
|
|
30595
31163
|
const at = newStats.atimeMs;
|
|
30596
31164
|
const mt = newStats.mtimeMs;
|
|
30597
31165
|
if (!at || at <= mt || mt !== prevStats.mtimeMs) {
|
|
@@ -31522,8 +32090,8 @@ function watch(paths, options = {}) {
|
|
|
31522
32090
|
}
|
|
31523
32091
|
|
|
31524
32092
|
// src/commands/progress.ts
|
|
31525
|
-
import { existsSync as
|
|
31526
|
-
import { join as
|
|
32093
|
+
import { existsSync as existsSync14, readdirSync as readdirSync6, statSync as statSync3 } from "node:fs";
|
|
32094
|
+
import { join as join16, basename as basename8 } from "node:path";
|
|
31527
32095
|
|
|
31528
32096
|
// ../../node_modules/.pnpm/log-update@7.0.2/node_modules/log-update/index.js
|
|
31529
32097
|
import process12 from "node:process";
|
|
@@ -32391,7 +32959,7 @@ var log_update_default = logUpdate;
|
|
|
32391
32959
|
var logUpdateStderr = createLogUpdate(process12.stderr);
|
|
32392
32960
|
|
|
32393
32961
|
// src/lib/guards.ts
|
|
32394
|
-
import { existsSync as existsSync6, mkdirSync as
|
|
32962
|
+
import { existsSync as existsSync6, mkdirSync as mkdirSync3 } from "node:fs";
|
|
32395
32963
|
import { join as join8 } from "node:path";
|
|
32396
32964
|
function checkOcrSetup(targetDir) {
|
|
32397
32965
|
const ocrDir = join8(targetDir, ".ocr");
|
|
@@ -32437,7 +33005,7 @@ function requireOcrSetup(targetDir) {
|
|
|
32437
33005
|
function ensureSessionsDir(targetDir) {
|
|
32438
33006
|
const sessionsDir = join8(targetDir, ".ocr", "sessions");
|
|
32439
33007
|
if (!existsSync6(sessionsDir)) {
|
|
32440
|
-
|
|
33008
|
+
mkdirSync3(sessionsDir, { recursive: true });
|
|
32441
33009
|
}
|
|
32442
33010
|
return sessionsDir;
|
|
32443
33011
|
}
|
|
@@ -33159,15 +33727,15 @@ function debounce(fn, delay) {
|
|
|
33159
33727
|
};
|
|
33160
33728
|
}
|
|
33161
33729
|
function findLatestActiveSession(sessionsDir) {
|
|
33162
|
-
if (!
|
|
33730
|
+
if (!existsSync14(sessionsDir)) {
|
|
33163
33731
|
return null;
|
|
33164
33732
|
}
|
|
33165
|
-
const sessions =
|
|
33166
|
-
const sessionPath =
|
|
33167
|
-
return
|
|
33733
|
+
const sessions = readdirSync6(sessionsDir).filter((name) => {
|
|
33734
|
+
const sessionPath = join16(sessionsDir, name);
|
|
33735
|
+
return statSync3(sessionPath).isDirectory();
|
|
33168
33736
|
}).sort().reverse();
|
|
33169
33737
|
for (const session of sessions) {
|
|
33170
|
-
const sessionPath =
|
|
33738
|
+
const sessionPath = join16(sessionsDir, session);
|
|
33171
33739
|
if (isSessionActive(sessionPath)) {
|
|
33172
33740
|
return session;
|
|
33173
33741
|
}
|
|
@@ -33182,8 +33750,8 @@ function getStrategyForSession(sessionPath, explicitWorkflow) {
|
|
|
33182
33750
|
return getStrategy(workflowType) ?? null;
|
|
33183
33751
|
}
|
|
33184
33752
|
async function initProgressDb(ocrDir) {
|
|
33185
|
-
const dbPath =
|
|
33186
|
-
if (!
|
|
33753
|
+
const dbPath = join16(ocrDir, "data", "ocr.db");
|
|
33754
|
+
if (!existsSync14(dbPath)) {
|
|
33187
33755
|
return;
|
|
33188
33756
|
}
|
|
33189
33757
|
try {
|
|
@@ -33208,11 +33776,11 @@ var progressCommand = new Command("progress").description("Watch real-time progr
|
|
|
33208
33776
|
const targetDir = process.cwd();
|
|
33209
33777
|
requireOcrSetup(targetDir);
|
|
33210
33778
|
const sessionsDir = ensureSessionsDir(targetDir);
|
|
33211
|
-
const ocrDir =
|
|
33779
|
+
const ocrDir = join16(targetDir, ".ocr");
|
|
33212
33780
|
await initProgressDb(ocrDir);
|
|
33213
33781
|
if (options.session) {
|
|
33214
|
-
const sessionPath =
|
|
33215
|
-
if (!
|
|
33782
|
+
const sessionPath = join16(sessionsDir, options.session);
|
|
33783
|
+
if (!existsSync14(sessionPath)) {
|
|
33216
33784
|
console.error(source_default.red(`Session not found: ${options.session}`));
|
|
33217
33785
|
process.exit(1);
|
|
33218
33786
|
}
|
|
@@ -33273,7 +33841,7 @@ var progressCommand = new Command("progress").description("Watch real-time progr
|
|
|
33273
33841
|
return;
|
|
33274
33842
|
}
|
|
33275
33843
|
let currentSession = findLatestActiveSession(sessionsDir);
|
|
33276
|
-
let currentSessionPath = currentSession ?
|
|
33844
|
+
let currentSessionPath = currentSession ? join16(sessionsDir, currentSession) : null;
|
|
33277
33845
|
let sessionWatcher = null;
|
|
33278
33846
|
const preservedStartTimes = {
|
|
33279
33847
|
review: void 0,
|
|
@@ -33281,11 +33849,11 @@ var progressCommand = new Command("progress").description("Watch real-time progr
|
|
|
33281
33849
|
};
|
|
33282
33850
|
let currentStrategy = null;
|
|
33283
33851
|
const updateDisplayImpl = () => {
|
|
33284
|
-
if (!currentSessionPath || !
|
|
33852
|
+
if (!currentSessionPath || !existsSync14(currentSessionPath) || !isSessionActive(currentSessionPath)) {
|
|
33285
33853
|
const latestActive = findLatestActiveSession(sessionsDir);
|
|
33286
33854
|
if (latestActive && latestActive !== currentSession) {
|
|
33287
33855
|
currentSession = latestActive;
|
|
33288
|
-
currentSessionPath =
|
|
33856
|
+
currentSessionPath = join16(sessionsDir, latestActive);
|
|
33289
33857
|
preservedStartTimes.review = void 0;
|
|
33290
33858
|
preservedStartTimes.map = void 0;
|
|
33291
33859
|
currentStrategy = null;
|
|
@@ -33298,7 +33866,7 @@ var progressCommand = new Command("progress").description("Watch real-time progr
|
|
|
33298
33866
|
currentStrategy = null;
|
|
33299
33867
|
}
|
|
33300
33868
|
}
|
|
33301
|
-
if (currentSessionPath &&
|
|
33869
|
+
if (currentSessionPath && existsSync14(currentSessionPath)) {
|
|
33302
33870
|
if (!options.workflow) {
|
|
33303
33871
|
const activeWorkflows = detectActiveWorkflows(currentSessionPath);
|
|
33304
33872
|
if (activeWorkflows.length > 1) {
|
|
@@ -33352,17 +33920,17 @@ var progressCommand = new Command("progress").description("Watch real-time progr
|
|
|
33352
33920
|
watchSession(currentSessionPath);
|
|
33353
33921
|
}
|
|
33354
33922
|
const timerInterval = setInterval(updateDisplay, 1e3);
|
|
33355
|
-
const watchDir =
|
|
33923
|
+
const watchDir = existsSync14(ocrDir) ? ocrDir : targetDir;
|
|
33356
33924
|
const dirWatcher = watch(watchDir, {
|
|
33357
33925
|
persistent: true,
|
|
33358
33926
|
ignoreInitial: true,
|
|
33359
33927
|
depth: 3
|
|
33360
33928
|
});
|
|
33361
33929
|
dirWatcher.on("addDir", (dirPath) => {
|
|
33362
|
-
const parentDir =
|
|
33363
|
-
const isDirectChild = parentDir.endsWith("sessions") || parentDir.endsWith(
|
|
33930
|
+
const parentDir = join16(dirPath, "..");
|
|
33931
|
+
const isDirectChild = parentDir.endsWith("sessions") || parentDir.endsWith(join16(".ocr", "sessions"));
|
|
33364
33932
|
if (isDirectChild && !dirPath.endsWith("sessions")) {
|
|
33365
|
-
const newSession =
|
|
33933
|
+
const newSession = basename8(dirPath);
|
|
33366
33934
|
currentSession = newSession;
|
|
33367
33935
|
currentSessionPath = dirPath;
|
|
33368
33936
|
preservedStartTimes.review = void 0;
|
|
@@ -33401,7 +33969,7 @@ function renderGenericWaiting() {
|
|
|
33401
33969
|
}
|
|
33402
33970
|
function renderCombinedProgress(sessionPath, preservedStartTimes, ocrDir) {
|
|
33403
33971
|
const lines = [];
|
|
33404
|
-
const session =
|
|
33972
|
+
const session = basename8(sessionPath);
|
|
33405
33973
|
lines.push("");
|
|
33406
33974
|
lines.push(
|
|
33407
33975
|
source_default.bold.white(" Open Code Review") + source_default.yellow(" \xB7 Parallel Workflows")
|
|
@@ -33462,21 +34030,21 @@ function renderCombinedProgress(sessionPath, preservedStartTimes, ocrDir) {
|
|
|
33462
34030
|
}
|
|
33463
34031
|
|
|
33464
34032
|
// src/commands/state.ts
|
|
33465
|
-
import { existsSync as
|
|
33466
|
-
import { join as
|
|
34033
|
+
import { existsSync as existsSync16, mkdirSync as mkdirSync7, readFileSync as readFileSync11 } from "node:fs";
|
|
34034
|
+
import { join as join18 } from "node:path";
|
|
33467
34035
|
|
|
33468
34036
|
// src/lib/state/index.ts
|
|
33469
34037
|
init_db();
|
|
33470
34038
|
init_exit_codes();
|
|
33471
34039
|
import {
|
|
33472
|
-
existsSync as
|
|
33473
|
-
mkdirSync as
|
|
33474
|
-
readdirSync as
|
|
34040
|
+
existsSync as existsSync15,
|
|
34041
|
+
mkdirSync as mkdirSync6,
|
|
34042
|
+
readdirSync as readdirSync7,
|
|
33475
34043
|
readFileSync as readFileSync10,
|
|
33476
|
-
statSync as
|
|
34044
|
+
statSync as statSync4,
|
|
33477
34045
|
writeFileSync as writeFileSync7
|
|
33478
34046
|
} from "node:fs";
|
|
33479
|
-
import { join as
|
|
34047
|
+
import { join as join17 } from "node:path";
|
|
33480
34048
|
|
|
33481
34049
|
// src/lib/state/phase-graph.ts
|
|
33482
34050
|
init_exit_codes();
|
|
@@ -33784,9 +34352,9 @@ function deriveNextRound(db, sessionId, fallbackRound) {
|
|
|
33784
34352
|
}
|
|
33785
34353
|
function hasArtifacts(dir) {
|
|
33786
34354
|
try {
|
|
33787
|
-
for (const entry of
|
|
34355
|
+
for (const entry of readdirSync7(dir, { withFileTypes: true })) {
|
|
33788
34356
|
if (entry.isDirectory()) {
|
|
33789
|
-
if (hasArtifacts(
|
|
34357
|
+
if (hasArtifacts(join17(dir, entry.name))) return true;
|
|
33790
34358
|
} else if (/\.(md|json)$/.test(entry.name)) {
|
|
33791
34359
|
return true;
|
|
33792
34360
|
}
|
|
@@ -33797,7 +34365,7 @@ function hasArtifacts(dir) {
|
|
|
33797
34365
|
}
|
|
33798
34366
|
function readJsonFromSource(params) {
|
|
33799
34367
|
if (params.source === "file") {
|
|
33800
|
-
if (!
|
|
34368
|
+
if (!existsSync15(params.filePath)) {
|
|
33801
34369
|
throw new StateError(STATE_EXIT.NOT_FOUND, `File not found: ${params.filePath}`);
|
|
33802
34370
|
}
|
|
33803
34371
|
return readFileSync10(params.filePath, "utf-8");
|
|
@@ -34124,7 +34692,7 @@ async function stateCompleteRound(params) {
|
|
|
34124
34692
|
}
|
|
34125
34693
|
const resolved = resolveSession(db, params.sessionId);
|
|
34126
34694
|
const roundNumber = params.round ?? resolved.current_round;
|
|
34127
|
-
const roundMetaPath =
|
|
34695
|
+
const roundMetaPath = join17(
|
|
34128
34696
|
resolved.session_dir,
|
|
34129
34697
|
"rounds",
|
|
34130
34698
|
`round-${roundNumber}`,
|
|
@@ -34145,8 +34713,8 @@ async function stateCompleteRound(params) {
|
|
|
34145
34713
|
);
|
|
34146
34714
|
}
|
|
34147
34715
|
if (params.requireFinal) {
|
|
34148
|
-
const finalPath =
|
|
34149
|
-
if (!
|
|
34716
|
+
const finalPath = join17(resolved.session_dir, "rounds", `round-${roundNumber}`, "final.md");
|
|
34717
|
+
if (!existsSync15(finalPath)) {
|
|
34150
34718
|
throw new StateError(
|
|
34151
34719
|
STATE_EXIT.INVARIANT_UNMET,
|
|
34152
34720
|
`Cannot complete round: --require-final set but ${finalPath} is missing.`
|
|
@@ -34155,8 +34723,8 @@ async function stateCompleteRound(params) {
|
|
|
34155
34723
|
}
|
|
34156
34724
|
let metaPath;
|
|
34157
34725
|
if (params.source === "stdin") {
|
|
34158
|
-
const roundDir =
|
|
34159
|
-
|
|
34726
|
+
const roundDir = join17(resolved.session_dir, "rounds", `round-${roundNumber}`);
|
|
34727
|
+
mkdirSync6(roundDir, { recursive: true });
|
|
34160
34728
|
metaPath = roundMetaPath;
|
|
34161
34729
|
writeFileSync7(metaPath, JSON.stringify(meta, null, 2));
|
|
34162
34730
|
}
|
|
@@ -34210,7 +34778,7 @@ async function stateCompleteMap(params) {
|
|
|
34210
34778
|
}
|
|
34211
34779
|
const resolved = resolveSession(db, params.sessionId);
|
|
34212
34780
|
const mapRunNumber = params.mapRun ?? resolved.current_map_run;
|
|
34213
|
-
const mapMetaPath =
|
|
34781
|
+
const mapMetaPath = join17(
|
|
34214
34782
|
resolved.session_dir,
|
|
34215
34783
|
"map",
|
|
34216
34784
|
"runs",
|
|
@@ -34233,8 +34801,8 @@ async function stateCompleteMap(params) {
|
|
|
34233
34801
|
}
|
|
34234
34802
|
let metaPath;
|
|
34235
34803
|
if (params.source === "stdin") {
|
|
34236
|
-
const runDir =
|
|
34237
|
-
|
|
34804
|
+
const runDir = join17(resolved.session_dir, "map", "runs", `run-${mapRunNumber}`);
|
|
34805
|
+
mkdirSync6(runDir, { recursive: true });
|
|
34238
34806
|
metaPath = mapMetaPath;
|
|
34239
34807
|
writeFileSync7(metaPath, JSON.stringify(meta, null, 2));
|
|
34240
34808
|
}
|
|
@@ -34319,17 +34887,17 @@ async function stateStatus(ocrDir, sessionId) {
|
|
|
34319
34887
|
}
|
|
34320
34888
|
async function stateSync(ocrDir) {
|
|
34321
34889
|
const db = await ensureDatabase(ocrDir);
|
|
34322
|
-
const sessionsRoot =
|
|
34323
|
-
if (!
|
|
34890
|
+
const sessionsRoot = join17(ocrDir, "sessions");
|
|
34891
|
+
if (!existsSync15(sessionsRoot)) {
|
|
34324
34892
|
return 0;
|
|
34325
34893
|
}
|
|
34326
|
-
const entries =
|
|
34327
|
-
const fullPath =
|
|
34328
|
-
return
|
|
34894
|
+
const entries = readdirSync7(sessionsRoot).filter((name) => {
|
|
34895
|
+
const fullPath = join17(sessionsRoot, name);
|
|
34896
|
+
return statSync4(fullPath).isDirectory();
|
|
34329
34897
|
});
|
|
34330
34898
|
let synced = 0;
|
|
34331
34899
|
for (const dirName of entries) {
|
|
34332
|
-
const dirPath =
|
|
34900
|
+
const dirPath = join17(sessionsRoot, dirName);
|
|
34333
34901
|
const existing = getSession(db, dirName);
|
|
34334
34902
|
if (existing) {
|
|
34335
34903
|
continue;
|
|
@@ -34337,8 +34905,8 @@ async function stateSync(ocrDir) {
|
|
|
34337
34905
|
if (!hasArtifacts(dirPath)) {
|
|
34338
34906
|
continue;
|
|
34339
34907
|
}
|
|
34340
|
-
const hasRoundsDir =
|
|
34341
|
-
const hasMapDir =
|
|
34908
|
+
const hasRoundsDir = existsSync15(join17(dirPath, "rounds"));
|
|
34909
|
+
const hasMapDir = existsSync15(join17(dirPath, "map"));
|
|
34342
34910
|
const workflowType = hasMapDir && !hasRoundsDir ? "map" : "review";
|
|
34343
34911
|
const branchMatch = dirName.match(/^\d{4}-\d{2}-\d{2}-(.+)$/);
|
|
34344
34912
|
const branch = branchMatch?.[1] ?? dirName;
|
|
@@ -34347,14 +34915,14 @@ async function stateSync(ocrDir) {
|
|
|
34347
34915
|
let inferredRound = 1;
|
|
34348
34916
|
let inferredMapRun = 1;
|
|
34349
34917
|
if (workflowType === "review") {
|
|
34350
|
-
const roundsDir =
|
|
34351
|
-
if (
|
|
34352
|
-
const roundDirs =
|
|
34918
|
+
const roundsDir = join17(dirPath, "rounds");
|
|
34919
|
+
if (existsSync15(roundsDir)) {
|
|
34920
|
+
const roundDirs = readdirSync7(roundsDir).filter((d) => /^round-\d+$/.test(d)).map((d) => parseInt(d.replace("round-", ""), 10)).filter((n) => Number.isFinite(n)).sort((a, b) => a - b);
|
|
34353
34921
|
const latestRoundNum = roundDirs[roundDirs.length - 1];
|
|
34354
34922
|
if (latestRoundNum !== void 0) {
|
|
34355
34923
|
inferredRound = latestRoundNum;
|
|
34356
|
-
if (
|
|
34357
|
-
|
|
34924
|
+
if (existsSync15(
|
|
34925
|
+
join17(roundsDir, `round-${latestRoundNum}`, "final.md")
|
|
34358
34926
|
)) {
|
|
34359
34927
|
inferredPhase = "complete";
|
|
34360
34928
|
inferredPhaseNumber = 8;
|
|
@@ -34362,13 +34930,13 @@ async function stateSync(ocrDir) {
|
|
|
34362
34930
|
}
|
|
34363
34931
|
}
|
|
34364
34932
|
} else if (workflowType === "map") {
|
|
34365
|
-
const runsDir =
|
|
34366
|
-
if (
|
|
34367
|
-
const runDirs =
|
|
34933
|
+
const runsDir = join17(dirPath, "map", "runs");
|
|
34934
|
+
if (existsSync15(runsDir)) {
|
|
34935
|
+
const runDirs = readdirSync7(runsDir).filter((d) => /^run-\d+$/.test(d)).map((d) => parseInt(d.replace("run-", ""), 10)).filter((n) => Number.isFinite(n)).sort((a, b) => a - b);
|
|
34368
34936
|
const latestRunNum = runDirs[runDirs.length - 1];
|
|
34369
34937
|
if (latestRunNum !== void 0) {
|
|
34370
34938
|
inferredMapRun = latestRunNum;
|
|
34371
|
-
if (
|
|
34939
|
+
if (existsSync15(join17(runsDir, `run-${latestRunNum}`, "map.md"))) {
|
|
34372
34940
|
inferredPhase = "complete";
|
|
34373
34941
|
inferredPhaseNumber = 6;
|
|
34374
34942
|
}
|
|
@@ -34406,7 +34974,7 @@ init_command_log();
|
|
|
34406
34974
|
init_db();
|
|
34407
34975
|
init_db();
|
|
34408
34976
|
function readDashboardSpawnMarker(ocrDir) {
|
|
34409
|
-
const path2 =
|
|
34977
|
+
const path2 = join18(ocrDir, "data", "dashboard-active-spawn.json");
|
|
34410
34978
|
let raw;
|
|
34411
34979
|
try {
|
|
34412
34980
|
raw = readFileSync11(path2, "utf-8");
|
|
@@ -34471,7 +35039,7 @@ async function linkDashboardInvocation(ocrDir, sessionId, explicitUid, label) {
|
|
|
34471
35039
|
var showSubcommand = new Command("show").description("Show current session state").option("--session-id <id>", "Session ID (defaults to latest active)").option("--json", "Output as JSON").action(async (options) => {
|
|
34472
35040
|
const targetDir = process.cwd();
|
|
34473
35041
|
requireOcrSetup(targetDir);
|
|
34474
|
-
const ocrDir =
|
|
35042
|
+
const ocrDir = join18(targetDir, ".ocr");
|
|
34475
35043
|
try {
|
|
34476
35044
|
const result = await stateShow(ocrDir, options.sessionId);
|
|
34477
35045
|
if (!result) {
|
|
@@ -34540,7 +35108,7 @@ var showSubcommand = new Command("show").description("Show current session state
|
|
|
34540
35108
|
var syncSubcommand = new Command("sync").description("Rebuild session state from filesystem artifacts").action(async () => {
|
|
34541
35109
|
const targetDir = process.cwd();
|
|
34542
35110
|
requireOcrSetup(targetDir);
|
|
34543
|
-
const ocrDir =
|
|
35111
|
+
const ocrDir = join18(targetDir, ".ocr");
|
|
34544
35112
|
try {
|
|
34545
35113
|
const synced = await stateSync(ocrDir);
|
|
34546
35114
|
console.log(`Synced ${synced} session${synced !== 1 ? "s" : ""} from filesystem.`);
|
|
@@ -34567,7 +35135,7 @@ var reconcileSubcommand = new Command("reconcile").description(
|
|
|
34567
35135
|
).option("--dry-run", "Print the repair plan without writing anything").option("--json", "Output the result as JSON").action(async (options) => {
|
|
34568
35136
|
const targetDir = process.cwd();
|
|
34569
35137
|
requireOcrSetup(targetDir);
|
|
34570
|
-
const ocrDir =
|
|
35138
|
+
const ocrDir = join18(targetDir, ".ocr");
|
|
34571
35139
|
try {
|
|
34572
35140
|
const db = await ensureDatabase(ocrDir);
|
|
34573
35141
|
const result = reconcileLegacyState(db, ocrDir, { dryRun: options.dryRun });
|
|
@@ -34626,9 +35194,9 @@ var beginSubcommand = new Command("begin").description("Start or resume a workfl
|
|
|
34626
35194
|
async (options) => {
|
|
34627
35195
|
const targetDir = process.cwd();
|
|
34628
35196
|
requireOcrSetup(targetDir);
|
|
34629
|
-
const ocrDir =
|
|
34630
|
-
const sessionDir = options.sessionDir ??
|
|
34631
|
-
if (!
|
|
35197
|
+
const ocrDir = join18(targetDir, ".ocr");
|
|
35198
|
+
const sessionDir = options.sessionDir ?? join18(ocrDir, "sessions", options.sessionId);
|
|
35199
|
+
if (!existsSync16(sessionDir)) mkdirSync7(sessionDir, { recursive: true });
|
|
34632
35200
|
try {
|
|
34633
35201
|
const result = await stateBegin({
|
|
34634
35202
|
sessionId: options.sessionId,
|
|
@@ -34650,7 +35218,7 @@ var advanceSubcommand = new Command("advance").description("Advance the workflow
|
|
|
34650
35218
|
async (options) => {
|
|
34651
35219
|
const targetDir = process.cwd();
|
|
34652
35220
|
requireOcrSetup(targetDir);
|
|
34653
|
-
const ocrDir =
|
|
35221
|
+
const ocrDir = join18(targetDir, ".ocr");
|
|
34654
35222
|
try {
|
|
34655
35223
|
const { id: sessionId } = await resolveActiveSession(ocrDir, options.sessionId);
|
|
34656
35224
|
await stateAdvance({
|
|
@@ -34670,7 +35238,7 @@ var completeRoundSubcommand = new Command("complete-round").description("Atomica
|
|
|
34670
35238
|
async (options) => {
|
|
34671
35239
|
const targetDir = process.cwd();
|
|
34672
35240
|
requireOcrSetup(targetDir);
|
|
34673
|
-
const ocrDir =
|
|
35241
|
+
const ocrDir = join18(targetDir, ".ocr");
|
|
34674
35242
|
try {
|
|
34675
35243
|
const base = options.stdin ? { source: "stdin", data: await readStdin() } : options.file ? { source: "file", filePath: options.file } : (() => {
|
|
34676
35244
|
throw new StateError(STATE_EXIT.USAGE, "Provide --stdin or --file with round metadata");
|
|
@@ -34694,7 +35262,7 @@ var completeMapSubcommand = new Command("complete-map").description("Atomically
|
|
|
34694
35262
|
async (options) => {
|
|
34695
35263
|
const targetDir = process.cwd();
|
|
34696
35264
|
requireOcrSetup(targetDir);
|
|
34697
|
-
const ocrDir =
|
|
35265
|
+
const ocrDir = join18(targetDir, ".ocr");
|
|
34698
35266
|
try {
|
|
34699
35267
|
const base = options.stdin ? { source: "stdin", data: await readStdin() } : options.file ? { source: "file", filePath: options.file } : (() => {
|
|
34700
35268
|
throw new StateError(STATE_EXIT.USAGE, "Provide --stdin or --file with map metadata");
|
|
@@ -34716,7 +35284,7 @@ var completeMapSubcommand = new Command("complete-map").description("Atomically
|
|
|
34716
35284
|
var finishSubcommand = new Command("finish").description("Close a workflow (refuses unless the current round/run is complete)").option("--session-id <id>", "Session ID (auto-detects active if omitted)").option("--abort", "Abandon the session \u2014 records a distinct, non-success terminal").action(async (options) => {
|
|
34717
35285
|
const targetDir = process.cwd();
|
|
34718
35286
|
requireOcrSetup(targetDir);
|
|
34719
|
-
const ocrDir =
|
|
35287
|
+
const ocrDir = join18(targetDir, ".ocr");
|
|
34720
35288
|
try {
|
|
34721
35289
|
const { id: sessionId } = await resolveActiveSession(ocrDir, options.sessionId);
|
|
34722
35290
|
await stateClose({ sessionId, ocrDir, abort: options.abort });
|
|
@@ -34728,7 +35296,7 @@ var finishSubcommand = new Command("finish").description("Close a workflow (refu
|
|
|
34728
35296
|
var statusSubcommand = new Command("status").description("Report whether a session is complete and, if not, what's missing").option("--session-id <id>", "Session ID (auto-detects active if omitted)").option("--json", "Output the result as JSON").action(async (options) => {
|
|
34729
35297
|
const targetDir = process.cwd();
|
|
34730
35298
|
requireOcrSetup(targetDir);
|
|
34731
|
-
const ocrDir =
|
|
35299
|
+
const ocrDir = join18(targetDir, ".ocr");
|
|
34732
35300
|
try {
|
|
34733
35301
|
const result = await stateStatus(ocrDir, options.sessionId);
|
|
34734
35302
|
if (options.json) {
|
|
@@ -34757,44 +35325,50 @@ var stateCommand = new Command("state").description("Manage OCR session state").
|
|
|
34757
35325
|
|
|
34758
35326
|
// src/commands/session.ts
|
|
34759
35327
|
import { randomUUID as randomUUID3 } from "node:crypto";
|
|
34760
|
-
import { join as
|
|
35328
|
+
import { join as join20 } from "node:path";
|
|
34761
35329
|
init_db();
|
|
34762
35330
|
|
|
34763
35331
|
// src/lib/runtime-config.ts
|
|
34764
|
-
import { existsSync as
|
|
34765
|
-
import { join as
|
|
35332
|
+
import { existsSync as existsSync17, readFileSync as readFileSync12 } from "node:fs";
|
|
35333
|
+
import { join as join19 } from "node:path";
|
|
34766
35334
|
var DEFAULT_AGENT_HEARTBEAT_SECONDS = 60;
|
|
34767
|
-
function
|
|
34768
|
-
const configPath =
|
|
34769
|
-
if (!
|
|
34770
|
-
return DEFAULT_AGENT_HEARTBEAT_SECONDS;
|
|
34771
|
-
}
|
|
35335
|
+
function readRuntimePositiveInt(ocrDir, key, defaultValue) {
|
|
35336
|
+
const configPath = join19(ocrDir, "config.yaml");
|
|
35337
|
+
if (!existsSync17(configPath)) return defaultValue;
|
|
34772
35338
|
let content;
|
|
34773
35339
|
try {
|
|
34774
35340
|
content = readFileSync12(configPath, "utf-8");
|
|
34775
35341
|
} catch {
|
|
34776
|
-
return
|
|
35342
|
+
return defaultValue;
|
|
34777
35343
|
}
|
|
34778
35344
|
const blockMatch = content.match(
|
|
34779
|
-
|
|
35345
|
+
new RegExp(
|
|
35346
|
+
String.raw`^runtime:\s*\n(?:\s+[^\n]*\n)*?\s+${key}:\s*([^\s#\n]+)`,
|
|
35347
|
+
"m"
|
|
35348
|
+
)
|
|
34780
35349
|
);
|
|
34781
35350
|
const inlineMatch = content.match(
|
|
34782
|
-
|
|
35351
|
+
new RegExp(String.raw`^runtime:\s*\{[^}]*\b${key}:\s*([^\s,}]+)`, "m")
|
|
34783
35352
|
);
|
|
34784
35353
|
const raw = blockMatch?.[1] ?? inlineMatch?.[1];
|
|
34785
|
-
if (!raw)
|
|
34786
|
-
return DEFAULT_AGENT_HEARTBEAT_SECONDS;
|
|
34787
|
-
}
|
|
35354
|
+
if (!raw) return defaultValue;
|
|
34788
35355
|
const parsed = Number(raw);
|
|
34789
35356
|
if (!Number.isFinite(parsed) || parsed <= 0 || !Number.isInteger(parsed)) {
|
|
34790
35357
|
process.stderr.write(
|
|
34791
|
-
`[ocr] runtime
|
|
35358
|
+
`[ocr] runtime.${key} is not a positive integer (got "${raw}"); falling back to ${defaultValue}.
|
|
34792
35359
|
`
|
|
34793
35360
|
);
|
|
34794
|
-
return
|
|
35361
|
+
return defaultValue;
|
|
34795
35362
|
}
|
|
34796
35363
|
return parsed;
|
|
34797
35364
|
}
|
|
35365
|
+
function getAgentHeartbeatSeconds(ocrDir) {
|
|
35366
|
+
return readRuntimePositiveInt(
|
|
35367
|
+
ocrDir,
|
|
35368
|
+
"agent_heartbeat_seconds",
|
|
35369
|
+
DEFAULT_AGENT_HEARTBEAT_SECONDS
|
|
35370
|
+
);
|
|
35371
|
+
}
|
|
34798
35372
|
|
|
34799
35373
|
// src/commands/session.ts
|
|
34800
35374
|
var TERMINAL_STATUSES = /* @__PURE__ */ new Set([
|
|
@@ -34810,7 +35384,7 @@ function fail(message) {
|
|
|
34810
35384
|
async function setup() {
|
|
34811
35385
|
const targetDir = process.cwd();
|
|
34812
35386
|
requireOcrSetup(targetDir);
|
|
34813
|
-
const ocrDir =
|
|
35387
|
+
const ocrDir = join20(targetDir, ".ocr");
|
|
34814
35388
|
return { ocrDir };
|
|
34815
35389
|
}
|
|
34816
35390
|
var startInstanceSubcommand = new Command("start-instance").description("Journal a new agent-CLI process spawned for the active review").option("--workflow <id>", "Workflow session id (auto-detects active if omitted)").option("--persona <name>", "Reviewer persona, e.g. 'principal'").option("--instance <number>", "Instance index within (workflow, persona)", parseInt).option("--name <name>", "Human-friendly name (default: '{persona}-{instance}')").requiredOption("--vendor <vendor>", "Underlying CLI vendor (e.g. 'claude', 'opencode')").option("--model <id>", "Resolved model id passed to the CLI's --model flag").option("--phase <phase>", "Workflow phase this instance is doing").option("--pid <pid>", "Process id of the spawned process", parseInt).option("--note <text>", "Free-form note to attach").action(
|
|
@@ -34945,6 +35519,7 @@ var listSubcommand = new Command("list").description("List agent sessions for a
|
|
|
34945
35519
|
var sessionCommand = new Command("session").description("Manage agent-CLI session lifecycle journal").addCommand(startInstanceSubcommand).addCommand(bindVendorIdSubcommand).addCommand(beatSubcommand).addCommand(endInstanceSubcommand).addCommand(listSubcommand);
|
|
34946
35520
|
|
|
34947
35521
|
// src/lib/models.ts
|
|
35522
|
+
init_src();
|
|
34948
35523
|
var BUNDLED_CLAUDE_MODELS = [
|
|
34949
35524
|
{ id: "claude-opus-4-7", displayName: "Claude Opus 4.7" },
|
|
34950
35525
|
{ id: "claude-sonnet-4-6", displayName: "Claude Sonnet 4.6" },
|
|
@@ -35065,8 +35640,8 @@ var modelsCommand = new Command("models").description("Inspect models available
|
|
|
35065
35640
|
|
|
35066
35641
|
// src/commands/team.ts
|
|
35067
35642
|
var import_yaml2 = __toESM(require_dist(), 1);
|
|
35068
|
-
import { existsSync as
|
|
35069
|
-
import { join as
|
|
35643
|
+
import { existsSync as existsSync18, readFileSync as readFileSync13, writeFileSync as writeFileSync8 } from "node:fs";
|
|
35644
|
+
import { join as join21 } from "node:path";
|
|
35070
35645
|
async function readStdin2() {
|
|
35071
35646
|
const chunks = [];
|
|
35072
35647
|
for await (const chunk of process.stdin) {
|
|
@@ -35125,7 +35700,7 @@ var resolveSubcommand = new Command("resolve").description("Resolve and print th
|
|
|
35125
35700
|
async (options) => {
|
|
35126
35701
|
const targetDir = process.cwd();
|
|
35127
35702
|
requireOcrSetup(targetDir);
|
|
35128
|
-
const ocrDir =
|
|
35703
|
+
const ocrDir = join21(targetDir, ".ocr");
|
|
35129
35704
|
try {
|
|
35130
35705
|
const { team } = loadTeamConfig(ocrDir);
|
|
35131
35706
|
let override;
|
|
@@ -35164,8 +35739,8 @@ var setSubcommand = new Command("set").description("Persist a new default_team c
|
|
|
35164
35739
|
}
|
|
35165
35740
|
const targetDir = process.cwd();
|
|
35166
35741
|
requireOcrSetup(targetDir);
|
|
35167
|
-
const ocrDir =
|
|
35168
|
-
const configPath =
|
|
35742
|
+
const ocrDir = join21(targetDir, ".ocr");
|
|
35743
|
+
const configPath = join21(ocrDir, "config.yaml");
|
|
35169
35744
|
try {
|
|
35170
35745
|
const raw = await readStdin2();
|
|
35171
35746
|
const team = parseSessionOverride(raw);
|
|
@@ -35175,12 +35750,12 @@ var setSubcommand = new Command("set").description("Persist a new default_team c
|
|
|
35175
35750
|
list.push(inst);
|
|
35176
35751
|
byPersona.set(inst.persona, list);
|
|
35177
35752
|
}
|
|
35178
|
-
const doc =
|
|
35753
|
+
const doc = existsSync18(configPath) ? (0, import_yaml2.parseDocument)(readFileSync13(configPath, "utf-8")) : new import_yaml2.Document({});
|
|
35179
35754
|
applyDefaultTeamSurgically(doc, byPersona);
|
|
35180
35755
|
const yamlOutput = doc.toString({ lineWidth: 0 });
|
|
35181
35756
|
writeFileSync8(configPath, yamlOutput, "utf-8");
|
|
35182
|
-
const reviewersDir =
|
|
35183
|
-
const metaPath =
|
|
35757
|
+
const reviewersDir = join21(ocrDir, "skills", "references", "reviewers");
|
|
35758
|
+
const metaPath = join21(ocrDir, "reviewers-meta.json");
|
|
35184
35759
|
let metaWritten = false;
|
|
35185
35760
|
try {
|
|
35186
35761
|
const meta = generateReviewersMeta(reviewersDir, configPath);
|
|
@@ -35276,7 +35851,7 @@ var teamCommand = new Command("team").description("Resolve and persist team comp
|
|
|
35276
35851
|
|
|
35277
35852
|
// src/commands/review.ts
|
|
35278
35853
|
import { spawn as spawn3 } from "node:child_process";
|
|
35279
|
-
import { join as
|
|
35854
|
+
import { join as join22 } from "node:path";
|
|
35280
35855
|
init_db();
|
|
35281
35856
|
|
|
35282
35857
|
// src/lib/vendor-resume.ts
|
|
@@ -35315,7 +35890,7 @@ var reviewCommand = new Command("review").description("Run or resume an OCR revi
|
|
|
35315
35890
|
}
|
|
35316
35891
|
const targetDir = process.cwd();
|
|
35317
35892
|
requireOcrSetup(targetDir);
|
|
35318
|
-
const ocrDir =
|
|
35893
|
+
const ocrDir = join22(targetDir, ".ocr");
|
|
35319
35894
|
const db = await ensureDatabase(ocrDir);
|
|
35320
35895
|
const session = getSession(db, options.resume);
|
|
35321
35896
|
if (!session) {
|
|
@@ -35357,23 +35932,23 @@ var reviewCommand = new Command("review").description("Run or resume an OCR revi
|
|
|
35357
35932
|
});
|
|
35358
35933
|
|
|
35359
35934
|
// src/commands/update.ts
|
|
35360
|
-
import { existsSync as
|
|
35361
|
-
import { join as
|
|
35935
|
+
import { existsSync as existsSync19 } from "node:fs";
|
|
35936
|
+
import { join as join23 } from "node:path";
|
|
35362
35937
|
function detectConfiguredTools(targetDir) {
|
|
35363
35938
|
return AI_TOOLS.filter((tool) => {
|
|
35364
35939
|
if (tool.commandStrategy === "subdirectory") {
|
|
35365
|
-
const ocrDir =
|
|
35366
|
-
return
|
|
35940
|
+
const ocrDir = join23(targetDir, tool.commandsDir, "ocr");
|
|
35941
|
+
return existsSync19(ocrDir);
|
|
35367
35942
|
} else {
|
|
35368
|
-
const reviewCmd =
|
|
35369
|
-
return
|
|
35943
|
+
const reviewCmd = join23(targetDir, tool.commandsDir, "ocr-review.md");
|
|
35944
|
+
return existsSync19(reviewCmd);
|
|
35370
35945
|
}
|
|
35371
35946
|
});
|
|
35372
35947
|
}
|
|
35373
35948
|
var updateCommand = new Command("update").description("Update OCR assets after package upgrade").option("--commands", "Update only commands/workflows").option(
|
|
35374
35949
|
"--skills",
|
|
35375
35950
|
"Update only skills (includes templates, references, assets)"
|
|
35376
|
-
).option("--inject", "Update only AGENTS.md
|
|
35951
|
+
).option("--inject", "Update only instruction-file injection (AGENTS.md + each tool's native file)").option("--dry-run", "Preview changes without modifying files").action(async (options) => {
|
|
35377
35952
|
const targetDir = process.cwd();
|
|
35378
35953
|
requireOcrSetup(targetDir);
|
|
35379
35954
|
console.log();
|
|
@@ -35440,7 +36015,7 @@ var updateCommand = new Command("update").description("Update OCR assets after p
|
|
|
35440
36015
|
const result = installForTool(tool, targetDir);
|
|
35441
36016
|
results.push(result);
|
|
35442
36017
|
}
|
|
35443
|
-
ensureGitignore(
|
|
36018
|
+
ensureGitignore(join23(targetDir, ".ocr"));
|
|
35444
36019
|
spinner.stop();
|
|
35445
36020
|
const successful = results.filter((r) => r.success);
|
|
35446
36021
|
const failed = results.filter((r) => !r.success);
|
|
@@ -35474,30 +36049,34 @@ var updateCommand = new Command("update").description("Update OCR assets after p
|
|
|
35474
36049
|
}
|
|
35475
36050
|
}
|
|
35476
36051
|
if (updateInject) {
|
|
36052
|
+
const planned = plannedInstructionFiles(toolsToUpdate);
|
|
35477
36053
|
if (options.dryRun) {
|
|
35478
36054
|
console.log(source_default.dim(" Would update:"));
|
|
35479
|
-
|
|
35480
|
-
|
|
36055
|
+
for (const path2 of planned) {
|
|
36056
|
+
const verb = existsSync19(join23(targetDir, path2)) ? "update" : "create";
|
|
36057
|
+
console.log(source_default.dim(` \u2022 ${path2} (${verb} OCR managed block)`));
|
|
35481
36058
|
}
|
|
35482
|
-
|
|
35483
|
-
|
|
36059
|
+
const staleDry = findStaleInstructionFiles(targetDir, planned);
|
|
36060
|
+
for (const warning of formatStaleWarnings(staleDry, "dry-run")) {
|
|
36061
|
+
console.log(source_default.dim(` \u2022 ${warning}`));
|
|
35484
36062
|
}
|
|
35485
36063
|
console.log();
|
|
35486
36064
|
} else {
|
|
35487
|
-
const spinner = ora("Updating
|
|
35488
|
-
const injectResults = injectIntoProjectFiles(targetDir);
|
|
36065
|
+
const spinner = ora("Updating instruction files...").start();
|
|
36066
|
+
const injectResults = injectIntoProjectFiles(targetDir, toolsToUpdate);
|
|
35489
36067
|
spinner.stop();
|
|
35490
|
-
if (injectResults.
|
|
36068
|
+
if (injectResults.written.length > 0) {
|
|
35491
36069
|
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`);
|
|
36070
|
+
for (const path2 of injectResults.written) {
|
|
36071
|
+
console.log(` ${source_default.green("\u2713")} ${path2}`);
|
|
35497
36072
|
}
|
|
35498
36073
|
} else {
|
|
35499
36074
|
console.log(source_default.dim(" No instruction files to update"));
|
|
35500
36075
|
}
|
|
36076
|
+
const stale = findStaleInstructionFiles(targetDir, injectResults.written);
|
|
36077
|
+
for (const warning of formatStaleWarnings(stale, "update")) {
|
|
36078
|
+
console.log(source_default.yellow(` \u26A0 ${warning}`));
|
|
36079
|
+
}
|
|
35501
36080
|
console.log();
|
|
35502
36081
|
}
|
|
35503
36082
|
}
|
|
@@ -35511,14 +36090,15 @@ var updateCommand = new Command("update").description("Update OCR assets after p
|
|
|
35511
36090
|
});
|
|
35512
36091
|
|
|
35513
36092
|
// src/commands/dashboard.ts
|
|
35514
|
-
import { existsSync as
|
|
35515
|
-
import { join as
|
|
36093
|
+
import { existsSync as existsSync20 } from "node:fs";
|
|
36094
|
+
import { join as join24, dirname as dirname9 } from "node:path";
|
|
35516
36095
|
import { fileURLToPath } from "node:url";
|
|
36096
|
+
init_src();
|
|
35517
36097
|
init_db();
|
|
35518
36098
|
var __filename = fileURLToPath(import.meta.url);
|
|
35519
|
-
var __dirname =
|
|
36099
|
+
var __dirname = dirname9(__filename);
|
|
35520
36100
|
function resolveServerPath() {
|
|
35521
|
-
return
|
|
36101
|
+
return join24(__dirname, "dashboard", "server.js");
|
|
35522
36102
|
}
|
|
35523
36103
|
var dashboardCommand = new Command("dashboard").description("Start the OCR dashboard web interface").option("-p, --port <port>", "Port to run the server on", "4173").option("--no-open", "Don't open the browser automatically").action(
|
|
35524
36104
|
async (options) => {
|
|
@@ -35529,7 +36109,7 @@ var dashboardCommand = new Command("dashboard").description("Start the OCR dashb
|
|
|
35529
36109
|
console.error(source_default.red(`Error: Invalid port "${options.port}". Must be 1-65535.`));
|
|
35530
36110
|
process.exit(1);
|
|
35531
36111
|
}
|
|
35532
|
-
const ocrDir =
|
|
36112
|
+
const ocrDir = join24(targetDir, ".ocr");
|
|
35533
36113
|
try {
|
|
35534
36114
|
await ensureDatabase(ocrDir);
|
|
35535
36115
|
closeAllDatabases();
|
|
@@ -35543,7 +36123,7 @@ var dashboardCommand = new Command("dashboard").description("Start the OCR dashb
|
|
|
35543
36123
|
process.exit(1);
|
|
35544
36124
|
}
|
|
35545
36125
|
const serverPath = resolveServerPath();
|
|
35546
|
-
if (!
|
|
36126
|
+
if (!existsSync20(serverPath)) {
|
|
35547
36127
|
console.error(source_default.red("Error: Dashboard server bundle not found."));
|
|
35548
36128
|
console.error(
|
|
35549
36129
|
source_default.dim(` Expected at: ${serverPath}`)
|
|
@@ -35577,8 +36157,8 @@ var dashboardCommand = new Command("dashboard").description("Start the OCR dashb
|
|
|
35577
36157
|
);
|
|
35578
36158
|
|
|
35579
36159
|
// src/commands/doctor.ts
|
|
35580
|
-
import { existsSync as
|
|
35581
|
-
import { join as
|
|
36160
|
+
import { existsSync as existsSync21 } from "node:fs";
|
|
36161
|
+
import { join as join25 } from "node:path";
|
|
35582
36162
|
init_db();
|
|
35583
36163
|
function printStorageEngine(probeWriteEnabled) {
|
|
35584
36164
|
console.log();
|
|
@@ -35635,10 +36215,10 @@ var doctorCommand = new Command("doctor").description("Check OCR installation an
|
|
|
35635
36215
|
console.log(source_default.bold(" OCR Installation"));
|
|
35636
36216
|
console.log();
|
|
35637
36217
|
const ocrStatus = checkOcrSetup(targetDir);
|
|
35638
|
-
const configPath =
|
|
35639
|
-
const dbPath =
|
|
35640
|
-
const hasConfig =
|
|
35641
|
-
const hasDb =
|
|
36218
|
+
const configPath = join25(targetDir, ".ocr", "config.yaml");
|
|
36219
|
+
const dbPath = join25(targetDir, ".ocr", "data", "ocr.db");
|
|
36220
|
+
const hasConfig = existsSync21(configPath);
|
|
36221
|
+
const hasDb = existsSync21(dbPath);
|
|
35642
36222
|
const ocrChecks = [
|
|
35643
36223
|
{ label: ".ocr/skills/", ok: ocrStatus.hasSkills },
|
|
35644
36224
|
{ label: ".ocr/sessions/", ok: ocrStatus.hasSessions },
|
|
@@ -35714,9 +36294,331 @@ var doctorCommand = new Command("doctor").description("Check OCR installation an
|
|
|
35714
36294
|
console.log();
|
|
35715
36295
|
});
|
|
35716
36296
|
|
|
36297
|
+
// src/commands/db.ts
|
|
36298
|
+
import { existsSync as existsSync22, readFileSync as readFileSync14 } from "node:fs";
|
|
36299
|
+
import { join as join26 } from "node:path";
|
|
36300
|
+
init_src();
|
|
36301
|
+
init_db();
|
|
36302
|
+
function fail4(message) {
|
|
36303
|
+
console.error(source_default.red(`Error: ${message}`));
|
|
36304
|
+
process.exit(1);
|
|
36305
|
+
}
|
|
36306
|
+
function resolveOcrDir() {
|
|
36307
|
+
const targetDir = process.cwd();
|
|
36308
|
+
requireOcrSetup(targetDir);
|
|
36309
|
+
return join26(targetDir, ".ocr");
|
|
36310
|
+
}
|
|
36311
|
+
function dbPathFor(ocrDir) {
|
|
36312
|
+
return join26(ocrDir, "data", "ocr.db");
|
|
36313
|
+
}
|
|
36314
|
+
function formatBytes(n) {
|
|
36315
|
+
if (n < 1024) return `${n} B`;
|
|
36316
|
+
const units = ["KB", "MB", "GB", "TB"];
|
|
36317
|
+
let v = n / 1024;
|
|
36318
|
+
let i = 0;
|
|
36319
|
+
while (v >= 1024 && i < units.length - 1) {
|
|
36320
|
+
v /= 1024;
|
|
36321
|
+
i++;
|
|
36322
|
+
}
|
|
36323
|
+
return `${v.toFixed(v >= 100 ? 0 : 1)} ${units[i]}`;
|
|
36324
|
+
}
|
|
36325
|
+
function liveDashboardPid(ocrDir) {
|
|
36326
|
+
const pidFile = join26(ocrDir, "data", "dashboard.pid");
|
|
36327
|
+
if (!existsSync22(pidFile)) return null;
|
|
36328
|
+
try {
|
|
36329
|
+
const pid = parseInt(readFileSync14(pidFile, "utf-8").trim(), 10);
|
|
36330
|
+
if (!Number.isNaN(pid) && isProcessAlive(pid)) return pid;
|
|
36331
|
+
} catch {
|
|
36332
|
+
}
|
|
36333
|
+
return null;
|
|
36334
|
+
}
|
|
36335
|
+
function guardExclusive(ocrDir, force, op) {
|
|
36336
|
+
const pid = liveDashboardPid(ocrDir);
|
|
36337
|
+
if (pid !== null && !force) {
|
|
36338
|
+
fail4(
|
|
36339
|
+
`a dashboard appears to be running (PID ${pid}); ${op} needs exclusive access to the database.
|
|
36340
|
+
Stop it first, or pass --force to proceed anyway.`
|
|
36341
|
+
);
|
|
36342
|
+
}
|
|
36343
|
+
}
|
|
36344
|
+
function printHealth(report) {
|
|
36345
|
+
console.log();
|
|
36346
|
+
console.log(source_default.bold(" Database Health"));
|
|
36347
|
+
console.log();
|
|
36348
|
+
console.log(` File: ${report.dbPath}`);
|
|
36349
|
+
console.log(` Size: ${formatBytes(report.fileSizeBytes)}`);
|
|
36350
|
+
if (report.reclaimableBytes > 0) {
|
|
36351
|
+
console.log(
|
|
36352
|
+
` Reclaimable: ${source_default.yellow(formatBytes(report.reclaimableBytes))} ` + source_default.dim(`(${report.freelistCount} free pages \u2014 run \`ocr db vacuum\`)`)
|
|
36353
|
+
);
|
|
36354
|
+
}
|
|
36355
|
+
console.log(
|
|
36356
|
+
` Records: ${report.sessionCount} session(s), ${report.eventCount} event(s)`
|
|
36357
|
+
);
|
|
36358
|
+
console.log();
|
|
36359
|
+
const ok = (s) => ` ${source_default.green("\u2713")} ${s}`;
|
|
36360
|
+
const bad = (s) => ` ${source_default.red("\u2717")} ${s}`;
|
|
36361
|
+
console.log(
|
|
36362
|
+
report.integrityOk ? ok("integrity_check: ok") : bad(`integrity_check: ${report.integrityErrors.length} error(s)`)
|
|
36363
|
+
);
|
|
36364
|
+
if (!report.integrityOk) {
|
|
36365
|
+
for (const e of report.integrityErrors.slice(0, 5)) {
|
|
36366
|
+
console.log(` ${source_default.dim(e)}`);
|
|
36367
|
+
}
|
|
36368
|
+
}
|
|
36369
|
+
const fkTotal = report.fkViolations.reduce((n, g) => n + g.count, 0) + report.protectedFkViolations.reduce((n, g) => n + g.count, 0);
|
|
36370
|
+
if (fkTotal === 0) {
|
|
36371
|
+
console.log(ok("foreign_key_check: 0 violations"));
|
|
36372
|
+
} else {
|
|
36373
|
+
console.log(bad(`foreign_key_check: ${fkTotal} violation(s)`));
|
|
36374
|
+
for (const g of report.fkViolations) {
|
|
36375
|
+
console.log(` ${source_default.dim(`${g.table}: ${g.count} orphan(s)`)}`);
|
|
36376
|
+
}
|
|
36377
|
+
for (const g of report.protectedFkViolations) {
|
|
36378
|
+
console.log(
|
|
36379
|
+
` ${source_default.yellow(`${g.table}: ${g.count} (protected \u2014 manual review)`)}`
|
|
36380
|
+
);
|
|
36381
|
+
}
|
|
36382
|
+
}
|
|
36383
|
+
if (report.markdownDuplicateRows === 0) {
|
|
36384
|
+
console.log(ok("markdown_artifacts: no duplicates"));
|
|
36385
|
+
} else {
|
|
36386
|
+
console.log(
|
|
36387
|
+
bad(`markdown_artifacts: ${report.markdownDuplicateRows} duplicate row(s)`)
|
|
36388
|
+
);
|
|
36389
|
+
}
|
|
36390
|
+
const reapable = report.orphanTempFiles.filter((f) => f.reapable);
|
|
36391
|
+
if (report.orphanTempFiles.length > 0) {
|
|
36392
|
+
console.log(
|
|
36393
|
+
` ${reapable.length > 0 ? source_default.yellow("\u26A0") : source_default.dim("\xB7")} orphan temp files: ${report.orphanTempFiles.length} (${reapable.length} reapable)`
|
|
36394
|
+
);
|
|
36395
|
+
}
|
|
36396
|
+
if (report.backupFiles.length > 0) {
|
|
36397
|
+
const total = report.backupFiles.reduce((n, b) => n + b.sizeBytes, 0);
|
|
36398
|
+
console.log(
|
|
36399
|
+
` ${source_default.dim("\xB7")} backups: ${report.backupFiles.length} (${formatBytes(total)})`
|
|
36400
|
+
);
|
|
36401
|
+
}
|
|
36402
|
+
console.log();
|
|
36403
|
+
}
|
|
36404
|
+
function needsFix(report) {
|
|
36405
|
+
return !report.integrityOk || report.fkViolations.length > 0 || report.markdownDuplicateRows > 0 || report.orphanTempFiles.some((f) => f.reapable) || report.reclaimableBytes > 0;
|
|
36406
|
+
}
|
|
36407
|
+
var doctorSubcommand = new Command("doctor").description("Report database health; --fix repairs orphans/dupes and VACUUMs").option("--fix", "apply repairs: FK-orphan sweep, dedup, temp reap, VACUUM").option("--no-snapshot", "skip the pre-fix snapshot (with --fix)").option("--force", "proceed even if a live dashboard owns the database").option("--json", "emit the health report as JSON (implies no --fix)").action(
|
|
36408
|
+
async (options) => {
|
|
36409
|
+
const ocrDir = resolveOcrDir();
|
|
36410
|
+
const dbPath = dbPathFor(ocrDir);
|
|
36411
|
+
const db = await ensureDatabase(ocrDir);
|
|
36412
|
+
if (options.json) {
|
|
36413
|
+
console.log(JSON.stringify(collectDbHealth(db, dbPath), null, 2));
|
|
36414
|
+
return;
|
|
36415
|
+
}
|
|
36416
|
+
const before = collectDbHealth(db, dbPath);
|
|
36417
|
+
printHealth(before);
|
|
36418
|
+
if (!options.fix) {
|
|
36419
|
+
if (needsFix(before)) {
|
|
36420
|
+
console.log(
|
|
36421
|
+
source_default.dim(" Run `ocr db doctor --fix` to repair the issues above.")
|
|
36422
|
+
);
|
|
36423
|
+
console.log();
|
|
36424
|
+
} else {
|
|
36425
|
+
console.log(source_default.green(" \u2713 Database is healthy"));
|
|
36426
|
+
console.log();
|
|
36427
|
+
}
|
|
36428
|
+
return;
|
|
36429
|
+
}
|
|
36430
|
+
guardExclusive(ocrDir, options.force ?? false, "doctor --fix");
|
|
36431
|
+
const result = fixDb(db, dbPath, { snapshot: options.snapshot !== false });
|
|
36432
|
+
console.log(source_default.bold(" Repairs applied"));
|
|
36433
|
+
console.log();
|
|
36434
|
+
if (result.snapshotPath) {
|
|
36435
|
+
console.log(` ${source_default.dim("snapshot:")} ${result.snapshotPath}`);
|
|
36436
|
+
}
|
|
36437
|
+
if (result.totalFkOrphansDeleted > 0) {
|
|
36438
|
+
console.log(
|
|
36439
|
+
` ${source_default.green("\u2713")} swept ${result.totalFkOrphansDeleted} FK-orphan row(s)`
|
|
36440
|
+
);
|
|
36441
|
+
for (const g of result.fkOrphansDeleted) {
|
|
36442
|
+
console.log(` ${source_default.dim(`${g.table}: ${g.count}`)}`);
|
|
36443
|
+
}
|
|
36444
|
+
}
|
|
36445
|
+
if (result.markdownDupsDeleted > 0) {
|
|
36446
|
+
console.log(
|
|
36447
|
+
` ${source_default.green("\u2713")} removed ${result.markdownDupsDeleted} duplicate markdown row(s)`
|
|
36448
|
+
);
|
|
36449
|
+
}
|
|
36450
|
+
if (result.tempsReaped.length > 0) {
|
|
36451
|
+
console.log(
|
|
36452
|
+
` ${source_default.green("\u2713")} reaped ${result.tempsReaped.length} orphan temp file(s)`
|
|
36453
|
+
);
|
|
36454
|
+
}
|
|
36455
|
+
if (result.vacuumed) {
|
|
36456
|
+
const saved = result.sizeBeforeBytes - result.sizeAfterBytes;
|
|
36457
|
+
console.log(
|
|
36458
|
+
` ${source_default.green("\u2713")} VACUUM: ${formatBytes(result.sizeBeforeBytes)} \u2192 ${formatBytes(result.sizeAfterBytes)} ` + source_default.dim(`(reclaimed ${formatBytes(Math.max(0, saved))})`)
|
|
36459
|
+
);
|
|
36460
|
+
}
|
|
36461
|
+
console.log();
|
|
36462
|
+
if (result.protectedViolationsRemaining.length > 0) {
|
|
36463
|
+
console.log(
|
|
36464
|
+
source_default.yellow(
|
|
36465
|
+
" \u26A0 Violations remain in protected (system-of-record) tables:"
|
|
36466
|
+
)
|
|
36467
|
+
);
|
|
36468
|
+
for (const g of result.protectedViolationsRemaining) {
|
|
36469
|
+
console.log(` ${source_default.yellow(`${g.table}: ${g.count}`)}`);
|
|
36470
|
+
}
|
|
36471
|
+
console.log();
|
|
36472
|
+
}
|
|
36473
|
+
if (result.integrityOkAfter && result.fkViolationsAfter === 0) {
|
|
36474
|
+
console.log(source_default.green(" \u2713 Database repaired and healthy"));
|
|
36475
|
+
} else {
|
|
36476
|
+
console.log(
|
|
36477
|
+
source_default.red(
|
|
36478
|
+
` \u2717 Post-fix check: integrity ${result.integrityOkAfter ? "ok" : "FAILED"}, ${result.fkViolationsAfter} FK violation(s) remaining`
|
|
36479
|
+
)
|
|
36480
|
+
);
|
|
36481
|
+
process.exitCode = 1;
|
|
36482
|
+
}
|
|
36483
|
+
console.log();
|
|
36484
|
+
}
|
|
36485
|
+
);
|
|
36486
|
+
var vacuumSubcommand = new Command("vacuum").description("Checkpoint the WAL and VACUUM the database (snapshot-first)").option("--no-snapshot", "skip the pre-vacuum snapshot").option("--force", "proceed even if a live dashboard owns the database").action(async (options) => {
|
|
36487
|
+
const ocrDir = resolveOcrDir();
|
|
36488
|
+
const dbPath = dbPathFor(ocrDir);
|
|
36489
|
+
guardExclusive(ocrDir, options.force ?? false, "vacuum");
|
|
36490
|
+
const db = await ensureDatabase(ocrDir);
|
|
36491
|
+
const result = vacuumDb(db, dbPath, { snapshot: options.snapshot !== false });
|
|
36492
|
+
console.log();
|
|
36493
|
+
if (result.snapshotPath) {
|
|
36494
|
+
console.log(` ${source_default.dim("snapshot:")} ${result.snapshotPath}`);
|
|
36495
|
+
}
|
|
36496
|
+
console.log(
|
|
36497
|
+
` ${source_default.green("\u2713")} VACUUM: ${formatBytes(result.sizeBeforeBytes)} \u2192 ${formatBytes(result.sizeAfterBytes)} ` + source_default.dim(`(reclaimed ${formatBytes(result.reclaimedBytes)})`)
|
|
36498
|
+
);
|
|
36499
|
+
console.log();
|
|
36500
|
+
});
|
|
36501
|
+
var pruneSubcommand = new Command("prune").description(
|
|
36502
|
+
"Drop derived artifacts of old CLOSED sessions (events + sessions kept)"
|
|
36503
|
+
).option(
|
|
36504
|
+
"--keep-sessions <n>",
|
|
36505
|
+
"protect the N most-recently-active closed sessions",
|
|
36506
|
+
(v) => parseInt(v, 10)
|
|
36507
|
+
).option(
|
|
36508
|
+
"--older-than <days>",
|
|
36509
|
+
"only prune closed sessions quiet for more than D days",
|
|
36510
|
+
(v) => parseInt(v, 10)
|
|
36511
|
+
).option("--dry-run", "show what would be pruned without deleting").option("--force", "proceed even if a live dashboard owns the database").action(
|
|
36512
|
+
async (options) => {
|
|
36513
|
+
const ocrDir = resolveOcrDir();
|
|
36514
|
+
const dbPath = dbPathFor(ocrDir);
|
|
36515
|
+
if (options.keepSessions === void 0 && options.olderThan === void 0) {
|
|
36516
|
+
fail4(
|
|
36517
|
+
"prune needs a bound: pass --older-than <days> and/or --keep-sessions <n>."
|
|
36518
|
+
);
|
|
36519
|
+
}
|
|
36520
|
+
if (!options.dryRun) {
|
|
36521
|
+
guardExclusive(ocrDir, options.force ?? false, "prune");
|
|
36522
|
+
}
|
|
36523
|
+
const db = await ensureDatabase(ocrDir);
|
|
36524
|
+
const result = pruneDb(db, dbPath, {
|
|
36525
|
+
keepSessions: options.keepSessions,
|
|
36526
|
+
olderThanDays: options.olderThan,
|
|
36527
|
+
dryRun: options.dryRun ?? false
|
|
36528
|
+
});
|
|
36529
|
+
console.log();
|
|
36530
|
+
if (result.prunedSessions.length === 0) {
|
|
36531
|
+
console.log(source_default.green(" \u2713 Nothing to prune"));
|
|
36532
|
+
console.log();
|
|
36533
|
+
return;
|
|
36534
|
+
}
|
|
36535
|
+
const verb = result.dryRun ? "Would prune" : "Pruned";
|
|
36536
|
+
console.log(
|
|
36537
|
+
source_default.bold(
|
|
36538
|
+
` ${verb} ${result.totalArtifactRows} artifact row(s) across ${result.prunedSessions.length} session(s)`
|
|
36539
|
+
)
|
|
36540
|
+
);
|
|
36541
|
+
console.log();
|
|
36542
|
+
for (const p of result.prunedSessions.slice(0, 20)) {
|
|
36543
|
+
console.log(
|
|
36544
|
+
` ${source_default.dim("\xB7")} ${p.sessionId} ${source_default.dim(`(${p.artifactRows} rows)`)}`
|
|
36545
|
+
);
|
|
36546
|
+
}
|
|
36547
|
+
if (result.prunedSessions.length > 20) {
|
|
36548
|
+
console.log(
|
|
36549
|
+
` ${source_default.dim(`\u2026 and ${result.prunedSessions.length - 20} more`)}`
|
|
36550
|
+
);
|
|
36551
|
+
}
|
|
36552
|
+
console.log();
|
|
36553
|
+
if (result.snapshotPath) {
|
|
36554
|
+
console.log(` ${source_default.dim("snapshot:")} ${result.snapshotPath}`);
|
|
36555
|
+
}
|
|
36556
|
+
console.log(
|
|
36557
|
+
source_default.dim(
|
|
36558
|
+
result.dryRun ? " Re-run without --dry-run to apply. Events + session rows are always kept." : " Events + session rows were kept; sessions remain fully auditable."
|
|
36559
|
+
)
|
|
36560
|
+
);
|
|
36561
|
+
console.log();
|
|
36562
|
+
}
|
|
36563
|
+
);
|
|
36564
|
+
function validatePruneBackupsOptions(options) {
|
|
36565
|
+
if (!Number.isInteger(options.keep) || options.keep < 0) {
|
|
36566
|
+
return `--keep must be a non-negative integer (got "${String(options.keep)}").`;
|
|
36567
|
+
}
|
|
36568
|
+
if (options.keep === 0 && !options.force && !options.dryRun) {
|
|
36569
|
+
return "--keep 0 removes every backup (including any just-written snapshot). Re-run with --dry-run to preview, or --force to confirm.";
|
|
36570
|
+
}
|
|
36571
|
+
return null;
|
|
36572
|
+
}
|
|
36573
|
+
var pruneBackupsSubcommand = new Command("prune-backups").description("Delete old ocr.db.bak.* snapshots, keeping the most recent few").option(
|
|
36574
|
+
"--keep <n>",
|
|
36575
|
+
"retain the N most-recent backups (default 1; 0 removes all, requires --force)",
|
|
36576
|
+
// Raw conversion only — `Number('oops')` is NaN and flows into
|
|
36577
|
+
// validatePruneBackupsOptions, the single validation home. (parseInt would
|
|
36578
|
+
// also silently accept "3abc" → 3; Number rejects it as NaN.)
|
|
36579
|
+
(v) => Number(v),
|
|
36580
|
+
1
|
|
36581
|
+
).option("--force", "permit --keep 0 (removing the last backup / safety net)").option("--dry-run", "show what would be deleted without deleting").action(async (options) => {
|
|
36582
|
+
const ocrDir = resolveOcrDir();
|
|
36583
|
+
const dataDir = join26(ocrDir, "data");
|
|
36584
|
+
const invalid = validatePruneBackupsOptions(options);
|
|
36585
|
+
if (invalid !== null) {
|
|
36586
|
+
fail4(invalid);
|
|
36587
|
+
}
|
|
36588
|
+
const result = pruneBackups(dataDir, dbPathFor(ocrDir), {
|
|
36589
|
+
keep: options.keep,
|
|
36590
|
+
dryRun: options.dryRun ?? false
|
|
36591
|
+
});
|
|
36592
|
+
console.log();
|
|
36593
|
+
if (result.deleted.length === 0) {
|
|
36594
|
+
console.log(source_default.green(" \u2713 No backups to remove"));
|
|
36595
|
+
console.log();
|
|
36596
|
+
return;
|
|
36597
|
+
}
|
|
36598
|
+
const verb = result.dryRun ? "Would delete" : "Deleted";
|
|
36599
|
+
console.log(
|
|
36600
|
+
source_default.bold(
|
|
36601
|
+
` ${verb} ${result.deleted.length} backup(s) \u2014 ${formatBytes(result.reclaimedBytes)}`
|
|
36602
|
+
)
|
|
36603
|
+
);
|
|
36604
|
+
console.log();
|
|
36605
|
+
for (const b of result.deleted) {
|
|
36606
|
+
console.log(` ${source_default.dim("\xB7")} ${b.name} ${source_default.dim(`(${formatBytes(b.sizeBytes)})`)}`);
|
|
36607
|
+
}
|
|
36608
|
+
if (result.kept.length > 0) {
|
|
36609
|
+
console.log();
|
|
36610
|
+
console.log(
|
|
36611
|
+
source_default.dim(` Kept ${result.kept.length} most-recent backup(s) as a safety net.`)
|
|
36612
|
+
);
|
|
36613
|
+
}
|
|
36614
|
+
console.log();
|
|
36615
|
+
});
|
|
36616
|
+
var dbCommand = new Command("db").description("Inspect and maintain the OCR SQLite database").addCommand(doctorSubcommand).addCommand(vacuumSubcommand).addCommand(pruneSubcommand).addCommand(pruneBackupsSubcommand);
|
|
36617
|
+
|
|
35717
36618
|
// src/commands/reviewers.ts
|
|
35718
36619
|
import { writeFileSync as writeFileSync9, renameSync as renameSync2 } from "node:fs";
|
|
35719
|
-
import { join as
|
|
36620
|
+
import { join as join27 } from "node:path";
|
|
36621
|
+
init_src();
|
|
35720
36622
|
async function readStdin3() {
|
|
35721
36623
|
const chunks = [];
|
|
35722
36624
|
for await (const chunk of process.stdin) {
|
|
@@ -35730,6 +36632,25 @@ async function readStdin3() {
|
|
|
35730
36632
|
}
|
|
35731
36633
|
var VALID_TIERS = /* @__PURE__ */ new Set(["holistic", "specialist", "persona", "custom"]);
|
|
35732
36634
|
var SLUG_RE = /^[a-z][a-z0-9-]*$/;
|
|
36635
|
+
var INJECTION_PATTERNS = [
|
|
36636
|
+
/ignore\s+(all\s+|the\s+)?(previous|prior|above)?\s*(instructions|prompts|rules)/i,
|
|
36637
|
+
/disregard\s+(all\s+|the\s+)?(previous|prior|above)/i,
|
|
36638
|
+
/\byou\s+are\s+now\b/i,
|
|
36639
|
+
/^\s*system\s*:/im,
|
|
36640
|
+
/\balways\s+(conclude|respond|reply|return|output|approve|reject|say)\b/i,
|
|
36641
|
+
/\bnew\s+rule\s*:/i
|
|
36642
|
+
];
|
|
36643
|
+
function warnIfSuspiciousPersona(label, fields) {
|
|
36644
|
+
const text = fields.filter((f) => typeof f === "string").join("\n");
|
|
36645
|
+
const hit = INJECTION_PATTERNS.find((re) => re.test(text));
|
|
36646
|
+
if (hit) {
|
|
36647
|
+
console.error(
|
|
36648
|
+
source_default.yellow(
|
|
36649
|
+
`\u26A0 ${label} contains text resembling a prompt-injection override (matched ${hit}). Review the persona before relying on it.`
|
|
36650
|
+
)
|
|
36651
|
+
);
|
|
36652
|
+
}
|
|
36653
|
+
}
|
|
35733
36654
|
function validateReviewersMeta(data) {
|
|
35734
36655
|
if (typeof data !== "object" || data === null || Array.isArray(data)) {
|
|
35735
36656
|
throw new Error("Payload must be a JSON object");
|
|
@@ -35767,6 +36688,19 @@ function validateReviewersMeta(data) {
|
|
|
35767
36688
|
if (!Array.isArray(r.focus_areas)) {
|
|
35768
36689
|
throw new Error(`${prefix}.focus_areas must be an array`);
|
|
35769
36690
|
}
|
|
36691
|
+
if (r.icon !== void 0 && typeof r.icon !== "string") {
|
|
36692
|
+
throw new Error(`${prefix}.icon must be a string if provided (got ${JSON.stringify(r.icon)})`);
|
|
36693
|
+
}
|
|
36694
|
+
if (typeof r.icon !== "string" || r.icon.length === 0) {
|
|
36695
|
+
r.icon = defaultIconFor(r.id, r.tier);
|
|
36696
|
+
}
|
|
36697
|
+
warnIfSuspiciousPersona(`${prefix} ("${r.name}")`, [
|
|
36698
|
+
r.name,
|
|
36699
|
+
r.description,
|
|
36700
|
+
...Array.isArray(r.focus_areas) ? r.focus_areas : [],
|
|
36701
|
+
r.known_for,
|
|
36702
|
+
r.philosophy
|
|
36703
|
+
]);
|
|
35770
36704
|
if (r.known_for !== void 0 && typeof r.known_for !== "string") {
|
|
35771
36705
|
throw new Error(`${prefix}.known_for must be a string if provided`);
|
|
35772
36706
|
}
|
|
@@ -35779,17 +36713,17 @@ function validateReviewersMeta(data) {
|
|
|
35779
36713
|
var syncSubcommand2 = new Command("sync").description("Sync reviewers-meta.json from reviewer markdown files or structured JSON").option("--stdin", "Read reviewers JSON from stdin (for AI-invoked sync)").action(async (options) => {
|
|
35780
36714
|
const targetDir = process.cwd();
|
|
35781
36715
|
requireOcrSetup(targetDir);
|
|
35782
|
-
const ocrDir =
|
|
36716
|
+
const ocrDir = join27(targetDir, ".ocr");
|
|
35783
36717
|
if (!options.stdin) {
|
|
35784
36718
|
try {
|
|
35785
|
-
const reviewersDir =
|
|
35786
|
-
const configPath =
|
|
36719
|
+
const reviewersDir = join27(ocrDir, "skills", "references", "reviewers");
|
|
36720
|
+
const configPath = join27(ocrDir, "config.yaml");
|
|
35787
36721
|
const meta = generateReviewersMeta(reviewersDir, configPath);
|
|
35788
36722
|
if (!meta || meta.reviewers.length === 0) {
|
|
35789
36723
|
console.error(source_default.yellow("No reviewer files found in .ocr/skills/references/reviewers/"));
|
|
35790
36724
|
process.exit(1);
|
|
35791
36725
|
}
|
|
35792
|
-
const metaPath =
|
|
36726
|
+
const metaPath = join27(ocrDir, "reviewers-meta.json");
|
|
35793
36727
|
const tmpPath = metaPath + ".tmp";
|
|
35794
36728
|
writeFileSync9(tmpPath, JSON.stringify(meta, null, 2) + "\n");
|
|
35795
36729
|
renameSync2(tmpPath, metaPath);
|
|
@@ -35819,7 +36753,7 @@ var syncSubcommand2 = new Command("sync").description("Sync reviewers-meta.json
|
|
|
35819
36753
|
throw new Error("Invalid JSON on stdin");
|
|
35820
36754
|
}
|
|
35821
36755
|
const meta = validateReviewersMeta(parsed);
|
|
35822
|
-
const metaPath =
|
|
36756
|
+
const metaPath = join27(ocrDir, "reviewers-meta.json");
|
|
35823
36757
|
const tmpPath = metaPath + ".tmp";
|
|
35824
36758
|
writeFileSync9(tmpPath, JSON.stringify(meta, null, 2) + "\n");
|
|
35825
36759
|
renameSync2(tmpPath, metaPath);
|
|
@@ -35845,26 +36779,74 @@ var syncSubcommand2 = new Command("sync").description("Sync reviewers-meta.json
|
|
|
35845
36779
|
});
|
|
35846
36780
|
var reviewersCommand = new Command("reviewers").description("Manage OCR reviewer metadata").addCommand(syncSubcommand2);
|
|
35847
36781
|
|
|
36782
|
+
// src/commands/host.ts
|
|
36783
|
+
function describeRow(id) {
|
|
36784
|
+
const tool = getToolById(id);
|
|
36785
|
+
const caps = getHostCapabilities(id);
|
|
36786
|
+
return {
|
|
36787
|
+
id,
|
|
36788
|
+
name: tool?.name ?? id,
|
|
36789
|
+
subagentSpawn: caps.subagentSpawn,
|
|
36790
|
+
perTaskModel: caps.perTaskModel,
|
|
36791
|
+
phase4: caps.subagentSpawn ? "parallel-subagents" : "sequential"
|
|
36792
|
+
};
|
|
36793
|
+
}
|
|
36794
|
+
var capabilitiesSubcommand = new Command("capabilities").description("Print host (AI CLI) Phase-4 capabilities").option("--tool <id>", "Show capabilities for a single tool id").option("--json", "Output JSON").action((options) => {
|
|
36795
|
+
if (options.tool) {
|
|
36796
|
+
const id = options.tool.trim().toLowerCase();
|
|
36797
|
+
if (!getToolIds().includes(id)) {
|
|
36798
|
+
console.error(
|
|
36799
|
+
source_default.red(
|
|
36800
|
+
`Error: unknown tool id "${options.tool}". Valid ids: ${getToolIds().join(", ")}`
|
|
36801
|
+
)
|
|
36802
|
+
);
|
|
36803
|
+
process.exit(1);
|
|
36804
|
+
}
|
|
36805
|
+
const row = describeRow(id);
|
|
36806
|
+
if (options.json) {
|
|
36807
|
+
console.log(JSON.stringify(row, null, 2));
|
|
36808
|
+
} else {
|
|
36809
|
+
printRows([row]);
|
|
36810
|
+
}
|
|
36811
|
+
return;
|
|
36812
|
+
}
|
|
36813
|
+
const rows = AI_TOOLS.map((t) => describeRow(t.id));
|
|
36814
|
+
if (options.json) {
|
|
36815
|
+
console.log(JSON.stringify(rows, null, 2));
|
|
36816
|
+
} else {
|
|
36817
|
+
printRows(rows);
|
|
36818
|
+
}
|
|
36819
|
+
});
|
|
36820
|
+
function printRows(rows) {
|
|
36821
|
+
const yn = (v) => v ? source_default.green("yes") : source_default.dim("no");
|
|
36822
|
+
for (const row of rows) {
|
|
36823
|
+
console.log(
|
|
36824
|
+
`${source_default.bold(row.name.padEnd(20))} subagentSpawn=${yn(row.subagentSpawn)} perTaskModel=${yn(row.perTaskModel)} \u2192 ${source_default.cyan(row.phase4)}`
|
|
36825
|
+
);
|
|
36826
|
+
}
|
|
36827
|
+
}
|
|
36828
|
+
var hostCommand = new Command("host").description("Inspect host (AI CLI) capabilities").addCommand(capabilitiesSubcommand);
|
|
36829
|
+
|
|
35848
36830
|
// src/lib/update-check.ts
|
|
35849
36831
|
import { homedir } from "node:os";
|
|
35850
|
-
import { join as
|
|
35851
|
-
import { readFileSync as
|
|
36832
|
+
import { join as join28 } from "node:path";
|
|
36833
|
+
import { readFileSync as readFileSync15, writeFileSync as writeFileSync10, mkdirSync as mkdirSync8 } from "node:fs";
|
|
35852
36834
|
var PACKAGE_NAME = "@open-code-review/cli";
|
|
35853
36835
|
var REGISTRY_URL = `https://registry.npmjs.org/${PACKAGE_NAME}/latest`;
|
|
35854
|
-
var CACHE_DIR2 =
|
|
35855
|
-
var CACHE_FILE =
|
|
36836
|
+
var CACHE_DIR2 = join28(homedir(), ".ocr");
|
|
36837
|
+
var CACHE_FILE = join28(CACHE_DIR2, "update-check.json");
|
|
35856
36838
|
var CHECK_INTERVAL_MS = 4 * 60 * 60 * 1e3;
|
|
35857
36839
|
var FETCH_TIMEOUT_MS = 3e3;
|
|
35858
36840
|
function readCache(cacheFile) {
|
|
35859
36841
|
try {
|
|
35860
|
-
return JSON.parse(
|
|
36842
|
+
return JSON.parse(readFileSync15(cacheFile, "utf-8"));
|
|
35861
36843
|
} catch {
|
|
35862
36844
|
return null;
|
|
35863
36845
|
}
|
|
35864
36846
|
}
|
|
35865
36847
|
function writeCache(cacheFile, cache) {
|
|
35866
36848
|
try {
|
|
35867
|
-
|
|
36849
|
+
mkdirSync8(join28(cacheFile, ".."), { recursive: true });
|
|
35868
36850
|
writeFileSync10(cacheFile, JSON.stringify(cache));
|
|
35869
36851
|
} catch {
|
|
35870
36852
|
}
|
|
@@ -35885,7 +36867,7 @@ async function checkForUpdate(currentVersion, options) {
|
|
|
35885
36867
|
if (process.env.CI || process.env.OCR_NO_UPDATE_CHECK) {
|
|
35886
36868
|
return null;
|
|
35887
36869
|
}
|
|
35888
|
-
const cacheFile =
|
|
36870
|
+
const cacheFile = join28(options?.cacheDir ?? CACHE_DIR2, "update-check.json");
|
|
35889
36871
|
try {
|
|
35890
36872
|
const cache = readCache(cacheFile);
|
|
35891
36873
|
if (cache && Date.now() - cache.lastCheck < CHECK_INTERVAL_MS) {
|
|
@@ -35944,7 +36926,9 @@ program2.addCommand(reviewCommand);
|
|
|
35944
36926
|
program2.addCommand(updateCommand);
|
|
35945
36927
|
program2.addCommand(dashboardCommand);
|
|
35946
36928
|
program2.addCommand(doctorCommand);
|
|
36929
|
+
program2.addCommand(dbCommand);
|
|
35947
36930
|
program2.addCommand(reviewersCommand);
|
|
36931
|
+
program2.addCommand(hostCommand);
|
|
35948
36932
|
await program2.parseAsync();
|
|
35949
36933
|
if (subcommand && HUMAN_COMMANDS.has(subcommand)) {
|
|
35950
36934
|
const drift = checkLocalArtifactVersion(process.cwd(), CLI_VERSION);
|