@rpamis/comet 0.4.0-beta.7 → 0.4.0-beta.9

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 (161) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +3 -3
  3. package/assets/manifest.json +1 -1
  4. package/assets/skills/comet/reference/comet-yaml-fields.md +2 -2
  5. package/assets/skills/comet/scripts/comet-entry-runtime.mjs +118 -6
  6. package/assets/skills/comet/scripts/comet-hook-router.mjs +244 -26
  7. package/assets/skills/comet/scripts/comet-runtime.mjs +221 -104
  8. package/assets/skills/comet-archive/SKILL.md +29 -20
  9. package/assets/skills/comet-native/SKILL.md +23 -15
  10. package/assets/skills/comet-native/reference/artifacts.md +19 -4
  11. package/assets/skills/comet-native/reference/commands.md +8 -2
  12. package/assets/skills/comet-native/reference/recovery.md +1 -1
  13. package/assets/skills/comet-native/scripts/comet-native-runtime.mjs +1136 -471
  14. package/assets/skills-zh/comet/reference/comet-yaml-fields.md +2 -2
  15. package/assets/skills-zh/comet-archive/SKILL.md +29 -20
  16. package/assets/skills-zh/comet-native/SKILL.md +23 -15
  17. package/assets/skills-zh/comet-native/reference/artifacts.md +20 -5
  18. package/assets/skills-zh/comet-native/reference/commands.md +8 -2
  19. package/assets/skills-zh/comet-native/reference/recovery.md +1 -1
  20. package/dist/app/commands/init.d.ts.map +1 -1
  21. package/dist/app/commands/init.js +3 -6
  22. package/dist/app/commands/init.js.map +1 -1
  23. package/dist/domains/bundle/bundle-platform.d.ts.map +1 -1
  24. package/dist/domains/bundle/bundle-platform.js +1 -1
  25. package/dist/domains/bundle/bundle-platform.js.map +1 -1
  26. package/dist/domains/comet-classic/classic-guard.d.ts.map +1 -1
  27. package/dist/domains/comet-classic/classic-guard.js +3 -2
  28. package/dist/domains/comet-classic/classic-guard.js.map +1 -1
  29. package/dist/domains/comet-entry/current-selection.d.ts.map +1 -1
  30. package/dist/domains/comet-entry/current-selection.js +6 -8
  31. package/dist/domains/comet-entry/current-selection.js.map +1 -1
  32. package/dist/domains/comet-entry/project-status.d.ts.map +1 -1
  33. package/dist/domains/comet-entry/project-status.js +5 -1
  34. package/dist/domains/comet-entry/project-status.js.map +1 -1
  35. package/dist/domains/comet-native/native-archive-transaction.d.ts.map +1 -1
  36. package/dist/domains/comet-native/native-archive-transaction.js +5 -6
  37. package/dist/domains/comet-native/native-archive-transaction.js.map +1 -1
  38. package/dist/domains/comet-native/native-atomic-file.d.ts.map +1 -1
  39. package/dist/domains/comet-native/native-atomic-file.js +11 -7
  40. package/dist/domains/comet-native/native-atomic-file.js.map +1 -1
  41. package/dist/domains/comet-native/native-bounded-file.d.ts.map +1 -1
  42. package/dist/domains/comet-native/native-bounded-file.js +11 -7
  43. package/dist/domains/comet-native/native-bounded-file.js.map +1 -1
  44. package/dist/domains/comet-native/native-build-evidence.js +2 -2
  45. package/dist/domains/comet-native/native-build-evidence.js.map +1 -1
  46. package/dist/domains/comet-native/native-change.d.ts +4 -2
  47. package/dist/domains/comet-native/native-change.d.ts.map +1 -1
  48. package/dist/domains/comet-native/native-change.js +18 -4
  49. package/dist/domains/comet-native/native-change.js.map +1 -1
  50. package/dist/domains/comet-native/native-check-receipt-storage.d.ts.map +1 -1
  51. package/dist/domains/comet-native/native-check-receipt-storage.js +11 -7
  52. package/dist/domains/comet-native/native-check-receipt-storage.js.map +1 -1
  53. package/dist/domains/comet-native/native-check-receipt.d.ts.map +1 -1
  54. package/dist/domains/comet-native/native-check-receipt.js +16 -11
  55. package/dist/domains/comet-native/native-check-receipt.js.map +1 -1
  56. package/dist/domains/comet-native/native-cli.d.ts.map +1 -1
  57. package/dist/domains/comet-native/native-cli.js +120 -16
  58. package/dist/domains/comet-native/native-cli.js.map +1 -1
  59. package/dist/domains/comet-native/native-config.d.ts +5 -1
  60. package/dist/domains/comet-native/native-config.d.ts.map +1 -1
  61. package/dist/domains/comet-native/native-config.js +78 -0
  62. package/dist/domains/comet-native/native-config.js.map +1 -1
  63. package/dist/domains/comet-native/native-continuation.d.ts +2 -1
  64. package/dist/domains/comet-native/native-continuation.d.ts.map +1 -1
  65. package/dist/domains/comet-native/native-continuation.js +13 -5
  66. package/dist/domains/comet-native/native-continuation.js.map +1 -1
  67. package/dist/domains/comet-native/native-diagnostics.d.ts +7 -3
  68. package/dist/domains/comet-native/native-diagnostics.d.ts.map +1 -1
  69. package/dist/domains/comet-native/native-diagnostics.js +19 -9
  70. package/dist/domains/comet-native/native-diagnostics.js.map +1 -1
  71. package/dist/domains/comet-native/native-evidence-retention.d.ts.map +1 -1
  72. package/dist/domains/comet-native/native-evidence-retention.js +5 -3
  73. package/dist/domains/comet-native/native-evidence-retention.js.map +1 -1
  74. package/dist/domains/comet-native/native-evidence-storage.d.ts.map +1 -1
  75. package/dist/domains/comet-native/native-evidence-storage.js +11 -7
  76. package/dist/domains/comet-native/native-evidence-storage.js.map +1 -1
  77. package/dist/domains/comet-native/native-file-identity.d.ts +3 -0
  78. package/dist/domains/comet-native/native-file-identity.d.ts.map +1 -0
  79. package/dist/domains/comet-native/native-file-identity.js +2 -0
  80. package/dist/domains/comet-native/native-file-identity.js.map +1 -0
  81. package/dist/domains/comet-native/native-findings.d.ts.map +1 -1
  82. package/dist/domains/comet-native/native-findings.js +19 -1
  83. package/dist/domains/comet-native/native-findings.js.map +1 -1
  84. package/dist/domains/comet-native/native-guards.d.ts +2 -1
  85. package/dist/domains/comet-native/native-guards.d.ts.map +1 -1
  86. package/dist/domains/comet-native/native-guards.js +14 -0
  87. package/dist/domains/comet-native/native-guards.js.map +1 -1
  88. package/dist/domains/comet-native/native-lock.d.ts.map +1 -1
  89. package/dist/domains/comet-native/native-lock.js +30 -35
  90. package/dist/domains/comet-native/native-lock.js.map +1 -1
  91. package/dist/domains/comet-native/native-protected-file.d.ts.map +1 -1
  92. package/dist/domains/comet-native/native-protected-file.js +9 -8
  93. package/dist/domains/comet-native/native-protected-file.js.map +1 -1
  94. package/dist/domains/comet-native/native-root-move.d.ts.map +1 -1
  95. package/dist/domains/comet-native/native-root-move.js +2 -0
  96. package/dist/domains/comet-native/native-root-move.js.map +1 -1
  97. package/dist/domains/comet-native/native-run-store.d.ts.map +1 -1
  98. package/dist/domains/comet-native/native-run-store.js +9 -8
  99. package/dist/domains/comet-native/native-run-store.js.map +1 -1
  100. package/dist/domains/comet-native/native-snapshot.d.ts +5 -1
  101. package/dist/domains/comet-native/native-snapshot.d.ts.map +1 -1
  102. package/dist/domains/comet-native/native-snapshot.js +225 -5
  103. package/dist/domains/comet-native/native-snapshot.js.map +1 -1
  104. package/dist/domains/comet-native/native-transitions.d.ts +2 -1
  105. package/dist/domains/comet-native/native-transitions.d.ts.map +1 -1
  106. package/dist/domains/comet-native/native-transitions.js +34 -11
  107. package/dist/domains/comet-native/native-transitions.js.map +1 -1
  108. package/dist/domains/comet-native/native-types.d.ts +16 -0
  109. package/dist/domains/comet-native/native-types.d.ts.map +1 -1
  110. package/dist/domains/comet-native/native-verification-runtime.d.ts.map +1 -1
  111. package/dist/domains/comet-native/native-verification-runtime.js +5 -3
  112. package/dist/domains/comet-native/native-verification-runtime.js.map +1 -1
  113. package/dist/domains/comet-native/native-verification-scope.d.ts +2 -1
  114. package/dist/domains/comet-native/native-verification-scope.d.ts.map +1 -1
  115. package/dist/domains/comet-native/native-verification-scope.js +6 -1
  116. package/dist/domains/comet-native/native-verification-scope.js.map +1 -1
  117. package/dist/domains/dashboard/native-collector.d.ts.map +1 -1
  118. package/dist/domains/dashboard/native-collector.js +4 -1
  119. package/dist/domains/dashboard/native-collector.js.map +1 -1
  120. package/dist/domains/dashboard/web/index.html +14 -14
  121. package/dist/domains/skill/platform-install.d.ts.map +1 -1
  122. package/dist/domains/skill/platform-install.js +9 -0
  123. package/dist/domains/skill/platform-install.js.map +1 -1
  124. package/dist/domains/skill/project-instructions.d.ts.map +1 -1
  125. package/dist/domains/skill/project-instructions.js +4 -2
  126. package/dist/domains/skill/project-instructions.js.map +1 -1
  127. package/dist/domains/workflow-contract/project-config.d.ts +1 -1
  128. package/dist/domains/workflow-contract/project-config.d.ts.map +1 -1
  129. package/dist/domains/workflow-contract/project-config.js +24 -2
  130. package/dist/domains/workflow-contract/project-config.js.map +1 -1
  131. package/dist/platform/fs/file-identity.d.ts +9 -0
  132. package/dist/platform/fs/file-identity.d.ts.map +1 -0
  133. package/dist/platform/fs/file-identity.js +21 -0
  134. package/dist/platform/fs/file-identity.js.map +1 -0
  135. package/dist/platform/fs/file-system.d.ts.map +1 -1
  136. package/dist/platform/fs/file-system.js +13 -9
  137. package/dist/platform/fs/file-system.js.map +1 -1
  138. package/dist/platform/fs/race-safe-read.d.ts +47 -0
  139. package/dist/platform/fs/race-safe-read.d.ts.map +1 -0
  140. package/dist/platform/fs/race-safe-read.js +132 -0
  141. package/dist/platform/fs/race-safe-read.js.map +1 -0
  142. package/eval/README.md +1 -1
  143. package/eval/local/tasks/comet-native-clarification-depth/environment/Dockerfile +27 -0
  144. package/eval/local/tasks/comet-native-clarification-depth/environment/test_wordcount.py +25 -0
  145. package/eval/local/tasks/comet-native-clarification-depth/environment/wordcount.py +36 -0
  146. package/eval/local/tasks/comet-native-clarification-depth/instruction.md +17 -0
  147. package/eval/local/tasks/comet-native-clarification-depth/task.toml +47 -0
  148. package/eval/local/tasks/comet-native-clarification-depth/validation/test_native_clarification_depth.py +618 -0
  149. package/eval/local/tasks/comet-native-clarification-modes/validation/test_native_clarification_modes.py +1 -1
  150. package/eval/local/tasks/index.yaml +6 -0
  151. package/eval/local/tests/conftest.py +146 -2
  152. package/eval/local/tests/scaffold/test_conftest_helpers.py +188 -1
  153. package/eval/local/tests/scaffold/test_tasks.py +440 -8
  154. package/eval/local/tests/scaffold/test_utils.py +114 -1
  155. package/eval/local/tests/tasks/test_tasks.py +2 -0
  156. package/eval/pyproject.toml +4 -4
  157. package/eval/scaffold/python/aligned_comparison.py +1 -0
  158. package/eval/scaffold/python/tasks.py +2 -0
  159. package/eval/scaffold/shell/docker.sh +1 -1
  160. package/eval/scaffold/shell/run-claude-loop.sh +25 -2
  161. package/package.json +8 -1
@@ -6915,14 +6915,14 @@ var require_parser = __commonJS({
6915
6915
  case "scalar":
6916
6916
  case "single-quoted-scalar":
6917
6917
  case "double-quoted-scalar": {
6918
- const fs33 = this.flowScalar(this.type);
6918
+ const fs34 = this.flowScalar(this.type);
6919
6919
  if (atNextItem || it.value) {
6920
- map.items.push({ start, key: fs33, sep: [] });
6920
+ map.items.push({ start, key: fs34, sep: [] });
6921
6921
  this.onKeyLine = true;
6922
6922
  } else if (it.sep) {
6923
- this.stack.push(fs33);
6923
+ this.stack.push(fs34);
6924
6924
  } else {
6925
- Object.assign(it, { key: fs33, sep: [] });
6925
+ Object.assign(it, { key: fs34, sep: [] });
6926
6926
  this.onKeyLine = true;
6927
6927
  }
6928
6928
  return;
@@ -7050,13 +7050,13 @@ var require_parser = __commonJS({
7050
7050
  case "scalar":
7051
7051
  case "single-quoted-scalar":
7052
7052
  case "double-quoted-scalar": {
7053
- const fs33 = this.flowScalar(this.type);
7053
+ const fs34 = this.flowScalar(this.type);
7054
7054
  if (!it || it.value)
7055
- fc.items.push({ start: [], key: fs33, sep: [] });
7055
+ fc.items.push({ start: [], key: fs34, sep: [] });
7056
7056
  else if (it.sep)
7057
- this.stack.push(fs33);
7057
+ this.stack.push(fs34);
7058
7058
  else
7059
- Object.assign(it, { key: fs33, sep: [] });
7059
+ Object.assign(it, { key: fs34, sep: [] });
7060
7060
  return;
7061
7061
  }
7062
7062
  case "flow-map-end":
@@ -7370,9 +7370,127 @@ import { pathToFileURL } from "url";
7370
7370
  // domains/comet-native/native-cli.ts
7371
7371
  import path47 from "path";
7372
7372
 
7373
+ // platform/fs/race-safe-read.ts
7374
+ import { constants as fsConstants, promises as fs } from "fs";
7375
+
7376
+ // platform/fs/file-identity.ts
7377
+ function hasPlatformIdentity(value) {
7378
+ return value !== 0 && value !== 0n && value !== "0";
7379
+ }
7380
+ function hasComparableFileObject(left, right) {
7381
+ return hasPlatformIdentity(left.dev) && hasPlatformIdentity(right.dev) && hasPlatformIdentity(left.ino) && hasPlatformIdentity(right.ino);
7382
+ }
7383
+ function sameFileObject(left, right) {
7384
+ const comparableDevice = hasPlatformIdentity(left.dev) && hasPlatformIdentity(right.dev);
7385
+ if (comparableDevice && left.dev !== right.dev) return false;
7386
+ const comparableInode = hasPlatformIdentity(left.ino) && hasPlatformIdentity(right.ino);
7387
+ if (comparableInode && left.ino !== right.ino) return false;
7388
+ if (comparableDevice && comparableInode) return true;
7389
+ return left.birthtime === right.birthtime;
7390
+ }
7391
+
7392
+ // platform/fs/race-safe-read.ts
7393
+ var RaceSafeReadError = class extends Error {
7394
+ reason;
7395
+ constructor(reason, message, options) {
7396
+ super(message, options);
7397
+ this.name = "RaceSafeReadError";
7398
+ this.reason = reason;
7399
+ }
7400
+ };
7401
+ function birthtimeOf(stat) {
7402
+ return "birthtimeNs" in stat && typeof stat.birthtimeNs === "bigint" ? stat.birthtimeNs : stat.birthtimeMs;
7403
+ }
7404
+ function ctimeOf(stat) {
7405
+ return "ctimeNs" in stat && typeof stat.ctimeNs === "bigint" ? stat.ctimeNs : stat.ctimeMs;
7406
+ }
7407
+ function identityOf(stat) {
7408
+ return { dev: stat.dev, ino: stat.ino, birthtime: birthtimeOf(stat) };
7409
+ }
7410
+ function sameStatIdentity(left, right) {
7411
+ const leftObject = identityOf(left);
7412
+ const rightObject = identityOf(right);
7413
+ if (hasComparableFileObject(leftObject, rightObject)) {
7414
+ return sameFileObject(leftObject, rightObject);
7415
+ }
7416
+ return sameFileObject(leftObject, rightObject) && birthtimeOf(left) === birthtimeOf(right) && ctimeOf(left) === ctimeOf(right) && left.size === right.size;
7417
+ }
7418
+ async function readFileRaceSafe(file, maxBytes, options = {}) {
7419
+ if (!Number.isSafeInteger(maxBytes) || maxBytes < 1) {
7420
+ throw new Error("race-safe read byte limit must be a positive integer");
7421
+ }
7422
+ const label = options.label ?? "file";
7423
+ const bigint = options.bigint === true;
7424
+ const before = await fs.lstat(file, { bigint });
7425
+ if (!before.isFile() || before.isSymbolicLink()) {
7426
+ throw new RaceSafeReadError("not-regular-file", `${label} must be a regular file`);
7427
+ }
7428
+ if (BigInt(before.size) > BigInt(maxBytes)) {
7429
+ throw new RaceSafeReadError("too-large", `${label} exceeds ${maxBytes} bytes`);
7430
+ }
7431
+ const beforeRealPath = await fs.realpath(file);
7432
+ await options.verify?.("pre-open", { realPath: beforeRealPath, identity: identityOf(before) });
7433
+ const flags = process.platform === "win32" ? fsConstants.O_RDONLY : fsConstants.O_RDONLY | fsConstants.O_NOFOLLOW | fsConstants.O_NONBLOCK;
7434
+ let handle;
7435
+ try {
7436
+ handle = await fs.open(file, flags);
7437
+ } catch (error) {
7438
+ if (error.code === "ELOOP") {
7439
+ throw new RaceSafeReadError("not-regular-file", `${label} must be a regular file`, {
7440
+ cause: error
7441
+ });
7442
+ }
7443
+ throw error;
7444
+ }
7445
+ try {
7446
+ const [opened, pathAfterOpen, realPathAfterOpen] = await Promise.all([
7447
+ handle.stat({ bigint }),
7448
+ fs.lstat(file, { bigint }),
7449
+ fs.realpath(file)
7450
+ ]);
7451
+ if (!opened.isFile() || !pathAfterOpen.isFile() || pathAfterOpen.isSymbolicLink() || realPathAfterOpen !== beforeRealPath || !sameStatIdentity(before, opened) || !sameStatIdentity(before, pathAfterOpen)) {
7452
+ throw new RaceSafeReadError("changed", `${label} changed while opening`);
7453
+ }
7454
+ await options.verify?.("post-open", {
7455
+ realPath: realPathAfterOpen,
7456
+ identity: identityOf(opened)
7457
+ });
7458
+ await options.hooks?.afterOpen?.();
7459
+ const chunks = [];
7460
+ let total = 0;
7461
+ const buffer = Buffer.allocUnsafe(Math.min(64 * 1024, maxBytes + 1));
7462
+ for (; ; ) {
7463
+ const remaining = maxBytes + 1 - total;
7464
+ const { bytesRead } = await handle.read(buffer, 0, Math.min(buffer.length, remaining), null);
7465
+ if (bytesRead === 0) break;
7466
+ total += bytesRead;
7467
+ if (total > maxBytes) {
7468
+ throw new RaceSafeReadError("too-large", `${label} exceeds ${maxBytes} bytes`);
7469
+ }
7470
+ chunks.push(Buffer.from(buffer.subarray(0, bytesRead)));
7471
+ }
7472
+ await options.hooks?.beforeFinalCheck?.();
7473
+ const [afterHandle, afterPath, afterRealPath] = await Promise.all([
7474
+ handle.stat({ bigint }),
7475
+ fs.lstat(file, { bigint }),
7476
+ fs.realpath(file)
7477
+ ]);
7478
+ if (!afterPath.isFile() || afterPath.isSymbolicLink() || afterRealPath !== beforeRealPath || !sameStatIdentity(before, afterHandle) || !sameStatIdentity(before, afterPath)) {
7479
+ throw new RaceSafeReadError("changed", `${label} changed while reading`);
7480
+ }
7481
+ await options.verify?.("post-read", {
7482
+ realPath: afterRealPath,
7483
+ identity: identityOf(afterHandle)
7484
+ });
7485
+ return { bytes: Buffer.concat(chunks, total), stat: afterHandle, realPath: afterRealPath };
7486
+ } finally {
7487
+ await handle.close();
7488
+ }
7489
+ }
7490
+
7373
7491
  // domains/comet-native/native-archive.ts
7374
7492
  import { randomUUID as randomUUID6 } from "crypto";
7375
- import { promises as fs22 } from "fs";
7493
+ import { promises as fs23 } from "fs";
7376
7494
  import path34 from "path";
7377
7495
  import { isDeepStrictEqual as isDeepStrictEqual2 } from "util";
7378
7496
 
@@ -7510,17 +7628,17 @@ function recordOutcomeWithResolver(pkg, state, outcome, resolver, context) {
7510
7628
  }
7511
7629
 
7512
7630
  // domains/comet-native/native-artifacts.ts
7513
- import { promises as fs13 } from "fs";
7631
+ import { promises as fs14 } from "fs";
7514
7632
  import path17 from "path";
7515
7633
 
7516
7634
  // domains/comet-native/native-change.ts
7517
7635
  var import_yaml3 = __toESM(require_dist(), 1);
7518
- import { promises as fs12 } from "fs";
7636
+ import { promises as fs13 } from "fs";
7519
7637
  import path16 from "path";
7520
7638
 
7521
7639
  // domains/comet-native/native-bounded-file.ts
7522
7640
  import { createHash as createHash2 } from "node:crypto";
7523
- import { promises as fs } from "node:fs";
7641
+ import { promises as fs2 } from "node:fs";
7524
7642
  import path2 from "node:path";
7525
7643
  import { TextDecoder } from "node:util";
7526
7644
 
@@ -7616,25 +7734,30 @@ function positiveLimit(value) {
7616
7734
  return value;
7617
7735
  }
7618
7736
  function sameDirectoryIdentity(identity, stat) {
7619
- if (identity.dev !== 0 || identity.ino !== 0 || stat.dev !== 0 || stat.ino !== 0) {
7620
- return identity.dev === stat.dev && identity.ino === stat.ino;
7621
- }
7622
- return identity.birthtimeMs === stat.birthtimeMs;
7737
+ return sameFileObject(
7738
+ { ...identity, birthtime: identity.birthtimeMs },
7739
+ {
7740
+ ...stat,
7741
+ birthtime: stat.birthtimeMs
7742
+ }
7743
+ );
7623
7744
  }
7624
7745
  function sameFileIdentity(left, right) {
7625
- if (left.dev !== 0 || left.ino !== 0 || right.dev !== 0 || right.ino !== 0) {
7626
- return left.dev === right.dev && left.ino === right.ino;
7746
+ const leftObject = { ...left, birthtime: left.birthtimeMs };
7747
+ const rightObject = { ...right, birthtime: right.birthtimeMs };
7748
+ if (hasComparableFileObject(leftObject, rightObject)) {
7749
+ return sameFileObject(leftObject, rightObject);
7627
7750
  }
7628
- return left.birthtimeMs === right.birthtimeMs && left.ctimeMs === right.ctimeMs && left.size === right.size;
7751
+ return sameFileObject(leftObject, rightObject) && left.birthtimeMs === right.birthtimeMs && left.ctimeMs === right.ctimeMs && left.size === right.size;
7629
7752
  }
7630
7753
  async function directoryIdentity(directory) {
7631
- const stat = await fs.lstat(directory);
7754
+ const stat = await fs2.lstat(directory);
7632
7755
  if (!stat.isDirectory() || stat.isSymbolicLink()) {
7633
7756
  throw new Error(`Native artifact parent must be a real directory: ${directory}`);
7634
7757
  }
7635
7758
  return {
7636
7759
  path: directory,
7637
- realPath: await fs.realpath(directory),
7760
+ realPath: await fs2.realpath(directory),
7638
7761
  dev: stat.dev,
7639
7762
  ino: stat.ino,
7640
7763
  birthtimeMs: stat.birthtimeMs
@@ -7660,8 +7783,8 @@ async function captureDirectoryChain(root, directory) {
7660
7783
  }
7661
7784
  async function verifyDirectoryChain(chain) {
7662
7785
  for (const identity of chain) {
7663
- const stat = await fs.lstat(identity.path);
7664
- if (!stat.isDirectory() || stat.isSymbolicLink() || !sameDirectoryIdentity(identity, stat) || await fs.realpath(identity.path) !== identity.realPath) {
7786
+ const stat = await fs2.lstat(identity.path);
7787
+ if (!stat.isDirectory() || stat.isSymbolicLink() || !sameDirectoryIdentity(identity, stat) || await fs2.realpath(identity.path) !== identity.realPath) {
7665
7788
  throw new Error(`Native artifact parent changed while reading: ${identity.path}`);
7666
7789
  }
7667
7790
  }
@@ -7672,21 +7795,21 @@ async function readNativeBoundedTextFile(options) {
7672
7795
  const file = path2.resolve(options.root, ...ref.split("/"));
7673
7796
  const chain = await captureDirectoryChain(options.root, path2.dirname(file));
7674
7797
  await options.hooks?.afterParentChainCaptured?.();
7675
- const before = await fs.lstat(file);
7798
+ const before = await fs2.lstat(file);
7676
7799
  if (!before.isFile() || before.isSymbolicLink()) {
7677
7800
  throw new Error(`Native artifact must be a regular file: ${ref}`);
7678
7801
  }
7679
7802
  if (before.size > maxBytes) throw new Error(`Native artifact exceeds ${maxBytes} bytes: ${ref}`);
7680
- const realPath = await fs.realpath(file);
7803
+ const realPath = await fs2.realpath(file);
7681
7804
  if (!isInside(chain[0].realPath, realPath)) {
7682
7805
  throw new Error(`Native artifact resolves outside its root: ${ref}`);
7683
7806
  }
7684
- const handle = await fs.open(file, "r");
7807
+ const handle = await fs2.open(file, "r");
7685
7808
  try {
7686
7809
  const [opened, afterOpenPath, afterOpenRealPath] = await Promise.all([
7687
7810
  handle.stat(),
7688
- fs.lstat(file),
7689
- fs.realpath(file)
7811
+ fs2.lstat(file),
7812
+ fs2.realpath(file)
7690
7813
  ]);
7691
7814
  await verifyDirectoryChain(chain);
7692
7815
  if (!opened.isFile() || !afterOpenPath.isFile() || afterOpenPath.isSymbolicLink() || afterOpenRealPath !== realPath || !sameFileIdentity(before, opened) || !sameFileIdentity(opened, afterOpenPath)) {
@@ -7707,8 +7830,8 @@ async function readNativeBoundedTextFile(options) {
7707
7830
  await options.hooks?.beforeFinalCheck?.();
7708
7831
  const [afterHandle, afterPath, afterRealPath] = await Promise.all([
7709
7832
  handle.stat(),
7710
- fs.lstat(file),
7711
- fs.realpath(file)
7833
+ fs2.lstat(file),
7834
+ fs2.realpath(file)
7712
7835
  ]);
7713
7836
  await verifyDirectoryChain(chain);
7714
7837
  if (!afterPath.isFile() || afterPath.isSymbolicLink() || afterRealPath !== realPath || !sameFileIdentity(opened, afterHandle) || !sameFileIdentity(opened, afterPath)) {
@@ -7734,32 +7857,37 @@ async function readNativeBoundedTextFile(options) {
7734
7857
 
7735
7858
  // domains/comet-native/native-atomic-file.ts
7736
7859
  import { randomUUID } from "crypto";
7737
- import { promises as fs2 } from "fs";
7860
+ import { promises as fs3 } from "fs";
7738
7861
  import path3 from "path";
7739
7862
  function isInside2(parent, target) {
7740
7863
  const relative = path3.relative(parent, target);
7741
7864
  return relative === "" || !path3.isAbsolute(relative) && relative !== ".." && !relative.startsWith(`..${path3.sep}`);
7742
7865
  }
7743
7866
  function sameDirectoryIdentity2(identity, stat) {
7744
- if (identity.dev !== 0 || identity.ino !== 0 || stat.dev !== 0 || stat.ino !== 0) {
7745
- return identity.dev === stat.dev && identity.ino === stat.ino;
7746
- }
7747
- return identity.birthtimeMs === stat.birthtimeMs;
7867
+ return sameFileObject(
7868
+ { ...identity, birthtime: identity.birthtimeMs },
7869
+ {
7870
+ ...stat,
7871
+ birthtime: stat.birthtimeMs
7872
+ }
7873
+ );
7748
7874
  }
7749
7875
  function sameFileIdentity2(left, right) {
7750
- if (left.dev !== 0 || left.ino !== 0 || right.dev !== 0 || right.ino !== 0) {
7751
- return left.dev === right.dev && left.ino === right.ino;
7876
+ const leftObject = { ...left, birthtime: left.birthtimeMs };
7877
+ const rightObject = { ...right, birthtime: right.birthtimeMs };
7878
+ if (hasComparableFileObject(leftObject, rightObject)) {
7879
+ return sameFileObject(leftObject, rightObject);
7752
7880
  }
7753
- return left.birthtimeMs === right.birthtimeMs && left.ctimeMs === right.ctimeMs && left.size === right.size;
7881
+ return sameFileObject(leftObject, rightObject) && left.birthtimeMs === right.birthtimeMs && left.ctimeMs === right.ctimeMs && left.size === right.size;
7754
7882
  }
7755
7883
  async function captureDirectoryIdentity(directory) {
7756
- const stat = await fs2.lstat(directory);
7884
+ const stat = await fs3.lstat(directory);
7757
7885
  if (!stat.isDirectory() || stat.isSymbolicLink()) {
7758
7886
  throw new Error(`Native atomic write parent must be a real directory: ${directory}`);
7759
7887
  }
7760
7888
  return {
7761
7889
  path: directory,
7762
- realPath: await fs2.realpath(directory),
7890
+ realPath: await fs3.realpath(directory),
7763
7891
  dev: stat.dev,
7764
7892
  ino: stat.ino,
7765
7893
  birthtimeMs: stat.birthtimeMs
@@ -7767,8 +7895,8 @@ async function captureDirectoryIdentity(directory) {
7767
7895
  }
7768
7896
  async function verifyDirectoryChain2(chain) {
7769
7897
  for (const identity of chain) {
7770
- const stat = await fs2.lstat(identity.path);
7771
- if (!stat.isDirectory() || stat.isSymbolicLink() || !sameDirectoryIdentity2(identity, stat) || await fs2.realpath(identity.path) !== identity.realPath) {
7898
+ const stat = await fs3.lstat(identity.path);
7899
+ if (!stat.isDirectory() || stat.isSymbolicLink() || !sameDirectoryIdentity2(identity, stat) || await fs3.realpath(identity.path) !== identity.realPath) {
7772
7900
  throw new Error(`Native atomic write parent changed before commit: ${identity.path}`);
7773
7901
  }
7774
7902
  }
@@ -7786,7 +7914,7 @@ async function prepareContainedDirectoryChain(root, directory) {
7786
7914
  await verifyDirectoryChain2(chain);
7787
7915
  cursor = path3.join(cursor, segment);
7788
7916
  try {
7789
- await fs2.mkdir(cursor);
7917
+ await fs3.mkdir(cursor);
7790
7918
  } catch (error) {
7791
7919
  if (error.code !== "EEXIST") throw error;
7792
7920
  }
@@ -7802,7 +7930,7 @@ async function prepareContainedDirectoryChain(root, directory) {
7802
7930
  async function syncDirectory(directory) {
7803
7931
  let handle;
7804
7932
  try {
7805
- handle = await fs2.open(directory, "r");
7933
+ handle = await fs3.open(directory, "r");
7806
7934
  await handle.sync();
7807
7935
  } catch (error) {
7808
7936
  const code = error.code;
@@ -7816,18 +7944,18 @@ async function syncDirectory(directory) {
7816
7944
  async function atomicWrite(file, content, options = {}) {
7817
7945
  const directory = path3.dirname(file);
7818
7946
  const directoryChain = options.containedRoot ? await prepareContainedDirectoryChain(options.containedRoot, directory) : null;
7819
- if (!directoryChain) await fs2.mkdir(directory, { recursive: true });
7947
+ if (!directoryChain) await fs3.mkdir(directory, { recursive: true });
7820
7948
  const temporary = path3.join(directory, `.${path3.basename(file)}.${randomUUID()}.tmp`);
7821
7949
  let handle;
7822
7950
  let temporaryIdentity;
7823
7951
  try {
7824
7952
  await options.beforeTemporaryOpen?.();
7825
- handle = await fs2.open(temporary, "wx");
7953
+ handle = await fs3.open(temporary, "wx");
7826
7954
  temporaryIdentity = await handle.stat();
7827
7955
  if (directoryChain) {
7828
7956
  const [temporaryPathStat, temporaryRealPath] = await Promise.all([
7829
- fs2.lstat(temporary),
7830
- fs2.realpath(temporary)
7957
+ fs3.lstat(temporary),
7958
+ fs3.realpath(temporary)
7831
7959
  ]);
7832
7960
  await verifyDirectoryChain2(directoryChain);
7833
7961
  if (!temporaryPathStat.isFile() || temporaryPathStat.isSymbolicLink() || !sameFileIdentity2(temporaryIdentity, temporaryPathStat) || !isInside2(directoryChain[0].realPath, temporaryRealPath)) {
@@ -7845,26 +7973,26 @@ async function atomicWrite(file, content, options = {}) {
7845
7973
  await options.beforeCommit?.();
7846
7974
  if (directoryChain) {
7847
7975
  await verifyDirectoryChain2(directoryChain);
7848
- const temporaryStat = await fs2.lstat(temporary);
7976
+ const temporaryStat = await fs3.lstat(temporary);
7849
7977
  if (!temporaryStat.isFile() || temporaryStat.isSymbolicLink() || !temporaryIdentity || !sameFileIdentity2(temporaryStat, temporaryIdentity)) {
7850
7978
  throw new Error("Native atomic write temporary file changed before commit");
7851
7979
  }
7852
7980
  }
7853
7981
  if (options.exclusive) {
7854
- await fs2.link(temporary, file);
7855
- await fs2.unlink(temporary);
7982
+ await fs3.link(temporary, file);
7983
+ await fs3.unlink(temporary);
7856
7984
  } else {
7857
- await fs2.rename(temporary, file);
7985
+ await fs3.rename(temporary, file);
7858
7986
  }
7859
7987
  await syncDirectory(directory);
7860
7988
  } catch (error) {
7861
7989
  await handle?.close();
7862
7990
  if (!directoryChain) {
7863
- await fs2.rm(temporary, { force: true });
7991
+ await fs3.rm(temporary, { force: true });
7864
7992
  } else {
7865
7993
  try {
7866
7994
  await verifyDirectoryChain2(directoryChain);
7867
- await fs2.rm(temporary, { force: true });
7995
+ await fs3.rm(temporary, { force: true });
7868
7996
  } catch {
7869
7997
  }
7870
7998
  }
@@ -7883,7 +8011,7 @@ async function atomicWriteJson(file, value, options = {}) {
7883
8011
 
7884
8012
  // domains/comet-native/native-config.ts
7885
8013
  var import_yaml2 = __toESM(require_dist(), 1);
7886
- import { promises as fs5 } from "fs";
8014
+ import { promises as fs6 } from "fs";
7887
8015
  import path6 from "path";
7888
8016
 
7889
8017
  // domains/workflow-contract/project-config.ts
@@ -7898,6 +8026,12 @@ var COMMENTS = {
7898
8026
  "native.artifact_root": "# Root directory where Native stores Comet specs, changes, and runtime data.",
7899
8027
  "native.language": "# Artifact language used by Native workflow documents.\n# language: en | zh-CN",
7900
8028
  "native.clarification_mode": "# Controls whether Native asks one clarification at a time or every currently answerable question in a round.\n# clarification_mode: sequential | batch",
8029
+ "native.snapshot": "# Controls the auditable project scope and bounded work used by Native content snapshots.",
8030
+ "native.snapshot.include": "# Selects the project-relative paths included in Native snapshots. Patterns use / and support *, **, and ?.",
8031
+ "native.snapshot.exclude": "# Removes paths from the included scope. Exclusions are bound into each new change baseline.",
8032
+ "native.snapshot.max_files": "# Bounds the number of files captured by one snapshot. Increase it for large monorepos.",
8033
+ "native.snapshot.max_total_bytes": "# Bounds the total file content hashed by one snapshot. Content is streamed and does not depend on Git hashes.",
8034
+ "native.snapshot.max_duration_ms": "# Bounds snapshot capture time in milliseconds. Increase it together with the byte budget on slower or larger repositories.",
7901
8035
  classic: "# Classic workflow settings. They do not change Native state or behavior.",
7902
8036
  "classic.language": "# Artifact language used by Classic workflow documents.\n# language: en | zh-CN",
7903
8037
  "classic.context_compression": "# Controls beta context compression for new Classic changes.\n# context_compression: off | beta",
@@ -7913,6 +8047,12 @@ var COMMENTS = {
7913
8047
  "native.artifact_root": "# Native 产物的存放根目录,包括规格、change 和运行时数据。",
7914
8048
  "native.language": "# Native 工作流文档使用的产物语言。\n# 可选值:en | zh-CN",
7915
8049
  "native.clarification_mode": "# Native 每轮询问一个问题,或一次提出当前所有可回答的问题。\n# 可选值:sequential | batch",
8050
+ "native.snapshot": "# Native 内容快照使用的可审计项目范围与有界工作预算。",
8051
+ "native.snapshot.include": "# Native 快照纳入的项目相对路径;模式使用 /,支持 *、** 和 ?。",
8052
+ "native.snapshot.exclude": "# 从纳入范围中排除路径;新 change 会把排除策略绑定到 baseline。",
8053
+ "native.snapshot.max_files": "# 单次快照最多捕获的文件数;大型 monorepo 可按需提高。",
8054
+ "native.snapshot.max_total_bytes": "# 单次快照最多哈希的文件内容总字节数;内容采用流式读取,不依赖 Git hash。",
8055
+ "native.snapshot.max_duration_ms": "# 单次快照的最长执行时间(毫秒);较慢或更大的仓库应与字节预算一并提高。",
7916
8056
  classic: "# Classic 工作流配置,不会改变 Native 的状态或行为。",
7917
8057
  "classic.language": "# Classic 工作流文档使用的产物语言。\n# 可选值:en | zh-CN",
7918
8058
  "classic.context_compression": "# 新建 Classic change 是否启用 beta 上下文压缩。\n# 可选值:off | beta",
@@ -7923,7 +8063,7 @@ var COMMENTS = {
7923
8063
  function projectConfigComment(key, language) {
7924
8064
  return COMMENTS[language][key];
7925
8065
  }
7926
- function commentKey(line, block) {
8066
+ function commentKey(line, block, nativeNested) {
7927
8067
  const match = /^(\s*)([a-z_]+):/u.exec(line);
7928
8068
  if (!match) return null;
7929
8069
  const indent = match[1].length;
@@ -7933,13 +8073,18 @@ function commentKey(line, block) {
7933
8073
  const blockKey = `${block}.${key}`;
7934
8074
  if (blockKey in COMMENTS.en) return blockKey;
7935
8075
  }
8076
+ if (indent === 4 && block === "native" && nativeNested === "snapshot") {
8077
+ const nestedKey = `native.snapshot.${key}`;
8078
+ if (nestedKey in COMMENTS.en) return nestedKey;
8079
+ }
7936
8080
  return null;
7937
8081
  }
7938
8082
  function renderStructuredProjectConfig(value, language) {
7939
8083
  const output = [];
7940
8084
  let block = null;
8085
+ let nativeNested = null;
7941
8086
  for (const line of (0, import_yaml.stringify)(value).trimEnd().split("\n")) {
7942
- const key = commentKey(line, block);
8087
+ const key = commentKey(line, block, nativeNested);
7943
8088
  if (key) {
7944
8089
  const indent = line.match(/^\s*/u)?.[0] ?? "";
7945
8090
  for (const comment of projectConfigComment(key, language).split("\n")) {
@@ -7951,6 +8096,9 @@ function renderStructuredProjectConfig(value, language) {
7951
8096
  if (line.startsWith("native:")) block = "native";
7952
8097
  else if (line.startsWith("classic:")) block = "classic";
7953
8098
  else block = null;
8099
+ nativeNested = null;
8100
+ } else if (/^ {2}[a-z_]+:/u.test(line) && block === "native") {
8101
+ nativeNested = line.startsWith(" snapshot:") ? "snapshot" : null;
7954
8102
  }
7955
8103
  }
7956
8104
  output.push("");
@@ -7959,7 +8107,7 @@ function renderStructuredProjectConfig(value, language) {
7959
8107
 
7960
8108
  // domains/comet-native/native-protected-file.ts
7961
8109
  import { createHash as createHash3 } from "node:crypto";
7962
- import { constants as fsConstants, promises as fs3 } from "node:fs";
8110
+ import { constants as fsConstants2, promises as fs4 } from "node:fs";
7963
8111
  import path4 from "node:path";
7964
8112
  import { TextDecoder as TextDecoder2 } from "node:util";
7965
8113
  function isInside3(parent, target) {
@@ -7973,10 +8121,13 @@ function positiveLimit2(value) {
7973
8121
  return value;
7974
8122
  }
7975
8123
  function sameDirectoryIdentity3(expected, actual) {
7976
- if (expected.dev !== 0 || expected.ino !== 0 || actual.dev !== 0 || actual.ino !== 0) {
7977
- return expected.dev === actual.dev && expected.ino === actual.ino;
7978
- }
7979
- return expected.birthtimeMs === actual.birthtimeMs;
8124
+ return sameFileObject(
8125
+ { ...expected, birthtime: expected.birthtimeMs },
8126
+ {
8127
+ ...actual,
8128
+ birthtime: actual.birthtimeMs
8129
+ }
8130
+ );
7980
8131
  }
7981
8132
  function asFileIdentity(stat) {
7982
8133
  return {
@@ -7989,17 +8140,22 @@ function asFileIdentity(stat) {
7989
8140
  };
7990
8141
  }
7991
8142
  function sameFileIdentity3(expected, actual) {
7992
- const sameObject = expected.dev !== 0 || expected.ino !== 0 || actual.dev !== 0 || actual.ino !== 0 ? expected.dev === actual.dev && expected.ino === actual.ino : expected.birthtimeMs === actual.birthtimeMs;
7993
- return sameObject && expected.birthtimeMs === actual.birthtimeMs && expected.ctimeMs === actual.ctimeMs && expected.mtimeMs === actual.mtimeMs && expected.size === actual.size;
8143
+ return sameFileObject(
8144
+ { ...expected, birthtime: expected.birthtimeMs },
8145
+ {
8146
+ ...actual,
8147
+ birthtime: actual.birthtimeMs
8148
+ }
8149
+ ) && expected.birthtimeMs === actual.birthtimeMs && expected.ctimeMs === actual.ctimeMs && expected.mtimeMs === actual.mtimeMs && expected.size === actual.size;
7994
8150
  }
7995
8151
  async function captureDirectoryIdentity2(directory, label) {
7996
- const stat = await fs3.lstat(directory);
8152
+ const stat = await fs4.lstat(directory);
7997
8153
  if (!stat.isDirectory() || stat.isSymbolicLink()) {
7998
8154
  throw new Error(`${label} parent must be a real directory: ${directory}`);
7999
8155
  }
8000
8156
  return {
8001
8157
  path: directory,
8002
- realPath: await fs3.realpath(directory),
8158
+ realPath: await fs4.realpath(directory),
8003
8159
  dev: stat.dev,
8004
8160
  ino: stat.ino,
8005
8161
  birthtimeMs: stat.birthtimeMs
@@ -8027,8 +8183,8 @@ async function captureDirectoryChain2(root, directory, label) {
8027
8183
  }
8028
8184
  async function verifyDirectoryChain3(chain, label) {
8029
8185
  for (const identity of chain) {
8030
- const stat = await fs3.lstat(identity.path);
8031
- if (!stat.isDirectory() || stat.isSymbolicLink() || !sameDirectoryIdentity3(identity, stat) || await fs3.realpath(identity.path) !== identity.realPath) {
8186
+ const stat = await fs4.lstat(identity.path);
8187
+ if (!stat.isDirectory() || stat.isSymbolicLink() || !sameDirectoryIdentity3(identity, stat) || await fs4.realpath(identity.path) !== identity.realPath) {
8032
8188
  throw new Error(`${label} parent changed during I/O: ${identity.path}`);
8033
8189
  }
8034
8190
  }
@@ -8058,27 +8214,27 @@ async function readNativeProtectedFile(options) {
8058
8214
  );
8059
8215
  await options.hooks?.afterParentChainCaptured?.();
8060
8216
  await verifyDirectoryChain3(chain, options.label);
8061
- const before = await fs3.lstat(file);
8217
+ const before = await fs4.lstat(file);
8062
8218
  if (!before.isFile() || before.isSymbolicLink()) {
8063
8219
  throw new Error(`${options.label} must be a regular file`);
8064
8220
  }
8065
8221
  if (before.size > maxBytes) throw new Error(`${options.label} exceeds ${maxBytes} bytes`);
8066
8222
  const beforeIdentity = asFileIdentity(before);
8067
- const beforeRealPath = await fs3.realpath(file);
8223
+ const beforeRealPath = await fs4.realpath(file);
8068
8224
  if (!isInside3(chain[0].realPath, beforeRealPath)) {
8069
8225
  throw new Error(`${options.label} resolves outside its managed root`);
8070
8226
  }
8071
8227
  if (forbidden.some((identity) => isInside3(identity.realPath, beforeRealPath))) {
8072
8228
  throw new Error(`${options.label} resolves inside an excluded root`);
8073
8229
  }
8074
- const flags = process.platform === "win32" ? fsConstants.O_RDONLY : fsConstants.O_RDONLY | fsConstants.O_NOFOLLOW | fsConstants.O_NONBLOCK;
8075
- const handle = await fs3.open(file, flags);
8230
+ const flags = process.platform === "win32" ? fsConstants2.O_RDONLY : fsConstants2.O_RDONLY | fsConstants2.O_NOFOLLOW | fsConstants2.O_NONBLOCK;
8231
+ const handle = await fs4.open(file, flags);
8076
8232
  try {
8077
8233
  const opened = await handle.stat();
8078
8234
  await options.hooks?.afterOpen?.();
8079
8235
  const [pathAfterOpen, realPathAfterOpen] = await Promise.all([
8080
- fs3.lstat(file),
8081
- fs3.realpath(file)
8236
+ fs4.lstat(file),
8237
+ fs4.realpath(file)
8082
8238
  ]);
8083
8239
  await verifyDirectoryChain3(chain, options.label);
8084
8240
  await verifyDirectoryChain3(forbidden, options.label);
@@ -8090,8 +8246,8 @@ async function readNativeProtectedFile(options) {
8090
8246
  await options.hooks?.beforeFinalCheck?.();
8091
8247
  const [afterHandle, afterPath, afterRealPath] = await Promise.all([
8092
8248
  handle.stat(),
8093
- fs3.lstat(file),
8094
- fs3.realpath(file)
8249
+ fs4.lstat(file),
8250
+ fs4.realpath(file)
8095
8251
  ]);
8096
8252
  await verifyDirectoryChain3(chain, options.label);
8097
8253
  await verifyDirectoryChain3(forbidden, options.label);
@@ -8121,11 +8277,11 @@ async function readNativeProtectedDirectory(options) {
8121
8277
  const chain = await captureDirectoryChain2(options.root, options.directory, options.label);
8122
8278
  let entries;
8123
8279
  if (options.maxEntries === void 0) {
8124
- entries = await fs3.readdir(options.directory, { withFileTypes: true });
8280
+ entries = await fs4.readdir(options.directory, { withFileTypes: true });
8125
8281
  } else {
8126
8282
  const maxEntries = positiveLimit2(options.maxEntries);
8127
8283
  entries = [];
8128
- const directory = await fs3.opendir(options.directory);
8284
+ const directory = await fs4.opendir(options.directory);
8129
8285
  try {
8130
8286
  for await (const entry2 of directory) {
8131
8287
  entries.push(entry2);
@@ -8166,25 +8322,25 @@ async function quarantineNativeProtectedDirectoryInternal(options) {
8166
8322
  }
8167
8323
  await options.beforeQuarantine?.();
8168
8324
  await verifyDirectoryChain3(parentChain, options.label);
8169
- const current = await fs3.lstat(directory);
8170
- if (!current.isDirectory() || current.isSymbolicLink() || !sameDirectoryIdentity3(identity, current) || await fs3.realpath(directory) !== identity.realPath) {
8325
+ const current = await fs4.lstat(directory);
8326
+ if (!current.isDirectory() || current.isSymbolicLink() || !sameDirectoryIdentity3(identity, current) || await fs4.realpath(directory) !== identity.realPath) {
8171
8327
  throw new Error(`${options.label} changed before quarantine`);
8172
8328
  }
8173
8329
  try {
8174
- await fs3.lstat(quarantine);
8330
+ await fs4.lstat(quarantine);
8175
8331
  throw new Error(`${options.label} quarantine path is occupied`);
8176
8332
  } catch (error) {
8177
8333
  if (error.code !== "ENOENT") throw error;
8178
8334
  }
8179
- await fs3.rename(directory, quarantine);
8335
+ await fs4.rename(directory, quarantine);
8180
8336
  await verifyDirectoryChain3(parentChain, options.label);
8181
- const quarantined = await fs3.lstat(quarantine);
8337
+ const quarantined = await fs4.lstat(quarantine);
8182
8338
  if (!quarantined.isDirectory() || quarantined.isSymbolicLink() || !sameDirectoryIdentity3(identity, quarantined)) {
8183
8339
  throw new Error(`${options.label} changed while quarantining`);
8184
8340
  }
8185
8341
  await options.afterQuarantine?.(quarantine);
8186
8342
  try {
8187
- await fs3.lstat(directory);
8343
+ await fs4.lstat(directory);
8188
8344
  throw new Error(`${options.label} path was recreated while quarantining`);
8189
8345
  } catch (error) {
8190
8346
  if (error.code !== "ENOENT") throw error;
@@ -8197,12 +8353,12 @@ async function quarantineNativeProtectedDirectory(options) {
8197
8353
  async function removeNativeProtectedFile(options) {
8198
8354
  const file = path4.resolve(options.file);
8199
8355
  const parentChain = await captureDirectoryChain2(options.root, path4.dirname(file), options.label);
8200
- const before = await fs3.lstat(file);
8356
+ const before = await fs4.lstat(file);
8201
8357
  if (!before.isFile() || before.isSymbolicLink()) {
8202
8358
  throw new Error(`${options.label} must be a regular file`);
8203
8359
  }
8204
8360
  const identity = asFileIdentity(before);
8205
- const realPath = await fs3.realpath(file);
8361
+ const realPath = await fs4.realpath(file);
8206
8362
  if (!isInside3(parentChain[0].realPath, realPath)) {
8207
8363
  throw new Error(`${options.label} resolves outside its managed root`);
8208
8364
  }
@@ -8215,17 +8371,17 @@ async function removeNativeProtectedFile(options) {
8215
8371
  if (snapshot2.hash !== options.expectedHash || snapshot2.size !== options.expectedSize) {
8216
8372
  throw new Error(`${options.label} changed before removal`);
8217
8373
  }
8218
- const [afterRead, afterReadRealPath] = await Promise.all([fs3.lstat(file), fs3.realpath(file)]);
8374
+ const [afterRead, afterReadRealPath] = await Promise.all([fs4.lstat(file), fs4.realpath(file)]);
8219
8375
  if (!afterRead.isFile() || afterRead.isSymbolicLink() || !sameFileIdentity3(identity, afterRead) || afterReadRealPath !== realPath) {
8220
8376
  throw new Error(`${options.label} changed while verifying removal`);
8221
8377
  }
8222
8378
  await options.beforeRemove?.();
8223
8379
  await verifyDirectoryChain3(parentChain, options.label);
8224
- const [current, currentRealPath] = await Promise.all([fs3.lstat(file), fs3.realpath(file)]);
8380
+ const [current, currentRealPath] = await Promise.all([fs4.lstat(file), fs4.realpath(file)]);
8225
8381
  if (!current.isFile() || current.isSymbolicLink() || !sameFileIdentity3(identity, current) || currentRealPath !== realPath) {
8226
8382
  throw new Error(`${options.label} changed before removal`);
8227
8383
  }
8228
- await fs3.rm(file);
8384
+ await fs4.rm(file);
8229
8385
  await verifyDirectoryChain3(parentChain, options.label);
8230
8386
  }
8231
8387
  async function removeNativeProtectedEmptyDirectory(options) {
@@ -8241,11 +8397,11 @@ async function removeNativeProtectedEmptyDirectory(options) {
8241
8397
  }
8242
8398
  await options.beforeRemove?.();
8243
8399
  await verifyDirectoryChain3(parentChain, options.label);
8244
- const current = await fs3.lstat(directory);
8245
- if (!current.isDirectory() || current.isSymbolicLink() || !sameDirectoryIdentity3(identity, current) || await fs3.realpath(directory) !== identity.realPath) {
8400
+ const current = await fs4.lstat(directory);
8401
+ if (!current.isDirectory() || current.isSymbolicLink() || !sameDirectoryIdentity3(identity, current) || await fs4.realpath(directory) !== identity.realPath) {
8246
8402
  throw new Error(`${options.label} changed before removal`);
8247
8403
  }
8248
- await fs3.rmdir(directory);
8404
+ await fs4.rmdir(directory);
8249
8405
  await verifyDirectoryChain3(parentChain, options.label);
8250
8406
  }
8251
8407
  async function ensureNativeProtectedDirectory(options) {
@@ -8260,7 +8416,7 @@ async function ensureNativeProtectedDirectory(options) {
8260
8416
  await verifyDirectoryChain3(chain, options.label);
8261
8417
  cursor = path4.join(cursor, segment);
8262
8418
  try {
8263
- await fs3.mkdir(cursor);
8419
+ await fs4.mkdir(cursor);
8264
8420
  } catch (error) {
8265
8421
  if (error.code !== "EEXIST") throw error;
8266
8422
  }
@@ -8291,7 +8447,7 @@ async function moveNativeProtectedDirectory(options) {
8291
8447
  throw new Error(`${options.label} source resolves outside its managed root`);
8292
8448
  }
8293
8449
  try {
8294
- await fs3.lstat(target);
8450
+ await fs4.lstat(target);
8295
8451
  throw new Error(`${options.label} target already exists`);
8296
8452
  } catch (error) {
8297
8453
  if (error.code !== "ENOENT") throw error;
@@ -8301,21 +8457,21 @@ async function moveNativeProtectedDirectory(options) {
8301
8457
  verifyDirectoryChain3(sourceParentChain, options.label),
8302
8458
  verifyDirectoryChain3(targetParentChain, options.label)
8303
8459
  ]);
8304
- const current = await fs3.lstat(source);
8305
- if (!current.isDirectory() || current.isSymbolicLink() || !sameDirectoryIdentity3(sourceIdentity, current) || await fs3.realpath(source) !== sourceIdentity.realPath) {
8460
+ const current = await fs4.lstat(source);
8461
+ if (!current.isDirectory() || current.isSymbolicLink() || !sameDirectoryIdentity3(sourceIdentity, current) || await fs4.realpath(source) !== sourceIdentity.realPath) {
8306
8462
  throw new Error(`${options.label} source changed before move`);
8307
8463
  }
8308
- await fs3.rename(source, target);
8464
+ await fs4.rename(source, target);
8309
8465
  await Promise.all([
8310
8466
  verifyDirectoryChain3(sourceParentChain, options.label),
8311
8467
  verifyDirectoryChain3(targetParentChain, options.label)
8312
8468
  ]);
8313
- const moved = await fs3.lstat(target);
8314
- if (!moved.isDirectory() || moved.isSymbolicLink() || !sameDirectoryIdentity3(sourceIdentity, moved) || !isInside3(sourceParentChain[0].realPath, await fs3.realpath(target))) {
8469
+ const moved = await fs4.lstat(target);
8470
+ if (!moved.isDirectory() || moved.isSymbolicLink() || !sameDirectoryIdentity3(sourceIdentity, moved) || !isInside3(sourceParentChain[0].realPath, await fs4.realpath(target))) {
8315
8471
  throw new Error(`${options.label} source identity changed while moving`);
8316
8472
  }
8317
8473
  try {
8318
- await fs3.lstat(source);
8474
+ await fs4.lstat(source);
8319
8475
  throw new Error(`${options.label} source was recreated while moving`);
8320
8476
  } catch (error) {
8321
8477
  if (error.code !== "ENOENT") throw error;
@@ -8341,7 +8497,7 @@ async function copyNativeProtectedFile(options) {
8341
8497
  if (options.expectedTargetHash === void 0) return;
8342
8498
  if (options.expectedTargetHash === null) {
8343
8499
  try {
8344
- await fs3.lstat(options.target);
8500
+ await fs4.lstat(options.target);
8345
8501
  } catch (error) {
8346
8502
  if (error.code === "ENOENT") return;
8347
8503
  throw error;
@@ -8372,13 +8528,13 @@ async function copyNativeProtectedFile(options) {
8372
8528
  }
8373
8529
 
8374
8530
  // domains/comet-native/native-paths.ts
8375
- import { promises as fs4 } from "fs";
8531
+ import { promises as fs5 } from "fs";
8376
8532
  import path5 from "path";
8377
8533
  import os from "os";
8378
8534
  var PROJECT_CONFIG_FILE = ".comet/config.yaml";
8379
8535
  async function isFileOrDirectory(target) {
8380
8536
  try {
8381
- await fs4.lstat(target);
8537
+ await fs5.lstat(target);
8382
8538
  return true;
8383
8539
  } catch (error) {
8384
8540
  if (error.code === "ENOENT") return false;
@@ -8387,7 +8543,7 @@ async function isFileOrDirectory(target) {
8387
8543
  }
8388
8544
  async function declaresNativeProjectConfig(target) {
8389
8545
  try {
8390
- const source = await fs4.readFile(target, "utf8");
8546
+ const source = await fs5.readFile(target, "utf8");
8391
8547
  return /^schema:\s*comet\.project\.v1\s*$/mu.test(source);
8392
8548
  } catch (error) {
8393
8549
  if (error.code === "ENOENT") return false;
@@ -8407,12 +8563,12 @@ async function physicalPath(target) {
8407
8563
  missing.push(path5.basename(cursor));
8408
8564
  cursor = parent;
8409
8565
  }
8410
- const existing = await fs4.realpath(cursor);
8566
+ const existing = await fs5.realpath(cursor);
8411
8567
  return path5.resolve(existing, ...missing.reverse());
8412
8568
  }
8413
8569
  async function isSymbolicLink(target) {
8414
8570
  try {
8415
- return (await fs4.lstat(target)).isSymbolicLink();
8571
+ return (await fs5.lstat(target)).isSymbolicLink();
8416
8572
  } catch (error) {
8417
8573
  if (error.code === "ENOENT") return false;
8418
8574
  throw error;
@@ -8421,7 +8577,7 @@ async function isSymbolicLink(target) {
8421
8577
  async function discoverNativeProject(startPath) {
8422
8578
  let cursor = path5.resolve(startPath);
8423
8579
  try {
8424
- if (!(await fs4.stat(cursor)).isDirectory()) cursor = path5.dirname(cursor);
8580
+ if (!(await fs5.stat(cursor)).isDirectory()) cursor = path5.dirname(cursor);
8425
8581
  } catch (error) {
8426
8582
  if (error.code !== "ENOENT") throw error;
8427
8583
  }
@@ -8460,7 +8616,7 @@ function normalizeArtifactRootRef(value) {
8460
8616
  async function resolveArtifactRoot(projectRoot, value) {
8461
8617
  const normalized = normalizeArtifactRootRef(value);
8462
8618
  const lexical = path5.resolve(projectRoot, ...normalized.split("/"));
8463
- const physicalProject = await fs4.realpath(projectRoot);
8619
+ const physicalProject = await fs5.realpath(projectRoot);
8464
8620
  const physicalTarget = await physicalPath(lexical);
8465
8621
  if (!inside(physicalProject, physicalTarget)) {
8466
8622
  throw new Error("native.artifact_root resolves outside the project root");
@@ -8506,7 +8662,7 @@ async function ensureNativeDirectories(paths) {
8506
8662
  await Promise.all(
8507
8663
  directories.map(async (directory) => {
8508
8664
  await resolveContainedNativePath(paths.nativeRoot, directory);
8509
- await fs4.mkdir(directory, { recursive: true });
8665
+ await fs5.mkdir(directory, { recursive: true });
8510
8666
  })
8511
8667
  );
8512
8668
  }
@@ -8537,11 +8693,100 @@ var NATIVE_KEYS = /* @__PURE__ */ new Set([
8537
8693
  "artifact_root",
8538
8694
  "language",
8539
8695
  "clarification_mode",
8696
+ "snapshot",
8540
8697
  "pending_root_move"
8541
8698
  ]);
8699
+ var SNAPSHOT_KEYS = /* @__PURE__ */ new Set([
8700
+ "include",
8701
+ "exclude",
8702
+ "max_files",
8703
+ "max_total_bytes",
8704
+ "max_duration_ms"
8705
+ ]);
8542
8706
  var PENDING_KEYS = /* @__PURE__ */ new Set(["id", "from_artifact_root", "to_artifact_root", "stage", "cleanup"]);
8543
8707
  var NATIVE_PROJECT_CONFIG_MAX_BYTES = 64 * 1024;
8544
8708
  var CLEANUP_KEYS = /* @__PURE__ */ new Set(["kind", "state", "manifest_hash"]);
8709
+ var MAX_NATIVE_SNAPSHOT_PATTERN_LENGTH = 1024;
8710
+ var MAX_NATIVE_SNAPSHOT_PATTERN_WILDCARDS = 64;
8711
+ var DEFAULT_NATIVE_SNAPSHOT_CONFIG = {
8712
+ include: ["**/*"],
8713
+ exclude: [],
8714
+ max_files: 1e4,
8715
+ max_total_bytes: 256 * 1024 * 1024,
8716
+ max_duration_ms: 6e4
8717
+ };
8718
+ function normalizeNativeSnapshotPattern(value, label) {
8719
+ if (typeof value !== "string" || value.length === 0 || value.includes("\\") || value.includes("\0") || value.startsWith("/") || value.split("/").includes("..")) {
8720
+ throw new Error(`${label} contains an unsafe pattern`);
8721
+ }
8722
+ if (value.length > MAX_NATIVE_SNAPSHOT_PATTERN_LENGTH) {
8723
+ throw new Error(`${label} exceeds ${MAX_NATIVE_SNAPSHOT_PATTERN_LENGTH} characters`);
8724
+ }
8725
+ let wildcardTokens = 0;
8726
+ for (let index = 0; index < value.length; index += 1) {
8727
+ if (value[index] === "?") {
8728
+ wildcardTokens += 1;
8729
+ } else if (value[index] === "*") {
8730
+ wildcardTokens += 1;
8731
+ if (value[index + 1] === "*") index += 1;
8732
+ }
8733
+ }
8734
+ if (wildcardTokens > MAX_NATIVE_SNAPSHOT_PATTERN_WILDCARDS) {
8735
+ throw new Error(
8736
+ `${label} contains more than ${MAX_NATIVE_SNAPSHOT_PATTERN_WILDCARDS} wildcard tokens`
8737
+ );
8738
+ }
8739
+ return value;
8740
+ }
8741
+ function snapshotPatterns(value, label, fallback) {
8742
+ if (value === void 0) return [...fallback];
8743
+ if (!Array.isArray(value)) {
8744
+ throw new Error(`${label} contains an unsafe pattern`);
8745
+ }
8746
+ return [...new Set(value.map((pattern) => normalizeNativeSnapshotPattern(pattern, label)))].sort(
8747
+ (left, right) => left.localeCompare(right, "en")
8748
+ );
8749
+ }
8750
+ function positiveSnapshotInteger(value, fallback, label) {
8751
+ const resolved = value ?? fallback;
8752
+ if (!Number.isSafeInteger(resolved) || resolved < 1) {
8753
+ throw new Error(`${label} must be a positive integer`);
8754
+ }
8755
+ return resolved;
8756
+ }
8757
+ function parseSnapshot(value) {
8758
+ if (value === void 0)
8759
+ return { ...DEFAULT_NATIVE_SNAPSHOT_CONFIG, include: ["**/*"], exclude: [] };
8760
+ const snapshot2 = record(value, "native.snapshot");
8761
+ rejectUnknown(snapshot2, SNAPSHOT_KEYS, "native.snapshot");
8762
+ return {
8763
+ include: snapshotPatterns(
8764
+ snapshot2.include,
8765
+ "native.snapshot.include",
8766
+ DEFAULT_NATIVE_SNAPSHOT_CONFIG.include
8767
+ ),
8768
+ exclude: snapshotPatterns(
8769
+ snapshot2.exclude,
8770
+ "native.snapshot.exclude",
8771
+ DEFAULT_NATIVE_SNAPSHOT_CONFIG.exclude
8772
+ ),
8773
+ max_files: positiveSnapshotInteger(
8774
+ snapshot2.max_files,
8775
+ DEFAULT_NATIVE_SNAPSHOT_CONFIG.max_files,
8776
+ "native.snapshot.max_files"
8777
+ ),
8778
+ max_total_bytes: positiveSnapshotInteger(
8779
+ snapshot2.max_total_bytes,
8780
+ DEFAULT_NATIVE_SNAPSHOT_CONFIG.max_total_bytes,
8781
+ "native.snapshot.max_total_bytes"
8782
+ ),
8783
+ max_duration_ms: positiveSnapshotInteger(
8784
+ snapshot2.max_duration_ms,
8785
+ DEFAULT_NATIVE_SNAPSHOT_CONFIG.max_duration_ms,
8786
+ "native.snapshot.max_duration_ms"
8787
+ )
8788
+ };
8789
+ }
8545
8790
  function record(value, label) {
8546
8791
  if (!value || typeof value !== "object" || Array.isArray(value)) {
8547
8792
  throw new Error(`${label} must be a mapping`);
@@ -8627,6 +8872,7 @@ function parseConfig(value) {
8627
8872
  throw new Error("native.clarification_mode must be sequential or batch");
8628
8873
  }
8629
8874
  const pending = parsePending(native.pending_root_move);
8875
+ const snapshot2 = parseSnapshot(native.snapshot);
8630
8876
  return {
8631
8877
  schema: "comet.project.v1",
8632
8878
  default_workflow: root.default_workflow,
@@ -8636,6 +8882,7 @@ function parseConfig(value) {
8636
8882
  artifact_root: normalizeArtifactRootRef(native.artifact_root),
8637
8883
  language,
8638
8884
  clarification_mode: clarificationMode,
8885
+ snapshot: snapshot2,
8639
8886
  ...pending ? { pending_root_move: pending } : {}
8640
8887
  }
8641
8888
  };
@@ -8648,7 +8895,8 @@ function defaultProjectConfig(artifactRoot = "docs", language = "en") {
8648
8895
  native: {
8649
8896
  artifact_root: normalizeArtifactRootRef(artifactRoot),
8650
8897
  language,
8651
- clarification_mode: "sequential"
8898
+ clarification_mode: "sequential",
8899
+ snapshot: { ...DEFAULT_NATIVE_SNAPSHOT_CONFIG, include: ["**/*"], exclude: [] }
8652
8900
  }
8653
8901
  };
8654
8902
  }
@@ -8656,7 +8904,7 @@ async function readProjectConfig(projectRoot) {
8656
8904
  const canonical = path6.join(projectRoot, ...PROJECT_CONFIG_FILE.split("/"));
8657
8905
  const file = canonical;
8658
8906
  try {
8659
- await fs5.lstat(file);
8907
+ await fs6.lstat(file);
8660
8908
  } catch (error) {
8661
8909
  if (error.code === "ENOENT") return null;
8662
8910
  throw error;
@@ -8717,6 +8965,7 @@ async function writeProjectConfig(projectRoot, config) {
8717
8965
  artifact_root: config.native.artifact_root,
8718
8966
  language: config.native.language,
8719
8967
  clarification_mode: config.native.clarification_mode,
8968
+ snapshot: config.native.snapshot,
8720
8969
  ...config.native.pending_root_move ? {
8721
8970
  pending_root_move: {
8722
8971
  id: config.native.pending_root_move.id,
@@ -8744,6 +8993,7 @@ async function writeProjectConfig(projectRoot, config) {
8744
8993
  artifact_root: validated.native.artifact_root,
8745
8994
  language: validated.native.language,
8746
8995
  clarification_mode: validated.native.clarification_mode,
8996
+ snapshot: validated.native.snapshot,
8747
8997
  ...validated.native.pending_root_move ? {
8748
8998
  pending_root_move: {
8749
8999
  id: validated.native.pending_root_move.id,
@@ -8762,7 +9012,7 @@ async function writeProjectConfig(projectRoot, config) {
8762
9012
  }
8763
9013
  };
8764
9014
  const canonical = path6.join(projectRoot, ...PROJECT_CONFIG_FILE.split("/"));
8765
- await fs5.mkdir(path6.dirname(canonical), { recursive: true });
9015
+ await fs6.mkdir(path6.dirname(canonical), { recursive: true });
8766
9016
  await atomicWriteText(
8767
9017
  canonical,
8768
9018
  renderStructuredProjectConfig(document, validated.native.language === "zh-CN" ? "zh-CN" : "en")
@@ -8791,13 +9041,13 @@ async function resolveNativeProject(options) {
8791
9041
  }
8792
9042
 
8793
9043
  // domains/comet-native/native-mutation-lock.ts
8794
- import { promises as fs8 } from "fs";
9044
+ import { promises as fs9 } from "fs";
8795
9045
  import path9 from "path";
8796
9046
 
8797
9047
  // domains/comet-native/native-lock.ts
8798
9048
  import { AsyncLocalStorage } from "async_hooks";
8799
9049
  import { randomUUID as randomUUID2 } from "crypto";
8800
- import { promises as fs6 } from "fs";
9050
+ import { promises as fs7 } from "fs";
8801
9051
  import os2 from "os";
8802
9052
  import path7 from "path";
8803
9053
  var NATIVE_LOCK_MAX_BYTES = 16 * 1024;
@@ -8830,10 +9080,12 @@ function nativeLockFileIdentity(stat) {
8830
9080
  };
8831
9081
  }
8832
9082
  function sameNativeLockObject(left, right) {
8833
- if (left.device !== "0" || left.inode !== "0" || right.device !== "0" || right.inode !== "0") {
8834
- return left.device === right.device && left.inode === right.inode;
9083
+ const leftObject = { dev: left.device, ino: left.inode, birthtime: left.birthtimeNs };
9084
+ const rightObject = { dev: right.device, ino: right.inode, birthtime: right.birthtimeNs };
9085
+ if (hasComparableFileObject(leftObject, rightObject)) {
9086
+ return sameFileObject(leftObject, rightObject);
8835
9087
  }
8836
- return left.birthtimeNs === right.birthtimeNs && left.size === right.size;
9088
+ return sameFileObject(leftObject, rightObject) && left.size === right.size;
8837
9089
  }
8838
9090
  function sameNativeLockVersion(left, right) {
8839
9091
  return sameNativeLockObject(left, right) && left.size === right.size && left.birthtimeNs === right.birthtimeNs && left.ctimeNs === right.ctimeNs && left.mtimeNs === right.mtimeNs;
@@ -8846,41 +9098,35 @@ function sameNativeLockDiagnosis(left, right) {
8846
9098
  return left.owner.id === right.owner.id && sameNativeLockVersion(left.identity, right.identity);
8847
9099
  }
8848
9100
  async function readNativeLockSnapshot(file) {
8849
- let handle;
9101
+ let bytes;
9102
+ let stat;
8850
9103
  try {
8851
- handle = await fs6.open(file, "r");
9104
+ const result2 = await readFileRaceSafe(file, NATIVE_LOCK_MAX_BYTES, {
9105
+ bigint: true,
9106
+ label: "Native lock"
9107
+ });
9108
+ bytes = result2.bytes;
9109
+ stat = result2.stat;
8852
9110
  } catch (error) {
8853
9111
  if (error.code === "ENOENT") return null;
8854
- throw error;
8855
- }
8856
- try {
8857
- const stat = await handle.stat({ bigint: true });
8858
- if (!stat.isFile() || stat.isSymbolicLink()) {
8859
- throw new Error(`Native lock must be a regular file: ${file}`);
8860
- }
8861
- if (stat.size > BigInt(NATIVE_LOCK_MAX_BYTES)) {
8862
- throw new Error(`Native lock metadata exceeds ${NATIVE_LOCK_MAX_BYTES} bytes: ${file}`);
8863
- }
8864
- const source = await handle.readFile({ encoding: "utf8" });
8865
- const pathStat = await fs6.lstat(file, { bigint: true });
8866
- if (pathStat.isSymbolicLink() || !pathStat.isFile()) {
8867
- throw new Error(`Native lock must be a regular file: ${file}`);
8868
- }
8869
- const identity = nativeLockFileIdentity(stat);
8870
- if (!sameNativeLockObject(identity, nativeLockFileIdentity(pathStat))) {
8871
- throw new Error(`Native lock changed while reading: ${file}`);
9112
+ if (error instanceof RaceSafeReadError) {
9113
+ if (error.reason === "too-large") {
9114
+ throw new Error(`Native lock metadata exceeds ${NATIVE_LOCK_MAX_BYTES} bytes: ${file}`, {
9115
+ cause: error
9116
+ });
9117
+ }
9118
+ if (error.reason === "not-regular-file") {
9119
+ throw new Error(`Native lock must be a regular file: ${file}`, { cause: error });
9120
+ }
9121
+ throw new Error(`Native lock changed while reading: ${file}`, { cause: error });
8872
9122
  }
8873
- return {
8874
- file,
8875
- owner: parseNativeLockOwner(JSON.parse(source), file),
8876
- identity
8877
- };
8878
- } catch (error) {
8879
- if (error.code === "ENOENT") return null;
8880
9123
  throw error;
8881
- } finally {
8882
- await handle.close();
8883
9124
  }
9125
+ return {
9126
+ file,
9127
+ owner: parseNativeLockOwner(JSON.parse(bytes.toString("utf8")), file),
9128
+ identity: nativeLockFileIdentity(stat)
9129
+ };
8884
9130
  }
8885
9131
  async function readNativeLock(file) {
8886
9132
  return (await readNativeLockSnapshot(file))?.owner ?? null;
@@ -8899,13 +9145,13 @@ function diagnosisFromSnapshot(snapshot2) {
8899
9145
  }
8900
9146
  async function restoreQuarantinedNativeLock(quarantine, file) {
8901
9147
  try {
8902
- await fs6.lstat(file);
9148
+ await fs7.lstat(file);
8903
9149
  return;
8904
9150
  } catch (error) {
8905
9151
  if (error.code !== "ENOENT") throw error;
8906
9152
  }
8907
9153
  try {
8908
- await fs6.rename(quarantine, file);
9154
+ await fs7.rename(quarantine, file);
8909
9155
  } catch (error) {
8910
9156
  if (error.code !== "ENOENT") throw error;
8911
9157
  }
@@ -8919,13 +9165,13 @@ async function removeBoundNativeLock(expected, quarantineDir) {
8919
9165
  if (!sameNativeLockVersion(current.identity, expected.identity)) {
8920
9166
  throw new Error(`Native lock identity changed: ${expected.file}`);
8921
9167
  }
8922
- await fs6.mkdir(quarantineDir, { recursive: true });
9168
+ await fs7.mkdir(quarantineDir, { recursive: true });
8923
9169
  const quarantine = path7.join(
8924
9170
  quarantineDir,
8925
9171
  `${path7.basename(expected.file)}.${expected.owner.id}.${randomUUID2()}.removed`
8926
9172
  );
8927
9173
  try {
8928
- await fs6.rename(expected.file, quarantine);
9174
+ await fs7.rename(expected.file, quarantine);
8929
9175
  } catch (error) {
8930
9176
  if (error.code === "ENOENT") return "missing";
8931
9177
  throw error;
@@ -8935,7 +9181,7 @@ async function removeBoundNativeLock(expected, quarantineDir) {
8935
9181
  await restoreQuarantinedNativeLock(quarantine, expected.file);
8936
9182
  throw new Error(`Native lock changed before quarantine: ${expected.file}`);
8937
9183
  }
8938
- await fs6.rm(quarantine, { force: true });
9184
+ await fs7.rm(quarantine, { force: true });
8939
9185
  return "removed";
8940
9186
  }
8941
9187
  function newNativeLockOwner(operation) {
@@ -8950,7 +9196,7 @@ function newNativeLockOwner(operation) {
8950
9196
  async function writeNativeLockFile(file, owner) {
8951
9197
  let handle;
8952
9198
  try {
8953
- handle = await fs6.open(file, "wx");
9199
+ handle = await fs7.open(file, "wx");
8954
9200
  } catch (error) {
8955
9201
  if (error.code === "EEXIST") {
8956
9202
  const existing = await readNativeLock(file);
@@ -8971,32 +9217,32 @@ async function writeNativeLockFile(file, owner) {
8971
9217
  }
8972
9218
  async function publishNativeCoordinatorClaim(paths, operation) {
8973
9219
  const locksDir = await resolveContainedNativePath(paths.nativeRoot, paths.locksDir);
8974
- await fs6.mkdir(locksDir, { recursive: true });
9220
+ await fs7.mkdir(locksDir, { recursive: true });
8975
9221
  const coordinatorDir = await resolveContainedNativePath(
8976
9222
  paths.nativeRoot,
8977
9223
  path7.join(locksDir, NATIVE_LOCK_COORDINATOR_DIR)
8978
9224
  );
8979
- await fs6.mkdir(coordinatorDir, { recursive: true });
9225
+ await fs7.mkdir(coordinatorDir, { recursive: true });
8980
9226
  const owner = newNativeLockOwner(operation);
8981
9227
  const temporary = path7.join(coordinatorDir, `.${owner.id}.tmp`);
8982
9228
  const file = path7.join(coordinatorDir, `${owner.id}.claim`);
8983
9229
  try {
8984
9230
  const identity = await writeNativeLockFile(temporary, owner);
8985
- await fs6.rename(temporary, file);
9231
+ await fs7.rename(temporary, file);
8986
9232
  const published = await readNativeLockSnapshot(file);
8987
9233
  if (!published || !sameNativeLockObject(identity, published.identity)) {
8988
9234
  throw new Error(`Native lock coordinator claim changed while publishing: ${file}`);
8989
9235
  }
8990
9236
  return { file, nativeRoot: paths.nativeRoot, locksDir, owner, identity: published.identity };
8991
9237
  } finally {
8992
- await fs6.rm(temporary, { force: true });
9238
+ await fs7.rm(temporary, { force: true });
8993
9239
  }
8994
9240
  }
8995
9241
  async function hasNativeCoordinatorPredecessor(claim) {
8996
9242
  const coordinatorDir = path7.dirname(claim.file);
8997
9243
  let predecessor = false;
8998
9244
  const claimName = path7.basename(claim.file);
8999
- for (const entry2 of await fs6.readdir(coordinatorDir, { withFileTypes: true })) {
9245
+ for (const entry2 of await fs7.readdir(coordinatorDir, { withFileTypes: true })) {
9000
9246
  if (!entry2.isFile() || entry2.isSymbolicLink() || !entry2.name.endsWith(".claim")) continue;
9001
9247
  const file = path7.join(coordinatorDir, entry2.name);
9002
9248
  if (path7.resolve(file) === path7.resolve(claim.file)) continue;
@@ -9082,7 +9328,7 @@ async function withNativeLockRecovery(pathEntries, operation, work) {
9082
9328
  async function acquireNativeLock(paths, name, operation) {
9083
9329
  return withNativeLockCoordinator(paths, `acquire ${name}`, async () => {
9084
9330
  const locksDir = await resolveContainedNativePath(paths.nativeRoot, paths.locksDir);
9085
- await fs6.mkdir(locksDir, { recursive: true });
9331
+ await fs7.mkdir(locksDir, { recursive: true });
9086
9332
  const file = await resolveContainedNativePath(
9087
9333
  paths.nativeRoot,
9088
9334
  path7.join(locksDir, lockName(name))
@@ -9144,7 +9390,7 @@ async function takeOverNativeStaleLock(paths, file, expected) {
9144
9390
  }
9145
9391
 
9146
9392
  // domains/comet-native/native-transaction.ts
9147
- import { promises as fs7 } from "fs";
9393
+ import { promises as fs8 } from "fs";
9148
9394
  import path8 from "path";
9149
9395
  import { TextDecoder as TextDecoder3 } from "util";
9150
9396
  var JOURNAL_KEYS = /* @__PURE__ */ new Set([
@@ -9606,7 +9852,7 @@ async function resolveRef(paths, ref) {
9606
9852
  }
9607
9853
  async function exists(file) {
9608
9854
  try {
9609
- await fs7.access(file);
9855
+ await fs8.access(file);
9610
9856
  return true;
9611
9857
  } catch (error) {
9612
9858
  if (error.code === "ENOENT") return false;
@@ -9616,7 +9862,7 @@ async function exists(file) {
9616
9862
  async function readEventLogSnapshot(paths, id, options = {}) {
9617
9863
  const tx = await resolveNativeTransactionPaths(paths, id);
9618
9864
  try {
9619
- await fs7.lstat(tx.events);
9865
+ await fs8.lstat(tx.events);
9620
9866
  } catch (error) {
9621
9867
  if (error.code === "ENOENT") {
9622
9868
  return {
@@ -9696,7 +9942,7 @@ async function appendNativeTransactionEvent(paths, id, type, operationId) {
9696
9942
  ...operationId ? { operationId } : {}
9697
9943
  };
9698
9944
  parseEvent(event, event.sequence);
9699
- await fs7.mkdir(tx.directory, { recursive: true });
9945
+ await fs8.mkdir(tx.directory, { recursive: true });
9700
9946
  await atomicWriteText(tx.events, canonicalEventLogSource([...snapshot2.events, event]), {
9701
9947
  containedRoot: paths.nativeRoot,
9702
9948
  beforeCommit: () => assertEventLogSnapshotUnchanged(paths, id, snapshot2)
@@ -9709,8 +9955,8 @@ async function createNativeTransaction(paths, journal) {
9709
9955
  }
9710
9956
  journal = parseJournal(journal);
9711
9957
  const tx = await resolveNativeTransactionPaths(paths, journal.id);
9712
- await fs7.mkdir(tx.staged, { recursive: true });
9713
- await fs7.mkdir(tx.backups, { recursive: true });
9958
+ await fs8.mkdir(tx.staged, { recursive: true });
9959
+ await fs8.mkdir(tx.backups, { recursive: true });
9714
9960
  await atomicWriteJson(tx.journal, journal);
9715
9961
  await appendNativeTransactionEvent(paths, journal.id, "prepared");
9716
9962
  }
@@ -9934,7 +10180,7 @@ function nativeRootRef(paths, target) {
9934
10180
  async function hasUnfinishedTransaction(paths, allowedTransactionId) {
9935
10181
  let entries;
9936
10182
  try {
9937
- entries = await fs8.readdir(paths.transactionsDir, { withFileTypes: true });
10183
+ entries = await fs9.readdir(paths.transactionsDir, { withFileTypes: true });
9938
10184
  } catch (error) {
9939
10185
  if (error.code === "ENOENT") return false;
9940
10186
  throw error;
@@ -10010,7 +10256,7 @@ async function compareAndSwapNativeRevision(options) {
10010
10256
  // domains/comet-native/native-snapshot.ts
10011
10257
  import { createHash as createHash5 } from "crypto";
10012
10258
  import { spawn } from "node:child_process";
10013
- import { promises as fs9 } from "fs";
10259
+ import { promises as fs10 } from "fs";
10014
10260
  import path10 from "path";
10015
10261
 
10016
10262
  // domains/comet-native/native-hash.ts
@@ -10045,9 +10291,17 @@ var MANIFEST_KEYS = /* @__PURE__ */ new Set([
10045
10291
  "entries",
10046
10292
  "omitted",
10047
10293
  "omittedCount",
10048
- "omissionOverflow"
10294
+ "omissionOverflow",
10295
+ "policy"
10296
+ ]);
10297
+ var LIMIT_KEYS = /* @__PURE__ */ new Set([
10298
+ "maxFiles",
10299
+ "maxFileBytes",
10300
+ "maxTotalBytes",
10301
+ "maxManifestBytes",
10302
+ "maxDurationMs"
10049
10303
  ]);
10050
- var LIMIT_KEYS = /* @__PURE__ */ new Set(["maxFiles", "maxFileBytes", "maxTotalBytes", "maxManifestBytes"]);
10304
+ var POLICY_KEYS = /* @__PURE__ */ new Set(["schema", "include", "exclude", "hash"]);
10051
10305
  var CAPTURE_KEYS = /* @__PURE__ */ new Set(["provider", "gitSelection", "physicalSelection", "projection"]);
10052
10306
  var GIT_PROJECTION_KEYS = /* @__PURE__ */ new Set(["provider", "selection"]);
10053
10307
  var GIT_SELECTION_KEYS = /* @__PURE__ */ new Set([
@@ -10471,7 +10725,7 @@ async function hasGitMetadataBoundary(projectRoot) {
10471
10725
  let cursor = path10.resolve(projectRoot);
10472
10726
  while (true) {
10473
10727
  try {
10474
- await fs9.lstat(path10.join(cursor, ".git"));
10728
+ await fs10.lstat(path10.join(cursor, ".git"));
10475
10729
  return true;
10476
10730
  } catch (error) {
10477
10731
  if (error.code !== "ENOENT") throw error;
@@ -10700,7 +10954,7 @@ async function nativePhysicalSnapshotSelection(options) {
10700
10954
  if (!hasExecutionBudget()) return;
10701
10955
  let handle;
10702
10956
  try {
10703
- handle = await fs9.opendir(directory);
10957
+ handle = await fs10.opendir(directory);
10704
10958
  } catch (error) {
10705
10959
  if (!hasExecutionBudget()) return;
10706
10960
  if (!isUnreadableError(error) && !isChangedDuringReadError(error)) throw error;
@@ -10750,7 +11004,7 @@ async function nativePhysicalSnapshotSelection(options) {
10750
11004
  if (!hasExecutionBudget()) break;
10751
11005
  let stat;
10752
11006
  try {
10753
- stat = await fs9.lstat(target);
11007
+ stat = await fs10.lstat(target);
10754
11008
  } catch (error) {
10755
11009
  if (!hasExecutionBudget()) break;
10756
11010
  if (!isUnreadableError(error) && !isChangedDuringReadError(error)) throw error;
@@ -10773,7 +11027,7 @@ async function nativePhysicalSnapshotSelection(options) {
10773
11027
  if (!hasExecutionBudget()) break;
10774
11028
  let realDirectory;
10775
11029
  try {
10776
- realDirectory = await fs9.realpath(target);
11030
+ realDirectory = await fs10.realpath(target);
10777
11031
  } catch (error) {
10778
11032
  if (!hasExecutionBudget()) break;
10779
11033
  if (!isUnreadableError(error) && !isChangedDuringReadError(error)) throw error;
@@ -10964,6 +11218,133 @@ function isChangedDuringReadError(error) {
10964
11218
  function serializedManifestBytes(manifest) {
10965
11219
  return Buffer.byteLength(JSON.stringify(manifest, null, 2) + "\n");
10966
11220
  }
11221
+ function snapshotPolicyHash(include, exclude) {
11222
+ return sha256Text(
11223
+ `comet.native.snapshot-policy.v1
11224
+ ${JSON.stringify({ include, exclude, hash: "sha256" })}`
11225
+ );
11226
+ }
11227
+ function epsilonClosure(tokens, positions, checkpoint) {
11228
+ const closure = new Set(positions);
11229
+ const pending = [...positions];
11230
+ while (pending.length > 0) {
11231
+ if (checkpoint && !checkpoint()) return null;
11232
+ const position = pending.pop();
11233
+ const token = tokens[position];
11234
+ if (token && (token.kind === "star" || token.kind === "globstar" || token.kind === "globstar-slash") && !closure.has(position + 1)) {
11235
+ closure.add(position + 1);
11236
+ pending.push(position + 1);
11237
+ }
11238
+ }
11239
+ return closure;
11240
+ }
11241
+ function cooperativePatternCheckpoint(hasBudget) {
11242
+ let operationsUntilCheck = 0;
11243
+ return () => {
11244
+ if (operationsUntilCheck > 0) {
11245
+ operationsUntilCheck -= 1;
11246
+ return true;
11247
+ }
11248
+ if (!hasBudget()) return false;
11249
+ operationsUntilCheck = 63;
11250
+ return true;
11251
+ };
11252
+ }
11253
+ function compileNativeSnapshotPattern(pattern) {
11254
+ const normalized = normalizeNativeSnapshotPattern(pattern, "Native snapshot pattern");
11255
+ const tokens = [];
11256
+ for (let index = 0; index < normalized.length; index += 1) {
11257
+ const character = normalized[index];
11258
+ if (character === "*" && normalized[index + 1] === "*") {
11259
+ index += 1;
11260
+ if (normalized[index + 1] === "/") {
11261
+ index += 1;
11262
+ tokens.push({ kind: "globstar-slash" });
11263
+ } else {
11264
+ tokens.push({ kind: "globstar" });
11265
+ }
11266
+ } else if (character === "*") {
11267
+ tokens.push({ kind: "star" });
11268
+ } else if (character === "?") {
11269
+ tokens.push({ kind: "question" });
11270
+ } else {
11271
+ tokens.push({ kind: "literal", value: character });
11272
+ }
11273
+ }
11274
+ return (relative, hasBudget) => {
11275
+ const checkpoint = hasBudget ? cooperativePatternCheckpoint(hasBudget) : void 0;
11276
+ if (checkpoint && !checkpoint()) return false;
11277
+ let positions = epsilonClosure(tokens, /* @__PURE__ */ new Set([0]), checkpoint);
11278
+ if (positions === null) return false;
11279
+ for (const character of relative) {
11280
+ if (checkpoint && !checkpoint()) return false;
11281
+ const next = /* @__PURE__ */ new Set();
11282
+ for (const position of positions) {
11283
+ if (checkpoint && !checkpoint()) return false;
11284
+ const token = tokens[position];
11285
+ if (!token) continue;
11286
+ if (token.kind === "literal" && token.value === character) {
11287
+ next.add(position + 1);
11288
+ } else if (token.kind === "question" && character !== "/") {
11289
+ next.add(position + 1);
11290
+ } else if (token.kind === "star" && character !== "/") {
11291
+ next.add(position);
11292
+ } else if (token.kind === "globstar") {
11293
+ next.add(position);
11294
+ } else if (token.kind === "globstar-slash") {
11295
+ next.add(position);
11296
+ if (character === "/") next.add(position + 1);
11297
+ }
11298
+ }
11299
+ positions = epsilonClosure(tokens, next, checkpoint);
11300
+ if (positions === null) return false;
11301
+ if (positions.size === 0) return false;
11302
+ }
11303
+ return epsilonClosure(tokens, positions, checkpoint)?.has(tokens.length) ?? false;
11304
+ };
11305
+ }
11306
+ function resolveSnapshotPolicy(value) {
11307
+ if (value === void 0) return void 0;
11308
+ const include = [
11309
+ ...new Set(value.include.map((item) => normalizeNativeSnapshotPattern(item, "include")))
11310
+ ].sort((left, right) => left.localeCompare(right, "en"));
11311
+ const exclude = [
11312
+ ...new Set(value.exclude.map((item) => normalizeNativeSnapshotPattern(item, "exclude")))
11313
+ ].sort((left, right) => left.localeCompare(right, "en"));
11314
+ if (include.length === 0) throw new Error("Native snapshot policy include must not be empty");
11315
+ const hash6 = snapshotPolicyHash(include, exclude);
11316
+ if ("hash" in value && value.hash !== hash6) {
11317
+ throw new Error("Native snapshot policy hash is invalid");
11318
+ }
11319
+ return {
11320
+ manifest: {
11321
+ schema: "comet.native.snapshot-policy.v1",
11322
+ include,
11323
+ exclude,
11324
+ hash: hash6
11325
+ },
11326
+ includeMatchers: include.map(compileNativeSnapshotPattern),
11327
+ excludeMatchers: exclude.map(compileNativeSnapshotPattern)
11328
+ };
11329
+ }
11330
+ function snapshotPolicyIncludes(policy, relative, execution) {
11331
+ if (!policy) return true;
11332
+ const hasBudget = () => nativeSnapshotExecutionHasBudget(execution);
11333
+ let included = false;
11334
+ for (const matcher of policy.includeMatchers) {
11335
+ if (!hasBudget()) return false;
11336
+ if (matcher(relative, hasBudget)) {
11337
+ included = true;
11338
+ break;
11339
+ }
11340
+ }
11341
+ if (!included) return false;
11342
+ for (const matcher of policy.excludeMatchers) {
11343
+ if (!hasBudget()) return false;
11344
+ if (matcher(relative, hasBudget)) return false;
11345
+ }
11346
+ return true;
11347
+ }
10967
11348
  function foldSnapshotOverflowHash(previous, kind, value) {
10968
11349
  const payload = JSON.stringify(value);
10969
11350
  return sha256Text(
@@ -10985,16 +11366,18 @@ function takeLastCompactableOmission(omissions) {
10985
11366
  return null;
10986
11367
  }
10987
11368
  function sameFileIdentity4(left, right) {
10988
- if (left.dev !== 0 || left.ino !== 0 || right.dev !== 0 || right.ino !== 0) {
10989
- return left.dev === right.dev && left.ino === right.ino;
11369
+ const leftObject = { ...left, birthtime: left.birthtimeMs };
11370
+ const rightObject = { ...right, birthtime: right.birthtimeMs };
11371
+ if (hasComparableFileObject(leftObject, rightObject)) {
11372
+ return sameFileObject(leftObject, rightObject);
10990
11373
  }
10991
- return left.birthtimeMs === right.birthtimeMs && left.ctimeMs === right.ctimeMs && left.size === right.size;
11374
+ return sameFileObject(leftObject, rightObject) && left.birthtimeMs === right.birthtimeMs && left.ctimeMs === right.ctimeMs && left.size === right.size;
10992
11375
  }
10993
11376
  async function sha256FileBounded(file, maxBytes, expected, execution) {
10994
11377
  if (!nativeSnapshotExecutionHasBudget(execution)) return { status: "budget-exhausted" };
10995
11378
  let handle;
10996
11379
  try {
10997
- handle = await fs9.open(file, "r");
11380
+ handle = await fs10.open(file, "r");
10998
11381
  } catch (error) {
10999
11382
  if (!nativeSnapshotExecutionHasBudget(execution)) return { status: "budget-exhausted" };
11000
11383
  throw error;
@@ -11329,8 +11712,28 @@ function parseNativeContentSnapshotManifest(value) {
11329
11712
  maxManifestBytes: positiveInteger(
11330
11713
  limitValue.maxManifestBytes,
11331
11714
  "Native snapshot maxManifestBytes"
11332
- )
11715
+ ),
11716
+ ...limitValue.maxDurationMs === void 0 ? {} : {
11717
+ maxDurationMs: positiveInteger(limitValue.maxDurationMs, "Native snapshot maxDurationMs")
11718
+ }
11333
11719
  };
11720
+ let policy;
11721
+ if (manifest.policy !== void 0) {
11722
+ const policyValue = record3(manifest.policy, "Native snapshot policy");
11723
+ rejectUnknown3(policyValue, POLICY_KEYS, "Native snapshot policy");
11724
+ if (policyValue.schema !== "comet.native.snapshot-policy.v1") {
11725
+ throw new Error("Native snapshot policy schema is invalid");
11726
+ }
11727
+ if (!Array.isArray(policyValue.include) || !Array.isArray(policyValue.exclude)) {
11728
+ throw new Error("Native snapshot policy patterns must be arrays");
11729
+ }
11730
+ policy = resolveSnapshotPolicy({
11731
+ include: policyValue.include,
11732
+ exclude: policyValue.exclude,
11733
+ hash: policyValue.hash,
11734
+ schema: "comet.native.snapshot-policy.v1"
11735
+ }).manifest;
11736
+ }
11334
11737
  if (!Array.isArray(manifest.entries) || !Array.isArray(manifest.omitted)) {
11335
11738
  throw new Error("Native content snapshot entries and omissions must be arrays");
11336
11739
  }
@@ -11413,6 +11816,7 @@ function parseNativeContentSnapshotManifest(value) {
11413
11816
  createdAt: manifest.createdAt,
11414
11817
  complete: manifest.complete,
11415
11818
  limits,
11819
+ ...policy ? { policy } : {},
11416
11820
  entries,
11417
11821
  omitted,
11418
11822
  omittedCount,
@@ -11553,13 +11957,18 @@ function nativeBaselineManifestFile(paths, name) {
11553
11957
  return path10.join(changeDir, "runtime", "baseline-manifest.json");
11554
11958
  }
11555
11959
  async function createNativeContentSnapshot(paths, options = {}) {
11556
- const execution = createNativeSnapshotExecution(options);
11557
11960
  const limits = {
11558
11961
  maxFiles: options.limits?.maxFiles ?? DEFAULT_NATIVE_SNAPSHOT_LIMITS.maxFiles,
11559
11962
  maxFileBytes: options.limits?.maxFileBytes ?? DEFAULT_NATIVE_SNAPSHOT_LIMITS.maxFileBytes,
11560
11963
  maxTotalBytes: options.limits?.maxTotalBytes ?? DEFAULT_NATIVE_SNAPSHOT_LIMITS.maxTotalBytes,
11561
- maxManifestBytes: options.limits?.maxManifestBytes ?? DEFAULT_NATIVE_SNAPSHOT_LIMITS.maxManifestBytes
11964
+ maxManifestBytes: options.limits?.maxManifestBytes ?? DEFAULT_NATIVE_SNAPSHOT_LIMITS.maxManifestBytes,
11965
+ ...options.limits?.maxDurationMs === void 0 ? {} : { maxDurationMs: options.limits.maxDurationMs }
11562
11966
  };
11967
+ const policy = resolveSnapshotPolicy(options.policy);
11968
+ const execution = createNativeSnapshotExecution({
11969
+ ...options,
11970
+ deadlineMs: options.deadlineMs ?? limits.maxDurationMs
11971
+ });
11563
11972
  const gitSelectionLimits = resolveNativeGitSelectionLimits(options.gitSelectionLimits);
11564
11973
  const physicalSelectionLimits = resolveNativePhysicalSelectionLimits(
11565
11974
  options.physicalSelectionLimits
@@ -11568,9 +11977,9 @@ async function createNativeContentSnapshot(paths, options = {}) {
11568
11977
  throw new Error("Native snapshot limits must be positive");
11569
11978
  }
11570
11979
  const projectRoot = path10.resolve(paths.projectRoot);
11571
- const physicalProjectRoot = await fs9.realpath(projectRoot);
11980
+ const physicalProjectRoot = await fs10.realpath(projectRoot);
11572
11981
  const nativeRoot = path10.resolve(paths.nativeRoot);
11573
- const physicalNativeRoot = await fs9.realpath(nativeRoot);
11982
+ const physicalNativeRoot = await fs10.realpath(nativeRoot);
11574
11983
  const configFile = path10.resolve(paths.configFile);
11575
11984
  const selectionFile = path10.join(projectRoot, ".comet", "current-change.json");
11576
11985
  const denylist = normalizedDenylist(projectRoot, options.denylist ?? []);
@@ -11642,7 +12051,7 @@ async function createNativeContentSnapshot(paths, options = {}) {
11642
12051
  if (!nativeSnapshotExecutionHasBudget(execution)) return;
11643
12052
  let realTarget;
11644
12053
  try {
11645
- realTarget = await fs9.realpath(target);
12054
+ realTarget = await fs10.realpath(target);
11646
12055
  } catch (error) {
11647
12056
  if (!nativeSnapshotExecutionHasBudget(execution)) return;
11648
12057
  if (!isUnreadableError(error) && !isChangedDuringReadError(error)) throw error;
@@ -11683,9 +12092,9 @@ async function createNativeContentSnapshot(paths, options = {}) {
11683
12092
  return;
11684
12093
  }
11685
12094
  if (!nativeSnapshotExecutionHasBudget(execution)) return;
11686
- afterRealTarget = await fs9.realpath(target);
12095
+ afterRealTarget = await fs10.realpath(target);
11687
12096
  if (!nativeSnapshotExecutionHasBudget(execution)) return;
11688
- after = await fs9.lstat(target);
12097
+ after = await fs10.lstat(target);
11689
12098
  } catch (error) {
11690
12099
  if (!nativeSnapshotExecutionHasBudget(execution)) return;
11691
12100
  if (!isUnreadableError(error) && !isChangedDuringReadError(error)) throw error;
@@ -11719,11 +12128,11 @@ async function createNativeContentSnapshot(paths, options = {}) {
11719
12128
  let secondTarget;
11720
12129
  let after;
11721
12130
  try {
11722
- firstTarget = await fs9.readlink(target, { encoding: "buffer" });
12131
+ firstTarget = await fs10.readlink(target, { encoding: "buffer" });
11723
12132
  if (!nativeSnapshotExecutionHasBudget(execution)) return;
11724
- after = await fs9.lstat(target);
12133
+ after = await fs10.lstat(target);
11725
12134
  if (!nativeSnapshotExecutionHasBudget(execution)) return;
11726
- secondTarget = await fs9.readlink(target, { encoding: "buffer" });
12135
+ secondTarget = await fs10.readlink(target, { encoding: "buffer" });
11727
12136
  } catch (error) {
11728
12137
  if (!nativeSnapshotExecutionHasBudget(execution)) return;
11729
12138
  if (!isUnreadableError(error) && !isChangedDuringReadError(error)) throw error;
@@ -11767,9 +12176,9 @@ async function createNativeContentSnapshot(paths, options = {}) {
11767
12176
  let realTarget2;
11768
12177
  let stat2;
11769
12178
  try {
11770
- realTarget2 = await fs9.realpath(validation2.target);
12179
+ realTarget2 = await fs10.realpath(validation2.target);
11771
12180
  if (!nativeSnapshotExecutionHasBudget(execution)) return;
11772
- stat2 = await fs9.lstat(validation2.target);
12181
+ stat2 = await fs10.lstat(validation2.target);
11773
12182
  } catch (error) {
11774
12183
  if (!nativeSnapshotExecutionHasBudget(execution)) return;
11775
12184
  if (!isUnreadableError(error) && !isChangedDuringReadError(error)) throw error;
@@ -11796,9 +12205,9 @@ async function createNativeContentSnapshot(paths, options = {}) {
11796
12205
  let stat2;
11797
12206
  let rawTarget;
11798
12207
  try {
11799
- stat2 = await fs9.lstat(validation2.target);
12208
+ stat2 = await fs10.lstat(validation2.target);
11800
12209
  if (!nativeSnapshotExecutionHasBudget(execution)) return;
11801
- rawTarget = await fs9.readlink(validation2.target, { encoding: "buffer" });
12210
+ rawTarget = await fs10.readlink(validation2.target, { encoding: "buffer" });
11802
12211
  } catch (error) {
11803
12212
  if (!nativeSnapshotExecutionHasBudget(execution)) return;
11804
12213
  if (!isUnreadableError(error) && !isChangedDuringReadError(error)) throw error;
@@ -11824,9 +12233,9 @@ async function createNativeContentSnapshot(paths, options = {}) {
11824
12233
  let realTarget;
11825
12234
  let stat;
11826
12235
  try {
11827
- realTarget = await fs9.realpath(validation2.target);
12236
+ realTarget = await fs10.realpath(validation2.target);
11828
12237
  if (!nativeSnapshotExecutionHasBudget(execution)) return;
11829
- stat = await fs9.lstat(validation2.target);
12238
+ stat = await fs10.lstat(validation2.target);
11830
12239
  } catch {
11831
12240
  if (!nativeSnapshotExecutionHasBudget(execution)) return;
11832
12241
  invalidateCapturedEntry(relative, {
@@ -11882,7 +12291,7 @@ async function createNativeContentSnapshot(paths, options = {}) {
11882
12291
  for (const [relative, target] of capturedTrackedAbsences) {
11883
12292
  if (!nativeSnapshotExecutionHasBudget(execution)) return;
11884
12293
  try {
11885
- const stat = await fs9.lstat(target);
12294
+ const stat = await fs10.lstat(target);
11886
12295
  if (!nativeSnapshotExecutionHasBudget(execution)) return;
11887
12296
  omit({
11888
12297
  path: relative,
@@ -11918,10 +12327,12 @@ async function createNativeContentSnapshot(paths, options = {}) {
11918
12327
  for (const record8 of before.records) {
11919
12328
  if (record8.type !== "file" && record8.type !== "symlink") continue;
11920
12329
  if (remainingNativeSnapshotTime(execution) < 1) break;
12330
+ if (!snapshotPolicyIncludes(policy, record8.path, execution)) continue;
12331
+ if (remainingNativeSnapshotTime(execution) < 1) break;
11921
12332
  const target = path10.resolve(projectRoot, ...record8.path.split("/"));
11922
12333
  let stat;
11923
12334
  try {
11924
- stat = await fs9.lstat(target);
12335
+ stat = await fs10.lstat(target);
11925
12336
  } catch (error) {
11926
12337
  if (!nativeSnapshotExecutionHasBudget(execution)) break;
11927
12338
  if (!isUnreadableError(error) && !isChangedDuringReadError(error)) throw error;
@@ -11974,6 +12385,9 @@ async function createNativeContentSnapshot(paths, options = {}) {
11974
12385
  await options.gitSelectionHooks?.afterInitialSelection?.();
11975
12386
  for (const relative of selectionPaths(gitSelection)) {
11976
12387
  if (!isSnapshotProjectRef(paths, relative)) continue;
12388
+ if (remainingNativeSnapshotTime(execution) < 1) break;
12389
+ if (!snapshotPolicyIncludes(policy, relative, execution)) continue;
12390
+ if (remainingNativeSnapshotTime(execution) < 1) break;
11977
12391
  const target = path10.resolve(projectRoot, ...relative.split("/"));
11978
12392
  if (target === configFile || target === selectionFile || denylist.some((denied) => sameOrInside(denied, target))) {
11979
12393
  continue;
@@ -11986,7 +12400,7 @@ async function createNativeContentSnapshot(paths, options = {}) {
11986
12400
  let realGitlink;
11987
12401
  let gitlinkStat;
11988
12402
  try {
11989
- [realGitlink, gitlinkStat] = await Promise.all([fs9.realpath(target), fs9.lstat(target)]);
12403
+ [realGitlink, gitlinkStat] = await Promise.all([fs10.realpath(target), fs10.lstat(target)]);
11990
12404
  } catch {
11991
12405
  omit({
11992
12406
  path: relative,
@@ -12044,7 +12458,7 @@ async function createNativeContentSnapshot(paths, options = {}) {
12044
12458
  }
12045
12459
  let before;
12046
12460
  try {
12047
- before = await fs9.lstat(target);
12461
+ before = await fs10.lstat(target);
12048
12462
  } catch (error) {
12049
12463
  if (isChangedDuringReadError(error) && gitSelection.tracked.has(relative)) {
12050
12464
  capturedTrackedAbsences.set(relative, target);
@@ -12098,6 +12512,7 @@ async function createNativeContentSnapshot(paths, options = {}) {
12098
12512
  createdAt: (options.now ?? /* @__PURE__ */ new Date()).toISOString(),
12099
12513
  complete: omittedCount === 0,
12100
12514
  limits,
12515
+ ...policy ? { policy: policy.manifest } : {},
12101
12516
  entries,
12102
12517
  omitted,
12103
12518
  omittedCount,
@@ -12134,6 +12549,21 @@ async function createNativeContentSnapshot(paths, options = {}) {
12134
12549
  }
12135
12550
  return manifest;
12136
12551
  }
12552
+ async function createNativeCurrentContentSnapshot(paths, baseline, options = {}) {
12553
+ const config = await readProjectConfig(paths.projectRoot);
12554
+ const settings = config?.native.snapshot ?? DEFAULT_NATIVE_SNAPSHOT_CONFIG;
12555
+ return createNativeContentSnapshot(paths, {
12556
+ ...options,
12557
+ policy: baseline.policy,
12558
+ limits: {
12559
+ maxFiles: settings.max_files,
12560
+ maxFileBytes: settings.max_total_bytes,
12561
+ maxTotalBytes: settings.max_total_bytes,
12562
+ maxDurationMs: settings.max_duration_ms
12563
+ },
12564
+ deadlineMs: settings.max_duration_ms
12565
+ });
12566
+ }
12137
12567
  async function writeNativeBaselineManifest(paths, name, manifest) {
12138
12568
  const file = nativeBaselineManifestFile(paths, name);
12139
12569
  await resolveContainedNativePath(paths.nativeRoot, file);
@@ -12182,7 +12612,7 @@ function assertRunStorageLayout(storage) {
12182
12612
 
12183
12613
  // domains/comet-native/native-run-store.ts
12184
12614
  import { createHash as createHash6 } from "node:crypto";
12185
- import { constants as fsConstants2, promises as fs10 } from "node:fs";
12615
+ import { constants as fsConstants3, promises as fs11 } from "node:fs";
12186
12616
  import path13 from "node:path";
12187
12617
  import { TextDecoder as TextDecoder4 } from "node:util";
12188
12618
 
@@ -12371,14 +12801,22 @@ function asIdentity(stat) {
12371
12801
  };
12372
12802
  }
12373
12803
  function sameFileIdentity5(expected, actual) {
12374
- const samePlatformIdentity = expected.dev !== 0 || expected.ino !== 0 || actual.dev !== 0 || actual.ino !== 0 ? expected.dev === actual.dev && expected.ino === actual.ino : expected.birthtimeMs === actual.birthtimeMs;
12375
- return samePlatformIdentity && expected.birthtimeMs === actual.birthtimeMs && expected.ctimeMs === actual.ctimeMs && expected.mtimeMs === actual.mtimeMs && expected.size === actual.size;
12804
+ return sameFileObject(
12805
+ { ...expected, birthtime: expected.birthtimeMs },
12806
+ {
12807
+ ...actual,
12808
+ birthtime: actual.birthtimeMs
12809
+ }
12810
+ ) && expected.birthtimeMs === actual.birthtimeMs && expected.ctimeMs === actual.ctimeMs && expected.mtimeMs === actual.mtimeMs && expected.size === actual.size;
12376
12811
  }
12377
12812
  function sameDirectoryIdentity4(expected, actual) {
12378
- if (expected.dev !== 0 || expected.ino !== 0 || actual.dev !== 0 || actual.ino !== 0) {
12379
- return expected.dev === actual.dev && expected.ino === actual.ino;
12380
- }
12381
- return expected.birthtimeMs === actual.birthtimeMs;
12813
+ return sameFileObject(
12814
+ { ...expected, birthtime: expected.birthtimeMs },
12815
+ {
12816
+ ...actual,
12817
+ birthtime: actual.birthtimeMs
12818
+ }
12819
+ );
12382
12820
  }
12383
12821
  function runFile(changeDir, kind, relativePath) {
12384
12822
  const expected = NATIVE_RUN_STORAGE[kind];
@@ -12391,13 +12829,13 @@ function runFile(changeDir, kind, relativePath) {
12391
12829
  return target;
12392
12830
  }
12393
12831
  async function directoryIdentity2(directory) {
12394
- const stat = await fs10.lstat(directory);
12832
+ const stat = await fs11.lstat(directory);
12395
12833
  if (!stat.isDirectory() || stat.isSymbolicLink()) {
12396
12834
  throw new Error(`Native Run parent must be a real directory: ${directory}`);
12397
12835
  }
12398
12836
  return {
12399
12837
  path: directory,
12400
- realPath: await fs10.realpath(directory),
12838
+ realPath: await fs11.realpath(directory),
12401
12839
  dev: stat.dev,
12402
12840
  ino: stat.ino,
12403
12841
  birthtimeMs: stat.birthtimeMs
@@ -12429,8 +12867,8 @@ async function captureDirectoryChain3(root, directory) {
12429
12867
  }
12430
12868
  async function verifyDirectoryChain4(chain) {
12431
12869
  for (const identity of chain) {
12432
- const stat = await fs10.lstat(identity.path);
12433
- if (!stat.isDirectory() || stat.isSymbolicLink() || !sameDirectoryIdentity4(identity, stat) || await fs10.realpath(identity.path) !== identity.realPath) {
12870
+ const stat = await fs11.lstat(identity.path);
12871
+ if (!stat.isDirectory() || stat.isSymbolicLink() || !sameDirectoryIdentity4(identity, stat) || await fs11.realpath(identity.path) !== identity.realPath) {
12434
12872
  throw new Error(`Native Run parent changed during I/O: ${identity.path}`);
12435
12873
  }
12436
12874
  }
@@ -12455,7 +12893,7 @@ async function readProtectedText(changeDir, file, maxBytes, label, hooks) {
12455
12893
  await hooks?.afterParentChainCaptured?.();
12456
12894
  let before;
12457
12895
  try {
12458
- before = await fs10.lstat(file);
12896
+ before = await fs11.lstat(file);
12459
12897
  } catch (error) {
12460
12898
  if (error.code === "ENOENT") return null;
12461
12899
  throw error;
@@ -12465,22 +12903,22 @@ async function readProtectedText(changeDir, file, maxBytes, label, hooks) {
12465
12903
  }
12466
12904
  if (before.size > maxBytes) throw new Error(`${label} exceeds ${maxBytes} bytes`);
12467
12905
  const beforeIdentity = asIdentity(before);
12468
- const beforeRealPath = await fs10.realpath(file);
12906
+ const beforeRealPath = await fs11.realpath(file);
12469
12907
  if (!isInside4(chain[0].realPath, beforeRealPath)) {
12470
12908
  throw new Error(`${label} resolves outside its change`);
12471
12909
  }
12472
- const flags = process.platform === "win32" ? fsConstants2.O_RDONLY : fsConstants2.O_RDONLY | fsConstants2.O_NOFOLLOW | fsConstants2.O_NONBLOCK;
12910
+ const flags = process.platform === "win32" ? fsConstants3.O_RDONLY : fsConstants3.O_RDONLY | fsConstants3.O_NOFOLLOW | fsConstants3.O_NONBLOCK;
12473
12911
  let handle;
12474
12912
  try {
12475
- handle = await fs10.open(file, flags);
12913
+ handle = await fs11.open(file, flags);
12476
12914
  } catch (error) {
12477
12915
  throw new Error(`${label} changed while opening`, { cause: error });
12478
12916
  }
12479
12917
  try {
12480
12918
  const [opened, pathAfterOpen, realPathAfterOpen] = await Promise.all([
12481
12919
  handle.stat(),
12482
- fs10.lstat(file),
12483
- fs10.realpath(file)
12920
+ fs11.lstat(file),
12921
+ fs11.realpath(file)
12484
12922
  ]);
12485
12923
  await verifyDirectoryChain4(chain);
12486
12924
  if (!opened.isFile() || !pathAfterOpen.isFile() || pathAfterOpen.isSymbolicLink() || realPathAfterOpen !== beforeRealPath || !sameFileIdentity5(beforeIdentity, opened) || !sameFileIdentity5(beforeIdentity, pathAfterOpen)) {
@@ -12491,8 +12929,8 @@ async function readProtectedText(changeDir, file, maxBytes, label, hooks) {
12491
12929
  await hooks?.beforeFinalCheck?.();
12492
12930
  const [afterHandle, afterPath, afterRealPath] = await Promise.all([
12493
12931
  handle.stat(),
12494
- fs10.lstat(file),
12495
- fs10.realpath(file)
12932
+ fs11.lstat(file),
12933
+ fs11.realpath(file)
12496
12934
  ]);
12497
12935
  await verifyDirectoryChain4(chain);
12498
12936
  if (!afterPath.isFile() || afterPath.isSymbolicLink() || afterRealPath !== beforeRealPath || !sameFileIdentity5(beforeIdentity, afterHandle) || !sameFileIdentity5(beforeIdentity, afterPath)) {
@@ -12517,13 +12955,13 @@ async function captureTarget(changeDir, file, label) {
12517
12955
  if (!chain) return { exists: false };
12518
12956
  let stat;
12519
12957
  try {
12520
- stat = await fs10.lstat(file);
12958
+ stat = await fs11.lstat(file);
12521
12959
  } catch (error) {
12522
12960
  if (error.code === "ENOENT") return { exists: false };
12523
12961
  throw error;
12524
12962
  }
12525
12963
  if (!stat.isFile() || stat.isSymbolicLink()) throw new Error(`${label} must be a regular file`);
12526
- const realPath = await fs10.realpath(file);
12964
+ const realPath = await fs11.realpath(file);
12527
12965
  if (!isInside4(chain[0].realPath, realPath))
12528
12966
  throw new Error(`${label} resolves outside its change`);
12529
12967
  await verifyDirectoryChain4(chain);
@@ -12537,12 +12975,12 @@ async function verifyTarget(changeDir, file, expected, label) {
12537
12975
  }
12538
12976
  let stat;
12539
12977
  try {
12540
- stat = await fs10.lstat(file);
12978
+ stat = await fs11.lstat(file);
12541
12979
  } catch (error) {
12542
12980
  if (error.code === "ENOENT" && !expected.exists) return;
12543
12981
  throw new Error(`${label} changed before commit`, { cause: error });
12544
12982
  }
12545
- if (!expected.exists || !stat.isFile() || stat.isSymbolicLink() || !sameFileIdentity5(expected.identity, stat) || await fs10.realpath(file) !== expected.realPath) {
12983
+ if (!expected.exists || !stat.isFile() || stat.isSymbolicLink() || !sameFileIdentity5(expected.identity, stat) || await fs11.realpath(file) !== expected.realPath) {
12546
12984
  throw new Error(`${label} changed before commit`);
12547
12985
  }
12548
12986
  await verifyDirectoryChain4(chain);
@@ -12851,7 +13289,7 @@ async function repairNativeTrajectoryTail(paths, name, hooks) {
12851
13289
 
12852
13290
  // domains/comet-native/native-workspace.ts
12853
13291
  import { createHash as createHash8 } from "node:crypto";
12854
- import { promises as fs11 } from "node:fs";
13292
+ import { promises as fs12 } from "node:fs";
12855
13293
  import path15 from "node:path";
12856
13294
  var HASH_PATTERN2 = /^[a-f0-9]{64}$/u;
12857
13295
  var MAX_WORKSPACE_IDENTITY_BYTES = 16 * 1024;
@@ -12890,8 +13328,8 @@ function identityHash(tag, value) {
12890
13328
  ${value}`).digest("hex");
12891
13329
  }
12892
13330
  async function physicalDirectoryIdentity(tag, value) {
12893
- const realPath = await fs11.realpath(value);
12894
- const stat = await fs11.lstat(realPath);
13331
+ const realPath = await fs12.realpath(value);
13332
+ const stat = await fs12.lstat(realPath);
12895
13333
  if (!stat.isDirectory() || stat.isSymbolicLink()) {
12896
13334
  throw new Error("Native workspace identity requires a real directory");
12897
13335
  }
@@ -12902,8 +13340,8 @@ ${stat.ino}
12902
13340
  ${stat.birthtimeMs}`);
12903
13341
  }
12904
13342
  async function directoryPathIdentity(tag, value) {
12905
- const realPath = await fs11.realpath(value);
12906
- const stat = await fs11.lstat(realPath);
13343
+ const realPath = await fs12.realpath(value);
13344
+ const stat = await fs12.lstat(realPath);
12907
13345
  if (!stat.isDirectory() || stat.isSymbolicLink()) {
12908
13346
  throw new Error("Native workspace identity requires a real directory");
12909
13347
  }
@@ -13170,15 +13608,17 @@ var NativeChangeRevisionConflictError = class extends Error {
13170
13608
  code = "native-change-revision-conflict";
13171
13609
  };
13172
13610
  var NativeBaselineIncompleteError = class extends Error {
13173
- constructor(change, omittedCount, omittedByReason, samplePaths, sampleTruncated) {
13611
+ constructor(change, omittedCount, omittedByReason, samplePaths, sampleTruncated, effectiveLimits = null, policyHash = null) {
13174
13612
  super(
13175
- `Native change ${change} baseline is incomplete (${omittedCount} omitted entr${omittedCount === 1 ? "y" : "ies"})`
13613
+ `Native change ${change} baseline is incomplete (${omittedCount} omitted entr${omittedCount === 1 ? "y" : "ies"}). Adjust native.snapshot scope or resource budgets in .comet/config.yaml, then retry.`
13176
13614
  );
13177
13615
  this.change = change;
13178
13616
  this.omittedCount = omittedCount;
13179
13617
  this.omittedByReason = omittedByReason;
13180
13618
  this.samplePaths = samplePaths;
13181
13619
  this.sampleTruncated = sampleTruncated;
13620
+ this.effectiveLimits = effectiveLimits;
13621
+ this.policyHash = policyHash;
13182
13622
  this.name = "NativeBaselineIncompleteError";
13183
13623
  }
13184
13624
  change;
@@ -13186,6 +13626,8 @@ var NativeBaselineIncompleteError = class extends Error {
13186
13626
  omittedByReason;
13187
13627
  samplePaths;
13188
13628
  sampleTruncated;
13629
+ effectiveLimits;
13630
+ policyHash;
13189
13631
  code = "native-baseline-incomplete";
13190
13632
  };
13191
13633
  var NATIVE_BRIEF_TEMPLATE = [
@@ -13542,7 +13984,7 @@ async function hasPendingNativeSchemaMigration(paths, name) {
13542
13984
  const file = path16.join(nativeChangeDir(paths, name), "runtime", "schema-migration.json");
13543
13985
  await resolveContainedNativePath(paths.nativeRoot, file);
13544
13986
  try {
13545
- await fs12.lstat(file);
13987
+ await fs13.lstat(file);
13546
13988
  return true;
13547
13989
  } catch (error) {
13548
13990
  if (error.code === "ENOENT") return false;
@@ -13553,7 +13995,7 @@ async function hasPendingNativeCheckpointRecovery(paths, name) {
13553
13995
  const file = path16.join(nativeChangeDir(paths, name), "runtime", "checkpoint-journal.json");
13554
13996
  await resolveContainedNativePath(paths.nativeRoot, file);
13555
13997
  try {
13556
- await fs12.lstat(file);
13998
+ await fs13.lstat(file);
13557
13999
  return true;
13558
14000
  } catch (error) {
13559
14001
  if (error.code === "ENOENT") return false;
@@ -13574,13 +14016,13 @@ async function createNativeChangeLocked(options) {
13574
14016
  let createdChangeDir = false;
13575
14017
  try {
13576
14018
  try {
13577
- await fs12.mkdir(changeDir, { recursive: false });
14019
+ await fs13.mkdir(changeDir, { recursive: false });
13578
14020
  createdChangeDir = true;
13579
14021
  } catch (error) {
13580
14022
  if (error.code === "ENOENT") {
13581
- await fs12.mkdir(options.paths.changesDir, { recursive: true });
14023
+ await fs13.mkdir(options.paths.changesDir, { recursive: true });
13582
14024
  try {
13583
- await fs12.mkdir(changeDir, { recursive: false });
14025
+ await fs13.mkdir(changeDir, { recursive: false });
13584
14026
  createdChangeDir = true;
13585
14027
  } catch (retryError) {
13586
14028
  if (retryError.code === "EEXIST") {
@@ -13617,13 +14059,23 @@ async function createNativeChangeLocked(options) {
13617
14059
  run_id: null
13618
14060
  };
13619
14061
  await Promise.all([
13620
- fs12.mkdir(path16.join(changeDir, "specs"), { recursive: true }),
13621
- fs12.mkdir(path16.join(changeDir, "runtime", "checkpoints"), { recursive: true }),
14062
+ fs13.mkdir(path16.join(changeDir, "specs"), { recursive: true }),
14063
+ fs13.mkdir(path16.join(changeDir, "runtime", "checkpoints"), { recursive: true }),
13622
14064
  atomicWriteText(path16.join(changeDir, "brief.md"), NATIVE_BRIEF_TEMPLATE)
13623
14065
  ]);
14066
+ const projectConfig = await readProjectConfig(options.paths.projectRoot);
14067
+ const snapshot2 = projectConfig?.native.snapshot ?? DEFAULT_NATIVE_SNAPSHOT_CONFIG;
13624
14068
  const baseline = await createNativeContentSnapshot(options.paths, {
13625
14069
  now: options.now,
13626
- origin: "change-created"
14070
+ origin: "change-created",
14071
+ policy: snapshot2,
14072
+ limits: {
14073
+ maxFiles: snapshot2.max_files,
14074
+ maxFileBytes: snapshot2.max_total_bytes,
14075
+ maxTotalBytes: snapshot2.max_total_bytes,
14076
+ maxDurationMs: snapshot2.max_duration_ms
14077
+ },
14078
+ deadlineMs: snapshot2.max_duration_ms
13627
14079
  });
13628
14080
  if (!baseline.complete) {
13629
14081
  const health = inspectNativeContentSnapshotHealth(baseline);
@@ -13638,7 +14090,9 @@ async function createNativeChangeLocked(options) {
13638
14090
  baseline.omittedCount,
13639
14091
  omittedByReason,
13640
14092
  health.samplePaths,
13641
- health.sampleTruncated
14093
+ health.sampleTruncated,
14094
+ baseline.limits,
14095
+ baseline.policy?.hash ?? null
13642
14096
  );
13643
14097
  }
13644
14098
  await writeNativeBaselineManifest(options.paths, state.name, baseline);
@@ -13651,7 +14105,7 @@ async function createNativeChangeLocked(options) {
13651
14105
  });
13652
14106
  return state;
13653
14107
  } catch (error) {
13654
- if (createdChangeDir) await fs12.rm(changeDir, { recursive: true, force: true });
14108
+ if (createdChangeDir) await fs13.rm(changeDir, { recursive: true, force: true });
13655
14109
  throw error;
13656
14110
  }
13657
14111
  }
@@ -13701,7 +14155,7 @@ async function createNativeChangeFile(paths, state) {
13701
14155
  const file = path16.join(nativeChangeDir(paths, state.name), NATIVE_CHANGE_STATE_FILE);
13702
14156
  await resolveContainedNativePath(paths.nativeRoot, file);
13703
14157
  try {
13704
- await fs12.access(file);
14158
+ await fs13.access(file);
13705
14159
  throw new Error(`Native change state already exists: ${state.name}`);
13706
14160
  } catch (error) {
13707
14161
  if (error.code !== "ENOENT") throw error;
@@ -13799,12 +14253,12 @@ async function readContainedFile(root, relativeRef) {
13799
14253
  const target = path17.resolve(root, ...relativeRef.split(/[\\/]/u));
13800
14254
  if (!isInsidePath(root, target))
13801
14255
  throw new Error(`Artifact escapes Native change: ${relativeRef}`);
13802
- const realRoot = await fs13.realpath(root);
13803
- const realTarget = await fs13.realpath(target);
14256
+ const realRoot = await fs14.realpath(root);
14257
+ const realTarget = await fs14.realpath(target);
13804
14258
  if (!isInsidePath(realRoot, realTarget)) {
13805
14259
  throw new Error(`Artifact symlink escapes Native change: ${relativeRef}`);
13806
14260
  }
13807
- if (!(await fs13.stat(realTarget)).isFile())
14261
+ if (!(await fs14.stat(realTarget)).isFile())
13808
14262
  throw new Error(`Artifact is not a file: ${relativeRef}`);
13809
14263
  return realTarget;
13810
14264
  }
@@ -13946,7 +14400,7 @@ async function resolveNativeArtifactFile(changeDir, relativeRef) {
13946
14400
  }
13947
14401
 
13948
14402
  // domains/comet-native/native-archive-inspection.ts
13949
- import { promises as fs17 } from "node:fs";
14403
+ import { promises as fs18 } from "node:fs";
13950
14404
  import path29 from "node:path";
13951
14405
 
13952
14406
  // domains/comet-native/native-archive-preflight.ts
@@ -14536,7 +14990,7 @@ function buildNativeConflictRadar(input) {
14536
14990
 
14537
14991
  // domains/comet-native/native-evidence-storage.ts
14538
14992
  import { createHash as createHash12 } from "node:crypto";
14539
- import { promises as fs14 } from "node:fs";
14993
+ import { promises as fs15 } from "node:fs";
14540
14994
  import path22 from "node:path";
14541
14995
 
14542
14996
  // domains/comet-native/native-verification-scope.ts
@@ -14725,8 +15179,10 @@ function snapshotProjection(manifest) {
14725
15179
  maxFiles: parsed.limits.maxFiles,
14726
15180
  maxFileBytes: parsed.limits.maxFileBytes,
14727
15181
  maxTotalBytes: parsed.limits.maxTotalBytes,
14728
- maxManifestBytes: parsed.limits.maxManifestBytes
15182
+ maxManifestBytes: parsed.limits.maxManifestBytes,
15183
+ ...parsed.limits.maxDurationMs === void 0 ? {} : { maxDurationMs: parsed.limits.maxDurationMs }
14729
15184
  },
15185
+ ...parsed.policy ? { policy: parsed.policy } : {},
14730
15186
  entries,
14731
15187
  omitted,
14732
15188
  omittedCount: parsed.omittedCount,
@@ -15204,7 +15660,7 @@ function parseNativeSnapshotProjection(value, expectedHash) {
15204
15660
  exactScopeKeys(
15205
15661
  root,
15206
15662
  ["schema", "origin", "complete", "limits", "entries", "omitted", "omittedCount"],
15207
- ["capture", "omissionOverflow"],
15663
+ ["capture", "omissionOverflow", "policy"],
15208
15664
  "Native snapshot projection"
15209
15665
  );
15210
15666
  if (root.schema !== NATIVE_SNAPSHOT_PROJECTION_SCHEMA) {
@@ -15217,6 +15673,7 @@ function parseNativeSnapshotProjection(value, expectedHash) {
15217
15673
  createdAt: "1970-01-01T00:00:00.000Z",
15218
15674
  complete: root.complete,
15219
15675
  limits: root.limits,
15676
+ ...root.policy === void 0 ? {} : { policy: root.policy },
15220
15677
  entries: root.entries,
15221
15678
  omitted: root.omitted,
15222
15679
  omittedCount: root.omittedCount,
@@ -16040,25 +16497,30 @@ function isInside5(parent, target) {
16040
16497
  return relative === "" || !path22.isAbsolute(relative) && relative !== ".." && !relative.startsWith(`..${path22.sep}`);
16041
16498
  }
16042
16499
  function sameDirectoryIdentity5(identity, stat) {
16043
- if (identity.dev !== 0 || identity.ino !== 0 || stat.dev !== 0 || stat.ino !== 0) {
16044
- return identity.dev === stat.dev && identity.ino === stat.ino;
16045
- }
16046
- return identity.birthtimeMs === stat.birthtimeMs;
16500
+ return sameFileObject(
16501
+ { ...identity, birthtime: identity.birthtimeMs },
16502
+ {
16503
+ ...stat,
16504
+ birthtime: stat.birthtimeMs
16505
+ }
16506
+ );
16047
16507
  }
16048
16508
  function sameFileIdentity6(left, right) {
16049
- if (left.dev !== 0 || left.ino !== 0 || right.dev !== 0 || right.ino !== 0) {
16050
- return left.dev === right.dev && left.ino === right.ino;
16509
+ const leftObject = { ...left, birthtime: left.birthtimeMs };
16510
+ const rightObject = { ...right, birthtime: right.birthtimeMs };
16511
+ if (hasComparableFileObject(leftObject, rightObject)) {
16512
+ return sameFileObject(leftObject, rightObject);
16051
16513
  }
16052
- return left.birthtimeMs === right.birthtimeMs && left.ctimeMs === right.ctimeMs && left.size === right.size;
16514
+ return sameFileObject(leftObject, rightObject) && left.birthtimeMs === right.birthtimeMs && left.ctimeMs === right.ctimeMs && left.size === right.size;
16053
16515
  }
16054
16516
  async function captureDirectoryIdentity3(directory) {
16055
- const stat = await fs14.lstat(directory);
16517
+ const stat = await fs15.lstat(directory);
16056
16518
  if (!stat.isDirectory() || stat.isSymbolicLink()) {
16057
16519
  throw new Error(`Native evidence parent must be a real directory: ${directory}`);
16058
16520
  }
16059
16521
  return {
16060
16522
  path: directory,
16061
- realPath: await fs14.realpath(directory),
16523
+ realPath: await fs15.realpath(directory),
16062
16524
  dev: stat.dev,
16063
16525
  ino: stat.ino,
16064
16526
  birthtimeMs: stat.birthtimeMs
@@ -16084,8 +16546,8 @@ async function captureDirectoryChain4(root, directory) {
16084
16546
  }
16085
16547
  async function verifyDirectoryChain5(chain) {
16086
16548
  for (const identity of chain) {
16087
- const stat = await fs14.lstat(identity.path);
16088
- if (!stat.isDirectory() || stat.isSymbolicLink() || !sameDirectoryIdentity5(identity, stat) || await fs14.realpath(identity.path) !== identity.realPath) {
16549
+ const stat = await fs15.lstat(identity.path);
16550
+ if (!stat.isDirectory() || stat.isSymbolicLink() || !sameDirectoryIdentity5(identity, stat) || await fs15.realpath(identity.path) !== identity.realPath) {
16089
16551
  throw new Error(`Native evidence parent changed while reading: ${identity.path}`);
16090
16552
  }
16091
16553
  }
@@ -16093,20 +16555,20 @@ async function verifyDirectoryChain5(chain) {
16093
16555
  async function readBoundedEvidenceJson(file, changeRoot, hooks = {}) {
16094
16556
  const chain = await captureDirectoryChain4(changeRoot, path22.dirname(file));
16095
16557
  await hooks.afterParentChainCaptured?.();
16096
- const lexical = await fs14.lstat(file);
16558
+ const lexical = await fs15.lstat(file);
16097
16559
  if (!lexical.isFile() || lexical.isSymbolicLink()) {
16098
16560
  throw new Error("Native evidence document must be a regular file");
16099
16561
  }
16100
- const realPath = await fs14.realpath(file);
16562
+ const realPath = await fs15.realpath(file);
16101
16563
  if (!isInside5(chain[0].realPath, realPath)) {
16102
16564
  throw new Error("Native evidence document resolves outside its change");
16103
16565
  }
16104
- const handle = await fs14.open(file, "r");
16566
+ const handle = await fs15.open(file, "r");
16105
16567
  try {
16106
16568
  const [opened, pathAfterOpen, realPathAfterOpen] = await Promise.all([
16107
16569
  handle.stat(),
16108
- fs14.lstat(file),
16109
- fs14.realpath(file)
16570
+ fs15.lstat(file),
16571
+ fs15.realpath(file)
16110
16572
  ]);
16111
16573
  await verifyDirectoryChain5(chain);
16112
16574
  if (!opened.isFile() || !pathAfterOpen.isFile() || pathAfterOpen.isSymbolicLink() || realPathAfterOpen !== realPath || !sameFileIdentity6(opened, lexical) || !sameFileIdentity6(opened, pathAfterOpen)) {
@@ -16136,8 +16598,8 @@ async function readBoundedEvidenceJson(file, changeRoot, hooks = {}) {
16136
16598
  await hooks.beforeFinalCheck?.();
16137
16599
  const [afterHandle, afterPath, afterRealPath] = await Promise.all([
16138
16600
  handle.stat(),
16139
- fs14.lstat(file),
16140
- fs14.realpath(file)
16601
+ fs15.lstat(file),
16602
+ fs15.realpath(file)
16141
16603
  ]);
16142
16604
  await verifyDirectoryChain5(chain);
16143
16605
  if (!afterPath.isFile() || afterPath.isSymbolicLink() || afterRealPath !== realPath || !sameFileIdentity6(opened, afterHandle) || !sameFileIdentity6(opened, afterPath)) {
@@ -16229,7 +16691,7 @@ function assertEvidenceDocumentBudget(value) {
16229
16691
  function serializedEvidenceBytes2(value) {
16230
16692
  return Buffer.byteLength(JSON.stringify(value, null, 2) + "\n", "utf8");
16231
16693
  }
16232
- function parseSnapshot(value, expectedHash) {
16694
+ function parseSnapshot2(value, expectedHash) {
16233
16695
  return parseNativeSnapshotProjection(value, expectedHash);
16234
16696
  }
16235
16697
  function parseScope(value, expectedHash) {
@@ -16305,10 +16767,10 @@ async function readNativeImplementationScopeBundle(paths, name, ref, hooks) {
16305
16767
  const currentHash = parseEvidenceRef(scope.currentProjectionRef, "snapshots");
16306
16768
  const [baseline, current] = await Promise.all([
16307
16769
  readEvidenceDocument({ paths, name, kind: "snapshots", hash: baselineHash }).then(
16308
- (value) => parseSnapshot(value, baselineHash)
16770
+ (value) => parseSnapshot2(value, baselineHash)
16309
16771
  ),
16310
16772
  readEvidenceDocument({ paths, name, kind: "snapshots", hash: currentHash }).then(
16311
- (value) => parseSnapshot(value, currentHash)
16773
+ (value) => parseSnapshot2(value, currentHash)
16312
16774
  )
16313
16775
  ]);
16314
16776
  return rebuildNativeImplementationScopeBundle({ baseline, current, scope });
@@ -16433,7 +16895,7 @@ async function inspectNativeChangeConflicts(paths, name) {
16433
16895
 
16434
16896
  // domains/comet-native/native-transition-journal.ts
16435
16897
  import { randomUUID as randomUUID3 } from "crypto";
16436
- import { promises as fs15 } from "fs";
16898
+ import { promises as fs16 } from "fs";
16437
16899
  import path23 from "path";
16438
16900
  import { isDeepStrictEqual } from "util";
16439
16901
 
@@ -17891,7 +18353,7 @@ async function continueNativeTransitionLocked(paths, name, hooks) {
17891
18353
  `Native transition journal changed while continuing ${journal.change}; it was not removed`
17892
18354
  );
17893
18355
  }
17894
- await fs15.rm(nativeTransitionJournalFile(paths, name), { force: true });
18356
+ await fs16.rm(nativeTransitionJournalFile(paths, name), { force: true });
17895
18357
  return journal.nextState;
17896
18358
  }
17897
18359
  async function continueNativeTransition(paths, name, hooks) {
@@ -18355,9 +18817,16 @@ function canonicalEvidencePayload(entries) {
18355
18817
  const validated = validateEvidenceEntries([...entries]).map((entry2) => ({ ...entry2, evidence_refs: [...entry2.evidence_refs].sort() })).sort((left, right) => left.acceptance_id.localeCompare(right.acceptance_id));
18356
18818
  return JSON.stringify(validated, null, 2);
18357
18819
  }
18820
+ function serializeNativeVerificationMachineBlock(entries) {
18821
+ return [
18822
+ NATIVE_ACCEPTANCE_EVIDENCE_START_MARKER,
18823
+ canonicalEvidencePayload(entries),
18824
+ NATIVE_ACCEPTANCE_EVIDENCE_END_MARKER
18825
+ ].join("\n");
18826
+ }
18358
18827
 
18359
18828
  // domains/comet-native/native-check-receipt-storage.ts
18360
- import { constants as fsConstants3, promises as fs16 } from "node:fs";
18829
+ import { constants as fsConstants4, promises as fs17 } from "node:fs";
18361
18830
  import path26 from "node:path";
18362
18831
 
18363
18832
  // domains/comet-native/native-check-receipt-model.ts
@@ -18706,16 +19175,21 @@ var HASH_PATTERN11 = /^[a-f0-9]{64}$/u;
18706
19175
  var RECEIPT_REF_PATTERN = /^runtime\/evidence\/check-receipts\/([a-f0-9]{64})\.json$/u;
18707
19176
  var MAX_NATIVE_CHECK_RECEIPT_BYTES = 512 * 1024;
18708
19177
  function sameDirectoryIdentity6(identity, stat) {
18709
- if (identity.dev !== 0 || identity.ino !== 0 || stat.dev !== 0 || stat.ino !== 0) {
18710
- return identity.dev === stat.dev && identity.ino === stat.ino;
18711
- }
18712
- return identity.birthtimeMs === stat.birthtimeMs;
19178
+ return sameFileObject(
19179
+ { ...identity, birthtime: identity.birthtimeMs },
19180
+ {
19181
+ ...stat,
19182
+ birthtime: stat.birthtimeMs
19183
+ }
19184
+ );
18713
19185
  }
18714
19186
  function sameFileIdentity7(left, right) {
18715
- if (left.dev !== 0 || left.ino !== 0 || right.dev !== 0 || right.ino !== 0) {
18716
- return left.dev === right.dev && left.ino === right.ino;
19187
+ const leftObject = { ...left, birthtime: left.birthtimeMs };
19188
+ const rightObject = { ...right, birthtime: right.birthtimeMs };
19189
+ if (hasComparableFileObject(leftObject, rightObject)) {
19190
+ return sameFileObject(leftObject, rightObject);
18717
19191
  }
18718
- return left.birthtimeMs === right.birthtimeMs && left.ctimeMs === right.ctimeMs && left.size === right.size;
19192
+ return sameFileObject(leftObject, rightObject) && left.birthtimeMs === right.birthtimeMs && left.ctimeMs === right.ctimeMs && left.size === right.size;
18719
19193
  }
18720
19194
  async function captureDirectoryChain5(root, directory) {
18721
19195
  const lexicalRoot = path26.resolve(root);
@@ -18730,11 +19204,11 @@ async function captureDirectoryChain5(root, directory) {
18730
19204
  ...path26.relative(lexicalRoot, lexicalDirectory).split(path26.sep).filter(Boolean)
18731
19205
  ]) {
18732
19206
  if (segment) cursor = path26.join(cursor, segment);
18733
- const stat = await fs16.lstat(cursor);
19207
+ const stat = await fs17.lstat(cursor);
18734
19208
  if (!stat.isDirectory() || stat.isSymbolicLink()) {
18735
19209
  throw new Error(`Native check receipt parent must be a real directory: ${cursor}`);
18736
19210
  }
18737
- const realPath = await fs16.realpath(cursor);
19211
+ const realPath = await fs17.realpath(cursor);
18738
19212
  if (chain.length > 0 && !isInsidePath(chain[0].realPath, realPath)) {
18739
19213
  throw new Error("Native check receipt parent resolves outside the Native root");
18740
19214
  }
@@ -18750,8 +19224,8 @@ async function captureDirectoryChain5(root, directory) {
18750
19224
  }
18751
19225
  async function verifyDirectoryChain6(chain) {
18752
19226
  for (const identity of chain) {
18753
- const stat = await fs16.lstat(identity.path);
18754
- if (!stat.isDirectory() || stat.isSymbolicLink() || !sameDirectoryIdentity6(identity, stat) || await fs16.realpath(identity.path) !== identity.realPath) {
19227
+ const stat = await fs17.lstat(identity.path);
19228
+ if (!stat.isDirectory() || stat.isSymbolicLink() || !sameDirectoryIdentity6(identity, stat) || await fs17.realpath(identity.path) !== identity.realPath) {
18755
19229
  throw new Error(`Native check receipt parent changed while reading: ${identity.path}`);
18756
19230
  }
18757
19231
  }
@@ -18770,7 +19244,7 @@ function receiptFile(paths, name, hash6) {
18770
19244
  }
18771
19245
  async function readBoundedReceipt(file, changeRoot, nativeRoot) {
18772
19246
  const chain = await captureDirectoryChain5(nativeRoot, path26.dirname(file));
18773
- const before = await fs16.lstat(file);
19247
+ const before = await fs17.lstat(file);
18774
19248
  if (!before.isFile() || before.isSymbolicLink()) {
18775
19249
  throw new Error("Native check receipt must be a regular file");
18776
19250
  }
@@ -18778,14 +19252,14 @@ async function readBoundedReceipt(file, changeRoot, nativeRoot) {
18778
19252
  throw new Error(`Native check receipt exceeds ${MAX_NATIVE_CHECK_RECEIPT_BYTES} bytes`);
18779
19253
  }
18780
19254
  const [realChangeRoot, beforeRealPath] = await Promise.all([
18781
- fs16.realpath(changeRoot),
18782
- fs16.realpath(file)
19255
+ fs17.realpath(changeRoot),
19256
+ fs17.realpath(file)
18783
19257
  ]);
18784
19258
  if (!isInsidePath(realChangeRoot, beforeRealPath)) {
18785
19259
  throw new Error("Native check receipt resolves outside its change");
18786
19260
  }
18787
- const openFlags = process.platform === "win32" ? "r" : fsConstants3.O_RDONLY | fsConstants3.O_NOFOLLOW | fsConstants3.O_NONBLOCK;
18788
- const handle = await fs16.open(file, openFlags).catch((error) => {
19261
+ const openFlags = process.platform === "win32" ? "r" : fsConstants4.O_RDONLY | fsConstants4.O_NOFOLLOW | fsConstants4.O_NONBLOCK;
19262
+ const handle = await fs17.open(file, openFlags).catch((error) => {
18789
19263
  if (error.code === "ELOOP" || error.code === "ENXIO") {
18790
19264
  throw new Error("Native check receipt became unsafe while opening");
18791
19265
  }
@@ -18794,8 +19268,8 @@ async function readBoundedReceipt(file, changeRoot, nativeRoot) {
18794
19268
  try {
18795
19269
  const [opened, pathAfterOpen, realPathAfterOpen] = await Promise.all([
18796
19270
  handle.stat(),
18797
- fs16.lstat(file),
18798
- fs16.realpath(file)
19271
+ fs17.lstat(file),
19272
+ fs17.realpath(file)
18799
19273
  ]);
18800
19274
  await verifyDirectoryChain6(chain);
18801
19275
  if (!opened.isFile() || !pathAfterOpen.isFile() || pathAfterOpen.isSymbolicLink() || realPathAfterOpen !== beforeRealPath || !sameFileIdentity7(before, opened) || !sameFileIdentity7(opened, pathAfterOpen)) {
@@ -18816,8 +19290,8 @@ async function readBoundedReceipt(file, changeRoot, nativeRoot) {
18816
19290
  }
18817
19291
  const [afterHandle, afterPath, afterRealPath] = await Promise.all([
18818
19292
  handle.stat(),
18819
- fs16.lstat(file),
18820
- fs16.realpath(file)
19293
+ fs17.lstat(file),
19294
+ fs17.realpath(file)
18821
19295
  ]);
18822
19296
  await verifyDirectoryChain6(chain);
18823
19297
  if (!afterPath.isFile() || afterPath.isSymbolicLink() || afterRealPath !== beforeRealPath || !isInsidePath(realChangeRoot, afterRealPath) || !sameFileIdentity7(opened, afterHandle) || !sameFileIdentity7(opened, afterPath)) {
@@ -19067,6 +19541,7 @@ function projectionManifest(projection) {
19067
19541
  createdAt: "1970-01-01T00:00:00.000Z",
19068
19542
  complete: projection.complete,
19069
19543
  limits: projection.limits,
19544
+ ...projection.policy ? { policy: projection.policy } : {},
19070
19545
  entries: projection.entries,
19071
19546
  omitted: projection.omitted,
19072
19547
  omittedCount: projection.omittedCount,
@@ -19081,12 +19556,13 @@ function nativeRootRef2(paths) {
19081
19556
  return value;
19082
19557
  }
19083
19558
  async function currentProjectionHash(options) {
19084
- const current = await createNativeContentSnapshot(options.paths, {
19559
+ const baseline = projectionManifest(options.bundle.baseline);
19560
+ const current = await createNativeCurrentContentSnapshot(options.paths, baseline, {
19085
19561
  origin: "explicit",
19086
19562
  now: options.now
19087
19563
  });
19088
19564
  return buildNativeImplementationScopeBundle({
19089
- baseline: projectionManifest(options.bundle.baseline),
19565
+ baseline,
19090
19566
  current,
19091
19567
  contractHash: options.bundle.scope.contractHash,
19092
19568
  declaredArtifacts: options.bundle.scope.declaredArtifacts,
@@ -19350,7 +19826,7 @@ function archiveTargetRef(name, now) {
19350
19826
  }
19351
19827
  async function exists2(target) {
19352
19828
  try {
19353
- await fs17.lstat(target);
19829
+ await fs18.lstat(target);
19354
19830
  return true;
19355
19831
  } catch (error) {
19356
19832
  if (error.code === "ENOENT") return false;
@@ -19432,7 +19908,7 @@ async function inspectNativeArchivePreflight(options) {
19432
19908
  }
19433
19909
 
19434
19910
  // domains/comet-native/native-archive-content.ts
19435
- import { promises as fs18 } from "node:fs";
19911
+ import { promises as fs19 } from "node:fs";
19436
19912
  import path30 from "node:path";
19437
19913
  var TREE_HASH_TAG = "comet.native.archive-tree.v1";
19438
19914
  var NATIVE_ARCHIVE_CONTENT_LIMITS = {
@@ -19492,7 +19968,7 @@ async function walkArchiveTree(root, directory, entries, budget, limits, depth)
19492
19968
  await protectedDirectory.verify();
19493
19969
  const target = path30.join(directory, child.name);
19494
19970
  const ref = path30.relative(root, target).replaceAll("\\", "/");
19495
- const stat = await fs18.lstat(target);
19971
+ const stat = await fs19.lstat(target);
19496
19972
  if (child.isSymbolicLink() || stat.isSymbolicLink()) {
19497
19973
  throw new Error(`Native Archive content must not contain symlinks or junctions: ${ref}`);
19498
19974
  }
@@ -19539,7 +20015,7 @@ async function walkArchiveTree(root, directory, entries, budget, limits, depth)
19539
20015
  async function hashNativeArchiveTree(directory, requestedLimits = {}) {
19540
20016
  const limits = normalizedLimits(requestedLimits);
19541
20017
  directory = path30.resolve(directory);
19542
- const stat = await fs18.lstat(directory);
20018
+ const stat = await fs19.lstat(directory);
19543
20019
  if (!stat.isDirectory() || stat.isSymbolicLink()) {
19544
20020
  throw new Error(`Native Archive move source must be a real directory: ${directory}`);
19545
20021
  }
@@ -19557,7 +20033,7 @@ async function inspectNativeArchiveContent(target, requestedLimits = {}) {
19557
20033
  target = path30.resolve(target);
19558
20034
  let stat;
19559
20035
  try {
19560
- stat = await fs18.lstat(target);
20036
+ stat = await fs19.lstat(target);
19561
20037
  } catch (error) {
19562
20038
  if (error.code === "ENOENT") return null;
19563
20039
  throw error;
@@ -19581,7 +20057,7 @@ async function inspectNativeArchiveContent(target, requestedLimits = {}) {
19581
20057
  }
19582
20058
 
19583
20059
  // domains/comet-native/native-archive-transaction.ts
19584
- import { promises as fs19 } from "node:fs";
20060
+ import { promises as fs20 } from "node:fs";
19585
20061
  import path31 from "node:path";
19586
20062
  var NATIVE_ARCHIVE_COPY_MAX_BYTES = 16 * 1024 * 1024;
19587
20063
  var NATIVE_ARCHIVE_JOURNAL_MAX_BYTES = 256 * 1024;
@@ -19615,8 +20091,8 @@ async function assertContent(options) {
19615
20091
  async function createNativeArchiveTransactionV2(paths, journal) {
19616
20092
  const validated = parseNativeArchiveTransactionJournalV2(journal);
19617
20093
  const tx = await resolveNativeTransactionPaths(paths, validated.id);
19618
- await fs19.mkdir(tx.staged, { recursive: true });
19619
- await fs19.mkdir(tx.backups, { recursive: true });
20094
+ await fs20.mkdir(tx.staged, { recursive: true });
20095
+ await fs20.mkdir(tx.backups, { recursive: true });
19620
20096
  await atomicWriteJson(tx.journal, validated, { containedRoot: paths.nativeRoot });
19621
20097
  await appendNativeTransactionEvent(paths, validated.id, "prepared");
19622
20098
  }
@@ -19655,18 +20131,21 @@ function fileVersion(stat) {
19655
20131
  size: stat.size
19656
20132
  };
19657
20133
  }
19658
- function sameFileObject(expected, actual) {
19659
- if (expected.dev !== 0 || expected.ino !== 0 || actual.dev !== 0 || actual.ino !== 0) {
19660
- return expected.dev === actual.dev && expected.ino === actual.ino && expected.birthtimeMs === actual.birthtimeMs;
19661
- }
19662
- return expected.birthtimeMs === actual.birthtimeMs;
20134
+ function sameFileObject2(expected, actual) {
20135
+ return sameFileObject(
20136
+ { ...expected, birthtime: expected.birthtimeMs },
20137
+ {
20138
+ ...actual,
20139
+ birthtime: actual.birthtimeMs
20140
+ }
20141
+ ) && expected.birthtimeMs === actual.birthtimeMs;
19663
20142
  }
19664
20143
  function sameFileVersion(expected, actual) {
19665
- return sameFileObject(expected, actual) && expected.birthtimeMs === actual.birthtimeMs && expected.ctimeMs === actual.ctimeMs && expected.mtimeMs === actual.mtimeMs && expected.size === actual.size;
20144
+ return sameFileObject2(expected, actual) && expected.birthtimeMs === actual.birthtimeMs && expected.ctimeMs === actual.ctimeMs && expected.mtimeMs === actual.mtimeMs && expected.size === actual.size;
19666
20145
  }
19667
20146
  async function pathExists(target) {
19668
20147
  try {
19669
- await fs19.lstat(target);
20148
+ await fs20.lstat(target);
19670
20149
  return true;
19671
20150
  } catch (error) {
19672
20151
  if (error.code === "ENOENT") return false;
@@ -19674,7 +20153,7 @@ async function pathExists(target) {
19674
20153
  }
19675
20154
  }
19676
20155
  async function captureStableArchiveFile(options) {
19677
- const before = await fs19.lstat(options.file);
20156
+ const before = await fs20.lstat(options.file);
19678
20157
  if (!before.isFile() || before.isSymbolicLink()) {
19679
20158
  throw new Error(`${options.label} must be a regular file`);
19680
20159
  }
@@ -19685,7 +20164,7 @@ async function captureStableArchiveFile(options) {
19685
20164
  maxBytes: NATIVE_ARCHIVE_COPY_MAX_BYTES,
19686
20165
  label: options.label
19687
20166
  });
19688
- const after = await fs19.lstat(options.file);
20167
+ const after = await fs20.lstat(options.file);
19689
20168
  if (!after.isFile() || after.isSymbolicLink() || !sameFileVersion(beforeVersion, fileVersion(after)) || snapshot2.hash !== options.expectedHash) {
19690
20169
  throw new Error(`${options.label} content changed or object identity changed while binding`);
19691
20170
  }
@@ -19763,7 +20242,7 @@ async function readCasRecord(options) {
19763
20242
  async function persistCasRecord(options) {
19764
20243
  const existing = await readCasRecord(options);
19765
20244
  if (existing) {
19766
- if (!sameFileObject(existing.identity, options.identity)) {
20245
+ if (!sameFileObject2(existing.identity, options.identity)) {
19767
20246
  throw new Error(
19768
20247
  `Archive CAS ${options.operation.id} ${options.role} object identity changed`
19769
20248
  );
@@ -19791,7 +20270,7 @@ async function persistCasRecord(options) {
19791
20270
  if (error.code !== "EEXIST") throw error;
19792
20271
  }
19793
20272
  const persisted = await readCasRecord(options);
19794
- if (!persisted || !sameFileObject(persisted.identity, options.identity)) {
20273
+ if (!persisted || !sameFileObject2(persisted.identity, options.identity)) {
19795
20274
  throw new Error(`Archive CAS ${options.operation.id} ${options.role} record changed`);
19796
20275
  }
19797
20276
  return persisted;
@@ -19803,7 +20282,7 @@ async function validateFileAgainstCasRecord(options) {
19803
20282
  expectedHash: options.record.hash,
19804
20283
  label: options.label
19805
20284
  });
19806
- if (!sameFileObject(options.record.identity, current.identity)) {
20285
+ if (!sameFileObject2(options.record.identity, current.identity)) {
19807
20286
  throw new Error(`${options.label} object identity changed`);
19808
20287
  }
19809
20288
  }
@@ -19840,20 +20319,20 @@ async function restoreUnexpectedQuarantine(options) {
19840
20319
  });
19841
20320
  await guard.verify();
19842
20321
  try {
19843
- await fs19.link(options.quarantine, options.target);
20322
+ await fs20.link(options.quarantine, options.target);
19844
20323
  } catch (error) {
19845
20324
  if (error.code === "EEXIST") return;
19846
20325
  throw error;
19847
20326
  }
19848
20327
  await guard.verify();
19849
20328
  const [quarantine, target] = await Promise.all([
19850
- fs19.lstat(options.quarantine),
19851
- fs19.lstat(options.target)
20329
+ fs20.lstat(options.quarantine),
20330
+ fs20.lstat(options.target)
19852
20331
  ]);
19853
- if (!sameFileObject(fileObjectIdentity(quarantine), fileObjectIdentity(target))) {
20332
+ if (!sameFileObject2(fileObjectIdentity(quarantine), fileObjectIdentity(target))) {
19854
20333
  throw new Error(`Archive CAS ${options.operation.id} could not restore quarantined content`);
19855
20334
  }
19856
- await fs19.unlink(options.quarantine);
20335
+ await fs20.unlink(options.quarantine);
19857
20336
  await guard.verify();
19858
20337
  }
19859
20338
  async function quarantineBoundTarget(options) {
@@ -19889,7 +20368,7 @@ async function quarantineBoundTarget(options) {
19889
20368
  label: `Archive ${options.phase} target ${options.operation.target}`
19890
20369
  });
19891
20370
  try {
19892
- await fs19.rename(options.target, options.quarantine);
20371
+ await fs20.rename(options.target, options.quarantine);
19893
20372
  await guard.verify();
19894
20373
  await validateFileAgainstCasRecord({
19895
20374
  paths: options.paths,
@@ -19921,7 +20400,7 @@ async function removeExactCasFile(options) {
19921
20400
  });
19922
20401
  await guard.verify();
19923
20402
  await validateFileAgainstCasRecord(options);
19924
- await fs19.unlink(options.file);
20403
+ await fs20.unlink(options.file);
19925
20404
  await guard.verify();
19926
20405
  }
19927
20406
  async function ensureBackup(paths, operation, hooks) {
@@ -20114,7 +20593,7 @@ async function ensureWriteInstalled(options) {
20114
20593
  expectedHash,
20115
20594
  label: `Archive write target ${options.operation.target}`
20116
20595
  });
20117
- if (!sameFileObject(candidateIdentity2.identity, targetIdentity2.identity)) {
20596
+ if (!sameFileObject2(candidateIdentity2.identity, targetIdentity2.identity)) {
20118
20597
  throw new Error(
20119
20598
  `Archive write target ${options.operation.target} is occupied by an external object`
20120
20599
  );
@@ -20169,7 +20648,7 @@ async function ensureWriteInstalled(options) {
20169
20648
  label: `Archive write candidate ${options.operation.target}`
20170
20649
  });
20171
20650
  try {
20172
- await fs19.link(candidate, options.target);
20651
+ await fs20.link(candidate, options.target);
20173
20652
  } catch (error) {
20174
20653
  if (error.code === "EEXIST") {
20175
20654
  throw new Error(
@@ -20187,7 +20666,7 @@ async function ensureWriteInstalled(options) {
20187
20666
  expectedHash,
20188
20667
  label: `Archive write target ${options.operation.target}`
20189
20668
  });
20190
- if (!sameFileObject(candidateIdentity.identity, targetIdentity.identity)) {
20669
+ if (!sameFileObject2(candidateIdentity.identity, targetIdentity.identity)) {
20191
20670
  throw new Error(`Archive write target ${options.operation.target} changed during install`);
20192
20671
  }
20193
20672
  record8 = await persistCasRecord({
@@ -20273,8 +20752,8 @@ async function applyMove(paths, operation) {
20273
20752
  `Archive transaction move ${operation.id} content changed: source=${contentDescription(sourceContent)}, target=${contentDescription(targetContent)}`
20274
20753
  );
20275
20754
  }
20276
- await fs19.mkdir(path31.dirname(target), { recursive: true });
20277
- await fs19.rename(source, target);
20755
+ await fs20.mkdir(path31.dirname(target), { recursive: true });
20756
+ await fs20.rename(source, target);
20278
20757
  await assertContent({
20279
20758
  target,
20280
20759
  expectedHash: operation.expectedSourceHash,
@@ -20476,7 +20955,7 @@ async function installOriginalTarget(options) {
20476
20955
  label: `Archive rollback original quarantine ${options.operation.target}`
20477
20956
  });
20478
20957
  try {
20479
- await fs19.link(options.quarantine, options.target);
20958
+ await fs20.link(options.quarantine, options.target);
20480
20959
  } catch (error) {
20481
20960
  if (error.code === "EEXIST") {
20482
20961
  throw new Error(
@@ -20556,7 +21035,7 @@ async function rollbackWriteOrRemove(paths, journal, operation, hooks) {
20556
21035
  expectedHash: operation.stagedHash,
20557
21036
  label: `Archive rollback target ${operation.target}`
20558
21037
  });
20559
- if (sameFileObject(candidate.identity, current.identity)) {
21038
+ if (sameFileObject2(candidate.identity, current.identity)) {
20560
21039
  postRecord = await persistCasRecord({
20561
21040
  paths,
20562
21041
  file: cas.postRecord,
@@ -20670,8 +21149,8 @@ async function rollbackMove(paths, operation) {
20670
21149
  `Archive rollback move ${operation.id} content changed: source=${contentDescription(sourceContent)}, target=${contentDescription(targetContent)}`
20671
21150
  );
20672
21151
  }
20673
- await fs19.mkdir(path31.dirname(source), { recursive: true });
20674
- await fs19.rename(target, source);
21152
+ await fs20.mkdir(path31.dirname(source), { recursive: true });
21153
+ await fs20.rename(target, source);
20675
21154
  }
20676
21155
  async function rollbackNativeArchiveTransactionV2(paths, journal, hooks) {
20677
21156
  const events = await readNativeTransactionEvents(paths, journal.id);
@@ -20769,7 +21248,7 @@ async function finalizeNativeArchiveTransactionV2(paths, journal, event) {
20769
21248
 
20770
21249
  // domains/comet-native/native-checkpoint-journal.ts
20771
21250
  import { randomUUID as randomUUID4 } from "crypto";
20772
- import { promises as fs20 } from "fs";
21251
+ import { promises as fs21 } from "fs";
20773
21252
 
20774
21253
  // domains/comet-native/native-checkpoint-storage.ts
20775
21254
  import path32 from "path";
@@ -21305,7 +21784,7 @@ async function continueNativeCheckpointLocked(paths, name, hooks) {
21305
21784
  await hooks?.afterStateWritten?.(journal);
21306
21785
  await writeNativeProgressCheckpoint(paths, journal.checkpoint);
21307
21786
  await hooks?.afterProgressWritten?.(journal);
21308
- await fs20.rm(nativeCheckpointJournalFile(paths, name), { force: true });
21787
+ await fs21.rm(nativeCheckpointJournalFile(paths, name), { force: true });
21309
21788
  return journal;
21310
21789
  }
21311
21790
  async function continueNativeCheckpoint(paths, name, hooks) {
@@ -21327,7 +21806,7 @@ async function settleNativeChangeJournalsLocked(paths, name) {
21327
21806
 
21328
21807
  // domains/comet-entry/current-selection.ts
21329
21808
  import { randomUUID as randomUUID5 } from "crypto";
21330
- import { promises as fs21 } from "fs";
21809
+ import { promises as fs22 } from "fs";
21331
21810
  import path33 from "path";
21332
21811
  var COMET_CURRENT_SELECTION_SCHEMA = "comet.selection.v2";
21333
21812
  var COMET_CURRENT_SELECTION_MAX_BYTES = 16 * 1024;
@@ -21390,16 +21869,11 @@ function parseSelection(source) {
21390
21869
  async function readCometCurrentSelection(projectRoot) {
21391
21870
  let source;
21392
21871
  try {
21393
- const stat = await fs21.lstat(cometCurrentSelectionFile(projectRoot));
21394
- if (stat.isSymbolicLink() || !stat.isFile()) {
21395
- throw new Error("current change selection must be a regular file");
21396
- }
21397
- if (stat.size > COMET_CURRENT_SELECTION_MAX_BYTES) {
21398
- throw new Error(
21399
- `current change selection exceeds ${COMET_CURRENT_SELECTION_MAX_BYTES} bytes`
21400
- );
21401
- }
21402
- source = await fs21.readFile(cometCurrentSelectionFile(projectRoot), "utf8");
21872
+ const file = cometCurrentSelectionFile(projectRoot);
21873
+ const { bytes } = await readFileRaceSafe(file, COMET_CURRENT_SELECTION_MAX_BYTES, {
21874
+ label: "current change selection"
21875
+ });
21876
+ source = bytes.toString("utf8");
21403
21877
  } catch (error) {
21404
21878
  if (error.code === "ENOENT") return { status: "missing" };
21405
21879
  throw new Error(
@@ -21415,18 +21889,18 @@ async function writeCometCurrentSelection(projectRoot, selection) {
21415
21889
  if (parsed.legacy) throw new Error("cannot write a legacy current change selection");
21416
21890
  const file = cometCurrentSelectionFile(projectRoot);
21417
21891
  const temporary = `${file}.${randomUUID5()}.tmp`;
21418
- await fs21.mkdir(path33.dirname(file), { recursive: true });
21892
+ await fs22.mkdir(path33.dirname(file), { recursive: true });
21419
21893
  try {
21420
- await fs21.writeFile(temporary, `${JSON.stringify(parsed.selection, null, 2)}
21894
+ await fs22.writeFile(temporary, `${JSON.stringify(parsed.selection, null, 2)}
21421
21895
  `, "utf8");
21422
- await fs21.rename(temporary, file);
21896
+ await fs22.rename(temporary, file);
21423
21897
  } catch (error) {
21424
- await fs21.rm(temporary, { force: true });
21898
+ await fs22.rm(temporary, { force: true });
21425
21899
  throw error;
21426
21900
  }
21427
21901
  }
21428
21902
  async function clearCometCurrentSelection(projectRoot) {
21429
- await fs21.rm(cometCurrentSelectionFile(projectRoot), { force: true });
21903
+ await fs22.rm(cometCurrentSelectionFile(projectRoot), { force: true });
21430
21904
  }
21431
21905
  async function clearCometCurrentSelectionIf(projectRoot, workflow, change) {
21432
21906
  const current = await readCometCurrentSelection(projectRoot);
@@ -21540,7 +22014,7 @@ function archiveTarget(paths, name, now) {
21540
22014
  }
21541
22015
  async function pathExists2(target) {
21542
22016
  try {
21543
- await fs22.access(target);
22017
+ await fs23.access(target);
21544
22018
  return true;
21545
22019
  } catch (error) {
21546
22020
  if (error.code === "ENOENT") return false;
@@ -21851,7 +22325,7 @@ async function recoverArchiveTransaction(options) {
21851
22325
  }
21852
22326
 
21853
22327
  // domains/comet-native/native-diagnostics.ts
21854
- import { promises as fs23 } from "fs";
22328
+ import { promises as fs24 } from "fs";
21855
22329
 
21856
22330
  // domains/comet-native/native-run-consistency.ts
21857
22331
  import path35 from "path";
@@ -21977,8 +22451,12 @@ async function inspectNativeRunConsistency(paths, state) {
21977
22451
  // domains/comet-native/native-continuation.ts
21978
22452
  var REPAIR_CODES = /^(?:run-|trajectory-|checkpoint-(?:missing|mismatch|invalid|progress-invalid)|transition-(?:incomplete|invalid))/u;
21979
22453
  function requiredPhaseInputs(state) {
21980
- if (state.phase === "shape") return ["summary"];
21981
- if (state.phase === "build") return ["summary", "artifact-or-no-code-reason"];
22454
+ if (state.phase === "shape") {
22455
+ return ["summary", "shared-understanding-confirmation"];
22456
+ }
22457
+ if (state.phase === "build") {
22458
+ return state.approval === "confirmed" ? ["summary", "artifact-or-no-code-reason"] : ["summary", "artifact-or-no-code-reason", "shared-understanding-confirmation"];
22459
+ }
21982
22460
  if (state.phase === "verify") return ["summary", "verification-result", "verification-report"];
21983
22461
  return [];
21984
22462
  }
@@ -22109,6 +22587,7 @@ function nativeContinuation(options) {
22109
22587
  requiredInputs: options.archiveReady ? [] : ["archive-readiness"]
22110
22588
  };
22111
22589
  }
22590
+ const confirmationSuffix = options.state.phase === "shape" || options.state.phase === "build" && options.state.approval !== "confirmed" ? " --confirmed" : "";
22112
22591
  return {
22113
22592
  schema: "comet.native.continuation.v1",
22114
22593
  skill: "comet-native",
@@ -22117,7 +22596,7 @@ function nativeContinuation(options) {
22117
22596
  revision: options.state.revision,
22118
22597
  disposition: "continue",
22119
22598
  action: "advance-phase",
22120
- command: `comet native next ${options.state.name} --summary "<summary>"`,
22599
+ command: `comet native next ${options.state.name} --summary "<summary>"${confirmationSuffix}`,
22121
22600
  requiresUserDecision: false,
22122
22601
  requiredInputs: requiredPhaseInputs(options.state)
22123
22602
  };
@@ -22133,6 +22612,18 @@ var EXACT_METADATA = {
22133
22612
  retry: "next",
22134
22613
  repair: "none"
22135
22614
  },
22615
+ "shape-confirmation-required": {
22616
+ severity: "error",
22617
+ requiredAction: "confirm-shared-understanding",
22618
+ retry: "next",
22619
+ repair: "none"
22620
+ },
22621
+ "approval-confirmation-required": {
22622
+ severity: "error",
22623
+ requiredAction: "confirm-shared-understanding",
22624
+ retry: "next",
22625
+ repair: "none"
22626
+ },
22136
22627
  "transition-incomplete": {
22137
22628
  severity: "error",
22138
22629
  requiredAction: "recover-transition",
@@ -22304,7 +22795,7 @@ function projectRelativePath3(paths, state, finding) {
22304
22795
  }
22305
22796
  function retryCommand(retry, state, code) {
22306
22797
  if (retry === "next") {
22307
- return `comet native next ${state.name} --summary "<summary>"${code === "contract-changed-after-approval" ? " --confirmed" : ""}`;
22798
+ return `comet native next ${state.name} --summary "<summary>"${code === "contract-changed-after-approval" || code === "shape-confirmation-required" || code === "approval-confirmation-required" ? " --confirmed" : ""}`;
22308
22799
  }
22309
22800
  if (retry === "status") return `comet native status ${state.name} --details`;
22310
22801
  return null;
@@ -22322,7 +22813,7 @@ function structureNativeFindings(options) {
22322
22813
  repairCommand: metadata.repair === "doctor" ? `comet native doctor ${options.state.name} --repair${finding.code.startsWith("transition-") ? " --strategy continue" : ""}` : null,
22323
22814
  // This is intentionally code-based, not severity-based. Model-actionable
22324
22815
  // missing data must never be presented as a user decision.
22325
- requiresUserDecision: finding.code === "brief-blocking-question" || finding.code === "contract-changed-after-approval" || finding.code === "verification-scope-partial" || finding.code === "repair-iteration-limit" || finding.code === "repair-override-exhausted"
22816
+ requiresUserDecision: finding.code === "brief-blocking-question" || finding.code === "shape-confirmation-required" || finding.code === "approval-confirmation-required" || finding.code === "contract-changed-after-approval" || finding.code === "verification-scope-partial" || finding.code === "repair-iteration-limit" || finding.code === "repair-override-exhausted"
22326
22817
  };
22327
22818
  }).sort((left, right) => {
22328
22819
  const severityRank = { error: 0, warning: 1, info: 2 };
@@ -22636,11 +23127,11 @@ async function selectedName(paths) {
22636
23127
  return null;
22637
23128
  }
22638
23129
  }
22639
- function nativeNextCommand(state, archiveReady, evidenceRetreat = false) {
23130
+ function nativeNextCommand(state, archiveReady, evidenceRetreat = false, _clarificationMode) {
22640
23131
  if (state.phase === "archive") {
22641
23132
  return archiveReady ? `comet native archive ${state.name} --dry-run` : evidenceRetreat ? `comet native next ${state.name} --summary "<summary>"` : null;
22642
23133
  }
22643
- return `comet native next ${state.name} --summary "<summary>"`;
23134
+ return `comet native next ${state.name} --summary "<summary>"${state.phase === "shape" || state.phase === "build" && state.approval !== "confirmed" ? " --confirmed" : ""}`;
22644
23135
  }
22645
23136
  async function statusFindings(paths, state) {
22646
23137
  const changeDir = nativeChangeDir(paths, state.name);
@@ -22971,13 +23462,19 @@ async function inspectNativeStatus(paths, name, options) {
22971
23462
  verificationResult: state.verification_result,
22972
23463
  specChanges: state.spec_changes.length,
22973
23464
  selected,
22974
- nextCommand: mutationBlocked || repairBlocked ? null : nativeNextCommand(state, archiveReady, evidenceRetreat),
23465
+ nextCommand: mutationBlocked || repairBlocked ? null : nativeNextCommand(state, archiveReady, evidenceRetreat, options?.clarificationMode),
22975
23466
  archiveReady,
22976
23467
  inspection: resume.inspection,
22977
23468
  findingSummary: summarizeNativeFindings(findings),
22978
23469
  detailsCommand: `comet native status ${state.name} --details`,
22979
23470
  checkpoint: resume.checkpoint,
22980
- continuation: nativeContinuation({ state, findings, archiveReady, evidenceRetreat }),
23471
+ continuation: nativeContinuation({
23472
+ state,
23473
+ findings,
23474
+ archiveReady,
23475
+ evidenceRetreat,
23476
+ clarificationMode: options?.clarificationMode
23477
+ }),
22981
23478
  repair,
22982
23479
  ...options?.details ? {
22983
23480
  ...acceptancePage ? { acceptancePage } : {},
@@ -23011,7 +23508,7 @@ async function boundedNativeChangeNames(paths) {
23011
23508
  throw error;
23012
23509
  }
23013
23510
  const names = [];
23014
- const directory = await fs23.opendir(paths.changesDir);
23511
+ const directory = await fs24.opendir(paths.changesDir);
23015
23512
  try {
23016
23513
  for await (const entry2 of directory) {
23017
23514
  if (!entry2.isDirectory() || entry2.isSymbolicLink()) continue;
@@ -23060,7 +23557,11 @@ async function listNativeStatusPage(paths, options) {
23060
23557
  cursor: options?.cursor
23061
23558
  });
23062
23559
  const candidates = await Promise.all(
23063
- names.slice(offset, offset + NATIVE_STATUS_PAGE_LIMITS.maxItems).map((name) => inspectNativeStatus(paths, name))
23560
+ names.slice(offset, offset + NATIVE_STATUS_PAGE_LIMITS.maxItems).map(
23561
+ (name) => inspectNativeStatus(paths, name, {
23562
+ clarificationMode: options?.clarificationMode
23563
+ })
23564
+ )
23064
23565
  );
23065
23566
  const items = [];
23066
23567
  for (const candidate of candidates) {
@@ -23092,23 +23593,25 @@ async function listNativeStatusPage(paths, options) {
23092
23593
  limits: { ...NATIVE_STATUS_PAGE_LIMITS }
23093
23594
  };
23094
23595
  }
23095
- async function listNativeStatus(paths) {
23096
- return (await listNativeStatusPage(paths)).items;
23596
+ async function listNativeStatus(paths, options) {
23597
+ return (await listNativeStatusPage(paths, {
23598
+ clarificationMode: options?.clarificationMode
23599
+ })).items;
23097
23600
  }
23098
23601
 
23099
23602
  // domains/comet-native/native-doctor.ts
23100
- import { promises as fs27 } from "fs";
23603
+ import { promises as fs28 } from "fs";
23101
23604
  import path41 from "path";
23102
23605
 
23103
23606
  // domains/comet-native/native-evidence-retention.ts
23104
23607
  import { createHash as createHash13, randomUUID as randomUUID8 } from "node:crypto";
23105
- import { constants as fsConstants4, promises as fs25 } from "node:fs";
23608
+ import { constants as fsConstants5, promises as fs26 } from "node:fs";
23106
23609
  import path39 from "node:path";
23107
23610
 
23108
23611
  // domains/comet-native/native-schema-migration.ts
23109
23612
  var import_yaml4 = __toESM(require_dist(), 1);
23110
23613
  import { randomUUID as randomUUID7 } from "crypto";
23111
- import { promises as fs24 } from "fs";
23614
+ import { promises as fs25 } from "fs";
23112
23615
  import path38 from "path";
23113
23616
  import { isDeepStrictEqual as isDeepStrictEqual3 } from "util";
23114
23617
  var HASH_PATTERN14 = /^[a-f0-9]{64}$/u;
@@ -23591,7 +24094,7 @@ async function continueTransitionSupersede(paths, journal, hooks) {
23591
24094
  if (transitionHash !== supersede.sourceHash) {
23592
24095
  throw new Error(`Native superseded transition changed before removal for ${journal.change}`);
23593
24096
  }
23594
- await fs24.rm(transitionFile);
24097
+ await fs25.rm(transitionFile);
23595
24098
  await hooks?.afterTransitionSuperseded?.(journal);
23596
24099
  }
23597
24100
  }
@@ -23673,7 +24176,7 @@ async function continueNativeSchemaMigrationLocked(paths, name, hooks) {
23673
24176
  }
23674
24177
  await continueTransitionSupersede(paths, journal, hooks);
23675
24178
  await continueRunRetreat(paths, journal, hooks);
23676
- await fs24.rm(nativeSchemaMigrationJournalFile(paths, name), { force: true });
24179
+ await fs25.rm(nativeSchemaMigrationJournalFile(paths, name), { force: true });
23677
24180
  return journal.nextState;
23678
24181
  }
23679
24182
  async function stableEvidenceRetreat(options) {
@@ -23979,7 +24482,13 @@ var MANAGED_KINDS = [
23979
24482
  "check-receipts"
23980
24483
  ];
23981
24484
  function sameObjectIdentity(left, right) {
23982
- return left.dev !== 0 || left.ino !== 0 || right.dev !== 0 || right.ino !== 0 ? left.dev === right.dev && left.ino === right.ino : left.birthtimeMs === right.birthtimeMs;
24485
+ return sameFileObject(
24486
+ { ...left, birthtime: left.birthtimeMs },
24487
+ {
24488
+ ...right,
24489
+ birthtime: right.birthtimeMs
24490
+ }
24491
+ );
23983
24492
  }
23984
24493
  function sameIdentity(left, right) {
23985
24494
  return sameObjectIdentity(left, right) && left.birthtimeMs === right.birthtimeMs && left.ctimeMs === right.ctimeMs && left.mtimeMs === right.mtimeMs && left.size === right.size;
@@ -24024,11 +24533,11 @@ async function captureDirectoryChain6(managedRoot, directory) {
24024
24533
  let cursor = root;
24025
24534
  for (const segment of ["", ...segments]) {
24026
24535
  if (segment) cursor = path39.join(cursor, segment);
24027
- const stat = await fs25.lstat(cursor);
24536
+ const stat = await fs26.lstat(cursor);
24028
24537
  if (!stat.isDirectory() || stat.isSymbolicLink()) {
24029
24538
  throw new Error(`Native evidence retention parent must be a real directory: ${cursor}`);
24030
24539
  }
24031
- const realPath = await fs25.realpath(cursor);
24540
+ const realPath = await fs26.realpath(cursor);
24032
24541
  if (chain.length > 0 && !isInsidePath(chain[0].realPath, realPath)) {
24033
24542
  throw new Error(`Native evidence retention parent resolves outside its change: ${cursor}`);
24034
24543
  }
@@ -24038,8 +24547,8 @@ async function captureDirectoryChain6(managedRoot, directory) {
24038
24547
  }
24039
24548
  async function verifyDirectoryChain7(chain) {
24040
24549
  for (const identity of chain) {
24041
- const stat = await fs25.lstat(identity.path);
24042
- if (!stat.isDirectory() || stat.isSymbolicLink() || !sameObjectIdentity(identity, stat) || await fs25.realpath(identity.path) !== identity.realPath) {
24550
+ const stat = await fs26.lstat(identity.path);
24551
+ if (!stat.isDirectory() || stat.isSymbolicLink() || !sameObjectIdentity(identity, stat) || await fs26.realpath(identity.path) !== identity.realPath) {
24043
24552
  throw new Error(`Native evidence retention parent changed: ${identity.path}`);
24044
24553
  }
24045
24554
  }
@@ -24111,7 +24620,7 @@ function parseDocument4(kind, hash6, value, expectedChange) {
24111
24620
  return { canonical: receipt, dependencies: [] };
24112
24621
  }
24113
24622
  async function readCanonicalDocument(options) {
24114
- const before = await fs25.lstat(options.file);
24623
+ const before = await fs26.lstat(options.file);
24115
24624
  if (!before.isFile() || before.isSymbolicLink()) {
24116
24625
  throw new Error(`Native evidence entry is not a regular file: ${options.ref}`);
24117
24626
  }
@@ -24119,12 +24628,12 @@ async function readCanonicalDocument(options) {
24119
24628
  if (before.size > maximumBytes) {
24120
24629
  throw new Error(`Native evidence entry exceeds its byte budget: ${options.ref}`);
24121
24630
  }
24122
- const beforeRealPath = await fs25.realpath(options.file);
24631
+ const beforeRealPath = await fs26.realpath(options.file);
24123
24632
  if (!isInsidePath(options.directoryChain[0].realPath, beforeRealPath)) {
24124
24633
  throw new Error(`Native evidence entry resolves outside its change: ${options.ref}`);
24125
24634
  }
24126
- const openFlags = process.platform === "win32" ? "r" : fsConstants4.O_RDONLY | fsConstants4.O_NOFOLLOW | fsConstants4.O_NONBLOCK;
24127
- const handle = await fs25.open(options.file, openFlags).catch((error) => {
24635
+ const openFlags = process.platform === "win32" ? "r" : fsConstants5.O_RDONLY | fsConstants5.O_NOFOLLOW | fsConstants5.O_NONBLOCK;
24636
+ const handle = await fs26.open(options.file, openFlags).catch((error) => {
24128
24637
  if (error.code === "ELOOP" || error.code === "ENXIO") {
24129
24638
  throw new Error(`Native evidence entry became unsafe while opening: ${options.ref}`);
24130
24639
  }
@@ -24133,8 +24642,8 @@ async function readCanonicalDocument(options) {
24133
24642
  try {
24134
24643
  const [opened, pathAfterOpen, realPathAfterOpen] = await Promise.all([
24135
24644
  handle.stat(),
24136
- fs25.lstat(options.file),
24137
- fs25.realpath(options.file)
24645
+ fs26.lstat(options.file),
24646
+ fs26.realpath(options.file)
24138
24647
  ]);
24139
24648
  await verifyDirectoryChain7(options.directoryChain);
24140
24649
  if (!opened.isFile() || !pathAfterOpen.isFile() || pathAfterOpen.isSymbolicLink() || realPathAfterOpen !== beforeRealPath || !sameIdentity(fileIdentity2(before), opened) || !sameIdentity(fileIdentity2(opened), pathAfterOpen)) {
@@ -24155,8 +24664,8 @@ async function readCanonicalDocument(options) {
24155
24664
  }
24156
24665
  const [afterHandle, afterPath, afterRealPath] = await Promise.all([
24157
24666
  handle.stat(),
24158
- fs25.lstat(options.file),
24159
- fs25.realpath(options.file)
24667
+ fs26.lstat(options.file),
24668
+ fs26.realpath(options.file)
24160
24669
  ]);
24161
24670
  await verifyDirectoryChain7(options.directoryChain);
24162
24671
  if (!afterPath.isFile() || afterPath.isSymbolicLink() || afterRealPath !== beforeRealPath || !sameIdentity(fileIdentity2(opened), afterHandle) || !sameIdentity(fileIdentity2(opened), afterPath)) {
@@ -24193,7 +24702,7 @@ async function readCanonicalDocument(options) {
24193
24702
  }
24194
24703
  async function pathIsMissing(file) {
24195
24704
  try {
24196
- await fs25.lstat(file);
24705
+ await fs26.lstat(file);
24197
24706
  return false;
24198
24707
  } catch (error) {
24199
24708
  if (error.code === "ENOENT") return true;
@@ -24207,7 +24716,7 @@ async function inspectCleanupQuarantines(options) {
24207
24716
  let rootEntries;
24208
24717
  try {
24209
24718
  const rootChain = await captureDirectoryChain6(changeRoot, evidenceRoot);
24210
- rootEntries = await fs25.readdir(evidenceRoot, { withFileTypes: true });
24719
+ rootEntries = await fs26.readdir(evidenceRoot, { withFileTypes: true });
24211
24720
  await verifyDirectoryChain7(rootChain);
24212
24721
  } catch (error) {
24213
24722
  if (error.code === "ENOENT") {
@@ -24220,7 +24729,7 @@ async function inspectCleanupQuarantines(options) {
24220
24729
  if (!rootEntries.some((entry2) => entry2.name === kind)) continue;
24221
24730
  const directory = path39.join(evidenceRoot, kind);
24222
24731
  const directoryChain = await captureDirectoryChain6(changeRoot, directory);
24223
- const entries = (await fs25.readdir(directory, { withFileTypes: true })).sort(
24732
+ const entries = (await fs26.readdir(directory, { withFileTypes: true })).sort(
24224
24733
  (left, right) => compareText8(left.name, right.name)
24225
24734
  );
24226
24735
  const byOriginal = /* @__PURE__ */ new Map();
@@ -24323,20 +24832,20 @@ async function inspectCleanupQuarantines(options) {
24323
24832
  `Native evidence cleanup original or quarantine changed during recovery: ${recovery.ref}`
24324
24833
  );
24325
24834
  }
24326
- await fs25.rm(recovery.quarantine);
24835
+ await fs26.rm(recovery.quarantine);
24327
24836
  await verifyDirectoryChain7(recovery.document.directoryChain);
24328
24837
  continue;
24329
24838
  }
24330
24839
  if (!await pathIsMissing(recovery.original)) {
24331
24840
  throw new Error(`Native evidence cleanup original appeared during recovery: ${recovery.ref}`);
24332
24841
  }
24333
- const quarantineStat = await fs25.lstat(recovery.quarantine);
24842
+ const quarantineStat = await fs26.lstat(recovery.quarantine);
24334
24843
  if (!quarantineStat.isFile() || quarantineStat.isSymbolicLink() || !sameIdentity(recovery.document.identity, quarantineStat)) {
24335
24844
  throw new Error(
24336
24845
  `Native evidence cleanup quarantine changed during recovery: ${recovery.ref}`
24337
24846
  );
24338
24847
  }
24339
- await fs25.link(recovery.quarantine, recovery.original);
24848
+ await fs26.link(recovery.quarantine, recovery.original);
24340
24849
  await options.hooks.afterRecoveryLink?.({
24341
24850
  ref: recovery.ref,
24342
24851
  original: recovery.original,
@@ -24363,7 +24872,7 @@ async function inspectCleanupQuarantines(options) {
24363
24872
  if (!sameContentVersionAfterLinkOrRename(recovery.document.identity, restored.identity) || !sameIdentity(restored.identity, linkedQuarantine.identity)) {
24364
24873
  throw new Error(`Native evidence cleanup recovery identity changed: ${recovery.ref}`);
24365
24874
  }
24366
- await fs25.rm(recovery.quarantine);
24875
+ await fs26.rm(recovery.quarantine);
24367
24876
  await verifyDirectoryChain7(recovery.document.directoryChain);
24368
24877
  }
24369
24878
  return {
@@ -24387,7 +24896,7 @@ async function scanEvidenceStore(paths, name) {
24387
24896
  let rootEntries;
24388
24897
  try {
24389
24898
  const rootChain = await captureDirectoryChain6(changeRoot, evidenceRoot);
24390
- rootEntries = await fs25.readdir(evidenceRoot, { withFileTypes: true });
24899
+ rootEntries = await fs26.readdir(evidenceRoot, { withFileTypes: true });
24391
24900
  await verifyDirectoryChain7(rootChain);
24392
24901
  } catch (error) {
24393
24902
  if (error.code === "ENOENT") return [];
@@ -24407,7 +24916,7 @@ async function scanEvidenceStore(paths, name) {
24407
24916
  if (!rootEntries.some((entry2) => entry2.name === kind)) continue;
24408
24917
  const directory = path39.join(evidenceRoot, kind);
24409
24918
  const directoryChain = await captureDirectoryChain6(changeRoot, directory);
24410
- const entries = (await fs25.readdir(directory, { withFileTypes: true })).sort(
24919
+ const entries = (await fs26.readdir(directory, { withFileTypes: true })).sort(
24411
24920
  (left, right) => compareText8(left.name, right.name)
24412
24921
  );
24413
24922
  await verifyDirectoryChain7(directoryChain);
@@ -24551,13 +25060,13 @@ function summaryFinding(name, plan, repaired, evidenceRoot) {
24551
25060
  }
24552
25061
  async function restoreQuarantineIfSafe(quarantine, original) {
24553
25062
  try {
24554
- await fs25.lstat(original);
25063
+ await fs26.lstat(original);
24555
25064
  return;
24556
25065
  } catch (error) {
24557
25066
  if (error.code !== "ENOENT") return;
24558
25067
  }
24559
25068
  try {
24560
- await fs25.rename(quarantine, original);
25069
+ await fs26.rename(quarantine, original);
24561
25070
  } catch {
24562
25071
  }
24563
25072
  }
@@ -24573,8 +25082,8 @@ async function deleteCandidate(candidate, hooks) {
24573
25082
  name: candidate.change,
24574
25083
  directoryChain: candidate.directoryChain
24575
25084
  });
24576
- const before = await fs25.lstat(candidate.file);
24577
- const beforeRealPath = await fs25.realpath(candidate.file);
25085
+ const before = await fs26.lstat(candidate.file);
25086
+ const beforeRealPath = await fs26.realpath(candidate.file);
24578
25087
  if (!before.isFile() || before.isSymbolicLink() || !sameIdentity(candidate.identity, before) || !sameIdentity(candidate.identity, beforeDocument.identity) || !isInsidePath(candidate.directoryChain[0].realPath, beforeRealPath)) {
24579
25088
  throw new Error(`Native evidence candidate changed before cleanup: ${candidate.ref}`);
24580
25089
  }
@@ -24582,18 +25091,18 @@ async function deleteCandidate(candidate, hooks) {
24582
25091
  path39.dirname(candidate.file),
24583
25092
  `.${path39.basename(candidate.file)}.${randomUUID8()}.gc`
24584
25093
  );
24585
- await fs25.rename(candidate.file, quarantine);
25094
+ await fs26.rename(candidate.file, quarantine);
24586
25095
  try {
24587
25096
  const [moved, movedRealPath] = await Promise.all([
24588
- fs25.lstat(quarantine),
24589
- fs25.realpath(quarantine)
25097
+ fs26.lstat(quarantine),
25098
+ fs26.realpath(quarantine)
24590
25099
  ]);
24591
25100
  await verifyDirectoryChain7(candidate.directoryChain);
24592
25101
  if (!moved.isFile() || moved.isSymbolicLink() || !sameContentVersionAfterLinkOrRename(candidate.identity, moved) || !isInsidePath(candidate.directoryChain[0].realPath, movedRealPath)) {
24593
25102
  await restoreQuarantineIfSafe(quarantine, candidate.file);
24594
25103
  throw new Error(`Native evidence candidate changed during cleanup: ${candidate.ref}`);
24595
25104
  }
24596
- await fs25.rm(quarantine);
25105
+ await fs26.rm(quarantine);
24597
25106
  await verifyDirectoryChain7(candidate.directoryChain);
24598
25107
  } catch (error) {
24599
25108
  await restoreQuarantineIfSafe(quarantine, candidate.file);
@@ -24605,7 +25114,7 @@ async function hasPendingRelocationOrArchive(paths) {
24605
25114
  if (config?.native.pending_root_move) return true;
24606
25115
  let entries;
24607
25116
  try {
24608
- entries = await fs25.readdir(paths.transactionsDir, { withFileTypes: true });
25117
+ entries = await fs26.readdir(paths.transactionsDir, { withFileTypes: true });
24609
25118
  } catch (error) {
24610
25119
  if (error.code === "ENOENT") return false;
24611
25120
  throw error;
@@ -24625,7 +25134,7 @@ async function changeNames(paths, requested) {
24625
25134
  if (requested) return [requested];
24626
25135
  let entries;
24627
25136
  try {
24628
- entries = await fs25.readdir(paths.changesDir, { withFileTypes: true });
25137
+ entries = await fs26.readdir(paths.changesDir, { withFileTypes: true });
24629
25138
  } catch (error) {
24630
25139
  if (error.code === "ENOENT") return [];
24631
25140
  throw error;
@@ -24755,14 +25264,14 @@ async function inspectNativeEvidenceRetention(options) {
24755
25264
 
24756
25265
  // domains/comet-native/native-root-move.ts
24757
25266
  import { createHash as createHash14, randomUUID as randomUUID9 } from "crypto";
24758
- import { promises as fs26 } from "fs";
25267
+ import { promises as fs27 } from "fs";
24759
25268
  import path40 from "path";
24760
25269
  var NATIVE_ROOT_MOVE_MAX_FILE_BYTES = 64 * 1024 * 1024;
24761
25270
  var NATIVE_ROOT_MOVE_MAX_JOURNAL_BYTES = 256 * 1024;
24762
25271
  var NATIVE_ROOT_MOVE_MAX_MANIFEST_BYTES = 16 * 1024 * 1024;
24763
25272
  async function exists3(target) {
24764
25273
  try {
24765
- await fs26.access(target);
25274
+ await fs27.access(target);
24766
25275
  return true;
24767
25276
  } catch (error) {
24768
25277
  if (error.code === "ENOENT") return false;
@@ -24772,7 +25281,7 @@ async function exists3(target) {
24772
25281
  async function assertNoUnfinishedTransactions(paths) {
24773
25282
  let entries;
24774
25283
  try {
24775
- entries = await fs26.readdir(paths.transactionsDir, { withFileTypes: true });
25284
+ entries = await fs27.readdir(paths.transactionsDir, { withFileTypes: true });
24776
25285
  } catch (error) {
24777
25286
  if (error.code === "ENOENT") return;
24778
25287
  throw error;
@@ -24797,7 +25306,7 @@ async function assertNoUnfinishedTransactions(paths) {
24797
25306
  }
24798
25307
  }
24799
25308
  async function assertNoOtherLocks(paths, ownedLock) {
24800
- for (const entry2 of await fs26.readdir(paths.locksDir, { withFileTypes: true })) {
25309
+ for (const entry2 of await fs27.readdir(paths.locksDir, { withFileTypes: true })) {
24801
25310
  const file = path40.join(paths.locksDir, entry2.name);
24802
25311
  if (path40.resolve(file) === path40.resolve(ownedLock)) continue;
24803
25312
  if (entry2.isFile() || entry2.isSymbolicLink()) {
@@ -24808,7 +25317,7 @@ async function assertNoOtherLocks(paths, ownedLock) {
24808
25317
  async function refreshNativeWorkspaceIdentities(paths) {
24809
25318
  let entries;
24810
25319
  try {
24811
- entries = await fs26.readdir(paths.changesDir, { withFileTypes: true });
25320
+ entries = await fs27.readdir(paths.changesDir, { withFileTypes: true });
24812
25321
  } catch (error) {
24813
25322
  if (error.code === "ENOENT") return;
24814
25323
  throw error;
@@ -24848,7 +25357,7 @@ async function walkTree(root, options) {
24848
25357
  await protectedDirectory.verify();
24849
25358
  continue;
24850
25359
  }
24851
- const stat = await fs26.lstat(target);
25360
+ const stat = await fs27.lstat(target);
24852
25361
  if (entry2.isSymbolicLink() || stat.isSymbolicLink()) {
24853
25362
  if (options.rejectSymlinks) throw new Error(`Native root contains a symlink: ${target}`);
24854
25363
  await protectedDirectory.verify();
@@ -24902,7 +25411,7 @@ async function copyTree(source, target, excludedFile, targetRoot) {
24902
25411
  await protectedDirectory.verify();
24903
25412
  continue;
24904
25413
  }
24905
- const stat = await fs26.lstat(sourceEntry);
25414
+ const stat = await fs27.lstat(sourceEntry);
24906
25415
  if (entry2.isSymbolicLink() || stat.isSymbolicLink()) {
24907
25416
  throw new Error(`Native root contains a symlink: ${sourceEntry}`);
24908
25417
  }
@@ -25349,7 +25858,7 @@ async function finishForwardMove(options) {
25349
25858
  options.staging,
25350
25859
  options.lockFile
25351
25860
  );
25352
- await fs26.rename(options.staging, options.destinationPaths.nativeRoot);
25861
+ await fs27.rename(options.staging, options.destinationPaths.nativeRoot);
25353
25862
  }
25354
25863
  config = await setPendingStage({
25355
25864
  projectRoot: options.projectRoot,
@@ -25395,7 +25904,8 @@ async function finishForwardMove(options) {
25395
25904
  const stableNative = {
25396
25905
  artifact_root: config.native.artifact_root,
25397
25906
  language: config.native.language,
25398
- clarification_mode: config.native.clarification_mode
25907
+ clarification_mode: config.native.clarification_mode,
25908
+ snapshot: config.native.snapshot
25399
25909
  };
25400
25910
  const committed = {
25401
25911
  ...config,
@@ -25538,7 +26048,8 @@ async function recoverNativeRootMove(options) {
25538
26048
  const stableNative = {
25539
26049
  artifact_root: config.native.artifact_root,
25540
26050
  language: config.native.language,
25541
- clarification_mode: config.native.clarification_mode
26051
+ clarification_mode: config.native.clarification_mode,
26052
+ snapshot: config.native.snapshot
25542
26053
  };
25543
26054
  const restored = {
25544
26055
  ...config,
@@ -25669,7 +26180,7 @@ async function inspectSelection(paths, repair) {
25669
26180
  }
25670
26181
  }
25671
26182
  if (repair) {
25672
- await fs27.rm(file, { force: true });
26183
+ await fs28.rm(file, { force: true });
25673
26184
  return [
25674
26185
  {
25675
26186
  severity: "info",
@@ -26256,7 +26767,7 @@ async function doctorNativeProject(options) {
26256
26767
 
26257
26768
  // domains/comet-native/native-check-receipt.ts
26258
26769
  import { createHash as createHash15 } from "node:crypto";
26259
- import { constants as fsConstants5, promises as fs28 } from "node:fs";
26770
+ import { constants as fsConstants6, promises as fs29 } from "node:fs";
26260
26771
  import path42 from "node:path";
26261
26772
  import { TextDecoder as TextDecoder5 } from "node:util";
26262
26773
  var ISSUE_KIND_RANK = {
@@ -26282,6 +26793,7 @@ function projectionManifest2(projection) {
26282
26793
  createdAt: "1970-01-01T00:00:00.000Z",
26283
26794
  complete: projection.complete,
26284
26795
  limits: projection.limits,
26796
+ ...projection.policy ? { policy: projection.policy } : {},
26285
26797
  entries: projection.entries,
26286
26798
  omitted: projection.omitted,
26287
26799
  omittedCount: projection.omittedCount,
@@ -26289,16 +26801,17 @@ function projectionManifest2(projection) {
26289
26801
  };
26290
26802
  }
26291
26803
  async function collectBoundFacts(options) {
26804
+ const baseline = projectionManifest2(options.scope.baseline);
26292
26805
  const [contract, snapshot2] = await Promise.all([
26293
26806
  collectNativeContractFiles({
26294
26807
  changeDir: nativeChangeDir(options.paths, options.state.name),
26295
26808
  briefRef: options.state.brief,
26296
26809
  specChanges: options.state.spec_changes
26297
26810
  }),
26298
- createNativeContentSnapshot(options.paths, { origin: "explicit" })
26811
+ createNativeCurrentContentSnapshot(options.paths, baseline, { origin: "explicit" })
26299
26812
  ]);
26300
26813
  const currentBundle = buildNativeImplementationScopeBundle({
26301
- baseline: projectionManifest2(options.scope.baseline),
26814
+ baseline,
26302
26815
  current: snapshot2,
26303
26816
  contractHash: options.scope.authority.contractHash,
26304
26817
  declaredArtifacts: options.scope.authority.declaredArtifacts,
@@ -26343,17 +26856,23 @@ function staleReasons(before, after, scope) {
26343
26856
  }
26344
26857
  function sameDirectoryIdentity7(identity, stat) {
26345
26858
  const stableMetadata = identity.birthtimeMs === stat.birthtimeMs && identity.ctimeMs === stat.ctimeMs;
26346
- if (identity.dev !== 0 || identity.ino !== 0 || stat.dev !== 0 || stat.ino !== 0) {
26347
- return stableMetadata && identity.dev === stat.dev && identity.ino === stat.ino;
26348
- }
26349
- return stableMetadata;
26859
+ return stableMetadata && sameFileObject(
26860
+ { ...identity, birthtime: identity.birthtimeMs },
26861
+ {
26862
+ ...stat,
26863
+ birthtime: stat.birthtimeMs
26864
+ }
26865
+ );
26350
26866
  }
26351
26867
  function sameFileIdentity8(left, right) {
26352
26868
  const stableMetadata = left.birthtimeMs === right.birthtimeMs && left.ctimeMs === right.ctimeMs && left.mtimeMs === right.mtimeMs && left.size === right.size;
26353
- if (left.dev !== 0 || left.ino !== 0 || right.dev !== 0 || right.ino !== 0) {
26354
- return stableMetadata && left.dev === right.dev && left.ino === right.ino;
26355
- }
26356
- return stableMetadata;
26869
+ return stableMetadata && sameFileObject(
26870
+ { ...left, birthtime: left.birthtimeMs },
26871
+ {
26872
+ ...right,
26873
+ birthtime: right.birthtimeMs
26874
+ }
26875
+ );
26357
26876
  }
26358
26877
  async function captureProjectDirectoryChain(projectRoot, directory) {
26359
26878
  const lexicalRoot = path42.resolve(projectRoot);
@@ -26370,7 +26889,7 @@ async function captureProjectDirectoryChain(projectRoot, directory) {
26370
26889
  if (segment) cursor = path42.join(cursor, segment);
26371
26890
  let stat;
26372
26891
  try {
26373
- stat = await fs28.lstat(cursor);
26892
+ stat = await fs29.lstat(cursor);
26374
26893
  } catch (error) {
26375
26894
  if (error.code === "ENOENT") {
26376
26895
  throw new ScopedFileError("scope-mismatch", "Scoped file parent no longer exists");
@@ -26380,7 +26899,7 @@ async function captureProjectDirectoryChain(projectRoot, directory) {
26380
26899
  if (!stat.isDirectory() || stat.isSymbolicLink()) {
26381
26900
  throw new ScopedFileError("unsafe-file", "Scoped file parent is not a real directory");
26382
26901
  }
26383
- const realPath = await fs28.realpath(cursor);
26902
+ const realPath = await fs29.realpath(cursor);
26384
26903
  if (chain.length > 0 && !isInsidePath(chain[0].realPath, realPath)) {
26385
26904
  throw new ScopedFileError("unsafe-file", "Scoped file parent resolves outside the project");
26386
26905
  }
@@ -26399,14 +26918,14 @@ async function verifyProjectDirectoryChain(chain) {
26399
26918
  for (const identity of chain) {
26400
26919
  let stat;
26401
26920
  try {
26402
- stat = await fs28.lstat(identity.path);
26921
+ stat = await fs29.lstat(identity.path);
26403
26922
  } catch (error) {
26404
26923
  if (error.code === "ENOENT") {
26405
26924
  throw new ScopedFileError("unsafe-file", "Scoped file parent changed while reading");
26406
26925
  }
26407
26926
  throw error;
26408
26927
  }
26409
- if (!stat.isDirectory() || stat.isSymbolicLink() || !sameDirectoryIdentity7(identity, stat) || await fs28.realpath(identity.path) !== identity.realPath) {
26928
+ if (!stat.isDirectory() || stat.isSymbolicLink() || !sameDirectoryIdentity7(identity, stat) || await fs29.realpath(identity.path) !== identity.realPath) {
26410
26929
  throw new ScopedFileError("unsafe-file", "Scoped file parent changed while reading");
26411
26930
  }
26412
26931
  }
@@ -26422,7 +26941,7 @@ async function readScopedFile(options) {
26422
26941
  );
26423
26942
  let before;
26424
26943
  try {
26425
- before = await fs28.lstat(lexicalFile);
26944
+ before = await fs29.lstat(lexicalFile);
26426
26945
  } catch (error) {
26427
26946
  if (error.code === "ENOENT") {
26428
26947
  throw new ScopedFileError("scope-mismatch", "Scoped file no longer exists");
@@ -26438,12 +26957,12 @@ async function readScopedFile(options) {
26438
26957
  "Scoped file size no longer matches its projection"
26439
26958
  );
26440
26959
  }
26441
- const beforeRealPath = await fs28.realpath(lexicalFile);
26960
+ const beforeRealPath = await fs29.realpath(lexicalFile);
26442
26961
  if (!isInsidePath(physicalRoot, beforeRealPath)) {
26443
26962
  throw new ScopedFileError("unsafe-file", "Scoped file resolves outside the project root");
26444
26963
  }
26445
- const openFlags = process.platform === "win32" ? "r" : fsConstants5.O_RDONLY | fsConstants5.O_NOFOLLOW | fsConstants5.O_NONBLOCK;
26446
- const handle = await fs28.open(lexicalFile, openFlags).catch((error) => {
26964
+ const openFlags = process.platform === "win32" ? "r" : fsConstants6.O_RDONLY | fsConstants6.O_NOFOLLOW | fsConstants6.O_NONBLOCK;
26965
+ const handle = await fs29.open(lexicalFile, openFlags).catch((error) => {
26447
26966
  if (error.code === "ENOENT") {
26448
26967
  throw new ScopedFileError("scope-mismatch", "Scoped file no longer exists");
26449
26968
  }
@@ -26455,8 +26974,8 @@ async function readScopedFile(options) {
26455
26974
  try {
26456
26975
  const [opened, pathAfterOpen, realPathAfterOpen] = await Promise.all([
26457
26976
  handle.stat(),
26458
- fs28.lstat(lexicalFile),
26459
- fs28.realpath(lexicalFile)
26977
+ fs29.lstat(lexicalFile),
26978
+ fs29.realpath(lexicalFile)
26460
26979
  ]);
26461
26980
  await verifyProjectDirectoryChain(chain);
26462
26981
  if (!opened.isFile() || !pathAfterOpen.isFile() || pathAfterOpen.isSymbolicLink() || realPathAfterOpen !== beforeRealPath || !sameFileIdentity8(before, opened) || !sameFileIdentity8(opened, pathAfterOpen)) {
@@ -26483,8 +27002,8 @@ async function readScopedFile(options) {
26483
27002
  }
26484
27003
  const [afterHandle, afterPath, afterRealPath] = await Promise.all([
26485
27004
  handle.stat(),
26486
- fs28.lstat(lexicalFile),
26487
- fs28.realpath(lexicalFile)
27005
+ fs29.lstat(lexicalFile),
27006
+ fs29.realpath(lexicalFile)
26488
27007
  ]);
26489
27008
  await verifyProjectDirectoryChain(chain);
26490
27009
  if (total !== options.file.expected.size || digest.digest("hex") !== options.file.expected.hash) {
@@ -26798,7 +27317,7 @@ async function checkpointNativeChange(options) {
26798
27317
  }
26799
27318
 
26800
27319
  // domains/comet-native/native-specs.ts
26801
- import { promises as fs29 } from "fs";
27320
+ import { promises as fs30 } from "fs";
26802
27321
  import path43 from "path";
26803
27322
  var MAX_NATIVE_PROPOSED_SPEC_DIRECTORY_ENTRIES = NATIVE_CONTRACT_FILE_LIMITS.maxSpecs * 4;
26804
27323
  async function optionalHash3(file) {
@@ -26824,7 +27343,7 @@ async function proposedCapabilities(paths, name) {
26824
27343
  }
26825
27344
  const capabilities = [];
26826
27345
  let entryCount = 0;
26827
- const directory = await fs29.opendir(specsDir);
27346
+ const directory = await fs30.opendir(specsDir);
26828
27347
  try {
26829
27348
  for await (const entry2 of directory) {
26830
27349
  entryCount += 1;
@@ -26842,7 +27361,7 @@ async function proposedCapabilities(paths, name) {
26842
27361
  await resolveContainedNativePath(paths.nativeRoot, source);
26843
27362
  let stat;
26844
27363
  try {
26845
- stat = await fs29.lstat(source);
27364
+ stat = await fs30.lstat(source);
26846
27365
  } catch (error) {
26847
27366
  if (error.code === "ENOENT") continue;
26848
27367
  throw error;
@@ -27062,7 +27581,7 @@ async function readNativeProposedSpecs(paths, name) {
27062
27581
  import { randomUUID as randomUUID11 } from "crypto";
27063
27582
 
27064
27583
  // domains/comet-native/native-guards.ts
27065
- import { promises as fs30 } from "fs";
27584
+ import { promises as fs31 } from "fs";
27066
27585
  import path44 from "path";
27067
27586
  function validation(findings) {
27068
27587
  return { valid: findings.length === 0, findings };
@@ -27095,7 +27614,7 @@ async function validateBuildArtifacts(paths, evidence) {
27095
27614
  continue;
27096
27615
  }
27097
27616
  try {
27098
- await fs30.access(target);
27617
+ await fs31.access(target);
27099
27618
  } catch {
27100
27619
  findings.push({
27101
27620
  code: "build-artifact-missing",
@@ -27146,12 +27665,24 @@ async function inspectNativeGuard(options) {
27146
27665
  const brief = await validateNativeBrief(changeDir, options.state.brief);
27147
27666
  const specs = await validateNativeSpecChanges(options.paths, options.state);
27148
27667
  findings.push(...brief.findings, ...specs.findings);
27668
+ if (findings.length === 0 && !options.evidence.confirmed) {
27669
+ findings.push({
27670
+ code: "shape-confirmation-required",
27671
+ message: "Native clarification requires explicit user confirmation of the shared understanding before Build"
27672
+ });
27673
+ }
27149
27674
  } else if (options.state.phase === "build") {
27150
27675
  findings.push(
27151
27676
  ...(await validateNativeBrief(changeDir, options.state.brief)).findings,
27152
27677
  ...(await validateNativeSpecChanges(options.paths, options.state)).findings
27153
27678
  );
27154
27679
  findings.push(...await validateBuildArtifacts(options.paths, options.evidence));
27680
+ if (findings.length === 0 && options.state.approval !== "confirmed" && !options.evidence.confirmed) {
27681
+ findings.push({
27682
+ code: "approval-confirmation-required",
27683
+ message: "Native approval is implicit; confirm the current shared understanding before leaving Build"
27684
+ });
27685
+ }
27155
27686
  } else if (options.state.phase === "verify") {
27156
27687
  const report = options.evidence.verificationReport ?? options.state.verification_report;
27157
27688
  if (!report) {
@@ -27179,7 +27710,7 @@ async function inspectNativeGuard(options) {
27179
27710
  }
27180
27711
 
27181
27712
  // domains/comet-native/native-build-evidence.ts
27182
- import { promises as fs31 } from "node:fs";
27713
+ import { promises as fs32 } from "node:fs";
27183
27714
  import path45 from "node:path";
27184
27715
  var NATIVE_BUILD_EVIDENCE_LIMITS = {
27185
27716
  maxDeclaredArtifacts: 128,
@@ -27255,12 +27786,12 @@ async function inspectDeclaredArtifact(paths, baseline, rawRef) {
27255
27786
  }
27256
27787
  await resolveContainedNativePath(paths.projectRoot, target);
27257
27788
  try {
27258
- const stat = await fs31.lstat(target);
27789
+ const stat = await fs32.lstat(target);
27259
27790
  if (stat.isSymbolicLink()) {
27260
27791
  throw new Error(`Native build artifact must not be a symlink or junction: ${artifactRef}`);
27261
27792
  }
27262
- const realTarget = await fs31.realpath(target);
27263
- const realProjectRoot = await fs31.realpath(paths.projectRoot);
27793
+ const realTarget = await fs32.realpath(target);
27794
+ const realProjectRoot = await fs32.realpath(paths.projectRoot);
27264
27795
  if (!isInsidePath(realProjectRoot, realTarget)) {
27265
27796
  throw new Error(`Native build artifact resolves outside the project: ${artifactRef}`);
27266
27797
  }
@@ -27330,7 +27861,7 @@ async function inspectNativeBuildEvidence(options) {
27330
27861
  baseline,
27331
27862
  refs: options.artifactRefs
27332
27863
  });
27333
- const current = await createNativeContentSnapshot(options.paths, {
27864
+ const current = await createNativeCurrentContentSnapshot(options.paths, baseline, {
27334
27865
  origin: "explicit",
27335
27866
  now: options.now
27336
27867
  });
@@ -27528,7 +28059,8 @@ async function retreatStaleNativeEvidence(options) {
27528
28059
  findings,
27529
28060
  continuation: nativeContinuation({
27530
28061
  state: options.state,
27531
- archiveReady: previousPhase === "archive"
28062
+ archiveReady: previousPhase === "archive",
28063
+ clarificationMode: options.transition.clarificationMode
27532
28064
  })
27533
28065
  };
27534
28066
  }
@@ -27583,7 +28115,10 @@ async function retreatStaleNativeEvidence(options) {
27583
28115
  next: "auto",
27584
28116
  nextCommand: null,
27585
28117
  findings: [],
27586
- continuation: nativeContinuation({ state: persisted })
28118
+ continuation: nativeContinuation({
28119
+ state: persisted,
28120
+ clarificationMode: options.transition.clarificationMode
28121
+ })
27587
28122
  };
27588
28123
  }
27589
28124
  async function advanceNativeChange(options) {
@@ -27630,7 +28165,8 @@ async function advanceNativeChangeLocked(options) {
27630
28165
  continuation: nativeContinuation({
27631
28166
  state,
27632
28167
  findings: repairFindings2,
27633
- archiveReady: state.phase === "archive" && state.verification_result === "pass"
28168
+ archiveReady: state.phase === "archive" && state.verification_result === "pass",
28169
+ clarificationMode: options.clarificationMode
27634
28170
  }),
27635
28171
  ...repair ? { repair } : {}
27636
28172
  };
@@ -27669,7 +28205,8 @@ async function advanceNativeChangeLocked(options) {
27669
28205
  const guard = await inspectNativeGuard({
27670
28206
  paths: options.paths,
27671
28207
  state: candidate,
27672
- evidence: options.evidence
28208
+ evidence: options.evidence,
28209
+ clarificationMode: options.clarificationMode
27673
28210
  });
27674
28211
  if (!guard.valid) {
27675
28212
  const findings = structureNativeFindings({
@@ -27683,7 +28220,11 @@ async function advanceNativeChangeLocked(options) {
27683
28220
  next: "manual",
27684
28221
  nextCommand: null,
27685
28222
  findings,
27686
- continuation: nativeContinuation({ state, findings })
28223
+ continuation: nativeContinuation({
28224
+ state,
28225
+ findings,
28226
+ clarificationMode: options.clarificationMode
28227
+ })
27687
28228
  };
27688
28229
  }
27689
28230
  const shapeContract = state.phase === "shape" ? await collectNativeContractFiles({
@@ -27722,7 +28263,11 @@ async function advanceNativeChangeLocked(options) {
27722
28263
  next: "manual",
27723
28264
  nextCommand: null,
27724
28265
  findings,
27725
- continuation: nativeContinuation({ state, findings })
28266
+ continuation: nativeContinuation({
28267
+ state,
28268
+ findings,
28269
+ clarificationMode: options.clarificationMode
28270
+ })
27726
28271
  };
27727
28272
  }
27728
28273
  const preparedScope = buildEvidence ? {
@@ -27754,7 +28299,11 @@ async function advanceNativeChangeLocked(options) {
27754
28299
  next: "manual",
27755
28300
  nextCommand: null,
27756
28301
  findings,
27757
- continuation: nativeContinuation({ state, findings }),
28302
+ continuation: nativeContinuation({
28303
+ state,
28304
+ findings,
28305
+ clarificationMode: options.clarificationMode
28306
+ }),
27758
28307
  preparedScope
27759
28308
  };
27760
28309
  }
@@ -27799,7 +28348,11 @@ async function advanceNativeChangeLocked(options) {
27799
28348
  next: "manual",
27800
28349
  nextCommand: null,
27801
28350
  findings,
27802
- continuation: nativeContinuation({ state, findings }),
28351
+ continuation: nativeContinuation({
28352
+ state,
28353
+ findings,
28354
+ clarificationMode: options.clarificationMode
28355
+ }),
27803
28356
  preparedScope: preparedScope ? { ...preparedScope, partialAllowanceRef: null } : preparedScope
27804
28357
  };
27805
28358
  }
@@ -27828,7 +28381,11 @@ async function advanceNativeChangeLocked(options) {
27828
28381
  next: "manual",
27829
28382
  nextCommand: null,
27830
28383
  findings,
27831
- continuation: nativeContinuation({ state, findings })
28384
+ continuation: nativeContinuation({
28385
+ state,
28386
+ findings,
28387
+ clarificationMode: options.clarificationMode
28388
+ })
27832
28389
  };
27833
28390
  }
27834
28391
  let repairDecision = null;
@@ -27883,7 +28440,7 @@ async function advanceNativeChangeLocked(options) {
27883
28440
  ...candidate,
27884
28441
  revision: state.revision + 1,
27885
28442
  phase: advanced.currentStep,
27886
- approval: options.evidence.confirmed ? "confirmed" : state.phase === "shape" && state.approval === null ? "implicit" : state.approval,
28443
+ approval: options.evidence.confirmed ? "confirmed" : state.approval,
27887
28444
  approved_contract_hash: state.phase === "shape" ? shapeContract.contract.contractHash : state.phase === "build" && options.evidence.confirmed ? buildEvidence.contract.contract.contractHash : state.approved_contract_hash ?? null,
27888
28445
  run_id: run.runId,
27889
28446
  ...state.phase === "build" ? {
@@ -27960,7 +28517,8 @@ async function advanceNativeChangeLocked(options) {
27960
28517
  continuation: nativeContinuation({
27961
28518
  state: persisted,
27962
28519
  findings: repairFindings,
27963
- archiveReady: persisted.phase === "archive" && persisted.verification_result === "pass"
28520
+ archiveReady: persisted.phase === "archive" && persisted.verification_result === "pass",
28521
+ clarificationMode: options.clarificationMode
27964
28522
  }),
27965
28523
  ...preparedScope ? { preparedScope } : {},
27966
28524
  ...repairDecision ? { repair: repairDecision } : {}
@@ -27968,7 +28526,7 @@ async function advanceNativeChangeLocked(options) {
27968
28526
  }
27969
28527
 
27970
28528
  // domains/comet-native/native-hook-guard.ts
27971
- import { promises as fs32 } from "fs";
28529
+ import { promises as fs33 } from "fs";
27972
28530
  import path46 from "path";
27973
28531
 
27974
28532
  // domains/comet-entry/hook-adapter.ts
@@ -28133,7 +28691,7 @@ async function activeNativeContext(projectRoot) {
28133
28691
  const paths = await nativeProjectPaths(projectRoot, config.native.artifact_root);
28134
28692
  let entries;
28135
28693
  try {
28136
- entries = await fs32.readdir(paths.changesDir, { withFileTypes: true });
28694
+ entries = await fs33.readdir(paths.changesDir, { withFileTypes: true });
28137
28695
  } catch (error) {
28138
28696
  if (error.code === "ENOENT") return { paths, changes: [] };
28139
28697
  throw error;
@@ -28254,6 +28812,7 @@ Commands:
28254
28812
  select <change-name>
28255
28813
  checkpoint <change-name> --summary <text> --next-action <text> [--artifact <project-relative>] [--expect-revision <n>]
28256
28814
  check <change-name>
28815
+ evidence format [--entries <path>]
28257
28816
  next <change-name> --summary <text> [--confirmed] [--artifact <path>] [--no-code-reason <text>] [--allow-partial-scope <sha256> --partial-reason <text>] [--result pass|fail] [--report <path>] [--receipt <change-relative-ref>] [--failure-category <token>] [--failed-check <token>] [--override-repair <sha256> --override-summary <text>]
28258
28817
  archive <change-name> --dry-run
28259
28818
  archive <change-name> --expect-preflight <sha256>
@@ -28334,6 +28893,44 @@ async function doctorPaths(projectRoot) {
28334
28893
  function success(command, data, text) {
28335
28894
  return { command, exitCode: 0, data, text: text ?? JSON.stringify(data, null, 2) + "\n" };
28336
28895
  }
28896
+ async function readBoundedEvidenceFile(filePath, maxBytes) {
28897
+ try {
28898
+ const { bytes } = await readFileRaceSafe(filePath, maxBytes, {
28899
+ label: "Acceptance evidence entries file"
28900
+ });
28901
+ return bytes.toString("utf8");
28902
+ } catch (error) {
28903
+ if (error instanceof RaceSafeReadError) {
28904
+ if (error.reason === "not-regular-file") {
28905
+ throw new Error(`Acceptance evidence entries path is not a regular file: ${filePath}`, {
28906
+ cause: error
28907
+ });
28908
+ }
28909
+ if (error.reason === "too-large") {
28910
+ throw new Error(`Acceptance evidence entries file exceeds ${maxBytes} bytes: ${filePath}`, {
28911
+ cause: error
28912
+ });
28913
+ }
28914
+ throw new Error(`Acceptance evidence entries file changed while reading: ${filePath}`, {
28915
+ cause: error
28916
+ });
28917
+ }
28918
+ throw error;
28919
+ }
28920
+ }
28921
+ async function readBoundedEvidenceStdin(maxBytes) {
28922
+ const chunks = [];
28923
+ let total = 0;
28924
+ for await (const chunk of process.stdin) {
28925
+ const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(String(chunk));
28926
+ total += buffer.byteLength;
28927
+ if (total > maxBytes) {
28928
+ throw new Error(`Acceptance evidence entries on stdin exceed ${maxBytes} bytes`);
28929
+ }
28930
+ chunks.push(buffer);
28931
+ }
28932
+ return Buffer.concat(chunks).toString("utf8");
28933
+ }
28337
28934
  async function dispatch(rawArgs, explicitProjectRoot) {
28338
28935
  if (rawArgs.length === 0 || rawArgs[0] === "--help" || rawArgs[0] === "help") {
28339
28936
  return { command: rawArgs[0] ?? null, exitCode: 0, data: { usage: USAGE }, text: USAGE };
@@ -28439,7 +29036,9 @@ async function dispatch(rawArgs, explicitProjectRoot) {
28439
29036
  await ensureNativeDirectories(paths);
28440
29037
  const state = await createNativeChange({ paths, name, language });
28441
29038
  await selectNativeChange(paths, state.name);
28442
- const status = await inspectNativeStatus(paths, state.name);
29039
+ const status = await inspectNativeStatus(paths, state.name, {
29040
+ clarificationMode: config.native.clarification_mode
29041
+ });
28443
29042
  return success(
28444
29043
  "new",
28445
29044
  { ...state, continuation: status.continuation },
@@ -28453,9 +29052,11 @@ async function dispatch(rawArgs, explicitProjectRoot) {
28453
29052
  const name = requiredPositional(rawArgs, "change name");
28454
29053
  const capability = requiredPositional(rawArgs, "capability");
28455
29054
  assertNoArguments(rawArgs);
28456
- const { paths } = await configuredPaths(projectRoot);
29055
+ const { config, paths } = await configuredPaths(projectRoot);
28457
29056
  const state = await markNativeSpecRemoval(paths, name, capability);
28458
- const status = await inspectNativeStatus(paths, state.name);
29057
+ const status = await inspectNativeStatus(paths, state.name, {
29058
+ clarificationMode: config.native.clarification_mode
29059
+ });
28459
29060
  return success(
28460
29061
  "spec remove",
28461
29062
  { ...state, continuation: status.continuation },
@@ -28468,9 +29069,11 @@ async function dispatch(rawArgs, explicitProjectRoot) {
28468
29069
  const summary = takeOption(rawArgs, "--summary");
28469
29070
  if (!summary) throw new NativeUsageError("--summary is required");
28470
29071
  assertNoArguments(rawArgs);
28471
- const { paths } = await configuredPaths(projectRoot);
29072
+ const { config, paths } = await configuredPaths(projectRoot);
28472
29073
  const state = await rebaseNativeSpecChanges({ paths, name, summary });
28473
- const status = await inspectNativeStatus(paths, state.name);
29074
+ const status = await inspectNativeStatus(paths, state.name, {
29075
+ clarificationMode: config.native.clarification_mode
29076
+ });
28474
29077
  return success(
28475
29078
  "spec rebase",
28476
29079
  { ...state, continuation: status.continuation },
@@ -28483,8 +29086,11 @@ async function dispatch(rawArgs, explicitProjectRoot) {
28483
29086
  if (command === "list") {
28484
29087
  const cursor = takeOption(rawArgs, "--cursor");
28485
29088
  assertNoArguments(rawArgs);
28486
- const { paths } = await configuredPaths(projectRoot);
28487
- const page = await listNativeStatusPage(paths, cursor ? { cursor } : void 0);
29089
+ const { config, paths } = await configuredPaths(projectRoot);
29090
+ const page = await listNativeStatusPage(paths, {
29091
+ ...cursor ? { cursor } : {},
29092
+ clarificationMode: config.native.clarification_mode
29093
+ });
28488
29094
  return success("list", page);
28489
29095
  }
28490
29096
  if (command === "show") {
@@ -28540,19 +29146,25 @@ async function dispatch(rawArgs, explicitProjectRoot) {
28540
29146
  throw new NativeUsageError("--acceptance-cursor requires a change name");
28541
29147
  }
28542
29148
  assertNoArguments(rawArgs);
28543
- const { paths } = await configuredPaths(projectRoot);
29149
+ const { config, paths } = await configuredPaths(projectRoot);
28544
29150
  const data = name ? await inspectNativeStatus(paths, name, {
28545
29151
  details,
28546
- ...acceptanceCursor2 ? { acceptanceCursor: acceptanceCursor2 } : {}
28547
- }) : await listNativeStatusPage(paths, cursor ? { cursor } : void 0);
29152
+ ...acceptanceCursor2 ? { acceptanceCursor: acceptanceCursor2 } : {},
29153
+ clarificationMode: config.native.clarification_mode
29154
+ }) : await listNativeStatusPage(paths, {
29155
+ ...cursor ? { cursor } : {},
29156
+ clarificationMode: config.native.clarification_mode
29157
+ });
28548
29158
  return success("status", data);
28549
29159
  }
28550
29160
  if (command === "select") {
28551
29161
  const name = requiredPositional(rawArgs, "change name");
28552
29162
  assertNoArguments(rawArgs);
28553
- const { paths } = await configuredPaths(projectRoot);
29163
+ const { config, paths } = await configuredPaths(projectRoot);
28554
29164
  await selectNativeChange(paths, name);
28555
- const status = await inspectNativeStatus(paths, name);
29165
+ const status = await inspectNativeStatus(paths, name, {
29166
+ clarificationMode: config.native.clarification_mode
29167
+ });
28556
29168
  return success(
28557
29169
  "select",
28558
29170
  { selected: name, continuation: status.continuation },
@@ -28569,7 +29181,7 @@ async function dispatch(rawArgs, explicitProjectRoot) {
28569
29181
  const artifacts = takeMany(rawArgs, "--artifact");
28570
29182
  const expectedRevision = revisionOption(rawArgs);
28571
29183
  assertNoArguments(rawArgs);
28572
- const { paths } = await configuredPaths(projectRoot);
29184
+ const { config, paths } = await configuredPaths(projectRoot);
28573
29185
  const result2 = await checkpointNativeChange({
28574
29186
  paths,
28575
29187
  name,
@@ -28578,7 +29190,9 @@ async function dispatch(rawArgs, explicitProjectRoot) {
28578
29190
  artifacts,
28579
29191
  expectedRevision
28580
29192
  });
28581
- const status = await inspectNativeStatus(paths, name);
29193
+ const status = await inspectNativeStatus(paths, name, {
29194
+ clarificationMode: config.native.clarification_mode
29195
+ });
28582
29196
  const manifestRef = path47.relative(
28583
29197
  paths.projectRoot,
28584
29198
  path47.join(nativeChangeDir(paths, name), ...result2.checkpoint.manifestRef.split("/"))
@@ -28617,6 +29231,43 @@ async function dispatch(rawArgs, explicitProjectRoot) {
28617
29231
  `
28618
29232
  };
28619
29233
  }
29234
+ if (command === "evidence") {
29235
+ const subcommand = requiredPositional(rawArgs, "evidence subcommand");
29236
+ if (subcommand === "format") {
29237
+ const entriesPath = takeOption(rawArgs, "--entries");
29238
+ assertNoArguments(rawArgs);
29239
+ let raw;
29240
+ if (entriesPath) {
29241
+ raw = await readBoundedEvidenceFile(
29242
+ path47.resolve(entriesPath),
29243
+ MAX_NATIVE_IMPLEMENTATION_EVIDENCE_DOCUMENT_BYTES
29244
+ );
29245
+ } else {
29246
+ if (process.stdin.isTTY) {
29247
+ throw new NativeUsageError(
29248
+ "evidence format requires acceptance evidence entries JSON on stdin, or --entries <path>"
29249
+ );
29250
+ }
29251
+ raw = await readBoundedEvidenceStdin(MAX_NATIVE_IMPLEMENTATION_EVIDENCE_DOCUMENT_BYTES);
29252
+ }
29253
+ let entries;
29254
+ try {
29255
+ entries = JSON.parse(raw);
29256
+ } catch (error) {
29257
+ throw new Error(
29258
+ `Acceptance evidence entries must be valid JSON: ${error.message}`,
29259
+ { cause: error }
29260
+ );
29261
+ }
29262
+ if (!Array.isArray(entries)) {
29263
+ throw new Error("Acceptance evidence entries must be a JSON array");
29264
+ }
29265
+ const block = serializeNativeVerificationMachineBlock(entries);
29266
+ return success("evidence format", { block }, `${block}
29267
+ `);
29268
+ }
29269
+ throw new NativeUsageError(`Unknown evidence command: ${subcommand}`);
29270
+ }
28620
29271
  if (command === "next") {
28621
29272
  const name = requiredPositional(rawArgs, "change name");
28622
29273
  const summary = takeOption(rawArgs, "--summary");
@@ -28665,7 +29316,7 @@ async function dispatch(rawArgs, explicitProjectRoot) {
28665
29316
  throw new NativeUsageError("--override-repair cannot be combined with --result");
28666
29317
  }
28667
29318
  assertNoArguments(rawArgs);
28668
- const { paths } = await configuredPaths(projectRoot);
29319
+ const { config, paths } = await configuredPaths(projectRoot);
28669
29320
  const evidence = {
28670
29321
  summary,
28671
29322
  ...confirmed ? { confirmed: true } : {},
@@ -28681,7 +29332,12 @@ async function dispatch(rawArgs, explicitProjectRoot) {
28681
29332
  ...repairOverrideSignature ? { repairOverrideSignature } : {},
28682
29333
  ...repairOverrideSummary ? { repairOverrideSummary } : {}
28683
29334
  };
28684
- const result2 = await advanceNativeChange({ paths, name, evidence });
29335
+ const result2 = await advanceNativeChange({
29336
+ paths,
29337
+ name,
29338
+ evidence,
29339
+ clarificationMode: config.native.clarification_mode
29340
+ });
28685
29341
  if (result2.next === "manual") {
28686
29342
  const repairBlocked = result2.repair?.disposition === "manual-stop" || result2.repair?.disposition === "hard-stop" || result2.findings.some(
28687
29343
  (finding) => [
@@ -28700,7 +29356,9 @@ async function dispatch(rawArgs, explicitProjectRoot) {
28700
29356
  }
28701
29357
  };
28702
29358
  }
28703
- const status = await inspectNativeStatus(paths, name);
29359
+ const status = await inspectNativeStatus(paths, name, {
29360
+ clarificationMode: config.native.clarification_mode
29361
+ });
28704
29362
  return success("next", { ...result2, continuation: status.continuation });
28705
29363
  }
28706
29364
  if (command === "archive") {
@@ -28817,6 +29475,13 @@ function errorResult(command, error) {
28817
29475
  omittedByReason: error.omittedByReason,
28818
29476
  samplePaths: error.samplePaths,
28819
29477
  sampleTruncated: error.sampleTruncated,
29478
+ effectiveLimits: error.effectiveLimits,
29479
+ policyHash: error.policyHash,
29480
+ configPath: ".comet/config.yaml",
29481
+ supportedFixes: [
29482
+ "increase native.snapshot.max_total_bytes or native.snapshot.max_duration_ms",
29483
+ "add an explicit native.snapshot.exclude pattern for data outside implementation scope"
29484
+ ],
28820
29485
  requiredAction: "resolve-native-baseline"
28821
29486
  },
28822
29487
  error: { code: "baseline-incomplete", message: error.message }