@primitive.ai/prim 0.1.0-alpha.92 → 0.1.0-alpha.93

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 (39) hide show
  1. package/README.md +34 -2
  2. package/dist/{chunk-67VNLLBN.js → chunk-2GP5UFV7.js} +1 -0
  3. package/dist/{chunk-ADUTW66W.js → chunk-36TWVNBE.js} +6 -6
  4. package/dist/{chunk-5HRR4AOR.js → chunk-C3VAA5CG.js} +1 -1
  5. package/dist/{chunk-LGGMJYY4.js → chunk-DCJN6LZH.js} +37 -5
  6. package/dist/{chunk-M6RIJVRE.js → chunk-DSWT6BCC.js} +78 -4
  7. package/dist/{chunk-FBAYCS3X.js → chunk-DXJQR4QI.js} +150 -38
  8. package/dist/{chunk-PCVGBE5B.js → chunk-G2VMA2QO.js} +2 -1
  9. package/dist/{chunk-NAHHFSLH.js → chunk-ICM2XCXS.js} +2 -2
  10. package/dist/{chunk-OAAP2MY6.js → chunk-K7D2EYBS.js} +2 -2
  11. package/dist/{chunk-5AFRMOPX.js → chunk-OH3EZXYX.js} +1 -1
  12. package/dist/{chunk-KFXF2V5G.js → chunk-OTHKG76J.js} +1 -1
  13. package/dist/chunk-OYHYCK4K.js +13 -0
  14. package/dist/{chunk-FKXFUUWJ.js → chunk-TYAHC25F.js} +1 -1
  15. package/dist/{chunk-5GYQ4J3D.js → chunk-UKUAAAXE.js} +2 -2
  16. package/dist/{chunk-26P57LAR.js → chunk-X64HTHUF.js} +3 -1
  17. package/dist/{chunk-WX52U7Z2.js → chunk-ZU563PQI.js} +58 -2
  18. package/dist/daemon/server.js +372 -123
  19. package/dist/hook-runtime/daemon/server.js +372 -123
  20. package/dist/hook-runtime/hooks/post-commit.js +57 -39
  21. package/dist/hook-runtime/hooks/post-rewrite.js +58 -40
  22. package/dist/hook-runtime/hooks/post-tool-use.js +95 -62
  23. package/dist/hook-runtime/hooks/pre-tool-use.js +32 -10
  24. package/dist/hook-runtime/hooks/prim-hook.js +142 -102
  25. package/dist/hook-runtime/hooks/session-start.js +37 -3
  26. package/dist/hook-runtime/index.js +1264 -523
  27. package/dist/hook-runtime/statusline-main.js +23 -1
  28. package/dist/hooks/post-commit.js +11 -8
  29. package/dist/hooks/post-rewrite.js +11 -8
  30. package/dist/hooks/post-tool-use.js +13 -15
  31. package/dist/hooks/pre-commit.js +4 -4
  32. package/dist/hooks/pre-tool-use.js +10 -12
  33. package/dist/hooks/prim-hook.js +17 -14
  34. package/dist/hooks/session-end.js +4 -4
  35. package/dist/hooks/session-start.js +12 -36
  36. package/dist/index.js +857 -290
  37. package/dist/statusline-main.js +23 -1
  38. package/package.json +1 -1
  39. package/dist/chunk-BTNNEH2W.js +0 -79
@@ -3035,7 +3035,7 @@ var require_commander = __commonJS({
3035
3035
  var require_polyfills = __commonJS({
3036
3036
  "node_modules/.pnpm/graceful-fs@4.2.11/node_modules/graceful-fs/polyfills.js"(exports, module) {
3037
3037
  "use strict";
3038
- var constants8 = __require("constants");
3038
+ var constants9 = __require("constants");
3039
3039
  var origCwd = process.cwd;
3040
3040
  var cwd = null;
3041
3041
  var platform2 = process.env.GRACEFUL_FS_PLATFORM || process.platform;
@@ -3059,7 +3059,7 @@ var require_polyfills = __commonJS({
3059
3059
  var chdir;
3060
3060
  module.exports = patch;
3061
3061
  function patch(fs5) {
3062
- if (constants8.hasOwnProperty("O_SYMLINK") && process.version.match(/^v0\.6\.[0-2]|^v0\.5\./)) {
3062
+ if (constants9.hasOwnProperty("O_SYMLINK") && process.version.match(/^v0\.6\.[0-2]|^v0\.5\./)) {
3063
3063
  patchLchmod(fs5);
3064
3064
  }
3065
3065
  if (!fs5.lutimes) {
@@ -3161,7 +3161,7 @@ var require_polyfills = __commonJS({
3161
3161
  fs6.lchmod = function(path9, mode, callback) {
3162
3162
  fs6.open(
3163
3163
  path9,
3164
- constants8.O_WRONLY | constants8.O_SYMLINK,
3164
+ constants9.O_WRONLY | constants9.O_SYMLINK,
3165
3165
  mode,
3166
3166
  function(err, fd) {
3167
3167
  if (err) {
@@ -3177,7 +3177,7 @@ var require_polyfills = __commonJS({
3177
3177
  );
3178
3178
  };
3179
3179
  fs6.lchmodSync = function(path9, mode) {
3180
- var fd = fs6.openSync(path9, constants8.O_WRONLY | constants8.O_SYMLINK, mode);
3180
+ var fd = fs6.openSync(path9, constants9.O_WRONLY | constants9.O_SYMLINK, mode);
3181
3181
  var threw = true;
3182
3182
  var ret;
3183
3183
  try {
@@ -3197,9 +3197,9 @@ var require_polyfills = __commonJS({
3197
3197
  };
3198
3198
  }
3199
3199
  function patchLutimes(fs6) {
3200
- if (constants8.hasOwnProperty("O_SYMLINK") && fs6.futimes) {
3200
+ if (constants9.hasOwnProperty("O_SYMLINK") && fs6.futimes) {
3201
3201
  fs6.lutimes = function(path9, at, mt, cb) {
3202
- fs6.open(path9, constants8.O_SYMLINK, function(er, fd) {
3202
+ fs6.open(path9, constants9.O_SYMLINK, function(er, fd) {
3203
3203
  if (er) {
3204
3204
  if (cb) cb(er);
3205
3205
  return;
@@ -3212,7 +3212,7 @@ var require_polyfills = __commonJS({
3212
3212
  });
3213
3213
  };
3214
3214
  fs6.lutimesSync = function(path9, at, mt) {
3215
- var fd = fs6.openSync(path9, constants8.O_SYMLINK);
3215
+ var fd = fs6.openSync(path9, constants9.O_SYMLINK);
3216
3216
  var ret;
3217
3217
  var threw = true;
3218
3218
  try {
@@ -3495,14 +3495,14 @@ var require_graceful_fs = __commonJS({
3495
3495
  return close;
3496
3496
  })(fs5.close);
3497
3497
  fs5.closeSync = (function(fs$closeSync) {
3498
- function closeSync8(fd) {
3498
+ function closeSync9(fd) {
3499
3499
  fs$closeSync.apply(fs5, arguments);
3500
3500
  resetQueue();
3501
3501
  }
3502
- Object.defineProperty(closeSync8, previousSymbol, {
3502
+ Object.defineProperty(closeSync9, previousSymbol, {
3503
3503
  value: fs$closeSync
3504
3504
  });
3505
- return closeSync8;
3505
+ return closeSync9;
3506
3506
  })(fs5.closeSync);
3507
3507
  if (/\bgfs4\b/i.test(process.env.NODE_DEBUG || "")) {
3508
3508
  process.on("exit", function() {
@@ -4963,7 +4963,7 @@ var require_rc = __commonJS({
4963
4963
  "node_modules/.pnpm/rc@1.2.8/node_modules/rc/index.js"(exports, module) {
4964
4964
  "use strict";
4965
4965
  var cc = require_utils();
4966
- var join28 = __require("path").join;
4966
+ var join30 = __require("path").join;
4967
4967
  var deepExtend = require_deep_extend();
4968
4968
  var etc = "/etc";
4969
4969
  var win = process.platform === "win32";
@@ -4988,15 +4988,15 @@ var require_rc = __commonJS({
4988
4988
  }
4989
4989
  if (!win)
4990
4990
  [
4991
- join28(etc, name, "config"),
4992
- join28(etc, name + "rc")
4991
+ join30(etc, name, "config"),
4992
+ join30(etc, name + "rc")
4993
4993
  ].forEach(addConfigFile);
4994
4994
  if (home)
4995
4995
  [
4996
- join28(home, ".config", name, "config"),
4997
- join28(home, ".config", name),
4998
- join28(home, "." + name, "config"),
4999
- join28(home, "." + name + "rc")
4996
+ join30(home, ".config", name, "config"),
4997
+ join30(home, ".config", name),
4998
+ join30(home, "." + name, "config"),
4999
+ join30(home, "." + name + "rc")
5000
5000
  ].forEach(addConfigFile);
5001
5001
  addConfigFile(cc.find("." + name + "rc"));
5002
5002
  if (env4.config) addConfigFile(env4.config);
@@ -5014,7 +5014,7 @@ var require_rc = __commonJS({
5014
5014
  var require_polyfills2 = __commonJS({
5015
5015
  "node_modules/.pnpm/graceful-fs@4.2.10/node_modules/graceful-fs/polyfills.js"(exports, module) {
5016
5016
  "use strict";
5017
- var constants8 = __require("constants");
5017
+ var constants9 = __require("constants");
5018
5018
  var origCwd = process.cwd;
5019
5019
  var cwd = null;
5020
5020
  var platform2 = process.env.GRACEFUL_FS_PLATFORM || process.platform;
@@ -5038,7 +5038,7 @@ var require_polyfills2 = __commonJS({
5038
5038
  var chdir;
5039
5039
  module.exports = patch;
5040
5040
  function patch(fs5) {
5041
- if (constants8.hasOwnProperty("O_SYMLINK") && process.version.match(/^v0\.6\.[0-2]|^v0\.5\./)) {
5041
+ if (constants9.hasOwnProperty("O_SYMLINK") && process.version.match(/^v0\.6\.[0-2]|^v0\.5\./)) {
5042
5042
  patchLchmod(fs5);
5043
5043
  }
5044
5044
  if (!fs5.lutimes) {
@@ -5140,7 +5140,7 @@ var require_polyfills2 = __commonJS({
5140
5140
  fs6.lchmod = function(path9, mode, callback) {
5141
5141
  fs6.open(
5142
5142
  path9,
5143
- constants8.O_WRONLY | constants8.O_SYMLINK,
5143
+ constants9.O_WRONLY | constants9.O_SYMLINK,
5144
5144
  mode,
5145
5145
  function(err, fd) {
5146
5146
  if (err) {
@@ -5156,7 +5156,7 @@ var require_polyfills2 = __commonJS({
5156
5156
  );
5157
5157
  };
5158
5158
  fs6.lchmodSync = function(path9, mode) {
5159
- var fd = fs6.openSync(path9, constants8.O_WRONLY | constants8.O_SYMLINK, mode);
5159
+ var fd = fs6.openSync(path9, constants9.O_WRONLY | constants9.O_SYMLINK, mode);
5160
5160
  var threw = true;
5161
5161
  var ret;
5162
5162
  try {
@@ -5176,9 +5176,9 @@ var require_polyfills2 = __commonJS({
5176
5176
  };
5177
5177
  }
5178
5178
  function patchLutimes(fs6) {
5179
- if (constants8.hasOwnProperty("O_SYMLINK") && fs6.futimes) {
5179
+ if (constants9.hasOwnProperty("O_SYMLINK") && fs6.futimes) {
5180
5180
  fs6.lutimes = function(path9, at, mt, cb) {
5181
- fs6.open(path9, constants8.O_SYMLINK, function(er, fd) {
5181
+ fs6.open(path9, constants9.O_SYMLINK, function(er, fd) {
5182
5182
  if (er) {
5183
5183
  if (cb) cb(er);
5184
5184
  return;
@@ -5191,7 +5191,7 @@ var require_polyfills2 = __commonJS({
5191
5191
  });
5192
5192
  };
5193
5193
  fs6.lutimesSync = function(path9, at, mt) {
5194
- var fd = fs6.openSync(path9, constants8.O_SYMLINK);
5194
+ var fd = fs6.openSync(path9, constants9.O_SYMLINK);
5195
5195
  var ret;
5196
5196
  var threw = true;
5197
5197
  try {
@@ -5474,14 +5474,14 @@ var require_graceful_fs2 = __commonJS({
5474
5474
  return close;
5475
5475
  })(fs5.close);
5476
5476
  fs5.closeSync = (function(fs$closeSync) {
5477
- function closeSync8(fd) {
5477
+ function closeSync9(fd) {
5478
5478
  fs$closeSync.apply(fs5, arguments);
5479
5479
  resetQueue();
5480
5480
  }
5481
- Object.defineProperty(closeSync8, previousSymbol, {
5481
+ Object.defineProperty(closeSync9, previousSymbol, {
5482
5482
  value: fs$closeSync
5483
5483
  });
5484
- return closeSync8;
5484
+ return closeSync9;
5485
5485
  })(fs5.closeSync);
5486
5486
  if (/\bgfs4\b/i.test(process.env.NODE_DEBUG || "")) {
5487
5487
  process.on("exit", function() {
@@ -6340,11 +6340,11 @@ var require_util = __commonJS({
6340
6340
  if (files.includes("node_modules") || files.includes("package.json") || files.includes("package.json5") || files.includes("package.yaml") || files.includes("pnpm-workspace.yaml")) {
6341
6341
  return name2;
6342
6342
  }
6343
- const dirname19 = path9.dirname(name2);
6344
- if (dirname19 === name2) {
6343
+ const dirname21 = path9.dirname(name2);
6344
+ if (dirname21 === name2) {
6345
6345
  return original;
6346
6346
  }
6347
- return find(dirname19, original);
6347
+ return find(dirname21, original);
6348
6348
  } catch (error) {
6349
6349
  if (name2 === original) {
6350
6350
  if (error.code === "ENOENT") {
@@ -8313,7 +8313,7 @@ var require_semver2 = __commonJS({
8313
8313
  "node_modules/.pnpm/semver@7.7.4/node_modules/semver/index.js"(exports, module) {
8314
8314
  "use strict";
8315
8315
  var internalRe = require_re();
8316
- var constants8 = require_constants();
8316
+ var constants9 = require_constants();
8317
8317
  var SemVer = require_semver();
8318
8318
  var identifiers = require_identifiers();
8319
8319
  var parse2 = require_parse();
@@ -8395,8 +8395,8 @@ var require_semver2 = __commonJS({
8395
8395
  re: internalRe.re,
8396
8396
  src: internalRe.src,
8397
8397
  tokens: internalRe.t,
8398
- SEMVER_SPEC_VERSION: constants8.SEMVER_SPEC_VERSION,
8399
- RELEASE_TYPES: constants8.RELEASE_TYPES,
8398
+ SEMVER_SPEC_VERSION: constants9.SEMVER_SPEC_VERSION,
8399
+ RELEASE_TYPES: constants9.RELEASE_TYPES,
8400
8400
  compareIdentifiers: identifiers.compareIdentifiers,
8401
8401
  rcompareIdentifiers: identifiers.rcompareIdentifiers
8402
8402
  };
@@ -8896,8 +8896,8 @@ var require_ucs2length = __commonJS({
8896
8896
  });
8897
8897
 
8898
8898
  // src/index.ts
8899
- import { readFileSync as readFileSync22 } from "fs";
8900
- import { dirname as dirname18, resolve as resolve10 } from "path";
8899
+ import { readFileSync as readFileSync24 } from "fs";
8900
+ import { dirname as dirname20, resolve as resolve10 } from "path";
8901
8901
  import { fileURLToPath as fileURLToPath7 } from "url";
8902
8902
 
8903
8903
  // node_modules/.pnpm/commander@12.1.0/node_modules/commander/esm.mjs
@@ -9257,14 +9257,14 @@ var Temp = {
9257
9257
  }
9258
9258
  },
9259
9259
  truncate: (filePath) => {
9260
- const basename4 = path2.basename(filePath);
9261
- if (basename4.length <= LIMIT_BASENAME_LENGTH)
9260
+ const basename5 = path2.basename(filePath);
9261
+ if (basename5.length <= LIMIT_BASENAME_LENGTH)
9262
9262
  return filePath;
9263
- const truncable = /^(\.?)(.*?)((?:\.[^.]+)?(?:\.tmp-\d{10}[a-f0-9]{6})?)$/.exec(basename4);
9263
+ const truncable = /^(\.?)(.*?)((?:\.[^.]+)?(?:\.tmp-\d{10}[a-f0-9]{6})?)$/.exec(basename5);
9264
9264
  if (!truncable)
9265
9265
  return filePath;
9266
- const truncationLength = basename4.length - LIMIT_BASENAME_LENGTH;
9267
- return `${filePath.slice(0, -basename4.length)}${truncable[1]}${truncable[2].slice(0, -truncationLength)}${truncable[3]}`;
9266
+ const truncationLength = basename5.length - LIMIT_BASENAME_LENGTH;
9267
+ return `${filePath.slice(0, -basename5.length)}${truncable[1]}${truncable[2].slice(0, -truncationLength)}${truncable[3]}`;
9268
9268
  }
9269
9269
  };
9270
9270
  node_default(Temp.purgeSyncAll);
@@ -12398,13 +12398,13 @@ function sameIssuerUrl(value, field, issuer) {
12398
12398
  return parsed.toString();
12399
12399
  }
12400
12400
  function parseWorkosConnectConfiguration(value) {
12401
- const record2 = recordFrom(value, "Connect discovery");
12401
+ const record3 = recordFrom(value, "Connect discovery");
12402
12402
  const expectedKeys = ["protocol_version", "issuer", "client_id", "default_scopes"];
12403
- if (record2.protocol_version !== 1 || Object.keys(record2).length !== expectedKeys.length || expectedKeys.some((key) => !Object.hasOwn(record2, key)) || !Array.isArray(record2.default_scopes) || record2.default_scopes.length !== WORKOS_CONNECT_DEFAULT_SCOPES.length || record2.default_scopes.some((scope, index) => scope !== WORKOS_CONNECT_DEFAULT_SCOPES[index])) {
12403
+ if (record3.protocol_version !== 1 || Object.keys(record3).length !== expectedKeys.length || expectedKeys.some((key) => !Object.hasOwn(record3, key)) || !Array.isArray(record3.default_scopes) || record3.default_scopes.length !== WORKOS_CONNECT_DEFAULT_SCOPES.length || record3.default_scopes.some((scope, index) => scope !== WORKOS_CONNECT_DEFAULT_SCOPES[index])) {
12404
12404
  throw new WorkosConnectProtocolError("invalid_response", "Connect discovery is invalid");
12405
12405
  }
12406
- const issuer = canonicalIssuer(record2.issuer);
12407
- const clientId = exactString(record2.client_id, "client_id", MAX_CLIENT_ID_LENGTH);
12406
+ const issuer = canonicalIssuer(record3.issuer);
12407
+ const clientId = exactString(record3.client_id, "client_id", MAX_CLIENT_ID_LENGTH);
12408
12408
  if (!CONNECT_CLIENT_ID_PATTERN.test(clientId)) {
12409
12409
  throw new WorkosConnectProtocolError("invalid_response", "Connect discovery is invalid");
12410
12410
  }
@@ -12415,14 +12415,14 @@ function parseWorkosConnectConfiguration(value) {
12415
12415
  };
12416
12416
  }
12417
12417
  function parseWorkosConnectCredentialMetadata(value) {
12418
- const record2 = recordFrom(value, "credential metadata");
12419
- if (record2.version !== 1 || record2.family !== "workos_connect") {
12418
+ const record3 = recordFrom(value, "credential metadata");
12419
+ if (record3.version !== 1 || record3.family !== "workos_connect") {
12420
12420
  throw new WorkosConnectProtocolError("invalid_metadata", "credential metadata is invalid");
12421
12421
  }
12422
- const issuer = canonicalIssuer(record2.issuer);
12423
- const clientId = exactString(record2.clientId, "clientId", MAX_CLIENT_ID_LENGTH);
12424
- const accessTokenHash = exactString(record2.accessTokenHash, "accessTokenHash", 64);
12425
- const refreshTokenHash = exactString(record2.refreshTokenHash, "refreshTokenHash", 64);
12422
+ const issuer = canonicalIssuer(record3.issuer);
12423
+ const clientId = exactString(record3.clientId, "clientId", MAX_CLIENT_ID_LENGTH);
12424
+ const accessTokenHash = exactString(record3.accessTokenHash, "accessTokenHash", 64);
12425
+ const refreshTokenHash = exactString(record3.refreshTokenHash, "refreshTokenHash", 64);
12426
12426
  if (!CONNECT_CLIENT_ID_PATTERN.test(clientId)) {
12427
12427
  throw new WorkosConnectProtocolError("invalid_metadata", "credential clientId is invalid");
12428
12428
  }
@@ -12526,37 +12526,37 @@ async function requestWorkosDeviceAuthorization(configuration, fetchImpl = fetch
12526
12526
  `Device authorization returned HTTP ${response.status}`
12527
12527
  );
12528
12528
  }
12529
- const record2 = recordFrom(value, "device authorization");
12529
+ const record3 = recordFrom(value, "device authorization");
12530
12530
  const verificationUri = sameIssuerUrl(
12531
- record2.verification_uri,
12531
+ record3.verification_uri,
12532
12532
  "verification_uri",
12533
12533
  configuration.issuer
12534
12534
  );
12535
12535
  return {
12536
- deviceCode: exactString(record2.device_code, "device_code", MAX_DEVICE_CODE_LENGTH),
12537
- userCode: exactString(record2.user_code, "user_code", MAX_USER_CODE_LENGTH),
12536
+ deviceCode: exactString(record3.device_code, "device_code", MAX_DEVICE_CODE_LENGTH),
12537
+ userCode: exactString(record3.user_code, "user_code", MAX_USER_CODE_LENGTH),
12538
12538
  verificationUri,
12539
- verificationUriComplete: record2.verification_uri_complete === void 0 ? void 0 : sameIssuerUrl(
12540
- record2.verification_uri_complete,
12539
+ verificationUriComplete: record3.verification_uri_complete === void 0 ? void 0 : sameIssuerUrl(
12540
+ record3.verification_uri_complete,
12541
12541
  "verification_uri_complete",
12542
12542
  configuration.issuer
12543
12543
  ),
12544
- expiresIn: safeInteger(record2.expires_in, "expires_in", 1, MAX_DEVICE_LIFETIME_SECONDS),
12545
- interval: safeInteger(record2.interval ?? 5, "interval", 1, MAX_INTERVAL_SECONDS)
12544
+ expiresIn: safeInteger(record3.expires_in, "expires_in", 1, MAX_DEVICE_LIFETIME_SECONDS),
12545
+ interval: safeInteger(record3.interval ?? 5, "interval", 1, MAX_INTERVAL_SECONDS)
12546
12546
  };
12547
12547
  }
12548
12548
  function parseWorkosConnectTokens(value, options = {}) {
12549
- const record2 = recordFrom(value, "token response");
12549
+ const record3 = recordFrom(value, "token response");
12550
12550
  const accessToken = exactString(
12551
- record2.access_token,
12551
+ record3.access_token,
12552
12552
  "access_token",
12553
12553
  WORKOS_CONNECT_RESPONSE_MAX_BYTES
12554
12554
  );
12555
- const refreshToken2 = record2.refresh_token === void 0 && options.fallbackRefreshToken !== void 0 ? options.fallbackRefreshToken : exactString(record2.refresh_token, "refresh_token", WORKOS_CONNECT_RESPONSE_MAX_BYTES);
12555
+ const refreshToken2 = record3.refresh_token === void 0 && options.fallbackRefreshToken !== void 0 ? options.fallbackRefreshToken : exactString(record3.refresh_token, "refresh_token", WORKOS_CONNECT_RESPONSE_MAX_BYTES);
12556
12556
  return {
12557
12557
  accessToken,
12558
12558
  refreshToken: refreshToken2,
12559
- expiresIn: record2.expires_in === void 0 ? void 0 : safeInteger(record2.expires_in, "expires_in", 1, MAX_TOKEN_LIFETIME_SECONDS)
12559
+ expiresIn: record3.expires_in === void 0 ? void 0 : safeInteger(record3.expires_in, "expires_in", 1, MAX_TOKEN_LIFETIME_SECONDS)
12560
12560
  };
12561
12561
  }
12562
12562
  function providerError(value) {
@@ -12678,15 +12678,15 @@ function parseLegacyBrokerCredentialMetadata(value) {
12678
12678
  if (value === null || typeof value !== "object" || Array.isArray(value)) {
12679
12679
  throw new Error("credential family is invalid");
12680
12680
  }
12681
- const record2 = value;
12682
- if (record2.version !== 1 || record2.family !== "legacy_broker" || typeof record2.accessTokenHash !== "string" || typeof record2.refreshTokenHash !== "string" || !/^[a-f0-9]{64}$/u.test(record2.accessTokenHash) || !/^[a-f0-9]{64}$/u.test(record2.refreshTokenHash)) {
12681
+ const record3 = value;
12682
+ if (record3.version !== 1 || record3.family !== "legacy_broker" || typeof record3.accessTokenHash !== "string" || typeof record3.refreshTokenHash !== "string" || !/^[a-f0-9]{64}$/u.test(record3.accessTokenHash) || !/^[a-f0-9]{64}$/u.test(record3.refreshTokenHash)) {
12683
12683
  throw new Error("credential family is invalid");
12684
12684
  }
12685
12685
  return {
12686
12686
  version: 1,
12687
12687
  family: "legacy_broker",
12688
- accessTokenHash: record2.accessTokenHash,
12689
- refreshTokenHash: record2.refreshTokenHash
12688
+ accessTokenHash: record3.accessTokenHash,
12689
+ refreshTokenHash: record3.refreshTokenHash
12690
12690
  };
12691
12691
  }
12692
12692
  function readBoundedCredentialJson(path9) {
@@ -12721,9 +12721,9 @@ function readCredentialFamily(path9) {
12721
12721
  }
12722
12722
  function isCredentialMigrationSentinel(value) {
12723
12723
  if (value === null || typeof value !== "object" || Array.isArray(value)) return false;
12724
- const record2 = value;
12725
- const keys = Object.keys(record2).sort();
12726
- return keys.length === 2 && keys[0] === "state" && keys[1] === "version" && record2.version === CREDENTIAL_MIGRATION_VERSION && record2.state === CREDENTIAL_MIGRATION_STATE;
12724
+ const record3 = value;
12725
+ const keys = Object.keys(record3).sort();
12726
+ return keys.length === 2 && keys[0] === "state" && keys[1] === "version" && record3.version === CREDENTIAL_MIGRATION_VERSION && record3.state === CREDENTIAL_MIGRATION_STATE;
12727
12727
  }
12728
12728
  function readCredentialMigrationState(path9 = CREDENTIAL_MIGRATION_PATH) {
12729
12729
  const parsed = readBoundedCredentialJson(path9);
@@ -13837,10 +13837,10 @@ async function performTokenRefresh(options = {}) {
13837
13837
  }
13838
13838
  return void 0;
13839
13839
  }
13840
- const record2 = typeof data === "object" && data !== null && !Array.isArray(data) ? data : void 0;
13841
- accessToken = typeof record2?.access_token === "string" ? record2.access_token.trim() : "";
13842
- replacementRefreshToken = typeof record2?.refresh_token === "string" ? record2.refresh_token.trim() : "";
13843
- expiresIn = typeof record2?.expires_in === "number" && Number.isFinite(record2.expires_in) ? record2.expires_in : void 0;
13840
+ const record3 = typeof data === "object" && data !== null && !Array.isArray(data) ? data : void 0;
13841
+ accessToken = typeof record3?.access_token === "string" ? record3.access_token.trim() : "";
13842
+ replacementRefreshToken = typeof record3?.refresh_token === "string" ? record3.refresh_token.trim() : "";
13843
+ expiresIn = typeof record3?.expires_in === "number" && Number.isFinite(record3.expires_in) ? record3.expires_in : void 0;
13844
13844
  if (!(accessToken && replacementRefreshToken)) {
13845
13845
  if (readTrimmed2(REFRESH_TOKEN_PATH) === currentGeneration) {
13846
13846
  writeTerminalFingerprint(currentGeneration);
@@ -31856,19 +31856,19 @@ var defaultDependencies = {
31856
31856
  function isSafeVersion(value) {
31857
31857
  return typeof value === "number" && Number.isSafeInteger(value) && value >= 0;
31858
31858
  }
31859
- function optionalStringArray(record2, key) {
31860
- const value = record2[key];
31859
+ function optionalStringArray(record3, key) {
31860
+ const value = record3[key];
31861
31861
  if (value === void 0) return void 0;
31862
31862
  if (!Array.isArray(value) || value.some((item) => typeof item !== "string")) return void 0;
31863
31863
  return [...value];
31864
31864
  }
31865
- function optionalSafeInteger(record2, key) {
31866
- const value = record2[key];
31865
+ function optionalSafeInteger(record3, key) {
31866
+ const value = record3[key];
31867
31867
  if (value === void 0) return void 0;
31868
31868
  return typeof value === "number" && Number.isSafeInteger(value) ? value : void 0;
31869
31869
  }
31870
- function optionalUserScopeMembers(record2) {
31871
- const value = record2.users;
31870
+ function optionalUserScopeMembers(record3) {
31871
+ const value = record3.users;
31872
31872
  if (value === void 0) return void 0;
31873
31873
  if (!Array.isArray(value)) return void 0;
31874
31874
  const members = [];
@@ -31897,27 +31897,27 @@ function optionalUserScopeMembers(record2) {
31897
31897
  }
31898
31898
  function policyFromUnknown(value) {
31899
31899
  if (typeof value !== "object" || value === null || Array.isArray(value)) return void 0;
31900
- const record2 = value;
31901
- if (!isSafeVersion(record2.updatedAt)) return void 0;
31900
+ const record3 = value;
31901
+ if (!isSafeVersion(record3.updatedAt)) return void 0;
31902
31902
  const fields = {
31903
- repositories: optionalStringArray(record2, "repositories"),
31904
- directories: optionalStringArray(record2, "directories"),
31905
- globs: optionalStringArray(record2, "globs"),
31906
- branches: optionalStringArray(record2, "branches"),
31907
- effectiveFrom: optionalSafeInteger(record2, "effectiveFrom"),
31908
- effectiveUntil: optionalSafeInteger(record2, "effectiveUntil"),
31909
- users: optionalUserScopeMembers(record2)
31903
+ repositories: optionalStringArray(record3, "repositories"),
31904
+ directories: optionalStringArray(record3, "directories"),
31905
+ globs: optionalStringArray(record3, "globs"),
31906
+ branches: optionalStringArray(record3, "branches"),
31907
+ effectiveFrom: optionalSafeInteger(record3, "effectiveFrom"),
31908
+ effectiveUntil: optionalSafeInteger(record3, "effectiveUntil"),
31909
+ users: optionalUserScopeMembers(record3)
31910
31910
  };
31911
31911
  if (Object.entries(fields).some(
31912
- ([key, parsed]) => record2[key] !== void 0 && parsed === void 0
31912
+ ([key, parsed]) => record3[key] !== void 0 && parsed === void 0
31913
31913
  )) {
31914
31914
  return void 0;
31915
31915
  }
31916
- if (record2.effectiveFrom !== void 0 && fields.effectiveFrom === void 0 || record2.effectiveUntil !== void 0 && fields.effectiveUntil === void 0 || fields.effectiveFrom !== void 0 && fields.effectiveUntil !== void 0 && fields.effectiveFrom >= fields.effectiveUntil) {
31916
+ if (record3.effectiveFrom !== void 0 && fields.effectiveFrom === void 0 || record3.effectiveUntil !== void 0 && fields.effectiveUntil === void 0 || fields.effectiveFrom !== void 0 && fields.effectiveUntil !== void 0 && fields.effectiveFrom >= fields.effectiveUntil) {
31917
31917
  return void 0;
31918
31918
  }
31919
31919
  return {
31920
- updatedAt: record2.updatedAt,
31920
+ updatedAt: record3.updatedAt,
31921
31921
  ...fields.repositories === void 0 ? {} : { repositories: fields.repositories },
31922
31922
  ...fields.directories === void 0 ? {} : { directories: fields.directories },
31923
31923
  ...fields.globs === void 0 ? {} : { globs: fields.globs },
@@ -33697,18 +33697,18 @@ function parseAuthBrokerConfig(value) {
33697
33697
  if (value === null || typeof value !== "object" || Array.isArray(value)) {
33698
33698
  throw new Error("broker config must be an object");
33699
33699
  }
33700
- const record2 = value;
33701
- if (typeof record2.authorization_server !== "string" || !record2.authorization_server.trim()) {
33700
+ const record3 = value;
33701
+ if (typeof record3.authorization_server !== "string" || !record3.authorization_server.trim()) {
33702
33702
  throw new Error("authorization_server is required");
33703
33703
  }
33704
- if (typeof record2.client_id !== "string" || !record2.client_id.trim()) {
33704
+ if (typeof record3.client_id !== "string" || !record3.client_id.trim()) {
33705
33705
  throw new Error("client_id is required");
33706
33706
  }
33707
- if (!Array.isArray(record2.default_scopes) || !record2.default_scopes.every((scope) => typeof scope === "string" && scope.length > 0)) {
33707
+ if (!Array.isArray(record3.default_scopes) || !record3.default_scopes.every((scope) => typeof scope === "string" && scope.length > 0)) {
33708
33708
  throw new Error("default_scopes must be a string array");
33709
33709
  }
33710
- const authorizationServer = parseHttpUrl(record2.authorization_server, "authorization_server");
33711
- const rawEndpoint = record2.authorization_endpoint === void 0 ? DEFAULT_WORKOS_AUTHORIZE_URL : record2.authorization_endpoint;
33710
+ const authorizationServer = parseHttpUrl(record3.authorization_server, "authorization_server");
33711
+ const rawEndpoint = record3.authorization_endpoint === void 0 ? DEFAULT_WORKOS_AUTHORIZE_URL : record3.authorization_endpoint;
33712
33712
  if (typeof rawEndpoint !== "string" || !rawEndpoint.trim()) {
33713
33713
  throw new Error("authorization_endpoint must be a URL");
33714
33714
  }
@@ -33719,8 +33719,8 @@ function parseAuthBrokerConfig(value) {
33719
33719
  return {
33720
33720
  authorization_server: authorizationServer.toString(),
33721
33721
  authorization_endpoint: authorizationEndpoint.toString(),
33722
- client_id: record2.client_id.trim(),
33723
- default_scopes: [...record2.default_scopes]
33722
+ client_id: record3.client_id.trim(),
33723
+ default_scopes: [...record3.default_scopes]
33724
33724
  };
33725
33725
  }
33726
33726
  async function fetchAuthBrokerConfig(siteUrl, fetchImpl = fetch) {
@@ -36386,7 +36386,7 @@ function registerCursorCommands(program3) {
36386
36386
 
36387
36387
  // src/commands/daemon.ts
36388
36388
  import { spawn as spawn3 } from "child_process";
36389
- import { closeSync as closeSync3, existsSync as existsSync12, mkdirSync as mkdirSync9, openSync as openSync3, readFileSync as readFileSync13, unlinkSync as unlinkSync3 } from "fs";
36389
+ import { closeSync as closeSync4, existsSync as existsSync12, mkdirSync as mkdirSync9, openSync as openSync4, readFileSync as readFileSync13, unlinkSync as unlinkSync3 } from "fs";
36390
36390
  import { join as join16 } from "path";
36391
36391
 
36392
36392
  // src/daemon/latest-bootstrap.ts
@@ -36458,12 +36458,12 @@ async function runLatestDaemonBootstrap(ensureCurrent, options = {}) {
36458
36458
  import { spawnSync } from "child_process";
36459
36459
  import { createHash as createHash8, randomBytes as randomBytes5 } from "crypto";
36460
36460
  import {
36461
- constants as constants4,
36461
+ constants as constants5,
36462
36462
  accessSync as accessSync2,
36463
36463
  chmodSync as chmodSync4,
36464
36464
  copyFileSync as copyFileSync2,
36465
36465
  existsSync as existsSync11,
36466
- lstatSync as lstatSync5,
36466
+ lstatSync as lstatSync6,
36467
36467
  mkdirSync as mkdirSync8,
36468
36468
  mkdtempSync as mkdtempSync2,
36469
36469
  readFileSync as readFileSync12,
@@ -36472,16 +36472,53 @@ import {
36472
36472
  realpathSync as realpathSync3,
36473
36473
  renameSync as renameSync4,
36474
36474
  rmSync as rmSync5,
36475
+ statSync as statSync3,
36475
36476
  symlinkSync,
36476
36477
  writeFileSync as writeFileSync7
36477
36478
  } from "fs";
36478
36479
  import { homedir as homedir8 } from "os";
36479
36480
  import { basename as basename3, dirname as dirname10, isAbsolute as isAbsolute7, join as join15, resolve as resolve6 } from "path";
36480
36481
 
36482
+ // src/lib/bounded-file.ts
36483
+ import { constants as constants4, closeSync as closeSync3, fstatSync, lstatSync as lstatSync5, openSync as openSync3, readSync } from "fs";
36484
+ function readBoundedRegularFile(path9, maxBytes) {
36485
+ let fd;
36486
+ try {
36487
+ const linked = lstatSync5(path9);
36488
+ if (!linked.isFile() || linked.size > maxBytes) return void 0;
36489
+ fd = openSync3(path9, constants4.O_RDONLY | constants4.O_NOFOLLOW | constants4.O_NONBLOCK);
36490
+ const opened = fstatSync(fd);
36491
+ if (!opened.isFile() || opened.dev !== linked.dev || opened.ino !== linked.ino || opened.size > maxBytes) {
36492
+ return void 0;
36493
+ }
36494
+ const buffer = Buffer.allocUnsafe(opened.size + 1);
36495
+ let length = 0;
36496
+ while (length < buffer.length) {
36497
+ const count = readSync(fd, buffer, length, buffer.length - length, length);
36498
+ if (count === 0) break;
36499
+ length += count;
36500
+ }
36501
+ if (length > opened.size) return void 0;
36502
+ return { text: buffer.subarray(0, length).toString("utf8"), ownerUid: opened.uid };
36503
+ } catch {
36504
+ return void 0;
36505
+ } finally {
36506
+ if (fd !== void 0) {
36507
+ try {
36508
+ closeSync3(fd);
36509
+ } catch {
36510
+ }
36511
+ }
36512
+ }
36513
+ }
36514
+
36481
36515
  // src/daemon/env-binding.ts
36482
36516
  function normalizeApiUrl(url) {
36483
36517
  return url.trim().replace(/\/+$/, "");
36484
36518
  }
36519
+ function apiUrlsMatch(a, b) {
36520
+ return normalizeApiUrl(a) === normalizeApiUrl(b);
36521
+ }
36485
36522
 
36486
36523
  // src/daemon/launchd.ts
36487
36524
  var LAUNCHD_LABEL = "ai.getprimitive.prim-daemon";
@@ -36500,6 +36537,11 @@ var SERVICE_NOT_FOUND = 113;
36500
36537
  var TRANSITION_IN_PROGRESS = 5;
36501
36538
  var OUTPUT_LIMIT = 4096;
36502
36539
  var DAEMON_DISABLED_CONTENT = "disabled by `prim daemon stop`\n";
36540
+ var LAUNCHER_MAX_BYTES = 64 * 1024;
36541
+ var PLIST_MAX_BYTES = 64 * 1024;
36542
+ var DRIFT_HEAL_MARKER = "daemon-drift-heal.json";
36543
+ var DRIFT_HEAL_MARKER_TEMPORARY_RE = /^daemon-drift-heal\.json\.[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\.tmp$/u;
36544
+ var DRIFT_HEAL_CLAIM_OWNER = "owner.json";
36503
36545
  var LaunchctlError = class extends Error {
36504
36546
  status;
36505
36547
  timedOut;
@@ -36539,6 +36581,8 @@ function daemonControlPaths(options = {}) {
36539
36581
  return {
36540
36582
  launcher: join15(configDir, `prim-daemon-launcher-v${LAUNCHER_SCHEMA_VERSION}`),
36541
36583
  disabledMarker: join15(configDir, "daemon.disabled"),
36584
+ driftHealMarker: join15(configDir, DRIFT_HEAL_MARKER),
36585
+ driftHealClaim: join15(configDir, "daemon-drift-heal.lock"),
36542
36586
  lifecycleLockDir: join15(configDir, "daemon.lifecycle.lock"),
36543
36587
  legacyDisabledMarker: legacy.disabledMarker,
36544
36588
  legacyLifecycleLockDir: legacy.lifecycleLockDir
@@ -36599,7 +36643,7 @@ function readOwnedRuntimeManifest(releaseDir) {
36599
36643
  }
36600
36644
  const manifest = parsed;
36601
36645
  if (manifest.schemaVersion !== RUNTIME_SCHEMA_VERSION || compareSemver(manifest.version, manifest.version) !== 0 || !isAbsolute7(manifest.nodePath) || /[\r\n]/u.test(manifest.nodePath) || manifest.daemonFile !== "prim-daemon-server" || !/^[0-9a-f]{64}$/u.test(manifest.daemonSha256) || new Date(manifest.stagedAt).toISOString() !== manifest.stagedAt || raw !== `${JSON.stringify(manifest, null, 2)}
36602
- ` || !lstatSync5(daemonPath).isFile() || sha256File(daemonPath) !== manifest.daemonSha256) {
36646
+ ` || !lstatSync6(daemonPath).isFile() || sha256File(daemonPath) !== manifest.daemonSha256) {
36603
36647
  return null;
36604
36648
  }
36605
36649
  const entries = readdirSync3(releaseDir).sort();
@@ -36608,8 +36652,17 @@ function readOwnedRuntimeManifest(releaseDir) {
36608
36652
  return null;
36609
36653
  }
36610
36654
  }
36655
+ function sameNode(recorded, desired) {
36656
+ if (recorded === desired) return true;
36657
+ try {
36658
+ const target = realpathSync3(desired);
36659
+ return recorded !== target && realpathSync3(recorded) === target;
36660
+ } catch {
36661
+ return false;
36662
+ }
36663
+ }
36611
36664
  function sameRuntime(current, desired, daemonPath) {
36612
- if (!current || current.version !== desired.version || current.nodePath !== desired.nodePath || current.daemonSha256 !== desired.daemonSha256) {
36665
+ if (!current || current.version !== desired.version || !sameNode(current.nodePath, desired.nodePath) || current.daemonSha256 !== desired.daemonSha256) {
36613
36666
  return false;
36614
36667
  }
36615
36668
  try {
@@ -36637,7 +36690,7 @@ function stageRuntime(options = {}) {
36637
36690
  if (!daemonSource || !existsSync11(daemonSource)) {
36638
36691
  throw new Error("cannot stage runtime: prim-daemon-server bundle is unavailable");
36639
36692
  }
36640
- const nodePath = resolve6(options.nodePath ?? process.execPath);
36693
+ const nodePath = resolve6(options.nodePath ?? stableNodePath());
36641
36694
  const version = options.version ?? findPackageVersion(daemonSource);
36642
36695
  const desired = {
36643
36696
  schemaVersion: RUNTIME_SCHEMA_VERSION,
@@ -36736,13 +36789,30 @@ function assertRegularFile(path9, label) {
36736
36789
  }
36737
36790
  function lstatIfPresent3(path9) {
36738
36791
  try {
36739
- return lstatSync5(path9);
36792
+ return lstatSync6(path9);
36740
36793
  } catch (error) {
36741
36794
  const code = error.code;
36742
36795
  if (code === "ENOENT" || code === "ENOTDIR") return void 0;
36743
36796
  throw error;
36744
36797
  }
36745
36798
  }
36799
+ function driftHealMarkerTemporaries(configDir) {
36800
+ try {
36801
+ return readdirSync3(configDir).filter((entry) => DRIFT_HEAL_MARKER_TEMPORARY_RE.test(entry)).map((entry) => join15(configDir, entry));
36802
+ } catch (error) {
36803
+ const code = error.code;
36804
+ if (code === "ENOENT" || code === "ENOTDIR") return [];
36805
+ throw error;
36806
+ }
36807
+ }
36808
+ function assertDriftHealClaim(path9) {
36809
+ const claim = lstatIfPresent3(path9);
36810
+ if (!claim) return;
36811
+ if (!claim.isDirectory() || readdirSync3(path9).some((entry) => entry !== DRIFT_HEAL_CLAIM_OWNER)) {
36812
+ throw new Error(`refusing to remove unrecognized daemon drift-heal claim at ${path9}`);
36813
+ }
36814
+ assertRegularFile(join15(path9, DRIFT_HEAL_CLAIM_OWNER), "daemon drift-heal claim owner");
36815
+ }
36746
36816
  function assertExactFile(path9, expected, label) {
36747
36817
  if (!lstatIfPresent3(path9)) return;
36748
36818
  assertRegularFile(path9, label);
@@ -36781,17 +36851,16 @@ function assertOwnedDaemonRuntime(options) {
36781
36851
  );
36782
36852
  }
36783
36853
  assertRegularFile(logPath, "daemon log");
36854
+ assertRegularFile(control.driftHealMarker, "daemon drift-heal marker");
36855
+ for (const temporary of driftHealMarkerTemporaries(configDir)) {
36856
+ assertRegularFile(temporary, "daemon drift-heal marker temporary");
36857
+ }
36858
+ assertDriftHealClaim(control.driftHealClaim);
36784
36859
  assertRegularFile(join15(configDir, "daemon-health.json"), "daemon health snapshot");
36785
36860
  assertRegularFile(join15(configDir, "client_instance_id"), "daemon client instance id");
36786
36861
  if (service && lstatIfPresent3(service.plistPath)) {
36787
36862
  assertRegularFile(service.plistPath, "launchd property list");
36788
- const expected = generateLaunchAgentPlist({
36789
- launcherPath: control.launcher,
36790
- logPath,
36791
- workingDirectory: options.homeDir ?? homedir8(),
36792
- label: options.label
36793
- });
36794
- if (readFileSync12(service.plistPath, "utf8") !== expected) {
36863
+ if (readFileSync12(service.plistPath, "utf8") !== ownedLaunchAgentPlist(options, logPath)) {
36795
36864
  throw new Error(
36796
36865
  `refusing to remove unrecognized launchd property list at ${service.plistPath}`
36797
36866
  );
@@ -36868,6 +36937,9 @@ async function removeDaemonRuntime(options = {}) {
36868
36937
  control.launcher,
36869
36938
  control.disabledMarker,
36870
36939
  control.legacyDisabledMarker,
36940
+ control.driftHealMarker,
36941
+ ...driftHealMarkerTemporaries(configDir),
36942
+ control.driftHealClaim,
36871
36943
  ...service ? [service.plistPath] : [],
36872
36944
  logPath,
36873
36945
  join15(configDir, "daemon-health.json"),
@@ -36879,7 +36951,7 @@ async function removeDaemonRuntime(options = {}) {
36879
36951
  return withDaemonLifecycleLock(async () => {
36880
36952
  assertOwnedDaemonRuntime(options);
36881
36953
  for (const path9 of ownedArtifacts.filter((candidate) => candidate !== runtime.runtimeDir)) {
36882
- rmSync5(path9, { force: true });
36954
+ rmSync5(path9, { force: true, recursive: path9 === control.driftHealClaim });
36883
36955
  }
36884
36956
  if (lstatIfPresent3(runtime.runtimeDir)) {
36885
36957
  const quarantined = `${runtime.runtimeDir}.uninstall-${String(process.pid)}-${randomBytes5(8).toString("hex")}`;
@@ -36913,9 +36985,22 @@ ${config.configDir ? `export PRIM_CONFIG_DIR=${shellQuote2(config.configDir)}
36913
36985
  `
36914
36986
  };
36915
36987
  }
36988
+ function ownedLaunchAgentPlist(options, logPath) {
36989
+ return generateLaunchAgentPlist({
36990
+ launcherPath: daemonControlPaths(options).launcher,
36991
+ logPath,
36992
+ workingDirectory: options.homeDir ?? homedir8(),
36993
+ label: options.label
36994
+ });
36995
+ }
36916
36996
  function readDaemonLauncher(path9) {
36997
+ return readDaemonLauncherFile(path9)?.config ?? null;
36998
+ }
36999
+ function readDaemonLauncherFile(path9) {
36917
37000
  try {
36918
- const content = readFileSync12(path9, "utf8");
37001
+ const file = readBoundedRegularFile(path9, LAUNCHER_MAX_BYTES);
37002
+ if (!file) return null;
37003
+ const content = file.text;
36919
37004
  const encoded = /^# prim-daemon-launcher: ([A-Za-z0-9_-]+)$/mu.exec(content)?.[1];
36920
37005
  if (!encoded) return null;
36921
37006
  const value = JSON.parse(Buffer.from(encoded, "base64url").toString("utf8"));
@@ -36930,7 +37015,7 @@ function readDaemonLauncher(path9) {
36930
37015
  ...typeof value.configDir === "string" ? { configDir: value.configDir } : {}
36931
37016
  };
36932
37017
  const expected = generateDaemonLauncher(config);
36933
- return expected.revision === value.revision && expected.content === content ? config : null;
37018
+ return expected.revision === value.revision && expected.content === content ? { config, ownerUid: file.ownerUid } : null;
36934
37019
  } catch {
36935
37020
  return null;
36936
37021
  }
@@ -36940,7 +37025,7 @@ function readUsableDaemonConfig(path9) {
36940
37025
  if (!config) return null;
36941
37026
  const manifest = readRuntimeManifest(join15(dirname10(config.daemonPath), "manifest.json"));
36942
37027
  try {
36943
- accessSync2(config.nodePath, constants4.X_OK);
37028
+ accessSync2(config.nodePath, constants5.X_OK);
36944
37029
  return manifest?.version === config.runtimeVersion && manifest.nodePath === config.nodePath && manifest.daemonSha256 === sha256File(config.daemonPath) ? config : null;
36945
37030
  } catch {
36946
37031
  return null;
@@ -37284,6 +37369,68 @@ function setDaemonExplicitlyDisabled(disabled, options = {}) {
37284
37369
  }
37285
37370
  atomicWrite3(paths.disabledMarker, content, RUNTIME_FILE_MODE);
37286
37371
  }
37372
+ function launcherRunnable(config) {
37373
+ try {
37374
+ accessSync2(config.nodePath, constants5.X_OK);
37375
+ return statSync3(config.daemonPath).isFile();
37376
+ } catch {
37377
+ return false;
37378
+ }
37379
+ }
37380
+ function selectedDaemonLauncher(options = {}) {
37381
+ const file = readDaemonLauncherFile(daemonControlPaths(options).launcher);
37382
+ if (!file) return null;
37383
+ const { config, ownerUid } = file;
37384
+ return {
37385
+ runtimeVersion: config.runtimeVersion,
37386
+ ...config.apiUrl ? { apiUrl: config.apiUrl } : {},
37387
+ ownerUid,
37388
+ runnable: launcherRunnable(config)
37389
+ };
37390
+ }
37391
+ var XML_ENTITIES = {
37392
+ "&amp;": "&",
37393
+ "&lt;": "<",
37394
+ "&gt;": ">",
37395
+ "&quot;": '"',
37396
+ "&apos;": "'"
37397
+ };
37398
+ var XML_ENTITY_RE = /&(?:amp|lt|gt|quot|apos);/gu;
37399
+ var PLIST_LABEL_KEY = "<key>Label</key>";
37400
+ var PLIST_PROGRAM_KEY = "<key>ProgramArguments</key>";
37401
+ var PLIST_LABEL_RE = /<key>Label<\/key>\s*<string>([^<]*)<\/string>/u;
37402
+ var PLIST_PROGRAM_RE = /<key>ProgramArguments<\/key>\s*<array>\s*<string>([^<]*)<\/string>/u;
37403
+ function xmlUnescape(value) {
37404
+ if (value.replace(XML_ENTITY_RE, "").includes("&")) return void 0;
37405
+ return value.replace(XML_ENTITY_RE, (entity) => XML_ENTITIES[entity] ?? entity);
37406
+ }
37407
+ function launchAgentProgram(plist) {
37408
+ if (plist.split(PLIST_LABEL_KEY).length !== 2 || plist.split(PLIST_PROGRAM_KEY).length !== 2) {
37409
+ return void 0;
37410
+ }
37411
+ const label = PLIST_LABEL_RE.exec(plist)?.[1];
37412
+ const program3 = PLIST_PROGRAM_RE.exec(plist)?.[1];
37413
+ if (label === void 0 || program3 === void 0) return void 0;
37414
+ const decodedLabel = xmlUnescape(label);
37415
+ const decodedProgram = xmlUnescape(program3);
37416
+ return decodedLabel === void 0 || decodedProgram === void 0 ? void 0 : { label: decodedLabel, program: decodedProgram };
37417
+ }
37418
+ function launchAgentRunsConfigRoot(options = {}) {
37419
+ try {
37420
+ const service = launchdPaths(options);
37421
+ const plist = readBoundedRegularFile(service.plistPath, PLIST_MAX_BYTES);
37422
+ const agent = plist ? launchAgentProgram(plist.text) : void 0;
37423
+ return agent?.label === (options.label ?? LAUNCHD_LABEL) && agent.program === daemonControlPaths(options).launcher;
37424
+ } catch {
37425
+ return false;
37426
+ }
37427
+ }
37428
+ function daemonDriftHealMarkerPath(options = {}) {
37429
+ return daemonControlPaths(options).driftHealMarker;
37430
+ }
37431
+ function daemonDriftHealClaimPath(options = {}) {
37432
+ return daemonControlPaths(options).driftHealClaim;
37433
+ }
37287
37434
  async function ensureMacDaemon(options = {}) {
37288
37435
  return withDaemonLifecycleLock(() => ensureMacDaemonLocked(options), options);
37289
37436
  }
@@ -37471,6 +37618,54 @@ async function bootoutMacDaemonLocked(options) {
37471
37618
  return { wasLoaded, legacyStopped, service };
37472
37619
  }
37473
37620
 
37621
+ // src/lib/delivery-backlog.ts
37622
+ var MS_PER_SECOND = 1e3;
37623
+ var MS_PER_MINUTE = 60 * MS_PER_SECOND;
37624
+ var MS_PER_HOUR = 60 * MS_PER_MINUTE;
37625
+ var MS_PER_DAY = 24 * MS_PER_HOUR;
37626
+ function isNonNegativeInteger(value) {
37627
+ return typeof value === "number" && Number.isInteger(value) && value >= 0;
37628
+ }
37629
+ function isPositiveInteger(value) {
37630
+ return isNonNegativeInteger(value) && value > 0;
37631
+ }
37632
+ function deliveryBacklogState(ingestion) {
37633
+ if (ingestion?.healthy !== false) return void 0;
37634
+ const failures = ingestion.consecutiveFailures;
37635
+ const failedWithoutProgress = failures !== 0 && !(isPositiveInteger(failures) && isPositiveInteger(ingestion.lastFailedDrainAcknowledgedCount));
37636
+ if (failedWithoutProgress) return "failing";
37637
+ const retained = ingestion.lastRetainedBucketCount;
37638
+ if (!isNonNegativeInteger(retained)) return "failing";
37639
+ return retained > 0 ? "retained" : "draining";
37640
+ }
37641
+ function formatBacklogAge(ageMs) {
37642
+ if (ageMs >= MS_PER_DAY) return `${String(Math.floor(ageMs / MS_PER_DAY))}d`;
37643
+ if (ageMs >= MS_PER_HOUR) return `${String(Math.floor(ageMs / MS_PER_HOUR))}h`;
37644
+ if (ageMs >= MS_PER_MINUTE) return `${String(Math.floor(ageMs / MS_PER_MINUTE))}m`;
37645
+ return `${String(Math.max(0, Math.floor(ageMs / MS_PER_SECOND)))}s`;
37646
+ }
37647
+ function formatPendingBacklog(backlog, now = Date.now()) {
37648
+ const count = backlog.pendingCount;
37649
+ const moves = typeof count === "number" && count > 0 ? `${backlog.pendingSampled ? "at least " : ""}${String(count)} pending move${count === 1 ? "" : "s"}` : "an unknown number of pending moves";
37650
+ const age = backlog.oldestPendingAt === void 0 ? "" : ` (oldest ${backlog.pendingSampled ? "\u2265 " : ""}${formatBacklogAge(now - backlog.oldestPendingAt)})`;
37651
+ return `${moves}${age}`;
37652
+ }
37653
+ function deliveryBacklogSummary(ingestion, now = Date.now(), backlog = ingestion) {
37654
+ const state = deliveryBacklogState(ingestion);
37655
+ if (!(state && ingestion && backlog)) return void 0;
37656
+ const pending = formatPendingBacklog(backlog, now);
37657
+ if (state === "draining") return `draining ${pending} in the background`;
37658
+ if (state === "retained") {
37659
+ const buckets = ingestion.lastRetainedBucketCount ?? 0;
37660
+ const reasons = boundedHealthError(ingestion.lastRetainedReasons);
37661
+ return `delivery held back: ${String(buckets)} journal bucket${buckets === 1 ? "" : "s"} retained${reasons ? ` (${reasons})` : ""} \xB7 holding ${pending} \u2014 run \`prim doctor\``;
37662
+ }
37663
+ const failures = ingestion.consecutiveFailures;
37664
+ const count = isPositiveInteger(failures) ? ` (${String(failures)} consecutive failure${failures === 1 ? "" : "s"})` : "";
37665
+ const error = boundedHealthError(ingestion.lastError);
37666
+ return `delivery failing${count}${error ? `: ${error}` : ""} \xB7 retrying ${pending} in the background`;
37667
+ }
37668
+
37474
37669
  // src/lib/presence.ts
37475
37670
  var DECISION_ORIGIN = "https://app.getprimitive.ai";
37476
37671
  function validDecisionUrl(value) {
@@ -37545,7 +37740,8 @@ function formatStatusline(version, snapshot, resolveIngestionStatus, options = {
37545
37740
  if (snapshot.ingestion?.healthy === false) {
37546
37741
  const pending = snapshot.ingestion.pendingCount;
37547
37742
  const qualifier = snapshot.ingestion.pendingSampled ? "at least " : "";
37548
- return `primitive ${version} (daemon: degraded \xB7 delivery: stalled${typeof pending === "number" ? ` \xB7 ${qualifier}${String(pending)} pending` : ""}${repositorySuffix}${ingestionSuffix(ingestionStatus)})`;
37743
+ const delivery = deliveryBacklogState(snapshot.ingestion) === "draining" && snapshot.heartbeat?.healthy === true ? "draining" : "stalled";
37744
+ return `primitive ${version} (daemon: degraded \xB7 delivery: ${delivery}${typeof pending === "number" ? ` \xB7 ${qualifier}${String(pending)} pending` : ""}${repositorySuffix}${ingestionSuffix(ingestionStatus)})`;
37549
37745
  }
37550
37746
  if (snapshot.heartbeat?.healthy === false) {
37551
37747
  return `primitive ${version} (daemon: degraded \xB7 presence: unavailable${repositorySuffix}${ingestionSuffix(ingestionStatus)})`;
@@ -37588,16 +37784,20 @@ var STATUS_PROBE_TIMEOUT_MS = 500;
37588
37784
  var READY_TIMEOUT_MS = 5e3;
37589
37785
  var READY_POLL_MS2 = 100;
37590
37786
  var READY_PROBE_TIMEOUT_MS2 = 250;
37591
- var HEALTHY_TIMEOUT_MS = 3e4;
37592
- var HEALTHY_POLL_MS = 250;
37787
+ var READY_SNAPSHOT_TIMEOUT_MS = 3e4;
37788
+ var READY_SNAPSHOT_POLL_MS = 250;
37593
37789
  var EXIT_OK3 = 0;
37594
37790
  var EXIT_NOT_RUNNING = 2;
37595
37791
  var EXIT_BOOTING = 3;
37596
- function formatDaemonLifecycleMessage(message, decisionIngestion) {
37597
- return `${message} \xB7 Decision ingestion ${decisionIngestion}`;
37792
+ var VERIFIED_PREFIX = "[prim] \u2713 ";
37793
+ function formatDaemonLifecycleMessage(message, decisionIngestion, snapshot, now = Date.now()) {
37794
+ const backlog = deliveryBacklogSummary(snapshot?.ingestion, now);
37795
+ const state = deliveryBacklogState(snapshot?.ingestion);
37796
+ const verdict = (state === "failing" || state === "retained") && message.startsWith(VERIFIED_PREFIX) ? `[prim] \u26A0 ${message.slice(VERIFIED_PREFIX.length)}` : message;
37797
+ return `${verdict} \xB7 Decision ingestion ${decisionIngestion}${backlog ? ` \xB7 ${backlog}` : ""}`;
37598
37798
  }
37599
- function formatCurrentDaemonLifecycleMessage(message) {
37600
- return formatDaemonLifecycleMessage(message, decisionIngestionStatus(process.cwd()));
37799
+ function formatCurrentDaemonLifecycleMessage(message, snapshot) {
37800
+ return formatDaemonLifecycleMessage(message, decisionIngestionStatus(process.cwd()), snapshot);
37601
37801
  }
37602
37802
  function daemonDegradedReason(snapshot) {
37603
37803
  if (!snapshot || snapshot.healthy !== false) return void 0;
@@ -37648,7 +37848,7 @@ function openDaemonLog(configDir = CONFIG_DIR) {
37648
37848
  if (!existsSync12(configDir)) {
37649
37849
  mkdirSync9(configDir, { recursive: true, mode: CONFIG_DIR_MODE2 });
37650
37850
  }
37651
- return openSync3(join16(configDir, "daemon.log"), "a", LOG_FILE_MODE);
37851
+ return openSync4(join16(configDir, "daemon.log"), "a", LOG_FILE_MODE);
37652
37852
  }
37653
37853
  async function waitForReady() {
37654
37854
  const deadline = Date.now() + READY_TIMEOUT_MS;
@@ -37660,17 +37860,23 @@ async function waitForReady() {
37660
37860
  }
37661
37861
  return daemonIsLive(READY_PROBE_TIMEOUT_MS2);
37662
37862
  }
37663
- async function waitForHealthySnapshot(expectedVersion) {
37664
- const deadline = Date.now() + HEALTHY_TIMEOUT_MS;
37863
+ function requestStatusSnapshot() {
37864
+ return daemonRequest(
37865
+ "status_snapshot",
37866
+ {},
37867
+ { timeoutMs: STATUS_PROBE_TIMEOUT_MS }
37868
+ );
37869
+ }
37870
+ async function waitForReadySnapshot(expectedVersion, options = {}) {
37871
+ const request2 = options.requestSnapshot ?? requestStatusSnapshot;
37872
+ const wait = options.sleep ?? sleep;
37873
+ const nowMs = options.nowMs ?? Date.now;
37874
+ const deadline = nowMs() + READY_SNAPSHOT_TIMEOUT_MS;
37665
37875
  let snapshot = null;
37666
- while (Date.now() < deadline) {
37667
- snapshot = await daemonRequest(
37668
- "status_snapshot",
37669
- {},
37670
- { timeoutMs: STATUS_PROBE_TIMEOUT_MS }
37671
- );
37672
- if (snapshot?.healthy === true && snapshot.version === expectedVersion) return snapshot;
37673
- await sleep(HEALTHY_POLL_MS);
37876
+ while (nowMs() < deadline) {
37877
+ snapshot = await request2();
37878
+ if (daemonStartIsReady(true, snapshot, expectedVersion)) return snapshot;
37879
+ await wait(READY_SNAPSHOT_POLL_MS);
37674
37880
  }
37675
37881
  return snapshot;
37676
37882
  }
@@ -37683,11 +37889,20 @@ async function verifiedPid(existing) {
37683
37889
  );
37684
37890
  return snapshot?.pid === existing.pid ? snapshot : null;
37685
37891
  }
37686
- function daemonStartIsHealthy(serviceReady, snapshot, expectedVersion) {
37687
- return serviceReady && snapshot?.healthy === true && expectedVersion !== void 0 && snapshot.version === expectedVersion;
37892
+ function daemonStartIsReady(serviceReady, snapshot, expectedVersion) {
37893
+ if (!serviceReady || !snapshot || expectedVersion === void 0) return false;
37894
+ if (snapshot.version !== expectedVersion || snapshot.needsReauth === true) return false;
37895
+ if (snapshot.healthy === true) return true;
37896
+ return snapshot.heartbeat?.healthy === true && snapshot.ingestion?.healthy === false;
37688
37897
  }
37689
- function daemonStartHealthFields(healthy, snapshot) {
37690
- if (healthy) return {};
37898
+ function daemonBacklogFields(snapshot) {
37899
+ const state = deliveryBacklogState(snapshot?.ingestion);
37900
+ if (!state) return {};
37901
+ if (state === "draining") return { draining: true, ingestion: snapshot?.ingestion };
37902
+ return state === "retained" ? { draining: false, deliveryRetained: true, ingestion: snapshot?.ingestion } : { draining: false, deliveryFailing: true, ingestion: snapshot?.ingestion };
37903
+ }
37904
+ function daemonStartHealthFields(ready, snapshot) {
37905
+ if (ready) return daemonBacklogFields(snapshot);
37691
37906
  return {
37692
37907
  state: "degraded",
37693
37908
  needsReauth: snapshot?.needsReauth === true,
@@ -37695,16 +37910,28 @@ function daemonStartHealthFields(healthy, snapshot) {
37695
37910
  ingestion: snapshot?.ingestion ?? null
37696
37911
  };
37697
37912
  }
37913
+ function daemonStartFailureReason(snapshot, expectedVersion) {
37914
+ if (snapshot && !snapshot.needsReauth && expectedVersion !== void 0 && snapshot.version !== expectedVersion) {
37915
+ return `daemon reports ${snapshot.version ? `v${snapshot.version}` : "no version"}, expected v${expectedVersion}`;
37916
+ }
37917
+ return daemonDegradedReason(snapshot);
37918
+ }
37698
37919
  async function detachedDaemonStart(opts) {
37699
37920
  const existing = readPidfile();
37700
37921
  if (existing?.alive) {
37701
37922
  const snapshot = await verifiedPid(existing);
37702
37923
  if (snapshot) {
37703
37924
  process.stderr.write(
37704
- `${formatCurrentDaemonLifecycleMessage(`[prim] daemon already running (pid=${existing.pid})`)}
37925
+ `${formatCurrentDaemonLifecycleMessage(`[prim] daemon already running (pid=${existing.pid})`, snapshot)}
37705
37926
  `
37706
37927
  );
37707
- console.log(JSON.stringify({ started: false, pid: existing.pid }, null, 2));
37928
+ console.log(
37929
+ JSON.stringify(
37930
+ { started: false, pid: existing.pid, ...daemonBacklogFields(snapshot) },
37931
+ null,
37932
+ 2
37933
+ )
37934
+ );
37708
37935
  return;
37709
37936
  }
37710
37937
  process.stderr.write(
@@ -37753,16 +37980,19 @@ async function detachedDaemonStart(opts) {
37753
37980
  });
37754
37981
  child.unref();
37755
37982
  if (logFd !== void 0) {
37756
- closeSync3(logFd);
37983
+ closeSync4(logFd);
37757
37984
  }
37758
37985
  const live = await waitForReady();
37759
37986
  if (live) {
37760
37987
  const after = readPidfile();
37988
+ const snapshot = await requestStatusSnapshot();
37761
37989
  process.stderr.write(
37762
- `${formatCurrentDaemonLifecycleMessage(`[prim] \u2713 daemon started (pid=${after?.pid ?? "?"}, socket=${SOCK_PATH2})`)}
37990
+ `${formatCurrentDaemonLifecycleMessage(`[prim] \u2713 daemon started (pid=${after?.pid ?? "?"}, socket=${SOCK_PATH2})`, snapshot)}
37763
37991
  `
37764
37992
  );
37765
- console.log(JSON.stringify({ started: true, pid: after?.pid }, null, 2));
37993
+ console.log(
37994
+ JSON.stringify({ started: true, pid: after?.pid, ...daemonBacklogFields(snapshot) }, null, 2)
37995
+ );
37766
37996
  return;
37767
37997
  }
37768
37998
  process.stderr.write(
@@ -37885,19 +38115,25 @@ async function macDaemonStart(forceRestart = false) {
37885
38115
  const result = await ensureMacDaemon({ explicitlyStarted: true, forceRestart });
37886
38116
  const serviceReady = result.state === "running";
37887
38117
  const expectedVersion = result.runtime?.manifest.version;
37888
- const snapshot = serviceReady && expectedVersion ? await waitForHealthySnapshot(expectedVersion) : null;
37889
- const healthy = daemonStartIsHealthy(serviceReady, snapshot, expectedVersion);
37890
- if (healthy) {
38118
+ const snapshot = serviceReady && expectedVersion ? await waitForReadySnapshot(expectedVersion) : null;
38119
+ const ready = daemonStartIsReady(serviceReady, snapshot, expectedVersion);
38120
+ if (ready) {
37891
38121
  const verb = result.action === "none" ? "already running" : "started";
37892
38122
  const servicePid = result.service.loaded ? result.service.pid : void 0;
37893
38123
  process.stderr.write(
37894
- `${formatCurrentDaemonLifecycleMessage(`[prim] \u2713 daemon ${verb} under launchd (pid=${snapshot?.pid ?? servicePid ?? "?"})`)}
38124
+ `${formatCurrentDaemonLifecycleMessage(`[prim] \u2713 daemon ${verb} under launchd (pid=${snapshot?.pid ?? servicePid ?? "?"})`, snapshot)}
37895
38125
  `
37896
38126
  );
38127
+ } else if (!(serviceReady && expectedVersion)) {
38128
+ process.stderr.write(
38129
+ `[prim] \u2717 launchd did not converge on the desired daemon (${result.action}) (see ${LOG_PATH})
38130
+ `
38131
+ );
38132
+ if (!process.exitCode) process.exitCode = EXIT_NOT_RUNNING;
37897
38133
  } else {
37898
- const reason = daemonDegradedReason(snapshot);
38134
+ const reason = daemonStartFailureReason(snapshot, expectedVersion);
37899
38135
  process.stderr.write(
37900
- `[prim] \u2717 launchd daemon did not reach healthy heartbeat + ingestion state${reason ? ` \xB7 ${reason}` : ""} (see ${LOG_PATH})
38136
+ `[prim] \u2717 launchd daemon did not reach a healthy heartbeat on the expected version${reason ? ` \xB7 ${reason}` : ""} (see ${LOG_PATH})
37901
38137
  `
37902
38138
  );
37903
38139
  if (!process.exitCode) process.exitCode = EXIT_NOT_RUNNING;
@@ -37905,14 +38141,20 @@ async function macDaemonStart(forceRestart = false) {
37905
38141
  console.log(
37906
38142
  JSON.stringify(
37907
38143
  {
37908
- started: healthy,
38144
+ started: ready,
37909
38145
  supervised: true,
37910
38146
  action: result.action,
37911
38147
  pid: snapshot?.pid ?? (result.service.loaded ? result.service.pid : void 0),
37912
38148
  loaded: result.service.loaded,
37913
38149
  responding: result.responding,
37914
- healthy,
37915
- ...daemonStartHealthFields(healthy, snapshot),
38150
+ // `healthy` keeps meaning "start succeeded" (always equal to `started`
38151
+ // and the exit code) so existing consumers stay correct. It is not the
38152
+ // daemon's own health: `daemon status --json` stays degraded while a
38153
+ // backlog drains, is held back, or fails to deliver, which start
38154
+ // reports additively via `draining`, `deliveryRetained`,
38155
+ // `deliveryFailing`, and `ingestion`.
38156
+ healthy: ready,
38157
+ ...daemonStartHealthFields(ready, snapshot),
37916
38158
  version: snapshot?.version,
37917
38159
  expectedVersion
37918
38160
  },
@@ -38216,11 +38458,12 @@ async function daemonEnsure() {
38216
38458
  return { disabled };
38217
38459
  }
38218
38460
  const result = await ensureMacDaemon();
38461
+ const snapshot = result.state === "running" ? await requestStatusSnapshot() : null;
38219
38462
  if (result.state === "disabled") {
38220
38463
  process.stderr.write("[prim] daemon remains explicitly disabled\n");
38221
38464
  } else if (result.state === "running") {
38222
38465
  process.stderr.write(
38223
- `${formatCurrentDaemonLifecycleMessage(`[prim] \u2713 daemon ensured under launchd (${result.action})`)}
38466
+ `${formatCurrentDaemonLifecycleMessage(`[prim] \u2713 daemon ensured under launchd (${result.action})`, snapshot)}
38224
38467
  `
38225
38468
  );
38226
38469
  } else {
@@ -38234,7 +38477,8 @@ async function daemonEnsure() {
38234
38477
  ensured: result.state === "running",
38235
38478
  disabled: result.state === "disabled",
38236
38479
  supervised: true,
38237
- action: result.action
38480
+ action: result.action,
38481
+ ...daemonBacklogFields(snapshot)
38238
38482
  },
38239
38483
  null,
38240
38484
  2
@@ -39290,11 +39534,11 @@ function asRecord(value, path9) {
39290
39534
  }
39291
39535
  return value;
39292
39536
  }
39293
- function assertKeys(record2, path9, required, optional = []) {
39537
+ function assertKeys(record3, path9, required, optional = []) {
39294
39538
  const allowed = /* @__PURE__ */ new Set([...required, ...optional]);
39295
- const missing = required.find((key) => !Object.hasOwn(record2, key));
39539
+ const missing = required.find((key) => !Object.hasOwn(record3, key));
39296
39540
  if (missing !== void 0) contractError(path9, `is missing ${missing}`);
39297
- const extra = Object.keys(record2).find((key) => !allowed.has(key));
39541
+ const extra = Object.keys(record3).find((key) => !allowed.has(key));
39298
39542
  if (extra !== void 0) contractError(path9, `contains unexpected field ${extra}`);
39299
39543
  }
39300
39544
  function asString(value, path9, options) {
@@ -39324,97 +39568,97 @@ function asSha(value, path9) {
39324
39568
  if (!SHA_RE.test(sha)) contractError(path9, "must be a canonical lowercase Git commit SHA");
39325
39569
  return sha;
39326
39570
  }
39327
- function optionalString(record2, key, path9, max) {
39328
- if (!Object.hasOwn(record2, key)) return void 0;
39329
- return asString(record2[key], `${path9}.${key}`, { max, nonempty: true });
39571
+ function optionalString(record3, key, path9, max) {
39572
+ if (!Object.hasOwn(record3, key)) return void 0;
39573
+ return asString(record3[key], `${path9}.${key}`, { max, nonempty: true });
39330
39574
  }
39331
39575
  function parseProposal(value, path9) {
39332
- const record2 = asRecord(value, path9);
39576
+ const record3 = asRecord(value, path9);
39333
39577
  assertKeys(
39334
- record2,
39578
+ record3,
39335
39579
  path9,
39336
39580
  ["_id", "repoFullName", "deadCommitSha", "status", "signals", "lastEvaluatedAt"],
39337
39581
  ["proposedSha", "proposedTier", "nextEvaluateAt"]
39338
39582
  );
39339
- const repoFullName = asString(record2.repoFullName, `${path9}.repoFullName`, {
39583
+ const repoFullName = asString(record3.repoFullName, `${path9}.repoFullName`, {
39340
39584
  max: 200,
39341
39585
  nonempty: true
39342
39586
  });
39343
39587
  if (!REPO_FULL_NAME_RE.test(repoFullName)) {
39344
39588
  contractError(`${path9}.repoFullName`, "must be canonical owner/repository syntax");
39345
39589
  }
39346
- const status = record2.status;
39590
+ const status = record3.status;
39347
39591
  if (status !== "proposed" && status !== "confirmed" && status !== "verification_failed") {
39348
39592
  contractError(`${path9}.status`, "is not a review-visible proposal status");
39349
39593
  }
39350
- if (!Array.isArray(record2.signals) || record2.signals.length > MAX_SIGNALS) {
39594
+ if (!Array.isArray(record3.signals) || record3.signals.length > MAX_SIGNALS) {
39351
39595
  contractError(`${path9}.signals`, "must be a bounded array");
39352
39596
  }
39353
- const signals = record2.signals.map(
39597
+ const signals = record3.signals.map(
39354
39598
  (signal, index) => asString(signal, `${path9}.signals[${String(index)}]`, {
39355
39599
  max: MAX_SIGNAL_LENGTH,
39356
39600
  nonempty: true
39357
39601
  })
39358
39602
  );
39359
- const proposedSha = Object.hasOwn(record2, "proposedSha") ? asSha(record2.proposedSha, `${path9}.proposedSha`) : void 0;
39603
+ const proposedSha = Object.hasOwn(record3, "proposedSha") ? asSha(record3.proposedSha, `${path9}.proposedSha`) : void 0;
39360
39604
  let proposedTier;
39361
- if (Object.hasOwn(record2, "proposedTier")) {
39362
- if (record2.proposedTier !== "exact_patch_body" && record2.proposedTier !== "exact_file_set") {
39605
+ if (Object.hasOwn(record3, "proposedTier")) {
39606
+ if (record3.proposedTier !== "exact_patch_body" && record3.proposedTier !== "exact_file_set") {
39363
39607
  contractError(`${path9}.proposedTier`, "is not a supported match tier");
39364
39608
  }
39365
- proposedTier = record2.proposedTier;
39609
+ proposedTier = record3.proposedTier;
39366
39610
  }
39367
- const nextEvaluateAt = Object.hasOwn(record2, "nextEvaluateAt") ? asTimestamp(record2.nextEvaluateAt, `${path9}.nextEvaluateAt`) : void 0;
39611
+ const nextEvaluateAt = Object.hasOwn(record3, "nextEvaluateAt") ? asTimestamp(record3.nextEvaluateAt, `${path9}.nextEvaluateAt`) : void 0;
39368
39612
  return {
39369
- _id: asString(record2._id, `${path9}._id`, { max: 256, nonempty: true }),
39613
+ _id: asString(record3._id, `${path9}._id`, { max: 256, nonempty: true }),
39370
39614
  repoFullName,
39371
- deadCommitSha: asSha(record2.deadCommitSha, `${path9}.deadCommitSha`),
39615
+ deadCommitSha: asSha(record3.deadCommitSha, `${path9}.deadCommitSha`),
39372
39616
  status,
39373
39617
  proposedSha,
39374
39618
  proposedTier,
39375
39619
  signals,
39376
- lastEvaluatedAt: asTimestamp(record2.lastEvaluatedAt, `${path9}.lastEvaluatedAt`),
39620
+ lastEvaluatedAt: asTimestamp(record3.lastEvaluatedAt, `${path9}.lastEvaluatedAt`),
39377
39621
  nextEvaluateAt
39378
39622
  };
39379
39623
  }
39380
39624
  function parseDecision(value, path9) {
39381
- const record2 = asRecord(value, path9);
39382
- assertKeys(record2, path9, ["_id", "intent", "status", "intentTruncated"], ["shortId"]);
39383
- const status = record2.status;
39625
+ const record3 = asRecord(value, path9);
39626
+ assertKeys(record3, path9, ["_id", "intent", "status", "intentTruncated"], ["shortId"]);
39627
+ const status = record3.status;
39384
39628
  if (status !== "provisional" && status !== "abandoned") {
39385
39629
  contractError(`${path9}.status`, "is not a repairable decision status");
39386
39630
  }
39387
39631
  return {
39388
- _id: asString(record2._id, `${path9}._id`, { max: 256, nonempty: true }),
39389
- shortId: optionalString(record2, "shortId", path9, 128),
39390
- intent: asString(record2.intent, `${path9}.intent`, { max: MAX_INTENT_LENGTH }),
39632
+ _id: asString(record3._id, `${path9}._id`, { max: 256, nonempty: true }),
39633
+ shortId: optionalString(record3, "shortId", path9, 128),
39634
+ intent: asString(record3.intent, `${path9}.intent`, { max: MAX_INTENT_LENGTH }),
39391
39635
  status,
39392
- intentTruncated: asBoolean(record2.intentTruncated, `${path9}.intentTruncated`)
39636
+ intentTruncated: asBoolean(record3.intentTruncated, `${path9}.intentTruncated`)
39393
39637
  };
39394
39638
  }
39395
39639
  function parseListItem(value, path9) {
39396
- const record2 = asRecord(value, path9);
39397
- assertKeys(record2, path9, [
39640
+ const record3 = asRecord(value, path9);
39641
+ assertKeys(record3, path9, [
39398
39642
  "proposal",
39399
39643
  "decisions",
39400
39644
  "affectedDecisionCount",
39401
39645
  "decisionsTruncated",
39402
39646
  "reviewToken"
39403
39647
  ]);
39404
- if (!Array.isArray(record2.decisions) || record2.decisions.length > MAX_REPAIR_DECISIONS_FOR_CONFIRM) {
39648
+ if (!Array.isArray(record3.decisions) || record3.decisions.length > MAX_REPAIR_DECISIONS_FOR_CONFIRM) {
39405
39649
  contractError(`${path9}.decisions`, "must be a bounded array");
39406
39650
  }
39407
- const decisions = record2.decisions.map(
39651
+ const decisions = record3.decisions.map(
39408
39652
  (decision, index) => parseDecision(decision, `${path9}.decisions[${String(index)}]`)
39409
39653
  );
39410
- const proposal = parseProposal(record2.proposal, `${path9}.proposal`);
39411
- const decisionsTruncated = asBoolean(record2.decisionsTruncated, `${path9}.decisionsTruncated`);
39412
- const affectedDecisionCount = record2.affectedDecisionCount === null ? null : asCount(record2.affectedDecisionCount, `${path9}.affectedDecisionCount`);
39654
+ const proposal = parseProposal(record3.proposal, `${path9}.proposal`);
39655
+ const decisionsTruncated = asBoolean(record3.decisionsTruncated, `${path9}.decisionsTruncated`);
39656
+ const affectedDecisionCount = record3.affectedDecisionCount === null ? null : asCount(record3.affectedDecisionCount, `${path9}.affectedDecisionCount`);
39413
39657
  let reviewToken;
39414
- if (record2.reviewToken === null) {
39658
+ if (record3.reviewToken === null) {
39415
39659
  reviewToken = null;
39416
39660
  } else {
39417
- reviewToken = asString(record2.reviewToken, `${path9}.reviewToken`, {
39661
+ reviewToken = asString(record3.reviewToken, `${path9}.reviewToken`, {
39418
39662
  max: 64,
39419
39663
  nonempty: true
39420
39664
  });
@@ -39447,19 +39691,19 @@ function parseListItem(value, path9) {
39447
39691
  }
39448
39692
  function parseListPage(value, pageNumber) {
39449
39693
  const path9 = `page ${String(pageNumber)}`;
39450
- const record2 = asRecord(value, path9);
39451
- assertKeys(record2, path9, ["repairs", "isDone", "nextCursor", "truncated"]);
39452
- if (!Array.isArray(record2.repairs) || record2.repairs.length > MAX_REPAIRS_PER_PAGE) {
39694
+ const record3 = asRecord(value, path9);
39695
+ assertKeys(record3, path9, ["repairs", "isDone", "nextCursor", "truncated"]);
39696
+ if (!Array.isArray(record3.repairs) || record3.repairs.length > MAX_REPAIRS_PER_PAGE) {
39453
39697
  contractError(`${path9}.repairs`, "must be a bounded array");
39454
39698
  }
39455
- const isDone = asBoolean(record2.isDone, `${path9}.isDone`);
39456
- const truncated = asBoolean(record2.truncated, `${path9}.truncated`);
39699
+ const isDone = asBoolean(record3.isDone, `${path9}.isDone`);
39700
+ const truncated = asBoolean(record3.truncated, `${path9}.truncated`);
39457
39701
  if (truncated === isDone) contractError(path9, "has inconsistent isDone/truncated flags");
39458
39702
  let nextCursor;
39459
- if (record2.nextCursor === null) {
39703
+ if (record3.nextCursor === null) {
39460
39704
  nextCursor = null;
39461
39705
  } else {
39462
- nextCursor = asString(record2.nextCursor, `${path9}.nextCursor`, {
39706
+ nextCursor = asString(record3.nextCursor, `${path9}.nextCursor`, {
39463
39707
  max: MAX_CURSOR_LENGTH,
39464
39708
  nonempty: true
39465
39709
  });
@@ -39467,7 +39711,7 @@ function parseListPage(value, pageNumber) {
39467
39711
  if (isDone && nextCursor !== null) contractError(path9, "is done but includes a next cursor");
39468
39712
  if (!isDone && nextCursor === null) contractError(path9, "is incomplete without a next cursor");
39469
39713
  return {
39470
- repairs: record2.repairs.map(
39714
+ repairs: record3.repairs.map(
39471
39715
  (item, index) => parseListItem(item, `${path9}.repairs[${String(index)}]`)
39472
39716
  ),
39473
39717
  isDone,
@@ -39545,23 +39789,23 @@ function normalizedResolutionInput(proposalId, expectedProposedSha) {
39545
39789
  return { proposalId, expectedProposedSha: expectedProposedSha.toLowerCase() };
39546
39790
  }
39547
39791
  function parseResolutionOutcome(value, action) {
39548
- const record2 = asRecord(value, "resolution response");
39792
+ const record3 = asRecord(value, "resolution response");
39549
39793
  const allowed = action === "confirm" ? ["confirmed", "already_applied", "disabled", "no_op", "stale"] : ["rejected", "already_applied", "no_op", "stale"];
39550
- if (record2.status === "backoff") {
39551
- assertKeys(record2, "resolution response", ["status", "retryAt"]);
39794
+ if (record3.status === "backoff") {
39795
+ assertKeys(record3, "resolution response", ["status", "retryAt"]);
39552
39796
  if (action !== "confirm") {
39553
39797
  contractError("resolution response.status", "backoff is impossible for reject");
39554
39798
  }
39555
39799
  return {
39556
39800
  status: "backoff",
39557
- retryAt: asTimestamp(record2.retryAt, "resolution response.retryAt")
39801
+ retryAt: asTimestamp(record3.retryAt, "resolution response.retryAt")
39558
39802
  };
39559
39803
  }
39560
- assertKeys(record2, "resolution response", ["status"]);
39561
- if (typeof record2.status !== "string" || !allowed.includes(record2.status)) {
39804
+ assertKeys(record3, "resolution response", ["status"]);
39805
+ if (typeof record3.status !== "string" || !allowed.includes(record3.status)) {
39562
39806
  contractError("resolution response.status", "is not a supported outcome");
39563
39807
  }
39564
- return { status: record2.status };
39808
+ return { status: record3.status };
39565
39809
  }
39566
39810
  function normalizedReviewToken(value) {
39567
39811
  if (value === void 0 || !REVIEW_TOKEN_INPUT_RE.test(value)) {
@@ -39572,18 +39816,18 @@ function normalizedReviewToken(value) {
39572
39816
  return value.toLowerCase();
39573
39817
  }
39574
39818
  function parseConflictOutcome(value, action) {
39575
- const record2 = asRecord(value, "resolution conflict response");
39576
- assertKeys(record2, "resolution conflict response", ["status"]);
39819
+ const record3 = asRecord(value, "resolution conflict response");
39820
+ assertKeys(record3, "resolution conflict response", ["status"]);
39577
39821
  if (action !== "confirm") {
39578
39822
  contractError(
39579
39823
  "resolution conflict response.status",
39580
39824
  "a review conflict is impossible for reject"
39581
39825
  );
39582
39826
  }
39583
- if (record2.status !== "review_too_large" && record2.status !== "stale_review") {
39827
+ if (record3.status !== "review_too_large" && record3.status !== "stale_review") {
39584
39828
  contractError("resolution conflict response.status", "is not a supported 409 outcome");
39585
39829
  }
39586
- return { status: record2.status };
39830
+ return { status: record3.status };
39587
39831
  }
39588
39832
  async function resolveRepair(proposalId, expectedProposedSha, action, expectedReviewToken, deps = defaultDeps6) {
39589
39833
  const normalized = normalizedResolutionInput(proposalId, expectedProposedSha);
@@ -40725,7 +40969,7 @@ function registerDecisionsCommands(program3) {
40725
40969
  }
40726
40970
 
40727
40971
  // src/commands/doctor.ts
40728
- import { existsSync as existsSync15 } from "fs";
40972
+ import { existsSync as existsSync16 } from "fs";
40729
40973
 
40730
40974
  // src/decisions/feedback.ts
40731
40975
  var FEEDBACK_PROTOCOL_VERSION = 2;
@@ -40774,28 +41018,35 @@ function parseOrganizationBinding(value) {
40774
41018
  if (typeof value !== "object" || value === null || Array.isArray(value)) {
40775
41019
  return { state: "server_contract_unavailable" };
40776
41020
  }
40777
- const record2 = value;
40778
- if (record2.authenticated !== true || record2.organizationBindingVersion !== ORGANIZATION_BINDING_VERSION || !isCaptureAuthorityKind(record2.captureAuthorityKind)) {
41021
+ const record3 = value;
41022
+ if (record3.authenticated !== true || record3.organizationBindingVersion !== ORGANIZATION_BINDING_VERSION || !isCaptureAuthorityKind(record3.captureAuthorityKind)) {
40779
41023
  return { state: "server_contract_unavailable" };
40780
41024
  }
40781
- if (record2.organizationId === null && record2.workosOrganizationId === null) {
41025
+ if (record3.organizationId === null && record3.workosOrganizationId === null) {
40782
41026
  return { state: "no_current_organization" };
40783
41027
  }
40784
- if (safeOrganizationId(record2.organizationId) && record2.workosOrganizationId === null) {
41028
+ if (safeOrganizationId(record3.organizationId) && record3.workosOrganizationId === null) {
40785
41029
  return { state: "organization_identity_unreconciled" };
40786
41030
  }
40787
- if (!safeOrganizationId(record2.organizationId) || !safeOrganizationId(record2.workosOrganizationId)) {
41031
+ if (!safeOrganizationId(record3.organizationId) || !safeOrganizationId(record3.workosOrganizationId)) {
40788
41032
  return { state: "server_contract_unavailable" };
40789
41033
  }
40790
41034
  return {
40791
41035
  state: "current",
40792
41036
  binding: {
40793
- captureAuthorityKind: record2.captureAuthorityKind,
40794
- organizationId: record2.organizationId,
40795
- workosOrganizationId: record2.workosOrganizationId
41037
+ captureAuthorityKind: record3.captureAuthorityKind,
41038
+ organizationId: record3.organizationId,
41039
+ workosOrganizationId: record3.workosOrganizationId
40796
41040
  }
40797
41041
  };
40798
41042
  }
41043
+ function summarizeRetainedBuckets(retained) {
41044
+ const counts = /* @__PURE__ */ new Map();
41045
+ for (const item of retained) {
41046
+ counts.set(item.reason, (counts.get(item.reason) ?? 0) + 1);
41047
+ }
41048
+ return [...counts].sort(([left], [right]) => left.localeCompare(right)).map(([reason, count]) => `${reason}:${String(count)}`).join(", ");
41049
+ }
40799
41050
  function classifyJournalBuckets(buckets, binding) {
40800
41051
  const deliverableBuckets = /* @__PURE__ */ new Set();
40801
41052
  const retainedBuckets = [];
@@ -40869,18 +41120,109 @@ async function inspectJournalDelivery(buckets, options = {}) {
40869
41120
  // src/journal.ts
40870
41121
  import {
40871
41122
  appendFileSync,
40872
- closeSync as closeSync4,
40873
- existsSync as existsSync13,
40874
- fstatSync,
41123
+ closeSync as closeSync5,
41124
+ existsSync as existsSync14,
41125
+ fstatSync as fstatSync2,
40875
41126
  mkdirSync as mkdirSync10,
40876
- openSync as openSync4,
41127
+ openSync as openSync5,
40877
41128
  opendirSync,
41129
+ readFileSync as readFileSync15,
41130
+ readSync as readSync2,
41131
+ readdirSync as readdirSync5
41132
+ } from "fs";
41133
+ import { dirname as dirname12, join as join18 } from "path";
41134
+
41135
+ // src/drain-progress.ts
41136
+ import {
41137
+ chmodSync as chmodSync5,
41138
+ existsSync as existsSync13,
40878
41139
  readFileSync as readFileSync14,
40879
- readSync,
40880
- readdirSync as readdirSync4
41140
+ readdirSync as readdirSync4,
41141
+ rmSync as rmSync6,
41142
+ rmdirSync
40881
41143
  } from "fs";
40882
- import { dirname as dirname11, join as join17 } from "path";
40883
- var JOURNAL_DIR = join17(primConfigDirectory(), "moves");
41144
+ import { basename as basename4, dirname as dirname11, join as join17 } from "path";
41145
+ var DIRECTORY_MODE3 = 448;
41146
+ var FILE_MODE2 = 384;
41147
+ var DRAIN_PROGRESS_DIRNAME = "drain-progress";
41148
+ var CHECKPOINT_NAME = /^(.+)\.json(?:\.[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\.tmp)?$/;
41149
+ function rotationIdentity(stat) {
41150
+ return { size: stat.size, ino: stat.ino, dev: stat.dev };
41151
+ }
41152
+ function drainProgressDirectory(flushingPath) {
41153
+ return join17(dirname11(flushingPath), DRAIN_PROGRESS_DIRNAME);
41154
+ }
41155
+ function drainProgressPath(flushingPath) {
41156
+ return join17(drainProgressDirectory(flushingPath), `${basename4(flushingPath)}.json`);
41157
+ }
41158
+ function isByteCount(value) {
41159
+ return typeof value === "number" && Number.isSafeInteger(value) && value >= 0;
41160
+ }
41161
+ function drainResumeOffset(flushingPath, rotation) {
41162
+ let parsed;
41163
+ try {
41164
+ parsed = JSON.parse(readFileSync14(drainProgressPath(flushingPath), "utf8"));
41165
+ } catch {
41166
+ return 0;
41167
+ }
41168
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
41169
+ return 0;
41170
+ }
41171
+ const { v, offset, size, ino, dev } = parsed;
41172
+ if (v !== 2 || ino !== rotation.ino || dev !== rotation.dev || size !== rotation.size || !isByteCount(offset) || offset > rotation.size) {
41173
+ return 0;
41174
+ }
41175
+ return offset;
41176
+ }
41177
+ function writeDrainCheckpoint(flushingPath, checkpoint) {
41178
+ const directory = drainProgressDirectory(flushingPath);
41179
+ ensureDurableDirectory(directory, DIRECTORY_MODE3);
41180
+ chmodSync5(directory, DIRECTORY_MODE3);
41181
+ atomicWriteFile(drainProgressPath(flushingPath), `${JSON.stringify(checkpoint)}
41182
+ `, {
41183
+ mode: FILE_MODE2
41184
+ });
41185
+ }
41186
+ function removeEmptyDirectory(directory) {
41187
+ try {
41188
+ rmdirSync(directory);
41189
+ } catch (error) {
41190
+ const code = error.code;
41191
+ if (code !== "ENOENT" && code !== "ENOTEMPTY" && code !== "EEXIST") {
41192
+ throw error;
41193
+ }
41194
+ }
41195
+ }
41196
+ function removeDrainCheckpoint(flushingPath) {
41197
+ rmSync6(drainProgressPath(flushingPath), { force: true });
41198
+ removeEmptyDirectory(drainProgressDirectory(flushingPath));
41199
+ }
41200
+ function sweepOrphanedDrainCheckpoints(bucketDirectory) {
41201
+ const directory = join17(bucketDirectory, DRAIN_PROGRESS_DIRNAME);
41202
+ let names;
41203
+ try {
41204
+ names = readdirSync4(directory);
41205
+ } catch {
41206
+ return;
41207
+ }
41208
+ for (const name of names) {
41209
+ const rotation = CHECKPOINT_NAME.exec(name)?.[1];
41210
+ if (rotation === void 0 || existsSync13(join17(bucketDirectory, rotation))) {
41211
+ continue;
41212
+ }
41213
+ try {
41214
+ rmSync6(join17(directory, name), { force: true });
41215
+ } catch {
41216
+ }
41217
+ }
41218
+ try {
41219
+ removeEmptyDirectory(directory);
41220
+ } catch {
41221
+ }
41222
+ }
41223
+
41224
+ // src/journal.ts
41225
+ var JOURNAL_DIR = join18(primConfigDirectory(), "moves");
40884
41226
  var JOURNAL_BASENAME = "journal.ndjson";
40885
41227
  var JOURNAL_STATS_SAMPLE_BYTES = 64 * 1024;
40886
41228
  var PENDING_STATS_MAX_FILES = 128;
@@ -40892,7 +41234,7 @@ function envSlug(apiUrl) {
40892
41234
  return slug;
40893
41235
  }
40894
41236
  function currentEnvDir() {
40895
- return join17(JOURNAL_DIR, envSlug(getSiteUrl()));
41237
+ return join18(JOURNAL_DIR, envSlug(getSiteUrl()));
40896
41238
  }
40897
41239
  function parseMoves(content) {
40898
41240
  const moves = [];
@@ -40908,23 +41250,23 @@ function parseMoves(content) {
40908
41250
  return moves;
40909
41251
  }
40910
41252
  function readMovesFromPath(path9) {
40911
- if (!existsSync13(path9)) {
41253
+ if (!existsSync14(path9)) {
40912
41254
  return [];
40913
41255
  }
40914
- return parseMoves(readFileSync14(path9, "utf-8"));
41256
+ return parseMoves(readFileSync15(path9, "utf-8"));
40915
41257
  }
40916
41258
  function listBuckets() {
40917
41259
  const out = [];
40918
41260
  const envDir = currentEnvDir();
40919
- if (!existsSync13(envDir)) {
41261
+ if (!existsSync14(envDir)) {
40920
41262
  return out;
40921
41263
  }
40922
- for (const entry of readdirSync4(envDir, { withFileTypes: true })) {
41264
+ for (const entry of readdirSync5(envDir, { withFileTypes: true })) {
40923
41265
  if (!entry.isDirectory()) {
40924
41266
  continue;
40925
41267
  }
40926
- const path9 = join17(envDir, entry.name, JOURNAL_BASENAME);
40927
- if (existsSync13(path9)) {
41268
+ const path9 = join18(envDir, entry.name, JOURNAL_BASENAME);
41269
+ if (existsSync14(path9)) {
40928
41270
  out.push({ bucket: entry.name, path: path9 });
40929
41271
  }
40930
41272
  }
@@ -40941,22 +41283,23 @@ function oldestCapturedAt(moves) {
40941
41283
  }
40942
41284
  return oldest;
40943
41285
  }
40944
- function sampleJournalFile(path9, maxBytes = JOURNAL_STATS_SAMPLE_BYTES) {
40945
- const fd = openSync4(path9, "r");
41286
+ function sampleJournalFile(path9, maxBytes = JOURNAL_STATS_SAMPLE_BYTES, options = {}) {
41287
+ const fd = openSync5(path9, "r");
40946
41288
  try {
40947
- const initial = fstatSync(fd);
40948
- const target = Math.min(initial.size, Math.max(0, maxBytes));
41289
+ const initial = fstatSync2(fd);
41290
+ const start = options.fromDrainCheckpoint ? drainResumeOffset(path9, rotationIdentity(initial)) : 0;
41291
+ const target = Math.min(initial.size - start, Math.max(0, maxBytes));
40949
41292
  const buffer = Buffer.allocUnsafe(target);
40950
41293
  let sampledBytes = 0;
40951
41294
  while (sampledBytes < target) {
40952
- const count = readSync(fd, buffer, sampledBytes, target - sampledBytes, sampledBytes);
41295
+ const count = readSync2(fd, buffer, sampledBytes, target - sampledBytes, start + sampledBytes);
40953
41296
  if (count === 0) {
40954
41297
  break;
40955
41298
  }
40956
41299
  sampledBytes += count;
40957
41300
  }
40958
- const stat = fstatSync(fd);
40959
- const sampled = stat.size > sampledBytes;
41301
+ const stat = fstatSync2(fd);
41302
+ const sampled = stat.size - start > sampledBytes;
40960
41303
  let content = buffer.subarray(0, sampledBytes).toString("utf-8");
40961
41304
  if (sampled) {
40962
41305
  const lastNewline = content.lastIndexOf("\n");
@@ -40966,6 +41309,7 @@ function sampleJournalFile(path9, maxBytes = JOURNAL_STATS_SAMPLE_BYTES) {
40966
41309
  const moves = parseMoves(content);
40967
41310
  return {
40968
41311
  sizeBytes: stat.size,
41312
+ pendingBytes: Math.max(0, stat.size - start),
40969
41313
  mtimeMs: stat.mtimeMs,
40970
41314
  lineCount: sampled && stat.size > 0 ? Math.max(1, lines.length) : lines.length,
40971
41315
  oldestCapturedAt: oldestCapturedAt(moves),
@@ -40973,7 +41317,7 @@ function sampleJournalFile(path9, maxBytes = JOURNAL_STATS_SAMPLE_BYTES) {
40973
41317
  sampledBytes
40974
41318
  };
40975
41319
  } finally {
40976
- closeSync4(fd);
41320
+ closeSync5(fd);
40977
41321
  }
40978
41322
  }
40979
41323
  function parseFlushingPid(name) {
@@ -40984,7 +41328,7 @@ function parseFlushingPid(name) {
40984
41328
  return Number.isSafeInteger(pid) && pid > 0 ? pid : void 0;
40985
41329
  }
40986
41330
  function listFlushingInDir(dir, bucket, options = {}) {
40987
- if (!existsSync13(dir)) {
41331
+ if (!existsSync14(dir)) {
40988
41332
  return [];
40989
41333
  }
40990
41334
  const out = [];
@@ -40998,10 +41342,10 @@ function listFlushingInDir(dir, bucket, options = {}) {
40998
41342
  entry = entries.readSync();
40999
41343
  continue;
41000
41344
  }
41001
- const path9 = join17(dir, entry.name);
41345
+ const path9 = join18(dir, entry.name);
41002
41346
  let sample;
41003
41347
  try {
41004
- sample = sampleJournalFile(path9, remainingBytes);
41348
+ sample = sampleJournalFile(path9, remainingBytes, { fromDrainCheckpoint: true });
41005
41349
  } catch (err) {
41006
41350
  if (err.code === "ENOENT") {
41007
41351
  entry = entries.readSync();
@@ -41025,18 +41369,18 @@ function listFlushingInDir(dir, bucket, options = {}) {
41025
41369
  }
41026
41370
  function listFlushing(options = {}) {
41027
41371
  const envDir = currentEnvDir();
41028
- if (!existsSync13(envDir)) {
41372
+ if (!existsSync14(envDir)) {
41029
41373
  return [];
41030
41374
  }
41031
41375
  const out = [];
41032
41376
  let remainingBytes = options.sampleBytes ?? JOURNAL_STATS_SAMPLE_BYTES;
41033
41377
  const maxFiles = options.maxFiles ?? Number.POSITIVE_INFINITY;
41034
- for (const entry of readdirSync4(envDir, { withFileTypes: true })) {
41378
+ for (const entry of readdirSync5(envDir, { withFileTypes: true })) {
41035
41379
  if (out.length >= maxFiles) {
41036
41380
  break;
41037
41381
  }
41038
41382
  if (entry.isDirectory()) {
41039
- const found = listFlushingInDir(join17(envDir, entry.name), entry.name, {
41383
+ const found = listFlushingInDir(join18(envDir, entry.name), entry.name, {
41040
41384
  sampleBytes: remainingBytes,
41041
41385
  maxFiles: maxFiles - out.length
41042
41386
  });
@@ -41049,6 +41393,17 @@ function listFlushing(options = {}) {
41049
41393
  }
41050
41394
  return out;
41051
41395
  }
41396
+ function sweepOrphanedDrainProgress() {
41397
+ const envDir = currentEnvDir();
41398
+ if (!existsSync14(envDir)) {
41399
+ return;
41400
+ }
41401
+ for (const entry of readdirSync5(envDir, { withFileTypes: true })) {
41402
+ if (entry.isDirectory()) {
41403
+ sweepOrphanedDrainCheckpoints(join18(envDir, entry.name));
41404
+ }
41405
+ }
41406
+ }
41052
41407
  function bucketStats(options = {}) {
41053
41408
  const out = [];
41054
41409
  let remainingBytes = options.sampleBytes ?? JOURNAL_STATS_SAMPLE_BYTES;
@@ -41094,9 +41449,9 @@ function pendingJournalStats() {
41094
41449
  }
41095
41450
 
41096
41451
  // src/commands/hermes-install.ts
41097
- import { existsSync as existsSync14, readFileSync as readFileSync15, rmSync as rmSync6, statSync as statSync3 } from "fs";
41452
+ import { existsSync as existsSync15, readFileSync as readFileSync16, rmSync as rmSync7, statSync as statSync4 } from "fs";
41098
41453
  import { homedir as homedir9 } from "os";
41099
- import { join as join18 } from "path";
41454
+ import { join as join19 } from "path";
41100
41455
 
41101
41456
  // node_modules/.pnpm/yaml@2.9.0/node_modules/yaml/browser/dist/nodes/identity.js
41102
41457
  var ALIAS = /* @__PURE__ */ Symbol.for("yaml.alias");
@@ -47375,13 +47730,13 @@ var JSON_INDENT3 = 2;
47375
47730
  var CONFIG_MODE = 384;
47376
47731
  var PRIM_BINS4 = [CAPTURE_BIN4, GATE_BIN4, POST_TOOL_USE_BIN3, SESSION_START_BIN4, SESSION_END_BIN3];
47377
47732
  function hermesHome() {
47378
- return process.env.HERMES_HOME ?? join18(homedir9(), ".hermes");
47733
+ return process.env.HERMES_HOME ?? join19(homedir9(), ".hermes");
47379
47734
  }
47380
47735
  function configPath() {
47381
- return join18(hermesHome(), "config.yaml");
47736
+ return join19(hermesHome(), "config.yaml");
47382
47737
  }
47383
47738
  function legacyShimPath() {
47384
- return join18(hermesHome(), "agent-hooks", "prim-shim.sh");
47739
+ return join19(hermesHome(), "agent-hooks", "prim-shim.sh");
47385
47740
  }
47386
47741
  function commandFor2(bin) {
47387
47742
  return stableHookCommand(bin, HERMES_ARGS);
@@ -47495,8 +47850,8 @@ function hookRuntimeResolutions3(hooks) {
47495
47850
  }
47496
47851
  function inspectHookRuntimeResolutions4() {
47497
47852
  const path9 = configPath();
47498
- if (!existsSync14(path9)) return [];
47499
- return hookRuntimeResolutions3(readHooks(parseHermesDocument(readFileSync15(path9, "utf8"))));
47853
+ if (!existsSync15(path9)) return [];
47854
+ return hookRuntimeResolutions3(readHooks(parseHermesDocument(readFileSync16(path9, "utf8"))));
47500
47855
  }
47501
47856
  function entryMatchesRegistration3(owned, registration) {
47502
47857
  const expected = entryFor(registration);
@@ -47765,11 +48120,11 @@ function assertMergeValid(before, after, expectedHooks) {
47765
48120
  }
47766
48121
  }
47767
48122
  function readConfigSnapshot(path9) {
47768
- if (!existsSync14(path9)) return { exists: false, mode: CONFIG_MODE, raw: "" };
48123
+ if (!existsSync15(path9)) return { exists: false, mode: CONFIG_MODE, raw: "" };
47769
48124
  return {
47770
48125
  exists: true,
47771
- mode: statSync3(path9).mode & 511,
47772
- raw: readFileSync15(path9, "utf8")
48126
+ mode: statSync4(path9).mode & 511,
48127
+ raw: readFileSync16(path9, "utf8")
47773
48128
  };
47774
48129
  }
47775
48130
  var CONCURRENT_CONFIG_MESSAGE = "Hermes config changed during update; concurrent changes were preserved, retry the command";
@@ -47778,7 +48133,7 @@ function writeHermesConfigSnapshot(path9, before, after, expectedHooks) {
47778
48133
  ensureParent: true,
47779
48134
  mode: before.mode,
47780
48135
  validate(temporaryPath) {
47781
- const flushed = readFileSync15(temporaryPath, "utf8");
48136
+ const flushed = readFileSync16(temporaryPath, "utf8");
47782
48137
  if (!mergePreservesHermesSemantics(before.raw, flushed, expectedHooks)) {
47783
48138
  throw new Error(INVALID_MERGE_MESSAGE);
47784
48139
  }
@@ -47790,7 +48145,7 @@ function writeHermesConfigSnapshot(path9, before, after, expectedHooks) {
47790
48145
  });
47791
48146
  }
47792
48147
  function removeLegacyShim() {
47793
- rmSync6(legacyShimPath(), { force: true });
48148
+ rmSync7(legacyShimPath(), { force: true });
47794
48149
  }
47795
48150
  function autoAcceptOf(raw) {
47796
48151
  if (raw.trim().length === 0) {
@@ -47831,7 +48186,7 @@ function performInstall4(opts) {
47831
48186
  }
47832
48187
  function performUninstall4() {
47833
48188
  const path9 = configPath();
47834
- if (!existsSync14(path9)) {
48189
+ if (!existsSync15(path9)) {
47835
48190
  removeLegacyShim();
47836
48191
  return { path: path9, gate: false, capture: false, autoAccept: false, changed: false };
47837
48192
  }
@@ -47856,7 +48211,7 @@ function performUninstall4() {
47856
48211
  }
47857
48212
  function performStatus4() {
47858
48213
  const path9 = configPath();
47859
- const raw = existsSync14(path9) ? readFileSync15(path9, "utf8") : "";
48214
+ const raw = existsSync15(path9) ? readFileSync16(path9, "utf8") : "";
47860
48215
  const hooks = raw ? readHooks(parseHermesDocument(raw)) : {};
47861
48216
  return {
47862
48217
  path: path9,
@@ -47910,7 +48265,6 @@ function registerHermesCommands(program3) {
47910
48265
  // src/commands/doctor.ts
47911
48266
  var DAEMON_PROBE_TIMEOUT_MS2 = 500;
47912
48267
  var CONNECTIVITY_TIMEOUT_MS = 3e3;
47913
- var MS_PER_SECOND = 1e3;
47914
48268
  var STALE_PENDING_MS = 3e4;
47915
48269
  var EXIT_UNHEALTHY = 1;
47916
48270
  function classifyDoctor(checks) {
@@ -47946,7 +48300,7 @@ function checkAuth() {
47946
48300
  return classifyAuthCredential(
47947
48301
  credential,
47948
48302
  storedCredential ? getTokenExpiresAt() : void 0,
47949
- storedCredential && existsSync15(REFRESH_TOKEN_PATH)
48303
+ storedCredential && existsSync16(REFRESH_TOKEN_PATH)
47950
48304
  );
47951
48305
  }
47952
48306
  function classifyDaemonHealth(snapshot, options = {}) {
@@ -48032,8 +48386,19 @@ function classifyDaemonHealth(snapshot, options = {}) {
48032
48386
  };
48033
48387
  }
48034
48388
  if (!snapshot.ingestion?.healthy) {
48035
- const pending = snapshot.ingestion?.pendingCount ?? 0;
48036
- const pendingLabel = snapshot.ingestion?.pendingSampled ? `at least ${String(pending)}` : String(pending);
48389
+ if (options.backlogExpected && deliveryBacklogState(snapshot.ingestion) === "draining") {
48390
+ const ingestionStatus2 = options.ingestionStatus ?? decisionIngestionStatus(process.cwd());
48391
+ const live = options.backlog;
48392
+ const progress = live && live.pendingCount === 0 && live.pendingSampled !== true ? "backlog drained; daemon health refreshes on its next sweep" : deliveryBacklogSummary(snapshot.ingestion, options.now, live);
48393
+ return {
48394
+ name: "daemon",
48395
+ status: "warn",
48396
+ detail: `supervised and live${snapshot.version ? ` \xB7 v${snapshot.version}` : ""} \xB7 Decision ingestion ${ingestionStatus2} \xB7 ${progress}`
48397
+ };
48398
+ }
48399
+ const counts = options.backlog ?? snapshot.ingestion;
48400
+ const pending = counts?.pendingCount ?? 0;
48401
+ const pendingLabel = counts?.pendingSampled ? `at least ${String(pending)}` : String(pending);
48037
48402
  return {
48038
48403
  name: "daemon",
48039
48404
  status: "fail",
@@ -48050,17 +48415,23 @@ function classifyDaemonHealth(snapshot, options = {}) {
48050
48415
  detail: `supervised and healthy${snapshot.version ? ` \xB7 v${snapshot.version}` : ""} \xB7 Decision ingestion ${ingestionStatus}`
48051
48416
  };
48052
48417
  }
48053
- async function checkDaemon() {
48418
+ async function probeDelivery() {
48419
+ const stats = pendingJournalStats();
48420
+ const now = Date.now();
48421
+ const disabled = daemonExplicitlyDisabled();
48422
+ const expectedVersion = packageVersion();
48054
48423
  let service;
48055
48424
  if (process.platform === "darwin") {
48056
48425
  try {
48057
48426
  service = getLaunchdService();
48058
48427
  } catch (error) {
48059
- const detail = boundedHealthError(error instanceof Error ? error.message : String(error));
48060
48428
  return {
48061
- name: "daemon",
48062
- status: "fail",
48063
- detail: `launchd status unavailable${detail ? `: ${detail}` : ""}`
48429
+ snapshot: null,
48430
+ launchdError: error instanceof Error ? error.message : String(error),
48431
+ disabled,
48432
+ expectedVersion,
48433
+ stats,
48434
+ now
48064
48435
  };
48065
48436
  }
48066
48437
  }
@@ -48069,53 +48440,74 @@ async function checkDaemon() {
48069
48440
  { callerEnv: getSiteUrl() },
48070
48441
  { timeoutMs: DAEMON_PROBE_TIMEOUT_MS2 }
48071
48442
  );
48072
- return classifyDaemonHealth(snapshot, {
48073
- disabled: daemonExplicitlyDisabled(),
48074
- service,
48075
- expectedVersion: packageVersion()
48443
+ return { snapshot, service, disabled, expectedVersion, stats, now };
48444
+ }
48445
+ function daemonCheckVouchesForDrain(check) {
48446
+ return check.name === "daemon" && check.status !== "fail";
48447
+ }
48448
+ function classifyDelivery(probe, options = {}) {
48449
+ const { stats, now } = probe;
48450
+ const launchdDetail = probe.launchdError === void 0 ? void 0 : boundedHealthError(probe.launchdError);
48451
+ const daemon = probe.launchdError === void 0 ? classifyDaemonHealth(probe.snapshot, {
48452
+ disabled: probe.disabled,
48453
+ service: probe.service,
48454
+ ingestionStatus: probe.ingestionStatus,
48455
+ expectedVersion: probe.expectedVersion,
48456
+ backlogExpected: options.backlogExpected,
48457
+ backlog: {
48458
+ pendingCount: stats.pendingCount,
48459
+ pendingSampled: stats.sampled,
48460
+ oldestPendingAt: stats.oldestPendingAt
48461
+ },
48462
+ now
48463
+ }) : {
48464
+ name: "daemon",
48465
+ status: "fail",
48466
+ detail: `launchd status unavailable${launchdDetail ? `: ${launchdDetail}` : ""}`
48467
+ };
48468
+ const journal = classifyJournal(stats, now, {
48469
+ backlogExpected: options.backlogExpected === true && daemonCheckVouchesForDrain(daemon)
48076
48470
  });
48471
+ return [daemon, journal, classifyStranded(stats)];
48077
48472
  }
48078
- function checkJournal() {
48079
- const stats = pendingJournalStats();
48473
+ function classifyJournal(stats, now, options = {}) {
48080
48474
  const pending = stats.pendingCount;
48081
48475
  const pendingLabel = stats.sampled ? `at least ${String(pending)}` : String(pending);
48476
+ const backlog = (detail) => options.backlogExpected ? {
48477
+ name: "journal",
48478
+ status: "warn",
48479
+ detail: `${formatPendingBacklog(
48480
+ {
48481
+ pendingCount: pending,
48482
+ pendingSampled: stats.sampled,
48483
+ oldestPendingAt: stats.oldestPendingAt
48484
+ },
48485
+ now
48486
+ )} \u2014 draining in the background`
48487
+ } : { name: "journal", status: "fail", detail };
48082
48488
  if (pending === 0) {
48083
48489
  if (stats.sampled) {
48084
- return {
48085
- name: "journal",
48086
- status: "fail",
48087
- detail: "bounded journal sample could not prove the queue is empty"
48088
- };
48490
+ return backlog("bounded journal sample could not prove the queue is empty");
48089
48491
  }
48090
48492
  return { name: "journal", status: "ok", detail: "no pending moves" };
48091
48493
  }
48092
48494
  if (stats.oldestPendingAt === void 0) {
48093
- return {
48094
- name: "journal",
48095
- status: "fail",
48096
- detail: `${pendingLabel} pending with no readable capture timestamp`
48097
- };
48495
+ const detail = `${pendingLabel} pending with no readable capture timestamp`;
48496
+ return stats.sampled ? backlog(detail) : { name: "journal", status: "fail", detail };
48098
48497
  }
48099
- const oldestMs = Date.now() - stats.oldestPendingAt;
48498
+ const oldestMs = now - stats.oldestPendingAt;
48100
48499
  const oldestS = Math.round(oldestMs / MS_PER_SECOND);
48101
48500
  if (oldestMs > STALE_PENDING_MS) {
48102
- return {
48103
- name: "journal",
48104
- status: "fail",
48105
- detail: `${pendingLabel} pending, oldest observed ${String(oldestS)}s \u2014 30s delivery SLA missed`
48106
- };
48501
+ return backlog(
48502
+ `${pendingLabel} pending, oldest observed ${String(oldestS)}s \u2014 30s delivery SLA missed`
48503
+ );
48107
48504
  }
48108
48505
  if (stats.sampled) {
48109
- return {
48110
- name: "journal",
48111
- status: "fail",
48112
- detail: `${pendingLabel} pending in bounded sample; 30s delivery SLA cannot be proven`
48113
- };
48506
+ return backlog(`${pendingLabel} pending in bounded sample; 30s delivery SLA cannot be proven`);
48114
48507
  }
48115
48508
  return { name: "journal", status: "ok", detail: `${String(pending)} pending, draining` };
48116
48509
  }
48117
- function checkStranded() {
48118
- const stats = pendingJournalStats();
48510
+ function classifyStranded(stats) {
48119
48511
  if (stats.strandedFileCount === 0 && !stats.strandedSampled) {
48120
48512
  return { name: "stranded", status: "ok", detail: "none" };
48121
48513
  }
@@ -48141,15 +48533,10 @@ function classifyJournalOrganization(bucketCount, retainedBuckets) {
48141
48533
  detail: "all pending buckets match the active credential"
48142
48534
  };
48143
48535
  }
48144
- const reasonCounts = /* @__PURE__ */ new Map();
48145
- for (const item of retainedBuckets) {
48146
- reasonCounts.set(item.reason, (reasonCounts.get(item.reason) ?? 0) + 1);
48147
- }
48148
- const reasons = [...reasonCounts].sort(([left], [right]) => left.localeCompare(right)).map(([reason, count]) => `${reason}:${String(count)}`).join(", ");
48149
48536
  return {
48150
48537
  name: "journal-org",
48151
48538
  status: "fail",
48152
- detail: `${String(retainedBuckets.length)} bucket(s) retained (${reasons})`
48539
+ detail: `${String(retainedBuckets.length)} bucket(s) retained (${summarizeRetainedBuckets(retainedBuckets)})`
48153
48540
  };
48154
48541
  }
48155
48542
  async function checkJournalOrganization() {
@@ -48560,30 +48947,37 @@ function refreshOwnedGlobalHooksForHealth() {
48560
48947
  } catch {
48561
48948
  }
48562
48949
  }
48563
- async function collectChecks() {
48950
+ async function independentChecks() {
48564
48951
  refreshOwnedGlobalHooksForHealth();
48565
48952
  const backend = await checkBackend();
48566
- return [
48567
- checkAuth(),
48568
- await checkDaemon(),
48569
- checkJournal(),
48570
- checkStranded(),
48571
- await checkJournalOrganization(),
48572
- checkFeedbackHooks(),
48573
- ...checkAgentHooks(),
48574
- checkHookRuntime(),
48575
- await checkRepositoryBinding(),
48576
- checkManagedHook("post-commit", inspectEffectivePostCommitHook),
48577
- checkManagedHook("post-rewrite", inspectEffectivePostRewriteHook),
48578
- ...backend,
48579
- await checkFeedbackCapability()
48580
- ];
48953
+ return {
48954
+ before: [checkAuth()],
48955
+ after: [
48956
+ await checkJournalOrganization(),
48957
+ checkFeedbackHooks(),
48958
+ ...checkAgentHooks(),
48959
+ checkHookRuntime(),
48960
+ await checkRepositoryBinding(),
48961
+ checkManagedHook("post-commit", inspectEffectivePostCommitHook),
48962
+ checkManagedHook("post-rewrite", inspectEffectivePostRewriteHook),
48963
+ ...backend,
48964
+ await checkFeedbackCapability()
48965
+ ]
48966
+ };
48967
+ }
48968
+ var DEFAULT_DOCTOR_PROBES = {
48969
+ delivery: probeDelivery,
48970
+ independent: independentChecks
48971
+ };
48972
+ async function collectChecks(options, probes) {
48973
+ const { before, after } = await probes.independent();
48974
+ return [...before, ...classifyDelivery(await probes.delivery(), options), ...after];
48581
48975
  }
48582
48976
  function icon(status) {
48583
48977
  return status === "ok" ? "\u2713" : status === "warn" ? "\u26A0" : "\u2717";
48584
48978
  }
48585
- async function runDoctor() {
48586
- const checks = await collectChecks();
48979
+ async function runDoctor(options, probes) {
48980
+ const checks = await collectChecks(options, probes);
48587
48981
  const { json, exitCode } = classifyDoctor(checks);
48588
48982
  const headline = json.status === "ok" ? "\u2713 healthy" : json.status === "warn" ? "\u26A0 degraded" : "\u2717 unhealthy";
48589
48983
  process.stderr.write(`[prim] doctor: ${headline}
@@ -48597,101 +48991,104 @@ async function runDoctor() {
48597
48991
  process.exitCode = exitCode;
48598
48992
  }
48599
48993
  }
48600
- function registerDoctorCommands(program3) {
48994
+ function registerDoctorCommands(program3, dependencies = {}) {
48601
48995
  program3.command("doctor").description(
48602
48996
  "Check capture and feedback health end to end (auth, supervisor, delivery, server)"
48603
- ).action(async () => {
48604
- await runDoctor();
48997
+ ).addOption(new Option("--expect-backlog").hideHelp()).action(async (opts) => {
48998
+ await runDoctor(
48999
+ { backlogExpected: opts.expectBacklog === true },
49000
+ dependencies.probes ?? DEFAULT_DOCTOR_PROBES
49001
+ );
48605
49002
  });
48606
49003
  }
48607
49004
 
48608
49005
  // src/commands/moves.ts
48609
- import { existsSync as existsSync17, mkdirSync as mkdirSync12, unlinkSync as unlinkSync5, writeFileSync as writeFileSync9 } from "fs";
48610
- import { join as join21 } from "path";
49006
+ import { existsSync as existsSync18, mkdirSync as mkdirSync12, unlinkSync as unlinkSync5, writeFileSync as writeFileSync9 } from "fs";
49007
+ import { join as join22 } from "path";
48611
49008
 
48612
49009
  // src/flusher.ts
48613
- import { createReadStream, renameSync as renameSync6, unlinkSync as unlinkSync4 } from "fs";
48614
- import { dirname as dirname13, join as join20 } from "path";
49010
+ import { createReadStream, renameSync as renameSync6, statSync as statSync5, unlinkSync as unlinkSync4 } from "fs";
49011
+ import { dirname as dirname14, join as join21 } from "path";
48615
49012
 
48616
49013
  // src/dead-letter.ts
48617
49014
  import { createHash as createHash9, randomBytes as randomBytes6 } from "crypto";
48618
49015
  import {
48619
- constants as constants5,
48620
- chmodSync as chmodSync5,
48621
- closeSync as closeSync5,
48622
- existsSync as existsSync16,
49016
+ constants as constants6,
49017
+ chmodSync as chmodSync6,
49018
+ closeSync as closeSync6,
49019
+ existsSync as existsSync17,
48623
49020
  fchmodSync as fchmodSync3,
48624
49021
  fsyncSync as fsyncSync3,
48625
49022
  mkdirSync as mkdirSync11,
48626
- openSync as openSync5,
49023
+ openSync as openSync6,
48627
49024
  renameSync as renameSync5,
48628
- rmSync as rmSync7,
49025
+ rmSync as rmSync8,
48629
49026
  writeFileSync as writeFileSync8
48630
49027
  } from "fs";
48631
- import { dirname as dirname12, join as join19 } from "path";
48632
- var DIRECTORY_MODE3 = 448;
48633
- var FILE_MODE2 = 384;
49028
+ import { dirname as dirname13, join as join20 } from "path";
49029
+ var DIRECTORY_MODE4 = 448;
49030
+ var FILE_MODE3 = 384;
48634
49031
  var DEAD_LETTER_DIRNAME = "dead-letter";
48635
49032
  function deadLetterDirectoryForRotation(flushingPath) {
48636
- return join19(dirname12(flushingPath), DEAD_LETTER_DIRNAME);
49033
+ return join20(dirname13(flushingPath), DEAD_LETTER_DIRNAME);
48637
49034
  }
48638
49035
  function deadLetterPathForRawLine(flushingPath, rawLine) {
48639
49036
  const quarantineId = createHash9("sha256").update(rawLine).digest("hex");
48640
- return join19(deadLetterDirectoryForRotation(flushingPath), `raw-${quarantineId}.json`);
49037
+ return join20(deadLetterDirectoryForRotation(flushingPath), `raw-${quarantineId}.json`);
48641
49038
  }
48642
49039
  function ensureDeadLetterDirectory(flushingPath, persistDirectory) {
48643
49040
  const directory = deadLetterDirectoryForRotation(flushingPath);
48644
- mkdirSync11(directory, { recursive: true, mode: DIRECTORY_MODE3 });
48645
- chmodSync5(directory, DIRECTORY_MODE3);
48646
- persistDirectory(dirname12(directory));
49041
+ mkdirSync11(directory, { recursive: true, mode: DIRECTORY_MODE4 });
49042
+ chmodSync6(directory, DIRECTORY_MODE4);
49043
+ persistDirectory(dirname13(directory));
48647
49044
  return directory;
48648
49045
  }
48649
- function persistDeadLetter(directory, path9, quarantineId, record2, persistDirectory) {
48650
- if (existsSync16(path9)) {
49046
+ function persistDeadLetter(directory, path9, quarantineId, record3, persistDirectory) {
49047
+ if (existsSync17(path9)) {
48651
49048
  persistDirectory(directory);
48652
49049
  return;
48653
49050
  }
48654
- const temporaryPath = join19(
49051
+ const temporaryPath = join20(
48655
49052
  directory,
48656
49053
  `.${quarantineId}.${String(process.pid)}.${randomBytes6(8).toString("hex")}.tmp`
48657
49054
  );
48658
- const flags = constants5.O_CREAT | constants5.O_EXCL | constants5.O_WRONLY | constants5.O_NOFOLLOW;
49055
+ const flags = constants6.O_CREAT | constants6.O_EXCL | constants6.O_WRONLY | constants6.O_NOFOLLOW;
48659
49056
  try {
48660
- const fd = openSync5(temporaryPath, flags, FILE_MODE2);
49057
+ const fd = openSync6(temporaryPath, flags, FILE_MODE3);
48661
49058
  try {
48662
- fchmodSync3(fd, FILE_MODE2);
48663
- writeFileSync8(fd, `${JSON.stringify(record2)}
49059
+ fchmodSync3(fd, FILE_MODE3);
49060
+ writeFileSync8(fd, `${JSON.stringify(record3)}
48664
49061
  `, "utf8");
48665
49062
  fsyncSync3(fd);
48666
49063
  } finally {
48667
- closeSync5(fd);
49064
+ closeSync6(fd);
48668
49065
  }
48669
49066
  renameSync5(temporaryPath, path9);
48670
49067
  persistDirectory(directory);
48671
49068
  } finally {
48672
- rmSync7(temporaryPath, { force: true });
49069
+ rmSync8(temporaryPath, { force: true });
48673
49070
  }
48674
49071
  }
48675
49072
  function quarantineMove(flushingPath, move, reason, now = Date.now, options = {}) {
48676
49073
  const persistDirectory = options.syncDirectory ?? syncDirectory;
48677
49074
  const directory = ensureDeadLetterDirectory(flushingPath, persistDirectory);
48678
49075
  const serializedMove = JSON.stringify(move);
48679
- const record2 = {
49076
+ const record3 = {
48680
49077
  version: 1,
48681
49078
  quarantineId: createHash9("sha256").update(serializedMove).digest("hex"),
48682
49079
  quarantinedAt: now(),
48683
49080
  reason,
48684
49081
  move
48685
49082
  };
48686
- const path9 = join19(directory, `${record2.quarantineId}.json`);
48687
- persistDeadLetter(directory, path9, record2.quarantineId, record2, persistDirectory);
48688
- return record2;
49083
+ const path9 = join20(directory, `${record3.quarantineId}.json`);
49084
+ persistDeadLetter(directory, path9, record3.quarantineId, record3, persistDirectory);
49085
+ return record3;
48689
49086
  }
48690
49087
  function quarantineRawLine(flushingPath, rawLine, now = Date.now, options = {}) {
48691
49088
  const persistDirectory = options.syncDirectory ?? syncDirectory;
48692
49089
  const directory = ensureDeadLetterDirectory(flushingPath, persistDirectory);
48693
49090
  const bytes = Buffer.from(rawLine);
48694
- const record2 = {
49091
+ const record3 = {
48695
49092
  version: 1,
48696
49093
  recordKind: "raw_line_v1",
48697
49094
  quarantineId: createHash9("sha256").update(bytes).digest("hex"),
@@ -48704,11 +49101,11 @@ function quarantineRawLine(flushingPath, rawLine, now = Date.now, options = {})
48704
49101
  persistDeadLetter(
48705
49102
  directory,
48706
49103
  deadLetterPathForRawLine(flushingPath, bytes),
48707
- record2.quarantineId,
48708
- record2,
49104
+ record3.quarantineId,
49105
+ record3,
48709
49106
  persistDirectory
48710
49107
  );
48711
- return record2;
49108
+ return record3;
48712
49109
  }
48713
49110
 
48714
49111
  // src/ingest-response.ts
@@ -48742,9 +49139,13 @@ function requireDurableIngestAcknowledgement(value, expected, cwd = process.cwd(
48742
49139
 
48743
49140
  // src/flusher.ts
48744
49141
  var BATCH_SIZE = 500;
49142
+ var BATCH_MAX_BYTES = 1048576;
48745
49143
  var HTTP_TIMEOUT_MS = 1e4;
48746
49144
  var OPPORTUNISTIC_FLUSH_AFTER_MS = 6e4;
48747
49145
  var ORPHAN_QUARANTINE_MS = 6e4;
49146
+ function exceedsBatchBytes(count, bytes, nextBytes, maxBytes) {
49147
+ return count > 0 && bytes + nextBytes > maxBytes;
49148
+ }
48748
49149
  function addDrainCounts(left, right) {
48749
49150
  return {
48750
49151
  flushed: left.flushed + right.flushed,
@@ -48755,8 +49156,8 @@ function collectionScopeRoots(batch) {
48755
49156
  return batch.flatMap((move) => {
48756
49157
  const env4 = move.env;
48757
49158
  if (typeof env4 !== "object" || env4 === null || Array.isArray(env4)) return [];
48758
- const record2 = env4;
48759
- const root = typeof record2.gitRoot === "string" ? record2.gitRoot : record2.cwd;
49159
+ const record3 = env4;
49160
+ const root = typeof record3.gitRoot === "string" ? record3.gitRoot : record3.cwd;
48760
49161
  return typeof root === "string" && root.length > 0 ? [root] : [];
48761
49162
  });
48762
49163
  }
@@ -48764,8 +49165,8 @@ function isVersionedInvalidMoveDisposition(body) {
48764
49165
  if (typeof body !== "object" || body === null || Array.isArray(body)) {
48765
49166
  return false;
48766
49167
  }
48767
- const record2 = body;
48768
- return Object.keys(record2).length === 2 && record2.error === "invalid_move" && record2.errorVersion === 1;
49168
+ const record3 = body;
49169
+ return Object.keys(record3).length === 2 && record3.error === "invalid_move" && record3.errorVersion === 1;
48769
49170
  }
48770
49171
  function deadLetterReason(error) {
48771
49172
  if (error instanceof InvalidJournalEnvelopeError) {
@@ -48794,23 +49195,25 @@ function mayBeBatchSizeFailure(error) {
48794
49195
  }
48795
49196
  return error instanceof Error && error.name === "TimeoutError";
48796
49197
  }
48797
- async function* rawJournalLines(input) {
49198
+ async function* rawJournalLines(input, offset) {
48798
49199
  let pending = [];
48799
49200
  let pendingBytes = 0;
49201
+ let end = offset;
48800
49202
  for await (const value of input) {
48801
49203
  const chunk2 = Buffer.isBuffer(value) ? value : Buffer.from(value);
48802
49204
  let start = 0;
48803
49205
  let newline = chunk2.indexOf(10, start);
48804
49206
  while (newline !== -1) {
48805
49207
  const tail = chunk2.subarray(start, newline + 1);
48806
- if (pending.length === 0) {
48807
- yield tail;
48808
- } else {
49208
+ let line = tail;
49209
+ if (pending.length > 0) {
48809
49210
  pending.push(tail);
48810
- yield Buffer.concat(pending, pendingBytes + tail.length);
49211
+ line = Buffer.concat(pending, pendingBytes + tail.length);
48811
49212
  pending = [];
48812
49213
  pendingBytes = 0;
48813
49214
  }
49215
+ end += line.length;
49216
+ yield { bytes: line, end };
48814
49217
  start = newline + 1;
48815
49218
  newline = chunk2.indexOf(10, start);
48816
49219
  }
@@ -48821,18 +49224,43 @@ async function* rawJournalLines(input) {
48821
49224
  }
48822
49225
  }
48823
49226
  if (pendingBytes > 0) {
48824
- yield pending.length === 1 ? pending[0] : Buffer.concat(pending, pendingBytes);
49227
+ const bytes = pending.length === 1 ? pending[0] : Buffer.concat(pending, pendingBytes);
49228
+ yield { bytes, end: end + pendingBytes };
48825
49229
  }
48826
49230
  }
49231
+ var drainProgressWarned = false;
48827
49232
  async function drainFlushingPath(flushingPath, client, binding, options = {}) {
49233
+ const rotation = rotationIdentity(statSync5(flushingPath));
49234
+ const resumeAt = drainResumeOffset(flushingPath, rotation);
49235
+ const recordProgress = (end) => {
49236
+ if (end === void 0 || end >= rotation.size) {
49237
+ return;
49238
+ }
49239
+ try {
49240
+ writeDrainCheckpoint(flushingPath, { v: 2, offset: end, ...rotation });
49241
+ drainProgressWarned = false;
49242
+ } catch (error) {
49243
+ if (!drainProgressWarned) {
49244
+ drainProgressWarned = true;
49245
+ process.stderr.write(
49246
+ `[prim] could not record drain progress: ${error instanceof Error ? error.message : String(error)}
49247
+ `
49248
+ );
49249
+ }
49250
+ }
49251
+ };
49252
+ const reportProgress = (delta) => {
49253
+ options.onProgress?.(delta);
49254
+ return delta;
49255
+ };
48828
49256
  const postBatch = async (batch2) => {
49257
+ const moves = batch2.map((entry) => entry.move);
48829
49258
  try {
48830
- const request2 = buildOrganizationBoundMoveRequest(batch2, binding);
49259
+ const request2 = buildOrganizationBoundMoveRequest(moves, binding);
48831
49260
  const response = await client.post("/api/cli/moves/ingest", request2, {
48832
49261
  signal: AbortSignal.timeout(HTTP_TIMEOUT_MS)
48833
49262
  });
48834
- requireDurableIngestAcknowledgement(response, batch2.length, collectionScopeRoots(batch2));
48835
- return { flushed: batch2.length, quarantined: 0 };
49263
+ requireDurableIngestAcknowledgement(response, moves.length, collectionScopeRoots(moves));
48836
49264
  } catch (error) {
48837
49265
  const reason = deadLetterReason(error);
48838
49266
  if (!(reason || mayBeBatchSizeFailure(error))) {
@@ -48847,33 +49275,43 @@ async function drainFlushingPath(flushingPath, client, binding, options = {}) {
48847
49275
  if (!reason) {
48848
49276
  throw error;
48849
49277
  }
48850
- const [move] = batch2;
49278
+ const [entry] = batch2;
48851
49279
  const quarantined = quarantineMove(
48852
49280
  flushingPath,
48853
- move,
49281
+ entry.move,
48854
49282
  reason,
48855
49283
  Date.now,
48856
49284
  options.deadLetterPersistence
48857
49285
  );
49286
+ recordProgress(entry.end);
48858
49287
  process.stderr.write(
48859
49288
  `[prim] quarantined rejected move ${quarantined.quarantineId.slice(0, 12)} (${reason})
48860
49289
  `
48861
49290
  );
48862
- return { flushed: 0, quarantined: 1 };
49291
+ return reportProgress({ flushed: 0, quarantined: 1 });
48863
49292
  }
49293
+ recordProgress(batch2.at(-1)?.end);
49294
+ return reportProgress({ flushed: batch2.length, quarantined: 0 });
48864
49295
  };
48865
- const input = createReadStream(flushingPath);
49296
+ const input = createReadStream(flushingPath, { start: resumeAt });
48866
49297
  const decoder = new TextDecoder("utf-8", { fatal: true });
48867
49298
  let batch = [];
49299
+ let batchBytes = 0;
48868
49300
  let counts = { flushed: 0, quarantined: 0 };
49301
+ const postPending = async () => {
49302
+ counts = addDrainCounts(counts, await postBatch(batch));
49303
+ batch = [];
49304
+ batchBytes = 0;
49305
+ };
48869
49306
  try {
48870
- for await (const rawLine of rawJournalLines(input)) {
49307
+ for await (const { bytes: rawLine, end } of rawJournalLines(input, resumeAt)) {
48871
49308
  if (rawLine.length === 1 && rawLine[0] === 10 || rawLine.length === 2 && rawLine[0] === 13 && rawLine[1] === 10) {
48872
49309
  continue;
48873
49310
  }
49311
+ let move;
48874
49312
  try {
48875
49313
  const line = decoder.decode(rawLine);
48876
- batch.push(JSON.parse(line));
49314
+ move = JSON.parse(line);
48877
49315
  } catch {
48878
49316
  const quarantined = quarantineRawLine(
48879
49317
  flushingPath,
@@ -48885,24 +49323,35 @@ async function drainFlushingPath(flushingPath, client, binding, options = {}) {
48885
49323
  `[prim] quarantined invalid journal line ${quarantined.quarantineId.slice(0, 12)} (invalid_move)
48886
49324
  `
48887
49325
  );
48888
- counts.quarantined += 1;
49326
+ counts = addDrainCounts(counts, reportProgress({ flushed: 0, quarantined: 1 }));
49327
+ if (batch.length === 0) {
49328
+ recordProgress(end);
49329
+ }
48889
49330
  continue;
48890
49331
  }
48891
- if (batch.length === BATCH_SIZE) {
48892
- counts = addDrainCounts(counts, await postBatch(batch));
48893
- batch = [];
49332
+ if (exceedsBatchBytes(batch.length, batchBytes, rawLine.length, BATCH_MAX_BYTES)) {
49333
+ await postPending();
49334
+ }
49335
+ batch.push({ move, end });
49336
+ batchBytes += rawLine.length;
49337
+ if (batch.length >= BATCH_SIZE) {
49338
+ await postPending();
48894
49339
  }
48895
49340
  }
48896
49341
  if (batch.length > 0) {
48897
- counts = addDrainCounts(counts, await postBatch(batch));
49342
+ await postPending();
48898
49343
  }
48899
49344
  } finally {
48900
49345
  input.destroy();
48901
49346
  }
48902
49347
  unlinkSync4(flushingPath);
49348
+ try {
49349
+ removeDrainCheckpoint(flushingPath);
49350
+ } catch {
49351
+ }
48903
49352
  return counts;
48904
49353
  }
48905
- async function drainPath(path9, client, binding) {
49354
+ async function drainPath(path9, client, binding, onProgress) {
48906
49355
  const tmpPath = `${path9}.flushing.${String(Date.now())}.${String(process.pid)}`;
48907
49356
  try {
48908
49357
  renameSync6(path9, tmpPath);
@@ -48912,7 +49361,19 @@ async function drainPath(path9, client, binding) {
48912
49361
  }
48913
49362
  throw err;
48914
49363
  }
48915
- return drainFlushingPath(tmpPath, client, binding);
49364
+ return drainFlushingPath(tmpPath, client, binding, { onProgress });
49365
+ }
49366
+ async function settleDrain(drain) {
49367
+ let progress = { flushed: 0, quarantined: 0 };
49368
+ try {
49369
+ return {
49370
+ counts: await drain((delta) => {
49371
+ progress = addDrainCounts(progress, delta);
49372
+ })
49373
+ };
49374
+ } catch (error) {
49375
+ return { counts: progress, failure: { error } };
49376
+ }
48916
49377
  }
48917
49378
  function selectRecoverable(files, now, opts = {}) {
48918
49379
  const quarantineMs = opts.quarantineMs ?? ORPHAN_QUARANTINE_MS;
@@ -48929,7 +49390,8 @@ async function recoverOrphans(candidates, options) {
48929
49390
  flushed: 0,
48930
49391
  quarantined: 0,
48931
49392
  errors: [],
48932
- failedBuckets: /* @__PURE__ */ new Set()
49393
+ failedBuckets: /* @__PURE__ */ new Set(),
49394
+ failedDrainFlushed: 0
48933
49395
  };
48934
49396
  const recoverable = selectRecoverable(candidates, options.now ?? Date.now(), {
48935
49397
  ownerPid: options.ownerPid ?? process.pid,
@@ -48940,28 +49402,41 @@ async function recoverOrphans(candidates, options) {
48940
49402
  if (summary.failedBuckets.has(file.bucket)) {
48941
49403
  continue;
48942
49404
  }
48943
- try {
48944
- const counts = await drain(file.path);
48945
- summary.flushed += counts.flushed;
48946
- summary.quarantined += counts.quarantined;
48947
- } catch (err) {
48948
- summary.errors.push(err);
49405
+ const { counts, failure: failure4 } = await settleDrain((onProgress) => drain(file.path, onProgress));
49406
+ summary.flushed += counts.flushed;
49407
+ summary.quarantined += counts.quarantined;
49408
+ if (failure4) {
49409
+ summary.failedDrainFlushed += counts.flushed;
49410
+ summary.errors.push(failure4.error);
48949
49411
  summary.failedBuckets.add(file.bucket);
48950
49412
  }
48951
49413
  }
48952
49414
  return summary;
48953
49415
  }
48954
49416
  var FlushError = class extends Error {
49417
+ /** Moves the whole sweep acknowledged, across every drain it ran. */
48955
49418
  flushed;
48956
49419
  quarantined;
48957
- constructor(cause, flushed, quarantined) {
49420
+ /** Buckets the failed sweep held back without sending, as on success. */
49421
+ retained;
49422
+ /**
49423
+ * Of `flushed`, the Moves acknowledged by the drains that failed, before
49424
+ * they failed. Only this shows the failure itself is advancing: `flushed`
49425
+ * also counts other buckets' drains, so a bucket whose drain fails every
49426
+ * time would read as progress while a sibling bucket kept delivering.
49427
+ */
49428
+ failedDrainFlushed;
49429
+ constructor(cause, flushed, quarantined, retained = [], failedDrainFlushed = 0) {
48958
49430
  super(cause instanceof Error ? cause.message : String(cause), { cause });
48959
49431
  this.name = "FlushError";
48960
49432
  this.flushed = flushed;
48961
49433
  this.quarantined = quarantined;
49434
+ this.retained = retained;
49435
+ this.failedDrainFlushed = failedDrainFlushed;
48962
49436
  }
48963
49437
  };
48964
49438
  async function flushOnce() {
49439
+ sweepOrphanedDrainProgress();
48965
49440
  const orphanCandidates = listFlushing({ sampleBytes: 0 });
48966
49441
  const liveBuckets = listBuckets();
48967
49442
  if (orphanCandidates.length === 0 && liveBuckets.length === 0) {
@@ -48978,10 +49453,11 @@ async function flushOnce() {
48978
49453
  const binding = inspection.binding;
48979
49454
  const recovered = await recoverOrphans(
48980
49455
  orphanCandidates.filter((file) => inspection.deliverableBuckets.has(file.bucket)),
48981
- { drain: (path9) => drainFlushingPath(path9, client, binding) }
49456
+ { drain: (path9, onProgress) => drainFlushingPath(path9, client, binding, { onProgress }) }
48982
49457
  );
48983
49458
  let total = recovered.flushed;
48984
49459
  let quarantined = recovered.quarantined;
49460
+ let failedDrainFlushed = recovered.failedDrainFlushed;
48985
49461
  const errors = recovered.errors;
48986
49462
  for (const { bucket, path: path9 } of liveBuckets) {
48987
49463
  if (!inspection.deliverableBuckets.has(bucket)) {
@@ -48990,21 +49466,29 @@ async function flushOnce() {
48990
49466
  if (recovered.failedBuckets.has(bucket)) {
48991
49467
  continue;
48992
49468
  }
48993
- try {
48994
- const counts = await drainPath(path9, client, binding);
48995
- total += counts.flushed;
48996
- quarantined += counts.quarantined;
48997
- } catch (err) {
48998
- errors.push(err);
49469
+ const { counts, failure: failure4 } = await settleDrain(
49470
+ (onProgress) => drainPath(path9, client, binding, onProgress)
49471
+ );
49472
+ total += counts.flushed;
49473
+ quarantined += counts.quarantined;
49474
+ if (failure4) {
49475
+ failedDrainFlushed += counts.flushed;
49476
+ errors.push(failure4.error);
48999
49477
  }
49000
49478
  }
49001
49479
  if (errors.length > 0) {
49002
- throw new FlushError(errors[0], total, quarantined);
49480
+ throw new FlushError(
49481
+ errors[0],
49482
+ total,
49483
+ quarantined,
49484
+ inspection.retainedBuckets,
49485
+ failedDrainFlushed
49486
+ );
49003
49487
  }
49004
49488
  return inspection.retainedBuckets.length > 0 ? { flushed: total, quarantined, retained: inspection.retainedBuckets } : { flushed: total, quarantined };
49005
49489
  }
49006
49490
  var flushInFlight;
49007
- var FLUSH_LOCK_PATH = join20(dirname13(JOURNAL_DIR), ".flush.lock");
49491
+ var FLUSH_LOCK_PATH = join21(dirname14(JOURNAL_DIR), ".flush.lock");
49008
49492
  var FLUSH_LOCK_TIMEOUT_MS = 250;
49009
49493
  function isFlushLockContended(error) {
49010
49494
  return error instanceof Error && error.message.startsWith("timed out waiting for file lock");
@@ -49029,18 +49513,16 @@ function flush() {
49029
49513
  return attempt;
49030
49514
  }
49031
49515
  function shouldFlushPending(stats, now, thresholdMs = OPPORTUNISTIC_FLUSH_AFTER_MS) {
49032
- if (stats.sampled) {
49516
+ if (stats.sampled || stats.strandedFileCount > 0 && stats.strandedCount === 0) {
49033
49517
  return true;
49034
49518
  }
49035
49519
  return stats.pendingCount > 0 && (stats.oldestPendingAt === void 0 || now - stats.oldestPendingAt > thresholdMs);
49036
49520
  }
49037
- async function flushIfNeeded() {
49521
+ function journalNeedsFlush(now = Date.now()) {
49038
49522
  try {
49039
- const stats = pendingJournalStats();
49040
- if (shouldFlushPending(stats, Date.now())) {
49041
- await flush();
49042
- }
49523
+ return shouldFlushPending(pendingJournalStats(), now);
49043
49524
  } catch {
49525
+ return false;
49044
49526
  }
49045
49527
  }
49046
49528
 
@@ -49053,7 +49535,7 @@ var EVENT_COL_WIDTH = 20;
49053
49535
  var BUCKET_COL_WIDTH = 20;
49054
49536
  var TAIL_BUCKET_COL_WIDTH = 12;
49055
49537
  var DIR_MODE = 448;
49056
- var FILE_MODE3 = 384;
49538
+ var FILE_MODE4 = 384;
49057
49539
  var WORKSPACE_FILE = ".prim/workspace.json";
49058
49540
  function registerMovesCommands(program3) {
49059
49541
  const moves = program3.command("moves").description("Decision Event Pipeline \u2014 local journal");
@@ -49099,7 +49581,7 @@ function registerMovesCommands(program3) {
49099
49581
  }
49100
49582
  if (stranded.length > 0) {
49101
49583
  const moveCount = stranded.reduce((n, f) => n + f.lineCount, 0);
49102
- const byteCount = stranded.reduce((n, f) => n + f.sizeBytes, 0);
49584
+ const byteCount = stranded.reduce((n, f) => n + f.pendingBytes, 0);
49103
49585
  const sampled = stranded.some((file) => file.sampled);
49104
49586
  const qualifier = sampled ? "at least " : "";
49105
49587
  console.log(
@@ -49110,7 +49592,7 @@ function registerMovesCommands(program3) {
49110
49592
  const owner = f.pid === void 0 ? "no pid" : `pid ${String(f.pid)}`;
49111
49593
  const count = `${String(f.lineCount)}${f.sampled ? "+" : ""}`;
49112
49594
  console.log(
49113
- ` ${f.bucket.padEnd(BUCKET_COL_WIDTH)} ${count.padStart(5)} stranded, ${String(f.sizeBytes).padStart(8)} bytes, ${String(ageS)}s ago (${owner})`
49595
+ ` ${f.bucket.padEnd(BUCKET_COL_WIDTH)} ${count.padStart(5)} stranded, ${String(f.pendingBytes).padStart(8)} bytes, ${String(ageS)}s ago (${owner})`
49114
49596
  );
49115
49597
  }
49116
49598
  }
@@ -49138,19 +49620,19 @@ function registerMovesCommands(program3) {
49138
49620
  }
49139
49621
  });
49140
49622
  moves.command("bind").description("Pin the current directory to an org via .prim/workspace.json").requiredOption("--orgId <orgId>", "Convex organization id").action((opts) => {
49141
- const dir = join21(process.cwd(), ".prim");
49142
- if (!existsSync17(dir)) {
49623
+ const dir = join22(process.cwd(), ".prim");
49624
+ if (!existsSync18(dir)) {
49143
49625
  mkdirSync12(dir, { recursive: true, mode: DIR_MODE });
49144
49626
  }
49145
- const file = join21(process.cwd(), WORKSPACE_FILE);
49627
+ const file = join22(process.cwd(), WORKSPACE_FILE);
49146
49628
  writeFileSync9(file, JSON.stringify({ orgId: opts.orgId, boundAt: Date.now() }, null, 2), {
49147
- mode: FILE_MODE3
49629
+ mode: FILE_MODE4
49148
49630
  });
49149
49631
  console.log(`[prim] bound ${process.cwd()} to org ${opts.orgId}`);
49150
49632
  });
49151
49633
  moves.command("drop").description("Remove the .prim/workspace.json binding from the cwd").action(() => {
49152
- const file = join21(process.cwd(), WORKSPACE_FILE);
49153
- if (!existsSync17(file)) {
49634
+ const file = join22(process.cwd(), WORKSPACE_FILE);
49635
+ if (!existsSync18(file)) {
49154
49636
  console.log("[prim] no workspace binding in cwd");
49155
49637
  return;
49156
49638
  }
@@ -49241,31 +49723,31 @@ function registerReconcileCommands(program3) {
49241
49723
 
49242
49724
  // src/commands/session.ts
49243
49725
  import {
49244
- existsSync as existsSync19,
49726
+ existsSync as existsSync20,
49245
49727
  mkdirSync as mkdirSync13,
49246
- readFileSync as readFileSync17,
49247
- readdirSync as readdirSync5,
49728
+ readFileSync as readFileSync18,
49729
+ readdirSync as readdirSync6,
49248
49730
  unlinkSync as unlinkSync6,
49249
49731
  writeFileSync as writeFileSync10
49250
49732
  } from "fs";
49251
- import { join as join23 } from "path";
49733
+ import { join as join24 } from "path";
49252
49734
 
49253
49735
  // src/binding.ts
49254
- import { existsSync as existsSync18, readFileSync as readFileSync16 } from "fs";
49255
- import { dirname as dirname14, join as join22 } from "path";
49736
+ import { existsSync as existsSync19, readFileSync as readFileSync17 } from "fs";
49737
+ import { dirname as dirname15, join as join23 } from "path";
49256
49738
  var PRIM_CONFIG_DIR = primConfigDirectory();
49257
- var SESSIONS_DIR = join22(PRIM_CONFIG_DIR, "sessions");
49739
+ var SESSIONS_DIR = join23(PRIM_CONFIG_DIR, "sessions");
49258
49740
 
49259
49741
  // src/commands/session.ts
49260
49742
  var DIR_MODE2 = 448;
49261
- var FILE_MODE4 = 384;
49743
+ var FILE_MODE5 = 384;
49262
49744
  function ensureDir() {
49263
- if (!existsSync19(SESSIONS_DIR)) {
49745
+ if (!existsSync20(SESSIONS_DIR)) {
49264
49746
  mkdirSync13(SESSIONS_DIR, { recursive: true, mode: DIR_MODE2 });
49265
49747
  }
49266
49748
  }
49267
49749
  function markerPath(sessionId) {
49268
- return join23(SESSIONS_DIR, `${sessionId}.json`);
49750
+ return join24(SESSIONS_DIR, `${sessionId}.json`);
49269
49751
  }
49270
49752
  function registerSessionCommands(program3) {
49271
49753
  const session = program3.command("session").description("Decision Event Pipeline \u2014 session binding markers");
@@ -49276,16 +49758,16 @@ function registerSessionCommands(program3) {
49276
49758
  startedAt: Date.now()
49277
49759
  };
49278
49760
  writeFileSync10(markerPath(sessionId), JSON.stringify(marker, null, 2), {
49279
- mode: FILE_MODE4
49761
+ mode: FILE_MODE5
49280
49762
  });
49281
49763
  console.log(`[prim] session ${sessionId} bound to org ${opts.orgId}`);
49282
49764
  });
49283
49765
  session.command("list").description("List active session markers").action(() => {
49284
- if (!existsSync19(SESSIONS_DIR)) {
49766
+ if (!existsSync20(SESSIONS_DIR)) {
49285
49767
  console.log("[prim] no session markers");
49286
49768
  return;
49287
49769
  }
49288
- const files = readdirSync5(SESSIONS_DIR).filter((f) => f.endsWith(".json"));
49770
+ const files = readdirSync6(SESSIONS_DIR).filter((f) => f.endsWith(".json"));
49289
49771
  if (files.length === 0) {
49290
49772
  console.log("[prim] no session markers");
49291
49773
  return;
@@ -49293,7 +49775,7 @@ function registerSessionCommands(program3) {
49293
49775
  for (const f of files) {
49294
49776
  const sessionId = f.replace(/\.json$/, "");
49295
49777
  try {
49296
- const m = JSON.parse(readFileSync17(join23(SESSIONS_DIR, f), "utf-8"));
49778
+ const m = JSON.parse(readFileSync18(join24(SESSIONS_DIR, f), "utf-8"));
49297
49779
  console.log(`${sessionId} org=${m.orgId}`);
49298
49780
  } catch {
49299
49781
  }
@@ -49301,7 +49783,7 @@ function registerSessionCommands(program3) {
49301
49783
  });
49302
49784
  session.command("drop <sessionId>").description("Remove a session marker").action((sessionId) => {
49303
49785
  const p = markerPath(sessionId);
49304
- if (!existsSync19(p)) {
49786
+ if (!existsSync20(p)) {
49305
49787
  console.log(`[prim] no marker for session ${sessionId}`);
49306
49788
  return;
49307
49789
  }
@@ -49312,10 +49794,24 @@ function registerSessionCommands(program3) {
49312
49794
 
49313
49795
  // src/commands/setup.ts
49314
49796
  import { spawnSync as spawnSync2 } from "child_process";
49315
- import { existsSync as existsSync20, readFileSync as readFileSync18 } from "fs";
49316
- import { join as join24 } from "path";
49797
+ import { existsSync as existsSync21, readFileSync as readFileSync19 } from "fs";
49798
+ import { join as join25 } from "path";
49317
49799
  var EXIT_INCOMPLETE = 1;
49318
49800
  var EXIT_USAGE3 = 2;
49801
+ var SETUP_ORCHESTRATOR_ENV = "PRIM_SETUP_ORCHESTRATOR";
49802
+ var SETUP_DAEMON_DRAINS_ENV = "PRIM_SETUP_DAEMON_DRAINS";
49803
+ function setupStepSpawnOptions(capture, env4 = process.env, options = {}) {
49804
+ return {
49805
+ env: {
49806
+ ...env4,
49807
+ [SETUP_ORCHESTRATOR_ENV]: "1",
49808
+ // Unset rather than inherited when this setup starts no daemon.
49809
+ [SETUP_DAEMON_DRAINS_ENV]: options.startsDaemon === true ? "1" : void 0
49810
+ },
49811
+ stdio: capture ? ["inherit", "pipe", "ignore"] : "inherit",
49812
+ encoding: "utf-8"
49813
+ };
49814
+ }
49319
49815
  var SESSION_LABELS = {
49320
49816
  claude: "Claude Code integration",
49321
49817
  codex: "Codex integration",
@@ -49366,7 +49862,7 @@ function planSetupSteps(opts) {
49366
49862
  steps.push({
49367
49863
  key: "health",
49368
49864
  label: "Capture health",
49369
- args: ["doctor"],
49865
+ args: ["doctor", "--expect-backlog"],
49370
49866
  required: true
49371
49867
  });
49372
49868
  }
@@ -49395,8 +49891,8 @@ function detectProjectConflicts(agent, run) {
49395
49891
  }
49396
49892
  try {
49397
49893
  const root = gitToplevel();
49398
- const preCommit = root && join24(root, ".git", "hooks", "pre-commit");
49399
- if (preCommit && existsSync20(preCommit) && readFileSync18(preCommit, "utf-8").includes("prim-pre-commit")) {
49894
+ const preCommit = root && join25(root, ".git", "hooks", "pre-commit");
49895
+ if (preCommit && existsSync21(preCommit) && readFileSync19(preCommit, "utf-8").includes("prim-pre-commit")) {
49400
49896
  conflicts.push(CONFLICT_HOOKS);
49401
49897
  }
49402
49898
  } catch {
@@ -49466,10 +49962,11 @@ function registerSetupCommand(program3, dependencies = {}) {
49466
49962
  const self = process.argv[1];
49467
49963
  const globals = program3.optsWithGlobals();
49468
49964
  const run = dependencies.run ?? ((args, capture = false) => {
49469
- const r = spawnSync2(process.execPath, [self, ...args], {
49470
- stdio: capture ? ["inherit", "pipe", "ignore"] : "inherit",
49471
- encoding: "utf-8"
49472
- });
49965
+ const r = spawnSync2(
49966
+ process.execPath,
49967
+ [self, ...args],
49968
+ setupStepSpawnOptions(capture, process.env, { startsDaemon: opts.daemon })
49969
+ );
49473
49970
  return { code: r.status ?? 1, stdout: capture ? r.stdout ?? "" : "" };
49474
49971
  });
49475
49972
  const results = {};
@@ -49562,20 +50059,20 @@ function registerSetupCommand(program3, dependencies = {}) {
49562
50059
  // src/commands/skill.ts
49563
50060
  import { createHash as createHash11 } from "crypto";
49564
50061
  import {
49565
- constants as constants7,
49566
- closeSync as closeSync7,
49567
- existsSync as existsSync22,
49568
- fstatSync as fstatSync3,
49569
- lstatSync as lstatSync7,
49570
- openSync as openSync7,
49571
- readFileSync as readFileSync20,
49572
- readSync as readSync3,
49573
- readdirSync as readdirSync7,
49574
- rmdirSync as rmdirSync2,
50062
+ constants as constants8,
50063
+ closeSync as closeSync8,
50064
+ existsSync as existsSync23,
50065
+ fstatSync as fstatSync4,
50066
+ lstatSync as lstatSync8,
50067
+ openSync as openSync8,
50068
+ readFileSync as readFileSync21,
50069
+ readSync as readSync4,
50070
+ readdirSync as readdirSync8,
50071
+ rmdirSync as rmdirSync3,
49575
50072
  unlinkSync as unlinkSync7
49576
50073
  } from "fs";
49577
50074
  import { homedir as homedir11 } from "os";
49578
- import { dirname as dirname16, join as join27, resolve as resolve8 } from "path";
50075
+ import { dirname as dirname17, join as join28, resolve as resolve8 } from "path";
49579
50076
  import { fileURLToPath as fileURLToPath5 } from "url";
49580
50077
 
49581
50078
  // node_modules/.pnpm/diff@5.2.2/node_modules/diff/lib/index.mjs
@@ -49745,7 +50242,7 @@ Diff.prototype = {
49745
50242
  tokenize: function tokenize(value) {
49746
50243
  return value.split("");
49747
50244
  },
49748
- join: function join25(chars) {
50245
+ join: function join26(chars) {
49749
50246
  return chars.join("");
49750
50247
  }
49751
50248
  };
@@ -50083,30 +50580,30 @@ function createPatch(fileName, oldStr, newStr, oldHeader, newHeader, options) {
50083
50580
  // src/commands/claude-plugin.ts
50084
50581
  import { createHash as createHash10 } from "crypto";
50085
50582
  import {
50086
- constants as constants6,
50087
- closeSync as closeSync6,
50088
- existsSync as existsSync21,
50089
- fstatSync as fstatSync2,
50090
- lstatSync as lstatSync6,
50583
+ constants as constants7,
50584
+ closeSync as closeSync7,
50585
+ existsSync as existsSync22,
50586
+ fstatSync as fstatSync3,
50587
+ lstatSync as lstatSync7,
50091
50588
  mkdirSync as mkdirSync14,
50092
- openSync as openSync6,
50093
- readFileSync as readFileSync19,
50094
- readSync as readSync2,
50095
- readdirSync as readdirSync6,
50589
+ openSync as openSync7,
50590
+ readFileSync as readFileSync20,
50591
+ readSync as readSync3,
50592
+ readdirSync as readdirSync7,
50096
50593
  realpathSync as realpathSync4,
50097
- rmSync as rmSync8,
50098
- rmdirSync
50594
+ rmSync as rmSync9,
50595
+ rmdirSync as rmdirSync2
50099
50596
  } from "fs";
50100
50597
  import { homedir as homedir10 } from "os";
50101
- import { dirname as dirname15, isAbsolute as isAbsolute8, join as join26, relative as relative2, resolve as resolve7, sep as sep2 } from "path";
50598
+ import { dirname as dirname16, isAbsolute as isAbsolute8, join as join27, relative as relative2, resolve as resolve7, sep as sep2 } from "path";
50102
50599
  import { fileURLToPath as fileURLToPath4 } from "url";
50103
- var __dirname4 = dirname15(fileURLToPath4(import.meta.url));
50600
+ var __dirname4 = dirname16(fileURLToPath4(import.meta.url));
50104
50601
  var PLUGIN_DESCRIPTION = "Primitive decision-graph guidance for the prim CLI \u2014 a model-invoked skill installed by prim skill install.";
50105
50602
  var MAX_MANIFEST_BYTES2 = 64 * 1024;
50106
50603
  var MAX_SKILL_BYTES = 1024 * 1024;
50107
50604
  function pluginDirFor(cwd, scope) {
50108
- const base = scope === "user" ? join26(homedir10(), ".claude") : join26(gitToplevel(cwd) ?? cwd, ".claude");
50109
- return join26(base, "skills", "prim");
50605
+ const base = scope === "user" ? join27(homedir10(), ".claude") : join27(gitToplevel(cwd) ?? cwd, ".claude");
50606
+ return join27(base, "skills", "prim");
50110
50607
  }
50111
50608
  function resolvePluginDir(cwd, scope) {
50112
50609
  if (scope && scope !== "user" && scope !== "project") {
@@ -50117,10 +50614,10 @@ function resolvePluginDir(cwd, scope) {
50117
50614
  }
50118
50615
  function packageVersion2() {
50119
50616
  let dir = __dirname4;
50120
- while (dir !== dirname15(dir)) {
50617
+ while (dir !== dirname16(dir)) {
50121
50618
  const p = resolve7(dir, "package.json");
50122
- if (existsSync21(p)) return JSON.parse(readFileSync19(p, "utf-8")).version;
50123
- dir = dirname15(dir);
50619
+ if (existsSync22(p)) return JSON.parse(readFileSync20(p, "utf-8")).version;
50620
+ dir = dirname16(dir);
50124
50621
  }
50125
50622
  return "0.0.0";
50126
50623
  }
@@ -50134,12 +50631,12 @@ function renderClaudePlugin() {
50134
50631
  return { manifest: pluginManifest(version), skill: loadSkill(), version };
50135
50632
  }
50136
50633
  function removeDirIfEmpty(dir) {
50137
- if (existsSync21(dir) && readdirSync6(dir).length === 0) rmdirSync(dir);
50634
+ if (existsSync22(dir) && readdirSync7(dir).length === 0) rmdirSync2(dir);
50138
50635
  }
50139
50636
  function pluginPaths(dir) {
50140
50637
  return {
50141
- manifestPath: join26(dir, ".claude-plugin", "plugin.json"),
50142
- skillPath: join26(dir, "SKILL.md")
50638
+ manifestPath: join27(dir, ".claude-plugin", "plugin.json"),
50639
+ skillPath: join27(dir, "SKILL.md")
50143
50640
  };
50144
50641
  }
50145
50642
  function installClaudePlugin(cwd, opts) {
@@ -50147,8 +50644,8 @@ function installClaudePlugin(cwd, opts) {
50147
50644
  if (dir === null) return 1;
50148
50645
  const { manifestPath, skillPath } = pluginPaths(dir);
50149
50646
  const { manifest, skill } = renderClaudePlugin();
50150
- const manifestCurrent = existsSync21(manifestPath) ? readFileSync19(manifestPath, "utf-8") : null;
50151
- const skillCurrent = existsSync21(skillPath) ? readFileSync19(skillPath, "utf-8") : null;
50647
+ const manifestCurrent = existsSync22(manifestPath) ? readFileSync20(manifestPath, "utf-8") : null;
50648
+ const skillCurrent = existsSync22(skillPath) ? readFileSync20(skillPath, "utf-8") : null;
50152
50649
  if (manifestCurrent === manifest && skillCurrent === skill) {
50153
50650
  console.log("No changes \u2014 prim skill plugin already up to date.");
50154
50651
  return 0;
@@ -50157,7 +50654,7 @@ function installClaudePlugin(cwd, opts) {
50157
50654
  console.log(`Would write plugin to ${dir} (.claude-plugin/plugin.json + SKILL.md)`);
50158
50655
  return 0;
50159
50656
  }
50160
- mkdirSync14(join26(dir, ".claude-plugin"), { recursive: true });
50657
+ mkdirSync14(join27(dir, ".claude-plugin"), { recursive: true });
50161
50658
  atomicWrite4(manifestPath, manifest);
50162
50659
  atomicWrite4(skillPath, skill);
50163
50660
  console.log(`Installed prim skill plugin at ${dir}`);
@@ -50171,13 +50668,13 @@ function uninstallClaudePlugin(cwd, opts) {
50171
50668
  const dir = resolvePluginDir(cwd, opts.scope);
50172
50669
  if (dir === null) return 1;
50173
50670
  const { manifestPath, skillPath } = pluginPaths(dir);
50174
- if (!existsSync21(manifestPath) && !existsSync21(skillPath)) {
50671
+ if (!existsSync22(manifestPath) && !existsSync22(skillPath)) {
50175
50672
  console.log(`prim skill plugin not present at ${dir}`);
50176
50673
  return 0;
50177
50674
  }
50178
- rmSync8(manifestPath, { force: true });
50179
- rmSync8(skillPath, { force: true });
50180
- removeDirIfEmpty(join26(dir, ".claude-plugin"));
50675
+ rmSync9(manifestPath, { force: true });
50676
+ rmSync9(skillPath, { force: true });
50677
+ removeDirIfEmpty(join27(dir, ".claude-plugin"));
50181
50678
  removeDirIfEmpty(dir);
50182
50679
  console.log(`Removed prim skill plugin from ${dir}`);
50183
50680
  return 0;
@@ -50186,7 +50683,7 @@ function statusClaudePlugin(cwd, opts) {
50186
50683
  const dir = resolvePluginDir(cwd, opts.scope);
50187
50684
  if (dir === null) return 1;
50188
50685
  const { skillPath } = pluginPaths(dir);
50189
- const installed = existsSync21(skillPath);
50686
+ const installed = existsSync22(skillPath);
50190
50687
  if (opts.json) {
50191
50688
  printJson({ installed, target: dir });
50192
50689
  return installed ? 0 : 1;
@@ -50200,7 +50697,7 @@ function statusClaudePlugin(cwd, opts) {
50200
50697
  }
50201
50698
 
50202
50699
  // src/commands/skill.ts
50203
- var __dirname5 = dirname16(fileURLToPath5(import.meta.url));
50700
+ var __dirname5 = dirname17(fileURLToPath5(import.meta.url));
50204
50701
  var SKILL_BEGIN = "<!-- BEGIN PRIM SKILL v1 -->";
50205
50702
  var SKILL_END = "<!-- END PRIM SKILL v1 -->";
50206
50703
  var CODEX_GUIDANCE_LIMIT_BYTES = 1024 * 1024;
@@ -50228,25 +50725,25 @@ function sha2563(content) {
50228
50725
  }
50229
50726
  function cursorSkillDirectory(cwd, scope) {
50230
50727
  const base = scope === "user" ? cursorConfigDirectory() : gitToplevel(cwd) ?? cwd;
50231
- return join27(base, scope === "user" ? "skills" : ".cursor/skills", "prim");
50728
+ return join28(base, scope === "user" ? "skills" : ".cursor/skills", "prim");
50232
50729
  }
50233
50730
  function cursorSkillPaths(cwd, scope) {
50234
50731
  const directory = cursorSkillDirectory(cwd, scope);
50235
50732
  return {
50236
50733
  directory,
50237
- skill: join27(directory, "SKILL.md"),
50238
- manifest: join27(directory, CURSOR_SKILL_MANIFEST)
50734
+ skill: join28(directory, "SKILL.md"),
50735
+ manifest: join28(directory, CURSOR_SKILL_MANIFEST)
50239
50736
  };
50240
50737
  }
50241
50738
  function readCursorSkillManifest(path9) {
50242
50739
  try {
50243
- const value = JSON.parse(readFileSync20(path9, "utf8"));
50740
+ const value = JSON.parse(readFileSync21(path9, "utf8"));
50244
50741
  if (!value || typeof value !== "object" || Array.isArray(value)) return;
50245
- const record2 = value;
50246
- if (record2.version !== 1 || record2.owner !== CURSOR_SKILL_OWNER || typeof record2.contentSha256 !== "string" || !/^[0-9a-f]{64}$/u.test(record2.contentSha256)) {
50742
+ const record3 = value;
50743
+ if (record3.version !== 1 || record3.owner !== CURSOR_SKILL_OWNER || typeof record3.contentSha256 !== "string" || !/^[0-9a-f]{64}$/u.test(record3.contentSha256)) {
50247
50744
  return;
50248
50745
  }
50249
- return record2;
50746
+ return record3;
50250
50747
  } catch {
50251
50748
  return;
50252
50749
  }
@@ -50254,7 +50751,7 @@ function readCursorSkillManifest(path9) {
50254
50751
  function cursorSkillStatus(cwd, scope) {
50255
50752
  const paths = cursorSkillPaths(cwd, scope);
50256
50753
  const manifest = readCursorSkillManifest(paths.manifest);
50257
- const content = existsSync22(paths.skill) ? readFileSync20(paths.skill, "utf8") : void 0;
50754
+ const content = existsSync23(paths.skill) ? readFileSync21(paths.skill, "utf8") : void 0;
50258
50755
  const owned = manifest !== void 0;
50259
50756
  const installed = owned && content !== void 0 && sha2563(content) === manifest.contentSha256;
50260
50757
  return {
@@ -50272,8 +50769,8 @@ function validCursorSkillScope(scope) {
50272
50769
  function installCursorSkill(cwd, opts) {
50273
50770
  const paths = cursorSkillPaths(cwd, opts.scope);
50274
50771
  const status = cursorSkillStatus(cwd, opts.scope);
50275
- const manifestExists = existsSync22(paths.manifest);
50276
- const skillExists = existsSync22(paths.skill);
50772
+ const manifestExists = existsSync23(paths.manifest);
50773
+ const skillExists = existsSync23(paths.skill);
50277
50774
  if (manifestExists && !status.owned || !manifestExists && skillExists || status.edited) {
50278
50775
  console.error(`Refusing to overwrite a foreign or edited Cursor skill at ${paths.skill}`);
50279
50776
  return 1;
@@ -50284,12 +50781,12 @@ function installCursorSkill(cwd, opts) {
50284
50781
  owner: CURSOR_SKILL_OWNER,
50285
50782
  contentSha256: sha2563(content)
50286
50783
  };
50287
- if (status.installed && readFileSync20(paths.skill, "utf8") === content) {
50784
+ if (status.installed && readFileSync21(paths.skill, "utf8") === content) {
50288
50785
  console.log(`No changes \u2014 Cursor skill already up to date at ${paths.skill}`);
50289
50786
  return 0;
50290
50787
  }
50291
50788
  if (opts.dryRun) {
50292
- const existing = skillExists ? readFileSync20(paths.skill, "utf8") : "";
50789
+ const existing = skillExists ? readFileSync21(paths.skill, "utf8") : "";
50293
50790
  process.stdout.write(createPatch(paths.skill, existing, content, "current", "proposed"));
50294
50791
  return 0;
50295
50792
  }
@@ -50310,34 +50807,34 @@ function uninstallCursorSkill(cwd, scope) {
50310
50807
  console.error(`Refusing to remove an edited Cursor skill at ${paths.skill}`);
50311
50808
  return 1;
50312
50809
  }
50313
- if (existsSync22(paths.skill)) unlinkSync7(paths.skill);
50314
- if (existsSync22(paths.manifest)) unlinkSync7(paths.manifest);
50810
+ if (existsSync23(paths.skill)) unlinkSync7(paths.skill);
50811
+ if (existsSync23(paths.manifest)) unlinkSync7(paths.manifest);
50315
50812
  try {
50316
- if (readdirSync7(paths.directory).length === 0) rmdirSync2(paths.directory);
50813
+ if (readdirSync8(paths.directory).length === 0) rmdirSync3(paths.directory);
50317
50814
  } catch {
50318
50815
  }
50319
50816
  console.log(`Removed Primitive-owned Cursor skill from ${paths.skill}`);
50320
50817
  return 0;
50321
50818
  }
50322
50819
  function userTargetFor(agent) {
50323
- if (agent === "claude") return join27(homedir11(), ".claude", "CLAUDE.md");
50324
- if (agent === "codex") return join27(homedir11(), ".codex", "AGENTS.md");
50820
+ if (agent === "claude") return join28(homedir11(), ".claude", "CLAUDE.md");
50821
+ if (agent === "codex") return join28(homedir11(), ".codex", "AGENTS.md");
50325
50822
  if (agent === "hermes") {
50326
- return join27(process.env.HERMES_HOME ?? join27(homedir11(), ".hermes"), ".hermes.md");
50823
+ return join28(process.env.HERMES_HOME ?? join28(homedir11(), ".hermes"), ".hermes.md");
50327
50824
  }
50328
50825
  return null;
50329
50826
  }
50330
50827
  function loadSkill() {
50331
50828
  let dir = __dirname5;
50332
- while (dir !== dirname16(dir)) {
50829
+ while (dir !== dirname17(dir)) {
50333
50830
  const p = resolve8(dir, "SKILL.md");
50334
- if (existsSync22(p)) return readFileSync20(p, "utf-8");
50335
- dir = dirname16(dir);
50831
+ if (existsSync23(p)) return readFileSync21(p, "utf-8");
50832
+ dir = dirname17(dir);
50336
50833
  }
50337
50834
  throw new Error("SKILL.md not found in package");
50338
50835
  }
50339
50836
  function detectTargets(cwd) {
50340
- return TARGET_CANDIDATES.filter((p) => existsSync22(resolve8(cwd, p)));
50837
+ return TARGET_CANDIDATES.filter((p) => existsSync23(resolve8(cwd, p)));
50341
50838
  }
50342
50839
  function detectNewline(content) {
50343
50840
  return content.includes("\r\n") ? "\r\n" : "\n";
@@ -50403,7 +50900,7 @@ function runInstall(cwd, opts) {
50403
50900
  }
50404
50901
  const target = resolveTarget(cwd, opts);
50405
50902
  if (target === null) return 1;
50406
- const existing = existsSync22(target) ? readFileSync20(target, "utf-8") : "";
50903
+ const existing = existsSync23(target) ? readFileSync21(target, "utf-8") : "";
50407
50904
  const eol = existing ? detectNewline(existing) : "\n";
50408
50905
  const block = composeBlock(loadSkill(), eol);
50409
50906
  const next = applyBlock(existing, block, eol);
@@ -50427,11 +50924,11 @@ function runUninstall(cwd, opts) {
50427
50924
  }
50428
50925
  const target = resolveTarget(cwd, opts);
50429
50926
  if (target === null) return 1;
50430
- if (!existsSync22(target)) {
50927
+ if (!existsSync23(target)) {
50431
50928
  console.log(`Skill block not present at ${target}`);
50432
50929
  return 0;
50433
50930
  }
50434
- const existing = readFileSync20(target, "utf-8");
50931
+ const existing = readFileSync21(target, "utf-8");
50435
50932
  const next = removeBlock(existing);
50436
50933
  if (next === null) {
50437
50934
  console.log(`Skill block not present at ${target}`);
@@ -50455,10 +50952,10 @@ function runStatus(cwd, opts) {
50455
50952
  }
50456
50953
  const target = resolveTarget(cwd, opts);
50457
50954
  if (target === null) return 1;
50458
- const fileExists = existsSync22(target);
50955
+ const fileExists = existsSync23(target);
50459
50956
  let installed = false;
50460
50957
  if (fileExists) {
50461
- const content = readFileSync20(target, "utf-8");
50958
+ const content = readFileSync21(target, "utf-8");
50462
50959
  installed = content.includes(SKILL_BEGIN) && content.includes(SKILL_END);
50463
50960
  }
50464
50961
  if (opts.json) {
@@ -50495,13 +50992,13 @@ function registerSkillCommands(program3) {
50495
50992
  }
50496
50993
 
50497
50994
  // src/commands/statusline.ts
50498
- import { readFileSync as readFileSync21 } from "fs";
50499
- import { dirname as dirname17, resolve as resolve9 } from "path";
50995
+ import { readFileSync as readFileSync22 } from "fs";
50996
+ import { dirname as dirname18, resolve as resolve9 } from "path";
50500
50997
  import { fileURLToPath as fileURLToPath6 } from "url";
50501
50998
  var STATUSLINE_TIMEOUT_MS = 200;
50502
50999
  function readPackageVersion() {
50503
51000
  try {
50504
- const here = dirname17(fileURLToPath6(import.meta.url));
51001
+ const here = dirname18(fileURLToPath6(import.meta.url));
50505
51002
  const candidates = [
50506
51003
  // Schema-v2 runtimes staged a manifest beside this standalone bundle.
50507
51004
  // Keep resolving it while those legacy launchers transition to schema v3.
@@ -50511,7 +51008,7 @@ function readPackageVersion() {
50511
51008
  ];
50512
51009
  for (const path9 of candidates) {
50513
51010
  try {
50514
- const pkg2 = JSON.parse(readFileSync21(path9, "utf-8"));
51011
+ const pkg2 = JSON.parse(readFileSync22(path9, "utf-8"));
50515
51012
  if (pkg2.version) {
50516
51013
  return pkg2.version;
50517
51014
  }
@@ -50925,28 +51422,272 @@ function registerWelcomeCommand(program3, deps = { getClient }) {
50925
51422
  });
50926
51423
  }
50927
51424
 
51425
+ // src/daemon/self-heal.ts
51426
+ import { spawn as spawn4 } from "child_process";
51427
+
51428
+ // src/lib/unattended.ts
51429
+ var UNATTENDED_ENV = "PRIM_UNATTENDED";
51430
+ function unattendedEnv(env4 = process.env) {
51431
+ return { ...env4, [UNATTENDED_ENV]: "1" };
51432
+ }
51433
+ function isUnattended(env4 = process.env) {
51434
+ return env4[UNATTENDED_ENV] === "1";
51435
+ }
51436
+
51437
+ // src/daemon/self-heal.ts
51438
+ function kickDaemonEnsure(options = {}) {
51439
+ const primEntry = options.primEntry === void 0 ? binFile("prim") : options.primEntry;
51440
+ if (!primEntry) {
51441
+ return false;
51442
+ }
51443
+ try {
51444
+ const args = [primEntry, "daemon", "ensure"];
51445
+ if (options.latestBootstrap ?? (options.platform ?? process.platform) === "darwin") {
51446
+ args.push("--latest-bootstrap");
51447
+ }
51448
+ const child = (options.spawnProcess ?? spawn4)(
51449
+ options.nodeEntry ?? process.execPath,
51450
+ args,
51451
+ // Unattended: neither SessionStart's repair nor a drift heal is a person
51452
+ // running prim, so the child (and its descendants) never heal in turn.
51453
+ { detached: true, stdio: "ignore", env: unattendedEnv() }
51454
+ );
51455
+ child.once("error", () => {
51456
+ });
51457
+ child.unref();
51458
+ return true;
51459
+ } catch {
51460
+ return false;
51461
+ }
51462
+ }
51463
+
51464
+ // src/daemon/drift-heal.ts
51465
+ var ATTEMPT_MARKER_MODE = 384;
51466
+ var ATTEMPT_MARKER_MAX_BYTES = 1024;
51467
+ var PATH_SEPARATORS = /[\\/]/u;
51468
+ var DAEMON_DRIFT_HEAL_ENV = "PRIM_DAEMON_DRIFT_HEAL";
51469
+ var DAEMON_DRIFT_HEAL_RETRY_MS = 60 * 60 * 1e3;
51470
+ function readAttempt(path9) {
51471
+ const file = readBoundedRegularFile(path9, ATTEMPT_MARKER_MAX_BYTES);
51472
+ if (!file) return null;
51473
+ try {
51474
+ const value = JSON.parse(file.text);
51475
+ if (typeof value !== "object" || value === null) return null;
51476
+ const attempt = value;
51477
+ return typeof attempt.attemptedAt === "number" && Number.isFinite(attempt.attemptedAt) && typeof attempt.fromVersion === "string" && typeof attempt.toVersion === "string" ? attempt : null;
51478
+ } catch {
51479
+ return null;
51480
+ }
51481
+ }
51482
+ function claimHeld() {
51483
+ throw new Error("another command holds the daemon drift-heal claim");
51484
+ }
51485
+ function claimAttempt(markerPath2, attempt) {
51486
+ const previous = readAttempt(markerPath2);
51487
+ const elapsed = previous ? attempt.attemptedAt - previous.attemptedAt : Number.POSITIVE_INFINITY;
51488
+ if (elapsed >= 0 && elapsed < DAEMON_DRIFT_HEAL_RETRY_MS) return false;
51489
+ atomicWriteFile(markerPath2, `${JSON.stringify(attempt)}
51490
+ `, { mode: ATTEMPT_MARKER_MODE });
51491
+ return true;
51492
+ }
51493
+ function isInstalledPackage(root) {
51494
+ return root?.split(PATH_SEPARATORS).includes("node_modules") === true;
51495
+ }
51496
+ function needsHeal(launcher, toVersion) {
51497
+ const order = compareSemver(launcher.runtimeVersion, toVersion);
51498
+ return order === -1 || order === 0 && !launcher.runnable;
51499
+ }
51500
+ function startDriftHeal(options) {
51501
+ if ((options.platform ?? process.platform) !== "darwin") return false;
51502
+ const env4 = options.env ?? process.env;
51503
+ if (env4[DAEMON_DRIFT_HEAL_ENV] === "0") return false;
51504
+ const root = options.packageRoot === void 0 ? packageRoot() : options.packageRoot;
51505
+ if (!isInstalledPackage(root)) return false;
51506
+ const euid = options.euid ?? process.geteuid?.();
51507
+ if (euid === void 0 || euid === 0) return false;
51508
+ const paths = { env: env4, homeDir: options.homeDir };
51509
+ if (daemonExplicitlyDisabled(paths)) return false;
51510
+ const launcher = (options.selectedLauncher ?? (() => selectedDaemonLauncher(paths)))();
51511
+ if (!launcher || launcher.ownerUid !== euid) return false;
51512
+ const toVersion = options.cliVersion === void 0 ? packageVersion() : options.cliVersion;
51513
+ if (!(toVersion && needsHeal(launcher, toVersion))) return false;
51514
+ if (!apiUrlsMatch(
51515
+ getSiteUrlForEnvironment(launcher.apiUrl),
51516
+ getSiteUrlForEnvironment(env4.PRIM_API_URL)
51517
+ )) {
51518
+ return false;
51519
+ }
51520
+ const runsConfigRoot = options.launchAgentRunsConfigRoot ?? (() => launchAgentRunsConfigRoot(paths));
51521
+ if (!runsConfigRoot()) return false;
51522
+ const primEntry = options.primEntry === void 0 ? binFile("prim") : options.primEntry;
51523
+ if (!primEntry) return false;
51524
+ const attempt = {
51525
+ attemptedAt: (options.nowMs ?? Date.now)(),
51526
+ fromVersion: launcher.runtimeVersion,
51527
+ toVersion
51528
+ };
51529
+ const claimed = withFileLockSync(
51530
+ daemonDriftHealClaimPath(paths),
51531
+ () => claimAttempt(daemonDriftHealMarkerPath(paths), attempt),
51532
+ { timeoutMs: 0, sleep: claimHeld }
51533
+ );
51534
+ if (!claimed) return false;
51535
+ return kickDaemonEnsure({
51536
+ primEntry,
51537
+ nodeEntry: options.nodeEntry,
51538
+ spawnProcess: options.spawnProcess,
51539
+ latestBootstrap: false
51540
+ });
51541
+ }
51542
+ function healDaemonDrift(options = {}) {
51543
+ try {
51544
+ return startDriftHeal(options);
51545
+ } catch {
51546
+ return false;
51547
+ }
51548
+ }
51549
+
51550
+ // src/lib/background-flush.ts
51551
+ import { spawn as spawn5 } from "child_process";
51552
+ import { readFileSync as readFileSync23 } from "fs";
51553
+
51554
+ // src/daemon/health.ts
51555
+ import { chmodSync as chmodSync7, existsSync as existsSync24, mkdirSync as mkdirSync15, renameSync as renameSync7, writeFileSync as writeFileSync11 } from "fs";
51556
+ import { dirname as dirname19, join as join29 } from "path";
51557
+ var HEARTBEAT_FRESH_MS = 9e4;
51558
+ var DAEMON_HEALTH_PATH = join29(primConfigDirectory(), "daemon-health.json");
51559
+
51560
+ // src/lib/background-flush.ts
51561
+ function record2(value) {
51562
+ return typeof value === "object" && value !== null && !Array.isArray(value) ? value : void 0;
51563
+ }
51564
+ function daemonOwnsDrain(options = {}) {
51565
+ try {
51566
+ const expectedVersion = options.expectedVersion === void 0 ? packageVersion() : options.expectedVersion;
51567
+ if (!expectedVersion) return false;
51568
+ const health = record2(
51569
+ JSON.parse(readFileSync23(options.healthPath ?? DAEMON_HEALTH_PATH, "utf8"))
51570
+ );
51571
+ const heartbeat = record2(health?.heartbeat);
51572
+ const ingestion = record2(health?.ingestion);
51573
+ if (!(health && heartbeat && ingestion)) return false;
51574
+ if (health.schemaVersion !== 1 || health.version !== expectedVersion) return false;
51575
+ const daemonSiteUrl = health.siteUrl;
51576
+ if (typeof daemonSiteUrl !== "string" || envSlug(daemonSiteUrl) !== envSlug(options.siteUrl ?? getSiteUrl())) {
51577
+ return false;
51578
+ }
51579
+ if (health.needsReauth === true) return false;
51580
+ const pid = health.pid;
51581
+ if (typeof pid !== "number" || !(options.isAlive ?? processIsAlive)(pid)) return false;
51582
+ const lastSuccessAt = heartbeat.lastSuccessAt;
51583
+ const now = options.now ?? Date.now();
51584
+ return heartbeat.consecutiveFailures === 0 && typeof lastSuccessAt === "number" && lastSuccessAt <= now && now - lastSuccessAt < HEARTBEAT_FRESH_MS && ingestion.consecutiveFailures === 0;
51585
+ } catch {
51586
+ return false;
51587
+ }
51588
+ }
51589
+ function startBackgroundFlush(options = {}) {
51590
+ try {
51591
+ if ((options.daemonOwnsDrain ?? daemonOwnsDrain)()) return false;
51592
+ if (!(options.needsFlush ?? journalNeedsFlush)()) return false;
51593
+ const primEntry = options.primEntry === void 0 ? binFile("prim") : options.primEntry;
51594
+ if (!primEntry) return false;
51595
+ const child = (options.spawnProcess ?? spawn5)(
51596
+ options.nodeEntry ?? process.execPath,
51597
+ [primEntry, "moves", "flush"],
51598
+ // Ignored stdio matters as much as detaching: a child holding inherited
51599
+ // pipes would keep a caller that captures them (spawnSync) waiting.
51600
+ // windowsHide keeps the detached child from opening a console window.
51601
+ // The child is unattended even when its parent is not: `doctor` or
51602
+ // `statusline` must not reach the daemon drift heal through its drain.
51603
+ { detached: true, stdio: "ignore", windowsHide: true, env: unattendedEnv() }
51604
+ );
51605
+ child.once("error", () => {
51606
+ });
51607
+ child.unref();
51608
+ return true;
51609
+ } catch {
51610
+ return false;
51611
+ }
51612
+ }
51613
+
50928
51614
  // src/lib/startup-background.ts
50929
51615
  var ROOT_OPTIONS = /* @__PURE__ */ new Set(["-y", "--yes", "--non-interactive"]);
51616
+ var HELP_OPTIONS = /* @__PURE__ */ new Set(["-h", "--help"]);
51617
+ var DAEMON_HEAL_COMMANDS = /* @__PURE__ */ new Map([
51618
+ // Credentials; `auth login` is how a person returns to a daemon held for re-auth.
51619
+ ["auth", /* @__PURE__ */ new Set(["api-keys", "login", "set-token", "status"])],
51620
+ // Reading and curating the decision graph: the everyday attended surface.
51621
+ [
51622
+ "decisions",
51623
+ /* @__PURE__ */ new Set([
51624
+ "cascade",
51625
+ "check",
51626
+ "confirm",
51627
+ "create",
51628
+ "demote",
51629
+ "link",
51630
+ "promote",
51631
+ "publish",
51632
+ "ratify",
51633
+ "recent",
51634
+ "repairs",
51635
+ "rescope",
51636
+ "restore",
51637
+ "show",
51638
+ "supersede",
51639
+ "unlink",
51640
+ "withdraw"
51641
+ ])
51642
+ ],
51643
+ ["reconcile", "any"],
51644
+ // Repository connection, activation, and journal plumbing a person steers.
51645
+ // Hook-side `moves flush` drains are unattended and never reach this table.
51646
+ ["github", /* @__PURE__ */ new Set(["connect"])],
51647
+ ["enable", "any"],
51648
+ ["moves", /* @__PURE__ */ new Set(["bind", "drop", "flush", "status", "tail"])],
51649
+ ["session", /* @__PURE__ */ new Set(["drop", "list", "start"])],
51650
+ // Installing or inspecting an integration, never removing one.
51651
+ ["claude", /* @__PURE__ */ new Set(["install", "preauth", "status"])],
51652
+ ["codex", /* @__PURE__ */ new Set(["install", "status"])],
51653
+ ["cursor", /* @__PURE__ */ new Set(["install", "status"])],
51654
+ ["hermes", /* @__PURE__ */ new Set(["install", "status"])],
51655
+ ["hooks", /* @__PURE__ */ new Set(["install"])],
51656
+ ["skill", /* @__PURE__ */ new Set(["install", "status"])],
51657
+ ["welcome", "any"]
51658
+ ]);
50930
51659
  function commandArguments(argv) {
50931
51660
  let index = 0;
50932
51661
  while (ROOT_OPTIONS.has(argv[index] ?? "")) index += 1;
50933
51662
  return argv.slice(index);
50934
51663
  }
51664
+ function mayHealDaemon(argv, env4) {
51665
+ if (isUnattended(env4) || env4[SETUP_ORCHESTRATOR_ENV] === "1") return false;
51666
+ const args = commandArguments(argv);
51667
+ if (args.some((arg) => HELP_OPTIONS.has(arg))) return false;
51668
+ const [command, subcommand] = args;
51669
+ const allowed = command === void 0 ? void 0 : DAEMON_HEAL_COMMANDS.get(command);
51670
+ if (allowed === void 0) return false;
51671
+ return allowed === "any" || subcommand !== void 0 && allowed.has(subcommand);
51672
+ }
50935
51673
  function runStartupBackgroundWork(argv, env4, work) {
50936
- const [command, subcommand] = commandArguments(argv);
51674
+ const args = commandArguments(argv);
51675
+ const [command, subcommand] = args;
50937
51676
  const uninstall = command === "uninstall" || env4[UNINSTALL_ORCHESTRATOR_ENV] === "1";
50938
51677
  if (uninstall) return;
50939
51678
  work.notify();
50940
- if (command !== "moves" || subcommand !== "flush") work.flush().catch(() => {
50941
- });
51679
+ const setupDaemonDrains = command === "setup" && !args.includes("--no-daemon") || env4[SETUP_DAEMON_DRAINS_ENV] === "1";
51680
+ if (!setupDaemonDrains && (command !== "moves" || subcommand !== "flush")) work.flush();
51681
+ if (mayHealDaemon(argv, env4)) work.healDaemon();
50942
51682
  }
50943
51683
 
50944
51684
  // src/index.ts
50945
- var __dirname6 = dirname18(fileURLToPath7(import.meta.url));
50946
- var pkg = JSON.parse(readFileSync22(resolve10(__dirname6, "../package.json"), "utf-8"));
51685
+ var __dirname6 = dirname20(fileURLToPath7(import.meta.url));
51686
+ var pkg = JSON.parse(readFileSync24(resolve10(__dirname6, "../package.json"), "utf-8"));
50947
51687
  runStartupBackgroundWork(process.argv.slice(2), process.env, {
50948
51688
  notify: () => updateNotifier({ pkg }).notify(),
50949
- flush: () => flushIfNeeded()
51689
+ flush: () => startBackgroundFlush(),
51690
+ healDaemon: () => healDaemonDrift()
50950
51691
  });
50951
51692
  var program2 = new Command();
50952
51693
  program2.name("prim").description("CLI for Primitive's decision graph").version(pkg.version).option("-y, --yes", "auto-confirm prompts").option(