@open-code-review/cli 2.2.1 → 2.3.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.
Files changed (64) hide show
  1. package/README.md +9 -0
  2. package/dist/dashboard/client/assets/{_basePickBy-BAlGnwHG.js → _basePickBy-CyrHyeyN.js} +1 -1
  3. package/dist/dashboard/client/assets/{_baseUniq-CoauyOeL.js → _baseUniq-Bg7NJSGS.js} +1 -1
  4. package/dist/dashboard/client/assets/{arc-DtS0aHfP.js → arc-zDGAKMur.js} +1 -1
  5. package/dist/dashboard/client/assets/{architectureDiagram-VXUJARFQ-CnWmtRTh.js → architectureDiagram-VXUJARFQ-BxlGxm0Q.js} +1 -1
  6. package/dist/dashboard/client/assets/{blockDiagram-VD42YOAC-DgPp4oGV.js → blockDiagram-VD42YOAC-BskTNyX5.js} +1 -1
  7. package/dist/dashboard/client/assets/{c4Diagram-YG6GDRKO--LV4qQaE.js → c4Diagram-YG6GDRKO-Dr9QQ-dn.js} +1 -1
  8. package/dist/dashboard/client/assets/channel-BUnm_-UQ.js +1 -0
  9. package/dist/dashboard/client/assets/{chunk-4BX2VUAB-BRglpc7Z.js → chunk-4BX2VUAB-xq9xoCTv.js} +1 -1
  10. package/dist/dashboard/client/assets/{chunk-55IACEB6-Bgx06_CV.js → chunk-55IACEB6-DYdXYVh5.js} +1 -1
  11. package/dist/dashboard/client/assets/{chunk-B4BG7PRW-D6HN3Yiy.js → chunk-B4BG7PRW-BGAyFRFS.js} +1 -1
  12. package/dist/dashboard/client/assets/{chunk-DI55MBZ5-NH9EgN9T.js → chunk-DI55MBZ5-C5ul9stk.js} +1 -1
  13. package/dist/dashboard/client/assets/{chunk-FMBD7UC4-xriO6WNP.js → chunk-FMBD7UC4-BSaPo2xa.js} +1 -1
  14. package/dist/dashboard/client/assets/{chunk-QN33PNHL-CV1h6_Zl.js → chunk-QN33PNHL-CyzabUv0.js} +1 -1
  15. package/dist/dashboard/client/assets/{chunk-QZHKN3VN-CV4VzxNq.js → chunk-QZHKN3VN-CceRbxt_.js} +1 -1
  16. package/dist/dashboard/client/assets/{chunk-TZMSLE5B-isdklocW.js → chunk-TZMSLE5B-Bjg9IoOQ.js} +1 -1
  17. package/dist/dashboard/client/assets/classDiagram-2ON5EDUG-D_fkmNvU.js +1 -0
  18. package/dist/dashboard/client/assets/classDiagram-v2-WZHVMYZB-D_fkmNvU.js +1 -0
  19. package/dist/dashboard/client/assets/clone-DTyrNOLZ.js +1 -0
  20. package/dist/dashboard/client/assets/{cose-bilkent-S5V4N54A-CCzlFSJf.js → cose-bilkent-S5V4N54A-DEdXBrCt.js} +1 -1
  21. package/dist/dashboard/client/assets/{dagre-6UL2VRFP-DVN3PkjZ.js → dagre-6UL2VRFP-DRdIiP58.js} +1 -1
  22. package/dist/dashboard/client/assets/{diagram-PSM6KHXK-SzJVoSsb.js → diagram-PSM6KHXK-Bo7Q2VlK.js} +1 -1
  23. package/dist/dashboard/client/assets/{diagram-QEK2KX5R-CgGn7ts-.js → diagram-QEK2KX5R-2Fmc2o5x.js} +1 -1
  24. package/dist/dashboard/client/assets/{diagram-S2PKOQOG-Bz1ukSx8.js → diagram-S2PKOQOG-5WE8f0p7.js} +1 -1
  25. package/dist/dashboard/client/assets/{erDiagram-Q2GNP2WA-CpstUTMZ.js → erDiagram-Q2GNP2WA-DD-iXWd_.js} +1 -1
  26. package/dist/dashboard/client/assets/{flowDiagram-NV44I4VS-aYVydGhp.js → flowDiagram-NV44I4VS-CCWo8Ue9.js} +1 -1
  27. package/dist/dashboard/client/assets/{ganttDiagram-JELNMOA3-Cb2DUSRk.js → ganttDiagram-JELNMOA3-CNY4d5UK.js} +1 -1
  28. package/dist/dashboard/client/assets/{gitGraphDiagram-V2S2FVAM-BUOnwA2w.js → gitGraphDiagram-V2S2FVAM-Dq5SBEJJ.js} +1 -1
  29. package/dist/dashboard/client/assets/{graph-4X5ddhLp.js → graph-BTt9lokK.js} +1 -1
  30. package/dist/dashboard/client/assets/{index-CKWqYAfu.js → index-B0k81q2b.js} +138 -138
  31. package/dist/dashboard/client/assets/index-Czwdh6UA.css +1 -0
  32. package/dist/dashboard/client/assets/{infoDiagram-HS3SLOUP-BlMqcrwm.js → infoDiagram-HS3SLOUP-AnKZja-G.js} +1 -1
  33. package/dist/dashboard/client/assets/{journeyDiagram-XKPGCS4Q-DF2ew7ju.js → journeyDiagram-XKPGCS4Q-nC-_WjPN.js} +1 -1
  34. package/dist/dashboard/client/assets/{kanban-definition-3W4ZIXB7-BKQMx0-n.js → kanban-definition-3W4ZIXB7-BEY73sWU.js} +1 -1
  35. package/dist/dashboard/client/assets/{layout-DNcn2g9w.js → layout-D4DfNpzH.js} +1 -1
  36. package/dist/dashboard/client/assets/{linear-Bqy9gvqb.js → linear-ZpGvKjeP.js} +1 -1
  37. package/dist/dashboard/client/assets/{mermaid-renderer-dJ71wgld.js → mermaid-renderer-BCDxmS9g.js} +4 -4
  38. package/dist/dashboard/client/assets/{mindmap-definition-VGOIOE7T-BARc8sqJ.js → mindmap-definition-VGOIOE7T-MzAaKESA.js} +1 -1
  39. package/dist/dashboard/client/assets/{pieDiagram-ADFJNKIX-CULlNZTd.js → pieDiagram-ADFJNKIX-B_X1kySF.js} +1 -1
  40. package/dist/dashboard/client/assets/{quadrantDiagram-AYHSOK5B-BJEZPVe9.js → quadrantDiagram-AYHSOK5B-CMoIEMLN.js} +1 -1
  41. package/dist/dashboard/client/assets/{requirementDiagram-UZGBJVZJ-BhMsmUIs.js → requirementDiagram-UZGBJVZJ-v4CRsn1w.js} +1 -1
  42. package/dist/dashboard/client/assets/{sankeyDiagram-TZEHDZUN-BYbNgogG.js → sankeyDiagram-TZEHDZUN-CPcyN8Jj.js} +1 -1
  43. package/dist/dashboard/client/assets/{sequenceDiagram-WL72ISMW-MoM_NwWk.js → sequenceDiagram-WL72ISMW-CTg0Vx1H.js} +1 -1
  44. package/dist/dashboard/client/assets/{stateDiagram-FKZM4ZOC-ditrlbM3.js → stateDiagram-FKZM4ZOC-BMWBN6Nq.js} +1 -1
  45. package/dist/dashboard/client/assets/stateDiagram-v2-4FDKWEC3-C9Jk1xd0.js +1 -0
  46. package/dist/dashboard/client/assets/{timeline-definition-IT6M3QCI-DOAJyjuz.js → timeline-definition-IT6M3QCI-B8xFcSGb.js} +1 -1
  47. package/dist/dashboard/client/assets/{treemap-GDKQZRPO-BBJkjnJl.js → treemap-GDKQZRPO-HQQuGl9w.js} +1 -1
  48. package/dist/dashboard/client/assets/{xychartDiagram-PRI3JC2R-CPW4s5vm.js → xychartDiagram-PRI3JC2R-Drz0SW3I.js} +1 -1
  49. package/dist/dashboard/client/index.html +2 -2
  50. package/dist/dashboard/server.js +910 -461
  51. package/dist/index.js +1257 -321
  52. package/package.json +6 -39
  53. package/dist/dashboard/client/assets/channel-BU2129fl.js +0 -1
  54. package/dist/dashboard/client/assets/classDiagram-2ON5EDUG-CVftFGiR.js +0 -1
  55. package/dist/dashboard/client/assets/classDiagram-v2-WZHVMYZB-CVftFGiR.js +0 -1
  56. package/dist/dashboard/client/assets/clone-DC6LEEC5.js +0 -1
  57. package/dist/dashboard/client/assets/index-CzxeSSaQ.css +0 -1
  58. package/dist/dashboard/client/assets/stateDiagram-v2-4FDKWEC3-SqoG2LCn.js +0 -1
  59. package/dist/lib/db/index.js +0 -2177
  60. package/dist/lib/models.js +0 -160
  61. package/dist/lib/runtime-config.js +0 -55
  62. package/dist/lib/state/index.js +0 -2196
  63. package/dist/lib/team-config.js +0 -175
  64. package/dist/lib/vendor-resume.js +0 -31
package/dist/index.js CHANGED
@@ -39,7 +39,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
39
39
  mod
40
40
  ));
41
41
 
42
- // src/lib/runtime-checks.ts
42
+ // ../shared/persistence/src/runtime-checks.ts
43
43
  function isSupportedNode(version) {
44
44
  const [major = 0, minor = 0] = version.split(".").map((n) => Number.parseInt(n, 10) || 0);
45
45
  return major > NODE_FLOOR.major || major === NODE_FLOOR.major && minor >= NODE_FLOOR.minor;
@@ -57,7 +57,7 @@ function isSuppressibleSqliteWarning(warning) {
57
57
  }
58
58
  var NODE_FLOOR;
59
59
  var init_runtime_checks = __esm({
60
- "src/lib/runtime-checks.ts"() {
60
+ "../shared/persistence/src/runtime-checks.ts"() {
61
61
  "use strict";
62
62
  NODE_FLOOR = { major: 22, minor: 5 };
63
63
  }
@@ -16017,35 +16017,688 @@ 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
- }
16020
+ // ../../node_modules/.pnpm/isexe@2.0.0/node_modules/isexe/windows.js
16021
+ var require_windows = __commonJS({
16022
+ "../../node_modules/.pnpm/isexe@2.0.0/node_modules/isexe/windows.js"(exports, module) {
16023
+ "use strict";
16024
+ module.exports = isexe;
16025
+ isexe.sync = sync;
16026
+ var fs = __require("fs");
16027
+ function checkPathExt(path2, options) {
16028
+ var pathext = options.pathExt !== void 0 ? options.pathExt : process.env.PATHEXT;
16029
+ if (!pathext) {
16030
+ return true;
16031
+ }
16032
+ pathext = pathext.split(";");
16033
+ if (pathext.indexOf("") !== -1) {
16034
+ return true;
16035
+ }
16036
+ for (var i = 0; i < pathext.length; i++) {
16037
+ var p = pathext[i].toLowerCase();
16038
+ if (p && path2.substr(-p.length).toLowerCase() === p) {
16039
+ return true;
16040
+ }
16041
+ }
16042
+ return false;
16043
+ }
16044
+ function checkStat(stat4, path2, options) {
16045
+ if (!stat4.isSymbolicLink() && !stat4.isFile()) {
16046
+ return false;
16047
+ }
16048
+ return checkPathExt(path2, options);
16049
+ }
16050
+ function isexe(path2, options, cb) {
16051
+ fs.stat(path2, function(er, stat4) {
16052
+ cb(er, er ? false : checkStat(stat4, path2, options));
16053
+ });
16054
+ }
16055
+ function sync(path2, options) {
16056
+ return checkStat(fs.statSync(path2), path2, options);
16057
+ }
16058
+ }
16059
+ });
16060
+
16061
+ // ../../node_modules/.pnpm/isexe@2.0.0/node_modules/isexe/mode.js
16062
+ var require_mode = __commonJS({
16063
+ "../../node_modules/.pnpm/isexe@2.0.0/node_modules/isexe/mode.js"(exports, module) {
16064
+ "use strict";
16065
+ module.exports = isexe;
16066
+ isexe.sync = sync;
16067
+ var fs = __require("fs");
16068
+ function isexe(path2, options, cb) {
16069
+ fs.stat(path2, function(er, stat4) {
16070
+ cb(er, er ? false : checkStat(stat4, options));
16071
+ });
16072
+ }
16073
+ function sync(path2, options) {
16074
+ return checkStat(fs.statSync(path2), options);
16075
+ }
16076
+ function checkStat(stat4, options) {
16077
+ return stat4.isFile() && checkMode(stat4, options);
16078
+ }
16079
+ function checkMode(stat4, options) {
16080
+ var mod = stat4.mode;
16081
+ var uid = stat4.uid;
16082
+ var gid = stat4.gid;
16083
+ var myUid = options.uid !== void 0 ? options.uid : process.getuid && process.getuid();
16084
+ var myGid = options.gid !== void 0 ? options.gid : process.getgid && process.getgid();
16085
+ var u = parseInt("100", 8);
16086
+ var g = parseInt("010", 8);
16087
+ var o = parseInt("001", 8);
16088
+ var ug = u | g;
16089
+ var ret = mod & o || mod & g && gid === myGid || mod & u && uid === myUid || mod & ug && myUid === 0;
16090
+ return ret;
16091
+ }
16092
+ }
16093
+ });
16094
+
16095
+ // ../../node_modules/.pnpm/isexe@2.0.0/node_modules/isexe/index.js
16096
+ var require_isexe = __commonJS({
16097
+ "../../node_modules/.pnpm/isexe@2.0.0/node_modules/isexe/index.js"(exports, module) {
16098
+ "use strict";
16099
+ var fs = __require("fs");
16100
+ var core;
16101
+ if (process.platform === "win32" || global.TESTING_WINDOWS) {
16102
+ core = require_windows();
16103
+ } else {
16104
+ core = require_mode();
16105
+ }
16106
+ module.exports = isexe;
16107
+ isexe.sync = sync;
16108
+ function isexe(path2, options, cb) {
16109
+ if (typeof options === "function") {
16110
+ cb = options;
16111
+ options = {};
16112
+ }
16113
+ if (!cb) {
16114
+ if (typeof Promise !== "function") {
16115
+ throw new TypeError("callback not provided");
16116
+ }
16117
+ return new Promise(function(resolve3, reject) {
16118
+ isexe(path2, options || {}, function(er, is) {
16119
+ if (er) {
16120
+ reject(er);
16121
+ } else {
16122
+ resolve3(is);
16123
+ }
16124
+ });
16125
+ });
16126
+ }
16127
+ core(path2, options || {}, function(er, is) {
16128
+ if (er) {
16129
+ if (er.code === "EACCES" || options && options.ignoreErrors) {
16130
+ er = null;
16131
+ is = false;
16132
+ }
16133
+ }
16134
+ cb(er, is);
16135
+ });
16136
+ }
16137
+ function sync(path2, options) {
16138
+ try {
16139
+ return core.sync(path2, options || {});
16140
+ } catch (er) {
16141
+ if (options && options.ignoreErrors || er.code === "EACCES") {
16142
+ return false;
16143
+ } else {
16144
+ throw er;
16145
+ }
16146
+ }
16147
+ }
16148
+ }
16149
+ });
16150
+
16151
+ // ../../node_modules/.pnpm/which@2.0.2/node_modules/which/which.js
16152
+ var require_which = __commonJS({
16153
+ "../../node_modules/.pnpm/which@2.0.2/node_modules/which/which.js"(exports, module) {
16154
+ "use strict";
16155
+ var isWindows5 = process.platform === "win32" || process.env.OSTYPE === "cygwin" || process.env.OSTYPE === "msys";
16156
+ var path2 = __require("path");
16157
+ var COLON = isWindows5 ? ";" : ":";
16158
+ var isexe = require_isexe();
16159
+ var getNotFoundError = (cmd) => Object.assign(new Error(`not found: ${cmd}`), { code: "ENOENT" });
16160
+ var getPathInfo = (cmd, opt) => {
16161
+ const colon = opt.colon || COLON;
16162
+ const pathEnv = cmd.match(/\//) || isWindows5 && cmd.match(/\\/) ? [""] : [
16163
+ // windows always checks the cwd first
16164
+ ...isWindows5 ? [process.cwd()] : [],
16165
+ ...(opt.path || process.env.PATH || /* istanbul ignore next: very unusual */
16166
+ "").split(colon)
16167
+ ];
16168
+ const pathExtExe = isWindows5 ? opt.pathExt || process.env.PATHEXT || ".EXE;.CMD;.BAT;.COM" : "";
16169
+ const pathExt = isWindows5 ? pathExtExe.split(colon) : [""];
16170
+ if (isWindows5) {
16171
+ if (cmd.indexOf(".") !== -1 && pathExt[0] !== "")
16172
+ pathExt.unshift("");
16173
+ }
16174
+ return {
16175
+ pathEnv,
16176
+ pathExt,
16177
+ pathExtExe
16178
+ };
16179
+ };
16180
+ var which = (cmd, opt, cb) => {
16181
+ if (typeof opt === "function") {
16182
+ cb = opt;
16183
+ opt = {};
16184
+ }
16185
+ if (!opt)
16186
+ opt = {};
16187
+ const { pathEnv, pathExt, pathExtExe } = getPathInfo(cmd, opt);
16188
+ const found = [];
16189
+ const step = (i) => new Promise((resolve3, reject) => {
16190
+ if (i === pathEnv.length)
16191
+ return opt.all && found.length ? resolve3(found) : reject(getNotFoundError(cmd));
16192
+ const ppRaw = pathEnv[i];
16193
+ const pathPart = /^".*"$/.test(ppRaw) ? ppRaw.slice(1, -1) : ppRaw;
16194
+ const pCmd = path2.join(pathPart, cmd);
16195
+ const p = !pathPart && /^\.[\\\/]/.test(cmd) ? cmd.slice(0, 2) + pCmd : pCmd;
16196
+ resolve3(subStep(p, i, 0));
16197
+ });
16198
+ const subStep = (p, i, ii) => new Promise((resolve3, reject) => {
16199
+ if (ii === pathExt.length)
16200
+ return resolve3(step(i + 1));
16201
+ const ext = pathExt[ii];
16202
+ isexe(p + ext, { pathExt: pathExtExe }, (er, is) => {
16203
+ if (!er && is) {
16204
+ if (opt.all)
16205
+ found.push(p + ext);
16206
+ else
16207
+ return resolve3(p + ext);
16208
+ }
16209
+ return resolve3(subStep(p, i, ii + 1));
16210
+ });
16211
+ });
16212
+ return cb ? step(0).then((res) => cb(null, res), cb) : step(0);
16213
+ };
16214
+ var whichSync = (cmd, opt) => {
16215
+ opt = opt || {};
16216
+ const { pathEnv, pathExt, pathExtExe } = getPathInfo(cmd, opt);
16217
+ const found = [];
16218
+ for (let i = 0; i < pathEnv.length; i++) {
16219
+ const ppRaw = pathEnv[i];
16220
+ const pathPart = /^".*"$/.test(ppRaw) ? ppRaw.slice(1, -1) : ppRaw;
16221
+ const pCmd = path2.join(pathPart, cmd);
16222
+ const p = !pathPart && /^\.[\\\/]/.test(cmd) ? cmd.slice(0, 2) + pCmd : pCmd;
16223
+ for (let j = 0; j < pathExt.length; j++) {
16224
+ const cur = p + pathExt[j];
16225
+ try {
16226
+ const is = isexe.sync(cur, { pathExt: pathExtExe });
16227
+ if (is) {
16228
+ if (opt.all)
16229
+ found.push(cur);
16230
+ else
16231
+ return cur;
16232
+ }
16233
+ } catch (ex) {
16234
+ }
16235
+ }
16236
+ }
16237
+ if (opt.all && found.length)
16238
+ return found;
16239
+ if (opt.nothrow)
16240
+ return null;
16241
+ throw getNotFoundError(cmd);
16242
+ };
16243
+ module.exports = which;
16244
+ which.sync = whichSync;
16245
+ }
16246
+ });
16247
+
16248
+ // ../../node_modules/.pnpm/path-key@3.1.1/node_modules/path-key/index.js
16249
+ var require_path_key = __commonJS({
16250
+ "../../node_modules/.pnpm/path-key@3.1.1/node_modules/path-key/index.js"(exports, module) {
16251
+ "use strict";
16252
+ var pathKey = (options = {}) => {
16253
+ const environment = options.env || process.env;
16254
+ const platform2 = options.platform || process.platform;
16255
+ if (platform2 !== "win32") {
16256
+ return "PATH";
16257
+ }
16258
+ return Object.keys(environment).reverse().find((key) => key.toUpperCase() === "PATH") || "Path";
16259
+ };
16260
+ module.exports = pathKey;
16261
+ module.exports.default = pathKey;
16262
+ }
16263
+ });
16264
+
16265
+ // ../../node_modules/.pnpm/cross-spawn@7.0.6/node_modules/cross-spawn/lib/util/resolveCommand.js
16266
+ var require_resolveCommand = __commonJS({
16267
+ "../../node_modules/.pnpm/cross-spawn@7.0.6/node_modules/cross-spawn/lib/util/resolveCommand.js"(exports, module) {
16268
+ "use strict";
16269
+ var path2 = __require("path");
16270
+ var which = require_which();
16271
+ var getPathKey = require_path_key();
16272
+ function resolveCommandAttempt(parsed, withoutPathExt) {
16273
+ const env2 = parsed.options.env || process.env;
16274
+ const cwd = process.cwd();
16275
+ const hasCustomCwd = parsed.options.cwd != null;
16276
+ const shouldSwitchCwd = hasCustomCwd && process.chdir !== void 0 && !process.chdir.disabled;
16277
+ if (shouldSwitchCwd) {
16278
+ try {
16279
+ process.chdir(parsed.options.cwd);
16280
+ } catch (err) {
16281
+ }
16282
+ }
16283
+ let resolved;
16284
+ try {
16285
+ resolved = which.sync(parsed.command, {
16286
+ path: env2[getPathKey({ env: env2 })],
16287
+ pathExt: withoutPathExt ? path2.delimiter : void 0
16288
+ });
16289
+ } catch (e) {
16290
+ } finally {
16291
+ if (shouldSwitchCwd) {
16292
+ process.chdir(cwd);
16293
+ }
16294
+ }
16295
+ if (resolved) {
16296
+ resolved = path2.resolve(hasCustomCwd ? parsed.options.cwd : "", resolved);
16297
+ }
16298
+ return resolved;
16299
+ }
16300
+ function resolveCommand(parsed) {
16301
+ return resolveCommandAttempt(parsed) || resolveCommandAttempt(parsed, true);
16302
+ }
16303
+ module.exports = resolveCommand;
16304
+ }
16305
+ });
16306
+
16307
+ // ../../node_modules/.pnpm/cross-spawn@7.0.6/node_modules/cross-spawn/lib/util/escape.js
16308
+ var require_escape = __commonJS({
16309
+ "../../node_modules/.pnpm/cross-spawn@7.0.6/node_modules/cross-spawn/lib/util/escape.js"(exports, module) {
16310
+ "use strict";
16311
+ var metaCharsRegExp = /([()\][%!^"`<>&|;, *?])/g;
16312
+ function escapeCommand(arg) {
16313
+ arg = arg.replace(metaCharsRegExp, "^$1");
16314
+ return arg;
16315
+ }
16316
+ function escapeArgument(arg, doubleEscapeMetaChars) {
16317
+ arg = `${arg}`;
16318
+ arg = arg.replace(/(?=(\\+?)?)\1"/g, '$1$1\\"');
16319
+ arg = arg.replace(/(?=(\\+?)?)\1$/, "$1$1");
16320
+ arg = `"${arg}"`;
16321
+ arg = arg.replace(metaCharsRegExp, "^$1");
16322
+ if (doubleEscapeMetaChars) {
16323
+ arg = arg.replace(metaCharsRegExp, "^$1");
16324
+ }
16325
+ return arg;
16326
+ }
16327
+ module.exports.command = escapeCommand;
16328
+ module.exports.argument = escapeArgument;
16329
+ }
16330
+ });
16331
+
16332
+ // ../../node_modules/.pnpm/shebang-regex@3.0.0/node_modules/shebang-regex/index.js
16333
+ var require_shebang_regex = __commonJS({
16334
+ "../../node_modules/.pnpm/shebang-regex@3.0.0/node_modules/shebang-regex/index.js"(exports, module) {
16335
+ "use strict";
16336
+ module.exports = /^#!(.*)/;
16337
+ }
16338
+ });
16339
+
16340
+ // ../../node_modules/.pnpm/shebang-command@2.0.0/node_modules/shebang-command/index.js
16341
+ var require_shebang_command = __commonJS({
16342
+ "../../node_modules/.pnpm/shebang-command@2.0.0/node_modules/shebang-command/index.js"(exports, module) {
16343
+ "use strict";
16344
+ var shebangRegex = require_shebang_regex();
16345
+ module.exports = (string = "") => {
16346
+ const match = string.match(shebangRegex);
16347
+ if (!match) {
16348
+ return null;
16349
+ }
16350
+ const [path2, argument] = match[0].replace(/#! ?/, "").split(" ");
16351
+ const binary = path2.split("/").pop();
16352
+ if (binary === "env") {
16353
+ return argument;
16354
+ }
16355
+ return argument ? `${binary} ${argument}` : binary;
16356
+ };
16357
+ }
16358
+ });
16359
+
16360
+ // ../../node_modules/.pnpm/cross-spawn@7.0.6/node_modules/cross-spawn/lib/util/readShebang.js
16361
+ var require_readShebang = __commonJS({
16362
+ "../../node_modules/.pnpm/cross-spawn@7.0.6/node_modules/cross-spawn/lib/util/readShebang.js"(exports, module) {
16363
+ "use strict";
16364
+ var fs = __require("fs");
16365
+ var shebangCommand = require_shebang_command();
16366
+ function readShebang(command) {
16367
+ const size = 150;
16368
+ const buffer = Buffer.alloc(size);
16369
+ let fd;
16370
+ try {
16371
+ fd = fs.openSync(command, "r");
16372
+ fs.readSync(fd, buffer, 0, size, 0);
16373
+ fs.closeSync(fd);
16374
+ } catch (e) {
16375
+ }
16376
+ return shebangCommand(buffer.toString());
16377
+ }
16378
+ module.exports = readShebang;
16379
+ }
16380
+ });
16381
+
16382
+ // ../../node_modules/.pnpm/cross-spawn@7.0.6/node_modules/cross-spawn/lib/parse.js
16383
+ var require_parse = __commonJS({
16384
+ "../../node_modules/.pnpm/cross-spawn@7.0.6/node_modules/cross-spawn/lib/parse.js"(exports, module) {
16385
+ "use strict";
16386
+ var path2 = __require("path");
16387
+ var resolveCommand = require_resolveCommand();
16388
+ var escape = require_escape();
16389
+ var readShebang = require_readShebang();
16390
+ var isWin = process.platform === "win32";
16391
+ var isExecutableRegExp = /\.(?:com|exe)$/i;
16392
+ var isCmdShimRegExp = /node_modules[\\/].bin[\\/][^\\/]+\.cmd$/i;
16393
+ function detectShebang(parsed) {
16394
+ parsed.file = resolveCommand(parsed);
16395
+ const shebang = parsed.file && readShebang(parsed.file);
16396
+ if (shebang) {
16397
+ parsed.args.unshift(parsed.file);
16398
+ parsed.command = shebang;
16399
+ return resolveCommand(parsed);
16400
+ }
16401
+ return parsed.file;
16402
+ }
16403
+ function parseNonShell(parsed) {
16404
+ if (!isWin) {
16405
+ return parsed;
16406
+ }
16407
+ const commandFile = detectShebang(parsed);
16408
+ const needsShell = !isExecutableRegExp.test(commandFile);
16409
+ if (parsed.options.forceShell || needsShell) {
16410
+ const needsDoubleEscapeMetaChars = isCmdShimRegExp.test(commandFile);
16411
+ parsed.command = path2.normalize(parsed.command);
16412
+ parsed.command = escape.command(parsed.command);
16413
+ parsed.args = parsed.args.map((arg) => escape.argument(arg, needsDoubleEscapeMetaChars));
16414
+ const shellCommand = [parsed.command].concat(parsed.args).join(" ");
16415
+ parsed.args = ["/d", "/s", "/c", `"${shellCommand}"`];
16416
+ parsed.command = process.env.comspec || "cmd.exe";
16417
+ parsed.options.windowsVerbatimArguments = true;
16418
+ }
16419
+ return parsed;
16420
+ }
16421
+ function parse(command, args, options) {
16422
+ if (args && !Array.isArray(args)) {
16423
+ options = args;
16424
+ args = null;
16425
+ }
16426
+ args = args ? args.slice(0) : [];
16427
+ options = Object.assign({}, options);
16428
+ const parsed = {
16429
+ command,
16430
+ args,
16431
+ options,
16432
+ file: void 0,
16433
+ original: {
16434
+ command,
16435
+ args
16436
+ }
16437
+ };
16438
+ return options.shell ? parsed : parseNonShell(parsed);
16439
+ }
16440
+ module.exports = parse;
16441
+ }
16442
+ });
16443
+
16444
+ // ../../node_modules/.pnpm/cross-spawn@7.0.6/node_modules/cross-spawn/lib/enoent.js
16445
+ var require_enoent = __commonJS({
16446
+ "../../node_modules/.pnpm/cross-spawn@7.0.6/node_modules/cross-spawn/lib/enoent.js"(exports, module) {
16447
+ "use strict";
16448
+ var isWin = process.platform === "win32";
16449
+ function notFoundError(original, syscall) {
16450
+ return Object.assign(new Error(`${syscall} ${original.command} ENOENT`), {
16451
+ code: "ENOENT",
16452
+ errno: "ENOENT",
16453
+ syscall: `${syscall} ${original.command}`,
16454
+ path: original.command,
16455
+ spawnargs: original.args
16456
+ });
16457
+ }
16458
+ function hookChildProcess(cp, parsed) {
16459
+ if (!isWin) {
16460
+ return;
16461
+ }
16462
+ const originalEmit = cp.emit;
16463
+ cp.emit = function(name, arg1) {
16464
+ if (name === "exit") {
16465
+ const err = verifyENOENT(arg1, parsed);
16466
+ if (err) {
16467
+ return originalEmit.call(cp, "error", err);
16468
+ }
16469
+ }
16470
+ return originalEmit.apply(cp, arguments);
16471
+ };
16472
+ }
16473
+ function verifyENOENT(status, parsed) {
16474
+ if (isWin && status === 1 && !parsed.file) {
16475
+ return notFoundError(parsed.original, "spawn");
16476
+ }
16477
+ return null;
16478
+ }
16479
+ function verifyENOENTSync(status, parsed) {
16480
+ if (isWin && status === 1 && !parsed.file) {
16481
+ return notFoundError(parsed.original, "spawnSync");
16482
+ }
16483
+ return null;
16484
+ }
16485
+ module.exports = {
16486
+ hookChildProcess,
16487
+ verifyENOENT,
16488
+ verifyENOENTSync,
16489
+ notFoundError
16490
+ };
16491
+ }
16492
+ });
16493
+
16494
+ // ../../node_modules/.pnpm/cross-spawn@7.0.6/node_modules/cross-spawn/index.js
16495
+ var require_cross_spawn = __commonJS({
16496
+ "../../node_modules/.pnpm/cross-spawn@7.0.6/node_modules/cross-spawn/index.js"(exports, module) {
16497
+ "use strict";
16498
+ var cp = __require("child_process");
16499
+ var parse = require_parse();
16500
+ var enoent = require_enoent();
16501
+ function spawn2(command, args, options) {
16502
+ const parsed = parse(command, args, options);
16503
+ const spawned = cp.spawn(parsed.command, parsed.args, parsed.options);
16504
+ enoent.hookChildProcess(spawned, parsed);
16505
+ return spawned;
16506
+ }
16507
+ function spawnSync2(command, args, options) {
16508
+ const parsed = parse(command, args, options);
16509
+ const result = cp.spawnSync(parsed.command, parsed.args, parsed.options);
16510
+ result.error = result.error || enoent.verifyENOENTSync(result.status, parsed);
16511
+ return result;
16512
+ }
16513
+ module.exports = spawn2;
16514
+ module.exports.spawn = spawn2;
16515
+ module.exports.sync = spawnSync2;
16516
+ module.exports._parse = parse;
16517
+ module.exports._enoent = enoent;
16518
+ }
16519
+ });
16520
+
16521
+ // ../shared/platform/src/spawn.ts
16031
16522
  function execBinary(binary, args, opts) {
16032
- return execFileSync(binary, args, {
16033
- ...opts,
16034
- shell: isWindows
16523
+ const result = import_cross_spawn.default.sync(binary, args, {
16524
+ maxBuffer: DEFAULT_MAX_BUFFER,
16525
+ ...opts
16035
16526
  });
16527
+ if (result.error) throw result.error;
16528
+ if (result.status !== 0) {
16529
+ throw Object.assign(
16530
+ new Error(
16531
+ `Command failed: ${binary} ${args.join(" ")}
16532
+ ${String(result.stderr ?? "")}`
16533
+ ),
16534
+ {
16535
+ status: result.status,
16536
+ code: result.status,
16537
+ signal: result.signal,
16538
+ stdout: result.stdout,
16539
+ stderr: result.stderr,
16540
+ pid: result.pid
16541
+ }
16542
+ );
16543
+ }
16544
+ return result.stdout;
16036
16545
  }
16037
16546
  async function execBinaryAsync(binary, args, opts) {
16038
- return execFilePromise(binary, args, {
16547
+ return new Promise((resolvePromise, rejectPromise) => {
16548
+ const child = (0, import_cross_spawn.default)(binary, args, {
16549
+ cwd: opts.cwd,
16550
+ env: opts.env,
16551
+ windowsHide: true
16552
+ });
16553
+ const maxBuffer = opts.maxBuffer ?? DEFAULT_MAX_BUFFER;
16554
+ let stdout = "";
16555
+ let stderr = "";
16556
+ let killed = false;
16557
+ let settled = false;
16558
+ const settle = (fn) => {
16559
+ if (settled) return;
16560
+ settled = true;
16561
+ if (timer) clearTimeout(timer);
16562
+ fn();
16563
+ };
16564
+ const overflow = () => {
16565
+ killed = true;
16566
+ child.kill();
16567
+ };
16568
+ child.stdout?.setEncoding(opts.encoding);
16569
+ child.stderr?.setEncoding(opts.encoding);
16570
+ child.stdout?.on("data", (chunk) => {
16571
+ stdout += chunk;
16572
+ if (stdout.length > maxBuffer) overflow();
16573
+ });
16574
+ child.stderr?.on("data", (chunk) => {
16575
+ stderr += chunk;
16576
+ if (stderr.length > maxBuffer) overflow();
16577
+ });
16578
+ const timer = opts.timeout ? setTimeout(() => {
16579
+ killed = true;
16580
+ child.kill();
16581
+ }, opts.timeout) : void 0;
16582
+ child.on("error", (err) => {
16583
+ settle(
16584
+ () => rejectPromise(Object.assign(err, { stdout, stderr, killed }))
16585
+ );
16586
+ });
16587
+ child.on("close", (code, signal) => {
16588
+ settle(() => {
16589
+ if (code === 0 && !killed) {
16590
+ resolvePromise({ stdout, stderr });
16591
+ return;
16592
+ }
16593
+ rejectPromise(
16594
+ Object.assign(
16595
+ new Error(
16596
+ `Command failed: ${binary} ${args.join(" ")}
16597
+ ${stderr}`
16598
+ ),
16599
+ {
16600
+ code: code ?? void 0,
16601
+ signal,
16602
+ stdout,
16603
+ stderr,
16604
+ killed
16605
+ }
16606
+ )
16607
+ );
16608
+ });
16609
+ });
16610
+ });
16611
+ }
16612
+ function spawnBinary(binary, args, opts) {
16613
+ return (0, import_cross_spawn.default)(binary, args, {
16039
16614
  ...opts,
16040
- shell: isWindows
16615
+ windowsHide: true
16041
16616
  });
16042
16617
  }
16618
+ var import_cross_spawn, DEFAULT_MAX_BUFFER;
16619
+ var init_spawn = __esm({
16620
+ "../shared/platform/src/spawn.ts"() {
16621
+ "use strict";
16622
+ import_cross_spawn = __toESM(require_cross_spawn(), 1);
16623
+ DEFAULT_MAX_BUFFER = 1024 * 1024;
16624
+ }
16625
+ });
16626
+
16627
+ // ../shared/platform/src/verdict.ts
16628
+ function isCanonicalVerdict(v) {
16629
+ return VERDICT_SET.has(v);
16630
+ }
16631
+ var CANONICAL_VERDICTS, VERDICT_SET;
16632
+ var init_verdict = __esm({
16633
+ "../shared/platform/src/verdict.ts"() {
16634
+ "use strict";
16635
+ CANONICAL_VERDICTS = [
16636
+ "APPROVE",
16637
+ "REQUEST CHANGES",
16638
+ "NEEDS DISCUSSION"
16639
+ ];
16640
+ VERDICT_SET = new Set(CANONICAL_VERDICTS);
16641
+ }
16642
+ });
16643
+
16644
+ // ../shared/platform/src/counts.ts
16645
+ function deriveCounts(findings) {
16646
+ const counts = {
16647
+ blocker: 0,
16648
+ should_fix: 0,
16649
+ suggestion: 0,
16650
+ style: 0
16651
+ };
16652
+ for (const finding of findings) {
16653
+ const category = finding?.category;
16654
+ if (category === "blocker" || category === "should_fix" || category === "suggestion" || category === "style") {
16655
+ counts[category]++;
16656
+ }
16657
+ }
16658
+ return counts;
16659
+ }
16660
+ function collectFindings(meta) {
16661
+ const all = [];
16662
+ for (const reviewer of meta.reviewers ?? []) {
16663
+ for (const finding of reviewer?.findings ?? []) all.push(finding);
16664
+ }
16665
+ return all;
16666
+ }
16667
+ function preferred(scValue, derivedValue) {
16668
+ return typeof scValue === "number" && Number.isFinite(scValue) ? scValue : derivedValue;
16669
+ }
16670
+ function resolveRoundCounts(meta) {
16671
+ const allFindings = collectFindings(meta);
16672
+ const derived = deriveCounts(allFindings);
16673
+ const sc = meta.synthesis_counts ?? void 0;
16674
+ return {
16675
+ blockerCount: sc ? preferred(sc.blockers, derived.blocker) : derived.blocker,
16676
+ shouldFixCount: sc ? preferred(sc.should_fix, derived.should_fix) : derived.should_fix,
16677
+ suggestionCount: sc ? preferred(sc.suggestions, derived.suggestion) : derived.suggestion,
16678
+ reviewerCount: (meta.reviewers ?? []).length,
16679
+ totalFindingCount: allFindings.length
16680
+ };
16681
+ }
16682
+ var init_counts = __esm({
16683
+ "../shared/platform/src/counts.ts"() {
16684
+ "use strict";
16685
+ }
16686
+ });
16687
+
16688
+ // ../shared/platform/src/index.ts
16689
+ import { pathToFileURL } from "node:url";
16690
+ async function importModule(absolutePath) {
16691
+ return import(pathToFileURL(absolutePath).href);
16692
+ }
16693
+ function killErrorMeansDead(err) {
16694
+ return err instanceof Error && "code" in err && err.code === "ESRCH";
16695
+ }
16043
16696
  function isProcessAlive(pid) {
16044
16697
  try {
16045
16698
  process.kill(pid, 0);
16046
16699
  return true;
16047
16700
  } catch (err) {
16048
- return !(err instanceof Error && "code" in err && err.code === "ESRCH");
16701
+ return !killErrorMeansDead(err);
16049
16702
  }
16050
16703
  }
16051
16704
  function defaultIconFor(id, tier) {
@@ -16054,11 +16707,13 @@ function defaultIconFor(id, tier) {
16054
16707
  function hostCapabilitiesFor(vendor) {
16055
16708
  return vendor && HOST_CAPABILITIES[vendor] || DEFAULT_HOST_CAPABILITIES;
16056
16709
  }
16057
- var execFilePromise, isWindows, BUILTIN_ICON_MAP, DEFAULT_HOST_CAPABILITIES, HOST_CAPABILITIES;
16710
+ var isWindows, BUILTIN_ICON_MAP, DEFAULT_HOST_CAPABILITIES, HOST_CAPABILITIES;
16058
16711
  var init_src = __esm({
16059
16712
  "../shared/platform/src/index.ts"() {
16060
16713
  "use strict";
16061
- execFilePromise = promisify(execFile);
16714
+ init_spawn();
16715
+ init_verdict();
16716
+ init_counts();
16062
16717
  isWindows = process.platform === "win32";
16063
16718
  BUILTIN_ICON_MAP = {
16064
16719
  architect: "blocks",
@@ -23399,31 +24054,7 @@ var require_dist = __commonJS({
23399
24054
  }
23400
24055
  });
23401
24056
 
23402
- // src/lib/db/result-mapper.ts
23403
- function resultToRows(result) {
23404
- if (result.length === 0 || !result[0]) {
23405
- return [];
23406
- }
23407
- const { columns, values } = result[0];
23408
- return values.map((row) => {
23409
- const obj = {};
23410
- for (let i = 0; i < columns.length; i++) {
23411
- obj[columns[i]] = row[i];
23412
- }
23413
- return obj;
23414
- });
23415
- }
23416
- function resultToRow(result) {
23417
- const rows = resultToRows(result);
23418
- return rows[0];
23419
- }
23420
- var init_result_mapper = __esm({
23421
- "src/lib/db/result-mapper.ts"() {
23422
- "use strict";
23423
- }
23424
- });
23425
-
23426
- // src/lib/db/engine.ts
24057
+ // ../shared/persistence/src/db/engine.ts
23427
24058
  import { createRequire as createRequire2 } from "node:module";
23428
24059
  function applyEnginePreconditions() {
23429
24060
  if (_preconditionsApplied) return;
@@ -23477,7 +24108,7 @@ function openEngine(dbPath) {
23477
24108
  }
23478
24109
  var SQLITE_BUSY, SQLITE_BUSY_SNAPSHOT, BUSY_RETRY_ATTEMPTS, BUSY_RETRY_BACKOFF_MS, savepointName, nodeRequire, _preconditionsApplied, _DatabaseSyncCtor, SLEEP_BUF, NodeSqliteAdapter;
23479
24110
  var init_engine = __esm({
23480
- "src/lib/db/engine.ts"() {
24111
+ "../shared/persistence/src/db/engine.ts"() {
23481
24112
  "use strict";
23482
24113
  init_runtime_checks();
23483
24114
  SQLITE_BUSY = 5;
@@ -23604,7 +24235,7 @@ var init_engine = __esm({
23604
24235
  }
23605
24236
  });
23606
24237
 
23607
- // src/lib/db/migrations.ts
24238
+ // ../shared/persistence/src/db/migrations.ts
23608
24239
  function columnExists(db, table, column) {
23609
24240
  const result = db.exec(`PRAGMA table_info(${table})`);
23610
24241
  const first = result[0];
@@ -23659,7 +24290,7 @@ function runMigrations(db) {
23659
24290
  }
23660
24291
  var MIGRATIONS;
23661
24292
  var init_migrations = __esm({
23662
- "src/lib/db/migrations.ts"() {
24293
+ "../shared/persistence/src/db/migrations.ts"() {
23663
24294
  "use strict";
23664
24295
  MIGRATIONS = [
23665
24296
  {
@@ -24158,7 +24789,31 @@ var init_migrations = __esm({
24158
24789
  }
24159
24790
  });
24160
24791
 
24161
- // src/lib/db/queries.ts
24792
+ // ../shared/persistence/src/db/result-mapper.ts
24793
+ function resultToRows(result) {
24794
+ if (result.length === 0 || !result[0]) {
24795
+ return [];
24796
+ }
24797
+ const { columns, values } = result[0];
24798
+ return values.map((row) => {
24799
+ const obj = {};
24800
+ for (let i = 0; i < columns.length; i++) {
24801
+ obj[columns[i]] = row[i];
24802
+ }
24803
+ return obj;
24804
+ });
24805
+ }
24806
+ function resultToRow(result) {
24807
+ const rows = resultToRows(result);
24808
+ return rows[0];
24809
+ }
24810
+ var init_result_mapper = __esm({
24811
+ "../shared/persistence/src/db/result-mapper.ts"() {
24812
+ "use strict";
24813
+ }
24814
+ });
24815
+
24816
+ // ../shared/persistence/src/db/queries.ts
24162
24817
  function insertSession(db, params) {
24163
24818
  const {
24164
24819
  id,
@@ -24273,15 +24928,15 @@ function commitReasonClose(db, sessionId, reasonEvent, projectionUpdates) {
24273
24928
  });
24274
24929
  }
24275
24930
  var init_queries = __esm({
24276
- "src/lib/db/queries.ts"() {
24931
+ "../shared/persistence/src/db/queries.ts"() {
24277
24932
  "use strict";
24278
24933
  init_result_mapper();
24279
24934
  }
24280
24935
  });
24281
24936
 
24282
- // src/lib/db/reconcile.ts
24283
- import { existsSync as existsSync10 } from "node:fs";
24284
- import { isAbsolute as isAbsolute2, join as join12, dirname as dirname5 } from "node:path";
24937
+ // ../shared/persistence/src/db/reconcile.ts
24938
+ import { existsSync as existsSync7 } from "node:fs";
24939
+ import { isAbsolute as isAbsolute2, join as join9, dirname as dirname5 } from "node:path";
24285
24940
  function hasTerminalArtifactEvent(db, sessionId, workflowType, currentRound, currentMapRun) {
24286
24941
  const eventType = workflowType === "map" ? "map_completed" : "round_completed";
24287
24942
  const round = workflowType === "map" ? currentMapRun : currentRound;
@@ -24322,7 +24977,7 @@ function hasInFlightDependents(db, sessionId) {
24322
24977
  function resolveSessionDir(ocrDir, sessionDir) {
24323
24978
  if (!sessionDir) return null;
24324
24979
  if (isAbsolute2(sessionDir)) return sessionDir;
24325
- return join12(dirname5(ocrDir), sessionDir);
24980
+ return join9(dirname5(ocrDir), sessionDir);
24326
24981
  }
24327
24982
  function reconcileLegacyState(db, ocrDir, opts = {}) {
24328
24983
  const dryRun = opts.dryRun ?? false;
@@ -24334,8 +24989,8 @@ function reconcileLegacyState(db, ocrDir, opts = {}) {
24334
24989
  if (hasTerminalArtifactEvent(db, s.id, s.workflow_type, s.current_round, s.current_map_run) || hasReasonEvent(db, s.id)) {
24335
24990
  continue;
24336
24991
  }
24337
- const reviewFinal = s.workflow_type === "review" && dir ? existsSync10(join12(dir, "rounds", `round-${s.current_round}`, "final.md")) : false;
24338
- const mapFinal = s.workflow_type === "map" && dir ? existsSync10(join12(dir, "map", "runs", `run-${s.current_map_run}`, "map.md")) : false;
24992
+ const reviewFinal = s.workflow_type === "review" && dir ? existsSync7(join9(dir, "rounds", `round-${s.current_round}`, "final.md")) : false;
24993
+ const mapFinal = s.workflow_type === "map" && dir ? existsSync7(join9(dir, "map", "runs", `run-${s.current_map_run}`, "map.md")) : false;
24339
24994
  if (reviewFinal) {
24340
24995
  actions.push({
24341
24996
  sessionId: s.id,
@@ -24411,20 +25066,20 @@ function reconcileLegacyState(db, ocrDir, opts = {}) {
24411
25066
  }
24412
25067
  var DEFAULT_STALE_THRESHOLD_SECONDS;
24413
25068
  var init_reconcile = __esm({
24414
- "src/lib/db/reconcile.ts"() {
25069
+ "../shared/persistence/src/db/reconcile.ts"() {
24415
25070
  "use strict";
24416
25071
  init_queries();
24417
25072
  DEFAULT_STALE_THRESHOLD_SECONDS = 7 * 24 * 60 * 60;
24418
25073
  }
24419
25074
  });
24420
25075
 
24421
- // src/lib/db/liveness.ts
25076
+ // ../shared/persistence/src/db/liveness.ts
24422
25077
  function defaultIsAlive(pid) {
24423
25078
  try {
24424
25079
  process.kill(pid, 0);
24425
25080
  return true;
24426
25081
  } catch (err) {
24427
- return !(err instanceof Error && "code" in err && err.code === "ESRCH");
25082
+ return !killErrorMeansDead(err);
24428
25083
  }
24429
25084
  }
24430
25085
  function sqliteUtcMs(ts) {
@@ -24433,16 +25088,17 @@ function sqliteUtcMs(ts) {
24433
25088
  }
24434
25089
  var PID_REUSE_GUARD_MS;
24435
25090
  var init_liveness = __esm({
24436
- "src/lib/db/liveness.ts"() {
25091
+ "../shared/persistence/src/db/liveness.ts"() {
24437
25092
  "use strict";
25093
+ init_src();
24438
25094
  PID_REUSE_GUARD_MS = 24 * 60 * 60 * 1e3;
24439
25095
  }
24440
25096
  });
24441
25097
 
24442
- // src/lib/state/exit-codes.ts
25098
+ // ../shared/persistence/src/state/exit-codes.ts
24443
25099
  var STATE_EXIT, StateError, CANCELLED_EXIT_CODE, ORPHAN_EXIT_CODE, CASCADE_CLOSE_EXIT_CODE, WATCHDOG_DEADLINE_EXIT_CODE;
24444
25100
  var init_exit_codes = __esm({
24445
- "src/lib/state/exit-codes.ts"() {
25101
+ "../shared/persistence/src/state/exit-codes.ts"() {
24446
25102
  "use strict";
24447
25103
  STATE_EXIT = {
24448
25104
  OK: 0,
@@ -24469,7 +25125,7 @@ var init_exit_codes = __esm({
24469
25125
  }
24470
25126
  });
24471
25127
 
24472
- // src/lib/db/agent-sessions.ts
25128
+ // ../shared/persistence/src/db/agent-sessions.ts
24473
25129
  function cascadeTerminateExecutions(db, workflowId, exitCode, note) {
24474
25130
  db.run(
24475
25131
  `UPDATE command_executions
@@ -24640,6 +25296,9 @@ function bindVendorSessionIdOpportunistically(db, vendorSessionId) {
24640
25296
  );
24641
25297
  return candidate.uid ?? String(candidate.id);
24642
25298
  }
25299
+ function isSafeVendorSessionId(id) {
25300
+ return SAFE_VENDOR_SESSION_ID.test(id);
25301
+ }
24643
25302
  function recordVendorSessionIdForExecution(db, executionId, vendorSessionId) {
24644
25303
  db.run(
24645
25304
  `UPDATE command_executions
@@ -24835,9 +25494,9 @@ function sweepStaleSessions(db, thresholdSeconds) {
24835
25494
  }
24836
25495
  return { closedSessionIds: rows.map((r) => r.id) };
24837
25496
  }
24838
- var NOTE_ORPHAN_PREFIX, INSTANCE_COMMAND;
25497
+ var NOTE_ORPHAN_PREFIX, INSTANCE_COMMAND, SAFE_VENDOR_SESSION_ID;
24839
25498
  var init_agent_sessions = __esm({
24840
- "src/lib/db/agent-sessions.ts"() {
25499
+ "../shared/persistence/src/db/agent-sessions.ts"() {
24841
25500
  "use strict";
24842
25501
  init_result_mapper();
24843
25502
  init_queries();
@@ -24845,18 +25504,19 @@ var init_agent_sessions = __esm({
24845
25504
  init_exit_codes();
24846
25505
  NOTE_ORPHAN_PREFIX = "orphaned by liveness sweep";
24847
25506
  INSTANCE_COMMAND = "session-instance";
25507
+ SAFE_VENDOR_SESSION_ID = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,255}$/;
24848
25508
  }
24849
25509
  });
24850
25510
 
24851
- // src/lib/db/maintenance.ts
25511
+ // ../shared/persistence/src/db/maintenance.ts
24852
25512
  import {
24853
- existsSync as existsSync11,
24854
- readdirSync as readdirSync5,
25513
+ existsSync as existsSync8,
25514
+ readdirSync as readdirSync2,
24855
25515
  statSync,
24856
25516
  unlinkSync as unlinkSync3,
24857
25517
  copyFileSync
24858
25518
  } from "node:fs";
24859
- import { dirname as dirname6, join as join13, basename as basename7 } from "node:path";
25519
+ import { dirname as dirname6, join as join10, basename as basename3 } from "node:path";
24860
25520
  function withForeignKeysDisabled(db, fn) {
24861
25521
  db.pragma("foreign_keys = OFF");
24862
25522
  try {
@@ -24883,7 +25543,7 @@ function foreignKeyViolationGroups(db) {
24883
25543
  function scanOrphanTempFiles(dataDir) {
24884
25544
  let entries;
24885
25545
  try {
24886
- entries = readdirSync5(dataDir);
25546
+ entries = readdirSync2(dataDir);
24887
25547
  } catch {
24888
25548
  return [];
24889
25549
  }
@@ -24894,7 +25554,7 @@ function scanOrphanTempFiles(dataDir) {
24894
25554
  const pid = Number(m[1]);
24895
25555
  let ageMs = 0;
24896
25556
  try {
24897
- ageMs = Date.now() - statSync(join13(dataDir, name)).mtimeMs;
25557
+ ageMs = Date.now() - statSync(join10(dataDir, name)).mtimeMs;
24898
25558
  } catch {
24899
25559
  continue;
24900
25560
  }
@@ -24913,7 +25573,7 @@ function scanOrphanTempFiles(dataDir) {
24913
25573
  function scanBackupFiles(dataDir, dbBase) {
24914
25574
  let entries;
24915
25575
  try {
24916
- entries = readdirSync5(dataDir);
25576
+ entries = readdirSync2(dataDir);
24917
25577
  } catch {
24918
25578
  return [];
24919
25579
  }
@@ -24921,7 +25581,7 @@ function scanBackupFiles(dataDir, dbBase) {
24921
25581
  for (const name of entries) {
24922
25582
  if (!name.startsWith(`${dbBase}.bak`)) continue;
24923
25583
  try {
24924
- out.push({ name, sizeBytes: statSync(join13(dataDir, name)).size });
25584
+ out.push({ name, sizeBytes: statSync(join10(dataDir, name)).size });
24925
25585
  } catch {
24926
25586
  }
24927
25587
  }
@@ -24929,7 +25589,7 @@ function scanBackupFiles(dataDir, dbBase) {
24929
25589
  }
24930
25590
  function collectDbHealth(db, dbPath) {
24931
25591
  const dataDir = dirname6(dbPath);
24932
- const dbBase = basename7(dbPath);
25592
+ const dbBase = basename3(dbPath);
24933
25593
  const pageSize = scalarInt(db, "PRAGMA page_size");
24934
25594
  const pageCount = scalarInt(db, "PRAGMA page_count");
24935
25595
  const freelistCount = scalarInt(db, "PRAGMA freelist_count");
@@ -24941,7 +25601,7 @@ function collectDbHealth(db, dbPath) {
24941
25601
  const protectedFkViolations = allGroups.filter(
24942
25602
  (g) => PROTECTED_TABLES.has(g.table)
24943
25603
  );
24944
- const fileSizeBytes = existsSync11(dbPath) ? statSync(dbPath).size : 0;
25604
+ const fileSizeBytes = existsSync8(dbPath) ? statSync(dbPath).size : 0;
24945
25605
  return {
24946
25606
  dbPath,
24947
25607
  fileSizeBytes,
@@ -24969,7 +25629,7 @@ function collectDbHealth(db, dbPath) {
24969
25629
  }
24970
25630
  function snapshotDb(db, dbPath, label = "doctor") {
24971
25631
  try {
24972
- if (!existsSync11(dbPath) || statSync(dbPath).size === 0) return null;
25632
+ if (!existsSync8(dbPath) || statSync(dbPath).size === 0) return null;
24973
25633
  db.pragma("wal_checkpoint(TRUNCATE)");
24974
25634
  const ts = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
24975
25635
  const bakPath = `${dbPath}.bak.${label}.${ts}`;
@@ -24984,7 +25644,7 @@ function reapOrphanDbFiles(dataDir) {
24984
25644
  for (const f of scanOrphanTempFiles(dataDir)) {
24985
25645
  if (!f.reapable) continue;
24986
25646
  try {
24987
- unlinkSync3(join13(dataDir, f.name));
25647
+ unlinkSync3(join10(dataDir, f.name));
24988
25648
  reaped.push(f.name);
24989
25649
  } catch {
24990
25650
  }
@@ -24994,7 +25654,7 @@ function reapOrphanDbFiles(dataDir) {
24994
25654
  function reapStaleExecLogs(execLogsDir, maxAgeMs = SEVEN_DAYS_MS) {
24995
25655
  let entries;
24996
25656
  try {
24997
- entries = readdirSync5(execLogsDir);
25657
+ entries = readdirSync2(execLogsDir);
24998
25658
  } catch {
24999
25659
  return [];
25000
25660
  }
@@ -25002,7 +25662,7 @@ function reapStaleExecLogs(execLogsDir, maxAgeMs = SEVEN_DAYS_MS) {
25002
25662
  const reaped = [];
25003
25663
  for (const name of entries) {
25004
25664
  if (!name.endsWith(".log")) continue;
25005
- const full = join13(execLogsDir, name);
25665
+ const full = join10(execLogsDir, name);
25006
25666
  try {
25007
25667
  if (statSync(full).mtimeMs > cutoff) continue;
25008
25668
  unlinkSync3(full);
@@ -25020,11 +25680,11 @@ function pruneBackups(dataDir, dbPath, opts = {}) {
25020
25680
  );
25021
25681
  }
25022
25682
  const dryRun = opts.dryRun ?? false;
25023
- const dbBase = basename7(dbPath);
25683
+ const dbBase = basename3(dbPath);
25024
25684
  const withMtime = [];
25025
25685
  for (const file of scanBackupFiles(dataDir, dbBase)) {
25026
25686
  try {
25027
- withMtime.push({ file, mtimeMs: statSync(join13(dataDir, file.name)).mtimeMs });
25687
+ withMtime.push({ file, mtimeMs: statSync(join10(dataDir, file.name)).mtimeMs });
25028
25688
  } catch {
25029
25689
  }
25030
25690
  }
@@ -25035,7 +25695,7 @@ function pruneBackups(dataDir, dbPath, opts = {}) {
25035
25695
  if (!dryRun) {
25036
25696
  for (const b of toDelete) {
25037
25697
  try {
25038
- unlinkSync3(join13(dataDir, b.name));
25698
+ unlinkSync3(join10(dataDir, b.name));
25039
25699
  deleted.push(b);
25040
25700
  } catch {
25041
25701
  }
@@ -25051,7 +25711,7 @@ function pruneBackups(dataDir, dbPath, opts = {}) {
25051
25711
  }
25052
25712
  function fixDb(db, dbPath, opts = {}) {
25053
25713
  const dataDir = dirname6(dbPath);
25054
- const sizeBeforeBytes = existsSync11(dbPath) ? statSync(dbPath).size : 0;
25714
+ const sizeBeforeBytes = existsSync8(dbPath) ? statSync(dbPath).size : 0;
25055
25715
  const snapshotPath = opts.snapshot === false ? null : snapshotDb(db, dbPath, "doctor");
25056
25716
  const fkOrphansDeleted = [];
25057
25717
  withForeignKeysDisabled(db, () => {
@@ -25095,12 +25755,12 @@ function fixDb(db, dbPath, opts = {}) {
25095
25755
  };
25096
25756
  }
25097
25757
  function vacuumDb(db, dbPath, opts = {}) {
25098
- const sizeBeforeBytes = existsSync11(dbPath) ? statSync(dbPath).size : 0;
25758
+ const sizeBeforeBytes = existsSync8(dbPath) ? statSync(dbPath).size : 0;
25099
25759
  const snapshotPath = opts.snapshot === false ? null : snapshotDb(db, dbPath, "vacuum");
25100
25760
  db.pragma("wal_checkpoint(TRUNCATE)");
25101
25761
  db.run("VACUUM");
25102
25762
  db.pragma("wal_checkpoint(TRUNCATE)");
25103
- const sizeAfterBytes = existsSync11(dbPath) ? statSync(dbPath).size : 0;
25763
+ const sizeAfterBytes = existsSync8(dbPath) ? statSync(dbPath).size : 0;
25104
25764
  return {
25105
25765
  snapshotPath,
25106
25766
  sizeBeforeBytes,
@@ -25180,7 +25840,7 @@ function pruneDb(db, dbPath, opts = {}) {
25180
25840
  }
25181
25841
  var PROTECTED_TABLES, ORPHAN_SWEEPS, MARKDOWN_DEDUP_SQL, ONE_HOUR_MS, SEVEN_DAYS_MS;
25182
25842
  var init_maintenance = __esm({
25183
- "src/lib/db/maintenance.ts"() {
25843
+ "../shared/persistence/src/db/maintenance.ts"() {
25184
25844
  "use strict";
25185
25845
  init_src();
25186
25846
  PROTECTED_TABLES = /* @__PURE__ */ new Set([
@@ -25255,24 +25915,24 @@ var init_maintenance = __esm({
25255
25915
  }
25256
25916
  });
25257
25917
 
25258
- // src/lib/db/command-log.ts
25259
- import { appendFileSync, existsSync as existsSync12, mkdirSync as mkdirSync4, readFileSync as readFileSync9, renameSync, writeFileSync as writeFileSync6 } from "node:fs";
25260
- import { dirname as dirname7, join as join14 } from "node:path";
25918
+ // ../shared/persistence/src/db/command-log.ts
25919
+ import { appendFileSync, existsSync as existsSync9, mkdirSync as mkdirSync4, readFileSync as readFileSync7, renameSync, writeFileSync as writeFileSync6 } from "node:fs";
25920
+ import { dirname as dirname7, join as join11 } from "node:path";
25261
25921
  import { randomUUID as randomUUID2 } from "node:crypto";
25262
25922
  function generateCommandUid() {
25263
25923
  return randomUUID2();
25264
25924
  }
25265
25925
  function cacheDir(ocrDir) {
25266
- return join14(ocrDir, "data", CACHE_DIR);
25926
+ return join11(ocrDir, "data", CACHE_DIR);
25267
25927
  }
25268
25928
  function commandLogPath(ocrDir) {
25269
- return join14(cacheDir(ocrDir), FILENAME);
25929
+ return join11(cacheDir(ocrDir), FILENAME);
25270
25930
  }
25271
25931
  function appendCommandLog(ocrDir, entry) {
25272
25932
  try {
25273
25933
  const filePath = commandLogPath(ocrDir);
25274
25934
  const dir = dirname7(filePath);
25275
- if (!existsSync12(dir)) mkdirSync4(dir, { recursive: true });
25935
+ if (!existsSync9(dir)) mkdirSync4(dir, { recursive: true });
25276
25936
  const line = JSON.stringify(entry) + "\n";
25277
25937
  appendFileSync(filePath, line, { encoding: "utf-8" });
25278
25938
  if (approxLineCount >= 0) approxLineCount++;
@@ -25282,8 +25942,8 @@ function appendCommandLog(ocrDir, entry) {
25282
25942
  }
25283
25943
  function readCommandLog(ocrDir) {
25284
25944
  const filePath = commandLogPath(ocrDir);
25285
- if (!existsSync12(filePath)) return [];
25286
- const content = readFileSync9(filePath, "utf-8");
25945
+ if (!existsSync9(filePath)) return [];
25946
+ const content = readFileSync7(filePath, "utf-8");
25287
25947
  const entries = [];
25288
25948
  for (const line of content.split("\n")) {
25289
25949
  if (!line.trim()) continue;
@@ -25334,7 +25994,7 @@ function replayCommandLog(db, ocrDir) {
25334
25994
  function rotateIfNeeded(filePath) {
25335
25995
  try {
25336
25996
  if (approxLineCount >= 0 && approxLineCount <= MAX_LINES) return;
25337
- const content = readFileSync9(filePath, "utf-8");
25997
+ const content = readFileSync7(filePath, "utf-8");
25338
25998
  const lines = content.split("\n").filter((l) => l.trim());
25339
25999
  approxLineCount = lines.length;
25340
26000
  if (approxLineCount <= MAX_LINES) return;
@@ -25348,7 +26008,7 @@ function rotateIfNeeded(filePath) {
25348
26008
  }
25349
26009
  var CACHE_DIR, FILENAME, MAX_LINES, KEEP_LINES, approxLineCount;
25350
26010
  var init_command_log = __esm({
25351
- "src/lib/db/command-log.ts"() {
26011
+ "../shared/persistence/src/db/command-log.ts"() {
25352
26012
  "use strict";
25353
26013
  CACHE_DIR = ".cache";
25354
26014
  FILENAME = "command-history.jsonl";
@@ -25358,7 +26018,7 @@ var init_command_log = __esm({
25358
26018
  }
25359
26019
  });
25360
26020
 
25361
- // src/lib/db/index.ts
26021
+ // ../shared/persistence/src/db/index.ts
25362
26022
  var db_exports = {};
25363
26023
  __export(db_exports, {
25364
26024
  CANCELLED_EXIT_CODE: () => CANCELLED_EXIT_CODE,
@@ -25366,6 +26026,7 @@ __export(db_exports, {
25366
26026
  MIGRATIONS: () => MIGRATIONS,
25367
26027
  ORPHAN_EXIT_CODE: () => ORPHAN_EXIT_CODE,
25368
26028
  PID_REUSE_GUARD_MS: () => PID_REUSE_GUARD_MS,
26029
+ SAFE_VENDOR_SESSION_ID: () => SAFE_VENDOR_SESSION_ID,
25369
26030
  STATE_EXIT: () => STATE_EXIT,
25370
26031
  StateError: () => StateError,
25371
26032
  WATCHDOG_DEADLINE_EXIT_CODE: () => WATCHDOG_DEADLINE_EXIT_CODE,
@@ -25398,6 +26059,7 @@ __export(db_exports, {
25398
26059
  insertEvent: () => insertEvent,
25399
26060
  insertSession: () => insertSession,
25400
26061
  isBusyError: () => isBusyError,
26062
+ isSafeVendorSessionId: () => isSafeVendorSessionId,
25401
26063
  linkDashboardInvocationToWorkflow: () => linkDashboardInvocationToWorkflow,
25402
26064
  listAgentSessionsForWorkflow: () => listAgentSessionsForWorkflow,
25403
26065
  openDatabase: () => openDatabase,
@@ -25428,7 +26090,7 @@ __export(db_exports, {
25428
26090
  withForeignKeysDisabled: () => withForeignKeysDisabled
25429
26091
  });
25430
26092
  import {
25431
- existsSync as existsSync13,
26093
+ existsSync as existsSync10,
25432
26094
  mkdirSync as mkdirSync5,
25433
26095
  copyFileSync as copyFileSync2,
25434
26096
  statSync as statSync2,
@@ -25436,13 +26098,13 @@ import {
25436
26098
  rmSync
25437
26099
  } from "node:fs";
25438
26100
  import { tmpdir } from "node:os";
25439
- import { dirname as dirname8, join as join15 } from "node:path";
26101
+ import { dirname as dirname8, join as join12 } from "node:path";
25440
26102
  function maybeSnapshotBeforeUpgrade(db, dbPath, fromVersion) {
25441
26103
  if (fromVersion < 1 || fromVersion >= V2_SCHEMA_VERSION) return null;
25442
26104
  const bakPath = `${dbPath}.bak.v${fromVersion}`;
25443
- if (existsSync13(bakPath)) return bakPath;
26105
+ if (existsSync10(bakPath)) return bakPath;
25444
26106
  try {
25445
- if (!existsSync13(dbPath) || statSync2(dbPath).size === 0) return null;
26107
+ if (!existsSync10(dbPath) || statSync2(dbPath).size === 0) return null;
25446
26108
  db.pragma("wal_checkpoint(TRUNCATE)");
25447
26109
  copyFileSync2(dbPath, bakPath);
25448
26110
  return bakPath;
@@ -25478,7 +26140,7 @@ async function openDatabase(dbPath) {
25478
26140
  return cached;
25479
26141
  }
25480
26142
  const dir = dirname8(dbPath);
25481
- if (!existsSync13(dir)) {
26143
+ if (!existsSync10(dir)) {
25482
26144
  mkdirSync5(dir, { recursive: true });
25483
26145
  }
25484
26146
  const db = openEngine(dbPath);
@@ -25486,15 +26148,15 @@ async function openDatabase(dbPath) {
25486
26148
  return db;
25487
26149
  }
25488
26150
  async function getDb(ocrDir) {
25489
- const dbPath = join15(ocrDir, "data", "ocr.db");
26151
+ const dbPath = join12(ocrDir, "data", "ocr.db");
25490
26152
  return openDatabase(dbPath);
25491
26153
  }
25492
26154
  async function ensureDatabase(ocrDir) {
25493
- const dataDir = join15(ocrDir, "data");
25494
- if (!existsSync13(dataDir)) {
26155
+ const dataDir = join12(ocrDir, "data");
26156
+ if (!existsSync10(dataDir)) {
25495
26157
  mkdirSync5(dataDir, { recursive: true });
25496
26158
  }
25497
- const dbPath = join15(dataDir, "ocr.db");
26159
+ const dbPath = join12(dataDir, "ocr.db");
25498
26160
  const db = await openDatabase(dbPath);
25499
26161
  let before = 0;
25500
26162
  try {
@@ -25522,7 +26184,7 @@ async function ensureDatabase(ocrDir) {
25522
26184
  return db;
25523
26185
  }
25524
26186
  function walCheckpointTruncate(dbPath) {
25525
- if (!existsSync13(dbPath)) {
26187
+ if (!existsSync10(dbPath)) {
25526
26188
  return "skipped";
25527
26189
  }
25528
26190
  const cached = connections.get(dbPath);
@@ -25564,8 +26226,8 @@ function closeAllDatabases() {
25564
26226
  function probeWrite() {
25565
26227
  let dir;
25566
26228
  try {
25567
- dir = mkdtempSync(join15(tmpdir(), "ocr-probe-"));
25568
- const db = openEngine(join15(dir, "probe.db"));
26229
+ dir = mkdtempSync(join12(tmpdir(), "ocr-probe-"));
26230
+ const db = openEngine(join12(dir, "probe.db"));
25569
26231
  try {
25570
26232
  db.run("CREATE TABLE _probe_write (id INTEGER PRIMARY KEY, v TEXT)");
25571
26233
  db.transaction(() => {
@@ -25598,7 +26260,7 @@ function rmDirBestEffort(dir) {
25598
26260
  }
25599
26261
  var V2_SCHEMA_VERSION, connections;
25600
26262
  var init_db = __esm({
25601
- "src/lib/db/index.ts"() {
26263
+ "../shared/persistence/src/db/index.ts"() {
25602
26264
  "use strict";
25603
26265
  init_engine();
25604
26266
  init_migrations();
@@ -26367,7 +27029,7 @@ if (process.platform === "linux") {
26367
27029
  // ../../node_modules/.pnpm/signal-exit@4.1.0/node_modules/signal-exit/dist/mjs/index.js
26368
27030
  var processOk = (process13) => !!process13 && typeof process13 === "object" && typeof process13.removeListener === "function" && typeof process13.emit === "function" && typeof process13.reallyExit === "function" && typeof process13.listeners === "function" && typeof process13.kill === "function" && typeof process13.pid === "number" && typeof process13.on === "function";
26369
27031
  var kExitEmitter = Symbol.for("signal-exit emitter");
26370
- var global = globalThis;
27032
+ var global2 = globalThis;
26371
27033
  var ObjectDefineProperty = Object.defineProperty.bind(Object);
26372
27034
  var Emitter = class {
26373
27035
  emitted = {
@@ -26381,10 +27043,10 @@ var Emitter = class {
26381
27043
  count = 0;
26382
27044
  id = Math.random();
26383
27045
  constructor() {
26384
- if (global[kExitEmitter]) {
26385
- return global[kExitEmitter];
27046
+ if (global2[kExitEmitter]) {
27047
+ return global2[kExitEmitter];
26386
27048
  }
26387
- ObjectDefineProperty(global, kExitEmitter, {
27049
+ ObjectDefineProperty(global2, kExitEmitter, {
26388
27050
  value: this,
26389
27051
  writable: false,
26390
27052
  enumerable: false,
@@ -29358,7 +30020,7 @@ function ensureGitignore(ocrDir) {
29358
30020
  writeFileSync2(gitignorePath, content);
29359
30021
  }
29360
30022
 
29361
- // src/lib/team-config.ts
30023
+ // ../shared/config/src/team-config.ts
29362
30024
  var import_yaml = __toESM(require_dist(), 1);
29363
30025
  import { existsSync as existsSync2, readFileSync as readFileSync3 } from "node:fs";
29364
30026
  import { join as join2 } from "node:path";
@@ -29403,6 +30065,9 @@ function parseTeamConfigYaml(content) {
29403
30065
  aliases,
29404
30066
  defaultModel
29405
30067
  );
30068
+ if (resolvedModel !== null) {
30069
+ assertSafeModelId(resolvedModel, `default_team.${persona}[${i}]`);
30070
+ }
29406
30071
  team.push({
29407
30072
  persona,
29408
30073
  instance_index: i + 1,
@@ -29483,6 +30148,16 @@ function readOptionalString(obj, key, pathLabel) {
29483
30148
  }
29484
30149
  return value;
29485
30150
  }
30151
+ var SAFE_MODEL_ID = /^[A-Za-z0-9][A-Za-z0-9._/:@[\]+-]{0,255}$/;
30152
+ function assertSafeModelId(value, pathLabel) {
30153
+ if (SAFE_MODEL_ID.test(value)) return;
30154
+ const allowed = /[A-Za-z0-9._/:@[\]+-]/;
30155
+ const offending = [...value].find((ch) => !allowed.test(ch));
30156
+ const detail = value.length === 0 ? "empty string" : value.length > 256 ? "longer than 256 characters" : offending !== void 0 ? `contains ${JSON.stringify(offending)}` : `starts with ${JSON.stringify(value[0])}`;
30157
+ throw new Error(
30158
+ `${pathLabel}: model id ${detail} \u2014 no vendor model id uses that. Allowed: letters and digits plus . _ / : @ [ ] + - (max 256 chars).`
30159
+ );
30160
+ }
29486
30161
  function readAliases(root) {
29487
30162
  const models = root["models"];
29488
30163
  if (!models || typeof models !== "object" || Array.isArray(models)) return {};
@@ -29524,6 +30199,9 @@ function resolveTeamComposition(team, override) {
29524
30199
  result.push(inst);
29525
30200
  }
29526
30201
  for (const inst of override) {
30202
+ if (inst.model !== null) {
30203
+ assertSafeModelId(inst.model, `override ${inst.persona}#${inst.instance_index}`);
30204
+ }
29527
30205
  result.push(inst);
29528
30206
  }
29529
30207
  return result;
@@ -30092,7 +30770,7 @@ ${hint}
30092
30770
  }
30093
30771
 
30094
30772
  // src/lib/version.ts
30095
- var CLI_VERSION = true ? "2.2.1" : createRequire(import.meta.url)("../../package.json").version;
30773
+ var CLI_VERSION = true ? "2.3.0" : createRequire(import.meta.url)("../../package.json").version;
30096
30774
 
30097
30775
  // src/lib/deps.ts
30098
30776
  init_src();
@@ -33026,12 +33704,12 @@ function getStrategy(workflowType) {
33026
33704
  }
33027
33705
 
33028
33706
  // src/lib/progress/detector.ts
33029
- import { existsSync as existsSync7, readdirSync as readdirSync2 } from "node:fs";
33030
- import { join as join9, basename as basename4 } from "node:path";
33707
+ import { existsSync as existsSync11, readdirSync as readdirSync3 } from "node:fs";
33708
+ import { join as join13, basename as basename5 } from "node:path";
33031
33709
 
33032
33710
  // src/lib/progress/session-reader.ts
33033
- init_result_mapper();
33034
- import { basename as basename3 } from "node:path";
33711
+ init_db();
33712
+ import { basename as basename4 } from "node:path";
33035
33713
  var cachedDb = null;
33036
33714
  function setProgressDb(db) {
33037
33715
  cachedDb = db;
@@ -33050,7 +33728,7 @@ function readSessionState(sessionPath) {
33050
33728
  }
33051
33729
  }
33052
33730
  function readFromSqlite(sessionPath, db) {
33053
- const sessionId = basename3(sessionPath);
33731
+ const sessionId = basename4(sessionPath);
33054
33732
  let row = resultToRow(
33055
33733
  db.exec("SELECT * FROM sessions WHERE id = ?", [sessionId])
33056
33734
  );
@@ -33085,7 +33763,7 @@ function detectWorkflowType(sessionPath, explicitType) {
33085
33763
  const db = getProgressDb();
33086
33764
  if (db) {
33087
33765
  try {
33088
- const sessionId = basename4(sessionPath);
33766
+ const sessionId = basename5(sessionPath);
33089
33767
  const result = db.exec(
33090
33768
  "SELECT workflow_type FROM sessions WHERE id = ?",
33091
33769
  [sessionId]
@@ -33098,8 +33776,8 @@ function detectWorkflowType(sessionPath, explicitType) {
33098
33776
  } catch {
33099
33777
  }
33100
33778
  }
33101
- const hasMapDir = existsSync7(join9(sessionPath, "map"));
33102
- const hasRoundsDir = existsSync7(join9(sessionPath, "rounds"));
33779
+ const hasMapDir = existsSync11(join13(sessionPath, "map"));
33780
+ const hasRoundsDir = existsSync11(join13(sessionPath, "rounds"));
33103
33781
  if (hasMapDir && !hasRoundsDir) {
33104
33782
  return "map";
33105
33783
  }
@@ -33109,7 +33787,7 @@ function detectWorkflowType(sessionPath, explicitType) {
33109
33787
  if (hasMapDir && hasRoundsDir) {
33110
33788
  if (db) {
33111
33789
  try {
33112
- const sessionId = basename4(sessionPath);
33790
+ const sessionId = basename5(sessionPath);
33113
33791
  const result = db.exec(
33114
33792
  "SELECT current_phase FROM sessions WHERE id = ?",
33115
33793
  [sessionId]
@@ -33133,7 +33811,7 @@ function isSessionActive(sessionPath) {
33133
33811
  const db = getProgressDb();
33134
33812
  if (db) {
33135
33813
  try {
33136
- const sessionId = basename4(sessionPath);
33814
+ const sessionId = basename5(sessionPath);
33137
33815
  const result = db.exec(
33138
33816
  "SELECT status, current_phase FROM sessions WHERE id = ?",
33139
33817
  [sessionId]
@@ -33155,28 +33833,28 @@ function isSessionActive(sessionPath) {
33155
33833
  }
33156
33834
  function detectActiveWorkflows(sessionPath) {
33157
33835
  const activeWorkflows = [];
33158
- const hasRoundsDir = existsSync7(join9(sessionPath, "rounds"));
33836
+ const hasRoundsDir = existsSync11(join13(sessionPath, "rounds"));
33159
33837
  if (hasRoundsDir) {
33160
- const roundsDir = join9(sessionPath, "rounds");
33161
- const rounds = existsSync7(roundsDir) ? readdirSync2(roundsDir).filter((d) => d.match(/^round-\d+$/)).sort() : [];
33838
+ const roundsDir = join13(sessionPath, "rounds");
33839
+ const rounds = existsSync11(roundsDir) ? readdirSync3(roundsDir).filter((d) => d.match(/^round-\d+$/)).sort() : [];
33162
33840
  if (rounds.length > 0) {
33163
33841
  const latestRound = rounds[rounds.length - 1];
33164
- const finalPath = join9(roundsDir, latestRound, "final.md");
33165
- if (!existsSync7(finalPath)) {
33842
+ const finalPath = join13(roundsDir, latestRound, "final.md");
33843
+ if (!existsSync11(finalPath)) {
33166
33844
  activeWorkflows.push("review");
33167
33845
  }
33168
33846
  } else {
33169
33847
  activeWorkflows.push("review");
33170
33848
  }
33171
33849
  }
33172
- const hasMapDir = existsSync7(join9(sessionPath, "map"));
33850
+ const hasMapDir = existsSync11(join13(sessionPath, "map"));
33173
33851
  if (hasMapDir) {
33174
- const runsDir = join9(sessionPath, "map", "runs");
33175
- const runs = existsSync7(runsDir) ? readdirSync2(runsDir).filter((d) => d.match(/^run-\d+$/)).sort() : [];
33852
+ const runsDir = join13(sessionPath, "map", "runs");
33853
+ const runs = existsSync11(runsDir) ? readdirSync3(runsDir).filter((d) => d.match(/^run-\d+$/)).sort() : [];
33176
33854
  if (runs.length > 0) {
33177
33855
  const latestRun = runs[runs.length - 1];
33178
- const mapPath = join9(runsDir, latestRun, "map.md");
33179
- if (!existsSync7(mapPath)) {
33856
+ const mapPath = join13(runsDir, latestRun, "map.md");
33857
+ if (!existsSync11(mapPath)) {
33180
33858
  activeWorkflows.push("map");
33181
33859
  }
33182
33860
  } else {
@@ -33187,7 +33865,7 @@ function detectActiveWorkflows(sessionPath) {
33187
33865
  const db = getProgressDb();
33188
33866
  if (db) {
33189
33867
  try {
33190
- const sessionId = basename4(sessionPath);
33868
+ const sessionId = basename5(sessionPath);
33191
33869
  const result = db.exec(
33192
33870
  "SELECT workflow_type, current_phase FROM sessions WHERE id = ?",
33193
33871
  [sessionId]
@@ -33255,8 +33933,8 @@ function padLines(lines) {
33255
33933
  }
33256
33934
 
33257
33935
  // src/lib/progress/review-strategy.ts
33258
- import { existsSync as existsSync8, readdirSync as readdirSync3, readFileSync as readFileSync7 } from "node:fs";
33259
- import { join as join10, basename as basename5 } from "node:path";
33936
+ import { existsSync as existsSync12, readdirSync as readdirSync4, readFileSync as readFileSync8 } from "node:fs";
33937
+ import { join as join14, basename as basename6 } from "node:path";
33260
33938
  var REVIEW_PHASES = [
33261
33939
  { key: "context", label: "Context Discovery" },
33262
33940
  { key: "change-context", label: "Change Context" },
@@ -33268,10 +33946,10 @@ var REVIEW_PHASES = [
33268
33946
  { key: "complete", label: "Complete" }
33269
33947
  ];
33270
33948
  function countFindings(filePath) {
33271
- if (!existsSync8(filePath)) {
33949
+ if (!existsSync12(filePath)) {
33272
33950
  return 0;
33273
33951
  }
33274
- const content = readFileSync7(filePath, "utf-8");
33952
+ const content = readFileSync8(filePath, "utf-8");
33275
33953
  const findingMatches = content.match(/^##\s+(Finding|Issue|Suggestion)/gm);
33276
33954
  return findingMatches?.length ?? 0;
33277
33955
  }
@@ -33285,27 +33963,27 @@ function formatReviewerName(filename) {
33285
33963
  return base.charAt(0).toUpperCase() + base.slice(1);
33286
33964
  }
33287
33965
  function deriveRoundsFromFilesystem(roundsDir) {
33288
- if (!existsSync8(roundsDir)) {
33966
+ if (!existsSync12(roundsDir)) {
33289
33967
  return [];
33290
33968
  }
33291
- const roundDirs = readdirSync3(roundsDir).filter((d) => d.match(/^round-\d+$/)).sort((a, b) => {
33969
+ const roundDirs = readdirSync4(roundsDir).filter((d) => d.match(/^round-\d+$/)).sort((a, b) => {
33292
33970
  const numA = parseInt(a.replace("round-", ""));
33293
33971
  const numB = parseInt(b.replace("round-", ""));
33294
33972
  return numA - numB;
33295
33973
  });
33296
33974
  return roundDirs.map((dir) => {
33297
33975
  const roundNum = parseInt(dir.replace("round-", ""));
33298
- const roundPath = join10(roundsDir, dir);
33299
- const reviewsPath = join10(roundPath, "reviews");
33300
- const finalPath = join10(roundPath, "final.md");
33976
+ const roundPath = join14(roundsDir, dir);
33977
+ const reviewsPath = join14(roundPath, "reviews");
33978
+ const finalPath = join14(roundPath, "final.md");
33301
33979
  const reviewers = [];
33302
- if (existsSync8(reviewsPath)) {
33303
- const files = readdirSync3(reviewsPath).filter((f) => f.endsWith(".md"));
33980
+ if (existsSync12(reviewsPath)) {
33981
+ const files = readdirSync4(reviewsPath).filter((f) => f.endsWith(".md"));
33304
33982
  reviewers.push(...files.map((f) => f.replace(".md", "")));
33305
33983
  }
33306
33984
  return {
33307
33985
  round: roundNum,
33308
- isComplete: existsSync8(finalPath),
33986
+ isComplete: existsSync12(finalPath),
33309
33987
  reviewers
33310
33988
  };
33311
33989
  });
@@ -33315,7 +33993,7 @@ var ReviewProgressStrategy = class {
33315
33993
  phases = REVIEW_PHASES;
33316
33994
  totalPhases = 8;
33317
33995
  parseState(sessionPath, preservedStartTime) {
33318
- const session = basename5(sessionPath);
33996
+ const session = basename6(sessionPath);
33319
33997
  const state = readSessionState(sessionPath);
33320
33998
  if (!state) {
33321
33999
  return null;
@@ -33334,19 +34012,19 @@ var ReviewProgressStrategy = class {
33334
34012
  parseFromState(session, state, sessionPath, preservedStartTime) {
33335
34013
  const effectiveStartTime = state.round_started_at ?? state.started_at;
33336
34014
  const startTime = preservedStartTime ?? (effectiveStartTime ? new Date(effectiveStartTime).getTime() : Date.now());
33337
- const roundsDir = join10(sessionPath, "rounds");
34015
+ const roundsDir = join14(sessionPath, "rounds");
33338
34016
  const rounds = deriveRoundsFromFilesystem(roundsDir);
33339
34017
  const highestExistingRound = rounds.length > 0 ? Math.max(...rounds.map((r) => r.round)) : 1;
33340
34018
  const stateRound = state.current_round ?? 1;
33341
34019
  const currentRound = Math.min(stateRound, highestExistingRound);
33342
- const currentRoundDir = join10(roundsDir, `round-${currentRound}`);
33343
- const reviewsDir = join10(currentRoundDir, "reviews");
34020
+ const currentRoundDir = join14(roundsDir, `round-${currentRound}`);
34021
+ const reviewsDir = join14(currentRoundDir, "reviews");
33344
34022
  const reviewers = [];
33345
- if (existsSync8(reviewsDir)) {
33346
- const entries = readdirSync3(reviewsDir);
34023
+ if (existsSync12(reviewsDir)) {
34024
+ const entries = readdirSync4(reviewsDir);
33347
34025
  const reviewFiles = entries.filter((f) => f.endsWith(".md"));
33348
34026
  for (const file of reviewFiles) {
33349
- const reviewPath = join10(reviewsDir, file);
34027
+ const reviewPath = join14(reviewsDir, file);
33350
34028
  const findings = countFindings(reviewPath);
33351
34029
  reviewers.push({
33352
34030
  name: file.replace(".md", ""),
@@ -33356,14 +34034,14 @@ var ReviewProgressStrategy = class {
33356
34034
  });
33357
34035
  }
33358
34036
  }
33359
- const contextComplete = existsSync8(
33360
- join10(sessionPath, "discovered-standards.md")
34037
+ const contextComplete = existsSync12(
34038
+ join14(sessionPath, "discovered-standards.md")
33361
34039
  );
33362
- const changeContextComplete = existsSync8(join10(sessionPath, "context.md"));
34040
+ const changeContextComplete = existsSync12(join14(sessionPath, "context.md"));
33363
34041
  const analysisComplete = changeContextComplete;
33364
34042
  const reviewsComplete = state.phase_number > 4;
33365
- const discourseComplete = existsSync8(join10(currentRoundDir, "discourse.md"));
33366
- const synthesisComplete = existsSync8(join10(currentRoundDir, "final.md"));
34043
+ const discourseComplete = existsSync12(join14(currentRoundDir, "discourse.md"));
34044
+ const synthesisComplete = existsSync12(join14(currentRoundDir, "final.md"));
33367
34045
  return {
33368
34046
  workflowType: "review",
33369
34047
  session,
@@ -33495,8 +34173,8 @@ var ReviewProgressStrategy = class {
33495
34173
  var reviewStrategy = new ReviewProgressStrategy();
33496
34174
 
33497
34175
  // src/lib/progress/map-strategy.ts
33498
- import { existsSync as existsSync9, readdirSync as readdirSync4, readFileSync as readFileSync8 } from "node:fs";
33499
- import { join as join11, basename as basename6 } from "node:path";
34176
+ import { existsSync as existsSync13, readdirSync as readdirSync5, readFileSync as readFileSync9 } from "node:fs";
34177
+ import { join as join15, basename as basename7 } from "node:path";
33500
34178
  var MAP_PHASES = [
33501
34179
  { key: "map-context", label: "Context Discovery" },
33502
34180
  { key: "topology", label: "Topology Analysis" },
@@ -33506,23 +34184,23 @@ var MAP_PHASES = [
33506
34184
  { key: "complete", label: "Complete" }
33507
34185
  ];
33508
34186
  function deriveRunsFromFilesystem(mapDir) {
33509
- const runsDir = join11(mapDir, "runs");
33510
- if (!existsSync9(runsDir)) {
34187
+ const runsDir = join15(mapDir, "runs");
34188
+ if (!existsSync13(runsDir)) {
33511
34189
  return [];
33512
34190
  }
33513
- const runDirs = readdirSync4(runsDir).filter((d) => d.match(/^run-\d+$/)).sort((a, b) => {
34191
+ const runDirs = readdirSync5(runsDir).filter((d) => d.match(/^run-\d+$/)).sort((a, b) => {
33514
34192
  const numA = parseInt(a.replace("run-", ""));
33515
34193
  const numB = parseInt(b.replace("run-", ""));
33516
34194
  return numA - numB;
33517
34195
  });
33518
34196
  return runDirs.map((dir) => {
33519
34197
  const runNum = parseInt(dir.replace("run-", ""));
33520
- const runPath = join11(runsDir, dir);
33521
- const mapPath = join11(runPath, "map.md");
34198
+ const runPath = join15(runsDir, dir);
34199
+ const mapPath = join15(runPath, "map.md");
33522
34200
  let fileCount = 0;
33523
- const topologyPath = join11(runPath, "topology.md");
33524
- if (existsSync9(topologyPath)) {
33525
- const content = readFileSync8(topologyPath, "utf-8");
34201
+ const topologyPath = join15(runPath, "topology.md");
34202
+ if (existsSync13(topologyPath)) {
34203
+ const content = readFileSync9(topologyPath, "utf-8");
33526
34204
  const fileListMatch = content.match(
33527
34205
  /## Canonical File List[\s\S]*?```([\s\S]*?)```/
33528
34206
  );
@@ -33532,7 +34210,7 @@ function deriveRunsFromFilesystem(mapDir) {
33532
34210
  }
33533
34211
  return {
33534
34212
  run: runNum,
33535
- isComplete: existsSync9(mapPath),
34213
+ isComplete: existsSync13(mapPath),
33536
34214
  fileCount
33537
34215
  };
33538
34216
  });
@@ -33542,7 +34220,7 @@ var MapProgressStrategy = class {
33542
34220
  phases = MAP_PHASES;
33543
34221
  totalPhases = 6;
33544
34222
  parseState(sessionPath, preservedStartTime) {
33545
- const session = basename6(sessionPath);
34223
+ const session = basename7(sessionPath);
33546
34224
  const state = readSessionState(sessionPath);
33547
34225
  if (!state) {
33548
34226
  return null;
@@ -33564,24 +34242,24 @@ var MapProgressStrategy = class {
33564
34242
  parseFromState(session, state, sessionPath, preservedStartTime) {
33565
34243
  const effectiveStartTime = state.map_started_at ?? state.started_at;
33566
34244
  const startTime = preservedStartTime ?? (effectiveStartTime ? new Date(effectiveStartTime).getTime() : Date.now());
33567
- const mapDir = join11(sessionPath, "map");
34245
+ const mapDir = join15(sessionPath, "map");
33568
34246
  const runs = deriveRunsFromFilesystem(mapDir);
33569
34247
  const highestExistingRun = runs.length > 0 ? Math.max(...runs.map((r) => r.run)) : 1;
33570
34248
  const stateRun = state.current_map_run ?? 1;
33571
34249
  const currentRun = Math.min(stateRun, highestExistingRun);
33572
- const currentRunDir = join11(mapDir, "runs", `run-${currentRun}`);
33573
- const contextComplete = existsSync9(
33574
- join11(sessionPath, "discovered-standards.md")
34250
+ const currentRunDir = join15(mapDir, "runs", `run-${currentRun}`);
34251
+ const contextComplete = existsSync13(
34252
+ join15(sessionPath, "discovered-standards.md")
33575
34253
  );
33576
- const topologyComplete = existsSync9(join11(currentRunDir, "topology.md"));
33577
- const flowAnalysisComplete = existsSync9(
33578
- join11(currentRunDir, "flow-analysis.md")
34254
+ const topologyComplete = existsSync13(join15(currentRunDir, "topology.md"));
34255
+ const flowAnalysisComplete = existsSync13(
34256
+ join15(currentRunDir, "flow-analysis.md")
33579
34257
  );
33580
- const requirementsMappingComplete = existsSync9(
33581
- join11(currentRunDir, "requirements-mapping.md")
34258
+ const requirementsMappingComplete = existsSync13(
34259
+ join15(currentRunDir, "requirements-mapping.md")
33582
34260
  );
33583
- const synthesisComplete = existsSync9(join11(currentRunDir, "map.md"));
33584
- const hasRequirements = existsSync9(join11(sessionPath, "requirements.md"));
34261
+ const synthesisComplete = existsSync13(join15(currentRunDir, "map.md"));
34262
+ const hasRequirements = existsSync13(join15(sessionPath, "requirements.md"));
33585
34263
  const flowAnalysts = flowAnalysisComplete ? [
33586
34264
  {
33587
34265
  name: "flow-analyst",
@@ -34036,10 +34714,10 @@ function renderCombinedProgress(sessionPath, preservedStartTimes, ocrDir) {
34036
34714
  }
34037
34715
 
34038
34716
  // src/commands/state.ts
34039
- import { existsSync as existsSync16, mkdirSync as mkdirSync7, readFileSync as readFileSync11 } from "node:fs";
34040
- import { join as join18 } from "node:path";
34717
+ import { existsSync as existsSync17, mkdirSync as mkdirSync7, readFileSync as readFileSync12, readdirSync as readdirSync8 } from "node:fs";
34718
+ import { join as join19 } from "node:path";
34041
34719
 
34042
- // src/lib/state/index.ts
34720
+ // ../shared/persistence/src/state/index.ts
34043
34721
  init_db();
34044
34722
  init_exit_codes();
34045
34723
  import {
@@ -34052,7 +34730,7 @@ import {
34052
34730
  } from "node:fs";
34053
34731
  import { join as join17 } from "node:path";
34054
34732
 
34055
- // src/lib/state/phase-graph.ts
34733
+ // ../shared/persistence/src/state/phase-graph.ts
34056
34734
  init_exit_codes();
34057
34735
  var REVIEW_PHASE_NUMBERS = {
34058
34736
  context: 1,
@@ -34134,7 +34812,7 @@ function validatePhaseTransition(workflowType, source, target, isRoundBoundary)
34134
34812
  }
34135
34813
  }
34136
34814
 
34137
- // src/lib/state/meta-util.ts
34815
+ // ../shared/persistence/src/state/meta-util.ts
34138
34816
  var DEFAULT_METADATA_MAX_LEN = 4096;
34139
34817
  function sanitizeMetadataString(s, opts = {}) {
34140
34818
  const maxLen = opts.maxLen ?? DEFAULT_METADATA_MAX_LEN;
@@ -34144,9 +34822,11 @@ function sanitizeMetadataString(s, opts = {}) {
34144
34822
  return out;
34145
34823
  }
34146
34824
 
34147
- // src/lib/state/round-meta.ts
34825
+ // ../shared/persistence/src/state/round-meta.ts
34826
+ init_src();
34148
34827
  var VALID_CATEGORIES = /* @__PURE__ */ new Set(["blocker", "should_fix", "suggestion", "style"]);
34149
34828
  var VALID_SEVERITIES = /* @__PURE__ */ new Set(["critical", "high", "medium", "low", "info"]);
34829
+ var MIN_TITLE_LEN = 8;
34150
34830
  function validateRoundMeta(meta) {
34151
34831
  if (!meta || typeof meta !== "object") {
34152
34832
  throw new Error("round-meta.json must be a JSON object");
@@ -34157,10 +34837,16 @@ function validateRoundMeta(meta) {
34157
34837
  `Unsupported schema_version: ${String(obj.schema_version)}. Expected 1.`
34158
34838
  );
34159
34839
  }
34160
- if (typeof obj.verdict !== "string" || obj.verdict.trim().length === 0) {
34161
- throw new Error("round-meta.json must contain a non-empty verdict string");
34840
+ if (typeof obj.verdict !== "string") {
34841
+ throw new Error("round-meta.json must contain a verdict string");
34162
34842
  }
34163
- obj.verdict = sanitizeMetadataString(obj.verdict);
34843
+ const verdict = sanitizeMetadataString(obj.verdict).trim();
34844
+ if (!isCanonicalVerdict(verdict)) {
34845
+ throw new Error(
34846
+ `round-meta.json verdict "${String(obj.verdict)}" is not one of: ${CANONICAL_VERDICTS.join(", ")}`
34847
+ );
34848
+ }
34849
+ obj.verdict = verdict;
34164
34850
  if (!Array.isArray(obj.reviewers)) {
34165
34851
  throw new Error("round-meta.json must contain a reviewers array");
34166
34852
  }
@@ -34183,8 +34869,10 @@ function validateRoundMeta(meta) {
34183
34869
  throw new Error("Each finding must be an object");
34184
34870
  }
34185
34871
  const f = finding;
34186
- if (typeof f.title !== "string" || f.title.trim().length === 0) {
34187
- throw new Error("Each finding must have a non-empty title");
34872
+ if (typeof f.title !== "string" || f.title.trim().length < MIN_TITLE_LEN) {
34873
+ throw new Error(
34874
+ `Each finding title must be at least ${MIN_TITLE_LEN} characters; got "${String(f.title)}"`
34875
+ );
34188
34876
  }
34189
34877
  f.title = sanitizeMetadataString(f.title);
34190
34878
  if (typeof f.category !== "string" || !VALID_CATEGORIES.has(f.category)) {
@@ -34229,25 +34917,144 @@ function validateRoundMeta(meta) {
34229
34917
  if (typeof sc.suggestions !== "number" || sc.suggestions < 0) {
34230
34918
  throw new Error("synthesis_counts.suggestions must be a non-negative number");
34231
34919
  }
34920
+ const allFindings = obj.reviewers.flatMap((reviewer) => reviewer.findings);
34921
+ const derived = deriveCounts(allFindings);
34922
+ if (sc.blockers > derived.blocker) {
34923
+ throw new Error(
34924
+ `synthesis_counts.blockers (${sc.blockers}) exceeds the ${derived.blocker} blocker finding(s) present`
34925
+ );
34926
+ }
34927
+ if (sc.should_fix > derived.should_fix) {
34928
+ throw new Error(
34929
+ `synthesis_counts.should_fix (${sc.should_fix}) exceeds the ${derived.should_fix} should_fix finding(s) present`
34930
+ );
34931
+ }
34932
+ if (sc.suggestions > derived.suggestion) {
34933
+ throw new Error(
34934
+ `synthesis_counts.suggestions (${sc.suggestions}) exceeds the ${derived.suggestion} suggestion finding(s) present`
34935
+ );
34936
+ }
34937
+ }
34938
+ const { blockerCount } = resolveRoundCounts(obj);
34939
+ if (verdict === "APPROVE" && blockerCount > 0) {
34940
+ throw new Error(
34941
+ `round-meta.json verdict "APPROVE" is inconsistent with ${blockerCount} blocker finding(s); APPROVE requires zero blockers (use "REQUEST CHANGES", or carry residual work as should_fix/suggestion/style)`
34942
+ );
34943
+ }
34944
+ if (verdict === "REQUEST CHANGES" && blockerCount === 0) {
34945
+ throw new Error(
34946
+ `round-meta.json verdict "REQUEST CHANGES" requires at least one blocker finding; found ${blockerCount} (use "APPROVE" if there is nothing to block on, or "NEEDS DISCUSSION")`
34947
+ );
34232
34948
  }
34233
34949
  return meta;
34234
34950
  }
34235
34951
  function computeRoundCounts(meta) {
34236
- const allFindings = [];
34237
- for (const reviewer of meta.reviewers) {
34238
- allFindings.push(...reviewer.findings);
34952
+ return resolveRoundCounts(meta);
34953
+ }
34954
+
34955
+ // ../shared/persistence/src/state/forward-resume.ts
34956
+ init_db();
34957
+ init_liveness();
34958
+ var FORWARD_RESUME_KIND = "forward_resume";
34959
+ var FORWARD_RESUME_EXHAUSTED_REASON = "forward_resume_exhausted";
34960
+ function parseLeaseMetadata(e) {
34961
+ if (e.event_type !== "session_resumed" || !e.metadata) return null;
34962
+ try {
34963
+ return JSON.parse(e.metadata);
34964
+ } catch {
34965
+ return null;
34966
+ }
34967
+ }
34968
+ function remainingPhasesAfter(workflowType, currentPhase) {
34969
+ const numbers = workflowType === "map" ? MAP_PHASE_NUMBERS : REVIEW_PHASE_NUMBERS;
34970
+ const cur = numbers[currentPhase];
34971
+ if (cur === void 0) return [];
34972
+ return Object.entries(numbers).filter(([, n]) => n > cur).sort((a, b) => a[1] - b[1]).map(([phase]) => phase);
34973
+ }
34974
+ function countForwardResumeLeases(events, round) {
34975
+ return events.filter((e) => parseLeaseMetadata(e)?.kind === FORWARD_RESUME_KIND && parseLeaseMetadata(e)?.round === round).length;
34976
+ }
34977
+ function forwardResumeLeaseState(events, round, leaseMs, nowMs) {
34978
+ const leases = events.filter(
34979
+ (e) => parseLeaseMetadata(e)?.kind === FORWARD_RESUME_KIND && parseLeaseMetadata(e)?.round === round
34980
+ );
34981
+ if (leases.length === 0) return { leaseCount: 0, activeLeaseHeld: false };
34982
+ const latestLease = leases[leases.length - 1];
34983
+ const latestLeaseMs = sqliteUtcMs(latestLease.created_at);
34984
+ let effectiveMs = latestLeaseMs;
34985
+ for (const e of events) {
34986
+ if (e.event_type === "phase_transition" && (e.round == null || e.round === round)) {
34987
+ const t = sqliteUtcMs(e.created_at);
34988
+ if (t >= latestLeaseMs && t > effectiveMs) effectiveMs = t;
34989
+ }
34239
34990
  }
34240
- const sc = meta.synthesis_counts;
34241
34991
  return {
34242
- blockerCount: sc ? sc.blockers : allFindings.filter((f) => f.category === "blocker").length,
34243
- shouldFixCount: sc ? sc.should_fix : allFindings.filter((f) => f.category === "should_fix").length,
34244
- suggestionCount: sc ? sc.suggestions : allFindings.filter((f) => f.category === "suggestion").length,
34245
- reviewerCount: meta.reviewers.length,
34246
- totalFindingCount: allFindings.length
34992
+ leaseCount: leases.length,
34993
+ activeLeaseHeld: nowMs - effectiveMs < leaseMs
34994
+ };
34995
+ }
34996
+ function tryAcquireForwardResumeLease(db, sessionId, round, opts) {
34997
+ const nowMs = opts.nowMs ?? Date.now();
34998
+ return db.transaction(() => {
34999
+ const events = getEventsForSession(db, sessionId);
35000
+ const { leaseCount, activeLeaseHeld } = forwardResumeLeaseState(
35001
+ events,
35002
+ round,
35003
+ opts.leaseMs,
35004
+ nowMs
35005
+ );
35006
+ if (leaseCount >= opts.maxAttempts) {
35007
+ return { acquired: false, reason: "cap_exhausted", attemptsUsed: leaseCount };
35008
+ }
35009
+ if (activeLeaseHeld) {
35010
+ return { acquired: false, reason: "lease_held", attemptsUsed: leaseCount };
35011
+ }
35012
+ insertEvent(db, {
35013
+ session_id: sessionId,
35014
+ event_type: "session_resumed",
35015
+ metadata: JSON.stringify({ kind: FORWARD_RESUME_KIND, round })
35016
+ });
35017
+ return { acquired: true, attemptsUsed: leaseCount + 1 };
35018
+ });
35019
+ }
35020
+ function hasLiveOwningTurn(db, sessionId, heartbeatMs, nowMs) {
35021
+ const instances = listAgentSessionsForWorkflow(db, sessionId);
35022
+ return instances.some(
35023
+ (s) => s.ended_at == null && nowMs - sqliteUtcMs(s.last_heartbeat_at) <= heartbeatMs
35024
+ );
35025
+ }
35026
+ function deriveStrandedStatus(db, session, cfg) {
35027
+ const nowMs = cfg.nowMs ?? Date.now();
35028
+ if (hasLiveOwningTurn(db, session.id, cfg.heartbeatMs, nowMs)) return null;
35029
+ return strandedActionByCap(db, session, cfg.maxAttempts);
35030
+ }
35031
+ function strandedActionByCap(db, session, maxAttempts) {
35032
+ const events = getEventsForSession(db, session.id);
35033
+ const leaseCount = countForwardResumeLeases(events, session.current_round);
35034
+ const workflowType = session.workflow_type === "map" ? "map" : "review";
35035
+ return {
35036
+ action: leaseCount >= maxAttempts ? "abort_or_fresh" : "forward_resume",
35037
+ remainingPhases: remainingPhasesAfter(workflowType, session.current_phase),
35038
+ attemptsRemaining: Math.max(0, maxAttempts - leaseCount)
34247
35039
  };
34248
35040
  }
35041
+ function closeForwardResumeExhausted(db, sessionId, attempts) {
35042
+ commitReasonClose(
35043
+ db,
35044
+ sessionId,
35045
+ {
35046
+ event_type: "session_auto_closed_stale",
35047
+ phase: "complete",
35048
+ metadata: JSON.stringify({
35049
+ reason: FORWARD_RESUME_EXHAUSTED_REASON,
35050
+ attempts
35051
+ })
35052
+ },
35053
+ { status: "closed", current_phase: "complete" }
35054
+ );
35055
+ }
34249
35056
 
34250
- // src/lib/state/map-meta.ts
35057
+ // ../shared/persistence/src/state/map-meta.ts
34251
35058
  function validateMapMeta(meta) {
34252
35059
  if (!meta || typeof meta !== "object") {
34253
35060
  throw new Error("map-meta.json must be a JSON object");
@@ -34314,7 +35121,7 @@ function computeMapCounts(meta) {
34314
35121
  };
34315
35122
  }
34316
35123
 
34317
- // src/lib/state/projection.ts
35124
+ // ../shared/persistence/src/state/projection.ts
34318
35125
  init_db();
34319
35126
  var REASON_EVENT_TYPES = [
34320
35127
  "session_aborted",
@@ -34344,7 +35151,7 @@ function getCompletenessState(db, sessionId) {
34344
35151
  return r[0]?.values[0]?.[0] ?? null;
34345
35152
  }
34346
35153
 
34347
- // src/lib/state/index.ts
35154
+ // ../shared/persistence/src/state/index.ts
34348
35155
  init_exit_codes();
34349
35156
  function deriveNextRound(db, sessionId, fallbackRound) {
34350
35157
  const result = db.exec(
@@ -34399,6 +35206,12 @@ async function stateInit(params) {
34399
35206
  `Cannot re-open session ${sessionId} as workflow_type "${workflowType}": existing workflow_type is "${existing.workflow_type}". Maps and reviews have disjoint phase graphs.`
34400
35207
  );
34401
35208
  }
35209
+ if (existing.status === "active" && !hasCompletionInvariant(db, existing)) {
35210
+ throw new StateError(
35211
+ STATE_EXIT.INVARIANT_UNMET,
35212
+ `Session ${sessionId} is active and its current round is not complete \u2014 'begin' would reset it to "${initialPhaseFor(workflowType)}" and lose progress. Forward-resume instead: re-run the review (it continues from current_phase via 'ocr state status --json'), or 'ocr review --resume ${sessionId}'.`
35213
+ );
35214
+ }
34402
35215
  const nextRound = deriveNextRound(db, sessionId, existing.current_round);
34403
35216
  const initialPhase2 = workflowType === "map" ? "map-context" : "context";
34404
35217
  db.transaction(() => {
@@ -34698,18 +35511,19 @@ async function stateCompleteRound(params) {
34698
35511
  }
34699
35512
  const resolved = resolveSession(db, params.sessionId);
34700
35513
  const roundNumber = params.round ?? resolved.current_round;
34701
- const roundMetaPath = join17(
34702
- resolved.session_dir,
34703
- "rounds",
34704
- `round-${roundNumber}`,
34705
- "round-meta.json"
34706
- );
35514
+ const roundDir = join17(resolved.session_dir, "rounds", `round-${roundNumber}`);
35515
+ const roundMetaPath = join17(roundDir, "round-meta.json");
35516
+ const materializeArtifact = () => {
35517
+ mkdirSync6(roundDir, { recursive: true });
35518
+ writeFileSync7(roundMetaPath, JSON.stringify(meta, null, 2));
35519
+ };
34707
35520
  const already = db.exec(
34708
35521
  `SELECT 1 FROM orchestration_events
34709
35522
  WHERE session_id = ? AND event_type = 'round_completed' AND round = ? LIMIT 1`,
34710
35523
  [resolved.id, roundNumber]
34711
35524
  );
34712
35525
  if ((already[0]?.values.length ?? 0) > 0) {
35526
+ if (!existsSync15(roundMetaPath)) materializeArtifact();
34713
35527
  return { sessionId: resolved.id, round: roundNumber, metaPath: roundMetaPath, schema_version: 1 };
34714
35528
  }
34715
35529
  if (resolved.current_phase !== "synthesis") {
@@ -34719,7 +35533,7 @@ async function stateCompleteRound(params) {
34719
35533
  );
34720
35534
  }
34721
35535
  if (params.requireFinal) {
34722
- const finalPath = join17(resolved.session_dir, "rounds", `round-${roundNumber}`, "final.md");
35536
+ const finalPath = join17(roundDir, "final.md");
34723
35537
  if (!existsSync15(finalPath)) {
34724
35538
  throw new StateError(
34725
35539
  STATE_EXIT.INVARIANT_UNMET,
@@ -34727,13 +35541,8 @@ async function stateCompleteRound(params) {
34727
35541
  );
34728
35542
  }
34729
35543
  }
34730
- let metaPath;
34731
- if (params.source === "stdin") {
34732
- const roundDir = join17(resolved.session_dir, "rounds", `round-${roundNumber}`);
34733
- mkdirSync6(roundDir, { recursive: true });
34734
- metaPath = roundMetaPath;
34735
- writeFileSync7(metaPath, JSON.stringify(meta, null, 2));
34736
- }
35544
+ materializeArtifact();
35545
+ const metaPath = roundMetaPath;
34737
35546
  db.transaction(() => {
34738
35547
  insertEvent(db, {
34739
35548
  session_id: resolved.id,
@@ -34784,19 +35593,19 @@ async function stateCompleteMap(params) {
34784
35593
  }
34785
35594
  const resolved = resolveSession(db, params.sessionId);
34786
35595
  const mapRunNumber = params.mapRun ?? resolved.current_map_run;
34787
- const mapMetaPath = join17(
34788
- resolved.session_dir,
34789
- "map",
34790
- "runs",
34791
- `run-${mapRunNumber}`,
34792
- "map-meta.json"
34793
- );
35596
+ const runDir = join17(resolved.session_dir, "map", "runs", `run-${mapRunNumber}`);
35597
+ const mapMetaPath = join17(runDir, "map-meta.json");
35598
+ const materializeArtifact = () => {
35599
+ mkdirSync6(runDir, { recursive: true });
35600
+ writeFileSync7(mapMetaPath, JSON.stringify(meta, null, 2));
35601
+ };
34794
35602
  const already = db.exec(
34795
35603
  `SELECT 1 FROM orchestration_events
34796
35604
  WHERE session_id = ? AND event_type = 'map_completed' AND round = ? LIMIT 1`,
34797
35605
  [resolved.id, mapRunNumber]
34798
35606
  );
34799
35607
  if ((already[0]?.values.length ?? 0) > 0) {
35608
+ if (!existsSync15(mapMetaPath)) materializeArtifact();
34800
35609
  return { sessionId: resolved.id, mapRun: mapRunNumber, metaPath: mapMetaPath, schema_version: 1 };
34801
35610
  }
34802
35611
  if (resolved.current_phase !== "synthesis") {
@@ -34805,13 +35614,8 @@ async function stateCompleteMap(params) {
34805
35614
  `Cannot complete map: workflow is at "${resolved.current_phase}", not "synthesis". Advance first.`
34806
35615
  );
34807
35616
  }
34808
- let metaPath;
34809
- if (params.source === "stdin") {
34810
- const runDir = join17(resolved.session_dir, "map", "runs", `run-${mapRunNumber}`);
34811
- mkdirSync6(runDir, { recursive: true });
34812
- metaPath = mapMetaPath;
34813
- writeFileSync7(metaPath, JSON.stringify(meta, null, 2));
34814
- }
35617
+ materializeArtifact();
35618
+ const metaPath = mapMetaPath;
34815
35619
  db.transaction(() => {
34816
35620
  insertEvent(db, {
34817
35621
  session_id: resolved.id,
@@ -34837,7 +35641,7 @@ async function stateCompleteMap(params) {
34837
35641
  });
34838
35642
  return { sessionId: resolved.id, mapRun: mapRunNumber, metaPath, schema_version: 1 };
34839
35643
  }
34840
- async function stateStatus(ocrDir, sessionId) {
35644
+ async function stateStatus(ocrDir, sessionId, forwardResume) {
34841
35645
  const db = await ensureDatabase(ocrDir);
34842
35646
  const resolved = resolveSession(db, sessionId);
34843
35647
  const view = db.exec(
@@ -34850,6 +35654,8 @@ async function stateStatus(ocrDir, sessionId) {
34850
35654
  const hasTerminalArtifact = row?.[1] === 1;
34851
35655
  let nextAction;
34852
35656
  let nextActionKind;
35657
+ let remainingPhases;
35658
+ let attemptsRemaining;
34853
35659
  switch (completenessState) {
34854
35660
  case "complete":
34855
35661
  nextAction = "none \u2014 session is complete";
@@ -34867,12 +35673,25 @@ async function stateStatus(ocrDir, sessionId) {
34867
35673
  if (hasTerminalArtifact) {
34868
35674
  nextAction = "run 'ocr state finish' to close the workflow";
34869
35675
  nextActionKind = "finish";
34870
- } else if (resolved.current_phase === "synthesis") {
34871
- nextAction = "pipe round metadata to 'ocr state complete-round --stdin'";
34872
- nextActionKind = "complete_round";
34873
35676
  } else {
34874
- nextAction = "advance through the phases, then 'ocr state complete-round'";
34875
- nextActionKind = "advance";
35677
+ const stranded = forwardResume && resolved.status === "active" ? deriveStrandedStatus(db, resolved, forwardResume) : null;
35678
+ if (stranded) {
35679
+ remainingPhases = stranded.remainingPhases;
35680
+ attemptsRemaining = stranded.attemptsRemaining;
35681
+ if (stranded.action === "forward_resume") {
35682
+ nextAction = `forward-resume from '${resolved.current_phase}': re-run the review (it continues via 'ocr state status --json'), or 'ocr review --resume ${resolved.id}'`;
35683
+ nextActionKind = "forward_resume";
35684
+ } else {
35685
+ nextAction = "forward-resume attempts exhausted \u2014 abort with 'ocr state finish --abort' or start a fresh review";
35686
+ nextActionKind = "abort_or_fresh";
35687
+ }
35688
+ } else if (resolved.current_phase === "synthesis") {
35689
+ nextAction = "pipe round metadata to 'ocr state complete-round --stdin'";
35690
+ nextActionKind = "complete_round";
35691
+ } else {
35692
+ nextAction = "advance through the phases, then 'ocr state complete-round'";
35693
+ nextActionKind = "advance";
35694
+ }
34876
35695
  }
34877
35696
  }
34878
35697
  return {
@@ -34888,7 +35707,9 @@ async function stateStatus(ocrDir, sessionId) {
34888
35707
  marked_closed: row?.[2] === 1,
34889
35708
  dependents_settled: row?.[3] === 1,
34890
35709
  next_action: nextAction,
34891
- next_action_kind: nextActionKind
35710
+ next_action_kind: nextActionKind,
35711
+ ...remainingPhases ? { remaining_phases: remainingPhases } : {},
35712
+ ...attemptsRemaining !== void 0 ? { forward_resume_attempts_remaining: attemptsRemaining } : {}
34892
35713
  };
34893
35714
  }
34894
35715
  async function stateSync(ocrDir) {
@@ -34976,14 +35797,73 @@ async function stateSync(ocrDir) {
34976
35797
  }
34977
35798
 
34978
35799
  // src/commands/state.ts
34979
- init_command_log();
34980
35800
  init_db();
34981
35801
  init_db();
34982
- function readDashboardSpawnMarker(ocrDir) {
34983
- const path2 = join18(ocrDir, "data", "dashboard-active-spawn.json");
35802
+
35803
+ // ../shared/config/src/runtime-config.ts
35804
+ import { existsSync as existsSync16, readFileSync as readFileSync11 } from "node:fs";
35805
+ import { join as join18 } from "node:path";
35806
+ var DEFAULT_AGENT_HEARTBEAT_SECONDS = 60;
35807
+ var DEFAULT_FORWARD_RESUME_MAX_ATTEMPTS = 2;
35808
+ var DEFAULT_FORWARD_RESUME_LEASE_SECONDS = 1800;
35809
+ function readRuntimePositiveInt(ocrDir, key, defaultValue) {
35810
+ const configPath = join18(ocrDir, "config.yaml");
35811
+ if (!existsSync16(configPath)) return defaultValue;
35812
+ let content;
35813
+ try {
35814
+ content = readFileSync11(configPath, "utf-8");
35815
+ } catch {
35816
+ return defaultValue;
35817
+ }
35818
+ const blockMatch = content.match(
35819
+ new RegExp(
35820
+ String.raw`^runtime:\s*\n(?:\s+[^\n]*\n)*?\s+${key}:\s*([^\s#\n]+)`,
35821
+ "m"
35822
+ )
35823
+ );
35824
+ const inlineMatch = content.match(
35825
+ new RegExp(String.raw`^runtime:\s*\{[^}]*\b${key}:\s*([^\s,}]+)`, "m")
35826
+ );
35827
+ const raw = blockMatch?.[1] ?? inlineMatch?.[1];
35828
+ if (!raw) return defaultValue;
35829
+ const parsed = Number(raw);
35830
+ if (!Number.isFinite(parsed) || parsed <= 0 || !Number.isInteger(parsed)) {
35831
+ process.stderr.write(
35832
+ `[ocr] runtime.${key} is not a positive integer (got "${raw}"); falling back to ${defaultValue}.
35833
+ `
35834
+ );
35835
+ return defaultValue;
35836
+ }
35837
+ return parsed;
35838
+ }
35839
+ function getAgentHeartbeatSeconds(ocrDir) {
35840
+ return readRuntimePositiveInt(
35841
+ ocrDir,
35842
+ "agent_heartbeat_seconds",
35843
+ DEFAULT_AGENT_HEARTBEAT_SECONDS
35844
+ );
35845
+ }
35846
+ function getForwardResumeMaxAttempts(ocrDir) {
35847
+ return readRuntimePositiveInt(
35848
+ ocrDir,
35849
+ "forward_resume_max_attempts",
35850
+ DEFAULT_FORWARD_RESUME_MAX_ATTEMPTS
35851
+ );
35852
+ }
35853
+ function getForwardResumeLeaseMs(ocrDir) {
35854
+ return readRuntimePositiveInt(
35855
+ ocrDir,
35856
+ "forward_resume_lease_seconds",
35857
+ DEFAULT_FORWARD_RESUME_LEASE_SECONDS
35858
+ ) * 1e3;
35859
+ }
35860
+
35861
+ // src/commands/state.ts
35862
+ init_db();
35863
+ function readMarkerFile(path2) {
34984
35864
  let raw;
34985
35865
  try {
34986
- raw = readFileSync11(path2, "utf-8");
35866
+ raw = readFileSync12(path2, "utf-8");
34987
35867
  } catch {
34988
35868
  return null;
34989
35869
  }
@@ -35004,6 +35884,30 @@ function readDashboardSpawnMarker(ocrDir) {
35004
35884
  }
35005
35885
  return marker;
35006
35886
  }
35887
+ function readDashboardSpawnMarker(ocrDir) {
35888
+ const dir = join19(ocrDir, "data", "dashboard-active-spawn");
35889
+ let entries = [];
35890
+ try {
35891
+ entries = readdirSync8(dir).filter((f) => f.endsWith(".json"));
35892
+ } catch {
35893
+ entries = [];
35894
+ }
35895
+ const live = [];
35896
+ for (const entry of entries) {
35897
+ const marker = readMarkerFile(join19(dir, entry));
35898
+ if (marker) live.push(marker);
35899
+ }
35900
+ if (live.length === 1) return live[0] ?? null;
35901
+ if (live.length > 1) {
35902
+ console.error(
35903
+ source_default.gray(
35904
+ `[state] ${live.length} concurrent dashboard spawns live; marker fallback is ambiguous \u2014 pass --dashboard-uid for linkage`
35905
+ )
35906
+ );
35907
+ return null;
35908
+ }
35909
+ return readMarkerFile(join19(ocrDir, "data", "dashboard-active-spawn.json"));
35910
+ }
35007
35911
  async function readStdin() {
35008
35912
  const chunks = [];
35009
35913
  for await (const chunk of process.stdin) {
@@ -35045,7 +35949,7 @@ async function linkDashboardInvocation(ocrDir, sessionId, explicitUid, label) {
35045
35949
  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) => {
35046
35950
  const targetDir = process.cwd();
35047
35951
  requireOcrSetup(targetDir);
35048
- const ocrDir = join18(targetDir, ".ocr");
35952
+ const ocrDir = join19(targetDir, ".ocr");
35049
35953
  try {
35050
35954
  const result = await stateShow(ocrDir, options.sessionId);
35051
35955
  if (!result) {
@@ -35114,7 +36018,7 @@ var showSubcommand = new Command("show").description("Show current session state
35114
36018
  var syncSubcommand = new Command("sync").description("Rebuild session state from filesystem artifacts").action(async () => {
35115
36019
  const targetDir = process.cwd();
35116
36020
  requireOcrSetup(targetDir);
35117
- const ocrDir = join18(targetDir, ".ocr");
36021
+ const ocrDir = join19(targetDir, ".ocr");
35118
36022
  try {
35119
36023
  const synced = await stateSync(ocrDir);
35120
36024
  console.log(`Synced ${synced} session${synced !== 1 ? "s" : ""} from filesystem.`);
@@ -35141,7 +36045,7 @@ var reconcileSubcommand = new Command("reconcile").description(
35141
36045
  ).option("--dry-run", "Print the repair plan without writing anything").option("--json", "Output the result as JSON").action(async (options) => {
35142
36046
  const targetDir = process.cwd();
35143
36047
  requireOcrSetup(targetDir);
35144
- const ocrDir = join18(targetDir, ".ocr");
36048
+ const ocrDir = join19(targetDir, ".ocr");
35145
36049
  try {
35146
36050
  const db = await ensureDatabase(ocrDir);
35147
36051
  const result = reconcileLegacyState(db, ocrDir, { dryRun: options.dryRun });
@@ -35200,9 +36104,9 @@ var beginSubcommand = new Command("begin").description("Start or resume a workfl
35200
36104
  async (options) => {
35201
36105
  const targetDir = process.cwd();
35202
36106
  requireOcrSetup(targetDir);
35203
- const ocrDir = join18(targetDir, ".ocr");
35204
- const sessionDir = options.sessionDir ?? join18(ocrDir, "sessions", options.sessionId);
35205
- if (!existsSync16(sessionDir)) mkdirSync7(sessionDir, { recursive: true });
36107
+ const ocrDir = join19(targetDir, ".ocr");
36108
+ const sessionDir = options.sessionDir ?? join19(ocrDir, "sessions", options.sessionId);
36109
+ if (!existsSync17(sessionDir)) mkdirSync7(sessionDir, { recursive: true });
35206
36110
  try {
35207
36111
  const result = await stateBegin({
35208
36112
  sessionId: options.sessionId,
@@ -35224,7 +36128,7 @@ var advanceSubcommand = new Command("advance").description("Advance the workflow
35224
36128
  async (options) => {
35225
36129
  const targetDir = process.cwd();
35226
36130
  requireOcrSetup(targetDir);
35227
- const ocrDir = join18(targetDir, ".ocr");
36131
+ const ocrDir = join19(targetDir, ".ocr");
35228
36132
  try {
35229
36133
  const { id: sessionId } = await resolveActiveSession(ocrDir, options.sessionId);
35230
36134
  await stateAdvance({
@@ -35244,7 +36148,7 @@ var completeRoundSubcommand = new Command("complete-round").description("Atomica
35244
36148
  async (options) => {
35245
36149
  const targetDir = process.cwd();
35246
36150
  requireOcrSetup(targetDir);
35247
- const ocrDir = join18(targetDir, ".ocr");
36151
+ const ocrDir = join19(targetDir, ".ocr");
35248
36152
  try {
35249
36153
  const base = options.stdin ? { source: "stdin", data: await readStdin() } : options.file ? { source: "file", filePath: options.file } : (() => {
35250
36154
  throw new StateError(STATE_EXIT.USAGE, "Provide --stdin or --file with round metadata");
@@ -35268,7 +36172,7 @@ var completeMapSubcommand = new Command("complete-map").description("Atomically
35268
36172
  async (options) => {
35269
36173
  const targetDir = process.cwd();
35270
36174
  requireOcrSetup(targetDir);
35271
- const ocrDir = join18(targetDir, ".ocr");
36175
+ const ocrDir = join19(targetDir, ".ocr");
35272
36176
  try {
35273
36177
  const base = options.stdin ? { source: "stdin", data: await readStdin() } : options.file ? { source: "file", filePath: options.file } : (() => {
35274
36178
  throw new StateError(STATE_EXIT.USAGE, "Provide --stdin or --file with map metadata");
@@ -35290,7 +36194,7 @@ var completeMapSubcommand = new Command("complete-map").description("Atomically
35290
36194
  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) => {
35291
36195
  const targetDir = process.cwd();
35292
36196
  requireOcrSetup(targetDir);
35293
- const ocrDir = join18(targetDir, ".ocr");
36197
+ const ocrDir = join19(targetDir, ".ocr");
35294
36198
  try {
35295
36199
  const { id: sessionId } = await resolveActiveSession(ocrDir, options.sessionId);
35296
36200
  await stateClose({ sessionId, ocrDir, abort: options.abort });
@@ -35302,14 +36206,20 @@ var finishSubcommand = new Command("finish").description("Close a workflow (refu
35302
36206
  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) => {
35303
36207
  const targetDir = process.cwd();
35304
36208
  requireOcrSetup(targetDir);
35305
- const ocrDir = join18(targetDir, ".ocr");
36209
+ const ocrDir = join19(targetDir, ".ocr");
35306
36210
  try {
35307
- const result = await stateStatus(ocrDir, options.sessionId);
36211
+ const result = await stateStatus(ocrDir, options.sessionId, {
36212
+ maxAttempts: getForwardResumeMaxAttempts(ocrDir),
36213
+ heartbeatMs: getAgentHeartbeatSeconds(ocrDir) * 1e3
36214
+ });
35308
36215
  if (options.json) {
35309
36216
  console.log(JSON.stringify(result, null, 2));
35310
36217
  } else {
35311
36218
  console.log(`${result.session_id}: ${result.completeness_state}`);
35312
36219
  console.log(source_default.dim(` next: ${result.next_action}`));
36220
+ if (result.remaining_phases?.length) {
36221
+ console.log(source_default.dim(` remaining: ${result.remaining_phases.join(" \u2192 ")}`));
36222
+ }
35313
36223
  }
35314
36224
  } catch (error) {
35315
36225
  exitFromStateError(error, "Failed to read status");
@@ -35333,50 +36243,6 @@ var stateCommand = new Command("state").description("Manage OCR session state").
35333
36243
  import { randomUUID as randomUUID3 } from "node:crypto";
35334
36244
  import { join as join20 } from "node:path";
35335
36245
  init_db();
35336
-
35337
- // src/lib/runtime-config.ts
35338
- import { existsSync as existsSync17, readFileSync as readFileSync12 } from "node:fs";
35339
- import { join as join19 } from "node:path";
35340
- var DEFAULT_AGENT_HEARTBEAT_SECONDS = 60;
35341
- function readRuntimePositiveInt(ocrDir, key, defaultValue) {
35342
- const configPath = join19(ocrDir, "config.yaml");
35343
- if (!existsSync17(configPath)) return defaultValue;
35344
- let content;
35345
- try {
35346
- content = readFileSync12(configPath, "utf-8");
35347
- } catch {
35348
- return defaultValue;
35349
- }
35350
- const blockMatch = content.match(
35351
- new RegExp(
35352
- String.raw`^runtime:\s*\n(?:\s+[^\n]*\n)*?\s+${key}:\s*([^\s#\n]+)`,
35353
- "m"
35354
- )
35355
- );
35356
- const inlineMatch = content.match(
35357
- new RegExp(String.raw`^runtime:\s*\{[^}]*\b${key}:\s*([^\s,}]+)`, "m")
35358
- );
35359
- const raw = blockMatch?.[1] ?? inlineMatch?.[1];
35360
- if (!raw) return defaultValue;
35361
- const parsed = Number(raw);
35362
- if (!Number.isFinite(parsed) || parsed <= 0 || !Number.isInteger(parsed)) {
35363
- process.stderr.write(
35364
- `[ocr] runtime.${key} is not a positive integer (got "${raw}"); falling back to ${defaultValue}.
35365
- `
35366
- );
35367
- return defaultValue;
35368
- }
35369
- return parsed;
35370
- }
35371
- function getAgentHeartbeatSeconds(ocrDir) {
35372
- return readRuntimePositiveInt(
35373
- ocrDir,
35374
- "agent_heartbeat_seconds",
35375
- DEFAULT_AGENT_HEARTBEAT_SECONDS
35376
- );
35377
- }
35378
-
35379
- // src/commands/session.ts
35380
36246
  var TERMINAL_STATUSES = /* @__PURE__ */ new Set([
35381
36247
  "done",
35382
36248
  "crashed",
@@ -35427,6 +36293,11 @@ var startInstanceSubcommand = new Command("start-instance").description("Journal
35427
36293
  }
35428
36294
  );
35429
36295
  var bindVendorIdSubcommand = new Command("bind-vendor-id").description("Bind the underlying CLI's session id to an OCR agent session").argument("<agent-session-id>", "OCR agent session id").argument("<vendor-session-id>", "Underlying CLI's session id").action(async (agentId, vendorId) => {
36296
+ if (!SAFE_VENDOR_SESSION_ID.test(vendorId)) {
36297
+ fail(
36298
+ `vendor-session-id ${JSON.stringify(vendorId)} is not a plausible vendor session id (allowed: letters and digits plus . _ : - , max 256 chars). Nothing was bound \u2014 retry with the id the vendor CLI actually emitted.`
36299
+ );
36300
+ }
35430
36301
  const { ocrDir } = await setup();
35431
36302
  const db = await ensureDatabase(ocrDir);
35432
36303
  try {
@@ -35524,7 +36395,7 @@ var listSubcommand = new Command("list").description("List agent sessions for a
35524
36395
  });
35525
36396
  var sessionCommand = new Command("session").description("Manage agent-CLI session lifecycle journal").addCommand(startInstanceSubcommand).addCommand(bindVendorIdSubcommand).addCommand(beatSubcommand).addCommand(endInstanceSubcommand).addCommand(listSubcommand);
35526
36397
 
35527
- // src/lib/models.ts
36398
+ // ../shared/config/src/models.ts
35528
36399
  init_src();
35529
36400
  function parseOpenCodeModelList(stdout) {
35530
36401
  const models = [];
@@ -35926,11 +36797,11 @@ function pairKey(pair) {
35926
36797
  var teamCommand = new Command("team").description("Resolve and persist team composition").addCommand(resolveSubcommand).addCommand(setSubcommand);
35927
36798
 
35928
36799
  // src/commands/review.ts
35929
- import { spawn as spawn3 } from "node:child_process";
36800
+ init_src();
35930
36801
  import { join as join22 } from "node:path";
35931
36802
  init_db();
35932
36803
 
35933
- // src/lib/vendor-resume.ts
36804
+ // ../shared/persistence/src/vendor-resume.ts
35934
36805
  var VENDOR_BINARIES = {
35935
36806
  claude: "claude",
35936
36807
  opencode: "opencode"
@@ -35952,6 +36823,7 @@ function fail3(message) {
35952
36823
  console.error(source_default.red(`Error: ${message}`));
35953
36824
  process.exit(1);
35954
36825
  }
36826
+ var CONTROL_PROMPT = "Resume this OCR review: run `ocr state status --json` and act on `next_action`, continuing forward from `current_phase` without redoing completed phases.";
35955
36827
  var reviewCommand = new Command("review").description("Run or resume an OCR review").option("--resume <workflow-id>", "Resume a prior review by its workflow session id").action(async (options) => {
35956
36828
  if (!options.resume) {
35957
36829
  console.error(
@@ -35968,21 +36840,89 @@ var reviewCommand = new Command("review").description("Run or resume an OCR revi
35968
36840
  requireOcrSetup(targetDir);
35969
36841
  const ocrDir = join22(targetDir, ".ocr");
35970
36842
  const db = await ensureDatabase(ocrDir);
35971
- const session = getSession(db, options.resume);
36843
+ const workflowId = options.resume;
36844
+ const session = getSession(db, workflowId);
35972
36845
  if (!session) {
35973
- fail3(`Workflow session not found: ${options.resume}`);
36846
+ fail3(`Workflow session not found: ${workflowId}`);
36847
+ }
36848
+ const maxAttempts = getForwardResumeMaxAttempts(ocrDir);
36849
+ const leaseMs = getForwardResumeLeaseMs(ocrDir);
36850
+ const heartbeatMs = getAgentHeartbeatSeconds(ocrDir) * 1e3;
36851
+ const status = await stateStatus(ocrDir, workflowId, {
36852
+ maxAttempts,
36853
+ heartbeatMs
36854
+ });
36855
+ switch (status.next_action_kind) {
36856
+ case "none":
36857
+ console.error(source_default.green(`Workflow ${workflowId} is already complete \u2014 nothing to resume.`));
36858
+ process.exit(0);
36859
+ break;
36860
+ case "finish":
36861
+ console.error(
36862
+ source_default.yellow(`Workflow ${workflowId}'s round is complete but the session is still open.`)
36863
+ );
36864
+ console.error(source_default.dim("Run `ocr state finish` to close it."));
36865
+ process.exit(0);
36866
+ break;
36867
+ case "abort_or_fresh": {
36868
+ closeForwardResumeExhausted(db, workflowId, maxAttempts);
36869
+ fail3(
36870
+ `Forward-resume attempts exhausted for workflow ${workflowId} (cap ${maxAttempts}). Closed non-success (artifacts preserved). Start a fresh review, or run \`ocr state finish --abort\` if it was already closed.`
36871
+ );
36872
+ break;
36873
+ }
36874
+ case "advance":
36875
+ case "complete_round":
36876
+ case "wait":
36877
+ console.error(
36878
+ source_default.yellow(
36879
+ `Workflow ${workflowId} appears to still be running (phase "${status.current_phase}"). Nothing to resume yet.`
36880
+ )
36881
+ );
36882
+ process.exit(0);
36883
+ break;
36884
+ case "reopen":
36885
+ console.error(
36886
+ source_default.yellow(`Workflow ${workflowId} was closed without a completed round.`)
36887
+ );
36888
+ console.error(source_default.dim("Re-invoke the review skill to finalize it."));
36889
+ process.exit(0);
36890
+ break;
35974
36891
  }
35975
- const latest = getLatestAgentSessionWithVendorId(db, options.resume);
35976
- if (!latest || !latest.vendor_session_id) {
36892
+ const lease = tryAcquireForwardResumeLease(db, workflowId, session.current_round, {
36893
+ leaseMs,
36894
+ maxAttempts
36895
+ });
36896
+ if (!lease.acquired) {
36897
+ if (lease.reason === "cap_exhausted") {
36898
+ closeForwardResumeExhausted(db, workflowId, lease.attemptsUsed);
36899
+ fail3(
36900
+ `Forward-resume attempts exhausted for workflow ${workflowId} (cap ${maxAttempts}). Closed non-success (artifacts preserved). Start a fresh review.`
36901
+ );
36902
+ }
35977
36903
  fail3(
35978
- `No vendor session id has been captured for workflow ${options.resume}. Resume requires at least one journaled agent session with a bound vendor id. Start a fresh review with \`ocr review\` (no --resume).`
36904
+ `A forward-resume is already in progress for workflow ${workflowId} (lease held). Wait for it to finish or retry after the lease expires.`
35979
36905
  );
35980
36906
  }
35981
- const binary = VENDOR_BINARIES[latest.vendor];
35982
- if (!binary) {
35983
- fail3(
35984
- `Unknown vendor "${latest.vendor}" recorded for workflow ${options.resume}. OCR knows how to resume Claude Code and OpenCode; this workflow used something else.`
36907
+ console.error(
36908
+ source_default.dim(
36909
+ `Forward-resuming workflow ${session.id} on branch ${session.branch} from phase "${status.current_phase}" (${status.forward_resume_attempts_remaining ?? "?"} attempt(s) left).`
36910
+ )
36911
+ );
36912
+ const latest = getLatestAgentSessionWithVendorId(db, workflowId);
36913
+ const binary = latest?.vendor ? VENDOR_BINARIES[latest.vendor] : void 0;
36914
+ if (!latest || !latest.vendor_session_id || !binary) {
36915
+ console.error(
36916
+ source_default.yellow(
36917
+ `No resumable vendor session is captured for workflow ${workflowId}.`
36918
+ )
35985
36919
  );
36920
+ console.error(
36921
+ source_default.dim(
36922
+ `Continue it by re-invoking the review skill (\`/ocr-review\`) in your AI CLI \u2014 its Phase 0 reads \`ocr state status --json\` and continues forward from "${status.current_phase}". (${CONTROL_PROMPT})`
36923
+ )
36924
+ );
36925
+ process.exit(0);
35986
36926
  }
35987
36927
  let args;
35988
36928
  try {
@@ -35990,12 +36930,8 @@ var reviewCommand = new Command("review").description("Run or resume an OCR revi
35990
36930
  } catch (err) {
35991
36931
  fail3(err instanceof Error ? err.message : String(err));
35992
36932
  }
35993
- console.error(
35994
- source_default.dim(
35995
- `Resuming workflow ${session.id} on branch ${session.branch} via ${binary}\u2026`
35996
- )
35997
- );
35998
- const child = spawn3(binary, args, {
36933
+ console.error(source_default.dim(`Resuming via ${binary} (continue forward from "${status.current_phase}")\u2026`));
36934
+ const child = spawnBinary(binary, args, {
35999
36935
  stdio: "inherit",
36000
36936
  cwd: targetDir
36001
36937
  });