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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (161) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +3 -3
  3. package/assets/manifest.json +1 -1
  4. package/assets/skills/comet/reference/comet-yaml-fields.md +2 -2
  5. package/assets/skills/comet/scripts/comet-entry-runtime.mjs +118 -6
  6. package/assets/skills/comet/scripts/comet-hook-router.mjs +244 -26
  7. package/assets/skills/comet/scripts/comet-runtime.mjs +221 -104
  8. package/assets/skills/comet-archive/SKILL.md +29 -20
  9. package/assets/skills/comet-native/SKILL.md +23 -15
  10. package/assets/skills/comet-native/reference/artifacts.md +19 -4
  11. package/assets/skills/comet-native/reference/commands.md +8 -2
  12. package/assets/skills/comet-native/reference/recovery.md +1 -1
  13. package/assets/skills/comet-native/scripts/comet-native-runtime.mjs +1136 -471
  14. package/assets/skills-zh/comet/reference/comet-yaml-fields.md +2 -2
  15. package/assets/skills-zh/comet-archive/SKILL.md +29 -20
  16. package/assets/skills-zh/comet-native/SKILL.md +23 -15
  17. package/assets/skills-zh/comet-native/reference/artifacts.md +20 -5
  18. package/assets/skills-zh/comet-native/reference/commands.md +8 -2
  19. package/assets/skills-zh/comet-native/reference/recovery.md +1 -1
  20. package/dist/app/commands/init.d.ts.map +1 -1
  21. package/dist/app/commands/init.js +3 -6
  22. package/dist/app/commands/init.js.map +1 -1
  23. package/dist/domains/bundle/bundle-platform.d.ts.map +1 -1
  24. package/dist/domains/bundle/bundle-platform.js +1 -1
  25. package/dist/domains/bundle/bundle-platform.js.map +1 -1
  26. package/dist/domains/comet-classic/classic-guard.d.ts.map +1 -1
  27. package/dist/domains/comet-classic/classic-guard.js +3 -2
  28. package/dist/domains/comet-classic/classic-guard.js.map +1 -1
  29. package/dist/domains/comet-entry/current-selection.d.ts.map +1 -1
  30. package/dist/domains/comet-entry/current-selection.js +6 -8
  31. package/dist/domains/comet-entry/current-selection.js.map +1 -1
  32. package/dist/domains/comet-entry/project-status.d.ts.map +1 -1
  33. package/dist/domains/comet-entry/project-status.js +5 -1
  34. package/dist/domains/comet-entry/project-status.js.map +1 -1
  35. package/dist/domains/comet-native/native-archive-transaction.d.ts.map +1 -1
  36. package/dist/domains/comet-native/native-archive-transaction.js +5 -6
  37. package/dist/domains/comet-native/native-archive-transaction.js.map +1 -1
  38. package/dist/domains/comet-native/native-atomic-file.d.ts.map +1 -1
  39. package/dist/domains/comet-native/native-atomic-file.js +11 -7
  40. package/dist/domains/comet-native/native-atomic-file.js.map +1 -1
  41. package/dist/domains/comet-native/native-bounded-file.d.ts.map +1 -1
  42. package/dist/domains/comet-native/native-bounded-file.js +11 -7
  43. package/dist/domains/comet-native/native-bounded-file.js.map +1 -1
  44. package/dist/domains/comet-native/native-build-evidence.js +2 -2
  45. package/dist/domains/comet-native/native-build-evidence.js.map +1 -1
  46. package/dist/domains/comet-native/native-change.d.ts +4 -2
  47. package/dist/domains/comet-native/native-change.d.ts.map +1 -1
  48. package/dist/domains/comet-native/native-change.js +18 -4
  49. package/dist/domains/comet-native/native-change.js.map +1 -1
  50. package/dist/domains/comet-native/native-check-receipt-storage.d.ts.map +1 -1
  51. package/dist/domains/comet-native/native-check-receipt-storage.js +11 -7
  52. package/dist/domains/comet-native/native-check-receipt-storage.js.map +1 -1
  53. package/dist/domains/comet-native/native-check-receipt.d.ts.map +1 -1
  54. package/dist/domains/comet-native/native-check-receipt.js +16 -11
  55. package/dist/domains/comet-native/native-check-receipt.js.map +1 -1
  56. package/dist/domains/comet-native/native-cli.d.ts.map +1 -1
  57. package/dist/domains/comet-native/native-cli.js +120 -16
  58. package/dist/domains/comet-native/native-cli.js.map +1 -1
  59. package/dist/domains/comet-native/native-config.d.ts +5 -1
  60. package/dist/domains/comet-native/native-config.d.ts.map +1 -1
  61. package/dist/domains/comet-native/native-config.js +78 -0
  62. package/dist/domains/comet-native/native-config.js.map +1 -1
  63. package/dist/domains/comet-native/native-continuation.d.ts +2 -1
  64. package/dist/domains/comet-native/native-continuation.d.ts.map +1 -1
  65. package/dist/domains/comet-native/native-continuation.js +13 -5
  66. package/dist/domains/comet-native/native-continuation.js.map +1 -1
  67. package/dist/domains/comet-native/native-diagnostics.d.ts +7 -3
  68. package/dist/domains/comet-native/native-diagnostics.d.ts.map +1 -1
  69. package/dist/domains/comet-native/native-diagnostics.js +19 -9
  70. package/dist/domains/comet-native/native-diagnostics.js.map +1 -1
  71. package/dist/domains/comet-native/native-evidence-retention.d.ts.map +1 -1
  72. package/dist/domains/comet-native/native-evidence-retention.js +5 -3
  73. package/dist/domains/comet-native/native-evidence-retention.js.map +1 -1
  74. package/dist/domains/comet-native/native-evidence-storage.d.ts.map +1 -1
  75. package/dist/domains/comet-native/native-evidence-storage.js +11 -7
  76. package/dist/domains/comet-native/native-evidence-storage.js.map +1 -1
  77. package/dist/domains/comet-native/native-file-identity.d.ts +3 -0
  78. package/dist/domains/comet-native/native-file-identity.d.ts.map +1 -0
  79. package/dist/domains/comet-native/native-file-identity.js +2 -0
  80. package/dist/domains/comet-native/native-file-identity.js.map +1 -0
  81. package/dist/domains/comet-native/native-findings.d.ts.map +1 -1
  82. package/dist/domains/comet-native/native-findings.js +19 -1
  83. package/dist/domains/comet-native/native-findings.js.map +1 -1
  84. package/dist/domains/comet-native/native-guards.d.ts +2 -1
  85. package/dist/domains/comet-native/native-guards.d.ts.map +1 -1
  86. package/dist/domains/comet-native/native-guards.js +14 -0
  87. package/dist/domains/comet-native/native-guards.js.map +1 -1
  88. package/dist/domains/comet-native/native-lock.d.ts.map +1 -1
  89. package/dist/domains/comet-native/native-lock.js +30 -35
  90. package/dist/domains/comet-native/native-lock.js.map +1 -1
  91. package/dist/domains/comet-native/native-protected-file.d.ts.map +1 -1
  92. package/dist/domains/comet-native/native-protected-file.js +9 -8
  93. package/dist/domains/comet-native/native-protected-file.js.map +1 -1
  94. package/dist/domains/comet-native/native-root-move.d.ts.map +1 -1
  95. package/dist/domains/comet-native/native-root-move.js +2 -0
  96. package/dist/domains/comet-native/native-root-move.js.map +1 -1
  97. package/dist/domains/comet-native/native-run-store.d.ts.map +1 -1
  98. package/dist/domains/comet-native/native-run-store.js +9 -8
  99. package/dist/domains/comet-native/native-run-store.js.map +1 -1
  100. package/dist/domains/comet-native/native-snapshot.d.ts +5 -1
  101. package/dist/domains/comet-native/native-snapshot.d.ts.map +1 -1
  102. package/dist/domains/comet-native/native-snapshot.js +225 -5
  103. package/dist/domains/comet-native/native-snapshot.js.map +1 -1
  104. package/dist/domains/comet-native/native-transitions.d.ts +2 -1
  105. package/dist/domains/comet-native/native-transitions.d.ts.map +1 -1
  106. package/dist/domains/comet-native/native-transitions.js +34 -11
  107. package/dist/domains/comet-native/native-transitions.js.map +1 -1
  108. package/dist/domains/comet-native/native-types.d.ts +16 -0
  109. package/dist/domains/comet-native/native-types.d.ts.map +1 -1
  110. package/dist/domains/comet-native/native-verification-runtime.d.ts.map +1 -1
  111. package/dist/domains/comet-native/native-verification-runtime.js +5 -3
  112. package/dist/domains/comet-native/native-verification-runtime.js.map +1 -1
  113. package/dist/domains/comet-native/native-verification-scope.d.ts +2 -1
  114. package/dist/domains/comet-native/native-verification-scope.d.ts.map +1 -1
  115. package/dist/domains/comet-native/native-verification-scope.js +6 -1
  116. package/dist/domains/comet-native/native-verification-scope.js.map +1 -1
  117. package/dist/domains/dashboard/native-collector.d.ts.map +1 -1
  118. package/dist/domains/dashboard/native-collector.js +4 -1
  119. package/dist/domains/dashboard/native-collector.js.map +1 -1
  120. package/dist/domains/dashboard/web/index.html +14 -14
  121. package/dist/domains/skill/platform-install.d.ts.map +1 -1
  122. package/dist/domains/skill/platform-install.js +9 -0
  123. package/dist/domains/skill/platform-install.js.map +1 -1
  124. package/dist/domains/skill/project-instructions.d.ts.map +1 -1
  125. package/dist/domains/skill/project-instructions.js +4 -2
  126. package/dist/domains/skill/project-instructions.js.map +1 -1
  127. package/dist/domains/workflow-contract/project-config.d.ts +1 -1
  128. package/dist/domains/workflow-contract/project-config.d.ts.map +1 -1
  129. package/dist/domains/workflow-contract/project-config.js +24 -2
  130. package/dist/domains/workflow-contract/project-config.js.map +1 -1
  131. package/dist/platform/fs/file-identity.d.ts +9 -0
  132. package/dist/platform/fs/file-identity.d.ts.map +1 -0
  133. package/dist/platform/fs/file-identity.js +21 -0
  134. package/dist/platform/fs/file-identity.js.map +1 -0
  135. package/dist/platform/fs/file-system.d.ts.map +1 -1
  136. package/dist/platform/fs/file-system.js +13 -9
  137. package/dist/platform/fs/file-system.js.map +1 -1
  138. package/dist/platform/fs/race-safe-read.d.ts +47 -0
  139. package/dist/platform/fs/race-safe-read.d.ts.map +1 -0
  140. package/dist/platform/fs/race-safe-read.js +132 -0
  141. package/dist/platform/fs/race-safe-read.js.map +1 -0
  142. package/eval/README.md +1 -1
  143. package/eval/local/tasks/comet-native-clarification-depth/environment/Dockerfile +27 -0
  144. package/eval/local/tasks/comet-native-clarification-depth/environment/test_wordcount.py +25 -0
  145. package/eval/local/tasks/comet-native-clarification-depth/environment/wordcount.py +36 -0
  146. package/eval/local/tasks/comet-native-clarification-depth/instruction.md +17 -0
  147. package/eval/local/tasks/comet-native-clarification-depth/task.toml +47 -0
  148. package/eval/local/tasks/comet-native-clarification-depth/validation/test_native_clarification_depth.py +618 -0
  149. package/eval/local/tasks/comet-native-clarification-modes/validation/test_native_clarification_modes.py +1 -1
  150. package/eval/local/tasks/index.yaml +6 -0
  151. package/eval/local/tests/conftest.py +146 -2
  152. package/eval/local/tests/scaffold/test_conftest_helpers.py +188 -1
  153. package/eval/local/tests/scaffold/test_tasks.py +440 -8
  154. package/eval/local/tests/scaffold/test_utils.py +114 -1
  155. package/eval/local/tests/tasks/test_tasks.py +2 -0
  156. package/eval/pyproject.toml +4 -4
  157. package/eval/scaffold/python/aligned_comparison.py +1 -0
  158. package/eval/scaffold/python/tasks.py +2 -0
  159. package/eval/scaffold/shell/docker.sh +1 -1
  160. package/eval/scaffold/shell/run-claude-loop.sh +25 -2
  161. package/package.json +8 -1
package/LICENSE CHANGED
@@ -1,21 +1,21 @@
1
- MIT License
2
-
3
- Copyright (c) 2026 rpamis
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining a copy
6
- of this software and associated documentation files (the "Software"), to deal
7
- in the Software without restriction, including without limitation the rights
8
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- copies of the Software, and to permit persons to whom the Software is
10
- furnished to do so, subject to the following conditions:
11
-
12
- The above copyright notice and this permission notice shall be included in all
13
- copies or substantial portions of the Software.
14
-
15
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
- SOFTWARE.
1
+ MIT License
2
+
3
+ Copyright (c) 2026 rpamis
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -45,8 +45,8 @@ It provides two independent requirements workflows: Native for strong models, po
45
45
  It allows you to use a toolchain to handle everything from requirements to archiving, combine any skill to make it like Comet, evolving your skills based on scientific **Rubric**, **Pass@k**, and **Pass^k** scoring.
46
46
 
47
47
  > [!IMPORTANT]
48
- > **0.4.0-beta.7** — Adds a self-contained, recoverable Native workflow for strong models while keeping Native and Classic independent behind shared configuration, status, Guard, Dashboard, and Eval entry points.
49
- >
48
+ > **0.4.0-beta.7** — A new **high-performance, native and recoverable** Native workflow has been added. Native and Classic work independently through unified configuration, status, Guard, Dashboard, and Eval entry points. Eval alignment of experiments (16 tasks × 48 runs, selecting 41 pairs of samples that both passed) shows that **total Token has decreased by 76.8%**, **Agent rounds have dropped by 57.4%**, **time consumption has shrunk by 47.4%**, **pass^3 reaches 87.5% (+12.5pp) and pass@3 is 100%**. For more details, please refer to [Native vs. 0.4.0 Classic Real Evaluation](https://docs.comet.rpamis.com/zh/eval/comet-native-vs-040-experiment).
49
+ >
50
50
  > **0.4.0-beta.1** — Upgrades Comet to a pure Node runtime without Bash/WSL and adds three core capabilities: compose **any** Skill through `/comet-any`, evaluate **any** Skill through `comet eval` with LangSmith integration, and visualize every change through `comet dashboard`.
51
51
  >
52
52
  > **0.3.9** — Review mode (`off|standard|thorough`) controls Build/Verify code review with project defaults; init/update now use optional dependency prompts, broader CLI i18n, stronger phase guards, and macOS executable bits.
@@ -837,7 +837,7 @@ Track our development progress and upcoming features on the [Comet Roadmap](http
837
837
 
838
838
  ## Star History
839
839
 
840
- [![Star History Chart](https://api.star-history.com/chart?repos=rpamis/comet&type=date&legend=top-left&sealed_token=vRfs1efclBxdyNz7q0GUHGe9kUU96aSUCa1eHI8CEWehNHvZoop01eCjM0jpVMgeYBjvnGBcd0OUHnhQBC8p6gXP2Drpmo3pLXl_r0prKSuNW6OTqddOBCgaPtSt_KDlRgXjHZhx94_zcXWkIg5HOJEjPq4Qp2TMEa6inFxm7TixQQRIdPgKw2Z00nie)](https://www.star-history.com/?repos=rpamis%2Fcomet&type=date&legend=top-left)
840
+ [![Star History Chart](https://api.star-history.com/chart?repos=rpamis/comet&type=date&legend=top-left&sealed_token=lB84nxf4Emtuo-2F58IUt3ya0FIbY3dTDwnIl9UFA3u_IvEUqaI6dpYXmPItlNJcT00eAFXz5xyZN9TsoBh2la54tkgUbdncTIoFIJco7TO-Vrt-m9SFxw)](https://www.star-history.com/?repos=rpamis%2Fcomet&type=date&legend=top-left)
841
841
 
842
842
  ## Contributors
843
843
 
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "0.4.0-beta.7",
2
+ "version": "0.4.0-beta.9",
3
3
  "skills": [
4
4
  "comet/SKILL.md",
5
5
  "comet-classic/SKILL.md",
@@ -55,10 +55,10 @@ archived: false
55
55
  | `verify_result` | `pending`, `pass`, or `fail` |
56
56
  | `verify_failures` | Machine-owned consecutive verification failure count. `verify-fail` increments it; `verify-pass` or `archive-reopen` resets it to `0`. At `3`, the next failure requires the retry-limit strategy decision |
57
57
  | `verification_report` | Verification report file path; must point to an existing file before verify passes |
58
- | `branch_status` | `pending` or `handled`; keep pending through verify/archive, then set handled after the archive commit and selected branch handling complete |
58
+ | `branch_status` | `pending` or `handled`; keep `pending` through verify. After the user confirms immediate remote delivery before archive, set `handled` when archive finishes and include it in the only archive commit. `handled` means only that the delivery method is confirmed, not that push or PR creation succeeded; clear current selection and report workflow completion only after remote operations succeed |
59
59
  | `created_at` | Change creation date (auto-written at init), format `YYYY-MM-DD` |
60
60
  | `verified_at` | Verification pass timestamp; may be empty |
61
- | `archive_confirmation` | `null`, `pending`, or `confirmed`. `verify-pass` writes `pending` when entering the archive phase; after the user selects "Confirm archive" in `/comet-archive`, the `archive-confirm` transition writes `confirmed`; `archive-reopen` clears the field so an earlier confirmation cannot be reused |
61
+ | `archive_confirmation` | `null`, `pending`, or `confirmed`. `verify-pass` writes `pending` when entering the archive phase; after the user selects either "confirm archive and deliver remotely now" choice in `/comet-archive`, the `archive-confirm` transition writes `confirmed`; `archive-reopen` clears the field so an earlier confirmation cannot be reused |
62
62
  | `archived` | Whether the change has been archived |
63
63
 
64
64
  ## Optional Fields
@@ -7375,6 +7375,19 @@ import path3 from "path";
7375
7375
  // domains/workflow-contract/project-config.ts
7376
7376
  var import_yaml = __toESM(require_dist(), 1);
7377
7377
 
7378
+ // platform/fs/file-identity.ts
7379
+ function hasPlatformIdentity(value) {
7380
+ return value !== 0 && value !== 0n && value !== "0";
7381
+ }
7382
+ function sameFileObject(left, right) {
7383
+ const comparableDevice = hasPlatformIdentity(left.dev) && hasPlatformIdentity(right.dev);
7384
+ if (comparableDevice && left.dev !== right.dev) return false;
7385
+ const comparableInode = hasPlatformIdentity(left.ino) && hasPlatformIdentity(right.ino);
7386
+ if (comparableInode && left.ino !== right.ino) return false;
7387
+ if (comparableDevice && comparableInode) return true;
7388
+ return left.birthtime === right.birthtime;
7389
+ }
7390
+
7378
7391
  // domains/comet-native/native-protected-file.ts
7379
7392
  import { createHash } from "node:crypto";
7380
7393
  import { constants as fsConstants, promises as fs } from "node:fs";
@@ -7391,10 +7404,13 @@ function positiveLimit(value) {
7391
7404
  return value;
7392
7405
  }
7393
7406
  function sameDirectoryIdentity(expected, actual) {
7394
- if (expected.dev !== 0 || expected.ino !== 0 || actual.dev !== 0 || actual.ino !== 0) {
7395
- return expected.dev === actual.dev && expected.ino === actual.ino;
7396
- }
7397
- return expected.birthtimeMs === actual.birthtimeMs;
7407
+ return sameFileObject(
7408
+ { ...expected, birthtime: expected.birthtimeMs },
7409
+ {
7410
+ ...actual,
7411
+ birthtime: actual.birthtimeMs
7412
+ }
7413
+ );
7398
7414
  }
7399
7415
  function asFileIdentity(stat) {
7400
7416
  return {
@@ -7407,8 +7423,13 @@ function asFileIdentity(stat) {
7407
7423
  };
7408
7424
  }
7409
7425
  function sameFileIdentity(expected, actual) {
7410
- const sameObject = expected.dev !== 0 || expected.ino !== 0 || actual.dev !== 0 || actual.ino !== 0 ? expected.dev === actual.dev && expected.ino === actual.ino : expected.birthtimeMs === actual.birthtimeMs;
7411
- return sameObject && expected.birthtimeMs === actual.birthtimeMs && expected.ctimeMs === actual.ctimeMs && expected.mtimeMs === actual.mtimeMs && expected.size === actual.size;
7426
+ return sameFileObject(
7427
+ { ...expected, birthtime: expected.birthtimeMs },
7428
+ {
7429
+ ...actual,
7430
+ birthtime: actual.birthtimeMs
7431
+ }
7432
+ ) && expected.birthtimeMs === actual.birthtimeMs && expected.ctimeMs === actual.ctimeMs && expected.mtimeMs === actual.mtimeMs && expected.size === actual.size;
7412
7433
  }
7413
7434
  async function captureDirectoryIdentity(directory, label) {
7414
7435
  const stat = await fs.lstat(directory);
@@ -7604,11 +7625,100 @@ var NATIVE_KEYS = /* @__PURE__ */ new Set([
7604
7625
  "artifact_root",
7605
7626
  "language",
7606
7627
  "clarification_mode",
7628
+ "snapshot",
7607
7629
  "pending_root_move"
7608
7630
  ]);
7631
+ var SNAPSHOT_KEYS = /* @__PURE__ */ new Set([
7632
+ "include",
7633
+ "exclude",
7634
+ "max_files",
7635
+ "max_total_bytes",
7636
+ "max_duration_ms"
7637
+ ]);
7609
7638
  var PENDING_KEYS = /* @__PURE__ */ new Set(["id", "from_artifact_root", "to_artifact_root", "stage", "cleanup"]);
7610
7639
  var NATIVE_PROJECT_CONFIG_MAX_BYTES = 64 * 1024;
7611
7640
  var CLEANUP_KEYS = /* @__PURE__ */ new Set(["kind", "state", "manifest_hash"]);
7641
+ var MAX_NATIVE_SNAPSHOT_PATTERN_LENGTH = 1024;
7642
+ var MAX_NATIVE_SNAPSHOT_PATTERN_WILDCARDS = 64;
7643
+ var DEFAULT_NATIVE_SNAPSHOT_CONFIG = {
7644
+ include: ["**/*"],
7645
+ exclude: [],
7646
+ max_files: 1e4,
7647
+ max_total_bytes: 256 * 1024 * 1024,
7648
+ max_duration_ms: 6e4
7649
+ };
7650
+ function normalizeNativeSnapshotPattern(value, label) {
7651
+ if (typeof value !== "string" || value.length === 0 || value.includes("\\") || value.includes("\0") || value.startsWith("/") || value.split("/").includes("..")) {
7652
+ throw new Error(`${label} contains an unsafe pattern`);
7653
+ }
7654
+ if (value.length > MAX_NATIVE_SNAPSHOT_PATTERN_LENGTH) {
7655
+ throw new Error(`${label} exceeds ${MAX_NATIVE_SNAPSHOT_PATTERN_LENGTH} characters`);
7656
+ }
7657
+ let wildcardTokens = 0;
7658
+ for (let index = 0; index < value.length; index += 1) {
7659
+ if (value[index] === "?") {
7660
+ wildcardTokens += 1;
7661
+ } else if (value[index] === "*") {
7662
+ wildcardTokens += 1;
7663
+ if (value[index + 1] === "*") index += 1;
7664
+ }
7665
+ }
7666
+ if (wildcardTokens > MAX_NATIVE_SNAPSHOT_PATTERN_WILDCARDS) {
7667
+ throw new Error(
7668
+ `${label} contains more than ${MAX_NATIVE_SNAPSHOT_PATTERN_WILDCARDS} wildcard tokens`
7669
+ );
7670
+ }
7671
+ return value;
7672
+ }
7673
+ function snapshotPatterns(value, label, fallback) {
7674
+ if (value === void 0) return [...fallback];
7675
+ if (!Array.isArray(value)) {
7676
+ throw new Error(`${label} contains an unsafe pattern`);
7677
+ }
7678
+ return [...new Set(value.map((pattern) => normalizeNativeSnapshotPattern(pattern, label)))].sort(
7679
+ (left, right) => left.localeCompare(right, "en")
7680
+ );
7681
+ }
7682
+ function positiveSnapshotInteger(value, fallback, label) {
7683
+ const resolved = value ?? fallback;
7684
+ if (!Number.isSafeInteger(resolved) || resolved < 1) {
7685
+ throw new Error(`${label} must be a positive integer`);
7686
+ }
7687
+ return resolved;
7688
+ }
7689
+ function parseSnapshot(value) {
7690
+ if (value === void 0)
7691
+ return { ...DEFAULT_NATIVE_SNAPSHOT_CONFIG, include: ["**/*"], exclude: [] };
7692
+ const snapshot = record(value, "native.snapshot");
7693
+ rejectUnknown(snapshot, SNAPSHOT_KEYS, "native.snapshot");
7694
+ return {
7695
+ include: snapshotPatterns(
7696
+ snapshot.include,
7697
+ "native.snapshot.include",
7698
+ DEFAULT_NATIVE_SNAPSHOT_CONFIG.include
7699
+ ),
7700
+ exclude: snapshotPatterns(
7701
+ snapshot.exclude,
7702
+ "native.snapshot.exclude",
7703
+ DEFAULT_NATIVE_SNAPSHOT_CONFIG.exclude
7704
+ ),
7705
+ max_files: positiveSnapshotInteger(
7706
+ snapshot.max_files,
7707
+ DEFAULT_NATIVE_SNAPSHOT_CONFIG.max_files,
7708
+ "native.snapshot.max_files"
7709
+ ),
7710
+ max_total_bytes: positiveSnapshotInteger(
7711
+ snapshot.max_total_bytes,
7712
+ DEFAULT_NATIVE_SNAPSHOT_CONFIG.max_total_bytes,
7713
+ "native.snapshot.max_total_bytes"
7714
+ ),
7715
+ max_duration_ms: positiveSnapshotInteger(
7716
+ snapshot.max_duration_ms,
7717
+ DEFAULT_NATIVE_SNAPSHOT_CONFIG.max_duration_ms,
7718
+ "native.snapshot.max_duration_ms"
7719
+ )
7720
+ };
7721
+ }
7612
7722
  function record(value, label) {
7613
7723
  if (!value || typeof value !== "object" || Array.isArray(value)) {
7614
7724
  throw new Error(`${label} must be a mapping`);
@@ -7694,6 +7804,7 @@ function parseConfig(value) {
7694
7804
  throw new Error("native.clarification_mode must be sequential or batch");
7695
7805
  }
7696
7806
  const pending = parsePending(native.pending_root_move);
7807
+ const snapshot = parseSnapshot(native.snapshot);
7697
7808
  return {
7698
7809
  schema: "comet.project.v1",
7699
7810
  default_workflow: root.default_workflow,
@@ -7703,6 +7814,7 @@ function parseConfig(value) {
7703
7814
  artifact_root: normalizeArtifactRootRef(native.artifact_root),
7704
7815
  language,
7705
7816
  clarification_mode: clarificationMode,
7817
+ snapshot,
7706
7818
  ...pending ? { pending_root_move: pending } : {}
7707
7819
  }
7708
7820
  };
@@ -7964,8 +7964,127 @@ import { promises as fs6 } from "fs";
7964
7964
  import path6 from "path";
7965
7965
 
7966
7966
  // domains/comet-entry/current-selection.ts
7967
- import { promises as fs3 } from "fs";
7968
7967
  import path3 from "path";
7968
+
7969
+ // platform/fs/race-safe-read.ts
7970
+ import { constants as fsConstants, promises as fs3 } from "fs";
7971
+
7972
+ // platform/fs/file-identity.ts
7973
+ function hasPlatformIdentity(value) {
7974
+ return value !== 0 && value !== 0n && value !== "0";
7975
+ }
7976
+ function hasComparableFileObject(left, right) {
7977
+ return hasPlatformIdentity(left.dev) && hasPlatformIdentity(right.dev) && hasPlatformIdentity(left.ino) && hasPlatformIdentity(right.ino);
7978
+ }
7979
+ function sameFileObject(left, right) {
7980
+ const comparableDevice = hasPlatformIdentity(left.dev) && hasPlatformIdentity(right.dev);
7981
+ if (comparableDevice && left.dev !== right.dev) return false;
7982
+ const comparableInode = hasPlatformIdentity(left.ino) && hasPlatformIdentity(right.ino);
7983
+ if (comparableInode && left.ino !== right.ino) return false;
7984
+ if (comparableDevice && comparableInode) return true;
7985
+ return left.birthtime === right.birthtime;
7986
+ }
7987
+
7988
+ // platform/fs/race-safe-read.ts
7989
+ var RaceSafeReadError = class extends Error {
7990
+ reason;
7991
+ constructor(reason, message, options) {
7992
+ super(message, options);
7993
+ this.name = "RaceSafeReadError";
7994
+ this.reason = reason;
7995
+ }
7996
+ };
7997
+ function birthtimeOf(stat) {
7998
+ return "birthtimeNs" in stat && typeof stat.birthtimeNs === "bigint" ? stat.birthtimeNs : stat.birthtimeMs;
7999
+ }
8000
+ function ctimeOf(stat) {
8001
+ return "ctimeNs" in stat && typeof stat.ctimeNs === "bigint" ? stat.ctimeNs : stat.ctimeMs;
8002
+ }
8003
+ function identityOf(stat) {
8004
+ return { dev: stat.dev, ino: stat.ino, birthtime: birthtimeOf(stat) };
8005
+ }
8006
+ function sameStatIdentity(left, right) {
8007
+ const leftObject = identityOf(left);
8008
+ const rightObject = identityOf(right);
8009
+ if (hasComparableFileObject(leftObject, rightObject)) {
8010
+ return sameFileObject(leftObject, rightObject);
8011
+ }
8012
+ return sameFileObject(leftObject, rightObject) && birthtimeOf(left) === birthtimeOf(right) && ctimeOf(left) === ctimeOf(right) && left.size === right.size;
8013
+ }
8014
+ async function readFileRaceSafe(file, maxBytes, options = {}) {
8015
+ if (!Number.isSafeInteger(maxBytes) || maxBytes < 1) {
8016
+ throw new Error("race-safe read byte limit must be a positive integer");
8017
+ }
8018
+ const label = options.label ?? "file";
8019
+ const bigint = options.bigint === true;
8020
+ const before = await fs3.lstat(file, { bigint });
8021
+ if (!before.isFile() || before.isSymbolicLink()) {
8022
+ throw new RaceSafeReadError("not-regular-file", `${label} must be a regular file`);
8023
+ }
8024
+ if (BigInt(before.size) > BigInt(maxBytes)) {
8025
+ throw new RaceSafeReadError("too-large", `${label} exceeds ${maxBytes} bytes`);
8026
+ }
8027
+ const beforeRealPath = await fs3.realpath(file);
8028
+ await options.verify?.("pre-open", { realPath: beforeRealPath, identity: identityOf(before) });
8029
+ const flags = process.platform === "win32" ? fsConstants.O_RDONLY : fsConstants.O_RDONLY | fsConstants.O_NOFOLLOW | fsConstants.O_NONBLOCK;
8030
+ let handle;
8031
+ try {
8032
+ handle = await fs3.open(file, flags);
8033
+ } catch (error) {
8034
+ if (error.code === "ELOOP") {
8035
+ throw new RaceSafeReadError("not-regular-file", `${label} must be a regular file`, {
8036
+ cause: error
8037
+ });
8038
+ }
8039
+ throw error;
8040
+ }
8041
+ try {
8042
+ const [opened, pathAfterOpen, realPathAfterOpen] = await Promise.all([
8043
+ handle.stat({ bigint }),
8044
+ fs3.lstat(file, { bigint }),
8045
+ fs3.realpath(file)
8046
+ ]);
8047
+ if (!opened.isFile() || !pathAfterOpen.isFile() || pathAfterOpen.isSymbolicLink() || realPathAfterOpen !== beforeRealPath || !sameStatIdentity(before, opened) || !sameStatIdentity(before, pathAfterOpen)) {
8048
+ throw new RaceSafeReadError("changed", `${label} changed while opening`);
8049
+ }
8050
+ await options.verify?.("post-open", {
8051
+ realPath: realPathAfterOpen,
8052
+ identity: identityOf(opened)
8053
+ });
8054
+ await options.hooks?.afterOpen?.();
8055
+ const chunks = [];
8056
+ let total = 0;
8057
+ const buffer = Buffer.allocUnsafe(Math.min(64 * 1024, maxBytes + 1));
8058
+ for (; ; ) {
8059
+ const remaining = maxBytes + 1 - total;
8060
+ const { bytesRead } = await handle.read(buffer, 0, Math.min(buffer.length, remaining), null);
8061
+ if (bytesRead === 0) break;
8062
+ total += bytesRead;
8063
+ if (total > maxBytes) {
8064
+ throw new RaceSafeReadError("too-large", `${label} exceeds ${maxBytes} bytes`);
8065
+ }
8066
+ chunks.push(Buffer.from(buffer.subarray(0, bytesRead)));
8067
+ }
8068
+ await options.hooks?.beforeFinalCheck?.();
8069
+ const [afterHandle, afterPath, afterRealPath] = await Promise.all([
8070
+ handle.stat({ bigint }),
8071
+ fs3.lstat(file, { bigint }),
8072
+ fs3.realpath(file)
8073
+ ]);
8074
+ if (!afterPath.isFile() || afterPath.isSymbolicLink() || afterRealPath !== beforeRealPath || !sameStatIdentity(before, afterHandle) || !sameStatIdentity(before, afterPath)) {
8075
+ throw new RaceSafeReadError("changed", `${label} changed while reading`);
8076
+ }
8077
+ await options.verify?.("post-read", {
8078
+ realPath: afterRealPath,
8079
+ identity: identityOf(afterHandle)
8080
+ });
8081
+ return { bytes: Buffer.concat(chunks, total), stat: afterHandle, realPath: afterRealPath };
8082
+ } finally {
8083
+ await handle.close();
8084
+ }
8085
+ }
8086
+
8087
+ // domains/comet-entry/current-selection.ts
7969
8088
  var COMET_CURRENT_SELECTION_SCHEMA = "comet.selection.v2";
7970
8089
  var COMET_CURRENT_SELECTION_MAX_BYTES = 16 * 1024;
7971
8090
  function cometCurrentSelectionFile(projectRoot) {
@@ -8027,16 +8146,11 @@ function parseSelection(source) {
8027
8146
  async function readCometCurrentSelection(projectRoot) {
8028
8147
  let source;
8029
8148
  try {
8030
- const stat = await fs3.lstat(cometCurrentSelectionFile(projectRoot));
8031
- if (stat.isSymbolicLink() || !stat.isFile()) {
8032
- throw new Error("current change selection must be a regular file");
8033
- }
8034
- if (stat.size > COMET_CURRENT_SELECTION_MAX_BYTES) {
8035
- throw new Error(
8036
- `current change selection exceeds ${COMET_CURRENT_SELECTION_MAX_BYTES} bytes`
8037
- );
8038
- }
8039
- source = await fs3.readFile(cometCurrentSelectionFile(projectRoot), "utf8");
8149
+ const file = cometCurrentSelectionFile(projectRoot);
8150
+ const { bytes } = await readFileRaceSafe(file, COMET_CURRENT_SELECTION_MAX_BYTES, {
8151
+ label: "current change selection"
8152
+ });
8153
+ source = bytes.toString("utf8");
8040
8154
  } catch (error) {
8041
8155
  if (error.code === "ENOENT") return { status: "missing" };
8042
8156
  throw new Error(
@@ -9084,16 +9198,21 @@ function positiveLimit(value) {
9084
9198
  return value;
9085
9199
  }
9086
9200
  function sameDirectoryIdentity(identity, stat) {
9087
- if (identity.dev !== 0 || identity.ino !== 0 || stat.dev !== 0 || stat.ino !== 0) {
9088
- return identity.dev === stat.dev && identity.ino === stat.ino;
9089
- }
9090
- return identity.birthtimeMs === stat.birthtimeMs;
9201
+ return sameFileObject(
9202
+ { ...identity, birthtime: identity.birthtimeMs },
9203
+ {
9204
+ ...stat,
9205
+ birthtime: stat.birthtimeMs
9206
+ }
9207
+ );
9091
9208
  }
9092
9209
  function sameFileIdentity(left, right) {
9093
- if (left.dev !== 0 || left.ino !== 0 || right.dev !== 0 || right.ino !== 0) {
9094
- return left.dev === right.dev && left.ino === right.ino;
9210
+ const leftObject = { ...left, birthtime: left.birthtimeMs };
9211
+ const rightObject = { ...right, birthtime: right.birthtimeMs };
9212
+ if (hasComparableFileObject(leftObject, rightObject)) {
9213
+ return sameFileObject(leftObject, rightObject);
9095
9214
  }
9096
- return left.birthtimeMs === right.birthtimeMs && left.ctimeMs === right.ctimeMs && left.size === right.size;
9215
+ return sameFileObject(leftObject, rightObject) && left.birthtimeMs === right.birthtimeMs && left.ctimeMs === right.ctimeMs && left.size === right.size;
9097
9216
  }
9098
9217
  async function directoryIdentity(directory) {
9099
9218
  const stat = await fs8.lstat(directory);
@@ -9210,7 +9329,7 @@ var import_yaml3 = __toESM(require_dist(), 1);
9210
9329
 
9211
9330
  // domains/comet-native/native-protected-file.ts
9212
9331
  import { createHash as createHash2 } from "node:crypto";
9213
- import { constants as fsConstants, promises as fs9 } from "node:fs";
9332
+ import { constants as fsConstants2, promises as fs9 } from "node:fs";
9214
9333
  import path9 from "node:path";
9215
9334
  import { TextDecoder as TextDecoder2 } from "node:util";
9216
9335
  function isInside2(parent, target) {
@@ -9224,10 +9343,13 @@ function positiveLimit2(value) {
9224
9343
  return value;
9225
9344
  }
9226
9345
  function sameDirectoryIdentity2(expected, actual) {
9227
- if (expected.dev !== 0 || expected.ino !== 0 || actual.dev !== 0 || actual.ino !== 0) {
9228
- return expected.dev === actual.dev && expected.ino === actual.ino;
9229
- }
9230
- return expected.birthtimeMs === actual.birthtimeMs;
9346
+ return sameFileObject(
9347
+ { ...expected, birthtime: expected.birthtimeMs },
9348
+ {
9349
+ ...actual,
9350
+ birthtime: actual.birthtimeMs
9351
+ }
9352
+ );
9231
9353
  }
9232
9354
  function asFileIdentity(stat) {
9233
9355
  return {
@@ -9240,8 +9362,13 @@ function asFileIdentity(stat) {
9240
9362
  };
9241
9363
  }
9242
9364
  function sameFileIdentity2(expected, actual) {
9243
- const sameObject = expected.dev !== 0 || expected.ino !== 0 || actual.dev !== 0 || actual.ino !== 0 ? expected.dev === actual.dev && expected.ino === actual.ino : expected.birthtimeMs === actual.birthtimeMs;
9244
- return sameObject && expected.birthtimeMs === actual.birthtimeMs && expected.ctimeMs === actual.ctimeMs && expected.mtimeMs === actual.mtimeMs && expected.size === actual.size;
9365
+ return sameFileObject(
9366
+ { ...expected, birthtime: expected.birthtimeMs },
9367
+ {
9368
+ ...actual,
9369
+ birthtime: actual.birthtimeMs
9370
+ }
9371
+ ) && expected.birthtimeMs === actual.birthtimeMs && expected.ctimeMs === actual.ctimeMs && expected.mtimeMs === actual.mtimeMs && expected.size === actual.size;
9245
9372
  }
9246
9373
  async function captureDirectoryIdentity(directory, label) {
9247
9374
  const stat = await fs9.lstat(directory);
@@ -9322,7 +9449,7 @@ async function readNativeProtectedFile(options) {
9322
9449
  if (forbidden.some((identity) => isInside2(identity.realPath, beforeRealPath))) {
9323
9450
  throw new Error(`${options.label} resolves inside an excluded root`);
9324
9451
  }
9325
- const flags = process.platform === "win32" ? fsConstants.O_RDONLY : fsConstants.O_RDONLY | fsConstants.O_NOFOLLOW | fsConstants.O_NONBLOCK;
9452
+ const flags = process.platform === "win32" ? fsConstants2.O_RDONLY : fsConstants2.O_RDONLY | fsConstants2.O_NOFOLLOW | fsConstants2.O_NONBLOCK;
9326
9453
  const handle = await fs9.open(file, flags);
9327
9454
  try {
9328
9455
  const opened = await handle.stat();
@@ -9374,11 +9501,100 @@ var NATIVE_KEYS = /* @__PURE__ */ new Set([
9374
9501
  "artifact_root",
9375
9502
  "language",
9376
9503
  "clarification_mode",
9504
+ "snapshot",
9377
9505
  "pending_root_move"
9378
9506
  ]);
9507
+ var SNAPSHOT_KEYS = /* @__PURE__ */ new Set([
9508
+ "include",
9509
+ "exclude",
9510
+ "max_files",
9511
+ "max_total_bytes",
9512
+ "max_duration_ms"
9513
+ ]);
9379
9514
  var PENDING_KEYS = /* @__PURE__ */ new Set(["id", "from_artifact_root", "to_artifact_root", "stage", "cleanup"]);
9380
9515
  var NATIVE_PROJECT_CONFIG_MAX_BYTES = 64 * 1024;
9381
9516
  var CLEANUP_KEYS = /* @__PURE__ */ new Set(["kind", "state", "manifest_hash"]);
9517
+ var MAX_NATIVE_SNAPSHOT_PATTERN_LENGTH = 1024;
9518
+ var MAX_NATIVE_SNAPSHOT_PATTERN_WILDCARDS = 64;
9519
+ var DEFAULT_NATIVE_SNAPSHOT_CONFIG = {
9520
+ include: ["**/*"],
9521
+ exclude: [],
9522
+ max_files: 1e4,
9523
+ max_total_bytes: 256 * 1024 * 1024,
9524
+ max_duration_ms: 6e4
9525
+ };
9526
+ function normalizeNativeSnapshotPattern(value, label) {
9527
+ if (typeof value !== "string" || value.length === 0 || value.includes("\\") || value.includes("\0") || value.startsWith("/") || value.split("/").includes("..")) {
9528
+ throw new Error(`${label} contains an unsafe pattern`);
9529
+ }
9530
+ if (value.length > MAX_NATIVE_SNAPSHOT_PATTERN_LENGTH) {
9531
+ throw new Error(`${label} exceeds ${MAX_NATIVE_SNAPSHOT_PATTERN_LENGTH} characters`);
9532
+ }
9533
+ let wildcardTokens = 0;
9534
+ for (let index = 0; index < value.length; index += 1) {
9535
+ if (value[index] === "?") {
9536
+ wildcardTokens += 1;
9537
+ } else if (value[index] === "*") {
9538
+ wildcardTokens += 1;
9539
+ if (value[index + 1] === "*") index += 1;
9540
+ }
9541
+ }
9542
+ if (wildcardTokens > MAX_NATIVE_SNAPSHOT_PATTERN_WILDCARDS) {
9543
+ throw new Error(
9544
+ `${label} contains more than ${MAX_NATIVE_SNAPSHOT_PATTERN_WILDCARDS} wildcard tokens`
9545
+ );
9546
+ }
9547
+ return value;
9548
+ }
9549
+ function snapshotPatterns(value, label, fallback) {
9550
+ if (value === void 0) return [...fallback];
9551
+ if (!Array.isArray(value)) {
9552
+ throw new Error(`${label} contains an unsafe pattern`);
9553
+ }
9554
+ return [...new Set(value.map((pattern) => normalizeNativeSnapshotPattern(pattern, label)))].sort(
9555
+ (left, right) => left.localeCompare(right, "en")
9556
+ );
9557
+ }
9558
+ function positiveSnapshotInteger(value, fallback, label) {
9559
+ const resolved = value ?? fallback;
9560
+ if (!Number.isSafeInteger(resolved) || resolved < 1) {
9561
+ throw new Error(`${label} must be a positive integer`);
9562
+ }
9563
+ return resolved;
9564
+ }
9565
+ function parseSnapshot(value) {
9566
+ if (value === void 0)
9567
+ return { ...DEFAULT_NATIVE_SNAPSHOT_CONFIG, include: ["**/*"], exclude: [] };
9568
+ const snapshot = record(value, "native.snapshot");
9569
+ rejectUnknown(snapshot, SNAPSHOT_KEYS, "native.snapshot");
9570
+ return {
9571
+ include: snapshotPatterns(
9572
+ snapshot.include,
9573
+ "native.snapshot.include",
9574
+ DEFAULT_NATIVE_SNAPSHOT_CONFIG.include
9575
+ ),
9576
+ exclude: snapshotPatterns(
9577
+ snapshot.exclude,
9578
+ "native.snapshot.exclude",
9579
+ DEFAULT_NATIVE_SNAPSHOT_CONFIG.exclude
9580
+ ),
9581
+ max_files: positiveSnapshotInteger(
9582
+ snapshot.max_files,
9583
+ DEFAULT_NATIVE_SNAPSHOT_CONFIG.max_files,
9584
+ "native.snapshot.max_files"
9585
+ ),
9586
+ max_total_bytes: positiveSnapshotInteger(
9587
+ snapshot.max_total_bytes,
9588
+ DEFAULT_NATIVE_SNAPSHOT_CONFIG.max_total_bytes,
9589
+ "native.snapshot.max_total_bytes"
9590
+ ),
9591
+ max_duration_ms: positiveSnapshotInteger(
9592
+ snapshot.max_duration_ms,
9593
+ DEFAULT_NATIVE_SNAPSHOT_CONFIG.max_duration_ms,
9594
+ "native.snapshot.max_duration_ms"
9595
+ )
9596
+ };
9597
+ }
9382
9598
  function record(value, label) {
9383
9599
  if (!value || typeof value !== "object" || Array.isArray(value)) {
9384
9600
  throw new Error(`${label} must be a mapping`);
@@ -9464,6 +9680,7 @@ function parseConfig(value) {
9464
9680
  throw new Error("native.clarification_mode must be sequential or batch");
9465
9681
  }
9466
9682
  const pending = parsePending(native.pending_root_move);
9683
+ const snapshot = parseSnapshot(native.snapshot);
9467
9684
  return {
9468
9685
  schema: "comet.project.v1",
9469
9686
  default_workflow: root.default_workflow,
@@ -9473,6 +9690,7 @@ function parseConfig(value) {
9473
9690
  artifact_root: normalizeArtifactRootRef(native.artifact_root),
9474
9691
  language,
9475
9692
  clarification_mode: clarificationMode,
9693
+ snapshot,
9476
9694
  ...pending ? { pending_root_move: pending } : {}
9477
9695
  }
9478
9696
  };