@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.
- package/README.md +1 -1
- package/assets/manifest.json +1 -1
- package/assets/skills/comet/reference/comet-yaml-fields.md +2 -2
- package/assets/skills/comet/scripts/comet-entry-runtime.mjs +95 -4
- package/assets/skills/comet/scripts/comet-hook-router.mjs +224 -35
- package/assets/skills/comet/scripts/comet-runtime.mjs +218 -102
- package/assets/skills/comet-archive/SKILL.md +29 -20
- package/assets/skills/comet-native/SKILL.md +23 -15
- package/assets/skills/comet-native/reference/artifacts.md +19 -4
- package/assets/skills/comet-native/reference/commands.md +8 -2
- package/assets/skills/comet-native/reference/recovery.md +1 -1
- package/assets/skills/comet-native/scripts/comet-native-runtime.mjs +1052 -458
- package/assets/skills-zh/comet/reference/comet-yaml-fields.md +2 -2
- package/assets/skills-zh/comet-archive/SKILL.md +29 -20
- package/assets/skills-zh/comet-native/SKILL.md +23 -15
- package/assets/skills-zh/comet-native/reference/artifacts.md +20 -5
- package/assets/skills-zh/comet-native/reference/commands.md +8 -2
- package/assets/skills-zh/comet-native/reference/recovery.md +1 -1
- package/dist/app/commands/init.d.ts.map +1 -1
- package/dist/app/commands/init.js +3 -6
- package/dist/app/commands/init.js.map +1 -1
- package/dist/domains/comet-entry/current-selection.d.ts.map +1 -1
- package/dist/domains/comet-entry/current-selection.js +6 -8
- package/dist/domains/comet-entry/current-selection.js.map +1 -1
- package/dist/domains/comet-entry/project-status.d.ts.map +1 -1
- package/dist/domains/comet-entry/project-status.js +5 -1
- package/dist/domains/comet-entry/project-status.js.map +1 -1
- package/dist/domains/comet-native/native-build-evidence.js +2 -2
- package/dist/domains/comet-native/native-build-evidence.js.map +1 -1
- package/dist/domains/comet-native/native-change.d.ts +4 -2
- package/dist/domains/comet-native/native-change.d.ts.map +1 -1
- package/dist/domains/comet-native/native-change.js +18 -4
- package/dist/domains/comet-native/native-change.js.map +1 -1
- package/dist/domains/comet-native/native-check-receipt.d.ts.map +1 -1
- package/dist/domains/comet-native/native-check-receipt.js +5 -3
- package/dist/domains/comet-native/native-check-receipt.js.map +1 -1
- package/dist/domains/comet-native/native-cli.d.ts.map +1 -1
- package/dist/domains/comet-native/native-cli.js +120 -16
- package/dist/domains/comet-native/native-cli.js.map +1 -1
- package/dist/domains/comet-native/native-config.d.ts +5 -1
- package/dist/domains/comet-native/native-config.d.ts.map +1 -1
- package/dist/domains/comet-native/native-config.js +78 -0
- package/dist/domains/comet-native/native-config.js.map +1 -1
- package/dist/domains/comet-native/native-continuation.d.ts +2 -1
- package/dist/domains/comet-native/native-continuation.d.ts.map +1 -1
- package/dist/domains/comet-native/native-continuation.js +13 -5
- package/dist/domains/comet-native/native-continuation.js.map +1 -1
- package/dist/domains/comet-native/native-diagnostics.d.ts +7 -3
- package/dist/domains/comet-native/native-diagnostics.d.ts.map +1 -1
- package/dist/domains/comet-native/native-diagnostics.js +19 -9
- package/dist/domains/comet-native/native-diagnostics.js.map +1 -1
- package/dist/domains/comet-native/native-file-identity.d.ts +2 -8
- package/dist/domains/comet-native/native-file-identity.d.ts.map +1 -1
- package/dist/domains/comet-native/native-file-identity.js +1 -20
- package/dist/domains/comet-native/native-file-identity.js.map +1 -1
- package/dist/domains/comet-native/native-findings.d.ts.map +1 -1
- package/dist/domains/comet-native/native-findings.js +19 -1
- package/dist/domains/comet-native/native-findings.js.map +1 -1
- package/dist/domains/comet-native/native-guards.d.ts +2 -1
- package/dist/domains/comet-native/native-guards.d.ts.map +1 -1
- package/dist/domains/comet-native/native-guards.js +14 -0
- package/dist/domains/comet-native/native-guards.js.map +1 -1
- package/dist/domains/comet-native/native-lock.d.ts.map +1 -1
- package/dist/domains/comet-native/native-lock.js +24 -32
- package/dist/domains/comet-native/native-lock.js.map +1 -1
- package/dist/domains/comet-native/native-root-move.d.ts.map +1 -1
- package/dist/domains/comet-native/native-root-move.js +2 -0
- package/dist/domains/comet-native/native-root-move.js.map +1 -1
- package/dist/domains/comet-native/native-snapshot.d.ts +5 -1
- package/dist/domains/comet-native/native-snapshot.d.ts.map +1 -1
- package/dist/domains/comet-native/native-snapshot.js +218 -2
- package/dist/domains/comet-native/native-snapshot.js.map +1 -1
- package/dist/domains/comet-native/native-transitions.d.ts +2 -1
- package/dist/domains/comet-native/native-transitions.d.ts.map +1 -1
- package/dist/domains/comet-native/native-transitions.js +34 -11
- package/dist/domains/comet-native/native-transitions.js.map +1 -1
- package/dist/domains/comet-native/native-types.d.ts +16 -0
- package/dist/domains/comet-native/native-types.d.ts.map +1 -1
- package/dist/domains/comet-native/native-verification-runtime.d.ts.map +1 -1
- package/dist/domains/comet-native/native-verification-runtime.js +5 -3
- package/dist/domains/comet-native/native-verification-runtime.js.map +1 -1
- package/dist/domains/comet-native/native-verification-scope.d.ts +2 -1
- package/dist/domains/comet-native/native-verification-scope.d.ts.map +1 -1
- package/dist/domains/comet-native/native-verification-scope.js +6 -1
- package/dist/domains/comet-native/native-verification-scope.js.map +1 -1
- package/dist/domains/dashboard/native-collector.d.ts.map +1 -1
- package/dist/domains/dashboard/native-collector.js +4 -1
- package/dist/domains/dashboard/native-collector.js.map +1 -1
- package/dist/domains/skill/platform-install.d.ts.map +1 -1
- package/dist/domains/skill/platform-install.js +9 -0
- package/dist/domains/skill/platform-install.js.map +1 -1
- package/dist/domains/skill/project-instructions.d.ts.map +1 -1
- package/dist/domains/skill/project-instructions.js +4 -2
- package/dist/domains/skill/project-instructions.js.map +1 -1
- package/dist/domains/workflow-contract/project-config.d.ts +1 -1
- package/dist/domains/workflow-contract/project-config.d.ts.map +1 -1
- package/dist/domains/workflow-contract/project-config.js +24 -2
- package/dist/domains/workflow-contract/project-config.js.map +1 -1
- package/dist/platform/fs/file-identity.d.ts +9 -0
- package/dist/platform/fs/file-identity.d.ts.map +1 -0
- package/dist/platform/fs/file-identity.js +21 -0
- package/dist/platform/fs/file-identity.js.map +1 -0
- package/dist/platform/fs/file-system.d.ts.map +1 -1
- package/dist/platform/fs/file-system.js +13 -9
- package/dist/platform/fs/file-system.js.map +1 -1
- package/dist/platform/fs/race-safe-read.d.ts +47 -0
- package/dist/platform/fs/race-safe-read.d.ts.map +1 -0
- package/dist/platform/fs/race-safe-read.js +132 -0
- package/dist/platform/fs/race-safe-read.js.map +1 -0
- package/eval/README.md +1 -1
- package/eval/local/tasks/comet-native-clarification-depth/environment/Dockerfile +27 -0
- package/eval/local/tasks/comet-native-clarification-depth/environment/test_wordcount.py +25 -0
- package/eval/local/tasks/comet-native-clarification-depth/environment/wordcount.py +36 -0
- package/eval/local/tasks/comet-native-clarification-depth/instruction.md +17 -0
- package/eval/local/tasks/comet-native-clarification-depth/task.toml +47 -0
- package/eval/local/tasks/comet-native-clarification-depth/validation/test_native_clarification_depth.py +618 -0
- package/eval/local/tasks/comet-native-clarification-modes/validation/test_native_clarification_modes.py +1 -1
- package/eval/local/tasks/index.yaml +6 -0
- package/eval/local/tests/conftest.py +146 -2
- package/eval/local/tests/scaffold/test_conftest_helpers.py +188 -1
- package/eval/local/tests/scaffold/test_tasks.py +440 -8
- package/eval/local/tests/scaffold/test_utils.py +114 -1
- package/eval/local/tests/tasks/test_tasks.py +2 -0
- package/eval/scaffold/python/aligned_comparison.py +1 -0
- package/eval/scaffold/python/tasks.py +2 -0
- package/eval/scaffold/shell/docker.sh +1 -1
- package/eval/scaffold/shell/run-claude-loop.sh +25 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -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
|
-
[](https://www.star-history.com/?repos=rpamis%2Fcomet&type=date&legend=top-left)
|
|
841
841
|
|
|
842
842
|
## Contributors
|
|
843
843
|
|
package/assets/manifest.json
CHANGED
|
@@ -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
|
|
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 "
|
|
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,11 +7375,11 @@ import path3 from "path";
|
|
|
7375
7375
|
// domains/workflow-contract/project-config.ts
|
|
7376
7376
|
var import_yaml = __toESM(require_dist(), 1);
|
|
7377
7377
|
|
|
7378
|
-
//
|
|
7378
|
+
// platform/fs/file-identity.ts
|
|
7379
7379
|
function hasPlatformIdentity(value) {
|
|
7380
7380
|
return value !== 0 && value !== 0n && value !== "0";
|
|
7381
7381
|
}
|
|
7382
|
-
function
|
|
7382
|
+
function sameFileObject(left, right) {
|
|
7383
7383
|
const comparableDevice = hasPlatformIdentity(left.dev) && hasPlatformIdentity(right.dev);
|
|
7384
7384
|
if (comparableDevice && left.dev !== right.dev) return false;
|
|
7385
7385
|
const comparableInode = hasPlatformIdentity(left.ino) && hasPlatformIdentity(right.ino);
|
|
@@ -7404,7 +7404,7 @@ function positiveLimit(value) {
|
|
|
7404
7404
|
return value;
|
|
7405
7405
|
}
|
|
7406
7406
|
function sameDirectoryIdentity(expected, actual) {
|
|
7407
|
-
return
|
|
7407
|
+
return sameFileObject(
|
|
7408
7408
|
{ ...expected, birthtime: expected.birthtimeMs },
|
|
7409
7409
|
{
|
|
7410
7410
|
...actual,
|
|
@@ -7423,7 +7423,7 @@ function asFileIdentity(stat) {
|
|
|
7423
7423
|
};
|
|
7424
7424
|
}
|
|
7425
7425
|
function sameFileIdentity(expected, actual) {
|
|
7426
|
-
return
|
|
7426
|
+
return sameFileObject(
|
|
7427
7427
|
{ ...expected, birthtime: expected.birthtimeMs },
|
|
7428
7428
|
{
|
|
7429
7429
|
...actual,
|
|
@@ -7625,11 +7625,100 @@ var NATIVE_KEYS = /* @__PURE__ */ new Set([
|
|
|
7625
7625
|
"artifact_root",
|
|
7626
7626
|
"language",
|
|
7627
7627
|
"clarification_mode",
|
|
7628
|
+
"snapshot",
|
|
7628
7629
|
"pending_root_move"
|
|
7629
7630
|
]);
|
|
7631
|
+
var SNAPSHOT_KEYS = /* @__PURE__ */ new Set([
|
|
7632
|
+
"include",
|
|
7633
|
+
"exclude",
|
|
7634
|
+
"max_files",
|
|
7635
|
+
"max_total_bytes",
|
|
7636
|
+
"max_duration_ms"
|
|
7637
|
+
]);
|
|
7630
7638
|
var PENDING_KEYS = /* @__PURE__ */ new Set(["id", "from_artifact_root", "to_artifact_root", "stage", "cleanup"]);
|
|
7631
7639
|
var NATIVE_PROJECT_CONFIG_MAX_BYTES = 64 * 1024;
|
|
7632
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
|
+
}
|
|
7633
7722
|
function record(value, label) {
|
|
7634
7723
|
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
7635
7724
|
throw new Error(`${label} must be a mapping`);
|
|
@@ -7715,6 +7804,7 @@ function parseConfig(value) {
|
|
|
7715
7804
|
throw new Error("native.clarification_mode must be sequential or batch");
|
|
7716
7805
|
}
|
|
7717
7806
|
const pending = parsePending(native.pending_root_move);
|
|
7807
|
+
const snapshot = parseSnapshot(native.snapshot);
|
|
7718
7808
|
return {
|
|
7719
7809
|
schema: "comet.project.v1",
|
|
7720
7810
|
default_workflow: root.default_workflow,
|
|
@@ -7724,6 +7814,7 @@ function parseConfig(value) {
|
|
|
7724
7814
|
artifact_root: normalizeArtifactRootRef(native.artifact_root),
|
|
7725
7815
|
language,
|
|
7726
7816
|
clarification_mode: clarificationMode,
|
|
7817
|
+
snapshot,
|
|
7727
7818
|
...pending ? { pending_root_move: pending } : {}
|
|
7728
7819
|
}
|
|
7729
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
|
|
8031
|
-
|
|
8032
|
-
|
|
8033
|
-
}
|
|
8034
|
-
|
|
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(
|
|
@@ -9054,22 +9168,6 @@ function nativeSensitiveRelativePathReason(relativeRef) {
|
|
|
9054
9168
|
return null;
|
|
9055
9169
|
}
|
|
9056
9170
|
|
|
9057
|
-
// domains/comet-native/native-file-identity.ts
|
|
9058
|
-
function hasPlatformIdentity(value) {
|
|
9059
|
-
return value !== 0 && value !== 0n && value !== "0";
|
|
9060
|
-
}
|
|
9061
|
-
function hasComparableNativeFileObject(left, right) {
|
|
9062
|
-
return hasPlatformIdentity(left.dev) && hasPlatformIdentity(right.dev) && hasPlatformIdentity(left.ino) && hasPlatformIdentity(right.ino);
|
|
9063
|
-
}
|
|
9064
|
-
function sameNativeFileObject(left, right) {
|
|
9065
|
-
const comparableDevice = hasPlatformIdentity(left.dev) && hasPlatformIdentity(right.dev);
|
|
9066
|
-
if (comparableDevice && left.dev !== right.dev) return false;
|
|
9067
|
-
const comparableInode = hasPlatformIdentity(left.ino) && hasPlatformIdentity(right.ino);
|
|
9068
|
-
if (comparableInode && left.ino !== right.ino) return false;
|
|
9069
|
-
if (comparableDevice && comparableInode) return true;
|
|
9070
|
-
return left.birthtime === right.birthtime;
|
|
9071
|
-
}
|
|
9072
|
-
|
|
9073
9171
|
// domains/comet-native/native-bounded-file.ts
|
|
9074
9172
|
var DEFAULT_NATIVE_ARTIFACT_MAX_BYTES = 1024 * 1024;
|
|
9075
9173
|
function isInside(parent, target) {
|
|
@@ -9100,7 +9198,7 @@ function positiveLimit(value) {
|
|
|
9100
9198
|
return value;
|
|
9101
9199
|
}
|
|
9102
9200
|
function sameDirectoryIdentity(identity, stat) {
|
|
9103
|
-
return
|
|
9201
|
+
return sameFileObject(
|
|
9104
9202
|
{ ...identity, birthtime: identity.birthtimeMs },
|
|
9105
9203
|
{
|
|
9106
9204
|
...stat,
|
|
@@ -9111,10 +9209,10 @@ function sameDirectoryIdentity(identity, stat) {
|
|
|
9111
9209
|
function sameFileIdentity(left, right) {
|
|
9112
9210
|
const leftObject = { ...left, birthtime: left.birthtimeMs };
|
|
9113
9211
|
const rightObject = { ...right, birthtime: right.birthtimeMs };
|
|
9114
|
-
if (
|
|
9115
|
-
return
|
|
9212
|
+
if (hasComparableFileObject(leftObject, rightObject)) {
|
|
9213
|
+
return sameFileObject(leftObject, rightObject);
|
|
9116
9214
|
}
|
|
9117
|
-
return
|
|
9215
|
+
return sameFileObject(leftObject, rightObject) && left.birthtimeMs === right.birthtimeMs && left.ctimeMs === right.ctimeMs && left.size === right.size;
|
|
9118
9216
|
}
|
|
9119
9217
|
async function directoryIdentity(directory) {
|
|
9120
9218
|
const stat = await fs8.lstat(directory);
|
|
@@ -9231,7 +9329,7 @@ var import_yaml3 = __toESM(require_dist(), 1);
|
|
|
9231
9329
|
|
|
9232
9330
|
// domains/comet-native/native-protected-file.ts
|
|
9233
9331
|
import { createHash as createHash2 } from "node:crypto";
|
|
9234
|
-
import { constants as
|
|
9332
|
+
import { constants as fsConstants2, promises as fs9 } from "node:fs";
|
|
9235
9333
|
import path9 from "node:path";
|
|
9236
9334
|
import { TextDecoder as TextDecoder2 } from "node:util";
|
|
9237
9335
|
function isInside2(parent, target) {
|
|
@@ -9245,7 +9343,7 @@ function positiveLimit2(value) {
|
|
|
9245
9343
|
return value;
|
|
9246
9344
|
}
|
|
9247
9345
|
function sameDirectoryIdentity2(expected, actual) {
|
|
9248
|
-
return
|
|
9346
|
+
return sameFileObject(
|
|
9249
9347
|
{ ...expected, birthtime: expected.birthtimeMs },
|
|
9250
9348
|
{
|
|
9251
9349
|
...actual,
|
|
@@ -9264,7 +9362,7 @@ function asFileIdentity(stat) {
|
|
|
9264
9362
|
};
|
|
9265
9363
|
}
|
|
9266
9364
|
function sameFileIdentity2(expected, actual) {
|
|
9267
|
-
return
|
|
9365
|
+
return sameFileObject(
|
|
9268
9366
|
{ ...expected, birthtime: expected.birthtimeMs },
|
|
9269
9367
|
{
|
|
9270
9368
|
...actual,
|
|
@@ -9351,7 +9449,7 @@ async function readNativeProtectedFile(options) {
|
|
|
9351
9449
|
if (forbidden.some((identity) => isInside2(identity.realPath, beforeRealPath))) {
|
|
9352
9450
|
throw new Error(`${options.label} resolves inside an excluded root`);
|
|
9353
9451
|
}
|
|
9354
|
-
const flags = process.platform === "win32" ?
|
|
9452
|
+
const flags = process.platform === "win32" ? fsConstants2.O_RDONLY : fsConstants2.O_RDONLY | fsConstants2.O_NOFOLLOW | fsConstants2.O_NONBLOCK;
|
|
9355
9453
|
const handle = await fs9.open(file, flags);
|
|
9356
9454
|
try {
|
|
9357
9455
|
const opened = await handle.stat();
|
|
@@ -9403,11 +9501,100 @@ var NATIVE_KEYS = /* @__PURE__ */ new Set([
|
|
|
9403
9501
|
"artifact_root",
|
|
9404
9502
|
"language",
|
|
9405
9503
|
"clarification_mode",
|
|
9504
|
+
"snapshot",
|
|
9406
9505
|
"pending_root_move"
|
|
9407
9506
|
]);
|
|
9507
|
+
var SNAPSHOT_KEYS = /* @__PURE__ */ new Set([
|
|
9508
|
+
"include",
|
|
9509
|
+
"exclude",
|
|
9510
|
+
"max_files",
|
|
9511
|
+
"max_total_bytes",
|
|
9512
|
+
"max_duration_ms"
|
|
9513
|
+
]);
|
|
9408
9514
|
var PENDING_KEYS = /* @__PURE__ */ new Set(["id", "from_artifact_root", "to_artifact_root", "stage", "cleanup"]);
|
|
9409
9515
|
var NATIVE_PROJECT_CONFIG_MAX_BYTES = 64 * 1024;
|
|
9410
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
|
+
}
|
|
9411
9598
|
function record(value, label) {
|
|
9412
9599
|
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
9413
9600
|
throw new Error(`${label} must be a mapping`);
|
|
@@ -9493,6 +9680,7 @@ function parseConfig(value) {
|
|
|
9493
9680
|
throw new Error("native.clarification_mode must be sequential or batch");
|
|
9494
9681
|
}
|
|
9495
9682
|
const pending = parsePending(native.pending_root_move);
|
|
9683
|
+
const snapshot = parseSnapshot(native.snapshot);
|
|
9496
9684
|
return {
|
|
9497
9685
|
schema: "comet.project.v1",
|
|
9498
9686
|
default_workflow: root.default_workflow,
|
|
@@ -9502,6 +9690,7 @@ function parseConfig(value) {
|
|
|
9502
9690
|
artifact_root: normalizeArtifactRootRef(native.artifact_root),
|
|
9503
9691
|
language,
|
|
9504
9692
|
clarification_mode: clarificationMode,
|
|
9693
|
+
snapshot,
|
|
9505
9694
|
...pending ? { pending_root_move: pending } : {}
|
|
9506
9695
|
}
|
|
9507
9696
|
};
|