@open-code-review/cli 2.2.0 → 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 (65) hide show
  1. package/README.md +9 -0
  2. package/dist/dashboard/client/assets/{_basePickBy-BBPb8BJA.js → _basePickBy-CyrHyeyN.js} +1 -1
  3. package/dist/dashboard/client/assets/{_baseUniq-CFHdos6T.js → _baseUniq-Bg7NJSGS.js} +1 -1
  4. package/dist/dashboard/client/assets/{arc-BKGGWA2F.js → arc-zDGAKMur.js} +1 -1
  5. package/dist/dashboard/client/assets/{architectureDiagram-VXUJARFQ-B_ovNjX1.js → architectureDiagram-VXUJARFQ-BxlGxm0Q.js} +1 -1
  6. package/dist/dashboard/client/assets/{blockDiagram-VD42YOAC-C2M-avVp.js → blockDiagram-VD42YOAC-BskTNyX5.js} +1 -1
  7. package/dist/dashboard/client/assets/{c4Diagram-YG6GDRKO-BtOBpAzH.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-Cz2EbHPl.js → chunk-4BX2VUAB-xq9xoCTv.js} +1 -1
  10. package/dist/dashboard/client/assets/{chunk-55IACEB6-C8xpXw9G.js → chunk-55IACEB6-DYdXYVh5.js} +1 -1
  11. package/dist/dashboard/client/assets/{chunk-B4BG7PRW-BSRfOovX.js → chunk-B4BG7PRW-BGAyFRFS.js} +1 -1
  12. package/dist/dashboard/client/assets/{chunk-DI55MBZ5-CEUbYQWn.js → chunk-DI55MBZ5-C5ul9stk.js} +1 -1
  13. package/dist/dashboard/client/assets/{chunk-FMBD7UC4-5xWP6GRj.js → chunk-FMBD7UC4-BSaPo2xa.js} +1 -1
  14. package/dist/dashboard/client/assets/{chunk-QN33PNHL-DfNCVcy8.js → chunk-QN33PNHL-CyzabUv0.js} +1 -1
  15. package/dist/dashboard/client/assets/{chunk-QZHKN3VN--OdToKKu.js → chunk-QZHKN3VN-CceRbxt_.js} +1 -1
  16. package/dist/dashboard/client/assets/{chunk-TZMSLE5B-B_0K0Qso.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-Cc_Dmnxz.js → cose-bilkent-S5V4N54A-DEdXBrCt.js} +1 -1
  21. package/dist/dashboard/client/assets/{dagre-6UL2VRFP-DaAfvUXU.js → dagre-6UL2VRFP-DRdIiP58.js} +1 -1
  22. package/dist/dashboard/client/assets/{diagram-PSM6KHXK-7idwN0rC.js → diagram-PSM6KHXK-Bo7Q2VlK.js} +1 -1
  23. package/dist/dashboard/client/assets/{diagram-QEK2KX5R-D9j9H13n.js → diagram-QEK2KX5R-2Fmc2o5x.js} +1 -1
  24. package/dist/dashboard/client/assets/{diagram-S2PKOQOG-SMF5SB0K.js → diagram-S2PKOQOG-5WE8f0p7.js} +1 -1
  25. package/dist/dashboard/client/assets/{erDiagram-Q2GNP2WA-EVJ4Qa2F.js → erDiagram-Q2GNP2WA-DD-iXWd_.js} +1 -1
  26. package/dist/dashboard/client/assets/{flowDiagram-NV44I4VS-tZ7SFE77.js → flowDiagram-NV44I4VS-CCWo8Ue9.js} +1 -1
  27. package/dist/dashboard/client/assets/{ganttDiagram-JELNMOA3-DFSqguY7.js → ganttDiagram-JELNMOA3-CNY4d5UK.js} +1 -1
  28. package/dist/dashboard/client/assets/{gitGraphDiagram-V2S2FVAM-CqHdP3HE.js → gitGraphDiagram-V2S2FVAM-Dq5SBEJJ.js} +1 -1
  29. package/dist/dashboard/client/assets/{graph-C0XnkNkk.js → graph-BTt9lokK.js} +1 -1
  30. package/dist/dashboard/client/assets/index-B0k81q2b.js +581 -0
  31. package/dist/dashboard/client/assets/index-Czwdh6UA.css +1 -0
  32. package/dist/dashboard/client/assets/{infoDiagram-HS3SLOUP-DlXZo9U2.js → infoDiagram-HS3SLOUP-AnKZja-G.js} +1 -1
  33. package/dist/dashboard/client/assets/{journeyDiagram-XKPGCS4Q-CgC8_7eN.js → journeyDiagram-XKPGCS4Q-nC-_WjPN.js} +1 -1
  34. package/dist/dashboard/client/assets/{kanban-definition-3W4ZIXB7-BMAw_jNp.js → kanban-definition-3W4ZIXB7-BEY73sWU.js} +1 -1
  35. package/dist/dashboard/client/assets/{layout-XjM3Q-ka.js → layout-D4DfNpzH.js} +1 -1
  36. package/dist/dashboard/client/assets/{linear-CMUrrr1X.js → linear-ZpGvKjeP.js} +1 -1
  37. package/dist/dashboard/client/assets/{mermaid-renderer-D2jYNs7K.js → mermaid-renderer-BCDxmS9g.js} +4 -4
  38. package/dist/dashboard/client/assets/{mindmap-definition-VGOIOE7T-CL4hv-vg.js → mindmap-definition-VGOIOE7T-MzAaKESA.js} +1 -1
  39. package/dist/dashboard/client/assets/{pieDiagram-ADFJNKIX-DTqv-1h1.js → pieDiagram-ADFJNKIX-B_X1kySF.js} +1 -1
  40. package/dist/dashboard/client/assets/{quadrantDiagram-AYHSOK5B-BpFlSW9N.js → quadrantDiagram-AYHSOK5B-CMoIEMLN.js} +1 -1
  41. package/dist/dashboard/client/assets/{requirementDiagram-UZGBJVZJ-BqYqqXL4.js → requirementDiagram-UZGBJVZJ-v4CRsn1w.js} +1 -1
  42. package/dist/dashboard/client/assets/{sankeyDiagram-TZEHDZUN-kEI9kntR.js → sankeyDiagram-TZEHDZUN-CPcyN8Jj.js} +1 -1
  43. package/dist/dashboard/client/assets/{sequenceDiagram-WL72ISMW-Cnu_1j-N.js → sequenceDiagram-WL72ISMW-CTg0Vx1H.js} +1 -1
  44. package/dist/dashboard/client/assets/{stateDiagram-FKZM4ZOC-BoC-rqoG.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-CXMWuzDL.js → timeline-definition-IT6M3QCI-B8xFcSGb.js} +1 -1
  47. package/dist/dashboard/client/assets/{treemap-GDKQZRPO-o9ZFgpbJ.js → treemap-GDKQZRPO-HQQuGl9w.js} +1 -1
  48. package/dist/dashboard/client/assets/{xychartDiagram-PRI3JC2R-CfIuUpeA.js → xychartDiagram-PRI3JC2R-Drz0SW3I.js} +1 -1
  49. package/dist/dashboard/client/index.html +2 -2
  50. package/dist/dashboard/server.js +1085 -632
  51. package/dist/index.js +1395 -383
  52. package/package.json +6 -39
  53. package/dist/dashboard/client/assets/channel-rgw7C1e7.js +0 -1
  54. package/dist/dashboard/client/assets/classDiagram-2ON5EDUG-DTGi7d9X.js +0 -1
  55. package/dist/dashboard/client/assets/classDiagram-v2-WZHVMYZB-DTGi7d9X.js +0 -1
  56. package/dist/dashboard/client/assets/clone-Cz7hswqi.js +0 -1
  57. package/dist/dashboard/client/assets/index-C3NEq704.js +0 -571
  58. package/dist/dashboard/client/assets/index-CzxeSSaQ.css +0 -1
  59. package/dist/dashboard/client/assets/stateDiagram-v2-4FDKWEC3-COR3QD3v.js +0 -1
  60. package/dist/lib/db/index.js +0 -2177
  61. package/dist/lib/models.js +0 -85
  62. package/dist/lib/runtime-config.js +0 -55
  63. package/dist/lib/state/index.js +0 -2196
  64. package/dist/lib/team-config.js +0 -175
  65. 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,29 +16017,688 @@ var require_emoji_regex2 = __commonJS({
16017
16017
  }
16018
16018
  });
16019
16019
 
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
16522
+ function execBinary(binary, args, opts) {
16523
+ const result = import_cross_spawn.default.sync(binary, args, {
16524
+ maxBuffer: DEFAULT_MAX_BUFFER,
16525
+ ...opts
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;
16545
+ }
16546
+ async function execBinaryAsync(binary, args, opts) {
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, {
16614
+ ...opts,
16615
+ windowsHide: true
16616
+ });
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
+
16020
16688
  // ../shared/platform/src/index.ts
16021
16689
  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
16690
  async function importModule(absolutePath) {
16029
16691
  return import(pathToFileURL(absolutePath).href);
16030
16692
  }
16031
- function execBinary(binary, args, opts) {
16032
- return execFileSync(binary, args, {
16033
- ...opts,
16034
- shell: isWindows
16035
- });
16693
+ function killErrorMeansDead(err) {
16694
+ return err instanceof Error && "code" in err && err.code === "ESRCH";
16036
16695
  }
16037
16696
  function isProcessAlive(pid) {
16038
16697
  try {
16039
16698
  process.kill(pid, 0);
16040
16699
  return true;
16041
16700
  } catch (err) {
16042
- return !(err instanceof Error && "code" in err && err.code === "ESRCH");
16701
+ return !killErrorMeansDead(err);
16043
16702
  }
16044
16703
  }
16045
16704
  function defaultIconFor(id, tier) {
@@ -16048,11 +16707,13 @@ function defaultIconFor(id, tier) {
16048
16707
  function hostCapabilitiesFor(vendor) {
16049
16708
  return vendor && HOST_CAPABILITIES[vendor] || DEFAULT_HOST_CAPABILITIES;
16050
16709
  }
16051
- var execFilePromise, isWindows, BUILTIN_ICON_MAP, DEFAULT_HOST_CAPABILITIES, HOST_CAPABILITIES;
16710
+ var isWindows, BUILTIN_ICON_MAP, DEFAULT_HOST_CAPABILITIES, HOST_CAPABILITIES;
16052
16711
  var init_src = __esm({
16053
16712
  "../shared/platform/src/index.ts"() {
16054
16713
  "use strict";
16055
- execFilePromise = promisify(execFile);
16714
+ init_spawn();
16715
+ init_verdict();
16716
+ init_counts();
16056
16717
  isWindows = process.platform === "win32";
16057
16718
  BUILTIN_ICON_MAP = {
16058
16719
  architect: "blocks",
@@ -23393,31 +24054,7 @@ var require_dist = __commonJS({
23393
24054
  }
23394
24055
  });
23395
24056
 
23396
- // src/lib/db/result-mapper.ts
23397
- function resultToRows(result) {
23398
- if (result.length === 0 || !result[0]) {
23399
- return [];
23400
- }
23401
- const { columns, values } = result[0];
23402
- return values.map((row) => {
23403
- const obj = {};
23404
- for (let i = 0; i < columns.length; i++) {
23405
- obj[columns[i]] = row[i];
23406
- }
23407
- return obj;
23408
- });
23409
- }
23410
- function resultToRow(result) {
23411
- const rows = resultToRows(result);
23412
- return rows[0];
23413
- }
23414
- var init_result_mapper = __esm({
23415
- "src/lib/db/result-mapper.ts"() {
23416
- "use strict";
23417
- }
23418
- });
23419
-
23420
- // src/lib/db/engine.ts
24057
+ // ../shared/persistence/src/db/engine.ts
23421
24058
  import { createRequire as createRequire2 } from "node:module";
23422
24059
  function applyEnginePreconditions() {
23423
24060
  if (_preconditionsApplied) return;
@@ -23471,7 +24108,7 @@ function openEngine(dbPath) {
23471
24108
  }
23472
24109
  var SQLITE_BUSY, SQLITE_BUSY_SNAPSHOT, BUSY_RETRY_ATTEMPTS, BUSY_RETRY_BACKOFF_MS, savepointName, nodeRequire, _preconditionsApplied, _DatabaseSyncCtor, SLEEP_BUF, NodeSqliteAdapter;
23473
24110
  var init_engine = __esm({
23474
- "src/lib/db/engine.ts"() {
24111
+ "../shared/persistence/src/db/engine.ts"() {
23475
24112
  "use strict";
23476
24113
  init_runtime_checks();
23477
24114
  SQLITE_BUSY = 5;
@@ -23598,7 +24235,7 @@ var init_engine = __esm({
23598
24235
  }
23599
24236
  });
23600
24237
 
23601
- // src/lib/db/migrations.ts
24238
+ // ../shared/persistence/src/db/migrations.ts
23602
24239
  function columnExists(db, table, column) {
23603
24240
  const result = db.exec(`PRAGMA table_info(${table})`);
23604
24241
  const first = result[0];
@@ -23653,7 +24290,7 @@ function runMigrations(db) {
23653
24290
  }
23654
24291
  var MIGRATIONS;
23655
24292
  var init_migrations = __esm({
23656
- "src/lib/db/migrations.ts"() {
24293
+ "../shared/persistence/src/db/migrations.ts"() {
23657
24294
  "use strict";
23658
24295
  MIGRATIONS = [
23659
24296
  {
@@ -24152,7 +24789,31 @@ var init_migrations = __esm({
24152
24789
  }
24153
24790
  });
24154
24791
 
24155
- // 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
24156
24817
  function insertSession(db, params) {
24157
24818
  const {
24158
24819
  id,
@@ -24267,15 +24928,15 @@ function commitReasonClose(db, sessionId, reasonEvent, projectionUpdates) {
24267
24928
  });
24268
24929
  }
24269
24930
  var init_queries = __esm({
24270
- "src/lib/db/queries.ts"() {
24931
+ "../shared/persistence/src/db/queries.ts"() {
24271
24932
  "use strict";
24272
24933
  init_result_mapper();
24273
24934
  }
24274
24935
  });
24275
24936
 
24276
- // src/lib/db/reconcile.ts
24277
- import { existsSync as existsSync10 } from "node:fs";
24278
- 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";
24279
24940
  function hasTerminalArtifactEvent(db, sessionId, workflowType, currentRound, currentMapRun) {
24280
24941
  const eventType = workflowType === "map" ? "map_completed" : "round_completed";
24281
24942
  const round = workflowType === "map" ? currentMapRun : currentRound;
@@ -24316,7 +24977,7 @@ function hasInFlightDependents(db, sessionId) {
24316
24977
  function resolveSessionDir(ocrDir, sessionDir) {
24317
24978
  if (!sessionDir) return null;
24318
24979
  if (isAbsolute2(sessionDir)) return sessionDir;
24319
- return join12(dirname5(ocrDir), sessionDir);
24980
+ return join9(dirname5(ocrDir), sessionDir);
24320
24981
  }
24321
24982
  function reconcileLegacyState(db, ocrDir, opts = {}) {
24322
24983
  const dryRun = opts.dryRun ?? false;
@@ -24328,8 +24989,8 @@ function reconcileLegacyState(db, ocrDir, opts = {}) {
24328
24989
  if (hasTerminalArtifactEvent(db, s.id, s.workflow_type, s.current_round, s.current_map_run) || hasReasonEvent(db, s.id)) {
24329
24990
  continue;
24330
24991
  }
24331
- const reviewFinal = s.workflow_type === "review" && dir ? existsSync10(join12(dir, "rounds", `round-${s.current_round}`, "final.md")) : false;
24332
- 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;
24333
24994
  if (reviewFinal) {
24334
24995
  actions.push({
24335
24996
  sessionId: s.id,
@@ -24405,20 +25066,20 @@ function reconcileLegacyState(db, ocrDir, opts = {}) {
24405
25066
  }
24406
25067
  var DEFAULT_STALE_THRESHOLD_SECONDS;
24407
25068
  var init_reconcile = __esm({
24408
- "src/lib/db/reconcile.ts"() {
25069
+ "../shared/persistence/src/db/reconcile.ts"() {
24409
25070
  "use strict";
24410
25071
  init_queries();
24411
25072
  DEFAULT_STALE_THRESHOLD_SECONDS = 7 * 24 * 60 * 60;
24412
25073
  }
24413
25074
  });
24414
25075
 
24415
- // src/lib/db/liveness.ts
25076
+ // ../shared/persistence/src/db/liveness.ts
24416
25077
  function defaultIsAlive(pid) {
24417
25078
  try {
24418
25079
  process.kill(pid, 0);
24419
25080
  return true;
24420
25081
  } catch (err) {
24421
- return !(err instanceof Error && "code" in err && err.code === "ESRCH");
25082
+ return !killErrorMeansDead(err);
24422
25083
  }
24423
25084
  }
24424
25085
  function sqliteUtcMs(ts) {
@@ -24427,16 +25088,17 @@ function sqliteUtcMs(ts) {
24427
25088
  }
24428
25089
  var PID_REUSE_GUARD_MS;
24429
25090
  var init_liveness = __esm({
24430
- "src/lib/db/liveness.ts"() {
25091
+ "../shared/persistence/src/db/liveness.ts"() {
24431
25092
  "use strict";
25093
+ init_src();
24432
25094
  PID_REUSE_GUARD_MS = 24 * 60 * 60 * 1e3;
24433
25095
  }
24434
25096
  });
24435
25097
 
24436
- // src/lib/state/exit-codes.ts
25098
+ // ../shared/persistence/src/state/exit-codes.ts
24437
25099
  var STATE_EXIT, StateError, CANCELLED_EXIT_CODE, ORPHAN_EXIT_CODE, CASCADE_CLOSE_EXIT_CODE, WATCHDOG_DEADLINE_EXIT_CODE;
24438
25100
  var init_exit_codes = __esm({
24439
- "src/lib/state/exit-codes.ts"() {
25101
+ "../shared/persistence/src/state/exit-codes.ts"() {
24440
25102
  "use strict";
24441
25103
  STATE_EXIT = {
24442
25104
  OK: 0,
@@ -24463,7 +25125,7 @@ var init_exit_codes = __esm({
24463
25125
  }
24464
25126
  });
24465
25127
 
24466
- // src/lib/db/agent-sessions.ts
25128
+ // ../shared/persistence/src/db/agent-sessions.ts
24467
25129
  function cascadeTerminateExecutions(db, workflowId, exitCode, note) {
24468
25130
  db.run(
24469
25131
  `UPDATE command_executions
@@ -24634,6 +25296,9 @@ function bindVendorSessionIdOpportunistically(db, vendorSessionId) {
24634
25296
  );
24635
25297
  return candidate.uid ?? String(candidate.id);
24636
25298
  }
25299
+ function isSafeVendorSessionId(id) {
25300
+ return SAFE_VENDOR_SESSION_ID.test(id);
25301
+ }
24637
25302
  function recordVendorSessionIdForExecution(db, executionId, vendorSessionId) {
24638
25303
  db.run(
24639
25304
  `UPDATE command_executions
@@ -24829,9 +25494,9 @@ function sweepStaleSessions(db, thresholdSeconds) {
24829
25494
  }
24830
25495
  return { closedSessionIds: rows.map((r) => r.id) };
24831
25496
  }
24832
- var NOTE_ORPHAN_PREFIX, INSTANCE_COMMAND;
25497
+ var NOTE_ORPHAN_PREFIX, INSTANCE_COMMAND, SAFE_VENDOR_SESSION_ID;
24833
25498
  var init_agent_sessions = __esm({
24834
- "src/lib/db/agent-sessions.ts"() {
25499
+ "../shared/persistence/src/db/agent-sessions.ts"() {
24835
25500
  "use strict";
24836
25501
  init_result_mapper();
24837
25502
  init_queries();
@@ -24839,18 +25504,19 @@ var init_agent_sessions = __esm({
24839
25504
  init_exit_codes();
24840
25505
  NOTE_ORPHAN_PREFIX = "orphaned by liveness sweep";
24841
25506
  INSTANCE_COMMAND = "session-instance";
25507
+ SAFE_VENDOR_SESSION_ID = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,255}$/;
24842
25508
  }
24843
25509
  });
24844
25510
 
24845
- // src/lib/db/maintenance.ts
25511
+ // ../shared/persistence/src/db/maintenance.ts
24846
25512
  import {
24847
- existsSync as existsSync11,
24848
- readdirSync as readdirSync5,
25513
+ existsSync as existsSync8,
25514
+ readdirSync as readdirSync2,
24849
25515
  statSync,
24850
25516
  unlinkSync as unlinkSync3,
24851
25517
  copyFileSync
24852
25518
  } from "node:fs";
24853
- 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";
24854
25520
  function withForeignKeysDisabled(db, fn) {
24855
25521
  db.pragma("foreign_keys = OFF");
24856
25522
  try {
@@ -24877,7 +25543,7 @@ function foreignKeyViolationGroups(db) {
24877
25543
  function scanOrphanTempFiles(dataDir) {
24878
25544
  let entries;
24879
25545
  try {
24880
- entries = readdirSync5(dataDir);
25546
+ entries = readdirSync2(dataDir);
24881
25547
  } catch {
24882
25548
  return [];
24883
25549
  }
@@ -24888,7 +25554,7 @@ function scanOrphanTempFiles(dataDir) {
24888
25554
  const pid = Number(m[1]);
24889
25555
  let ageMs = 0;
24890
25556
  try {
24891
- ageMs = Date.now() - statSync(join13(dataDir, name)).mtimeMs;
25557
+ ageMs = Date.now() - statSync(join10(dataDir, name)).mtimeMs;
24892
25558
  } catch {
24893
25559
  continue;
24894
25560
  }
@@ -24907,7 +25573,7 @@ function scanOrphanTempFiles(dataDir) {
24907
25573
  function scanBackupFiles(dataDir, dbBase) {
24908
25574
  let entries;
24909
25575
  try {
24910
- entries = readdirSync5(dataDir);
25576
+ entries = readdirSync2(dataDir);
24911
25577
  } catch {
24912
25578
  return [];
24913
25579
  }
@@ -24915,7 +25581,7 @@ function scanBackupFiles(dataDir, dbBase) {
24915
25581
  for (const name of entries) {
24916
25582
  if (!name.startsWith(`${dbBase}.bak`)) continue;
24917
25583
  try {
24918
- out.push({ name, sizeBytes: statSync(join13(dataDir, name)).size });
25584
+ out.push({ name, sizeBytes: statSync(join10(dataDir, name)).size });
24919
25585
  } catch {
24920
25586
  }
24921
25587
  }
@@ -24923,7 +25589,7 @@ function scanBackupFiles(dataDir, dbBase) {
24923
25589
  }
24924
25590
  function collectDbHealth(db, dbPath) {
24925
25591
  const dataDir = dirname6(dbPath);
24926
- const dbBase = basename7(dbPath);
25592
+ const dbBase = basename3(dbPath);
24927
25593
  const pageSize = scalarInt(db, "PRAGMA page_size");
24928
25594
  const pageCount = scalarInt(db, "PRAGMA page_count");
24929
25595
  const freelistCount = scalarInt(db, "PRAGMA freelist_count");
@@ -24935,7 +25601,7 @@ function collectDbHealth(db, dbPath) {
24935
25601
  const protectedFkViolations = allGroups.filter(
24936
25602
  (g) => PROTECTED_TABLES.has(g.table)
24937
25603
  );
24938
- const fileSizeBytes = existsSync11(dbPath) ? statSync(dbPath).size : 0;
25604
+ const fileSizeBytes = existsSync8(dbPath) ? statSync(dbPath).size : 0;
24939
25605
  return {
24940
25606
  dbPath,
24941
25607
  fileSizeBytes,
@@ -24963,7 +25629,7 @@ function collectDbHealth(db, dbPath) {
24963
25629
  }
24964
25630
  function snapshotDb(db, dbPath, label = "doctor") {
24965
25631
  try {
24966
- if (!existsSync11(dbPath) || statSync(dbPath).size === 0) return null;
25632
+ if (!existsSync8(dbPath) || statSync(dbPath).size === 0) return null;
24967
25633
  db.pragma("wal_checkpoint(TRUNCATE)");
24968
25634
  const ts = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
24969
25635
  const bakPath = `${dbPath}.bak.${label}.${ts}`;
@@ -24978,7 +25644,7 @@ function reapOrphanDbFiles(dataDir) {
24978
25644
  for (const f of scanOrphanTempFiles(dataDir)) {
24979
25645
  if (!f.reapable) continue;
24980
25646
  try {
24981
- unlinkSync3(join13(dataDir, f.name));
25647
+ unlinkSync3(join10(dataDir, f.name));
24982
25648
  reaped.push(f.name);
24983
25649
  } catch {
24984
25650
  }
@@ -24988,7 +25654,7 @@ function reapOrphanDbFiles(dataDir) {
24988
25654
  function reapStaleExecLogs(execLogsDir, maxAgeMs = SEVEN_DAYS_MS) {
24989
25655
  let entries;
24990
25656
  try {
24991
- entries = readdirSync5(execLogsDir);
25657
+ entries = readdirSync2(execLogsDir);
24992
25658
  } catch {
24993
25659
  return [];
24994
25660
  }
@@ -24996,7 +25662,7 @@ function reapStaleExecLogs(execLogsDir, maxAgeMs = SEVEN_DAYS_MS) {
24996
25662
  const reaped = [];
24997
25663
  for (const name of entries) {
24998
25664
  if (!name.endsWith(".log")) continue;
24999
- const full = join13(execLogsDir, name);
25665
+ const full = join10(execLogsDir, name);
25000
25666
  try {
25001
25667
  if (statSync(full).mtimeMs > cutoff) continue;
25002
25668
  unlinkSync3(full);
@@ -25014,11 +25680,11 @@ function pruneBackups(dataDir, dbPath, opts = {}) {
25014
25680
  );
25015
25681
  }
25016
25682
  const dryRun = opts.dryRun ?? false;
25017
- const dbBase = basename7(dbPath);
25683
+ const dbBase = basename3(dbPath);
25018
25684
  const withMtime = [];
25019
25685
  for (const file of scanBackupFiles(dataDir, dbBase)) {
25020
25686
  try {
25021
- withMtime.push({ file, mtimeMs: statSync(join13(dataDir, file.name)).mtimeMs });
25687
+ withMtime.push({ file, mtimeMs: statSync(join10(dataDir, file.name)).mtimeMs });
25022
25688
  } catch {
25023
25689
  }
25024
25690
  }
@@ -25029,7 +25695,7 @@ function pruneBackups(dataDir, dbPath, opts = {}) {
25029
25695
  if (!dryRun) {
25030
25696
  for (const b of toDelete) {
25031
25697
  try {
25032
- unlinkSync3(join13(dataDir, b.name));
25698
+ unlinkSync3(join10(dataDir, b.name));
25033
25699
  deleted.push(b);
25034
25700
  } catch {
25035
25701
  }
@@ -25045,7 +25711,7 @@ function pruneBackups(dataDir, dbPath, opts = {}) {
25045
25711
  }
25046
25712
  function fixDb(db, dbPath, opts = {}) {
25047
25713
  const dataDir = dirname6(dbPath);
25048
- const sizeBeforeBytes = existsSync11(dbPath) ? statSync(dbPath).size : 0;
25714
+ const sizeBeforeBytes = existsSync8(dbPath) ? statSync(dbPath).size : 0;
25049
25715
  const snapshotPath = opts.snapshot === false ? null : snapshotDb(db, dbPath, "doctor");
25050
25716
  const fkOrphansDeleted = [];
25051
25717
  withForeignKeysDisabled(db, () => {
@@ -25089,12 +25755,12 @@ function fixDb(db, dbPath, opts = {}) {
25089
25755
  };
25090
25756
  }
25091
25757
  function vacuumDb(db, dbPath, opts = {}) {
25092
- const sizeBeforeBytes = existsSync11(dbPath) ? statSync(dbPath).size : 0;
25758
+ const sizeBeforeBytes = existsSync8(dbPath) ? statSync(dbPath).size : 0;
25093
25759
  const snapshotPath = opts.snapshot === false ? null : snapshotDb(db, dbPath, "vacuum");
25094
25760
  db.pragma("wal_checkpoint(TRUNCATE)");
25095
25761
  db.run("VACUUM");
25096
25762
  db.pragma("wal_checkpoint(TRUNCATE)");
25097
- const sizeAfterBytes = existsSync11(dbPath) ? statSync(dbPath).size : 0;
25763
+ const sizeAfterBytes = existsSync8(dbPath) ? statSync(dbPath).size : 0;
25098
25764
  return {
25099
25765
  snapshotPath,
25100
25766
  sizeBeforeBytes,
@@ -25174,7 +25840,7 @@ function pruneDb(db, dbPath, opts = {}) {
25174
25840
  }
25175
25841
  var PROTECTED_TABLES, ORPHAN_SWEEPS, MARKDOWN_DEDUP_SQL, ONE_HOUR_MS, SEVEN_DAYS_MS;
25176
25842
  var init_maintenance = __esm({
25177
- "src/lib/db/maintenance.ts"() {
25843
+ "../shared/persistence/src/db/maintenance.ts"() {
25178
25844
  "use strict";
25179
25845
  init_src();
25180
25846
  PROTECTED_TABLES = /* @__PURE__ */ new Set([
@@ -25249,24 +25915,24 @@ var init_maintenance = __esm({
25249
25915
  }
25250
25916
  });
25251
25917
 
25252
- // src/lib/db/command-log.ts
25253
- import { appendFileSync, existsSync as existsSync12, mkdirSync as mkdirSync4, readFileSync as readFileSync9, renameSync, writeFileSync as writeFileSync6 } from "node:fs";
25254
- import { dirname as dirname7, join as join14 } from "node:path";
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";
25255
25921
  import { randomUUID as randomUUID2 } from "node:crypto";
25256
25922
  function generateCommandUid() {
25257
25923
  return randomUUID2();
25258
25924
  }
25259
25925
  function cacheDir(ocrDir) {
25260
- return join14(ocrDir, "data", CACHE_DIR);
25926
+ return join11(ocrDir, "data", CACHE_DIR);
25261
25927
  }
25262
25928
  function commandLogPath(ocrDir) {
25263
- return join14(cacheDir(ocrDir), FILENAME);
25929
+ return join11(cacheDir(ocrDir), FILENAME);
25264
25930
  }
25265
25931
  function appendCommandLog(ocrDir, entry) {
25266
25932
  try {
25267
25933
  const filePath = commandLogPath(ocrDir);
25268
25934
  const dir = dirname7(filePath);
25269
- if (!existsSync12(dir)) mkdirSync4(dir, { recursive: true });
25935
+ if (!existsSync9(dir)) mkdirSync4(dir, { recursive: true });
25270
25936
  const line = JSON.stringify(entry) + "\n";
25271
25937
  appendFileSync(filePath, line, { encoding: "utf-8" });
25272
25938
  if (approxLineCount >= 0) approxLineCount++;
@@ -25276,8 +25942,8 @@ function appendCommandLog(ocrDir, entry) {
25276
25942
  }
25277
25943
  function readCommandLog(ocrDir) {
25278
25944
  const filePath = commandLogPath(ocrDir);
25279
- if (!existsSync12(filePath)) return [];
25280
- const content = readFileSync9(filePath, "utf-8");
25945
+ if (!existsSync9(filePath)) return [];
25946
+ const content = readFileSync7(filePath, "utf-8");
25281
25947
  const entries = [];
25282
25948
  for (const line of content.split("\n")) {
25283
25949
  if (!line.trim()) continue;
@@ -25328,7 +25994,7 @@ function replayCommandLog(db, ocrDir) {
25328
25994
  function rotateIfNeeded(filePath) {
25329
25995
  try {
25330
25996
  if (approxLineCount >= 0 && approxLineCount <= MAX_LINES) return;
25331
- const content = readFileSync9(filePath, "utf-8");
25997
+ const content = readFileSync7(filePath, "utf-8");
25332
25998
  const lines = content.split("\n").filter((l) => l.trim());
25333
25999
  approxLineCount = lines.length;
25334
26000
  if (approxLineCount <= MAX_LINES) return;
@@ -25342,7 +26008,7 @@ function rotateIfNeeded(filePath) {
25342
26008
  }
25343
26009
  var CACHE_DIR, FILENAME, MAX_LINES, KEEP_LINES, approxLineCount;
25344
26010
  var init_command_log = __esm({
25345
- "src/lib/db/command-log.ts"() {
26011
+ "../shared/persistence/src/db/command-log.ts"() {
25346
26012
  "use strict";
25347
26013
  CACHE_DIR = ".cache";
25348
26014
  FILENAME = "command-history.jsonl";
@@ -25352,7 +26018,7 @@ var init_command_log = __esm({
25352
26018
  }
25353
26019
  });
25354
26020
 
25355
- // src/lib/db/index.ts
26021
+ // ../shared/persistence/src/db/index.ts
25356
26022
  var db_exports = {};
25357
26023
  __export(db_exports, {
25358
26024
  CANCELLED_EXIT_CODE: () => CANCELLED_EXIT_CODE,
@@ -25360,6 +26026,7 @@ __export(db_exports, {
25360
26026
  MIGRATIONS: () => MIGRATIONS,
25361
26027
  ORPHAN_EXIT_CODE: () => ORPHAN_EXIT_CODE,
25362
26028
  PID_REUSE_GUARD_MS: () => PID_REUSE_GUARD_MS,
26029
+ SAFE_VENDOR_SESSION_ID: () => SAFE_VENDOR_SESSION_ID,
25363
26030
  STATE_EXIT: () => STATE_EXIT,
25364
26031
  StateError: () => StateError,
25365
26032
  WATCHDOG_DEADLINE_EXIT_CODE: () => WATCHDOG_DEADLINE_EXIT_CODE,
@@ -25392,6 +26059,7 @@ __export(db_exports, {
25392
26059
  insertEvent: () => insertEvent,
25393
26060
  insertSession: () => insertSession,
25394
26061
  isBusyError: () => isBusyError,
26062
+ isSafeVendorSessionId: () => isSafeVendorSessionId,
25395
26063
  linkDashboardInvocationToWorkflow: () => linkDashboardInvocationToWorkflow,
25396
26064
  listAgentSessionsForWorkflow: () => listAgentSessionsForWorkflow,
25397
26065
  openDatabase: () => openDatabase,
@@ -25422,7 +26090,7 @@ __export(db_exports, {
25422
26090
  withForeignKeysDisabled: () => withForeignKeysDisabled
25423
26091
  });
25424
26092
  import {
25425
- existsSync as existsSync13,
26093
+ existsSync as existsSync10,
25426
26094
  mkdirSync as mkdirSync5,
25427
26095
  copyFileSync as copyFileSync2,
25428
26096
  statSync as statSync2,
@@ -25430,13 +26098,13 @@ import {
25430
26098
  rmSync
25431
26099
  } from "node:fs";
25432
26100
  import { tmpdir } from "node:os";
25433
- import { dirname as dirname8, join as join15 } from "node:path";
26101
+ import { dirname as dirname8, join as join12 } from "node:path";
25434
26102
  function maybeSnapshotBeforeUpgrade(db, dbPath, fromVersion) {
25435
26103
  if (fromVersion < 1 || fromVersion >= V2_SCHEMA_VERSION) return null;
25436
26104
  const bakPath = `${dbPath}.bak.v${fromVersion}`;
25437
- if (existsSync13(bakPath)) return bakPath;
26105
+ if (existsSync10(bakPath)) return bakPath;
25438
26106
  try {
25439
- if (!existsSync13(dbPath) || statSync2(dbPath).size === 0) return null;
26107
+ if (!existsSync10(dbPath) || statSync2(dbPath).size === 0) return null;
25440
26108
  db.pragma("wal_checkpoint(TRUNCATE)");
25441
26109
  copyFileSync2(dbPath, bakPath);
25442
26110
  return bakPath;
@@ -25472,7 +26140,7 @@ async function openDatabase(dbPath) {
25472
26140
  return cached;
25473
26141
  }
25474
26142
  const dir = dirname8(dbPath);
25475
- if (!existsSync13(dir)) {
26143
+ if (!existsSync10(dir)) {
25476
26144
  mkdirSync5(dir, { recursive: true });
25477
26145
  }
25478
26146
  const db = openEngine(dbPath);
@@ -25480,15 +26148,15 @@ async function openDatabase(dbPath) {
25480
26148
  return db;
25481
26149
  }
25482
26150
  async function getDb(ocrDir) {
25483
- const dbPath = join15(ocrDir, "data", "ocr.db");
26151
+ const dbPath = join12(ocrDir, "data", "ocr.db");
25484
26152
  return openDatabase(dbPath);
25485
26153
  }
25486
26154
  async function ensureDatabase(ocrDir) {
25487
- const dataDir = join15(ocrDir, "data");
25488
- if (!existsSync13(dataDir)) {
26155
+ const dataDir = join12(ocrDir, "data");
26156
+ if (!existsSync10(dataDir)) {
25489
26157
  mkdirSync5(dataDir, { recursive: true });
25490
26158
  }
25491
- const dbPath = join15(dataDir, "ocr.db");
26159
+ const dbPath = join12(dataDir, "ocr.db");
25492
26160
  const db = await openDatabase(dbPath);
25493
26161
  let before = 0;
25494
26162
  try {
@@ -25516,7 +26184,7 @@ async function ensureDatabase(ocrDir) {
25516
26184
  return db;
25517
26185
  }
25518
26186
  function walCheckpointTruncate(dbPath) {
25519
- if (!existsSync13(dbPath)) {
26187
+ if (!existsSync10(dbPath)) {
25520
26188
  return "skipped";
25521
26189
  }
25522
26190
  const cached = connections.get(dbPath);
@@ -25558,8 +26226,8 @@ function closeAllDatabases() {
25558
26226
  function probeWrite() {
25559
26227
  let dir;
25560
26228
  try {
25561
- dir = mkdtempSync(join15(tmpdir(), "ocr-probe-"));
25562
- const db = openEngine(join15(dir, "probe.db"));
26229
+ dir = mkdtempSync(join12(tmpdir(), "ocr-probe-"));
26230
+ const db = openEngine(join12(dir, "probe.db"));
25563
26231
  try {
25564
26232
  db.run("CREATE TABLE _probe_write (id INTEGER PRIMARY KEY, v TEXT)");
25565
26233
  db.transaction(() => {
@@ -25592,7 +26260,7 @@ function rmDirBestEffort(dir) {
25592
26260
  }
25593
26261
  var V2_SCHEMA_VERSION, connections;
25594
26262
  var init_db = __esm({
25595
- "src/lib/db/index.ts"() {
26263
+ "../shared/persistence/src/db/index.ts"() {
25596
26264
  "use strict";
25597
26265
  init_engine();
25598
26266
  init_migrations();
@@ -26361,7 +27029,7 @@ if (process.platform === "linux") {
26361
27029
  // ../../node_modules/.pnpm/signal-exit@4.1.0/node_modules/signal-exit/dist/mjs/index.js
26362
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";
26363
27031
  var kExitEmitter = Symbol.for("signal-exit emitter");
26364
- var global = globalThis;
27032
+ var global2 = globalThis;
26365
27033
  var ObjectDefineProperty = Object.defineProperty.bind(Object);
26366
27034
  var Emitter = class {
26367
27035
  emitted = {
@@ -26375,10 +27043,10 @@ var Emitter = class {
26375
27043
  count = 0;
26376
27044
  id = Math.random();
26377
27045
  constructor() {
26378
- if (global[kExitEmitter]) {
26379
- return global[kExitEmitter];
27046
+ if (global2[kExitEmitter]) {
27047
+ return global2[kExitEmitter];
26380
27048
  }
26381
- ObjectDefineProperty(global, kExitEmitter, {
27049
+ ObjectDefineProperty(global2, kExitEmitter, {
26382
27050
  value: this,
26383
27051
  writable: false,
26384
27052
  enumerable: false,
@@ -29352,7 +30020,7 @@ function ensureGitignore(ocrDir) {
29352
30020
  writeFileSync2(gitignorePath, content);
29353
30021
  }
29354
30022
 
29355
- // src/lib/team-config.ts
30023
+ // ../shared/config/src/team-config.ts
29356
30024
  var import_yaml = __toESM(require_dist(), 1);
29357
30025
  import { existsSync as existsSync2, readFileSync as readFileSync3 } from "node:fs";
29358
30026
  import { join as join2 } from "node:path";
@@ -29397,6 +30065,9 @@ function parseTeamConfigYaml(content) {
29397
30065
  aliases,
29398
30066
  defaultModel
29399
30067
  );
30068
+ if (resolvedModel !== null) {
30069
+ assertSafeModelId(resolvedModel, `default_team.${persona}[${i}]`);
30070
+ }
29400
30071
  team.push({
29401
30072
  persona,
29402
30073
  instance_index: i + 1,
@@ -29477,6 +30148,16 @@ function readOptionalString(obj, key, pathLabel) {
29477
30148
  }
29478
30149
  return value;
29479
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
+ }
29480
30161
  function readAliases(root) {
29481
30162
  const models = root["models"];
29482
30163
  if (!models || typeof models !== "object" || Array.isArray(models)) return {};
@@ -29518,6 +30199,9 @@ function resolveTeamComposition(team, override) {
29518
30199
  result.push(inst);
29519
30200
  }
29520
30201
  for (const inst of override) {
30202
+ if (inst.model !== null) {
30203
+ assertSafeModelId(inst.model, `override ${inst.persona}#${inst.instance_index}`);
30204
+ }
29521
30205
  result.push(inst);
29522
30206
  }
29523
30207
  return result;
@@ -30086,7 +30770,7 @@ ${hint}
30086
30770
  }
30087
30771
 
30088
30772
  // src/lib/version.ts
30089
- var CLI_VERSION = true ? "2.2.0" : createRequire(import.meta.url)("../../package.json").version;
30773
+ var CLI_VERSION = true ? "2.3.0" : createRequire(import.meta.url)("../../package.json").version;
30090
30774
 
30091
30775
  // src/lib/deps.ts
30092
30776
  init_src();
@@ -33020,12 +33704,12 @@ function getStrategy(workflowType) {
33020
33704
  }
33021
33705
 
33022
33706
  // src/lib/progress/detector.ts
33023
- import { existsSync as existsSync7, readdirSync as readdirSync2 } from "node:fs";
33024
- 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";
33025
33709
 
33026
33710
  // src/lib/progress/session-reader.ts
33027
- init_result_mapper();
33028
- import { basename as basename3 } from "node:path";
33711
+ init_db();
33712
+ import { basename as basename4 } from "node:path";
33029
33713
  var cachedDb = null;
33030
33714
  function setProgressDb(db) {
33031
33715
  cachedDb = db;
@@ -33044,7 +33728,7 @@ function readSessionState(sessionPath) {
33044
33728
  }
33045
33729
  }
33046
33730
  function readFromSqlite(sessionPath, db) {
33047
- const sessionId = basename3(sessionPath);
33731
+ const sessionId = basename4(sessionPath);
33048
33732
  let row = resultToRow(
33049
33733
  db.exec("SELECT * FROM sessions WHERE id = ?", [sessionId])
33050
33734
  );
@@ -33079,7 +33763,7 @@ function detectWorkflowType(sessionPath, explicitType) {
33079
33763
  const db = getProgressDb();
33080
33764
  if (db) {
33081
33765
  try {
33082
- const sessionId = basename4(sessionPath);
33766
+ const sessionId = basename5(sessionPath);
33083
33767
  const result = db.exec(
33084
33768
  "SELECT workflow_type FROM sessions WHERE id = ?",
33085
33769
  [sessionId]
@@ -33092,8 +33776,8 @@ function detectWorkflowType(sessionPath, explicitType) {
33092
33776
  } catch {
33093
33777
  }
33094
33778
  }
33095
- const hasMapDir = existsSync7(join9(sessionPath, "map"));
33096
- const hasRoundsDir = existsSync7(join9(sessionPath, "rounds"));
33779
+ const hasMapDir = existsSync11(join13(sessionPath, "map"));
33780
+ const hasRoundsDir = existsSync11(join13(sessionPath, "rounds"));
33097
33781
  if (hasMapDir && !hasRoundsDir) {
33098
33782
  return "map";
33099
33783
  }
@@ -33103,7 +33787,7 @@ function detectWorkflowType(sessionPath, explicitType) {
33103
33787
  if (hasMapDir && hasRoundsDir) {
33104
33788
  if (db) {
33105
33789
  try {
33106
- const sessionId = basename4(sessionPath);
33790
+ const sessionId = basename5(sessionPath);
33107
33791
  const result = db.exec(
33108
33792
  "SELECT current_phase FROM sessions WHERE id = ?",
33109
33793
  [sessionId]
@@ -33127,7 +33811,7 @@ function isSessionActive(sessionPath) {
33127
33811
  const db = getProgressDb();
33128
33812
  if (db) {
33129
33813
  try {
33130
- const sessionId = basename4(sessionPath);
33814
+ const sessionId = basename5(sessionPath);
33131
33815
  const result = db.exec(
33132
33816
  "SELECT status, current_phase FROM sessions WHERE id = ?",
33133
33817
  [sessionId]
@@ -33149,28 +33833,28 @@ function isSessionActive(sessionPath) {
33149
33833
  }
33150
33834
  function detectActiveWorkflows(sessionPath) {
33151
33835
  const activeWorkflows = [];
33152
- const hasRoundsDir = existsSync7(join9(sessionPath, "rounds"));
33836
+ const hasRoundsDir = existsSync11(join13(sessionPath, "rounds"));
33153
33837
  if (hasRoundsDir) {
33154
- const roundsDir = join9(sessionPath, "rounds");
33155
- 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() : [];
33156
33840
  if (rounds.length > 0) {
33157
33841
  const latestRound = rounds[rounds.length - 1];
33158
- const finalPath = join9(roundsDir, latestRound, "final.md");
33159
- if (!existsSync7(finalPath)) {
33842
+ const finalPath = join13(roundsDir, latestRound, "final.md");
33843
+ if (!existsSync11(finalPath)) {
33160
33844
  activeWorkflows.push("review");
33161
33845
  }
33162
33846
  } else {
33163
33847
  activeWorkflows.push("review");
33164
33848
  }
33165
33849
  }
33166
- const hasMapDir = existsSync7(join9(sessionPath, "map"));
33850
+ const hasMapDir = existsSync11(join13(sessionPath, "map"));
33167
33851
  if (hasMapDir) {
33168
- const runsDir = join9(sessionPath, "map", "runs");
33169
- 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() : [];
33170
33854
  if (runs.length > 0) {
33171
33855
  const latestRun = runs[runs.length - 1];
33172
- const mapPath = join9(runsDir, latestRun, "map.md");
33173
- if (!existsSync7(mapPath)) {
33856
+ const mapPath = join13(runsDir, latestRun, "map.md");
33857
+ if (!existsSync11(mapPath)) {
33174
33858
  activeWorkflows.push("map");
33175
33859
  }
33176
33860
  } else {
@@ -33181,7 +33865,7 @@ function detectActiveWorkflows(sessionPath) {
33181
33865
  const db = getProgressDb();
33182
33866
  if (db) {
33183
33867
  try {
33184
- const sessionId = basename4(sessionPath);
33868
+ const sessionId = basename5(sessionPath);
33185
33869
  const result = db.exec(
33186
33870
  "SELECT workflow_type, current_phase FROM sessions WHERE id = ?",
33187
33871
  [sessionId]
@@ -33249,8 +33933,8 @@ function padLines(lines) {
33249
33933
  }
33250
33934
 
33251
33935
  // src/lib/progress/review-strategy.ts
33252
- import { existsSync as existsSync8, readdirSync as readdirSync3, readFileSync as readFileSync7 } from "node:fs";
33253
- 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";
33254
33938
  var REVIEW_PHASES = [
33255
33939
  { key: "context", label: "Context Discovery" },
33256
33940
  { key: "change-context", label: "Change Context" },
@@ -33262,10 +33946,10 @@ var REVIEW_PHASES = [
33262
33946
  { key: "complete", label: "Complete" }
33263
33947
  ];
33264
33948
  function countFindings(filePath) {
33265
- if (!existsSync8(filePath)) {
33949
+ if (!existsSync12(filePath)) {
33266
33950
  return 0;
33267
33951
  }
33268
- const content = readFileSync7(filePath, "utf-8");
33952
+ const content = readFileSync8(filePath, "utf-8");
33269
33953
  const findingMatches = content.match(/^##\s+(Finding|Issue|Suggestion)/gm);
33270
33954
  return findingMatches?.length ?? 0;
33271
33955
  }
@@ -33279,27 +33963,27 @@ function formatReviewerName(filename) {
33279
33963
  return base.charAt(0).toUpperCase() + base.slice(1);
33280
33964
  }
33281
33965
  function deriveRoundsFromFilesystem(roundsDir) {
33282
- if (!existsSync8(roundsDir)) {
33966
+ if (!existsSync12(roundsDir)) {
33283
33967
  return [];
33284
33968
  }
33285
- 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) => {
33286
33970
  const numA = parseInt(a.replace("round-", ""));
33287
33971
  const numB = parseInt(b.replace("round-", ""));
33288
33972
  return numA - numB;
33289
33973
  });
33290
33974
  return roundDirs.map((dir) => {
33291
33975
  const roundNum = parseInt(dir.replace("round-", ""));
33292
- const roundPath = join10(roundsDir, dir);
33293
- const reviewsPath = join10(roundPath, "reviews");
33294
- 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");
33295
33979
  const reviewers = [];
33296
- if (existsSync8(reviewsPath)) {
33297
- const files = readdirSync3(reviewsPath).filter((f) => f.endsWith(".md"));
33980
+ if (existsSync12(reviewsPath)) {
33981
+ const files = readdirSync4(reviewsPath).filter((f) => f.endsWith(".md"));
33298
33982
  reviewers.push(...files.map((f) => f.replace(".md", "")));
33299
33983
  }
33300
33984
  return {
33301
33985
  round: roundNum,
33302
- isComplete: existsSync8(finalPath),
33986
+ isComplete: existsSync12(finalPath),
33303
33987
  reviewers
33304
33988
  };
33305
33989
  });
@@ -33309,7 +33993,7 @@ var ReviewProgressStrategy = class {
33309
33993
  phases = REVIEW_PHASES;
33310
33994
  totalPhases = 8;
33311
33995
  parseState(sessionPath, preservedStartTime) {
33312
- const session = basename5(sessionPath);
33996
+ const session = basename6(sessionPath);
33313
33997
  const state = readSessionState(sessionPath);
33314
33998
  if (!state) {
33315
33999
  return null;
@@ -33328,19 +34012,19 @@ var ReviewProgressStrategy = class {
33328
34012
  parseFromState(session, state, sessionPath, preservedStartTime) {
33329
34013
  const effectiveStartTime = state.round_started_at ?? state.started_at;
33330
34014
  const startTime = preservedStartTime ?? (effectiveStartTime ? new Date(effectiveStartTime).getTime() : Date.now());
33331
- const roundsDir = join10(sessionPath, "rounds");
34015
+ const roundsDir = join14(sessionPath, "rounds");
33332
34016
  const rounds = deriveRoundsFromFilesystem(roundsDir);
33333
34017
  const highestExistingRound = rounds.length > 0 ? Math.max(...rounds.map((r) => r.round)) : 1;
33334
34018
  const stateRound = state.current_round ?? 1;
33335
34019
  const currentRound = Math.min(stateRound, highestExistingRound);
33336
- const currentRoundDir = join10(roundsDir, `round-${currentRound}`);
33337
- const reviewsDir = join10(currentRoundDir, "reviews");
34020
+ const currentRoundDir = join14(roundsDir, `round-${currentRound}`);
34021
+ const reviewsDir = join14(currentRoundDir, "reviews");
33338
34022
  const reviewers = [];
33339
- if (existsSync8(reviewsDir)) {
33340
- const entries = readdirSync3(reviewsDir);
34023
+ if (existsSync12(reviewsDir)) {
34024
+ const entries = readdirSync4(reviewsDir);
33341
34025
  const reviewFiles = entries.filter((f) => f.endsWith(".md"));
33342
34026
  for (const file of reviewFiles) {
33343
- const reviewPath = join10(reviewsDir, file);
34027
+ const reviewPath = join14(reviewsDir, file);
33344
34028
  const findings = countFindings(reviewPath);
33345
34029
  reviewers.push({
33346
34030
  name: file.replace(".md", ""),
@@ -33350,14 +34034,14 @@ var ReviewProgressStrategy = class {
33350
34034
  });
33351
34035
  }
33352
34036
  }
33353
- const contextComplete = existsSync8(
33354
- join10(sessionPath, "discovered-standards.md")
34037
+ const contextComplete = existsSync12(
34038
+ join14(sessionPath, "discovered-standards.md")
33355
34039
  );
33356
- const changeContextComplete = existsSync8(join10(sessionPath, "context.md"));
34040
+ const changeContextComplete = existsSync12(join14(sessionPath, "context.md"));
33357
34041
  const analysisComplete = changeContextComplete;
33358
34042
  const reviewsComplete = state.phase_number > 4;
33359
- const discourseComplete = existsSync8(join10(currentRoundDir, "discourse.md"));
33360
- const synthesisComplete = existsSync8(join10(currentRoundDir, "final.md"));
34043
+ const discourseComplete = existsSync12(join14(currentRoundDir, "discourse.md"));
34044
+ const synthesisComplete = existsSync12(join14(currentRoundDir, "final.md"));
33361
34045
  return {
33362
34046
  workflowType: "review",
33363
34047
  session,
@@ -33489,8 +34173,8 @@ var ReviewProgressStrategy = class {
33489
34173
  var reviewStrategy = new ReviewProgressStrategy();
33490
34174
 
33491
34175
  // src/lib/progress/map-strategy.ts
33492
- import { existsSync as existsSync9, readdirSync as readdirSync4, readFileSync as readFileSync8 } from "node:fs";
33493
- 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";
33494
34178
  var MAP_PHASES = [
33495
34179
  { key: "map-context", label: "Context Discovery" },
33496
34180
  { key: "topology", label: "Topology Analysis" },
@@ -33500,23 +34184,23 @@ var MAP_PHASES = [
33500
34184
  { key: "complete", label: "Complete" }
33501
34185
  ];
33502
34186
  function deriveRunsFromFilesystem(mapDir) {
33503
- const runsDir = join11(mapDir, "runs");
33504
- if (!existsSync9(runsDir)) {
34187
+ const runsDir = join15(mapDir, "runs");
34188
+ if (!existsSync13(runsDir)) {
33505
34189
  return [];
33506
34190
  }
33507
- 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) => {
33508
34192
  const numA = parseInt(a.replace("run-", ""));
33509
34193
  const numB = parseInt(b.replace("run-", ""));
33510
34194
  return numA - numB;
33511
34195
  });
33512
34196
  return runDirs.map((dir) => {
33513
34197
  const runNum = parseInt(dir.replace("run-", ""));
33514
- const runPath = join11(runsDir, dir);
33515
- const mapPath = join11(runPath, "map.md");
34198
+ const runPath = join15(runsDir, dir);
34199
+ const mapPath = join15(runPath, "map.md");
33516
34200
  let fileCount = 0;
33517
- const topologyPath = join11(runPath, "topology.md");
33518
- if (existsSync9(topologyPath)) {
33519
- const content = readFileSync8(topologyPath, "utf-8");
34201
+ const topologyPath = join15(runPath, "topology.md");
34202
+ if (existsSync13(topologyPath)) {
34203
+ const content = readFileSync9(topologyPath, "utf-8");
33520
34204
  const fileListMatch = content.match(
33521
34205
  /## Canonical File List[\s\S]*?```([\s\S]*?)```/
33522
34206
  );
@@ -33526,7 +34210,7 @@ function deriveRunsFromFilesystem(mapDir) {
33526
34210
  }
33527
34211
  return {
33528
34212
  run: runNum,
33529
- isComplete: existsSync9(mapPath),
34213
+ isComplete: existsSync13(mapPath),
33530
34214
  fileCount
33531
34215
  };
33532
34216
  });
@@ -33536,7 +34220,7 @@ var MapProgressStrategy = class {
33536
34220
  phases = MAP_PHASES;
33537
34221
  totalPhases = 6;
33538
34222
  parseState(sessionPath, preservedStartTime) {
33539
- const session = basename6(sessionPath);
34223
+ const session = basename7(sessionPath);
33540
34224
  const state = readSessionState(sessionPath);
33541
34225
  if (!state) {
33542
34226
  return null;
@@ -33558,24 +34242,24 @@ var MapProgressStrategy = class {
33558
34242
  parseFromState(session, state, sessionPath, preservedStartTime) {
33559
34243
  const effectiveStartTime = state.map_started_at ?? state.started_at;
33560
34244
  const startTime = preservedStartTime ?? (effectiveStartTime ? new Date(effectiveStartTime).getTime() : Date.now());
33561
- const mapDir = join11(sessionPath, "map");
34245
+ const mapDir = join15(sessionPath, "map");
33562
34246
  const runs = deriveRunsFromFilesystem(mapDir);
33563
34247
  const highestExistingRun = runs.length > 0 ? Math.max(...runs.map((r) => r.run)) : 1;
33564
34248
  const stateRun = state.current_map_run ?? 1;
33565
34249
  const currentRun = Math.min(stateRun, highestExistingRun);
33566
- const currentRunDir = join11(mapDir, "runs", `run-${currentRun}`);
33567
- const contextComplete = existsSync9(
33568
- join11(sessionPath, "discovered-standards.md")
34250
+ const currentRunDir = join15(mapDir, "runs", `run-${currentRun}`);
34251
+ const contextComplete = existsSync13(
34252
+ join15(sessionPath, "discovered-standards.md")
33569
34253
  );
33570
- const topologyComplete = existsSync9(join11(currentRunDir, "topology.md"));
33571
- const flowAnalysisComplete = existsSync9(
33572
- join11(currentRunDir, "flow-analysis.md")
34254
+ const topologyComplete = existsSync13(join15(currentRunDir, "topology.md"));
34255
+ const flowAnalysisComplete = existsSync13(
34256
+ join15(currentRunDir, "flow-analysis.md")
33573
34257
  );
33574
- const requirementsMappingComplete = existsSync9(
33575
- join11(currentRunDir, "requirements-mapping.md")
34258
+ const requirementsMappingComplete = existsSync13(
34259
+ join15(currentRunDir, "requirements-mapping.md")
33576
34260
  );
33577
- const synthesisComplete = existsSync9(join11(currentRunDir, "map.md"));
33578
- const hasRequirements = existsSync9(join11(sessionPath, "requirements.md"));
34261
+ const synthesisComplete = existsSync13(join15(currentRunDir, "map.md"));
34262
+ const hasRequirements = existsSync13(join15(sessionPath, "requirements.md"));
33579
34263
  const flowAnalysts = flowAnalysisComplete ? [
33580
34264
  {
33581
34265
  name: "flow-analyst",
@@ -34030,10 +34714,10 @@ function renderCombinedProgress(sessionPath, preservedStartTimes, ocrDir) {
34030
34714
  }
34031
34715
 
34032
34716
  // src/commands/state.ts
34033
- import { existsSync as existsSync16, mkdirSync as mkdirSync7, readFileSync as readFileSync11 } from "node:fs";
34034
- 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";
34035
34719
 
34036
- // src/lib/state/index.ts
34720
+ // ../shared/persistence/src/state/index.ts
34037
34721
  init_db();
34038
34722
  init_exit_codes();
34039
34723
  import {
@@ -34046,7 +34730,7 @@ import {
34046
34730
  } from "node:fs";
34047
34731
  import { join as join17 } from "node:path";
34048
34732
 
34049
- // src/lib/state/phase-graph.ts
34733
+ // ../shared/persistence/src/state/phase-graph.ts
34050
34734
  init_exit_codes();
34051
34735
  var REVIEW_PHASE_NUMBERS = {
34052
34736
  context: 1,
@@ -34128,7 +34812,7 @@ function validatePhaseTransition(workflowType, source, target, isRoundBoundary)
34128
34812
  }
34129
34813
  }
34130
34814
 
34131
- // src/lib/state/meta-util.ts
34815
+ // ../shared/persistence/src/state/meta-util.ts
34132
34816
  var DEFAULT_METADATA_MAX_LEN = 4096;
34133
34817
  function sanitizeMetadataString(s, opts = {}) {
34134
34818
  const maxLen = opts.maxLen ?? DEFAULT_METADATA_MAX_LEN;
@@ -34138,9 +34822,11 @@ function sanitizeMetadataString(s, opts = {}) {
34138
34822
  return out;
34139
34823
  }
34140
34824
 
34141
- // src/lib/state/round-meta.ts
34825
+ // ../shared/persistence/src/state/round-meta.ts
34826
+ init_src();
34142
34827
  var VALID_CATEGORIES = /* @__PURE__ */ new Set(["blocker", "should_fix", "suggestion", "style"]);
34143
34828
  var VALID_SEVERITIES = /* @__PURE__ */ new Set(["critical", "high", "medium", "low", "info"]);
34829
+ var MIN_TITLE_LEN = 8;
34144
34830
  function validateRoundMeta(meta) {
34145
34831
  if (!meta || typeof meta !== "object") {
34146
34832
  throw new Error("round-meta.json must be a JSON object");
@@ -34151,10 +34837,16 @@ function validateRoundMeta(meta) {
34151
34837
  `Unsupported schema_version: ${String(obj.schema_version)}. Expected 1.`
34152
34838
  );
34153
34839
  }
34154
- if (typeof obj.verdict !== "string" || obj.verdict.trim().length === 0) {
34155
- 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");
34842
+ }
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
+ );
34156
34848
  }
34157
- obj.verdict = sanitizeMetadataString(obj.verdict);
34849
+ obj.verdict = verdict;
34158
34850
  if (!Array.isArray(obj.reviewers)) {
34159
34851
  throw new Error("round-meta.json must contain a reviewers array");
34160
34852
  }
@@ -34177,8 +34869,10 @@ function validateRoundMeta(meta) {
34177
34869
  throw new Error("Each finding must be an object");
34178
34870
  }
34179
34871
  const f = finding;
34180
- if (typeof f.title !== "string" || f.title.trim().length === 0) {
34181
- 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
+ );
34182
34876
  }
34183
34877
  f.title = sanitizeMetadataString(f.title);
34184
34878
  if (typeof f.category !== "string" || !VALID_CATEGORIES.has(f.category)) {
@@ -34223,25 +34917,144 @@ function validateRoundMeta(meta) {
34223
34917
  if (typeof sc.suggestions !== "number" || sc.suggestions < 0) {
34224
34918
  throw new Error("synthesis_counts.suggestions must be a non-negative number");
34225
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
+ );
34226
34948
  }
34227
34949
  return meta;
34228
34950
  }
34229
34951
  function computeRoundCounts(meta) {
34230
- const allFindings = [];
34231
- for (const reviewer of meta.reviewers) {
34232
- 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
+ }
34233
34990
  }
34234
- const sc = meta.synthesis_counts;
34235
34991
  return {
34236
- blockerCount: sc ? sc.blockers : allFindings.filter((f) => f.category === "blocker").length,
34237
- shouldFixCount: sc ? sc.should_fix : allFindings.filter((f) => f.category === "should_fix").length,
34238
- suggestionCount: sc ? sc.suggestions : allFindings.filter((f) => f.category === "suggestion").length,
34239
- reviewerCount: meta.reviewers.length,
34240
- totalFindingCount: allFindings.length
34992
+ leaseCount: leases.length,
34993
+ activeLeaseHeld: nowMs - effectiveMs < leaseMs
34241
34994
  };
34242
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)
35039
+ };
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
+ }
34243
35056
 
34244
- // src/lib/state/map-meta.ts
35057
+ // ../shared/persistence/src/state/map-meta.ts
34245
35058
  function validateMapMeta(meta) {
34246
35059
  if (!meta || typeof meta !== "object") {
34247
35060
  throw new Error("map-meta.json must be a JSON object");
@@ -34308,7 +35121,7 @@ function computeMapCounts(meta) {
34308
35121
  };
34309
35122
  }
34310
35123
 
34311
- // src/lib/state/projection.ts
35124
+ // ../shared/persistence/src/state/projection.ts
34312
35125
  init_db();
34313
35126
  var REASON_EVENT_TYPES = [
34314
35127
  "session_aborted",
@@ -34338,7 +35151,7 @@ function getCompletenessState(db, sessionId) {
34338
35151
  return r[0]?.values[0]?.[0] ?? null;
34339
35152
  }
34340
35153
 
34341
- // src/lib/state/index.ts
35154
+ // ../shared/persistence/src/state/index.ts
34342
35155
  init_exit_codes();
34343
35156
  function deriveNextRound(db, sessionId, fallbackRound) {
34344
35157
  const result = db.exec(
@@ -34393,6 +35206,12 @@ async function stateInit(params) {
34393
35206
  `Cannot re-open session ${sessionId} as workflow_type "${workflowType}": existing workflow_type is "${existing.workflow_type}". Maps and reviews have disjoint phase graphs.`
34394
35207
  );
34395
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
+ }
34396
35215
  const nextRound = deriveNextRound(db, sessionId, existing.current_round);
34397
35216
  const initialPhase2 = workflowType === "map" ? "map-context" : "context";
34398
35217
  db.transaction(() => {
@@ -34692,18 +35511,19 @@ async function stateCompleteRound(params) {
34692
35511
  }
34693
35512
  const resolved = resolveSession(db, params.sessionId);
34694
35513
  const roundNumber = params.round ?? resolved.current_round;
34695
- const roundMetaPath = join17(
34696
- resolved.session_dir,
34697
- "rounds",
34698
- `round-${roundNumber}`,
34699
- "round-meta.json"
34700
- );
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
+ };
34701
35520
  const already = db.exec(
34702
35521
  `SELECT 1 FROM orchestration_events
34703
35522
  WHERE session_id = ? AND event_type = 'round_completed' AND round = ? LIMIT 1`,
34704
35523
  [resolved.id, roundNumber]
34705
35524
  );
34706
35525
  if ((already[0]?.values.length ?? 0) > 0) {
35526
+ if (!existsSync15(roundMetaPath)) materializeArtifact();
34707
35527
  return { sessionId: resolved.id, round: roundNumber, metaPath: roundMetaPath, schema_version: 1 };
34708
35528
  }
34709
35529
  if (resolved.current_phase !== "synthesis") {
@@ -34713,7 +35533,7 @@ async function stateCompleteRound(params) {
34713
35533
  );
34714
35534
  }
34715
35535
  if (params.requireFinal) {
34716
- const finalPath = join17(resolved.session_dir, "rounds", `round-${roundNumber}`, "final.md");
35536
+ const finalPath = join17(roundDir, "final.md");
34717
35537
  if (!existsSync15(finalPath)) {
34718
35538
  throw new StateError(
34719
35539
  STATE_EXIT.INVARIANT_UNMET,
@@ -34721,13 +35541,8 @@ async function stateCompleteRound(params) {
34721
35541
  );
34722
35542
  }
34723
35543
  }
34724
- let metaPath;
34725
- if (params.source === "stdin") {
34726
- const roundDir = join17(resolved.session_dir, "rounds", `round-${roundNumber}`);
34727
- mkdirSync6(roundDir, { recursive: true });
34728
- metaPath = roundMetaPath;
34729
- writeFileSync7(metaPath, JSON.stringify(meta, null, 2));
34730
- }
35544
+ materializeArtifact();
35545
+ const metaPath = roundMetaPath;
34731
35546
  db.transaction(() => {
34732
35547
  insertEvent(db, {
34733
35548
  session_id: resolved.id,
@@ -34778,19 +35593,19 @@ async function stateCompleteMap(params) {
34778
35593
  }
34779
35594
  const resolved = resolveSession(db, params.sessionId);
34780
35595
  const mapRunNumber = params.mapRun ?? resolved.current_map_run;
34781
- const mapMetaPath = join17(
34782
- resolved.session_dir,
34783
- "map",
34784
- "runs",
34785
- `run-${mapRunNumber}`,
34786
- "map-meta.json"
34787
- );
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
+ };
34788
35602
  const already = db.exec(
34789
35603
  `SELECT 1 FROM orchestration_events
34790
35604
  WHERE session_id = ? AND event_type = 'map_completed' AND round = ? LIMIT 1`,
34791
35605
  [resolved.id, mapRunNumber]
34792
35606
  );
34793
35607
  if ((already[0]?.values.length ?? 0) > 0) {
35608
+ if (!existsSync15(mapMetaPath)) materializeArtifact();
34794
35609
  return { sessionId: resolved.id, mapRun: mapRunNumber, metaPath: mapMetaPath, schema_version: 1 };
34795
35610
  }
34796
35611
  if (resolved.current_phase !== "synthesis") {
@@ -34799,13 +35614,8 @@ async function stateCompleteMap(params) {
34799
35614
  `Cannot complete map: workflow is at "${resolved.current_phase}", not "synthesis". Advance first.`
34800
35615
  );
34801
35616
  }
34802
- let metaPath;
34803
- if (params.source === "stdin") {
34804
- const runDir = join17(resolved.session_dir, "map", "runs", `run-${mapRunNumber}`);
34805
- mkdirSync6(runDir, { recursive: true });
34806
- metaPath = mapMetaPath;
34807
- writeFileSync7(metaPath, JSON.stringify(meta, null, 2));
34808
- }
35617
+ materializeArtifact();
35618
+ const metaPath = mapMetaPath;
34809
35619
  db.transaction(() => {
34810
35620
  insertEvent(db, {
34811
35621
  session_id: resolved.id,
@@ -34831,7 +35641,7 @@ async function stateCompleteMap(params) {
34831
35641
  });
34832
35642
  return { sessionId: resolved.id, mapRun: mapRunNumber, metaPath, schema_version: 1 };
34833
35643
  }
34834
- async function stateStatus(ocrDir, sessionId) {
35644
+ async function stateStatus(ocrDir, sessionId, forwardResume) {
34835
35645
  const db = await ensureDatabase(ocrDir);
34836
35646
  const resolved = resolveSession(db, sessionId);
34837
35647
  const view = db.exec(
@@ -34844,6 +35654,8 @@ async function stateStatus(ocrDir, sessionId) {
34844
35654
  const hasTerminalArtifact = row?.[1] === 1;
34845
35655
  let nextAction;
34846
35656
  let nextActionKind;
35657
+ let remainingPhases;
35658
+ let attemptsRemaining;
34847
35659
  switch (completenessState) {
34848
35660
  case "complete":
34849
35661
  nextAction = "none \u2014 session is complete";
@@ -34861,12 +35673,25 @@ async function stateStatus(ocrDir, sessionId) {
34861
35673
  if (hasTerminalArtifact) {
34862
35674
  nextAction = "run 'ocr state finish' to close the workflow";
34863
35675
  nextActionKind = "finish";
34864
- } else if (resolved.current_phase === "synthesis") {
34865
- nextAction = "pipe round metadata to 'ocr state complete-round --stdin'";
34866
- nextActionKind = "complete_round";
34867
35676
  } else {
34868
- nextAction = "advance through the phases, then 'ocr state complete-round'";
34869
- 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
+ }
34870
35695
  }
34871
35696
  }
34872
35697
  return {
@@ -34882,7 +35707,9 @@ async function stateStatus(ocrDir, sessionId) {
34882
35707
  marked_closed: row?.[2] === 1,
34883
35708
  dependents_settled: row?.[3] === 1,
34884
35709
  next_action: nextAction,
34885
- next_action_kind: nextActionKind
35710
+ next_action_kind: nextActionKind,
35711
+ ...remainingPhases ? { remaining_phases: remainingPhases } : {},
35712
+ ...attemptsRemaining !== void 0 ? { forward_resume_attempts_remaining: attemptsRemaining } : {}
34886
35713
  };
34887
35714
  }
34888
35715
  async function stateSync(ocrDir) {
@@ -34970,14 +35797,73 @@ async function stateSync(ocrDir) {
34970
35797
  }
34971
35798
 
34972
35799
  // src/commands/state.ts
34973
- init_command_log();
34974
35800
  init_db();
34975
35801
  init_db();
34976
- function readDashboardSpawnMarker(ocrDir) {
34977
- 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) {
34978
35864
  let raw;
34979
35865
  try {
34980
- raw = readFileSync11(path2, "utf-8");
35866
+ raw = readFileSync12(path2, "utf-8");
34981
35867
  } catch {
34982
35868
  return null;
34983
35869
  }
@@ -34998,6 +35884,30 @@ function readDashboardSpawnMarker(ocrDir) {
34998
35884
  }
34999
35885
  return marker;
35000
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
+ }
35001
35911
  async function readStdin() {
35002
35912
  const chunks = [];
35003
35913
  for await (const chunk of process.stdin) {
@@ -35039,7 +35949,7 @@ async function linkDashboardInvocation(ocrDir, sessionId, explicitUid, label) {
35039
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) => {
35040
35950
  const targetDir = process.cwd();
35041
35951
  requireOcrSetup(targetDir);
35042
- const ocrDir = join18(targetDir, ".ocr");
35952
+ const ocrDir = join19(targetDir, ".ocr");
35043
35953
  try {
35044
35954
  const result = await stateShow(ocrDir, options.sessionId);
35045
35955
  if (!result) {
@@ -35108,7 +36018,7 @@ var showSubcommand = new Command("show").description("Show current session state
35108
36018
  var syncSubcommand = new Command("sync").description("Rebuild session state from filesystem artifacts").action(async () => {
35109
36019
  const targetDir = process.cwd();
35110
36020
  requireOcrSetup(targetDir);
35111
- const ocrDir = join18(targetDir, ".ocr");
36021
+ const ocrDir = join19(targetDir, ".ocr");
35112
36022
  try {
35113
36023
  const synced = await stateSync(ocrDir);
35114
36024
  console.log(`Synced ${synced} session${synced !== 1 ? "s" : ""} from filesystem.`);
@@ -35135,7 +36045,7 @@ var reconcileSubcommand = new Command("reconcile").description(
35135
36045
  ).option("--dry-run", "Print the repair plan without writing anything").option("--json", "Output the result as JSON").action(async (options) => {
35136
36046
  const targetDir = process.cwd();
35137
36047
  requireOcrSetup(targetDir);
35138
- const ocrDir = join18(targetDir, ".ocr");
36048
+ const ocrDir = join19(targetDir, ".ocr");
35139
36049
  try {
35140
36050
  const db = await ensureDatabase(ocrDir);
35141
36051
  const result = reconcileLegacyState(db, ocrDir, { dryRun: options.dryRun });
@@ -35194,9 +36104,9 @@ var beginSubcommand = new Command("begin").description("Start or resume a workfl
35194
36104
  async (options) => {
35195
36105
  const targetDir = process.cwd();
35196
36106
  requireOcrSetup(targetDir);
35197
- const ocrDir = join18(targetDir, ".ocr");
35198
- const sessionDir = options.sessionDir ?? join18(ocrDir, "sessions", options.sessionId);
35199
- 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 });
35200
36110
  try {
35201
36111
  const result = await stateBegin({
35202
36112
  sessionId: options.sessionId,
@@ -35218,7 +36128,7 @@ var advanceSubcommand = new Command("advance").description("Advance the workflow
35218
36128
  async (options) => {
35219
36129
  const targetDir = process.cwd();
35220
36130
  requireOcrSetup(targetDir);
35221
- const ocrDir = join18(targetDir, ".ocr");
36131
+ const ocrDir = join19(targetDir, ".ocr");
35222
36132
  try {
35223
36133
  const { id: sessionId } = await resolveActiveSession(ocrDir, options.sessionId);
35224
36134
  await stateAdvance({
@@ -35238,7 +36148,7 @@ var completeRoundSubcommand = new Command("complete-round").description("Atomica
35238
36148
  async (options) => {
35239
36149
  const targetDir = process.cwd();
35240
36150
  requireOcrSetup(targetDir);
35241
- const ocrDir = join18(targetDir, ".ocr");
36151
+ const ocrDir = join19(targetDir, ".ocr");
35242
36152
  try {
35243
36153
  const base = options.stdin ? { source: "stdin", data: await readStdin() } : options.file ? { source: "file", filePath: options.file } : (() => {
35244
36154
  throw new StateError(STATE_EXIT.USAGE, "Provide --stdin or --file with round metadata");
@@ -35262,7 +36172,7 @@ var completeMapSubcommand = new Command("complete-map").description("Atomically
35262
36172
  async (options) => {
35263
36173
  const targetDir = process.cwd();
35264
36174
  requireOcrSetup(targetDir);
35265
- const ocrDir = join18(targetDir, ".ocr");
36175
+ const ocrDir = join19(targetDir, ".ocr");
35266
36176
  try {
35267
36177
  const base = options.stdin ? { source: "stdin", data: await readStdin() } : options.file ? { source: "file", filePath: options.file } : (() => {
35268
36178
  throw new StateError(STATE_EXIT.USAGE, "Provide --stdin or --file with map metadata");
@@ -35284,7 +36194,7 @@ var completeMapSubcommand = new Command("complete-map").description("Atomically
35284
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) => {
35285
36195
  const targetDir = process.cwd();
35286
36196
  requireOcrSetup(targetDir);
35287
- const ocrDir = join18(targetDir, ".ocr");
36197
+ const ocrDir = join19(targetDir, ".ocr");
35288
36198
  try {
35289
36199
  const { id: sessionId } = await resolveActiveSession(ocrDir, options.sessionId);
35290
36200
  await stateClose({ sessionId, ocrDir, abort: options.abort });
@@ -35296,14 +36206,20 @@ var finishSubcommand = new Command("finish").description("Close a workflow (refu
35296
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) => {
35297
36207
  const targetDir = process.cwd();
35298
36208
  requireOcrSetup(targetDir);
35299
- const ocrDir = join18(targetDir, ".ocr");
36209
+ const ocrDir = join19(targetDir, ".ocr");
35300
36210
  try {
35301
- 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
+ });
35302
36215
  if (options.json) {
35303
36216
  console.log(JSON.stringify(result, null, 2));
35304
36217
  } else {
35305
36218
  console.log(`${result.session_id}: ${result.completeness_state}`);
35306
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
+ }
35307
36223
  }
35308
36224
  } catch (error) {
35309
36225
  exitFromStateError(error, "Failed to read status");
@@ -35327,50 +36243,6 @@ var stateCommand = new Command("state").description("Manage OCR session state").
35327
36243
  import { randomUUID as randomUUID3 } from "node:crypto";
35328
36244
  import { join as join20 } from "node:path";
35329
36245
  init_db();
35330
-
35331
- // src/lib/runtime-config.ts
35332
- import { existsSync as existsSync17, readFileSync as readFileSync12 } from "node:fs";
35333
- import { join as join19 } from "node:path";
35334
- var DEFAULT_AGENT_HEARTBEAT_SECONDS = 60;
35335
- function readRuntimePositiveInt(ocrDir, key, defaultValue) {
35336
- const configPath = join19(ocrDir, "config.yaml");
35337
- if (!existsSync17(configPath)) return defaultValue;
35338
- let content;
35339
- try {
35340
- content = readFileSync12(configPath, "utf-8");
35341
- } catch {
35342
- return defaultValue;
35343
- }
35344
- const blockMatch = content.match(
35345
- new RegExp(
35346
- String.raw`^runtime:\s*\n(?:\s+[^\n]*\n)*?\s+${key}:\s*([^\s#\n]+)`,
35347
- "m"
35348
- )
35349
- );
35350
- const inlineMatch = content.match(
35351
- new RegExp(String.raw`^runtime:\s*\{[^}]*\b${key}:\s*([^\s,}]+)`, "m")
35352
- );
35353
- const raw = blockMatch?.[1] ?? inlineMatch?.[1];
35354
- if (!raw) return defaultValue;
35355
- const parsed = Number(raw);
35356
- if (!Number.isFinite(parsed) || parsed <= 0 || !Number.isInteger(parsed)) {
35357
- process.stderr.write(
35358
- `[ocr] runtime.${key} is not a positive integer (got "${raw}"); falling back to ${defaultValue}.
35359
- `
35360
- );
35361
- return defaultValue;
35362
- }
35363
- return parsed;
35364
- }
35365
- function getAgentHeartbeatSeconds(ocrDir) {
35366
- return readRuntimePositiveInt(
35367
- ocrDir,
35368
- "agent_heartbeat_seconds",
35369
- DEFAULT_AGENT_HEARTBEAT_SECONDS
35370
- );
35371
- }
35372
-
35373
- // src/commands/session.ts
35374
36246
  var TERMINAL_STATUSES = /* @__PURE__ */ new Set([
35375
36247
  "done",
35376
36248
  "crashed",
@@ -35421,6 +36293,11 @@ var startInstanceSubcommand = new Command("start-instance").description("Journal
35421
36293
  }
35422
36294
  );
35423
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
+ }
35424
36301
  const { ocrDir } = await setup();
35425
36302
  const db = await ensureDatabase(ocrDir);
35426
36303
  try {
@@ -35518,25 +36395,64 @@ var listSubcommand = new Command("list").description("List agent sessions for a
35518
36395
  });
35519
36396
  var sessionCommand = new Command("session").description("Manage agent-CLI session lifecycle journal").addCommand(startInstanceSubcommand).addCommand(bindVendorIdSubcommand).addCommand(beatSubcommand).addCommand(endInstanceSubcommand).addCommand(listSubcommand);
35520
36397
 
35521
- // src/lib/models.ts
36398
+ // ../shared/config/src/models.ts
35522
36399
  init_src();
35523
- var BUNDLED_CLAUDE_MODELS = [
35524
- { id: "claude-opus-4-7", displayName: "Claude Opus 4.7" },
35525
- { id: "claude-sonnet-4-6", displayName: "Claude Sonnet 4.6" },
35526
- { id: "claude-haiku-4-5-20251001", displayName: "Claude Haiku 4.5" }
35527
- ];
35528
- var BUNDLED_OPENCODE_MODELS = [
35529
- { id: "anthropic/claude-opus-4-7", provider: "anthropic" },
35530
- { id: "anthropic/claude-sonnet-4-6", provider: "anthropic" },
35531
- { id: "anthropic/claude-haiku-4-5-20251001", provider: "anthropic" }
35532
- ];
35533
- function detectActiveVendor() {
35534
- for (const vendor of ["claude", "opencode"]) {
36400
+ function parseOpenCodeModelList(stdout) {
36401
+ const models = [];
36402
+ for (const rawLine of stdout.split(/\r?\n/)) {
36403
+ const line = rawLine.trim();
36404
+ if (!/^[^\s:]+\/\S+$/.test(line)) continue;
36405
+ const provider = line.slice(0, line.indexOf("/"));
36406
+ models.push({ id: line, provider });
36407
+ }
36408
+ return models.length > 0 ? models : null;
36409
+ }
36410
+ var VENDOR_MODEL_STRATEGIES = {
36411
+ claude: {
36412
+ displayName: "Claude Code",
36413
+ native: {
36414
+ // Verified against Claude Code 2.1.x: the CLI has no model-listing
36415
+ // subcommand (`claude models --json` → "unknown option"). Revisit if
36416
+ // a future release adds one.
36417
+ unavailableReason: "Claude Code does not provide a model-listing command; showing its documented model aliases instead"
36418
+ },
36419
+ // Vendor-documented aliases that always track the latest generation —
36420
+ // dated ids here would go stale by construction (the exact bug class of
36421
+ // issue #39). Pinned dated ids remain available via free-text entry.
36422
+ bundled: [
36423
+ { id: "opus", displayName: "Claude Opus (latest)" },
36424
+ { id: "sonnet", displayName: "Claude Sonnet (latest)" },
36425
+ { id: "haiku", displayName: "Claude Haiku (latest)" }
36426
+ ]
36427
+ },
36428
+ opencode: {
36429
+ displayName: "OpenCode",
36430
+ native: {
36431
+ // Plain `opencode models` — newline-delimited ids. (`--json` is not a
36432
+ // real flag, and `--verbose` interleaves JSON metadata blocks that
36433
+ // defeat line parsing.)
36434
+ args: ["models"],
36435
+ parse: parseOpenCodeModelList
36436
+ },
36437
+ bundled: [
36438
+ { id: "anthropic/claude-opus-4-8", provider: "anthropic" },
36439
+ { id: "anthropic/claude-sonnet-4-6", provider: "anthropic" },
36440
+ { id: "anthropic/claude-haiku-4-5", provider: "anthropic" }
36441
+ ]
36442
+ }
36443
+ };
36444
+ var SUPPORTED_VENDORS = Object.keys(
36445
+ VENDOR_MODEL_STRATEGIES
36446
+ );
36447
+ function isModelVendor(value) {
36448
+ return Object.hasOwn(VENDOR_MODEL_STRATEGIES, value);
36449
+ }
36450
+ async function detectActiveVendor() {
36451
+ for (const vendor of SUPPORTED_VENDORS) {
35535
36452
  try {
35536
- execBinary(vendor, ["--version"], {
36453
+ await execBinaryAsync(vendor, ["--version"], {
35537
36454
  encoding: "utf-8",
35538
- timeout: 3e3,
35539
- stdio: ["ignore", "pipe", "ignore"]
36455
+ timeout: 3e3
35540
36456
  });
35541
36457
  return vendor;
35542
36458
  } catch {
@@ -35544,68 +36460,97 @@ function detectActiveVendor() {
35544
36460
  }
35545
36461
  return null;
35546
36462
  }
35547
- function tryNativeEnumeration(vendor) {
36463
+ function describeProbeFailure(vendor, args, err) {
36464
+ const command = `${vendor} ${args.join(" ")}`;
36465
+ const e = err;
36466
+ if (e.code === "ENOENT") {
36467
+ return `\`${vendor}\` is not installed or not on PATH`;
36468
+ }
36469
+ if (e.killed) {
36470
+ return `\`${command}\` timed out or exceeded output limits`;
36471
+ }
36472
+ const stderr = typeof e.stderr === "string" ? e.stderr.trim() : "";
36473
+ const firstLine = (stderr.split(/\r?\n/)[0] ?? "").replace(/\u001b\[[0-9;]*[A-Za-z]/g, "").replace(/[\u0000-\u001f\u007f]/g, "").slice(0, 200);
36474
+ const detail = firstLine ? `: ${firstLine}` : "";
36475
+ const exit = typeof e.code === "number" ? ` with exit code ${e.code}` : "";
36476
+ return `\`${command}\` failed${exit}${detail}`;
36477
+ }
36478
+ async function tryNativeEnumeration(vendor, probe) {
36479
+ let stdout;
35548
36480
  try {
35549
- const output = execBinary(vendor, ["models", "--json"], {
36481
+ const result = await execBinaryAsync(vendor, probe.args, {
35550
36482
  encoding: "utf-8",
35551
- timeout: 5e3,
35552
- stdio: ["ignore", "pipe", "ignore"]
36483
+ timeout: 5e3
35553
36484
  });
35554
- const parsed = JSON.parse(output);
35555
- if (!Array.isArray(parsed)) return null;
35556
- const models = [];
35557
- for (const item of parsed) {
35558
- if (typeof item === "string") {
35559
- models.push({ id: item });
35560
- } else if (typeof item === "object" && item !== null && "id" in item && typeof item.id === "string") {
35561
- const obj = item;
35562
- const desc = { id: obj.id };
35563
- if (typeof obj.displayName === "string") desc.displayName = obj.displayName;
35564
- if (typeof obj.provider === "string") desc.provider = obj.provider;
35565
- if (Array.isArray(obj.tags)) {
35566
- desc.tags = obj.tags.filter((t) => typeof t === "string");
35567
- }
35568
- models.push(desc);
35569
- }
35570
- }
35571
- return models.length > 0 ? models : null;
35572
- } catch {
35573
- return null;
36485
+ stdout = result.stdout;
36486
+ } catch (err) {
36487
+ return { models: null, reason: describeProbeFailure(vendor, probe.args, err) };
35574
36488
  }
36489
+ const models = probe.parse(stdout);
36490
+ if (!models) {
36491
+ return {
36492
+ models: null,
36493
+ reason: `\`${vendor} ${probe.args.join(" ")}\` output did not contain any model identifiers`
36494
+ };
36495
+ }
36496
+ return { models };
35575
36497
  }
35576
- function bundledForVendor(vendor) {
35577
- if (vendor === "claude") return BUNDLED_CLAUDE_MODELS;
35578
- return BUNDLED_OPENCODE_MODELS;
35579
- }
35580
- function listModelsForVendor(vendor) {
35581
- const native = tryNativeEnumeration(vendor);
35582
- if (native) {
35583
- return { vendor, source: "native", models: native };
36498
+ var SUCCESS_TTL_MS = 6e4;
36499
+ var FAILURE_TTL_MS = 1e4;
36500
+ var cache = /* @__PURE__ */ new Map();
36501
+ async function listModelsForVendor(vendor) {
36502
+ const cached = cache.get(vendor);
36503
+ if (cached && cached.expiresAt > Date.now()) {
36504
+ return cached.result;
36505
+ }
36506
+ const strategy = VENDOR_MODEL_STRATEGIES[vendor];
36507
+ if (!strategy) {
36508
+ throw new Error(`Unknown vendor: ${vendor}`);
35584
36509
  }
35585
- return { vendor, source: "bundled", models: bundledForVendor(vendor) };
36510
+ let result;
36511
+ if ("unavailableReason" in strategy.native) {
36512
+ result = {
36513
+ vendor,
36514
+ source: "bundled",
36515
+ models: strategy.bundled,
36516
+ nativeUnavailableReason: strategy.native.unavailableReason
36517
+ };
36518
+ } else {
36519
+ const native = await tryNativeEnumeration(vendor, strategy.native);
36520
+ result = native.models ? { vendor, source: "native", models: native.models } : {
36521
+ vendor,
36522
+ source: "bundled",
36523
+ models: strategy.bundled,
36524
+ nativeUnavailableReason: native.reason
36525
+ };
36526
+ }
36527
+ const ttl = result.source === "native" ? SUCCESS_TTL_MS : FAILURE_TTL_MS;
36528
+ cache.set(vendor, { result, expiresAt: Date.now() + ttl });
36529
+ return result;
35586
36530
  }
35587
36531
 
35588
36532
  // src/commands/models.ts
35589
- var listSubcommand2 = new Command("list").description("List models the active AI CLI is willing to accept").option(
35590
- "--vendor <vendor>",
35591
- "Override autodetection (claude | opencode)"
35592
- ).option("--json", "Emit JSON for programmatic consumption").action(async (options) => {
36533
+ var vendorList = SUPPORTED_VENDORS.join(" | ");
36534
+ var listSubcommand2 = new Command("list").description("List models the active AI CLI is willing to accept").option("--vendor <vendor>", `Override autodetection (${vendorList})`).option("--json", "Emit JSON for programmatic consumption").action(async (options) => {
35593
36535
  let vendor;
35594
36536
  if (options.vendor) {
35595
- if (options.vendor !== "claude" && options.vendor !== "opencode") {
36537
+ const requested = options.vendor.toLowerCase();
36538
+ if (!isModelVendor(requested)) {
35596
36539
  console.error(
35597
36540
  source_default.red(
35598
- `Invalid --vendor: "${options.vendor}". Must be "claude" or "opencode".`
36541
+ `Invalid --vendor: "${options.vendor}". Must be one of: ${vendorList}.`
35599
36542
  )
35600
36543
  );
35601
36544
  process.exit(1);
35602
36545
  }
35603
- vendor = options.vendor;
36546
+ vendor = requested;
35604
36547
  } else {
35605
- vendor = detectActiveVendor();
36548
+ vendor = await detectActiveVendor();
35606
36549
  if (!vendor) {
35607
36550
  if (options.json) {
35608
- console.log("[]");
36551
+ console.log(
36552
+ JSON.stringify({ vendor: null, source: null, models: [] }, null, 2)
36553
+ );
35609
36554
  return;
35610
36555
  }
35611
36556
  console.error(
@@ -35616,16 +36561,18 @@ var listSubcommand2 = new Command("list").description("List models the active AI
35616
36561
  process.exit(1);
35617
36562
  }
35618
36563
  }
35619
- const { source, models } = listModelsForVendor(vendor);
36564
+ const result = await listModelsForVendor(vendor);
35620
36565
  if (options.json) {
35621
- console.log(JSON.stringify(models, null, 2));
36566
+ console.log(JSON.stringify(result, null, 2));
35622
36567
  return;
35623
36568
  }
36569
+ const { source, models, nativeUnavailableReason } = result;
35624
36570
  console.log(source_default.bold(`Models for ${vendor} (${source})`));
35625
36571
  if (source === "bundled") {
36572
+ const reason = nativeUnavailableReason ? ` \u2014 ${nativeUnavailableReason}` : "";
35626
36573
  console.log(
35627
36574
  source_default.dim(
35628
- " Note: bundled fallback list \u2014 may be stale. Free-text input is always accepted."
36575
+ ` Note: bundled fallback list${reason}. Free-text input is always accepted.`
35629
36576
  )
35630
36577
  );
35631
36578
  }
@@ -35850,11 +36797,11 @@ function pairKey(pair) {
35850
36797
  var teamCommand = new Command("team").description("Resolve and persist team composition").addCommand(resolveSubcommand).addCommand(setSubcommand);
35851
36798
 
35852
36799
  // src/commands/review.ts
35853
- import { spawn as spawn3 } from "node:child_process";
36800
+ init_src();
35854
36801
  import { join as join22 } from "node:path";
35855
36802
  init_db();
35856
36803
 
35857
- // src/lib/vendor-resume.ts
36804
+ // ../shared/persistence/src/vendor-resume.ts
35858
36805
  var VENDOR_BINARIES = {
35859
36806
  claude: "claude",
35860
36807
  opencode: "opencode"
@@ -35876,6 +36823,7 @@ function fail3(message) {
35876
36823
  console.error(source_default.red(`Error: ${message}`));
35877
36824
  process.exit(1);
35878
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.";
35879
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) => {
35880
36828
  if (!options.resume) {
35881
36829
  console.error(
@@ -35892,21 +36840,89 @@ var reviewCommand = new Command("review").description("Run or resume an OCR revi
35892
36840
  requireOcrSetup(targetDir);
35893
36841
  const ocrDir = join22(targetDir, ".ocr");
35894
36842
  const db = await ensureDatabase(ocrDir);
35895
- const session = getSession(db, options.resume);
36843
+ const workflowId = options.resume;
36844
+ const session = getSession(db, workflowId);
35896
36845
  if (!session) {
35897
- 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;
35898
36891
  }
35899
- const latest = getLatestAgentSessionWithVendorId(db, options.resume);
35900
- 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
+ }
35901
36903
  fail3(
35902
- `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.`
35903
36905
  );
35904
36906
  }
35905
- const binary = VENDOR_BINARIES[latest.vendor];
35906
- if (!binary) {
35907
- fail3(
35908
- `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
+ )
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
+ )
35909
36924
  );
36925
+ process.exit(0);
35910
36926
  }
35911
36927
  let args;
35912
36928
  try {
@@ -35914,12 +36930,8 @@ var reviewCommand = new Command("review").description("Run or resume an OCR revi
35914
36930
  } catch (err) {
35915
36931
  fail3(err instanceof Error ? err.message : String(err));
35916
36932
  }
35917
- console.error(
35918
- source_default.dim(
35919
- `Resuming workflow ${session.id} on branch ${session.branch} via ${binary}\u2026`
35920
- )
35921
- );
35922
- 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, {
35923
36935
  stdio: "inherit",
35924
36936
  cwd: targetDir
35925
36937
  });
@@ -36844,10 +37856,10 @@ function readCache(cacheFile) {
36844
37856
  return null;
36845
37857
  }
36846
37858
  }
36847
- function writeCache(cacheFile, cache) {
37859
+ function writeCache(cacheFile, cache2) {
36848
37860
  try {
36849
37861
  mkdirSync8(join28(cacheFile, ".."), { recursive: true });
36850
- writeFileSync10(cacheFile, JSON.stringify(cache));
37862
+ writeFileSync10(cacheFile, JSON.stringify(cache2));
36851
37863
  } catch {
36852
37864
  }
36853
37865
  }
@@ -36869,14 +37881,14 @@ async function checkForUpdate(currentVersion, options) {
36869
37881
  }
36870
37882
  const cacheFile = join28(options?.cacheDir ?? CACHE_DIR2, "update-check.json");
36871
37883
  try {
36872
- const cache = readCache(cacheFile);
36873
- if (cache && Date.now() - cache.lastCheck < CHECK_INTERVAL_MS) {
36874
- if (!cache.latestVersion) return null;
36875
- if (!isNewer(cache.latestVersion, currentVersion)) return null;
37884
+ const cache2 = readCache(cacheFile);
37885
+ if (cache2 && Date.now() - cache2.lastCheck < CHECK_INTERVAL_MS) {
37886
+ if (!cache2.latestVersion) return null;
37887
+ if (!isNewer(cache2.latestVersion, currentVersion)) return null;
36876
37888
  return {
36877
37889
  updateAvailable: true,
36878
37890
  currentVersion,
36879
- latestVersion: cache.latestVersion,
37891
+ latestVersion: cache2.latestVersion,
36880
37892
  updateCommand: detectUpdateCommand()
36881
37893
  };
36882
37894
  }