@rpamis/comet 0.4.0-beta.8 → 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 (128) hide show
  1. package/README.md +1 -1
  2. package/assets/manifest.json +1 -1
  3. package/assets/skills/comet/reference/comet-yaml-fields.md +2 -2
  4. package/assets/skills/comet/scripts/comet-entry-runtime.mjs +95 -4
  5. package/assets/skills/comet/scripts/comet-hook-router.mjs +224 -35
  6. package/assets/skills/comet/scripts/comet-runtime.mjs +218 -102
  7. package/assets/skills/comet-archive/SKILL.md +29 -20
  8. package/assets/skills/comet-native/SKILL.md +23 -15
  9. package/assets/skills/comet-native/reference/artifacts.md +19 -4
  10. package/assets/skills/comet-native/reference/commands.md +8 -2
  11. package/assets/skills/comet-native/reference/recovery.md +1 -1
  12. package/assets/skills/comet-native/scripts/comet-native-runtime.mjs +1052 -458
  13. package/assets/skills-zh/comet/reference/comet-yaml-fields.md +2 -2
  14. package/assets/skills-zh/comet-archive/SKILL.md +29 -20
  15. package/assets/skills-zh/comet-native/SKILL.md +23 -15
  16. package/assets/skills-zh/comet-native/reference/artifacts.md +20 -5
  17. package/assets/skills-zh/comet-native/reference/commands.md +8 -2
  18. package/assets/skills-zh/comet-native/reference/recovery.md +1 -1
  19. package/dist/app/commands/init.d.ts.map +1 -1
  20. package/dist/app/commands/init.js +3 -6
  21. package/dist/app/commands/init.js.map +1 -1
  22. package/dist/domains/comet-entry/current-selection.d.ts.map +1 -1
  23. package/dist/domains/comet-entry/current-selection.js +6 -8
  24. package/dist/domains/comet-entry/current-selection.js.map +1 -1
  25. package/dist/domains/comet-entry/project-status.d.ts.map +1 -1
  26. package/dist/domains/comet-entry/project-status.js +5 -1
  27. package/dist/domains/comet-entry/project-status.js.map +1 -1
  28. package/dist/domains/comet-native/native-build-evidence.js +2 -2
  29. package/dist/domains/comet-native/native-build-evidence.js.map +1 -1
  30. package/dist/domains/comet-native/native-change.d.ts +4 -2
  31. package/dist/domains/comet-native/native-change.d.ts.map +1 -1
  32. package/dist/domains/comet-native/native-change.js +18 -4
  33. package/dist/domains/comet-native/native-change.js.map +1 -1
  34. package/dist/domains/comet-native/native-check-receipt.d.ts.map +1 -1
  35. package/dist/domains/comet-native/native-check-receipt.js +5 -3
  36. package/dist/domains/comet-native/native-check-receipt.js.map +1 -1
  37. package/dist/domains/comet-native/native-cli.d.ts.map +1 -1
  38. package/dist/domains/comet-native/native-cli.js +120 -16
  39. package/dist/domains/comet-native/native-cli.js.map +1 -1
  40. package/dist/domains/comet-native/native-config.d.ts +5 -1
  41. package/dist/domains/comet-native/native-config.d.ts.map +1 -1
  42. package/dist/domains/comet-native/native-config.js +78 -0
  43. package/dist/domains/comet-native/native-config.js.map +1 -1
  44. package/dist/domains/comet-native/native-continuation.d.ts +2 -1
  45. package/dist/domains/comet-native/native-continuation.d.ts.map +1 -1
  46. package/dist/domains/comet-native/native-continuation.js +13 -5
  47. package/dist/domains/comet-native/native-continuation.js.map +1 -1
  48. package/dist/domains/comet-native/native-diagnostics.d.ts +7 -3
  49. package/dist/domains/comet-native/native-diagnostics.d.ts.map +1 -1
  50. package/dist/domains/comet-native/native-diagnostics.js +19 -9
  51. package/dist/domains/comet-native/native-diagnostics.js.map +1 -1
  52. package/dist/domains/comet-native/native-file-identity.d.ts +2 -8
  53. package/dist/domains/comet-native/native-file-identity.d.ts.map +1 -1
  54. package/dist/domains/comet-native/native-file-identity.js +1 -20
  55. package/dist/domains/comet-native/native-file-identity.js.map +1 -1
  56. package/dist/domains/comet-native/native-findings.d.ts.map +1 -1
  57. package/dist/domains/comet-native/native-findings.js +19 -1
  58. package/dist/domains/comet-native/native-findings.js.map +1 -1
  59. package/dist/domains/comet-native/native-guards.d.ts +2 -1
  60. package/dist/domains/comet-native/native-guards.d.ts.map +1 -1
  61. package/dist/domains/comet-native/native-guards.js +14 -0
  62. package/dist/domains/comet-native/native-guards.js.map +1 -1
  63. package/dist/domains/comet-native/native-lock.d.ts.map +1 -1
  64. package/dist/domains/comet-native/native-lock.js +24 -32
  65. package/dist/domains/comet-native/native-lock.js.map +1 -1
  66. package/dist/domains/comet-native/native-root-move.d.ts.map +1 -1
  67. package/dist/domains/comet-native/native-root-move.js +2 -0
  68. package/dist/domains/comet-native/native-root-move.js.map +1 -1
  69. package/dist/domains/comet-native/native-snapshot.d.ts +5 -1
  70. package/dist/domains/comet-native/native-snapshot.d.ts.map +1 -1
  71. package/dist/domains/comet-native/native-snapshot.js +218 -2
  72. package/dist/domains/comet-native/native-snapshot.js.map +1 -1
  73. package/dist/domains/comet-native/native-transitions.d.ts +2 -1
  74. package/dist/domains/comet-native/native-transitions.d.ts.map +1 -1
  75. package/dist/domains/comet-native/native-transitions.js +34 -11
  76. package/dist/domains/comet-native/native-transitions.js.map +1 -1
  77. package/dist/domains/comet-native/native-types.d.ts +16 -0
  78. package/dist/domains/comet-native/native-types.d.ts.map +1 -1
  79. package/dist/domains/comet-native/native-verification-runtime.d.ts.map +1 -1
  80. package/dist/domains/comet-native/native-verification-runtime.js +5 -3
  81. package/dist/domains/comet-native/native-verification-runtime.js.map +1 -1
  82. package/dist/domains/comet-native/native-verification-scope.d.ts +2 -1
  83. package/dist/domains/comet-native/native-verification-scope.d.ts.map +1 -1
  84. package/dist/domains/comet-native/native-verification-scope.js +6 -1
  85. package/dist/domains/comet-native/native-verification-scope.js.map +1 -1
  86. package/dist/domains/dashboard/native-collector.d.ts.map +1 -1
  87. package/dist/domains/dashboard/native-collector.js +4 -1
  88. package/dist/domains/dashboard/native-collector.js.map +1 -1
  89. package/dist/domains/skill/platform-install.d.ts.map +1 -1
  90. package/dist/domains/skill/platform-install.js +9 -0
  91. package/dist/domains/skill/platform-install.js.map +1 -1
  92. package/dist/domains/skill/project-instructions.d.ts.map +1 -1
  93. package/dist/domains/skill/project-instructions.js +4 -2
  94. package/dist/domains/skill/project-instructions.js.map +1 -1
  95. package/dist/domains/workflow-contract/project-config.d.ts +1 -1
  96. package/dist/domains/workflow-contract/project-config.d.ts.map +1 -1
  97. package/dist/domains/workflow-contract/project-config.js +24 -2
  98. package/dist/domains/workflow-contract/project-config.js.map +1 -1
  99. package/dist/platform/fs/file-identity.d.ts +9 -0
  100. package/dist/platform/fs/file-identity.d.ts.map +1 -0
  101. package/dist/platform/fs/file-identity.js +21 -0
  102. package/dist/platform/fs/file-identity.js.map +1 -0
  103. package/dist/platform/fs/file-system.d.ts.map +1 -1
  104. package/dist/platform/fs/file-system.js +13 -9
  105. package/dist/platform/fs/file-system.js.map +1 -1
  106. package/dist/platform/fs/race-safe-read.d.ts +47 -0
  107. package/dist/platform/fs/race-safe-read.d.ts.map +1 -0
  108. package/dist/platform/fs/race-safe-read.js +132 -0
  109. package/dist/platform/fs/race-safe-read.js.map +1 -0
  110. package/eval/README.md +1 -1
  111. package/eval/local/tasks/comet-native-clarification-depth/environment/Dockerfile +27 -0
  112. package/eval/local/tasks/comet-native-clarification-depth/environment/test_wordcount.py +25 -0
  113. package/eval/local/tasks/comet-native-clarification-depth/environment/wordcount.py +36 -0
  114. package/eval/local/tasks/comet-native-clarification-depth/instruction.md +17 -0
  115. package/eval/local/tasks/comet-native-clarification-depth/task.toml +47 -0
  116. package/eval/local/tasks/comet-native-clarification-depth/validation/test_native_clarification_depth.py +618 -0
  117. package/eval/local/tasks/comet-native-clarification-modes/validation/test_native_clarification_modes.py +1 -1
  118. package/eval/local/tasks/index.yaml +6 -0
  119. package/eval/local/tests/conftest.py +146 -2
  120. package/eval/local/tests/scaffold/test_conftest_helpers.py +188 -1
  121. package/eval/local/tests/scaffold/test_tasks.py +440 -8
  122. package/eval/local/tests/scaffold/test_utils.py +114 -1
  123. package/eval/local/tests/tasks/test_tasks.py +2 -0
  124. package/eval/scaffold/python/aligned_comparison.py +1 -0
  125. package/eval/scaffold/python/tasks.py +2 -0
  126. package/eval/scaffold/shell/docker.sh +1 -1
  127. package/eval/scaffold/shell/run-claude-loop.sh +25 -2
  128. package/package.json +1 -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
 
@@ -7586,22 +7704,6 @@ function nativeSensitiveArtifactReason(paths, relativeRef) {
7586
7704
  return null;
7587
7705
  }
7588
7706
 
7589
- // domains/comet-native/native-file-identity.ts
7590
- function hasPlatformIdentity(value) {
7591
- return value !== 0 && value !== 0n && value !== "0";
7592
- }
7593
- function hasComparableNativeFileObject(left, right) {
7594
- return hasPlatformIdentity(left.dev) && hasPlatformIdentity(right.dev) && hasPlatformIdentity(left.ino) && hasPlatformIdentity(right.ino);
7595
- }
7596
- function sameNativeFileObject(left, right) {
7597
- const comparableDevice = hasPlatformIdentity(left.dev) && hasPlatformIdentity(right.dev);
7598
- if (comparableDevice && left.dev !== right.dev) return false;
7599
- const comparableInode = hasPlatformIdentity(left.ino) && hasPlatformIdentity(right.ino);
7600
- if (comparableInode && left.ino !== right.ino) return false;
7601
- if (comparableDevice && comparableInode) return true;
7602
- return left.birthtime === right.birthtime;
7603
- }
7604
-
7605
7707
  // domains/comet-native/native-bounded-file.ts
7606
7708
  var DEFAULT_NATIVE_ARTIFACT_MAX_BYTES = 1024 * 1024;
7607
7709
  function isInside(parent, target) {
@@ -7632,7 +7734,7 @@ function positiveLimit(value) {
7632
7734
  return value;
7633
7735
  }
7634
7736
  function sameDirectoryIdentity(identity, stat) {
7635
- return sameNativeFileObject(
7737
+ return sameFileObject(
7636
7738
  { ...identity, birthtime: identity.birthtimeMs },
7637
7739
  {
7638
7740
  ...stat,
@@ -7643,19 +7745,19 @@ function sameDirectoryIdentity(identity, stat) {
7643
7745
  function sameFileIdentity(left, right) {
7644
7746
  const leftObject = { ...left, birthtime: left.birthtimeMs };
7645
7747
  const rightObject = { ...right, birthtime: right.birthtimeMs };
7646
- if (hasComparableNativeFileObject(leftObject, rightObject)) {
7647
- return sameNativeFileObject(leftObject, rightObject);
7748
+ if (hasComparableFileObject(leftObject, rightObject)) {
7749
+ return sameFileObject(leftObject, rightObject);
7648
7750
  }
7649
- return sameNativeFileObject(leftObject, rightObject) && 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;
7650
7752
  }
7651
7753
  async function directoryIdentity(directory) {
7652
- const stat = await fs.lstat(directory);
7754
+ const stat = await fs2.lstat(directory);
7653
7755
  if (!stat.isDirectory() || stat.isSymbolicLink()) {
7654
7756
  throw new Error(`Native artifact parent must be a real directory: ${directory}`);
7655
7757
  }
7656
7758
  return {
7657
7759
  path: directory,
7658
- realPath: await fs.realpath(directory),
7760
+ realPath: await fs2.realpath(directory),
7659
7761
  dev: stat.dev,
7660
7762
  ino: stat.ino,
7661
7763
  birthtimeMs: stat.birthtimeMs
@@ -7681,8 +7783,8 @@ async function captureDirectoryChain(root, directory) {
7681
7783
  }
7682
7784
  async function verifyDirectoryChain(chain) {
7683
7785
  for (const identity of chain) {
7684
- const stat = await fs.lstat(identity.path);
7685
- 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) {
7686
7788
  throw new Error(`Native artifact parent changed while reading: ${identity.path}`);
7687
7789
  }
7688
7790
  }
@@ -7693,21 +7795,21 @@ async function readNativeBoundedTextFile(options) {
7693
7795
  const file = path2.resolve(options.root, ...ref.split("/"));
7694
7796
  const chain = await captureDirectoryChain(options.root, path2.dirname(file));
7695
7797
  await options.hooks?.afterParentChainCaptured?.();
7696
- const before = await fs.lstat(file);
7798
+ const before = await fs2.lstat(file);
7697
7799
  if (!before.isFile() || before.isSymbolicLink()) {
7698
7800
  throw new Error(`Native artifact must be a regular file: ${ref}`);
7699
7801
  }
7700
7802
  if (before.size > maxBytes) throw new Error(`Native artifact exceeds ${maxBytes} bytes: ${ref}`);
7701
- const realPath = await fs.realpath(file);
7803
+ const realPath = await fs2.realpath(file);
7702
7804
  if (!isInside(chain[0].realPath, realPath)) {
7703
7805
  throw new Error(`Native artifact resolves outside its root: ${ref}`);
7704
7806
  }
7705
- const handle = await fs.open(file, "r");
7807
+ const handle = await fs2.open(file, "r");
7706
7808
  try {
7707
7809
  const [opened, afterOpenPath, afterOpenRealPath] = await Promise.all([
7708
7810
  handle.stat(),
7709
- fs.lstat(file),
7710
- fs.realpath(file)
7811
+ fs2.lstat(file),
7812
+ fs2.realpath(file)
7711
7813
  ]);
7712
7814
  await verifyDirectoryChain(chain);
7713
7815
  if (!opened.isFile() || !afterOpenPath.isFile() || afterOpenPath.isSymbolicLink() || afterOpenRealPath !== realPath || !sameFileIdentity(before, opened) || !sameFileIdentity(opened, afterOpenPath)) {
@@ -7728,8 +7830,8 @@ async function readNativeBoundedTextFile(options) {
7728
7830
  await options.hooks?.beforeFinalCheck?.();
7729
7831
  const [afterHandle, afterPath, afterRealPath] = await Promise.all([
7730
7832
  handle.stat(),
7731
- fs.lstat(file),
7732
- fs.realpath(file)
7833
+ fs2.lstat(file),
7834
+ fs2.realpath(file)
7733
7835
  ]);
7734
7836
  await verifyDirectoryChain(chain);
7735
7837
  if (!afterPath.isFile() || afterPath.isSymbolicLink() || afterRealPath !== realPath || !sameFileIdentity(opened, afterHandle) || !sameFileIdentity(opened, afterPath)) {
@@ -7755,14 +7857,14 @@ async function readNativeBoundedTextFile(options) {
7755
7857
 
7756
7858
  // domains/comet-native/native-atomic-file.ts
7757
7859
  import { randomUUID } from "crypto";
7758
- import { promises as fs2 } from "fs";
7860
+ import { promises as fs3 } from "fs";
7759
7861
  import path3 from "path";
7760
7862
  function isInside2(parent, target) {
7761
7863
  const relative = path3.relative(parent, target);
7762
7864
  return relative === "" || !path3.isAbsolute(relative) && relative !== ".." && !relative.startsWith(`..${path3.sep}`);
7763
7865
  }
7764
7866
  function sameDirectoryIdentity2(identity, stat) {
7765
- return sameNativeFileObject(
7867
+ return sameFileObject(
7766
7868
  { ...identity, birthtime: identity.birthtimeMs },
7767
7869
  {
7768
7870
  ...stat,
@@ -7773,19 +7875,19 @@ function sameDirectoryIdentity2(identity, stat) {
7773
7875
  function sameFileIdentity2(left, right) {
7774
7876
  const leftObject = { ...left, birthtime: left.birthtimeMs };
7775
7877
  const rightObject = { ...right, birthtime: right.birthtimeMs };
7776
- if (hasComparableNativeFileObject(leftObject, rightObject)) {
7777
- return sameNativeFileObject(leftObject, rightObject);
7878
+ if (hasComparableFileObject(leftObject, rightObject)) {
7879
+ return sameFileObject(leftObject, rightObject);
7778
7880
  }
7779
- return sameNativeFileObject(leftObject, rightObject) && 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;
7780
7882
  }
7781
7883
  async function captureDirectoryIdentity(directory) {
7782
- const stat = await fs2.lstat(directory);
7884
+ const stat = await fs3.lstat(directory);
7783
7885
  if (!stat.isDirectory() || stat.isSymbolicLink()) {
7784
7886
  throw new Error(`Native atomic write parent must be a real directory: ${directory}`);
7785
7887
  }
7786
7888
  return {
7787
7889
  path: directory,
7788
- realPath: await fs2.realpath(directory),
7890
+ realPath: await fs3.realpath(directory),
7789
7891
  dev: stat.dev,
7790
7892
  ino: stat.ino,
7791
7893
  birthtimeMs: stat.birthtimeMs
@@ -7793,8 +7895,8 @@ async function captureDirectoryIdentity(directory) {
7793
7895
  }
7794
7896
  async function verifyDirectoryChain2(chain) {
7795
7897
  for (const identity of chain) {
7796
- const stat = await fs2.lstat(identity.path);
7797
- 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) {
7798
7900
  throw new Error(`Native atomic write parent changed before commit: ${identity.path}`);
7799
7901
  }
7800
7902
  }
@@ -7812,7 +7914,7 @@ async function prepareContainedDirectoryChain(root, directory) {
7812
7914
  await verifyDirectoryChain2(chain);
7813
7915
  cursor = path3.join(cursor, segment);
7814
7916
  try {
7815
- await fs2.mkdir(cursor);
7917
+ await fs3.mkdir(cursor);
7816
7918
  } catch (error) {
7817
7919
  if (error.code !== "EEXIST") throw error;
7818
7920
  }
@@ -7828,7 +7930,7 @@ async function prepareContainedDirectoryChain(root, directory) {
7828
7930
  async function syncDirectory(directory) {
7829
7931
  let handle;
7830
7932
  try {
7831
- handle = await fs2.open(directory, "r");
7933
+ handle = await fs3.open(directory, "r");
7832
7934
  await handle.sync();
7833
7935
  } catch (error) {
7834
7936
  const code = error.code;
@@ -7842,18 +7944,18 @@ async function syncDirectory(directory) {
7842
7944
  async function atomicWrite(file, content, options = {}) {
7843
7945
  const directory = path3.dirname(file);
7844
7946
  const directoryChain = options.containedRoot ? await prepareContainedDirectoryChain(options.containedRoot, directory) : null;
7845
- if (!directoryChain) await fs2.mkdir(directory, { recursive: true });
7947
+ if (!directoryChain) await fs3.mkdir(directory, { recursive: true });
7846
7948
  const temporary = path3.join(directory, `.${path3.basename(file)}.${randomUUID()}.tmp`);
7847
7949
  let handle;
7848
7950
  let temporaryIdentity;
7849
7951
  try {
7850
7952
  await options.beforeTemporaryOpen?.();
7851
- handle = await fs2.open(temporary, "wx");
7953
+ handle = await fs3.open(temporary, "wx");
7852
7954
  temporaryIdentity = await handle.stat();
7853
7955
  if (directoryChain) {
7854
7956
  const [temporaryPathStat, temporaryRealPath] = await Promise.all([
7855
- fs2.lstat(temporary),
7856
- fs2.realpath(temporary)
7957
+ fs3.lstat(temporary),
7958
+ fs3.realpath(temporary)
7857
7959
  ]);
7858
7960
  await verifyDirectoryChain2(directoryChain);
7859
7961
  if (!temporaryPathStat.isFile() || temporaryPathStat.isSymbolicLink() || !sameFileIdentity2(temporaryIdentity, temporaryPathStat) || !isInside2(directoryChain[0].realPath, temporaryRealPath)) {
@@ -7871,26 +7973,26 @@ async function atomicWrite(file, content, options = {}) {
7871
7973
  await options.beforeCommit?.();
7872
7974
  if (directoryChain) {
7873
7975
  await verifyDirectoryChain2(directoryChain);
7874
- const temporaryStat = await fs2.lstat(temporary);
7976
+ const temporaryStat = await fs3.lstat(temporary);
7875
7977
  if (!temporaryStat.isFile() || temporaryStat.isSymbolicLink() || !temporaryIdentity || !sameFileIdentity2(temporaryStat, temporaryIdentity)) {
7876
7978
  throw new Error("Native atomic write temporary file changed before commit");
7877
7979
  }
7878
7980
  }
7879
7981
  if (options.exclusive) {
7880
- await fs2.link(temporary, file);
7881
- await fs2.unlink(temporary);
7982
+ await fs3.link(temporary, file);
7983
+ await fs3.unlink(temporary);
7882
7984
  } else {
7883
- await fs2.rename(temporary, file);
7985
+ await fs3.rename(temporary, file);
7884
7986
  }
7885
7987
  await syncDirectory(directory);
7886
7988
  } catch (error) {
7887
7989
  await handle?.close();
7888
7990
  if (!directoryChain) {
7889
- await fs2.rm(temporary, { force: true });
7991
+ await fs3.rm(temporary, { force: true });
7890
7992
  } else {
7891
7993
  try {
7892
7994
  await verifyDirectoryChain2(directoryChain);
7893
- await fs2.rm(temporary, { force: true });
7995
+ await fs3.rm(temporary, { force: true });
7894
7996
  } catch {
7895
7997
  }
7896
7998
  }
@@ -7909,7 +8011,7 @@ async function atomicWriteJson(file, value, options = {}) {
7909
8011
 
7910
8012
  // domains/comet-native/native-config.ts
7911
8013
  var import_yaml2 = __toESM(require_dist(), 1);
7912
- import { promises as fs5 } from "fs";
8014
+ import { promises as fs6 } from "fs";
7913
8015
  import path6 from "path";
7914
8016
 
7915
8017
  // domains/workflow-contract/project-config.ts
@@ -7924,6 +8026,12 @@ var COMMENTS = {
7924
8026
  "native.artifact_root": "# Root directory where Native stores Comet specs, changes, and runtime data.",
7925
8027
  "native.language": "# Artifact language used by Native workflow documents.\n# language: en | zh-CN",
7926
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.",
7927
8035
  classic: "# Classic workflow settings. They do not change Native state or behavior.",
7928
8036
  "classic.language": "# Artifact language used by Classic workflow documents.\n# language: en | zh-CN",
7929
8037
  "classic.context_compression": "# Controls beta context compression for new Classic changes.\n# context_compression: off | beta",
@@ -7939,6 +8047,12 @@ var COMMENTS = {
7939
8047
  "native.artifact_root": "# Native 产物的存放根目录,包括规格、change 和运行时数据。",
7940
8048
  "native.language": "# Native 工作流文档使用的产物语言。\n# 可选值:en | zh-CN",
7941
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": "# 单次快照的最长执行时间(毫秒);较慢或更大的仓库应与字节预算一并提高。",
7942
8056
  classic: "# Classic 工作流配置,不会改变 Native 的状态或行为。",
7943
8057
  "classic.language": "# Classic 工作流文档使用的产物语言。\n# 可选值:en | zh-CN",
7944
8058
  "classic.context_compression": "# 新建 Classic change 是否启用 beta 上下文压缩。\n# 可选值:off | beta",
@@ -7949,7 +8063,7 @@ var COMMENTS = {
7949
8063
  function projectConfigComment(key, language) {
7950
8064
  return COMMENTS[language][key];
7951
8065
  }
7952
- function commentKey(line, block) {
8066
+ function commentKey(line, block, nativeNested) {
7953
8067
  const match = /^(\s*)([a-z_]+):/u.exec(line);
7954
8068
  if (!match) return null;
7955
8069
  const indent = match[1].length;
@@ -7959,13 +8073,18 @@ function commentKey(line, block) {
7959
8073
  const blockKey = `${block}.${key}`;
7960
8074
  if (blockKey in COMMENTS.en) return blockKey;
7961
8075
  }
8076
+ if (indent === 4 && block === "native" && nativeNested === "snapshot") {
8077
+ const nestedKey = `native.snapshot.${key}`;
8078
+ if (nestedKey in COMMENTS.en) return nestedKey;
8079
+ }
7962
8080
  return null;
7963
8081
  }
7964
8082
  function renderStructuredProjectConfig(value, language) {
7965
8083
  const output = [];
7966
8084
  let block = null;
8085
+ let nativeNested = null;
7967
8086
  for (const line of (0, import_yaml.stringify)(value).trimEnd().split("\n")) {
7968
- const key = commentKey(line, block);
8087
+ const key = commentKey(line, block, nativeNested);
7969
8088
  if (key) {
7970
8089
  const indent = line.match(/^\s*/u)?.[0] ?? "";
7971
8090
  for (const comment of projectConfigComment(key, language).split("\n")) {
@@ -7977,6 +8096,9 @@ function renderStructuredProjectConfig(value, language) {
7977
8096
  if (line.startsWith("native:")) block = "native";
7978
8097
  else if (line.startsWith("classic:")) block = "classic";
7979
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;
7980
8102
  }
7981
8103
  }
7982
8104
  output.push("");
@@ -7985,7 +8107,7 @@ function renderStructuredProjectConfig(value, language) {
7985
8107
 
7986
8108
  // domains/comet-native/native-protected-file.ts
7987
8109
  import { createHash as createHash3 } from "node:crypto";
7988
- import { constants as fsConstants, promises as fs3 } from "node:fs";
8110
+ import { constants as fsConstants2, promises as fs4 } from "node:fs";
7989
8111
  import path4 from "node:path";
7990
8112
  import { TextDecoder as TextDecoder2 } from "node:util";
7991
8113
  function isInside3(parent, target) {
@@ -7999,7 +8121,7 @@ function positiveLimit2(value) {
7999
8121
  return value;
8000
8122
  }
8001
8123
  function sameDirectoryIdentity3(expected, actual) {
8002
- return sameNativeFileObject(
8124
+ return sameFileObject(
8003
8125
  { ...expected, birthtime: expected.birthtimeMs },
8004
8126
  {
8005
8127
  ...actual,
@@ -8018,7 +8140,7 @@ function asFileIdentity(stat) {
8018
8140
  };
8019
8141
  }
8020
8142
  function sameFileIdentity3(expected, actual) {
8021
- return sameNativeFileObject(
8143
+ return sameFileObject(
8022
8144
  { ...expected, birthtime: expected.birthtimeMs },
8023
8145
  {
8024
8146
  ...actual,
@@ -8027,13 +8149,13 @@ function sameFileIdentity3(expected, actual) {
8027
8149
  ) && expected.birthtimeMs === actual.birthtimeMs && expected.ctimeMs === actual.ctimeMs && expected.mtimeMs === actual.mtimeMs && expected.size === actual.size;
8028
8150
  }
8029
8151
  async function captureDirectoryIdentity2(directory, label) {
8030
- const stat = await fs3.lstat(directory);
8152
+ const stat = await fs4.lstat(directory);
8031
8153
  if (!stat.isDirectory() || stat.isSymbolicLink()) {
8032
8154
  throw new Error(`${label} parent must be a real directory: ${directory}`);
8033
8155
  }
8034
8156
  return {
8035
8157
  path: directory,
8036
- realPath: await fs3.realpath(directory),
8158
+ realPath: await fs4.realpath(directory),
8037
8159
  dev: stat.dev,
8038
8160
  ino: stat.ino,
8039
8161
  birthtimeMs: stat.birthtimeMs
@@ -8061,8 +8183,8 @@ async function captureDirectoryChain2(root, directory, label) {
8061
8183
  }
8062
8184
  async function verifyDirectoryChain3(chain, label) {
8063
8185
  for (const identity of chain) {
8064
- const stat = await fs3.lstat(identity.path);
8065
- 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) {
8066
8188
  throw new Error(`${label} parent changed during I/O: ${identity.path}`);
8067
8189
  }
8068
8190
  }
@@ -8092,27 +8214,27 @@ async function readNativeProtectedFile(options) {
8092
8214
  );
8093
8215
  await options.hooks?.afterParentChainCaptured?.();
8094
8216
  await verifyDirectoryChain3(chain, options.label);
8095
- const before = await fs3.lstat(file);
8217
+ const before = await fs4.lstat(file);
8096
8218
  if (!before.isFile() || before.isSymbolicLink()) {
8097
8219
  throw new Error(`${options.label} must be a regular file`);
8098
8220
  }
8099
8221
  if (before.size > maxBytes) throw new Error(`${options.label} exceeds ${maxBytes} bytes`);
8100
8222
  const beforeIdentity = asFileIdentity(before);
8101
- const beforeRealPath = await fs3.realpath(file);
8223
+ const beforeRealPath = await fs4.realpath(file);
8102
8224
  if (!isInside3(chain[0].realPath, beforeRealPath)) {
8103
8225
  throw new Error(`${options.label} resolves outside its managed root`);
8104
8226
  }
8105
8227
  if (forbidden.some((identity) => isInside3(identity.realPath, beforeRealPath))) {
8106
8228
  throw new Error(`${options.label} resolves inside an excluded root`);
8107
8229
  }
8108
- const flags = process.platform === "win32" ? fsConstants.O_RDONLY : fsConstants.O_RDONLY | fsConstants.O_NOFOLLOW | fsConstants.O_NONBLOCK;
8109
- 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);
8110
8232
  try {
8111
8233
  const opened = await handle.stat();
8112
8234
  await options.hooks?.afterOpen?.();
8113
8235
  const [pathAfterOpen, realPathAfterOpen] = await Promise.all([
8114
- fs3.lstat(file),
8115
- fs3.realpath(file)
8236
+ fs4.lstat(file),
8237
+ fs4.realpath(file)
8116
8238
  ]);
8117
8239
  await verifyDirectoryChain3(chain, options.label);
8118
8240
  await verifyDirectoryChain3(forbidden, options.label);
@@ -8124,8 +8246,8 @@ async function readNativeProtectedFile(options) {
8124
8246
  await options.hooks?.beforeFinalCheck?.();
8125
8247
  const [afterHandle, afterPath, afterRealPath] = await Promise.all([
8126
8248
  handle.stat(),
8127
- fs3.lstat(file),
8128
- fs3.realpath(file)
8249
+ fs4.lstat(file),
8250
+ fs4.realpath(file)
8129
8251
  ]);
8130
8252
  await verifyDirectoryChain3(chain, options.label);
8131
8253
  await verifyDirectoryChain3(forbidden, options.label);
@@ -8155,11 +8277,11 @@ async function readNativeProtectedDirectory(options) {
8155
8277
  const chain = await captureDirectoryChain2(options.root, options.directory, options.label);
8156
8278
  let entries;
8157
8279
  if (options.maxEntries === void 0) {
8158
- entries = await fs3.readdir(options.directory, { withFileTypes: true });
8280
+ entries = await fs4.readdir(options.directory, { withFileTypes: true });
8159
8281
  } else {
8160
8282
  const maxEntries = positiveLimit2(options.maxEntries);
8161
8283
  entries = [];
8162
- const directory = await fs3.opendir(options.directory);
8284
+ const directory = await fs4.opendir(options.directory);
8163
8285
  try {
8164
8286
  for await (const entry2 of directory) {
8165
8287
  entries.push(entry2);
@@ -8200,25 +8322,25 @@ async function quarantineNativeProtectedDirectoryInternal(options) {
8200
8322
  }
8201
8323
  await options.beforeQuarantine?.();
8202
8324
  await verifyDirectoryChain3(parentChain, options.label);
8203
- const current = await fs3.lstat(directory);
8204
- 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) {
8205
8327
  throw new Error(`${options.label} changed before quarantine`);
8206
8328
  }
8207
8329
  try {
8208
- await fs3.lstat(quarantine);
8330
+ await fs4.lstat(quarantine);
8209
8331
  throw new Error(`${options.label} quarantine path is occupied`);
8210
8332
  } catch (error) {
8211
8333
  if (error.code !== "ENOENT") throw error;
8212
8334
  }
8213
- await fs3.rename(directory, quarantine);
8335
+ await fs4.rename(directory, quarantine);
8214
8336
  await verifyDirectoryChain3(parentChain, options.label);
8215
- const quarantined = await fs3.lstat(quarantine);
8337
+ const quarantined = await fs4.lstat(quarantine);
8216
8338
  if (!quarantined.isDirectory() || quarantined.isSymbolicLink() || !sameDirectoryIdentity3(identity, quarantined)) {
8217
8339
  throw new Error(`${options.label} changed while quarantining`);
8218
8340
  }
8219
8341
  await options.afterQuarantine?.(quarantine);
8220
8342
  try {
8221
- await fs3.lstat(directory);
8343
+ await fs4.lstat(directory);
8222
8344
  throw new Error(`${options.label} path was recreated while quarantining`);
8223
8345
  } catch (error) {
8224
8346
  if (error.code !== "ENOENT") throw error;
@@ -8231,12 +8353,12 @@ async function quarantineNativeProtectedDirectory(options) {
8231
8353
  async function removeNativeProtectedFile(options) {
8232
8354
  const file = path4.resolve(options.file);
8233
8355
  const parentChain = await captureDirectoryChain2(options.root, path4.dirname(file), options.label);
8234
- const before = await fs3.lstat(file);
8356
+ const before = await fs4.lstat(file);
8235
8357
  if (!before.isFile() || before.isSymbolicLink()) {
8236
8358
  throw new Error(`${options.label} must be a regular file`);
8237
8359
  }
8238
8360
  const identity = asFileIdentity(before);
8239
- const realPath = await fs3.realpath(file);
8361
+ const realPath = await fs4.realpath(file);
8240
8362
  if (!isInside3(parentChain[0].realPath, realPath)) {
8241
8363
  throw new Error(`${options.label} resolves outside its managed root`);
8242
8364
  }
@@ -8249,17 +8371,17 @@ async function removeNativeProtectedFile(options) {
8249
8371
  if (snapshot2.hash !== options.expectedHash || snapshot2.size !== options.expectedSize) {
8250
8372
  throw new Error(`${options.label} changed before removal`);
8251
8373
  }
8252
- 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)]);
8253
8375
  if (!afterRead.isFile() || afterRead.isSymbolicLink() || !sameFileIdentity3(identity, afterRead) || afterReadRealPath !== realPath) {
8254
8376
  throw new Error(`${options.label} changed while verifying removal`);
8255
8377
  }
8256
8378
  await options.beforeRemove?.();
8257
8379
  await verifyDirectoryChain3(parentChain, options.label);
8258
- 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)]);
8259
8381
  if (!current.isFile() || current.isSymbolicLink() || !sameFileIdentity3(identity, current) || currentRealPath !== realPath) {
8260
8382
  throw new Error(`${options.label} changed before removal`);
8261
8383
  }
8262
- await fs3.rm(file);
8384
+ await fs4.rm(file);
8263
8385
  await verifyDirectoryChain3(parentChain, options.label);
8264
8386
  }
8265
8387
  async function removeNativeProtectedEmptyDirectory(options) {
@@ -8275,11 +8397,11 @@ async function removeNativeProtectedEmptyDirectory(options) {
8275
8397
  }
8276
8398
  await options.beforeRemove?.();
8277
8399
  await verifyDirectoryChain3(parentChain, options.label);
8278
- const current = await fs3.lstat(directory);
8279
- 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) {
8280
8402
  throw new Error(`${options.label} changed before removal`);
8281
8403
  }
8282
- await fs3.rmdir(directory);
8404
+ await fs4.rmdir(directory);
8283
8405
  await verifyDirectoryChain3(parentChain, options.label);
8284
8406
  }
8285
8407
  async function ensureNativeProtectedDirectory(options) {
@@ -8294,7 +8416,7 @@ async function ensureNativeProtectedDirectory(options) {
8294
8416
  await verifyDirectoryChain3(chain, options.label);
8295
8417
  cursor = path4.join(cursor, segment);
8296
8418
  try {
8297
- await fs3.mkdir(cursor);
8419
+ await fs4.mkdir(cursor);
8298
8420
  } catch (error) {
8299
8421
  if (error.code !== "EEXIST") throw error;
8300
8422
  }
@@ -8325,7 +8447,7 @@ async function moveNativeProtectedDirectory(options) {
8325
8447
  throw new Error(`${options.label} source resolves outside its managed root`);
8326
8448
  }
8327
8449
  try {
8328
- await fs3.lstat(target);
8450
+ await fs4.lstat(target);
8329
8451
  throw new Error(`${options.label} target already exists`);
8330
8452
  } catch (error) {
8331
8453
  if (error.code !== "ENOENT") throw error;
@@ -8335,21 +8457,21 @@ async function moveNativeProtectedDirectory(options) {
8335
8457
  verifyDirectoryChain3(sourceParentChain, options.label),
8336
8458
  verifyDirectoryChain3(targetParentChain, options.label)
8337
8459
  ]);
8338
- const current = await fs3.lstat(source);
8339
- 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) {
8340
8462
  throw new Error(`${options.label} source changed before move`);
8341
8463
  }
8342
- await fs3.rename(source, target);
8464
+ await fs4.rename(source, target);
8343
8465
  await Promise.all([
8344
8466
  verifyDirectoryChain3(sourceParentChain, options.label),
8345
8467
  verifyDirectoryChain3(targetParentChain, options.label)
8346
8468
  ]);
8347
- const moved = await fs3.lstat(target);
8348
- 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))) {
8349
8471
  throw new Error(`${options.label} source identity changed while moving`);
8350
8472
  }
8351
8473
  try {
8352
- await fs3.lstat(source);
8474
+ await fs4.lstat(source);
8353
8475
  throw new Error(`${options.label} source was recreated while moving`);
8354
8476
  } catch (error) {
8355
8477
  if (error.code !== "ENOENT") throw error;
@@ -8375,7 +8497,7 @@ async function copyNativeProtectedFile(options) {
8375
8497
  if (options.expectedTargetHash === void 0) return;
8376
8498
  if (options.expectedTargetHash === null) {
8377
8499
  try {
8378
- await fs3.lstat(options.target);
8500
+ await fs4.lstat(options.target);
8379
8501
  } catch (error) {
8380
8502
  if (error.code === "ENOENT") return;
8381
8503
  throw error;
@@ -8406,13 +8528,13 @@ async function copyNativeProtectedFile(options) {
8406
8528
  }
8407
8529
 
8408
8530
  // domains/comet-native/native-paths.ts
8409
- import { promises as fs4 } from "fs";
8531
+ import { promises as fs5 } from "fs";
8410
8532
  import path5 from "path";
8411
8533
  import os from "os";
8412
8534
  var PROJECT_CONFIG_FILE = ".comet/config.yaml";
8413
8535
  async function isFileOrDirectory(target) {
8414
8536
  try {
8415
- await fs4.lstat(target);
8537
+ await fs5.lstat(target);
8416
8538
  return true;
8417
8539
  } catch (error) {
8418
8540
  if (error.code === "ENOENT") return false;
@@ -8421,7 +8543,7 @@ async function isFileOrDirectory(target) {
8421
8543
  }
8422
8544
  async function declaresNativeProjectConfig(target) {
8423
8545
  try {
8424
- const source = await fs4.readFile(target, "utf8");
8546
+ const source = await fs5.readFile(target, "utf8");
8425
8547
  return /^schema:\s*comet\.project\.v1\s*$/mu.test(source);
8426
8548
  } catch (error) {
8427
8549
  if (error.code === "ENOENT") return false;
@@ -8441,12 +8563,12 @@ async function physicalPath(target) {
8441
8563
  missing.push(path5.basename(cursor));
8442
8564
  cursor = parent;
8443
8565
  }
8444
- const existing = await fs4.realpath(cursor);
8566
+ const existing = await fs5.realpath(cursor);
8445
8567
  return path5.resolve(existing, ...missing.reverse());
8446
8568
  }
8447
8569
  async function isSymbolicLink(target) {
8448
8570
  try {
8449
- return (await fs4.lstat(target)).isSymbolicLink();
8571
+ return (await fs5.lstat(target)).isSymbolicLink();
8450
8572
  } catch (error) {
8451
8573
  if (error.code === "ENOENT") return false;
8452
8574
  throw error;
@@ -8455,7 +8577,7 @@ async function isSymbolicLink(target) {
8455
8577
  async function discoverNativeProject(startPath) {
8456
8578
  let cursor = path5.resolve(startPath);
8457
8579
  try {
8458
- if (!(await fs4.stat(cursor)).isDirectory()) cursor = path5.dirname(cursor);
8580
+ if (!(await fs5.stat(cursor)).isDirectory()) cursor = path5.dirname(cursor);
8459
8581
  } catch (error) {
8460
8582
  if (error.code !== "ENOENT") throw error;
8461
8583
  }
@@ -8494,7 +8616,7 @@ function normalizeArtifactRootRef(value) {
8494
8616
  async function resolveArtifactRoot(projectRoot, value) {
8495
8617
  const normalized = normalizeArtifactRootRef(value);
8496
8618
  const lexical = path5.resolve(projectRoot, ...normalized.split("/"));
8497
- const physicalProject = await fs4.realpath(projectRoot);
8619
+ const physicalProject = await fs5.realpath(projectRoot);
8498
8620
  const physicalTarget = await physicalPath(lexical);
8499
8621
  if (!inside(physicalProject, physicalTarget)) {
8500
8622
  throw new Error("native.artifact_root resolves outside the project root");
@@ -8540,7 +8662,7 @@ async function ensureNativeDirectories(paths) {
8540
8662
  await Promise.all(
8541
8663
  directories.map(async (directory) => {
8542
8664
  await resolveContainedNativePath(paths.nativeRoot, directory);
8543
- await fs4.mkdir(directory, { recursive: true });
8665
+ await fs5.mkdir(directory, { recursive: true });
8544
8666
  })
8545
8667
  );
8546
8668
  }
@@ -8571,11 +8693,100 @@ var NATIVE_KEYS = /* @__PURE__ */ new Set([
8571
8693
  "artifact_root",
8572
8694
  "language",
8573
8695
  "clarification_mode",
8696
+ "snapshot",
8574
8697
  "pending_root_move"
8575
8698
  ]);
8699
+ var SNAPSHOT_KEYS = /* @__PURE__ */ new Set([
8700
+ "include",
8701
+ "exclude",
8702
+ "max_files",
8703
+ "max_total_bytes",
8704
+ "max_duration_ms"
8705
+ ]);
8576
8706
  var PENDING_KEYS = /* @__PURE__ */ new Set(["id", "from_artifact_root", "to_artifact_root", "stage", "cleanup"]);
8577
8707
  var NATIVE_PROJECT_CONFIG_MAX_BYTES = 64 * 1024;
8578
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
+ }
8579
8790
  function record(value, label) {
8580
8791
  if (!value || typeof value !== "object" || Array.isArray(value)) {
8581
8792
  throw new Error(`${label} must be a mapping`);
@@ -8661,6 +8872,7 @@ function parseConfig(value) {
8661
8872
  throw new Error("native.clarification_mode must be sequential or batch");
8662
8873
  }
8663
8874
  const pending = parsePending(native.pending_root_move);
8875
+ const snapshot2 = parseSnapshot(native.snapshot);
8664
8876
  return {
8665
8877
  schema: "comet.project.v1",
8666
8878
  default_workflow: root.default_workflow,
@@ -8670,6 +8882,7 @@ function parseConfig(value) {
8670
8882
  artifact_root: normalizeArtifactRootRef(native.artifact_root),
8671
8883
  language,
8672
8884
  clarification_mode: clarificationMode,
8885
+ snapshot: snapshot2,
8673
8886
  ...pending ? { pending_root_move: pending } : {}
8674
8887
  }
8675
8888
  };
@@ -8682,7 +8895,8 @@ function defaultProjectConfig(artifactRoot = "docs", language = "en") {
8682
8895
  native: {
8683
8896
  artifact_root: normalizeArtifactRootRef(artifactRoot),
8684
8897
  language,
8685
- clarification_mode: "sequential"
8898
+ clarification_mode: "sequential",
8899
+ snapshot: { ...DEFAULT_NATIVE_SNAPSHOT_CONFIG, include: ["**/*"], exclude: [] }
8686
8900
  }
8687
8901
  };
8688
8902
  }
@@ -8690,7 +8904,7 @@ async function readProjectConfig(projectRoot) {
8690
8904
  const canonical = path6.join(projectRoot, ...PROJECT_CONFIG_FILE.split("/"));
8691
8905
  const file = canonical;
8692
8906
  try {
8693
- await fs5.lstat(file);
8907
+ await fs6.lstat(file);
8694
8908
  } catch (error) {
8695
8909
  if (error.code === "ENOENT") return null;
8696
8910
  throw error;
@@ -8751,6 +8965,7 @@ async function writeProjectConfig(projectRoot, config) {
8751
8965
  artifact_root: config.native.artifact_root,
8752
8966
  language: config.native.language,
8753
8967
  clarification_mode: config.native.clarification_mode,
8968
+ snapshot: config.native.snapshot,
8754
8969
  ...config.native.pending_root_move ? {
8755
8970
  pending_root_move: {
8756
8971
  id: config.native.pending_root_move.id,
@@ -8778,6 +8993,7 @@ async function writeProjectConfig(projectRoot, config) {
8778
8993
  artifact_root: validated.native.artifact_root,
8779
8994
  language: validated.native.language,
8780
8995
  clarification_mode: validated.native.clarification_mode,
8996
+ snapshot: validated.native.snapshot,
8781
8997
  ...validated.native.pending_root_move ? {
8782
8998
  pending_root_move: {
8783
8999
  id: validated.native.pending_root_move.id,
@@ -8796,7 +9012,7 @@ async function writeProjectConfig(projectRoot, config) {
8796
9012
  }
8797
9013
  };
8798
9014
  const canonical = path6.join(projectRoot, ...PROJECT_CONFIG_FILE.split("/"));
8799
- await fs5.mkdir(path6.dirname(canonical), { recursive: true });
9015
+ await fs6.mkdir(path6.dirname(canonical), { recursive: true });
8800
9016
  await atomicWriteText(
8801
9017
  canonical,
8802
9018
  renderStructuredProjectConfig(document, validated.native.language === "zh-CN" ? "zh-CN" : "en")
@@ -8825,13 +9041,13 @@ async function resolveNativeProject(options) {
8825
9041
  }
8826
9042
 
8827
9043
  // domains/comet-native/native-mutation-lock.ts
8828
- import { promises as fs8 } from "fs";
9044
+ import { promises as fs9 } from "fs";
8829
9045
  import path9 from "path";
8830
9046
 
8831
9047
  // domains/comet-native/native-lock.ts
8832
9048
  import { AsyncLocalStorage } from "async_hooks";
8833
9049
  import { randomUUID as randomUUID2 } from "crypto";
8834
- import { promises as fs6 } from "fs";
9050
+ import { promises as fs7 } from "fs";
8835
9051
  import os2 from "os";
8836
9052
  import path7 from "path";
8837
9053
  var NATIVE_LOCK_MAX_BYTES = 16 * 1024;
@@ -8866,10 +9082,10 @@ function nativeLockFileIdentity(stat) {
8866
9082
  function sameNativeLockObject(left, right) {
8867
9083
  const leftObject = { dev: left.device, ino: left.inode, birthtime: left.birthtimeNs };
8868
9084
  const rightObject = { dev: right.device, ino: right.inode, birthtime: right.birthtimeNs };
8869
- if (hasComparableNativeFileObject(leftObject, rightObject)) {
8870
- return sameNativeFileObject(leftObject, rightObject);
9085
+ if (hasComparableFileObject(leftObject, rightObject)) {
9086
+ return sameFileObject(leftObject, rightObject);
8871
9087
  }
8872
- return sameNativeFileObject(leftObject, rightObject) && left.size === right.size;
9088
+ return sameFileObject(leftObject, rightObject) && left.size === right.size;
8873
9089
  }
8874
9090
  function sameNativeLockVersion(left, right) {
8875
9091
  return sameNativeLockObject(left, right) && left.size === right.size && left.birthtimeNs === right.birthtimeNs && left.ctimeNs === right.ctimeNs && left.mtimeNs === right.mtimeNs;
@@ -8882,41 +9098,35 @@ function sameNativeLockDiagnosis(left, right) {
8882
9098
  return left.owner.id === right.owner.id && sameNativeLockVersion(left.identity, right.identity);
8883
9099
  }
8884
9100
  async function readNativeLockSnapshot(file) {
8885
- let handle;
9101
+ let bytes;
9102
+ let stat;
8886
9103
  try {
8887
- 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;
8888
9110
  } catch (error) {
8889
9111
  if (error.code === "ENOENT") return null;
8890
- throw error;
8891
- }
8892
- try {
8893
- const stat = await handle.stat({ bigint: true });
8894
- if (!stat.isFile() || stat.isSymbolicLink()) {
8895
- throw new Error(`Native lock must be a regular file: ${file}`);
8896
- }
8897
- if (stat.size > BigInt(NATIVE_LOCK_MAX_BYTES)) {
8898
- throw new Error(`Native lock metadata exceeds ${NATIVE_LOCK_MAX_BYTES} bytes: ${file}`);
8899
- }
8900
- const source = await handle.readFile({ encoding: "utf8" });
8901
- const pathStat = await fs6.lstat(file, { bigint: true });
8902
- if (pathStat.isSymbolicLink() || !pathStat.isFile()) {
8903
- throw new Error(`Native lock must be a regular file: ${file}`);
8904
- }
8905
- const identity = nativeLockFileIdentity(stat);
8906
- if (!sameNativeLockObject(identity, nativeLockFileIdentity(pathStat))) {
8907
- 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 });
8908
9122
  }
8909
- return {
8910
- file,
8911
- owner: parseNativeLockOwner(JSON.parse(source), file),
8912
- identity
8913
- };
8914
- } catch (error) {
8915
- if (error.code === "ENOENT") return null;
8916
9123
  throw error;
8917
- } finally {
8918
- await handle.close();
8919
9124
  }
9125
+ return {
9126
+ file,
9127
+ owner: parseNativeLockOwner(JSON.parse(bytes.toString("utf8")), file),
9128
+ identity: nativeLockFileIdentity(stat)
9129
+ };
8920
9130
  }
8921
9131
  async function readNativeLock(file) {
8922
9132
  return (await readNativeLockSnapshot(file))?.owner ?? null;
@@ -8935,13 +9145,13 @@ function diagnosisFromSnapshot(snapshot2) {
8935
9145
  }
8936
9146
  async function restoreQuarantinedNativeLock(quarantine, file) {
8937
9147
  try {
8938
- await fs6.lstat(file);
9148
+ await fs7.lstat(file);
8939
9149
  return;
8940
9150
  } catch (error) {
8941
9151
  if (error.code !== "ENOENT") throw error;
8942
9152
  }
8943
9153
  try {
8944
- await fs6.rename(quarantine, file);
9154
+ await fs7.rename(quarantine, file);
8945
9155
  } catch (error) {
8946
9156
  if (error.code !== "ENOENT") throw error;
8947
9157
  }
@@ -8955,13 +9165,13 @@ async function removeBoundNativeLock(expected, quarantineDir) {
8955
9165
  if (!sameNativeLockVersion(current.identity, expected.identity)) {
8956
9166
  throw new Error(`Native lock identity changed: ${expected.file}`);
8957
9167
  }
8958
- await fs6.mkdir(quarantineDir, { recursive: true });
9168
+ await fs7.mkdir(quarantineDir, { recursive: true });
8959
9169
  const quarantine = path7.join(
8960
9170
  quarantineDir,
8961
9171
  `${path7.basename(expected.file)}.${expected.owner.id}.${randomUUID2()}.removed`
8962
9172
  );
8963
9173
  try {
8964
- await fs6.rename(expected.file, quarantine);
9174
+ await fs7.rename(expected.file, quarantine);
8965
9175
  } catch (error) {
8966
9176
  if (error.code === "ENOENT") return "missing";
8967
9177
  throw error;
@@ -8971,7 +9181,7 @@ async function removeBoundNativeLock(expected, quarantineDir) {
8971
9181
  await restoreQuarantinedNativeLock(quarantine, expected.file);
8972
9182
  throw new Error(`Native lock changed before quarantine: ${expected.file}`);
8973
9183
  }
8974
- await fs6.rm(quarantine, { force: true });
9184
+ await fs7.rm(quarantine, { force: true });
8975
9185
  return "removed";
8976
9186
  }
8977
9187
  function newNativeLockOwner(operation) {
@@ -8986,7 +9196,7 @@ function newNativeLockOwner(operation) {
8986
9196
  async function writeNativeLockFile(file, owner) {
8987
9197
  let handle;
8988
9198
  try {
8989
- handle = await fs6.open(file, "wx");
9199
+ handle = await fs7.open(file, "wx");
8990
9200
  } catch (error) {
8991
9201
  if (error.code === "EEXIST") {
8992
9202
  const existing = await readNativeLock(file);
@@ -9007,32 +9217,32 @@ async function writeNativeLockFile(file, owner) {
9007
9217
  }
9008
9218
  async function publishNativeCoordinatorClaim(paths, operation) {
9009
9219
  const locksDir = await resolveContainedNativePath(paths.nativeRoot, paths.locksDir);
9010
- await fs6.mkdir(locksDir, { recursive: true });
9220
+ await fs7.mkdir(locksDir, { recursive: true });
9011
9221
  const coordinatorDir = await resolveContainedNativePath(
9012
9222
  paths.nativeRoot,
9013
9223
  path7.join(locksDir, NATIVE_LOCK_COORDINATOR_DIR)
9014
9224
  );
9015
- await fs6.mkdir(coordinatorDir, { recursive: true });
9225
+ await fs7.mkdir(coordinatorDir, { recursive: true });
9016
9226
  const owner = newNativeLockOwner(operation);
9017
9227
  const temporary = path7.join(coordinatorDir, `.${owner.id}.tmp`);
9018
9228
  const file = path7.join(coordinatorDir, `${owner.id}.claim`);
9019
9229
  try {
9020
9230
  const identity = await writeNativeLockFile(temporary, owner);
9021
- await fs6.rename(temporary, file);
9231
+ await fs7.rename(temporary, file);
9022
9232
  const published = await readNativeLockSnapshot(file);
9023
9233
  if (!published || !sameNativeLockObject(identity, published.identity)) {
9024
9234
  throw new Error(`Native lock coordinator claim changed while publishing: ${file}`);
9025
9235
  }
9026
9236
  return { file, nativeRoot: paths.nativeRoot, locksDir, owner, identity: published.identity };
9027
9237
  } finally {
9028
- await fs6.rm(temporary, { force: true });
9238
+ await fs7.rm(temporary, { force: true });
9029
9239
  }
9030
9240
  }
9031
9241
  async function hasNativeCoordinatorPredecessor(claim) {
9032
9242
  const coordinatorDir = path7.dirname(claim.file);
9033
9243
  let predecessor = false;
9034
9244
  const claimName = path7.basename(claim.file);
9035
- for (const entry2 of await fs6.readdir(coordinatorDir, { withFileTypes: true })) {
9245
+ for (const entry2 of await fs7.readdir(coordinatorDir, { withFileTypes: true })) {
9036
9246
  if (!entry2.isFile() || entry2.isSymbolicLink() || !entry2.name.endsWith(".claim")) continue;
9037
9247
  const file = path7.join(coordinatorDir, entry2.name);
9038
9248
  if (path7.resolve(file) === path7.resolve(claim.file)) continue;
@@ -9118,7 +9328,7 @@ async function withNativeLockRecovery(pathEntries, operation, work) {
9118
9328
  async function acquireNativeLock(paths, name, operation) {
9119
9329
  return withNativeLockCoordinator(paths, `acquire ${name}`, async () => {
9120
9330
  const locksDir = await resolveContainedNativePath(paths.nativeRoot, paths.locksDir);
9121
- await fs6.mkdir(locksDir, { recursive: true });
9331
+ await fs7.mkdir(locksDir, { recursive: true });
9122
9332
  const file = await resolveContainedNativePath(
9123
9333
  paths.nativeRoot,
9124
9334
  path7.join(locksDir, lockName(name))
@@ -9180,7 +9390,7 @@ async function takeOverNativeStaleLock(paths, file, expected) {
9180
9390
  }
9181
9391
 
9182
9392
  // domains/comet-native/native-transaction.ts
9183
- import { promises as fs7 } from "fs";
9393
+ import { promises as fs8 } from "fs";
9184
9394
  import path8 from "path";
9185
9395
  import { TextDecoder as TextDecoder3 } from "util";
9186
9396
  var JOURNAL_KEYS = /* @__PURE__ */ new Set([
@@ -9642,7 +9852,7 @@ async function resolveRef(paths, ref) {
9642
9852
  }
9643
9853
  async function exists(file) {
9644
9854
  try {
9645
- await fs7.access(file);
9855
+ await fs8.access(file);
9646
9856
  return true;
9647
9857
  } catch (error) {
9648
9858
  if (error.code === "ENOENT") return false;
@@ -9652,7 +9862,7 @@ async function exists(file) {
9652
9862
  async function readEventLogSnapshot(paths, id, options = {}) {
9653
9863
  const tx = await resolveNativeTransactionPaths(paths, id);
9654
9864
  try {
9655
- await fs7.lstat(tx.events);
9865
+ await fs8.lstat(tx.events);
9656
9866
  } catch (error) {
9657
9867
  if (error.code === "ENOENT") {
9658
9868
  return {
@@ -9732,7 +9942,7 @@ async function appendNativeTransactionEvent(paths, id, type, operationId) {
9732
9942
  ...operationId ? { operationId } : {}
9733
9943
  };
9734
9944
  parseEvent(event, event.sequence);
9735
- await fs7.mkdir(tx.directory, { recursive: true });
9945
+ await fs8.mkdir(tx.directory, { recursive: true });
9736
9946
  await atomicWriteText(tx.events, canonicalEventLogSource([...snapshot2.events, event]), {
9737
9947
  containedRoot: paths.nativeRoot,
9738
9948
  beforeCommit: () => assertEventLogSnapshotUnchanged(paths, id, snapshot2)
@@ -9745,8 +9955,8 @@ async function createNativeTransaction(paths, journal) {
9745
9955
  }
9746
9956
  journal = parseJournal(journal);
9747
9957
  const tx = await resolveNativeTransactionPaths(paths, journal.id);
9748
- await fs7.mkdir(tx.staged, { recursive: true });
9749
- await fs7.mkdir(tx.backups, { recursive: true });
9958
+ await fs8.mkdir(tx.staged, { recursive: true });
9959
+ await fs8.mkdir(tx.backups, { recursive: true });
9750
9960
  await atomicWriteJson(tx.journal, journal);
9751
9961
  await appendNativeTransactionEvent(paths, journal.id, "prepared");
9752
9962
  }
@@ -9970,7 +10180,7 @@ function nativeRootRef(paths, target) {
9970
10180
  async function hasUnfinishedTransaction(paths, allowedTransactionId) {
9971
10181
  let entries;
9972
10182
  try {
9973
- entries = await fs8.readdir(paths.transactionsDir, { withFileTypes: true });
10183
+ entries = await fs9.readdir(paths.transactionsDir, { withFileTypes: true });
9974
10184
  } catch (error) {
9975
10185
  if (error.code === "ENOENT") return false;
9976
10186
  throw error;
@@ -10046,7 +10256,7 @@ async function compareAndSwapNativeRevision(options) {
10046
10256
  // domains/comet-native/native-snapshot.ts
10047
10257
  import { createHash as createHash5 } from "crypto";
10048
10258
  import { spawn } from "node:child_process";
10049
- import { promises as fs9 } from "fs";
10259
+ import { promises as fs10 } from "fs";
10050
10260
  import path10 from "path";
10051
10261
 
10052
10262
  // domains/comet-native/native-hash.ts
@@ -10081,9 +10291,17 @@ var MANIFEST_KEYS = /* @__PURE__ */ new Set([
10081
10291
  "entries",
10082
10292
  "omitted",
10083
10293
  "omittedCount",
10084
- "omissionOverflow"
10294
+ "omissionOverflow",
10295
+ "policy"
10296
+ ]);
10297
+ var LIMIT_KEYS = /* @__PURE__ */ new Set([
10298
+ "maxFiles",
10299
+ "maxFileBytes",
10300
+ "maxTotalBytes",
10301
+ "maxManifestBytes",
10302
+ "maxDurationMs"
10085
10303
  ]);
10086
- var LIMIT_KEYS = /* @__PURE__ */ new Set(["maxFiles", "maxFileBytes", "maxTotalBytes", "maxManifestBytes"]);
10304
+ var POLICY_KEYS = /* @__PURE__ */ new Set(["schema", "include", "exclude", "hash"]);
10087
10305
  var CAPTURE_KEYS = /* @__PURE__ */ new Set(["provider", "gitSelection", "physicalSelection", "projection"]);
10088
10306
  var GIT_PROJECTION_KEYS = /* @__PURE__ */ new Set(["provider", "selection"]);
10089
10307
  var GIT_SELECTION_KEYS = /* @__PURE__ */ new Set([
@@ -10507,7 +10725,7 @@ async function hasGitMetadataBoundary(projectRoot) {
10507
10725
  let cursor = path10.resolve(projectRoot);
10508
10726
  while (true) {
10509
10727
  try {
10510
- await fs9.lstat(path10.join(cursor, ".git"));
10728
+ await fs10.lstat(path10.join(cursor, ".git"));
10511
10729
  return true;
10512
10730
  } catch (error) {
10513
10731
  if (error.code !== "ENOENT") throw error;
@@ -10736,7 +10954,7 @@ async function nativePhysicalSnapshotSelection(options) {
10736
10954
  if (!hasExecutionBudget()) return;
10737
10955
  let handle;
10738
10956
  try {
10739
- handle = await fs9.opendir(directory);
10957
+ handle = await fs10.opendir(directory);
10740
10958
  } catch (error) {
10741
10959
  if (!hasExecutionBudget()) return;
10742
10960
  if (!isUnreadableError(error) && !isChangedDuringReadError(error)) throw error;
@@ -10786,7 +11004,7 @@ async function nativePhysicalSnapshotSelection(options) {
10786
11004
  if (!hasExecutionBudget()) break;
10787
11005
  let stat;
10788
11006
  try {
10789
- stat = await fs9.lstat(target);
11007
+ stat = await fs10.lstat(target);
10790
11008
  } catch (error) {
10791
11009
  if (!hasExecutionBudget()) break;
10792
11010
  if (!isUnreadableError(error) && !isChangedDuringReadError(error)) throw error;
@@ -10809,7 +11027,7 @@ async function nativePhysicalSnapshotSelection(options) {
10809
11027
  if (!hasExecutionBudget()) break;
10810
11028
  let realDirectory;
10811
11029
  try {
10812
- realDirectory = await fs9.realpath(target);
11030
+ realDirectory = await fs10.realpath(target);
10813
11031
  } catch (error) {
10814
11032
  if (!hasExecutionBudget()) break;
10815
11033
  if (!isUnreadableError(error) && !isChangedDuringReadError(error)) throw error;
@@ -11000,6 +11218,133 @@ function isChangedDuringReadError(error) {
11000
11218
  function serializedManifestBytes(manifest) {
11001
11219
  return Buffer.byteLength(JSON.stringify(manifest, null, 2) + "\n");
11002
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
+ }
11003
11348
  function foldSnapshotOverflowHash(previous, kind, value) {
11004
11349
  const payload = JSON.stringify(value);
11005
11350
  return sha256Text(
@@ -11023,16 +11368,16 @@ function takeLastCompactableOmission(omissions) {
11023
11368
  function sameFileIdentity4(left, right) {
11024
11369
  const leftObject = { ...left, birthtime: left.birthtimeMs };
11025
11370
  const rightObject = { ...right, birthtime: right.birthtimeMs };
11026
- if (hasComparableNativeFileObject(leftObject, rightObject)) {
11027
- return sameNativeFileObject(leftObject, rightObject);
11371
+ if (hasComparableFileObject(leftObject, rightObject)) {
11372
+ return sameFileObject(leftObject, rightObject);
11028
11373
  }
11029
- return sameNativeFileObject(leftObject, rightObject) && 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;
11030
11375
  }
11031
11376
  async function sha256FileBounded(file, maxBytes, expected, execution) {
11032
11377
  if (!nativeSnapshotExecutionHasBudget(execution)) return { status: "budget-exhausted" };
11033
11378
  let handle;
11034
11379
  try {
11035
- handle = await fs9.open(file, "r");
11380
+ handle = await fs10.open(file, "r");
11036
11381
  } catch (error) {
11037
11382
  if (!nativeSnapshotExecutionHasBudget(execution)) return { status: "budget-exhausted" };
11038
11383
  throw error;
@@ -11367,8 +11712,28 @@ function parseNativeContentSnapshotManifest(value) {
11367
11712
  maxManifestBytes: positiveInteger(
11368
11713
  limitValue.maxManifestBytes,
11369
11714
  "Native snapshot maxManifestBytes"
11370
- )
11715
+ ),
11716
+ ...limitValue.maxDurationMs === void 0 ? {} : {
11717
+ maxDurationMs: positiveInteger(limitValue.maxDurationMs, "Native snapshot maxDurationMs")
11718
+ }
11371
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
+ }
11372
11737
  if (!Array.isArray(manifest.entries) || !Array.isArray(manifest.omitted)) {
11373
11738
  throw new Error("Native content snapshot entries and omissions must be arrays");
11374
11739
  }
@@ -11451,6 +11816,7 @@ function parseNativeContentSnapshotManifest(value) {
11451
11816
  createdAt: manifest.createdAt,
11452
11817
  complete: manifest.complete,
11453
11818
  limits,
11819
+ ...policy ? { policy } : {},
11454
11820
  entries,
11455
11821
  omitted,
11456
11822
  omittedCount,
@@ -11591,13 +11957,18 @@ function nativeBaselineManifestFile(paths, name) {
11591
11957
  return path10.join(changeDir, "runtime", "baseline-manifest.json");
11592
11958
  }
11593
11959
  async function createNativeContentSnapshot(paths, options = {}) {
11594
- const execution = createNativeSnapshotExecution(options);
11595
11960
  const limits = {
11596
11961
  maxFiles: options.limits?.maxFiles ?? DEFAULT_NATIVE_SNAPSHOT_LIMITS.maxFiles,
11597
11962
  maxFileBytes: options.limits?.maxFileBytes ?? DEFAULT_NATIVE_SNAPSHOT_LIMITS.maxFileBytes,
11598
11963
  maxTotalBytes: options.limits?.maxTotalBytes ?? DEFAULT_NATIVE_SNAPSHOT_LIMITS.maxTotalBytes,
11599
- 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 }
11600
11966
  };
11967
+ const policy = resolveSnapshotPolicy(options.policy);
11968
+ const execution = createNativeSnapshotExecution({
11969
+ ...options,
11970
+ deadlineMs: options.deadlineMs ?? limits.maxDurationMs
11971
+ });
11601
11972
  const gitSelectionLimits = resolveNativeGitSelectionLimits(options.gitSelectionLimits);
11602
11973
  const physicalSelectionLimits = resolveNativePhysicalSelectionLimits(
11603
11974
  options.physicalSelectionLimits
@@ -11606,9 +11977,9 @@ async function createNativeContentSnapshot(paths, options = {}) {
11606
11977
  throw new Error("Native snapshot limits must be positive");
11607
11978
  }
11608
11979
  const projectRoot = path10.resolve(paths.projectRoot);
11609
- const physicalProjectRoot = await fs9.realpath(projectRoot);
11980
+ const physicalProjectRoot = await fs10.realpath(projectRoot);
11610
11981
  const nativeRoot = path10.resolve(paths.nativeRoot);
11611
- const physicalNativeRoot = await fs9.realpath(nativeRoot);
11982
+ const physicalNativeRoot = await fs10.realpath(nativeRoot);
11612
11983
  const configFile = path10.resolve(paths.configFile);
11613
11984
  const selectionFile = path10.join(projectRoot, ".comet", "current-change.json");
11614
11985
  const denylist = normalizedDenylist(projectRoot, options.denylist ?? []);
@@ -11680,7 +12051,7 @@ async function createNativeContentSnapshot(paths, options = {}) {
11680
12051
  if (!nativeSnapshotExecutionHasBudget(execution)) return;
11681
12052
  let realTarget;
11682
12053
  try {
11683
- realTarget = await fs9.realpath(target);
12054
+ realTarget = await fs10.realpath(target);
11684
12055
  } catch (error) {
11685
12056
  if (!nativeSnapshotExecutionHasBudget(execution)) return;
11686
12057
  if (!isUnreadableError(error) && !isChangedDuringReadError(error)) throw error;
@@ -11721,9 +12092,9 @@ async function createNativeContentSnapshot(paths, options = {}) {
11721
12092
  return;
11722
12093
  }
11723
12094
  if (!nativeSnapshotExecutionHasBudget(execution)) return;
11724
- afterRealTarget = await fs9.realpath(target);
12095
+ afterRealTarget = await fs10.realpath(target);
11725
12096
  if (!nativeSnapshotExecutionHasBudget(execution)) return;
11726
- after = await fs9.lstat(target);
12097
+ after = await fs10.lstat(target);
11727
12098
  } catch (error) {
11728
12099
  if (!nativeSnapshotExecutionHasBudget(execution)) return;
11729
12100
  if (!isUnreadableError(error) && !isChangedDuringReadError(error)) throw error;
@@ -11757,11 +12128,11 @@ async function createNativeContentSnapshot(paths, options = {}) {
11757
12128
  let secondTarget;
11758
12129
  let after;
11759
12130
  try {
11760
- firstTarget = await fs9.readlink(target, { encoding: "buffer" });
12131
+ firstTarget = await fs10.readlink(target, { encoding: "buffer" });
11761
12132
  if (!nativeSnapshotExecutionHasBudget(execution)) return;
11762
- after = await fs9.lstat(target);
12133
+ after = await fs10.lstat(target);
11763
12134
  if (!nativeSnapshotExecutionHasBudget(execution)) return;
11764
- secondTarget = await fs9.readlink(target, { encoding: "buffer" });
12135
+ secondTarget = await fs10.readlink(target, { encoding: "buffer" });
11765
12136
  } catch (error) {
11766
12137
  if (!nativeSnapshotExecutionHasBudget(execution)) return;
11767
12138
  if (!isUnreadableError(error) && !isChangedDuringReadError(error)) throw error;
@@ -11805,9 +12176,9 @@ async function createNativeContentSnapshot(paths, options = {}) {
11805
12176
  let realTarget2;
11806
12177
  let stat2;
11807
12178
  try {
11808
- realTarget2 = await fs9.realpath(validation2.target);
12179
+ realTarget2 = await fs10.realpath(validation2.target);
11809
12180
  if (!nativeSnapshotExecutionHasBudget(execution)) return;
11810
- stat2 = await fs9.lstat(validation2.target);
12181
+ stat2 = await fs10.lstat(validation2.target);
11811
12182
  } catch (error) {
11812
12183
  if (!nativeSnapshotExecutionHasBudget(execution)) return;
11813
12184
  if (!isUnreadableError(error) && !isChangedDuringReadError(error)) throw error;
@@ -11834,9 +12205,9 @@ async function createNativeContentSnapshot(paths, options = {}) {
11834
12205
  let stat2;
11835
12206
  let rawTarget;
11836
12207
  try {
11837
- stat2 = await fs9.lstat(validation2.target);
12208
+ stat2 = await fs10.lstat(validation2.target);
11838
12209
  if (!nativeSnapshotExecutionHasBudget(execution)) return;
11839
- rawTarget = await fs9.readlink(validation2.target, { encoding: "buffer" });
12210
+ rawTarget = await fs10.readlink(validation2.target, { encoding: "buffer" });
11840
12211
  } catch (error) {
11841
12212
  if (!nativeSnapshotExecutionHasBudget(execution)) return;
11842
12213
  if (!isUnreadableError(error) && !isChangedDuringReadError(error)) throw error;
@@ -11862,9 +12233,9 @@ async function createNativeContentSnapshot(paths, options = {}) {
11862
12233
  let realTarget;
11863
12234
  let stat;
11864
12235
  try {
11865
- realTarget = await fs9.realpath(validation2.target);
12236
+ realTarget = await fs10.realpath(validation2.target);
11866
12237
  if (!nativeSnapshotExecutionHasBudget(execution)) return;
11867
- stat = await fs9.lstat(validation2.target);
12238
+ stat = await fs10.lstat(validation2.target);
11868
12239
  } catch {
11869
12240
  if (!nativeSnapshotExecutionHasBudget(execution)) return;
11870
12241
  invalidateCapturedEntry(relative, {
@@ -11920,7 +12291,7 @@ async function createNativeContentSnapshot(paths, options = {}) {
11920
12291
  for (const [relative, target] of capturedTrackedAbsences) {
11921
12292
  if (!nativeSnapshotExecutionHasBudget(execution)) return;
11922
12293
  try {
11923
- const stat = await fs9.lstat(target);
12294
+ const stat = await fs10.lstat(target);
11924
12295
  if (!nativeSnapshotExecutionHasBudget(execution)) return;
11925
12296
  omit({
11926
12297
  path: relative,
@@ -11956,10 +12327,12 @@ async function createNativeContentSnapshot(paths, options = {}) {
11956
12327
  for (const record8 of before.records) {
11957
12328
  if (record8.type !== "file" && record8.type !== "symlink") continue;
11958
12329
  if (remainingNativeSnapshotTime(execution) < 1) break;
12330
+ if (!snapshotPolicyIncludes(policy, record8.path, execution)) continue;
12331
+ if (remainingNativeSnapshotTime(execution) < 1) break;
11959
12332
  const target = path10.resolve(projectRoot, ...record8.path.split("/"));
11960
12333
  let stat;
11961
12334
  try {
11962
- stat = await fs9.lstat(target);
12335
+ stat = await fs10.lstat(target);
11963
12336
  } catch (error) {
11964
12337
  if (!nativeSnapshotExecutionHasBudget(execution)) break;
11965
12338
  if (!isUnreadableError(error) && !isChangedDuringReadError(error)) throw error;
@@ -12012,6 +12385,9 @@ async function createNativeContentSnapshot(paths, options = {}) {
12012
12385
  await options.gitSelectionHooks?.afterInitialSelection?.();
12013
12386
  for (const relative of selectionPaths(gitSelection)) {
12014
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;
12015
12391
  const target = path10.resolve(projectRoot, ...relative.split("/"));
12016
12392
  if (target === configFile || target === selectionFile || denylist.some((denied) => sameOrInside(denied, target))) {
12017
12393
  continue;
@@ -12024,7 +12400,7 @@ async function createNativeContentSnapshot(paths, options = {}) {
12024
12400
  let realGitlink;
12025
12401
  let gitlinkStat;
12026
12402
  try {
12027
- [realGitlink, gitlinkStat] = await Promise.all([fs9.realpath(target), fs9.lstat(target)]);
12403
+ [realGitlink, gitlinkStat] = await Promise.all([fs10.realpath(target), fs10.lstat(target)]);
12028
12404
  } catch {
12029
12405
  omit({
12030
12406
  path: relative,
@@ -12082,7 +12458,7 @@ async function createNativeContentSnapshot(paths, options = {}) {
12082
12458
  }
12083
12459
  let before;
12084
12460
  try {
12085
- before = await fs9.lstat(target);
12461
+ before = await fs10.lstat(target);
12086
12462
  } catch (error) {
12087
12463
  if (isChangedDuringReadError(error) && gitSelection.tracked.has(relative)) {
12088
12464
  capturedTrackedAbsences.set(relative, target);
@@ -12136,6 +12512,7 @@ async function createNativeContentSnapshot(paths, options = {}) {
12136
12512
  createdAt: (options.now ?? /* @__PURE__ */ new Date()).toISOString(),
12137
12513
  complete: omittedCount === 0,
12138
12514
  limits,
12515
+ ...policy ? { policy: policy.manifest } : {},
12139
12516
  entries,
12140
12517
  omitted,
12141
12518
  omittedCount,
@@ -12172,6 +12549,21 @@ async function createNativeContentSnapshot(paths, options = {}) {
12172
12549
  }
12173
12550
  return manifest;
12174
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
+ }
12175
12567
  async function writeNativeBaselineManifest(paths, name, manifest) {
12176
12568
  const file = nativeBaselineManifestFile(paths, name);
12177
12569
  await resolveContainedNativePath(paths.nativeRoot, file);
@@ -12220,7 +12612,7 @@ function assertRunStorageLayout(storage) {
12220
12612
 
12221
12613
  // domains/comet-native/native-run-store.ts
12222
12614
  import { createHash as createHash6 } from "node:crypto";
12223
- import { constants as fsConstants2, promises as fs10 } from "node:fs";
12615
+ import { constants as fsConstants3, promises as fs11 } from "node:fs";
12224
12616
  import path13 from "node:path";
12225
12617
  import { TextDecoder as TextDecoder4 } from "node:util";
12226
12618
 
@@ -12409,7 +12801,7 @@ function asIdentity(stat) {
12409
12801
  };
12410
12802
  }
12411
12803
  function sameFileIdentity5(expected, actual) {
12412
- return sameNativeFileObject(
12804
+ return sameFileObject(
12413
12805
  { ...expected, birthtime: expected.birthtimeMs },
12414
12806
  {
12415
12807
  ...actual,
@@ -12418,7 +12810,7 @@ function sameFileIdentity5(expected, actual) {
12418
12810
  ) && expected.birthtimeMs === actual.birthtimeMs && expected.ctimeMs === actual.ctimeMs && expected.mtimeMs === actual.mtimeMs && expected.size === actual.size;
12419
12811
  }
12420
12812
  function sameDirectoryIdentity4(expected, actual) {
12421
- return sameNativeFileObject(
12813
+ return sameFileObject(
12422
12814
  { ...expected, birthtime: expected.birthtimeMs },
12423
12815
  {
12424
12816
  ...actual,
@@ -12437,13 +12829,13 @@ function runFile(changeDir, kind, relativePath) {
12437
12829
  return target;
12438
12830
  }
12439
12831
  async function directoryIdentity2(directory) {
12440
- const stat = await fs10.lstat(directory);
12832
+ const stat = await fs11.lstat(directory);
12441
12833
  if (!stat.isDirectory() || stat.isSymbolicLink()) {
12442
12834
  throw new Error(`Native Run parent must be a real directory: ${directory}`);
12443
12835
  }
12444
12836
  return {
12445
12837
  path: directory,
12446
- realPath: await fs10.realpath(directory),
12838
+ realPath: await fs11.realpath(directory),
12447
12839
  dev: stat.dev,
12448
12840
  ino: stat.ino,
12449
12841
  birthtimeMs: stat.birthtimeMs
@@ -12475,8 +12867,8 @@ async function captureDirectoryChain3(root, directory) {
12475
12867
  }
12476
12868
  async function verifyDirectoryChain4(chain) {
12477
12869
  for (const identity of chain) {
12478
- const stat = await fs10.lstat(identity.path);
12479
- 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) {
12480
12872
  throw new Error(`Native Run parent changed during I/O: ${identity.path}`);
12481
12873
  }
12482
12874
  }
@@ -12501,7 +12893,7 @@ async function readProtectedText(changeDir, file, maxBytes, label, hooks) {
12501
12893
  await hooks?.afterParentChainCaptured?.();
12502
12894
  let before;
12503
12895
  try {
12504
- before = await fs10.lstat(file);
12896
+ before = await fs11.lstat(file);
12505
12897
  } catch (error) {
12506
12898
  if (error.code === "ENOENT") return null;
12507
12899
  throw error;
@@ -12511,22 +12903,22 @@ async function readProtectedText(changeDir, file, maxBytes, label, hooks) {
12511
12903
  }
12512
12904
  if (before.size > maxBytes) throw new Error(`${label} exceeds ${maxBytes} bytes`);
12513
12905
  const beforeIdentity = asIdentity(before);
12514
- const beforeRealPath = await fs10.realpath(file);
12906
+ const beforeRealPath = await fs11.realpath(file);
12515
12907
  if (!isInside4(chain[0].realPath, beforeRealPath)) {
12516
12908
  throw new Error(`${label} resolves outside its change`);
12517
12909
  }
12518
- 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;
12519
12911
  let handle;
12520
12912
  try {
12521
- handle = await fs10.open(file, flags);
12913
+ handle = await fs11.open(file, flags);
12522
12914
  } catch (error) {
12523
12915
  throw new Error(`${label} changed while opening`, { cause: error });
12524
12916
  }
12525
12917
  try {
12526
12918
  const [opened, pathAfterOpen, realPathAfterOpen] = await Promise.all([
12527
12919
  handle.stat(),
12528
- fs10.lstat(file),
12529
- fs10.realpath(file)
12920
+ fs11.lstat(file),
12921
+ fs11.realpath(file)
12530
12922
  ]);
12531
12923
  await verifyDirectoryChain4(chain);
12532
12924
  if (!opened.isFile() || !pathAfterOpen.isFile() || pathAfterOpen.isSymbolicLink() || realPathAfterOpen !== beforeRealPath || !sameFileIdentity5(beforeIdentity, opened) || !sameFileIdentity5(beforeIdentity, pathAfterOpen)) {
@@ -12537,8 +12929,8 @@ async function readProtectedText(changeDir, file, maxBytes, label, hooks) {
12537
12929
  await hooks?.beforeFinalCheck?.();
12538
12930
  const [afterHandle, afterPath, afterRealPath] = await Promise.all([
12539
12931
  handle.stat(),
12540
- fs10.lstat(file),
12541
- fs10.realpath(file)
12932
+ fs11.lstat(file),
12933
+ fs11.realpath(file)
12542
12934
  ]);
12543
12935
  await verifyDirectoryChain4(chain);
12544
12936
  if (!afterPath.isFile() || afterPath.isSymbolicLink() || afterRealPath !== beforeRealPath || !sameFileIdentity5(beforeIdentity, afterHandle) || !sameFileIdentity5(beforeIdentity, afterPath)) {
@@ -12563,13 +12955,13 @@ async function captureTarget(changeDir, file, label) {
12563
12955
  if (!chain) return { exists: false };
12564
12956
  let stat;
12565
12957
  try {
12566
- stat = await fs10.lstat(file);
12958
+ stat = await fs11.lstat(file);
12567
12959
  } catch (error) {
12568
12960
  if (error.code === "ENOENT") return { exists: false };
12569
12961
  throw error;
12570
12962
  }
12571
12963
  if (!stat.isFile() || stat.isSymbolicLink()) throw new Error(`${label} must be a regular file`);
12572
- const realPath = await fs10.realpath(file);
12964
+ const realPath = await fs11.realpath(file);
12573
12965
  if (!isInside4(chain[0].realPath, realPath))
12574
12966
  throw new Error(`${label} resolves outside its change`);
12575
12967
  await verifyDirectoryChain4(chain);
@@ -12583,12 +12975,12 @@ async function verifyTarget(changeDir, file, expected, label) {
12583
12975
  }
12584
12976
  let stat;
12585
12977
  try {
12586
- stat = await fs10.lstat(file);
12978
+ stat = await fs11.lstat(file);
12587
12979
  } catch (error) {
12588
12980
  if (error.code === "ENOENT" && !expected.exists) return;
12589
12981
  throw new Error(`${label} changed before commit`, { cause: error });
12590
12982
  }
12591
- 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) {
12592
12984
  throw new Error(`${label} changed before commit`);
12593
12985
  }
12594
12986
  await verifyDirectoryChain4(chain);
@@ -12897,7 +13289,7 @@ async function repairNativeTrajectoryTail(paths, name, hooks) {
12897
13289
 
12898
13290
  // domains/comet-native/native-workspace.ts
12899
13291
  import { createHash as createHash8 } from "node:crypto";
12900
- import { promises as fs11 } from "node:fs";
13292
+ import { promises as fs12 } from "node:fs";
12901
13293
  import path15 from "node:path";
12902
13294
  var HASH_PATTERN2 = /^[a-f0-9]{64}$/u;
12903
13295
  var MAX_WORKSPACE_IDENTITY_BYTES = 16 * 1024;
@@ -12936,8 +13328,8 @@ function identityHash(tag, value) {
12936
13328
  ${value}`).digest("hex");
12937
13329
  }
12938
13330
  async function physicalDirectoryIdentity(tag, value) {
12939
- const realPath = await fs11.realpath(value);
12940
- const stat = await fs11.lstat(realPath);
13331
+ const realPath = await fs12.realpath(value);
13332
+ const stat = await fs12.lstat(realPath);
12941
13333
  if (!stat.isDirectory() || stat.isSymbolicLink()) {
12942
13334
  throw new Error("Native workspace identity requires a real directory");
12943
13335
  }
@@ -12948,8 +13340,8 @@ ${stat.ino}
12948
13340
  ${stat.birthtimeMs}`);
12949
13341
  }
12950
13342
  async function directoryPathIdentity(tag, value) {
12951
- const realPath = await fs11.realpath(value);
12952
- const stat = await fs11.lstat(realPath);
13343
+ const realPath = await fs12.realpath(value);
13344
+ const stat = await fs12.lstat(realPath);
12953
13345
  if (!stat.isDirectory() || stat.isSymbolicLink()) {
12954
13346
  throw new Error("Native workspace identity requires a real directory");
12955
13347
  }
@@ -13216,15 +13608,17 @@ var NativeChangeRevisionConflictError = class extends Error {
13216
13608
  code = "native-change-revision-conflict";
13217
13609
  };
13218
13610
  var NativeBaselineIncompleteError = class extends Error {
13219
- constructor(change, omittedCount, omittedByReason, samplePaths, sampleTruncated) {
13611
+ constructor(change, omittedCount, omittedByReason, samplePaths, sampleTruncated, effectiveLimits = null, policyHash = null) {
13220
13612
  super(
13221
- `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.`
13222
13614
  );
13223
13615
  this.change = change;
13224
13616
  this.omittedCount = omittedCount;
13225
13617
  this.omittedByReason = omittedByReason;
13226
13618
  this.samplePaths = samplePaths;
13227
13619
  this.sampleTruncated = sampleTruncated;
13620
+ this.effectiveLimits = effectiveLimits;
13621
+ this.policyHash = policyHash;
13228
13622
  this.name = "NativeBaselineIncompleteError";
13229
13623
  }
13230
13624
  change;
@@ -13232,6 +13626,8 @@ var NativeBaselineIncompleteError = class extends Error {
13232
13626
  omittedByReason;
13233
13627
  samplePaths;
13234
13628
  sampleTruncated;
13629
+ effectiveLimits;
13630
+ policyHash;
13235
13631
  code = "native-baseline-incomplete";
13236
13632
  };
13237
13633
  var NATIVE_BRIEF_TEMPLATE = [
@@ -13588,7 +13984,7 @@ async function hasPendingNativeSchemaMigration(paths, name) {
13588
13984
  const file = path16.join(nativeChangeDir(paths, name), "runtime", "schema-migration.json");
13589
13985
  await resolveContainedNativePath(paths.nativeRoot, file);
13590
13986
  try {
13591
- await fs12.lstat(file);
13987
+ await fs13.lstat(file);
13592
13988
  return true;
13593
13989
  } catch (error) {
13594
13990
  if (error.code === "ENOENT") return false;
@@ -13599,7 +13995,7 @@ async function hasPendingNativeCheckpointRecovery(paths, name) {
13599
13995
  const file = path16.join(nativeChangeDir(paths, name), "runtime", "checkpoint-journal.json");
13600
13996
  await resolveContainedNativePath(paths.nativeRoot, file);
13601
13997
  try {
13602
- await fs12.lstat(file);
13998
+ await fs13.lstat(file);
13603
13999
  return true;
13604
14000
  } catch (error) {
13605
14001
  if (error.code === "ENOENT") return false;
@@ -13620,13 +14016,13 @@ async function createNativeChangeLocked(options) {
13620
14016
  let createdChangeDir = false;
13621
14017
  try {
13622
14018
  try {
13623
- await fs12.mkdir(changeDir, { recursive: false });
14019
+ await fs13.mkdir(changeDir, { recursive: false });
13624
14020
  createdChangeDir = true;
13625
14021
  } catch (error) {
13626
14022
  if (error.code === "ENOENT") {
13627
- await fs12.mkdir(options.paths.changesDir, { recursive: true });
14023
+ await fs13.mkdir(options.paths.changesDir, { recursive: true });
13628
14024
  try {
13629
- await fs12.mkdir(changeDir, { recursive: false });
14025
+ await fs13.mkdir(changeDir, { recursive: false });
13630
14026
  createdChangeDir = true;
13631
14027
  } catch (retryError) {
13632
14028
  if (retryError.code === "EEXIST") {
@@ -13663,13 +14059,23 @@ async function createNativeChangeLocked(options) {
13663
14059
  run_id: null
13664
14060
  };
13665
14061
  await Promise.all([
13666
- fs12.mkdir(path16.join(changeDir, "specs"), { recursive: true }),
13667
- 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 }),
13668
14064
  atomicWriteText(path16.join(changeDir, "brief.md"), NATIVE_BRIEF_TEMPLATE)
13669
14065
  ]);
14066
+ const projectConfig = await readProjectConfig(options.paths.projectRoot);
14067
+ const snapshot2 = projectConfig?.native.snapshot ?? DEFAULT_NATIVE_SNAPSHOT_CONFIG;
13670
14068
  const baseline = await createNativeContentSnapshot(options.paths, {
13671
14069
  now: options.now,
13672
- 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
13673
14079
  });
13674
14080
  if (!baseline.complete) {
13675
14081
  const health = inspectNativeContentSnapshotHealth(baseline);
@@ -13684,7 +14090,9 @@ async function createNativeChangeLocked(options) {
13684
14090
  baseline.omittedCount,
13685
14091
  omittedByReason,
13686
14092
  health.samplePaths,
13687
- health.sampleTruncated
14093
+ health.sampleTruncated,
14094
+ baseline.limits,
14095
+ baseline.policy?.hash ?? null
13688
14096
  );
13689
14097
  }
13690
14098
  await writeNativeBaselineManifest(options.paths, state.name, baseline);
@@ -13697,7 +14105,7 @@ async function createNativeChangeLocked(options) {
13697
14105
  });
13698
14106
  return state;
13699
14107
  } catch (error) {
13700
- if (createdChangeDir) await fs12.rm(changeDir, { recursive: true, force: true });
14108
+ if (createdChangeDir) await fs13.rm(changeDir, { recursive: true, force: true });
13701
14109
  throw error;
13702
14110
  }
13703
14111
  }
@@ -13747,7 +14155,7 @@ async function createNativeChangeFile(paths, state) {
13747
14155
  const file = path16.join(nativeChangeDir(paths, state.name), NATIVE_CHANGE_STATE_FILE);
13748
14156
  await resolveContainedNativePath(paths.nativeRoot, file);
13749
14157
  try {
13750
- await fs12.access(file);
14158
+ await fs13.access(file);
13751
14159
  throw new Error(`Native change state already exists: ${state.name}`);
13752
14160
  } catch (error) {
13753
14161
  if (error.code !== "ENOENT") throw error;
@@ -13845,12 +14253,12 @@ async function readContainedFile(root, relativeRef) {
13845
14253
  const target = path17.resolve(root, ...relativeRef.split(/[\\/]/u));
13846
14254
  if (!isInsidePath(root, target))
13847
14255
  throw new Error(`Artifact escapes Native change: ${relativeRef}`);
13848
- const realRoot = await fs13.realpath(root);
13849
- const realTarget = await fs13.realpath(target);
14256
+ const realRoot = await fs14.realpath(root);
14257
+ const realTarget = await fs14.realpath(target);
13850
14258
  if (!isInsidePath(realRoot, realTarget)) {
13851
14259
  throw new Error(`Artifact symlink escapes Native change: ${relativeRef}`);
13852
14260
  }
13853
- if (!(await fs13.stat(realTarget)).isFile())
14261
+ if (!(await fs14.stat(realTarget)).isFile())
13854
14262
  throw new Error(`Artifact is not a file: ${relativeRef}`);
13855
14263
  return realTarget;
13856
14264
  }
@@ -13992,7 +14400,7 @@ async function resolveNativeArtifactFile(changeDir, relativeRef) {
13992
14400
  }
13993
14401
 
13994
14402
  // domains/comet-native/native-archive-inspection.ts
13995
- import { promises as fs17 } from "node:fs";
14403
+ import { promises as fs18 } from "node:fs";
13996
14404
  import path29 from "node:path";
13997
14405
 
13998
14406
  // domains/comet-native/native-archive-preflight.ts
@@ -14582,7 +14990,7 @@ function buildNativeConflictRadar(input) {
14582
14990
 
14583
14991
  // domains/comet-native/native-evidence-storage.ts
14584
14992
  import { createHash as createHash12 } from "node:crypto";
14585
- import { promises as fs14 } from "node:fs";
14993
+ import { promises as fs15 } from "node:fs";
14586
14994
  import path22 from "node:path";
14587
14995
 
14588
14996
  // domains/comet-native/native-verification-scope.ts
@@ -14771,8 +15179,10 @@ function snapshotProjection(manifest) {
14771
15179
  maxFiles: parsed.limits.maxFiles,
14772
15180
  maxFileBytes: parsed.limits.maxFileBytes,
14773
15181
  maxTotalBytes: parsed.limits.maxTotalBytes,
14774
- maxManifestBytes: parsed.limits.maxManifestBytes
15182
+ maxManifestBytes: parsed.limits.maxManifestBytes,
15183
+ ...parsed.limits.maxDurationMs === void 0 ? {} : { maxDurationMs: parsed.limits.maxDurationMs }
14775
15184
  },
15185
+ ...parsed.policy ? { policy: parsed.policy } : {},
14776
15186
  entries,
14777
15187
  omitted,
14778
15188
  omittedCount: parsed.omittedCount,
@@ -15250,7 +15660,7 @@ function parseNativeSnapshotProjection(value, expectedHash) {
15250
15660
  exactScopeKeys(
15251
15661
  root,
15252
15662
  ["schema", "origin", "complete", "limits", "entries", "omitted", "omittedCount"],
15253
- ["capture", "omissionOverflow"],
15663
+ ["capture", "omissionOverflow", "policy"],
15254
15664
  "Native snapshot projection"
15255
15665
  );
15256
15666
  if (root.schema !== NATIVE_SNAPSHOT_PROJECTION_SCHEMA) {
@@ -15263,6 +15673,7 @@ function parseNativeSnapshotProjection(value, expectedHash) {
15263
15673
  createdAt: "1970-01-01T00:00:00.000Z",
15264
15674
  complete: root.complete,
15265
15675
  limits: root.limits,
15676
+ ...root.policy === void 0 ? {} : { policy: root.policy },
15266
15677
  entries: root.entries,
15267
15678
  omitted: root.omitted,
15268
15679
  omittedCount: root.omittedCount,
@@ -16086,7 +16497,7 @@ function isInside5(parent, target) {
16086
16497
  return relative === "" || !path22.isAbsolute(relative) && relative !== ".." && !relative.startsWith(`..${path22.sep}`);
16087
16498
  }
16088
16499
  function sameDirectoryIdentity5(identity, stat) {
16089
- return sameNativeFileObject(
16500
+ return sameFileObject(
16090
16501
  { ...identity, birthtime: identity.birthtimeMs },
16091
16502
  {
16092
16503
  ...stat,
@@ -16097,19 +16508,19 @@ function sameDirectoryIdentity5(identity, stat) {
16097
16508
  function sameFileIdentity6(left, right) {
16098
16509
  const leftObject = { ...left, birthtime: left.birthtimeMs };
16099
16510
  const rightObject = { ...right, birthtime: right.birthtimeMs };
16100
- if (hasComparableNativeFileObject(leftObject, rightObject)) {
16101
- return sameNativeFileObject(leftObject, rightObject);
16511
+ if (hasComparableFileObject(leftObject, rightObject)) {
16512
+ return sameFileObject(leftObject, rightObject);
16102
16513
  }
16103
- return sameNativeFileObject(leftObject, rightObject) && 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;
16104
16515
  }
16105
16516
  async function captureDirectoryIdentity3(directory) {
16106
- const stat = await fs14.lstat(directory);
16517
+ const stat = await fs15.lstat(directory);
16107
16518
  if (!stat.isDirectory() || stat.isSymbolicLink()) {
16108
16519
  throw new Error(`Native evidence parent must be a real directory: ${directory}`);
16109
16520
  }
16110
16521
  return {
16111
16522
  path: directory,
16112
- realPath: await fs14.realpath(directory),
16523
+ realPath: await fs15.realpath(directory),
16113
16524
  dev: stat.dev,
16114
16525
  ino: stat.ino,
16115
16526
  birthtimeMs: stat.birthtimeMs
@@ -16135,8 +16546,8 @@ async function captureDirectoryChain4(root, directory) {
16135
16546
  }
16136
16547
  async function verifyDirectoryChain5(chain) {
16137
16548
  for (const identity of chain) {
16138
- const stat = await fs14.lstat(identity.path);
16139
- 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) {
16140
16551
  throw new Error(`Native evidence parent changed while reading: ${identity.path}`);
16141
16552
  }
16142
16553
  }
@@ -16144,20 +16555,20 @@ async function verifyDirectoryChain5(chain) {
16144
16555
  async function readBoundedEvidenceJson(file, changeRoot, hooks = {}) {
16145
16556
  const chain = await captureDirectoryChain4(changeRoot, path22.dirname(file));
16146
16557
  await hooks.afterParentChainCaptured?.();
16147
- const lexical = await fs14.lstat(file);
16558
+ const lexical = await fs15.lstat(file);
16148
16559
  if (!lexical.isFile() || lexical.isSymbolicLink()) {
16149
16560
  throw new Error("Native evidence document must be a regular file");
16150
16561
  }
16151
- const realPath = await fs14.realpath(file);
16562
+ const realPath = await fs15.realpath(file);
16152
16563
  if (!isInside5(chain[0].realPath, realPath)) {
16153
16564
  throw new Error("Native evidence document resolves outside its change");
16154
16565
  }
16155
- const handle = await fs14.open(file, "r");
16566
+ const handle = await fs15.open(file, "r");
16156
16567
  try {
16157
16568
  const [opened, pathAfterOpen, realPathAfterOpen] = await Promise.all([
16158
16569
  handle.stat(),
16159
- fs14.lstat(file),
16160
- fs14.realpath(file)
16570
+ fs15.lstat(file),
16571
+ fs15.realpath(file)
16161
16572
  ]);
16162
16573
  await verifyDirectoryChain5(chain);
16163
16574
  if (!opened.isFile() || !pathAfterOpen.isFile() || pathAfterOpen.isSymbolicLink() || realPathAfterOpen !== realPath || !sameFileIdentity6(opened, lexical) || !sameFileIdentity6(opened, pathAfterOpen)) {
@@ -16187,8 +16598,8 @@ async function readBoundedEvidenceJson(file, changeRoot, hooks = {}) {
16187
16598
  await hooks.beforeFinalCheck?.();
16188
16599
  const [afterHandle, afterPath, afterRealPath] = await Promise.all([
16189
16600
  handle.stat(),
16190
- fs14.lstat(file),
16191
- fs14.realpath(file)
16601
+ fs15.lstat(file),
16602
+ fs15.realpath(file)
16192
16603
  ]);
16193
16604
  await verifyDirectoryChain5(chain);
16194
16605
  if (!afterPath.isFile() || afterPath.isSymbolicLink() || afterRealPath !== realPath || !sameFileIdentity6(opened, afterHandle) || !sameFileIdentity6(opened, afterPath)) {
@@ -16280,7 +16691,7 @@ function assertEvidenceDocumentBudget(value) {
16280
16691
  function serializedEvidenceBytes2(value) {
16281
16692
  return Buffer.byteLength(JSON.stringify(value, null, 2) + "\n", "utf8");
16282
16693
  }
16283
- function parseSnapshot(value, expectedHash) {
16694
+ function parseSnapshot2(value, expectedHash) {
16284
16695
  return parseNativeSnapshotProjection(value, expectedHash);
16285
16696
  }
16286
16697
  function parseScope(value, expectedHash) {
@@ -16356,10 +16767,10 @@ async function readNativeImplementationScopeBundle(paths, name, ref, hooks) {
16356
16767
  const currentHash = parseEvidenceRef(scope.currentProjectionRef, "snapshots");
16357
16768
  const [baseline, current] = await Promise.all([
16358
16769
  readEvidenceDocument({ paths, name, kind: "snapshots", hash: baselineHash }).then(
16359
- (value) => parseSnapshot(value, baselineHash)
16770
+ (value) => parseSnapshot2(value, baselineHash)
16360
16771
  ),
16361
16772
  readEvidenceDocument({ paths, name, kind: "snapshots", hash: currentHash }).then(
16362
- (value) => parseSnapshot(value, currentHash)
16773
+ (value) => parseSnapshot2(value, currentHash)
16363
16774
  )
16364
16775
  ]);
16365
16776
  return rebuildNativeImplementationScopeBundle({ baseline, current, scope });
@@ -16484,7 +16895,7 @@ async function inspectNativeChangeConflicts(paths, name) {
16484
16895
 
16485
16896
  // domains/comet-native/native-transition-journal.ts
16486
16897
  import { randomUUID as randomUUID3 } from "crypto";
16487
- import { promises as fs15 } from "fs";
16898
+ import { promises as fs16 } from "fs";
16488
16899
  import path23 from "path";
16489
16900
  import { isDeepStrictEqual } from "util";
16490
16901
 
@@ -17942,7 +18353,7 @@ async function continueNativeTransitionLocked(paths, name, hooks) {
17942
18353
  `Native transition journal changed while continuing ${journal.change}; it was not removed`
17943
18354
  );
17944
18355
  }
17945
- await fs15.rm(nativeTransitionJournalFile(paths, name), { force: true });
18356
+ await fs16.rm(nativeTransitionJournalFile(paths, name), { force: true });
17946
18357
  return journal.nextState;
17947
18358
  }
17948
18359
  async function continueNativeTransition(paths, name, hooks) {
@@ -18406,9 +18817,16 @@ function canonicalEvidencePayload(entries) {
18406
18817
  const validated = validateEvidenceEntries([...entries]).map((entry2) => ({ ...entry2, evidence_refs: [...entry2.evidence_refs].sort() })).sort((left, right) => left.acceptance_id.localeCompare(right.acceptance_id));
18407
18818
  return JSON.stringify(validated, null, 2);
18408
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
+ }
18409
18827
 
18410
18828
  // domains/comet-native/native-check-receipt-storage.ts
18411
- import { constants as fsConstants3, promises as fs16 } from "node:fs";
18829
+ import { constants as fsConstants4, promises as fs17 } from "node:fs";
18412
18830
  import path26 from "node:path";
18413
18831
 
18414
18832
  // domains/comet-native/native-check-receipt-model.ts
@@ -18757,7 +19175,7 @@ var HASH_PATTERN11 = /^[a-f0-9]{64}$/u;
18757
19175
  var RECEIPT_REF_PATTERN = /^runtime\/evidence\/check-receipts\/([a-f0-9]{64})\.json$/u;
18758
19176
  var MAX_NATIVE_CHECK_RECEIPT_BYTES = 512 * 1024;
18759
19177
  function sameDirectoryIdentity6(identity, stat) {
18760
- return sameNativeFileObject(
19178
+ return sameFileObject(
18761
19179
  { ...identity, birthtime: identity.birthtimeMs },
18762
19180
  {
18763
19181
  ...stat,
@@ -18768,10 +19186,10 @@ function sameDirectoryIdentity6(identity, stat) {
18768
19186
  function sameFileIdentity7(left, right) {
18769
19187
  const leftObject = { ...left, birthtime: left.birthtimeMs };
18770
19188
  const rightObject = { ...right, birthtime: right.birthtimeMs };
18771
- if (hasComparableNativeFileObject(leftObject, rightObject)) {
18772
- return sameNativeFileObject(leftObject, rightObject);
19189
+ if (hasComparableFileObject(leftObject, rightObject)) {
19190
+ return sameFileObject(leftObject, rightObject);
18773
19191
  }
18774
- return sameNativeFileObject(leftObject, rightObject) && 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;
18775
19193
  }
18776
19194
  async function captureDirectoryChain5(root, directory) {
18777
19195
  const lexicalRoot = path26.resolve(root);
@@ -18786,11 +19204,11 @@ async function captureDirectoryChain5(root, directory) {
18786
19204
  ...path26.relative(lexicalRoot, lexicalDirectory).split(path26.sep).filter(Boolean)
18787
19205
  ]) {
18788
19206
  if (segment) cursor = path26.join(cursor, segment);
18789
- const stat = await fs16.lstat(cursor);
19207
+ const stat = await fs17.lstat(cursor);
18790
19208
  if (!stat.isDirectory() || stat.isSymbolicLink()) {
18791
19209
  throw new Error(`Native check receipt parent must be a real directory: ${cursor}`);
18792
19210
  }
18793
- const realPath = await fs16.realpath(cursor);
19211
+ const realPath = await fs17.realpath(cursor);
18794
19212
  if (chain.length > 0 && !isInsidePath(chain[0].realPath, realPath)) {
18795
19213
  throw new Error("Native check receipt parent resolves outside the Native root");
18796
19214
  }
@@ -18806,8 +19224,8 @@ async function captureDirectoryChain5(root, directory) {
18806
19224
  }
18807
19225
  async function verifyDirectoryChain6(chain) {
18808
19226
  for (const identity of chain) {
18809
- const stat = await fs16.lstat(identity.path);
18810
- 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) {
18811
19229
  throw new Error(`Native check receipt parent changed while reading: ${identity.path}`);
18812
19230
  }
18813
19231
  }
@@ -18826,7 +19244,7 @@ function receiptFile(paths, name, hash6) {
18826
19244
  }
18827
19245
  async function readBoundedReceipt(file, changeRoot, nativeRoot) {
18828
19246
  const chain = await captureDirectoryChain5(nativeRoot, path26.dirname(file));
18829
- const before = await fs16.lstat(file);
19247
+ const before = await fs17.lstat(file);
18830
19248
  if (!before.isFile() || before.isSymbolicLink()) {
18831
19249
  throw new Error("Native check receipt must be a regular file");
18832
19250
  }
@@ -18834,14 +19252,14 @@ async function readBoundedReceipt(file, changeRoot, nativeRoot) {
18834
19252
  throw new Error(`Native check receipt exceeds ${MAX_NATIVE_CHECK_RECEIPT_BYTES} bytes`);
18835
19253
  }
18836
19254
  const [realChangeRoot, beforeRealPath] = await Promise.all([
18837
- fs16.realpath(changeRoot),
18838
- fs16.realpath(file)
19255
+ fs17.realpath(changeRoot),
19256
+ fs17.realpath(file)
18839
19257
  ]);
18840
19258
  if (!isInsidePath(realChangeRoot, beforeRealPath)) {
18841
19259
  throw new Error("Native check receipt resolves outside its change");
18842
19260
  }
18843
- const openFlags = process.platform === "win32" ? "r" : fsConstants3.O_RDONLY | fsConstants3.O_NOFOLLOW | fsConstants3.O_NONBLOCK;
18844
- 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) => {
18845
19263
  if (error.code === "ELOOP" || error.code === "ENXIO") {
18846
19264
  throw new Error("Native check receipt became unsafe while opening");
18847
19265
  }
@@ -18850,8 +19268,8 @@ async function readBoundedReceipt(file, changeRoot, nativeRoot) {
18850
19268
  try {
18851
19269
  const [opened, pathAfterOpen, realPathAfterOpen] = await Promise.all([
18852
19270
  handle.stat(),
18853
- fs16.lstat(file),
18854
- fs16.realpath(file)
19271
+ fs17.lstat(file),
19272
+ fs17.realpath(file)
18855
19273
  ]);
18856
19274
  await verifyDirectoryChain6(chain);
18857
19275
  if (!opened.isFile() || !pathAfterOpen.isFile() || pathAfterOpen.isSymbolicLink() || realPathAfterOpen !== beforeRealPath || !sameFileIdentity7(before, opened) || !sameFileIdentity7(opened, pathAfterOpen)) {
@@ -18872,8 +19290,8 @@ async function readBoundedReceipt(file, changeRoot, nativeRoot) {
18872
19290
  }
18873
19291
  const [afterHandle, afterPath, afterRealPath] = await Promise.all([
18874
19292
  handle.stat(),
18875
- fs16.lstat(file),
18876
- fs16.realpath(file)
19293
+ fs17.lstat(file),
19294
+ fs17.realpath(file)
18877
19295
  ]);
18878
19296
  await verifyDirectoryChain6(chain);
18879
19297
  if (!afterPath.isFile() || afterPath.isSymbolicLink() || afterRealPath !== beforeRealPath || !isInsidePath(realChangeRoot, afterRealPath) || !sameFileIdentity7(opened, afterHandle) || !sameFileIdentity7(opened, afterPath)) {
@@ -19123,6 +19541,7 @@ function projectionManifest(projection) {
19123
19541
  createdAt: "1970-01-01T00:00:00.000Z",
19124
19542
  complete: projection.complete,
19125
19543
  limits: projection.limits,
19544
+ ...projection.policy ? { policy: projection.policy } : {},
19126
19545
  entries: projection.entries,
19127
19546
  omitted: projection.omitted,
19128
19547
  omittedCount: projection.omittedCount,
@@ -19137,12 +19556,13 @@ function nativeRootRef2(paths) {
19137
19556
  return value;
19138
19557
  }
19139
19558
  async function currentProjectionHash(options) {
19140
- const current = await createNativeContentSnapshot(options.paths, {
19559
+ const baseline = projectionManifest(options.bundle.baseline);
19560
+ const current = await createNativeCurrentContentSnapshot(options.paths, baseline, {
19141
19561
  origin: "explicit",
19142
19562
  now: options.now
19143
19563
  });
19144
19564
  return buildNativeImplementationScopeBundle({
19145
- baseline: projectionManifest(options.bundle.baseline),
19565
+ baseline,
19146
19566
  current,
19147
19567
  contractHash: options.bundle.scope.contractHash,
19148
19568
  declaredArtifacts: options.bundle.scope.declaredArtifacts,
@@ -19406,7 +19826,7 @@ function archiveTargetRef(name, now) {
19406
19826
  }
19407
19827
  async function exists2(target) {
19408
19828
  try {
19409
- await fs17.lstat(target);
19829
+ await fs18.lstat(target);
19410
19830
  return true;
19411
19831
  } catch (error) {
19412
19832
  if (error.code === "ENOENT") return false;
@@ -19488,7 +19908,7 @@ async function inspectNativeArchivePreflight(options) {
19488
19908
  }
19489
19909
 
19490
19910
  // domains/comet-native/native-archive-content.ts
19491
- import { promises as fs18 } from "node:fs";
19911
+ import { promises as fs19 } from "node:fs";
19492
19912
  import path30 from "node:path";
19493
19913
  var TREE_HASH_TAG = "comet.native.archive-tree.v1";
19494
19914
  var NATIVE_ARCHIVE_CONTENT_LIMITS = {
@@ -19548,7 +19968,7 @@ async function walkArchiveTree(root, directory, entries, budget, limits, depth)
19548
19968
  await protectedDirectory.verify();
19549
19969
  const target = path30.join(directory, child.name);
19550
19970
  const ref = path30.relative(root, target).replaceAll("\\", "/");
19551
- const stat = await fs18.lstat(target);
19971
+ const stat = await fs19.lstat(target);
19552
19972
  if (child.isSymbolicLink() || stat.isSymbolicLink()) {
19553
19973
  throw new Error(`Native Archive content must not contain symlinks or junctions: ${ref}`);
19554
19974
  }
@@ -19595,7 +20015,7 @@ async function walkArchiveTree(root, directory, entries, budget, limits, depth)
19595
20015
  async function hashNativeArchiveTree(directory, requestedLimits = {}) {
19596
20016
  const limits = normalizedLimits(requestedLimits);
19597
20017
  directory = path30.resolve(directory);
19598
- const stat = await fs18.lstat(directory);
20018
+ const stat = await fs19.lstat(directory);
19599
20019
  if (!stat.isDirectory() || stat.isSymbolicLink()) {
19600
20020
  throw new Error(`Native Archive move source must be a real directory: ${directory}`);
19601
20021
  }
@@ -19613,7 +20033,7 @@ async function inspectNativeArchiveContent(target, requestedLimits = {}) {
19613
20033
  target = path30.resolve(target);
19614
20034
  let stat;
19615
20035
  try {
19616
- stat = await fs18.lstat(target);
20036
+ stat = await fs19.lstat(target);
19617
20037
  } catch (error) {
19618
20038
  if (error.code === "ENOENT") return null;
19619
20039
  throw error;
@@ -19637,7 +20057,7 @@ async function inspectNativeArchiveContent(target, requestedLimits = {}) {
19637
20057
  }
19638
20058
 
19639
20059
  // domains/comet-native/native-archive-transaction.ts
19640
- import { promises as fs19 } from "node:fs";
20060
+ import { promises as fs20 } from "node:fs";
19641
20061
  import path31 from "node:path";
19642
20062
  var NATIVE_ARCHIVE_COPY_MAX_BYTES = 16 * 1024 * 1024;
19643
20063
  var NATIVE_ARCHIVE_JOURNAL_MAX_BYTES = 256 * 1024;
@@ -19671,8 +20091,8 @@ async function assertContent(options) {
19671
20091
  async function createNativeArchiveTransactionV2(paths, journal) {
19672
20092
  const validated = parseNativeArchiveTransactionJournalV2(journal);
19673
20093
  const tx = await resolveNativeTransactionPaths(paths, validated.id);
19674
- await fs19.mkdir(tx.staged, { recursive: true });
19675
- await fs19.mkdir(tx.backups, { recursive: true });
20094
+ await fs20.mkdir(tx.staged, { recursive: true });
20095
+ await fs20.mkdir(tx.backups, { recursive: true });
19676
20096
  await atomicWriteJson(tx.journal, validated, { containedRoot: paths.nativeRoot });
19677
20097
  await appendNativeTransactionEvent(paths, validated.id, "prepared");
19678
20098
  }
@@ -19711,8 +20131,8 @@ function fileVersion(stat) {
19711
20131
  size: stat.size
19712
20132
  };
19713
20133
  }
19714
- function sameFileObject(expected, actual) {
19715
- return sameNativeFileObject(
20134
+ function sameFileObject2(expected, actual) {
20135
+ return sameFileObject(
19716
20136
  { ...expected, birthtime: expected.birthtimeMs },
19717
20137
  {
19718
20138
  ...actual,
@@ -19721,11 +20141,11 @@ function sameFileObject(expected, actual) {
19721
20141
  ) && expected.birthtimeMs === actual.birthtimeMs;
19722
20142
  }
19723
20143
  function sameFileVersion(expected, actual) {
19724
- 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;
19725
20145
  }
19726
20146
  async function pathExists(target) {
19727
20147
  try {
19728
- await fs19.lstat(target);
20148
+ await fs20.lstat(target);
19729
20149
  return true;
19730
20150
  } catch (error) {
19731
20151
  if (error.code === "ENOENT") return false;
@@ -19733,7 +20153,7 @@ async function pathExists(target) {
19733
20153
  }
19734
20154
  }
19735
20155
  async function captureStableArchiveFile(options) {
19736
- const before = await fs19.lstat(options.file);
20156
+ const before = await fs20.lstat(options.file);
19737
20157
  if (!before.isFile() || before.isSymbolicLink()) {
19738
20158
  throw new Error(`${options.label} must be a regular file`);
19739
20159
  }
@@ -19744,7 +20164,7 @@ async function captureStableArchiveFile(options) {
19744
20164
  maxBytes: NATIVE_ARCHIVE_COPY_MAX_BYTES,
19745
20165
  label: options.label
19746
20166
  });
19747
- const after = await fs19.lstat(options.file);
20167
+ const after = await fs20.lstat(options.file);
19748
20168
  if (!after.isFile() || after.isSymbolicLink() || !sameFileVersion(beforeVersion, fileVersion(after)) || snapshot2.hash !== options.expectedHash) {
19749
20169
  throw new Error(`${options.label} content changed or object identity changed while binding`);
19750
20170
  }
@@ -19822,7 +20242,7 @@ async function readCasRecord(options) {
19822
20242
  async function persistCasRecord(options) {
19823
20243
  const existing = await readCasRecord(options);
19824
20244
  if (existing) {
19825
- if (!sameFileObject(existing.identity, options.identity)) {
20245
+ if (!sameFileObject2(existing.identity, options.identity)) {
19826
20246
  throw new Error(
19827
20247
  `Archive CAS ${options.operation.id} ${options.role} object identity changed`
19828
20248
  );
@@ -19850,7 +20270,7 @@ async function persistCasRecord(options) {
19850
20270
  if (error.code !== "EEXIST") throw error;
19851
20271
  }
19852
20272
  const persisted = await readCasRecord(options);
19853
- if (!persisted || !sameFileObject(persisted.identity, options.identity)) {
20273
+ if (!persisted || !sameFileObject2(persisted.identity, options.identity)) {
19854
20274
  throw new Error(`Archive CAS ${options.operation.id} ${options.role} record changed`);
19855
20275
  }
19856
20276
  return persisted;
@@ -19862,7 +20282,7 @@ async function validateFileAgainstCasRecord(options) {
19862
20282
  expectedHash: options.record.hash,
19863
20283
  label: options.label
19864
20284
  });
19865
- if (!sameFileObject(options.record.identity, current.identity)) {
20285
+ if (!sameFileObject2(options.record.identity, current.identity)) {
19866
20286
  throw new Error(`${options.label} object identity changed`);
19867
20287
  }
19868
20288
  }
@@ -19899,20 +20319,20 @@ async function restoreUnexpectedQuarantine(options) {
19899
20319
  });
19900
20320
  await guard.verify();
19901
20321
  try {
19902
- await fs19.link(options.quarantine, options.target);
20322
+ await fs20.link(options.quarantine, options.target);
19903
20323
  } catch (error) {
19904
20324
  if (error.code === "EEXIST") return;
19905
20325
  throw error;
19906
20326
  }
19907
20327
  await guard.verify();
19908
20328
  const [quarantine, target] = await Promise.all([
19909
- fs19.lstat(options.quarantine),
19910
- fs19.lstat(options.target)
20329
+ fs20.lstat(options.quarantine),
20330
+ fs20.lstat(options.target)
19911
20331
  ]);
19912
- if (!sameFileObject(fileObjectIdentity(quarantine), fileObjectIdentity(target))) {
20332
+ if (!sameFileObject2(fileObjectIdentity(quarantine), fileObjectIdentity(target))) {
19913
20333
  throw new Error(`Archive CAS ${options.operation.id} could not restore quarantined content`);
19914
20334
  }
19915
- await fs19.unlink(options.quarantine);
20335
+ await fs20.unlink(options.quarantine);
19916
20336
  await guard.verify();
19917
20337
  }
19918
20338
  async function quarantineBoundTarget(options) {
@@ -19948,7 +20368,7 @@ async function quarantineBoundTarget(options) {
19948
20368
  label: `Archive ${options.phase} target ${options.operation.target}`
19949
20369
  });
19950
20370
  try {
19951
- await fs19.rename(options.target, options.quarantine);
20371
+ await fs20.rename(options.target, options.quarantine);
19952
20372
  await guard.verify();
19953
20373
  await validateFileAgainstCasRecord({
19954
20374
  paths: options.paths,
@@ -19980,7 +20400,7 @@ async function removeExactCasFile(options) {
19980
20400
  });
19981
20401
  await guard.verify();
19982
20402
  await validateFileAgainstCasRecord(options);
19983
- await fs19.unlink(options.file);
20403
+ await fs20.unlink(options.file);
19984
20404
  await guard.verify();
19985
20405
  }
19986
20406
  async function ensureBackup(paths, operation, hooks) {
@@ -20173,7 +20593,7 @@ async function ensureWriteInstalled(options) {
20173
20593
  expectedHash,
20174
20594
  label: `Archive write target ${options.operation.target}`
20175
20595
  });
20176
- if (!sameFileObject(candidateIdentity2.identity, targetIdentity2.identity)) {
20596
+ if (!sameFileObject2(candidateIdentity2.identity, targetIdentity2.identity)) {
20177
20597
  throw new Error(
20178
20598
  `Archive write target ${options.operation.target} is occupied by an external object`
20179
20599
  );
@@ -20228,7 +20648,7 @@ async function ensureWriteInstalled(options) {
20228
20648
  label: `Archive write candidate ${options.operation.target}`
20229
20649
  });
20230
20650
  try {
20231
- await fs19.link(candidate, options.target);
20651
+ await fs20.link(candidate, options.target);
20232
20652
  } catch (error) {
20233
20653
  if (error.code === "EEXIST") {
20234
20654
  throw new Error(
@@ -20246,7 +20666,7 @@ async function ensureWriteInstalled(options) {
20246
20666
  expectedHash,
20247
20667
  label: `Archive write target ${options.operation.target}`
20248
20668
  });
20249
- if (!sameFileObject(candidateIdentity.identity, targetIdentity.identity)) {
20669
+ if (!sameFileObject2(candidateIdentity.identity, targetIdentity.identity)) {
20250
20670
  throw new Error(`Archive write target ${options.operation.target} changed during install`);
20251
20671
  }
20252
20672
  record8 = await persistCasRecord({
@@ -20332,8 +20752,8 @@ async function applyMove(paths, operation) {
20332
20752
  `Archive transaction move ${operation.id} content changed: source=${contentDescription(sourceContent)}, target=${contentDescription(targetContent)}`
20333
20753
  );
20334
20754
  }
20335
- await fs19.mkdir(path31.dirname(target), { recursive: true });
20336
- await fs19.rename(source, target);
20755
+ await fs20.mkdir(path31.dirname(target), { recursive: true });
20756
+ await fs20.rename(source, target);
20337
20757
  await assertContent({
20338
20758
  target,
20339
20759
  expectedHash: operation.expectedSourceHash,
@@ -20535,7 +20955,7 @@ async function installOriginalTarget(options) {
20535
20955
  label: `Archive rollback original quarantine ${options.operation.target}`
20536
20956
  });
20537
20957
  try {
20538
- await fs19.link(options.quarantine, options.target);
20958
+ await fs20.link(options.quarantine, options.target);
20539
20959
  } catch (error) {
20540
20960
  if (error.code === "EEXIST") {
20541
20961
  throw new Error(
@@ -20615,7 +21035,7 @@ async function rollbackWriteOrRemove(paths, journal, operation, hooks) {
20615
21035
  expectedHash: operation.stagedHash,
20616
21036
  label: `Archive rollback target ${operation.target}`
20617
21037
  });
20618
- if (sameFileObject(candidate.identity, current.identity)) {
21038
+ if (sameFileObject2(candidate.identity, current.identity)) {
20619
21039
  postRecord = await persistCasRecord({
20620
21040
  paths,
20621
21041
  file: cas.postRecord,
@@ -20729,8 +21149,8 @@ async function rollbackMove(paths, operation) {
20729
21149
  `Archive rollback move ${operation.id} content changed: source=${contentDescription(sourceContent)}, target=${contentDescription(targetContent)}`
20730
21150
  );
20731
21151
  }
20732
- await fs19.mkdir(path31.dirname(source), { recursive: true });
20733
- await fs19.rename(target, source);
21152
+ await fs20.mkdir(path31.dirname(source), { recursive: true });
21153
+ await fs20.rename(target, source);
20734
21154
  }
20735
21155
  async function rollbackNativeArchiveTransactionV2(paths, journal, hooks) {
20736
21156
  const events = await readNativeTransactionEvents(paths, journal.id);
@@ -20828,7 +21248,7 @@ async function finalizeNativeArchiveTransactionV2(paths, journal, event) {
20828
21248
 
20829
21249
  // domains/comet-native/native-checkpoint-journal.ts
20830
21250
  import { randomUUID as randomUUID4 } from "crypto";
20831
- import { promises as fs20 } from "fs";
21251
+ import { promises as fs21 } from "fs";
20832
21252
 
20833
21253
  // domains/comet-native/native-checkpoint-storage.ts
20834
21254
  import path32 from "path";
@@ -21364,7 +21784,7 @@ async function continueNativeCheckpointLocked(paths, name, hooks) {
21364
21784
  await hooks?.afterStateWritten?.(journal);
21365
21785
  await writeNativeProgressCheckpoint(paths, journal.checkpoint);
21366
21786
  await hooks?.afterProgressWritten?.(journal);
21367
- await fs20.rm(nativeCheckpointJournalFile(paths, name), { force: true });
21787
+ await fs21.rm(nativeCheckpointJournalFile(paths, name), { force: true });
21368
21788
  return journal;
21369
21789
  }
21370
21790
  async function continueNativeCheckpoint(paths, name, hooks) {
@@ -21386,7 +21806,7 @@ async function settleNativeChangeJournalsLocked(paths, name) {
21386
21806
 
21387
21807
  // domains/comet-entry/current-selection.ts
21388
21808
  import { randomUUID as randomUUID5 } from "crypto";
21389
- import { promises as fs21 } from "fs";
21809
+ import { promises as fs22 } from "fs";
21390
21810
  import path33 from "path";
21391
21811
  var COMET_CURRENT_SELECTION_SCHEMA = "comet.selection.v2";
21392
21812
  var COMET_CURRENT_SELECTION_MAX_BYTES = 16 * 1024;
@@ -21449,16 +21869,11 @@ function parseSelection(source) {
21449
21869
  async function readCometCurrentSelection(projectRoot) {
21450
21870
  let source;
21451
21871
  try {
21452
- const stat = await fs21.lstat(cometCurrentSelectionFile(projectRoot));
21453
- if (stat.isSymbolicLink() || !stat.isFile()) {
21454
- throw new Error("current change selection must be a regular file");
21455
- }
21456
- if (stat.size > COMET_CURRENT_SELECTION_MAX_BYTES) {
21457
- throw new Error(
21458
- `current change selection exceeds ${COMET_CURRENT_SELECTION_MAX_BYTES} bytes`
21459
- );
21460
- }
21461
- 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");
21462
21877
  } catch (error) {
21463
21878
  if (error.code === "ENOENT") return { status: "missing" };
21464
21879
  throw new Error(
@@ -21474,18 +21889,18 @@ async function writeCometCurrentSelection(projectRoot, selection) {
21474
21889
  if (parsed.legacy) throw new Error("cannot write a legacy current change selection");
21475
21890
  const file = cometCurrentSelectionFile(projectRoot);
21476
21891
  const temporary = `${file}.${randomUUID5()}.tmp`;
21477
- await fs21.mkdir(path33.dirname(file), { recursive: true });
21892
+ await fs22.mkdir(path33.dirname(file), { recursive: true });
21478
21893
  try {
21479
- await fs21.writeFile(temporary, `${JSON.stringify(parsed.selection, null, 2)}
21894
+ await fs22.writeFile(temporary, `${JSON.stringify(parsed.selection, null, 2)}
21480
21895
  `, "utf8");
21481
- await fs21.rename(temporary, file);
21896
+ await fs22.rename(temporary, file);
21482
21897
  } catch (error) {
21483
- await fs21.rm(temporary, { force: true });
21898
+ await fs22.rm(temporary, { force: true });
21484
21899
  throw error;
21485
21900
  }
21486
21901
  }
21487
21902
  async function clearCometCurrentSelection(projectRoot) {
21488
- await fs21.rm(cometCurrentSelectionFile(projectRoot), { force: true });
21903
+ await fs22.rm(cometCurrentSelectionFile(projectRoot), { force: true });
21489
21904
  }
21490
21905
  async function clearCometCurrentSelectionIf(projectRoot, workflow, change) {
21491
21906
  const current = await readCometCurrentSelection(projectRoot);
@@ -21599,7 +22014,7 @@ function archiveTarget(paths, name, now) {
21599
22014
  }
21600
22015
  async function pathExists2(target) {
21601
22016
  try {
21602
- await fs22.access(target);
22017
+ await fs23.access(target);
21603
22018
  return true;
21604
22019
  } catch (error) {
21605
22020
  if (error.code === "ENOENT") return false;
@@ -21910,7 +22325,7 @@ async function recoverArchiveTransaction(options) {
21910
22325
  }
21911
22326
 
21912
22327
  // domains/comet-native/native-diagnostics.ts
21913
- import { promises as fs23 } from "fs";
22328
+ import { promises as fs24 } from "fs";
21914
22329
 
21915
22330
  // domains/comet-native/native-run-consistency.ts
21916
22331
  import path35 from "path";
@@ -22036,8 +22451,12 @@ async function inspectNativeRunConsistency(paths, state) {
22036
22451
  // domains/comet-native/native-continuation.ts
22037
22452
  var REPAIR_CODES = /^(?:run-|trajectory-|checkpoint-(?:missing|mismatch|invalid|progress-invalid)|transition-(?:incomplete|invalid))/u;
22038
22453
  function requiredPhaseInputs(state) {
22039
- if (state.phase === "shape") return ["summary"];
22040
- 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
+ }
22041
22460
  if (state.phase === "verify") return ["summary", "verification-result", "verification-report"];
22042
22461
  return [];
22043
22462
  }
@@ -22168,6 +22587,7 @@ function nativeContinuation(options) {
22168
22587
  requiredInputs: options.archiveReady ? [] : ["archive-readiness"]
22169
22588
  };
22170
22589
  }
22590
+ const confirmationSuffix = options.state.phase === "shape" || options.state.phase === "build" && options.state.approval !== "confirmed" ? " --confirmed" : "";
22171
22591
  return {
22172
22592
  schema: "comet.native.continuation.v1",
22173
22593
  skill: "comet-native",
@@ -22176,7 +22596,7 @@ function nativeContinuation(options) {
22176
22596
  revision: options.state.revision,
22177
22597
  disposition: "continue",
22178
22598
  action: "advance-phase",
22179
- command: `comet native next ${options.state.name} --summary "<summary>"`,
22599
+ command: `comet native next ${options.state.name} --summary "<summary>"${confirmationSuffix}`,
22180
22600
  requiresUserDecision: false,
22181
22601
  requiredInputs: requiredPhaseInputs(options.state)
22182
22602
  };
@@ -22192,6 +22612,18 @@ var EXACT_METADATA = {
22192
22612
  retry: "next",
22193
22613
  repair: "none"
22194
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
+ },
22195
22627
  "transition-incomplete": {
22196
22628
  severity: "error",
22197
22629
  requiredAction: "recover-transition",
@@ -22363,7 +22795,7 @@ function projectRelativePath3(paths, state, finding) {
22363
22795
  }
22364
22796
  function retryCommand(retry, state, code) {
22365
22797
  if (retry === "next") {
22366
- 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" : ""}`;
22367
22799
  }
22368
22800
  if (retry === "status") return `comet native status ${state.name} --details`;
22369
22801
  return null;
@@ -22381,7 +22813,7 @@ function structureNativeFindings(options) {
22381
22813
  repairCommand: metadata.repair === "doctor" ? `comet native doctor ${options.state.name} --repair${finding.code.startsWith("transition-") ? " --strategy continue" : ""}` : null,
22382
22814
  // This is intentionally code-based, not severity-based. Model-actionable
22383
22815
  // missing data must never be presented as a user decision.
22384
- 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"
22385
22817
  };
22386
22818
  }).sort((left, right) => {
22387
22819
  const severityRank = { error: 0, warning: 1, info: 2 };
@@ -22695,11 +23127,11 @@ async function selectedName(paths) {
22695
23127
  return null;
22696
23128
  }
22697
23129
  }
22698
- function nativeNextCommand(state, archiveReady, evidenceRetreat = false) {
23130
+ function nativeNextCommand(state, archiveReady, evidenceRetreat = false, _clarificationMode) {
22699
23131
  if (state.phase === "archive") {
22700
23132
  return archiveReady ? `comet native archive ${state.name} --dry-run` : evidenceRetreat ? `comet native next ${state.name} --summary "<summary>"` : null;
22701
23133
  }
22702
- 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" : ""}`;
22703
23135
  }
22704
23136
  async function statusFindings(paths, state) {
22705
23137
  const changeDir = nativeChangeDir(paths, state.name);
@@ -23030,13 +23462,19 @@ async function inspectNativeStatus(paths, name, options) {
23030
23462
  verificationResult: state.verification_result,
23031
23463
  specChanges: state.spec_changes.length,
23032
23464
  selected,
23033
- nextCommand: mutationBlocked || repairBlocked ? null : nativeNextCommand(state, archiveReady, evidenceRetreat),
23465
+ nextCommand: mutationBlocked || repairBlocked ? null : nativeNextCommand(state, archiveReady, evidenceRetreat, options?.clarificationMode),
23034
23466
  archiveReady,
23035
23467
  inspection: resume.inspection,
23036
23468
  findingSummary: summarizeNativeFindings(findings),
23037
23469
  detailsCommand: `comet native status ${state.name} --details`,
23038
23470
  checkpoint: resume.checkpoint,
23039
- continuation: nativeContinuation({ state, findings, archiveReady, evidenceRetreat }),
23471
+ continuation: nativeContinuation({
23472
+ state,
23473
+ findings,
23474
+ archiveReady,
23475
+ evidenceRetreat,
23476
+ clarificationMode: options?.clarificationMode
23477
+ }),
23040
23478
  repair,
23041
23479
  ...options?.details ? {
23042
23480
  ...acceptancePage ? { acceptancePage } : {},
@@ -23070,7 +23508,7 @@ async function boundedNativeChangeNames(paths) {
23070
23508
  throw error;
23071
23509
  }
23072
23510
  const names = [];
23073
- const directory = await fs23.opendir(paths.changesDir);
23511
+ const directory = await fs24.opendir(paths.changesDir);
23074
23512
  try {
23075
23513
  for await (const entry2 of directory) {
23076
23514
  if (!entry2.isDirectory() || entry2.isSymbolicLink()) continue;
@@ -23119,7 +23557,11 @@ async function listNativeStatusPage(paths, options) {
23119
23557
  cursor: options?.cursor
23120
23558
  });
23121
23559
  const candidates = await Promise.all(
23122
- 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
+ )
23123
23565
  );
23124
23566
  const items = [];
23125
23567
  for (const candidate of candidates) {
@@ -23151,23 +23593,25 @@ async function listNativeStatusPage(paths, options) {
23151
23593
  limits: { ...NATIVE_STATUS_PAGE_LIMITS }
23152
23594
  };
23153
23595
  }
23154
- async function listNativeStatus(paths) {
23155
- return (await listNativeStatusPage(paths)).items;
23596
+ async function listNativeStatus(paths, options) {
23597
+ return (await listNativeStatusPage(paths, {
23598
+ clarificationMode: options?.clarificationMode
23599
+ })).items;
23156
23600
  }
23157
23601
 
23158
23602
  // domains/comet-native/native-doctor.ts
23159
- import { promises as fs27 } from "fs";
23603
+ import { promises as fs28 } from "fs";
23160
23604
  import path41 from "path";
23161
23605
 
23162
23606
  // domains/comet-native/native-evidence-retention.ts
23163
23607
  import { createHash as createHash13, randomUUID as randomUUID8 } from "node:crypto";
23164
- import { constants as fsConstants4, promises as fs25 } from "node:fs";
23608
+ import { constants as fsConstants5, promises as fs26 } from "node:fs";
23165
23609
  import path39 from "node:path";
23166
23610
 
23167
23611
  // domains/comet-native/native-schema-migration.ts
23168
23612
  var import_yaml4 = __toESM(require_dist(), 1);
23169
23613
  import { randomUUID as randomUUID7 } from "crypto";
23170
- import { promises as fs24 } from "fs";
23614
+ import { promises as fs25 } from "fs";
23171
23615
  import path38 from "path";
23172
23616
  import { isDeepStrictEqual as isDeepStrictEqual3 } from "util";
23173
23617
  var HASH_PATTERN14 = /^[a-f0-9]{64}$/u;
@@ -23650,7 +24094,7 @@ async function continueTransitionSupersede(paths, journal, hooks) {
23650
24094
  if (transitionHash !== supersede.sourceHash) {
23651
24095
  throw new Error(`Native superseded transition changed before removal for ${journal.change}`);
23652
24096
  }
23653
- await fs24.rm(transitionFile);
24097
+ await fs25.rm(transitionFile);
23654
24098
  await hooks?.afterTransitionSuperseded?.(journal);
23655
24099
  }
23656
24100
  }
@@ -23732,7 +24176,7 @@ async function continueNativeSchemaMigrationLocked(paths, name, hooks) {
23732
24176
  }
23733
24177
  await continueTransitionSupersede(paths, journal, hooks);
23734
24178
  await continueRunRetreat(paths, journal, hooks);
23735
- await fs24.rm(nativeSchemaMigrationJournalFile(paths, name), { force: true });
24179
+ await fs25.rm(nativeSchemaMigrationJournalFile(paths, name), { force: true });
23736
24180
  return journal.nextState;
23737
24181
  }
23738
24182
  async function stableEvidenceRetreat(options) {
@@ -24038,7 +24482,7 @@ var MANAGED_KINDS = [
24038
24482
  "check-receipts"
24039
24483
  ];
24040
24484
  function sameObjectIdentity(left, right) {
24041
- return sameNativeFileObject(
24485
+ return sameFileObject(
24042
24486
  { ...left, birthtime: left.birthtimeMs },
24043
24487
  {
24044
24488
  ...right,
@@ -24089,11 +24533,11 @@ async function captureDirectoryChain6(managedRoot, directory) {
24089
24533
  let cursor = root;
24090
24534
  for (const segment of ["", ...segments]) {
24091
24535
  if (segment) cursor = path39.join(cursor, segment);
24092
- const stat = await fs25.lstat(cursor);
24536
+ const stat = await fs26.lstat(cursor);
24093
24537
  if (!stat.isDirectory() || stat.isSymbolicLink()) {
24094
24538
  throw new Error(`Native evidence retention parent must be a real directory: ${cursor}`);
24095
24539
  }
24096
- const realPath = await fs25.realpath(cursor);
24540
+ const realPath = await fs26.realpath(cursor);
24097
24541
  if (chain.length > 0 && !isInsidePath(chain[0].realPath, realPath)) {
24098
24542
  throw new Error(`Native evidence retention parent resolves outside its change: ${cursor}`);
24099
24543
  }
@@ -24103,8 +24547,8 @@ async function captureDirectoryChain6(managedRoot, directory) {
24103
24547
  }
24104
24548
  async function verifyDirectoryChain7(chain) {
24105
24549
  for (const identity of chain) {
24106
- const stat = await fs25.lstat(identity.path);
24107
- 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) {
24108
24552
  throw new Error(`Native evidence retention parent changed: ${identity.path}`);
24109
24553
  }
24110
24554
  }
@@ -24176,7 +24620,7 @@ function parseDocument4(kind, hash6, value, expectedChange) {
24176
24620
  return { canonical: receipt, dependencies: [] };
24177
24621
  }
24178
24622
  async function readCanonicalDocument(options) {
24179
- const before = await fs25.lstat(options.file);
24623
+ const before = await fs26.lstat(options.file);
24180
24624
  if (!before.isFile() || before.isSymbolicLink()) {
24181
24625
  throw new Error(`Native evidence entry is not a regular file: ${options.ref}`);
24182
24626
  }
@@ -24184,12 +24628,12 @@ async function readCanonicalDocument(options) {
24184
24628
  if (before.size > maximumBytes) {
24185
24629
  throw new Error(`Native evidence entry exceeds its byte budget: ${options.ref}`);
24186
24630
  }
24187
- const beforeRealPath = await fs25.realpath(options.file);
24631
+ const beforeRealPath = await fs26.realpath(options.file);
24188
24632
  if (!isInsidePath(options.directoryChain[0].realPath, beforeRealPath)) {
24189
24633
  throw new Error(`Native evidence entry resolves outside its change: ${options.ref}`);
24190
24634
  }
24191
- const openFlags = process.platform === "win32" ? "r" : fsConstants4.O_RDONLY | fsConstants4.O_NOFOLLOW | fsConstants4.O_NONBLOCK;
24192
- 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) => {
24193
24637
  if (error.code === "ELOOP" || error.code === "ENXIO") {
24194
24638
  throw new Error(`Native evidence entry became unsafe while opening: ${options.ref}`);
24195
24639
  }
@@ -24198,8 +24642,8 @@ async function readCanonicalDocument(options) {
24198
24642
  try {
24199
24643
  const [opened, pathAfterOpen, realPathAfterOpen] = await Promise.all([
24200
24644
  handle.stat(),
24201
- fs25.lstat(options.file),
24202
- fs25.realpath(options.file)
24645
+ fs26.lstat(options.file),
24646
+ fs26.realpath(options.file)
24203
24647
  ]);
24204
24648
  await verifyDirectoryChain7(options.directoryChain);
24205
24649
  if (!opened.isFile() || !pathAfterOpen.isFile() || pathAfterOpen.isSymbolicLink() || realPathAfterOpen !== beforeRealPath || !sameIdentity(fileIdentity2(before), opened) || !sameIdentity(fileIdentity2(opened), pathAfterOpen)) {
@@ -24220,8 +24664,8 @@ async function readCanonicalDocument(options) {
24220
24664
  }
24221
24665
  const [afterHandle, afterPath, afterRealPath] = await Promise.all([
24222
24666
  handle.stat(),
24223
- fs25.lstat(options.file),
24224
- fs25.realpath(options.file)
24667
+ fs26.lstat(options.file),
24668
+ fs26.realpath(options.file)
24225
24669
  ]);
24226
24670
  await verifyDirectoryChain7(options.directoryChain);
24227
24671
  if (!afterPath.isFile() || afterPath.isSymbolicLink() || afterRealPath !== beforeRealPath || !sameIdentity(fileIdentity2(opened), afterHandle) || !sameIdentity(fileIdentity2(opened), afterPath)) {
@@ -24258,7 +24702,7 @@ async function readCanonicalDocument(options) {
24258
24702
  }
24259
24703
  async function pathIsMissing(file) {
24260
24704
  try {
24261
- await fs25.lstat(file);
24705
+ await fs26.lstat(file);
24262
24706
  return false;
24263
24707
  } catch (error) {
24264
24708
  if (error.code === "ENOENT") return true;
@@ -24272,7 +24716,7 @@ async function inspectCleanupQuarantines(options) {
24272
24716
  let rootEntries;
24273
24717
  try {
24274
24718
  const rootChain = await captureDirectoryChain6(changeRoot, evidenceRoot);
24275
- rootEntries = await fs25.readdir(evidenceRoot, { withFileTypes: true });
24719
+ rootEntries = await fs26.readdir(evidenceRoot, { withFileTypes: true });
24276
24720
  await verifyDirectoryChain7(rootChain);
24277
24721
  } catch (error) {
24278
24722
  if (error.code === "ENOENT") {
@@ -24285,7 +24729,7 @@ async function inspectCleanupQuarantines(options) {
24285
24729
  if (!rootEntries.some((entry2) => entry2.name === kind)) continue;
24286
24730
  const directory = path39.join(evidenceRoot, kind);
24287
24731
  const directoryChain = await captureDirectoryChain6(changeRoot, directory);
24288
- const entries = (await fs25.readdir(directory, { withFileTypes: true })).sort(
24732
+ const entries = (await fs26.readdir(directory, { withFileTypes: true })).sort(
24289
24733
  (left, right) => compareText8(left.name, right.name)
24290
24734
  );
24291
24735
  const byOriginal = /* @__PURE__ */ new Map();
@@ -24388,20 +24832,20 @@ async function inspectCleanupQuarantines(options) {
24388
24832
  `Native evidence cleanup original or quarantine changed during recovery: ${recovery.ref}`
24389
24833
  );
24390
24834
  }
24391
- await fs25.rm(recovery.quarantine);
24835
+ await fs26.rm(recovery.quarantine);
24392
24836
  await verifyDirectoryChain7(recovery.document.directoryChain);
24393
24837
  continue;
24394
24838
  }
24395
24839
  if (!await pathIsMissing(recovery.original)) {
24396
24840
  throw new Error(`Native evidence cleanup original appeared during recovery: ${recovery.ref}`);
24397
24841
  }
24398
- const quarantineStat = await fs25.lstat(recovery.quarantine);
24842
+ const quarantineStat = await fs26.lstat(recovery.quarantine);
24399
24843
  if (!quarantineStat.isFile() || quarantineStat.isSymbolicLink() || !sameIdentity(recovery.document.identity, quarantineStat)) {
24400
24844
  throw new Error(
24401
24845
  `Native evidence cleanup quarantine changed during recovery: ${recovery.ref}`
24402
24846
  );
24403
24847
  }
24404
- await fs25.link(recovery.quarantine, recovery.original);
24848
+ await fs26.link(recovery.quarantine, recovery.original);
24405
24849
  await options.hooks.afterRecoveryLink?.({
24406
24850
  ref: recovery.ref,
24407
24851
  original: recovery.original,
@@ -24428,7 +24872,7 @@ async function inspectCleanupQuarantines(options) {
24428
24872
  if (!sameContentVersionAfterLinkOrRename(recovery.document.identity, restored.identity) || !sameIdentity(restored.identity, linkedQuarantine.identity)) {
24429
24873
  throw new Error(`Native evidence cleanup recovery identity changed: ${recovery.ref}`);
24430
24874
  }
24431
- await fs25.rm(recovery.quarantine);
24875
+ await fs26.rm(recovery.quarantine);
24432
24876
  await verifyDirectoryChain7(recovery.document.directoryChain);
24433
24877
  }
24434
24878
  return {
@@ -24452,7 +24896,7 @@ async function scanEvidenceStore(paths, name) {
24452
24896
  let rootEntries;
24453
24897
  try {
24454
24898
  const rootChain = await captureDirectoryChain6(changeRoot, evidenceRoot);
24455
- rootEntries = await fs25.readdir(evidenceRoot, { withFileTypes: true });
24899
+ rootEntries = await fs26.readdir(evidenceRoot, { withFileTypes: true });
24456
24900
  await verifyDirectoryChain7(rootChain);
24457
24901
  } catch (error) {
24458
24902
  if (error.code === "ENOENT") return [];
@@ -24472,7 +24916,7 @@ async function scanEvidenceStore(paths, name) {
24472
24916
  if (!rootEntries.some((entry2) => entry2.name === kind)) continue;
24473
24917
  const directory = path39.join(evidenceRoot, kind);
24474
24918
  const directoryChain = await captureDirectoryChain6(changeRoot, directory);
24475
- const entries = (await fs25.readdir(directory, { withFileTypes: true })).sort(
24919
+ const entries = (await fs26.readdir(directory, { withFileTypes: true })).sort(
24476
24920
  (left, right) => compareText8(left.name, right.name)
24477
24921
  );
24478
24922
  await verifyDirectoryChain7(directoryChain);
@@ -24616,13 +25060,13 @@ function summaryFinding(name, plan, repaired, evidenceRoot) {
24616
25060
  }
24617
25061
  async function restoreQuarantineIfSafe(quarantine, original) {
24618
25062
  try {
24619
- await fs25.lstat(original);
25063
+ await fs26.lstat(original);
24620
25064
  return;
24621
25065
  } catch (error) {
24622
25066
  if (error.code !== "ENOENT") return;
24623
25067
  }
24624
25068
  try {
24625
- await fs25.rename(quarantine, original);
25069
+ await fs26.rename(quarantine, original);
24626
25070
  } catch {
24627
25071
  }
24628
25072
  }
@@ -24638,8 +25082,8 @@ async function deleteCandidate(candidate, hooks) {
24638
25082
  name: candidate.change,
24639
25083
  directoryChain: candidate.directoryChain
24640
25084
  });
24641
- const before = await fs25.lstat(candidate.file);
24642
- const beforeRealPath = await fs25.realpath(candidate.file);
25085
+ const before = await fs26.lstat(candidate.file);
25086
+ const beforeRealPath = await fs26.realpath(candidate.file);
24643
25087
  if (!before.isFile() || before.isSymbolicLink() || !sameIdentity(candidate.identity, before) || !sameIdentity(candidate.identity, beforeDocument.identity) || !isInsidePath(candidate.directoryChain[0].realPath, beforeRealPath)) {
24644
25088
  throw new Error(`Native evidence candidate changed before cleanup: ${candidate.ref}`);
24645
25089
  }
@@ -24647,18 +25091,18 @@ async function deleteCandidate(candidate, hooks) {
24647
25091
  path39.dirname(candidate.file),
24648
25092
  `.${path39.basename(candidate.file)}.${randomUUID8()}.gc`
24649
25093
  );
24650
- await fs25.rename(candidate.file, quarantine);
25094
+ await fs26.rename(candidate.file, quarantine);
24651
25095
  try {
24652
25096
  const [moved, movedRealPath] = await Promise.all([
24653
- fs25.lstat(quarantine),
24654
- fs25.realpath(quarantine)
25097
+ fs26.lstat(quarantine),
25098
+ fs26.realpath(quarantine)
24655
25099
  ]);
24656
25100
  await verifyDirectoryChain7(candidate.directoryChain);
24657
25101
  if (!moved.isFile() || moved.isSymbolicLink() || !sameContentVersionAfterLinkOrRename(candidate.identity, moved) || !isInsidePath(candidate.directoryChain[0].realPath, movedRealPath)) {
24658
25102
  await restoreQuarantineIfSafe(quarantine, candidate.file);
24659
25103
  throw new Error(`Native evidence candidate changed during cleanup: ${candidate.ref}`);
24660
25104
  }
24661
- await fs25.rm(quarantine);
25105
+ await fs26.rm(quarantine);
24662
25106
  await verifyDirectoryChain7(candidate.directoryChain);
24663
25107
  } catch (error) {
24664
25108
  await restoreQuarantineIfSafe(quarantine, candidate.file);
@@ -24670,7 +25114,7 @@ async function hasPendingRelocationOrArchive(paths) {
24670
25114
  if (config?.native.pending_root_move) return true;
24671
25115
  let entries;
24672
25116
  try {
24673
- entries = await fs25.readdir(paths.transactionsDir, { withFileTypes: true });
25117
+ entries = await fs26.readdir(paths.transactionsDir, { withFileTypes: true });
24674
25118
  } catch (error) {
24675
25119
  if (error.code === "ENOENT") return false;
24676
25120
  throw error;
@@ -24690,7 +25134,7 @@ async function changeNames(paths, requested) {
24690
25134
  if (requested) return [requested];
24691
25135
  let entries;
24692
25136
  try {
24693
- entries = await fs25.readdir(paths.changesDir, { withFileTypes: true });
25137
+ entries = await fs26.readdir(paths.changesDir, { withFileTypes: true });
24694
25138
  } catch (error) {
24695
25139
  if (error.code === "ENOENT") return [];
24696
25140
  throw error;
@@ -24820,14 +25264,14 @@ async function inspectNativeEvidenceRetention(options) {
24820
25264
 
24821
25265
  // domains/comet-native/native-root-move.ts
24822
25266
  import { createHash as createHash14, randomUUID as randomUUID9 } from "crypto";
24823
- import { promises as fs26 } from "fs";
25267
+ import { promises as fs27 } from "fs";
24824
25268
  import path40 from "path";
24825
25269
  var NATIVE_ROOT_MOVE_MAX_FILE_BYTES = 64 * 1024 * 1024;
24826
25270
  var NATIVE_ROOT_MOVE_MAX_JOURNAL_BYTES = 256 * 1024;
24827
25271
  var NATIVE_ROOT_MOVE_MAX_MANIFEST_BYTES = 16 * 1024 * 1024;
24828
25272
  async function exists3(target) {
24829
25273
  try {
24830
- await fs26.access(target);
25274
+ await fs27.access(target);
24831
25275
  return true;
24832
25276
  } catch (error) {
24833
25277
  if (error.code === "ENOENT") return false;
@@ -24837,7 +25281,7 @@ async function exists3(target) {
24837
25281
  async function assertNoUnfinishedTransactions(paths) {
24838
25282
  let entries;
24839
25283
  try {
24840
- entries = await fs26.readdir(paths.transactionsDir, { withFileTypes: true });
25284
+ entries = await fs27.readdir(paths.transactionsDir, { withFileTypes: true });
24841
25285
  } catch (error) {
24842
25286
  if (error.code === "ENOENT") return;
24843
25287
  throw error;
@@ -24862,7 +25306,7 @@ async function assertNoUnfinishedTransactions(paths) {
24862
25306
  }
24863
25307
  }
24864
25308
  async function assertNoOtherLocks(paths, ownedLock) {
24865
- for (const entry2 of await fs26.readdir(paths.locksDir, { withFileTypes: true })) {
25309
+ for (const entry2 of await fs27.readdir(paths.locksDir, { withFileTypes: true })) {
24866
25310
  const file = path40.join(paths.locksDir, entry2.name);
24867
25311
  if (path40.resolve(file) === path40.resolve(ownedLock)) continue;
24868
25312
  if (entry2.isFile() || entry2.isSymbolicLink()) {
@@ -24873,7 +25317,7 @@ async function assertNoOtherLocks(paths, ownedLock) {
24873
25317
  async function refreshNativeWorkspaceIdentities(paths) {
24874
25318
  let entries;
24875
25319
  try {
24876
- entries = await fs26.readdir(paths.changesDir, { withFileTypes: true });
25320
+ entries = await fs27.readdir(paths.changesDir, { withFileTypes: true });
24877
25321
  } catch (error) {
24878
25322
  if (error.code === "ENOENT") return;
24879
25323
  throw error;
@@ -24913,7 +25357,7 @@ async function walkTree(root, options) {
24913
25357
  await protectedDirectory.verify();
24914
25358
  continue;
24915
25359
  }
24916
- const stat = await fs26.lstat(target);
25360
+ const stat = await fs27.lstat(target);
24917
25361
  if (entry2.isSymbolicLink() || stat.isSymbolicLink()) {
24918
25362
  if (options.rejectSymlinks) throw new Error(`Native root contains a symlink: ${target}`);
24919
25363
  await protectedDirectory.verify();
@@ -24967,7 +25411,7 @@ async function copyTree(source, target, excludedFile, targetRoot) {
24967
25411
  await protectedDirectory.verify();
24968
25412
  continue;
24969
25413
  }
24970
- const stat = await fs26.lstat(sourceEntry);
25414
+ const stat = await fs27.lstat(sourceEntry);
24971
25415
  if (entry2.isSymbolicLink() || stat.isSymbolicLink()) {
24972
25416
  throw new Error(`Native root contains a symlink: ${sourceEntry}`);
24973
25417
  }
@@ -25414,7 +25858,7 @@ async function finishForwardMove(options) {
25414
25858
  options.staging,
25415
25859
  options.lockFile
25416
25860
  );
25417
- await fs26.rename(options.staging, options.destinationPaths.nativeRoot);
25861
+ await fs27.rename(options.staging, options.destinationPaths.nativeRoot);
25418
25862
  }
25419
25863
  config = await setPendingStage({
25420
25864
  projectRoot: options.projectRoot,
@@ -25460,7 +25904,8 @@ async function finishForwardMove(options) {
25460
25904
  const stableNative = {
25461
25905
  artifact_root: config.native.artifact_root,
25462
25906
  language: config.native.language,
25463
- clarification_mode: config.native.clarification_mode
25907
+ clarification_mode: config.native.clarification_mode,
25908
+ snapshot: config.native.snapshot
25464
25909
  };
25465
25910
  const committed = {
25466
25911
  ...config,
@@ -25603,7 +26048,8 @@ async function recoverNativeRootMove(options) {
25603
26048
  const stableNative = {
25604
26049
  artifact_root: config.native.artifact_root,
25605
26050
  language: config.native.language,
25606
- clarification_mode: config.native.clarification_mode
26051
+ clarification_mode: config.native.clarification_mode,
26052
+ snapshot: config.native.snapshot
25607
26053
  };
25608
26054
  const restored = {
25609
26055
  ...config,
@@ -25734,7 +26180,7 @@ async function inspectSelection(paths, repair) {
25734
26180
  }
25735
26181
  }
25736
26182
  if (repair) {
25737
- await fs27.rm(file, { force: true });
26183
+ await fs28.rm(file, { force: true });
25738
26184
  return [
25739
26185
  {
25740
26186
  severity: "info",
@@ -26321,7 +26767,7 @@ async function doctorNativeProject(options) {
26321
26767
 
26322
26768
  // domains/comet-native/native-check-receipt.ts
26323
26769
  import { createHash as createHash15 } from "node:crypto";
26324
- import { constants as fsConstants5, promises as fs28 } from "node:fs";
26770
+ import { constants as fsConstants6, promises as fs29 } from "node:fs";
26325
26771
  import path42 from "node:path";
26326
26772
  import { TextDecoder as TextDecoder5 } from "node:util";
26327
26773
  var ISSUE_KIND_RANK = {
@@ -26347,6 +26793,7 @@ function projectionManifest2(projection) {
26347
26793
  createdAt: "1970-01-01T00:00:00.000Z",
26348
26794
  complete: projection.complete,
26349
26795
  limits: projection.limits,
26796
+ ...projection.policy ? { policy: projection.policy } : {},
26350
26797
  entries: projection.entries,
26351
26798
  omitted: projection.omitted,
26352
26799
  omittedCount: projection.omittedCount,
@@ -26354,16 +26801,17 @@ function projectionManifest2(projection) {
26354
26801
  };
26355
26802
  }
26356
26803
  async function collectBoundFacts(options) {
26804
+ const baseline = projectionManifest2(options.scope.baseline);
26357
26805
  const [contract, snapshot2] = await Promise.all([
26358
26806
  collectNativeContractFiles({
26359
26807
  changeDir: nativeChangeDir(options.paths, options.state.name),
26360
26808
  briefRef: options.state.brief,
26361
26809
  specChanges: options.state.spec_changes
26362
26810
  }),
26363
- createNativeContentSnapshot(options.paths, { origin: "explicit" })
26811
+ createNativeCurrentContentSnapshot(options.paths, baseline, { origin: "explicit" })
26364
26812
  ]);
26365
26813
  const currentBundle = buildNativeImplementationScopeBundle({
26366
- baseline: projectionManifest2(options.scope.baseline),
26814
+ baseline,
26367
26815
  current: snapshot2,
26368
26816
  contractHash: options.scope.authority.contractHash,
26369
26817
  declaredArtifacts: options.scope.authority.declaredArtifacts,
@@ -26408,7 +26856,7 @@ function staleReasons(before, after, scope) {
26408
26856
  }
26409
26857
  function sameDirectoryIdentity7(identity, stat) {
26410
26858
  const stableMetadata = identity.birthtimeMs === stat.birthtimeMs && identity.ctimeMs === stat.ctimeMs;
26411
- return stableMetadata && sameNativeFileObject(
26859
+ return stableMetadata && sameFileObject(
26412
26860
  { ...identity, birthtime: identity.birthtimeMs },
26413
26861
  {
26414
26862
  ...stat,
@@ -26418,7 +26866,7 @@ function sameDirectoryIdentity7(identity, stat) {
26418
26866
  }
26419
26867
  function sameFileIdentity8(left, right) {
26420
26868
  const stableMetadata = left.birthtimeMs === right.birthtimeMs && left.ctimeMs === right.ctimeMs && left.mtimeMs === right.mtimeMs && left.size === right.size;
26421
- return stableMetadata && sameNativeFileObject(
26869
+ return stableMetadata && sameFileObject(
26422
26870
  { ...left, birthtime: left.birthtimeMs },
26423
26871
  {
26424
26872
  ...right,
@@ -26441,7 +26889,7 @@ async function captureProjectDirectoryChain(projectRoot, directory) {
26441
26889
  if (segment) cursor = path42.join(cursor, segment);
26442
26890
  let stat;
26443
26891
  try {
26444
- stat = await fs28.lstat(cursor);
26892
+ stat = await fs29.lstat(cursor);
26445
26893
  } catch (error) {
26446
26894
  if (error.code === "ENOENT") {
26447
26895
  throw new ScopedFileError("scope-mismatch", "Scoped file parent no longer exists");
@@ -26451,7 +26899,7 @@ async function captureProjectDirectoryChain(projectRoot, directory) {
26451
26899
  if (!stat.isDirectory() || stat.isSymbolicLink()) {
26452
26900
  throw new ScopedFileError("unsafe-file", "Scoped file parent is not a real directory");
26453
26901
  }
26454
- const realPath = await fs28.realpath(cursor);
26902
+ const realPath = await fs29.realpath(cursor);
26455
26903
  if (chain.length > 0 && !isInsidePath(chain[0].realPath, realPath)) {
26456
26904
  throw new ScopedFileError("unsafe-file", "Scoped file parent resolves outside the project");
26457
26905
  }
@@ -26470,14 +26918,14 @@ async function verifyProjectDirectoryChain(chain) {
26470
26918
  for (const identity of chain) {
26471
26919
  let stat;
26472
26920
  try {
26473
- stat = await fs28.lstat(identity.path);
26921
+ stat = await fs29.lstat(identity.path);
26474
26922
  } catch (error) {
26475
26923
  if (error.code === "ENOENT") {
26476
26924
  throw new ScopedFileError("unsafe-file", "Scoped file parent changed while reading");
26477
26925
  }
26478
26926
  throw error;
26479
26927
  }
26480
- 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) {
26481
26929
  throw new ScopedFileError("unsafe-file", "Scoped file parent changed while reading");
26482
26930
  }
26483
26931
  }
@@ -26493,7 +26941,7 @@ async function readScopedFile(options) {
26493
26941
  );
26494
26942
  let before;
26495
26943
  try {
26496
- before = await fs28.lstat(lexicalFile);
26944
+ before = await fs29.lstat(lexicalFile);
26497
26945
  } catch (error) {
26498
26946
  if (error.code === "ENOENT") {
26499
26947
  throw new ScopedFileError("scope-mismatch", "Scoped file no longer exists");
@@ -26509,12 +26957,12 @@ async function readScopedFile(options) {
26509
26957
  "Scoped file size no longer matches its projection"
26510
26958
  );
26511
26959
  }
26512
- const beforeRealPath = await fs28.realpath(lexicalFile);
26960
+ const beforeRealPath = await fs29.realpath(lexicalFile);
26513
26961
  if (!isInsidePath(physicalRoot, beforeRealPath)) {
26514
26962
  throw new ScopedFileError("unsafe-file", "Scoped file resolves outside the project root");
26515
26963
  }
26516
- const openFlags = process.platform === "win32" ? "r" : fsConstants5.O_RDONLY | fsConstants5.O_NOFOLLOW | fsConstants5.O_NONBLOCK;
26517
- 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) => {
26518
26966
  if (error.code === "ENOENT") {
26519
26967
  throw new ScopedFileError("scope-mismatch", "Scoped file no longer exists");
26520
26968
  }
@@ -26526,8 +26974,8 @@ async function readScopedFile(options) {
26526
26974
  try {
26527
26975
  const [opened, pathAfterOpen, realPathAfterOpen] = await Promise.all([
26528
26976
  handle.stat(),
26529
- fs28.lstat(lexicalFile),
26530
- fs28.realpath(lexicalFile)
26977
+ fs29.lstat(lexicalFile),
26978
+ fs29.realpath(lexicalFile)
26531
26979
  ]);
26532
26980
  await verifyProjectDirectoryChain(chain);
26533
26981
  if (!opened.isFile() || !pathAfterOpen.isFile() || pathAfterOpen.isSymbolicLink() || realPathAfterOpen !== beforeRealPath || !sameFileIdentity8(before, opened) || !sameFileIdentity8(opened, pathAfterOpen)) {
@@ -26554,8 +27002,8 @@ async function readScopedFile(options) {
26554
27002
  }
26555
27003
  const [afterHandle, afterPath, afterRealPath] = await Promise.all([
26556
27004
  handle.stat(),
26557
- fs28.lstat(lexicalFile),
26558
- fs28.realpath(lexicalFile)
27005
+ fs29.lstat(lexicalFile),
27006
+ fs29.realpath(lexicalFile)
26559
27007
  ]);
26560
27008
  await verifyProjectDirectoryChain(chain);
26561
27009
  if (total !== options.file.expected.size || digest.digest("hex") !== options.file.expected.hash) {
@@ -26869,7 +27317,7 @@ async function checkpointNativeChange(options) {
26869
27317
  }
26870
27318
 
26871
27319
  // domains/comet-native/native-specs.ts
26872
- import { promises as fs29 } from "fs";
27320
+ import { promises as fs30 } from "fs";
26873
27321
  import path43 from "path";
26874
27322
  var MAX_NATIVE_PROPOSED_SPEC_DIRECTORY_ENTRIES = NATIVE_CONTRACT_FILE_LIMITS.maxSpecs * 4;
26875
27323
  async function optionalHash3(file) {
@@ -26895,7 +27343,7 @@ async function proposedCapabilities(paths, name) {
26895
27343
  }
26896
27344
  const capabilities = [];
26897
27345
  let entryCount = 0;
26898
- const directory = await fs29.opendir(specsDir);
27346
+ const directory = await fs30.opendir(specsDir);
26899
27347
  try {
26900
27348
  for await (const entry2 of directory) {
26901
27349
  entryCount += 1;
@@ -26913,7 +27361,7 @@ async function proposedCapabilities(paths, name) {
26913
27361
  await resolveContainedNativePath(paths.nativeRoot, source);
26914
27362
  let stat;
26915
27363
  try {
26916
- stat = await fs29.lstat(source);
27364
+ stat = await fs30.lstat(source);
26917
27365
  } catch (error) {
26918
27366
  if (error.code === "ENOENT") continue;
26919
27367
  throw error;
@@ -27133,7 +27581,7 @@ async function readNativeProposedSpecs(paths, name) {
27133
27581
  import { randomUUID as randomUUID11 } from "crypto";
27134
27582
 
27135
27583
  // domains/comet-native/native-guards.ts
27136
- import { promises as fs30 } from "fs";
27584
+ import { promises as fs31 } from "fs";
27137
27585
  import path44 from "path";
27138
27586
  function validation(findings) {
27139
27587
  return { valid: findings.length === 0, findings };
@@ -27166,7 +27614,7 @@ async function validateBuildArtifacts(paths, evidence) {
27166
27614
  continue;
27167
27615
  }
27168
27616
  try {
27169
- await fs30.access(target);
27617
+ await fs31.access(target);
27170
27618
  } catch {
27171
27619
  findings.push({
27172
27620
  code: "build-artifact-missing",
@@ -27217,12 +27665,24 @@ async function inspectNativeGuard(options) {
27217
27665
  const brief = await validateNativeBrief(changeDir, options.state.brief);
27218
27666
  const specs = await validateNativeSpecChanges(options.paths, options.state);
27219
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
+ }
27220
27674
  } else if (options.state.phase === "build") {
27221
27675
  findings.push(
27222
27676
  ...(await validateNativeBrief(changeDir, options.state.brief)).findings,
27223
27677
  ...(await validateNativeSpecChanges(options.paths, options.state)).findings
27224
27678
  );
27225
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
+ }
27226
27686
  } else if (options.state.phase === "verify") {
27227
27687
  const report = options.evidence.verificationReport ?? options.state.verification_report;
27228
27688
  if (!report) {
@@ -27250,7 +27710,7 @@ async function inspectNativeGuard(options) {
27250
27710
  }
27251
27711
 
27252
27712
  // domains/comet-native/native-build-evidence.ts
27253
- import { promises as fs31 } from "node:fs";
27713
+ import { promises as fs32 } from "node:fs";
27254
27714
  import path45 from "node:path";
27255
27715
  var NATIVE_BUILD_EVIDENCE_LIMITS = {
27256
27716
  maxDeclaredArtifacts: 128,
@@ -27326,12 +27786,12 @@ async function inspectDeclaredArtifact(paths, baseline, rawRef) {
27326
27786
  }
27327
27787
  await resolveContainedNativePath(paths.projectRoot, target);
27328
27788
  try {
27329
- const stat = await fs31.lstat(target);
27789
+ const stat = await fs32.lstat(target);
27330
27790
  if (stat.isSymbolicLink()) {
27331
27791
  throw new Error(`Native build artifact must not be a symlink or junction: ${artifactRef}`);
27332
27792
  }
27333
- const realTarget = await fs31.realpath(target);
27334
- const realProjectRoot = await fs31.realpath(paths.projectRoot);
27793
+ const realTarget = await fs32.realpath(target);
27794
+ const realProjectRoot = await fs32.realpath(paths.projectRoot);
27335
27795
  if (!isInsidePath(realProjectRoot, realTarget)) {
27336
27796
  throw new Error(`Native build artifact resolves outside the project: ${artifactRef}`);
27337
27797
  }
@@ -27401,7 +27861,7 @@ async function inspectNativeBuildEvidence(options) {
27401
27861
  baseline,
27402
27862
  refs: options.artifactRefs
27403
27863
  });
27404
- const current = await createNativeContentSnapshot(options.paths, {
27864
+ const current = await createNativeCurrentContentSnapshot(options.paths, baseline, {
27405
27865
  origin: "explicit",
27406
27866
  now: options.now
27407
27867
  });
@@ -27599,7 +28059,8 @@ async function retreatStaleNativeEvidence(options) {
27599
28059
  findings,
27600
28060
  continuation: nativeContinuation({
27601
28061
  state: options.state,
27602
- archiveReady: previousPhase === "archive"
28062
+ archiveReady: previousPhase === "archive",
28063
+ clarificationMode: options.transition.clarificationMode
27603
28064
  })
27604
28065
  };
27605
28066
  }
@@ -27654,7 +28115,10 @@ async function retreatStaleNativeEvidence(options) {
27654
28115
  next: "auto",
27655
28116
  nextCommand: null,
27656
28117
  findings: [],
27657
- continuation: nativeContinuation({ state: persisted })
28118
+ continuation: nativeContinuation({
28119
+ state: persisted,
28120
+ clarificationMode: options.transition.clarificationMode
28121
+ })
27658
28122
  };
27659
28123
  }
27660
28124
  async function advanceNativeChange(options) {
@@ -27701,7 +28165,8 @@ async function advanceNativeChangeLocked(options) {
27701
28165
  continuation: nativeContinuation({
27702
28166
  state,
27703
28167
  findings: repairFindings2,
27704
- archiveReady: state.phase === "archive" && state.verification_result === "pass"
28168
+ archiveReady: state.phase === "archive" && state.verification_result === "pass",
28169
+ clarificationMode: options.clarificationMode
27705
28170
  }),
27706
28171
  ...repair ? { repair } : {}
27707
28172
  };
@@ -27740,7 +28205,8 @@ async function advanceNativeChangeLocked(options) {
27740
28205
  const guard = await inspectNativeGuard({
27741
28206
  paths: options.paths,
27742
28207
  state: candidate,
27743
- evidence: options.evidence
28208
+ evidence: options.evidence,
28209
+ clarificationMode: options.clarificationMode
27744
28210
  });
27745
28211
  if (!guard.valid) {
27746
28212
  const findings = structureNativeFindings({
@@ -27754,7 +28220,11 @@ async function advanceNativeChangeLocked(options) {
27754
28220
  next: "manual",
27755
28221
  nextCommand: null,
27756
28222
  findings,
27757
- continuation: nativeContinuation({ state, findings })
28223
+ continuation: nativeContinuation({
28224
+ state,
28225
+ findings,
28226
+ clarificationMode: options.clarificationMode
28227
+ })
27758
28228
  };
27759
28229
  }
27760
28230
  const shapeContract = state.phase === "shape" ? await collectNativeContractFiles({
@@ -27793,7 +28263,11 @@ async function advanceNativeChangeLocked(options) {
27793
28263
  next: "manual",
27794
28264
  nextCommand: null,
27795
28265
  findings,
27796
- continuation: nativeContinuation({ state, findings })
28266
+ continuation: nativeContinuation({
28267
+ state,
28268
+ findings,
28269
+ clarificationMode: options.clarificationMode
28270
+ })
27797
28271
  };
27798
28272
  }
27799
28273
  const preparedScope = buildEvidence ? {
@@ -27825,7 +28299,11 @@ async function advanceNativeChangeLocked(options) {
27825
28299
  next: "manual",
27826
28300
  nextCommand: null,
27827
28301
  findings,
27828
- continuation: nativeContinuation({ state, findings }),
28302
+ continuation: nativeContinuation({
28303
+ state,
28304
+ findings,
28305
+ clarificationMode: options.clarificationMode
28306
+ }),
27829
28307
  preparedScope
27830
28308
  };
27831
28309
  }
@@ -27870,7 +28348,11 @@ async function advanceNativeChangeLocked(options) {
27870
28348
  next: "manual",
27871
28349
  nextCommand: null,
27872
28350
  findings,
27873
- continuation: nativeContinuation({ state, findings }),
28351
+ continuation: nativeContinuation({
28352
+ state,
28353
+ findings,
28354
+ clarificationMode: options.clarificationMode
28355
+ }),
27874
28356
  preparedScope: preparedScope ? { ...preparedScope, partialAllowanceRef: null } : preparedScope
27875
28357
  };
27876
28358
  }
@@ -27899,7 +28381,11 @@ async function advanceNativeChangeLocked(options) {
27899
28381
  next: "manual",
27900
28382
  nextCommand: null,
27901
28383
  findings,
27902
- continuation: nativeContinuation({ state, findings })
28384
+ continuation: nativeContinuation({
28385
+ state,
28386
+ findings,
28387
+ clarificationMode: options.clarificationMode
28388
+ })
27903
28389
  };
27904
28390
  }
27905
28391
  let repairDecision = null;
@@ -27954,7 +28440,7 @@ async function advanceNativeChangeLocked(options) {
27954
28440
  ...candidate,
27955
28441
  revision: state.revision + 1,
27956
28442
  phase: advanced.currentStep,
27957
- approval: options.evidence.confirmed ? "confirmed" : state.phase === "shape" && state.approval === null ? "implicit" : state.approval,
28443
+ approval: options.evidence.confirmed ? "confirmed" : state.approval,
27958
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,
27959
28445
  run_id: run.runId,
27960
28446
  ...state.phase === "build" ? {
@@ -28031,7 +28517,8 @@ async function advanceNativeChangeLocked(options) {
28031
28517
  continuation: nativeContinuation({
28032
28518
  state: persisted,
28033
28519
  findings: repairFindings,
28034
- archiveReady: persisted.phase === "archive" && persisted.verification_result === "pass"
28520
+ archiveReady: persisted.phase === "archive" && persisted.verification_result === "pass",
28521
+ clarificationMode: options.clarificationMode
28035
28522
  }),
28036
28523
  ...preparedScope ? { preparedScope } : {},
28037
28524
  ...repairDecision ? { repair: repairDecision } : {}
@@ -28039,7 +28526,7 @@ async function advanceNativeChangeLocked(options) {
28039
28526
  }
28040
28527
 
28041
28528
  // domains/comet-native/native-hook-guard.ts
28042
- import { promises as fs32 } from "fs";
28529
+ import { promises as fs33 } from "fs";
28043
28530
  import path46 from "path";
28044
28531
 
28045
28532
  // domains/comet-entry/hook-adapter.ts
@@ -28204,7 +28691,7 @@ async function activeNativeContext(projectRoot) {
28204
28691
  const paths = await nativeProjectPaths(projectRoot, config.native.artifact_root);
28205
28692
  let entries;
28206
28693
  try {
28207
- entries = await fs32.readdir(paths.changesDir, { withFileTypes: true });
28694
+ entries = await fs33.readdir(paths.changesDir, { withFileTypes: true });
28208
28695
  } catch (error) {
28209
28696
  if (error.code === "ENOENT") return { paths, changes: [] };
28210
28697
  throw error;
@@ -28325,6 +28812,7 @@ Commands:
28325
28812
  select <change-name>
28326
28813
  checkpoint <change-name> --summary <text> --next-action <text> [--artifact <project-relative>] [--expect-revision <n>]
28327
28814
  check <change-name>
28815
+ evidence format [--entries <path>]
28328
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>]
28329
28817
  archive <change-name> --dry-run
28330
28818
  archive <change-name> --expect-preflight <sha256>
@@ -28405,6 +28893,44 @@ async function doctorPaths(projectRoot) {
28405
28893
  function success(command, data, text) {
28406
28894
  return { command, exitCode: 0, data, text: text ?? JSON.stringify(data, null, 2) + "\n" };
28407
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
+ }
28408
28934
  async function dispatch(rawArgs, explicitProjectRoot) {
28409
28935
  if (rawArgs.length === 0 || rawArgs[0] === "--help" || rawArgs[0] === "help") {
28410
28936
  return { command: rawArgs[0] ?? null, exitCode: 0, data: { usage: USAGE }, text: USAGE };
@@ -28510,7 +29036,9 @@ async function dispatch(rawArgs, explicitProjectRoot) {
28510
29036
  await ensureNativeDirectories(paths);
28511
29037
  const state = await createNativeChange({ paths, name, language });
28512
29038
  await selectNativeChange(paths, state.name);
28513
- const status = await inspectNativeStatus(paths, state.name);
29039
+ const status = await inspectNativeStatus(paths, state.name, {
29040
+ clarificationMode: config.native.clarification_mode
29041
+ });
28514
29042
  return success(
28515
29043
  "new",
28516
29044
  { ...state, continuation: status.continuation },
@@ -28524,9 +29052,11 @@ async function dispatch(rawArgs, explicitProjectRoot) {
28524
29052
  const name = requiredPositional(rawArgs, "change name");
28525
29053
  const capability = requiredPositional(rawArgs, "capability");
28526
29054
  assertNoArguments(rawArgs);
28527
- const { paths } = await configuredPaths(projectRoot);
29055
+ const { config, paths } = await configuredPaths(projectRoot);
28528
29056
  const state = await markNativeSpecRemoval(paths, name, capability);
28529
- const status = await inspectNativeStatus(paths, state.name);
29057
+ const status = await inspectNativeStatus(paths, state.name, {
29058
+ clarificationMode: config.native.clarification_mode
29059
+ });
28530
29060
  return success(
28531
29061
  "spec remove",
28532
29062
  { ...state, continuation: status.continuation },
@@ -28539,9 +29069,11 @@ async function dispatch(rawArgs, explicitProjectRoot) {
28539
29069
  const summary = takeOption(rawArgs, "--summary");
28540
29070
  if (!summary) throw new NativeUsageError("--summary is required");
28541
29071
  assertNoArguments(rawArgs);
28542
- const { paths } = await configuredPaths(projectRoot);
29072
+ const { config, paths } = await configuredPaths(projectRoot);
28543
29073
  const state = await rebaseNativeSpecChanges({ paths, name, summary });
28544
- const status = await inspectNativeStatus(paths, state.name);
29074
+ const status = await inspectNativeStatus(paths, state.name, {
29075
+ clarificationMode: config.native.clarification_mode
29076
+ });
28545
29077
  return success(
28546
29078
  "spec rebase",
28547
29079
  { ...state, continuation: status.continuation },
@@ -28554,8 +29086,11 @@ async function dispatch(rawArgs, explicitProjectRoot) {
28554
29086
  if (command === "list") {
28555
29087
  const cursor = takeOption(rawArgs, "--cursor");
28556
29088
  assertNoArguments(rawArgs);
28557
- const { paths } = await configuredPaths(projectRoot);
28558
- 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
+ });
28559
29094
  return success("list", page);
28560
29095
  }
28561
29096
  if (command === "show") {
@@ -28611,19 +29146,25 @@ async function dispatch(rawArgs, explicitProjectRoot) {
28611
29146
  throw new NativeUsageError("--acceptance-cursor requires a change name");
28612
29147
  }
28613
29148
  assertNoArguments(rawArgs);
28614
- const { paths } = await configuredPaths(projectRoot);
29149
+ const { config, paths } = await configuredPaths(projectRoot);
28615
29150
  const data = name ? await inspectNativeStatus(paths, name, {
28616
29151
  details,
28617
- ...acceptanceCursor2 ? { acceptanceCursor: acceptanceCursor2 } : {}
28618
- }) : 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
+ });
28619
29158
  return success("status", data);
28620
29159
  }
28621
29160
  if (command === "select") {
28622
29161
  const name = requiredPositional(rawArgs, "change name");
28623
29162
  assertNoArguments(rawArgs);
28624
- const { paths } = await configuredPaths(projectRoot);
29163
+ const { config, paths } = await configuredPaths(projectRoot);
28625
29164
  await selectNativeChange(paths, name);
28626
- const status = await inspectNativeStatus(paths, name);
29165
+ const status = await inspectNativeStatus(paths, name, {
29166
+ clarificationMode: config.native.clarification_mode
29167
+ });
28627
29168
  return success(
28628
29169
  "select",
28629
29170
  { selected: name, continuation: status.continuation },
@@ -28640,7 +29181,7 @@ async function dispatch(rawArgs, explicitProjectRoot) {
28640
29181
  const artifacts = takeMany(rawArgs, "--artifact");
28641
29182
  const expectedRevision = revisionOption(rawArgs);
28642
29183
  assertNoArguments(rawArgs);
28643
- const { paths } = await configuredPaths(projectRoot);
29184
+ const { config, paths } = await configuredPaths(projectRoot);
28644
29185
  const result2 = await checkpointNativeChange({
28645
29186
  paths,
28646
29187
  name,
@@ -28649,7 +29190,9 @@ async function dispatch(rawArgs, explicitProjectRoot) {
28649
29190
  artifacts,
28650
29191
  expectedRevision
28651
29192
  });
28652
- const status = await inspectNativeStatus(paths, name);
29193
+ const status = await inspectNativeStatus(paths, name, {
29194
+ clarificationMode: config.native.clarification_mode
29195
+ });
28653
29196
  const manifestRef = path47.relative(
28654
29197
  paths.projectRoot,
28655
29198
  path47.join(nativeChangeDir(paths, name), ...result2.checkpoint.manifestRef.split("/"))
@@ -28688,6 +29231,43 @@ async function dispatch(rawArgs, explicitProjectRoot) {
28688
29231
  `
28689
29232
  };
28690
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
+ }
28691
29271
  if (command === "next") {
28692
29272
  const name = requiredPositional(rawArgs, "change name");
28693
29273
  const summary = takeOption(rawArgs, "--summary");
@@ -28736,7 +29316,7 @@ async function dispatch(rawArgs, explicitProjectRoot) {
28736
29316
  throw new NativeUsageError("--override-repair cannot be combined with --result");
28737
29317
  }
28738
29318
  assertNoArguments(rawArgs);
28739
- const { paths } = await configuredPaths(projectRoot);
29319
+ const { config, paths } = await configuredPaths(projectRoot);
28740
29320
  const evidence = {
28741
29321
  summary,
28742
29322
  ...confirmed ? { confirmed: true } : {},
@@ -28752,7 +29332,12 @@ async function dispatch(rawArgs, explicitProjectRoot) {
28752
29332
  ...repairOverrideSignature ? { repairOverrideSignature } : {},
28753
29333
  ...repairOverrideSummary ? { repairOverrideSummary } : {}
28754
29334
  };
28755
- 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
+ });
28756
29341
  if (result2.next === "manual") {
28757
29342
  const repairBlocked = result2.repair?.disposition === "manual-stop" || result2.repair?.disposition === "hard-stop" || result2.findings.some(
28758
29343
  (finding) => [
@@ -28771,7 +29356,9 @@ async function dispatch(rawArgs, explicitProjectRoot) {
28771
29356
  }
28772
29357
  };
28773
29358
  }
28774
- const status = await inspectNativeStatus(paths, name);
29359
+ const status = await inspectNativeStatus(paths, name, {
29360
+ clarificationMode: config.native.clarification_mode
29361
+ });
28775
29362
  return success("next", { ...result2, continuation: status.continuation });
28776
29363
  }
28777
29364
  if (command === "archive") {
@@ -28888,6 +29475,13 @@ function errorResult(command, error) {
28888
29475
  omittedByReason: error.omittedByReason,
28889
29476
  samplePaths: error.samplePaths,
28890
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
+ ],
28891
29485
  requiredAction: "resolve-native-baseline"
28892
29486
  },
28893
29487
  error: { code: "baseline-incomplete", message: error.message }