@rpamis/comet 0.4.0-beta.6 → 0.4.0-beta.8
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/LICENSE +21 -21
- package/README.md +209 -74
- package/assets/manifest.json +14 -5
- package/assets/skills/comet/SKILL.md +17 -282
- package/assets/skills/comet/reference/comet-yaml-fields.md +1 -1
- package/assets/skills/comet/reference/context-recovery.md +3 -3
- package/assets/skills/comet/reference/intent-frame.md +2 -2
- package/assets/skills/comet/reference/scripts.md +2 -2
- package/assets/skills/comet/rules/comet-workflow-guard.en.md +32 -0
- package/assets/skills/comet/rules/comet-workflow-guard.md +32 -0
- package/assets/skills/comet/scripts/comet-entry-runtime.mjs +7855 -0
- package/assets/skills/comet/scripts/comet-hook-router.mjs +10336 -0
- package/assets/skills/comet/scripts/comet-runtime.mjs +615 -501
- package/assets/skills/comet-any/SKILL.md +4 -4
- package/assets/skills/comet-any/reference/authoring-subagents.md +1 -1
- package/assets/skills/comet-any/reference/bundle-authoring.md +1 -1
- package/assets/skills/comet-any/reference/subagents/reference-author.md +1 -1
- package/assets/skills/comet-any/reference/subagents/script-author.md +1 -1
- package/assets/skills/comet-any/reference/subagents/skill-core-author.md +1 -1
- package/assets/skills/comet-any/reference/subagents/skill-reviewer.md +2 -2
- package/assets/skills/comet-any/reference/subagents/workflow-entry-author.md +3 -3
- package/assets/skills/comet-archive/SKILL.md +1 -2
- package/assets/skills/comet-build/SKILL.md +1 -1
- package/assets/skills/comet-classic/SKILL.md +293 -0
- package/assets/skills/comet-design/SKILL.md +1 -1
- package/assets/skills/comet-hotfix/SKILL.md +4 -4
- package/assets/skills/comet-native/SKILL.md +257 -0
- package/assets/skills/comet-native/reference/artifacts.md +204 -0
- package/assets/skills/comet-native/reference/commands.md +118 -0
- package/assets/skills/comet-native/reference/recovery.md +143 -0
- package/assets/skills/comet-native/scripts/comet-native-hook-guard.mjs +4 -0
- package/assets/skills/comet-native/scripts/comet-native-runtime.mjs +28969 -0
- package/assets/skills/comet-open/SKILL.md +4 -4
- package/assets/skills/comet-tweak/SKILL.md +7 -7
- package/assets/skills-zh/comet/SKILL.md +17 -270
- package/assets/skills-zh/comet/reference/comet-yaml-fields.md +1 -1
- package/assets/skills-zh/comet/reference/context-recovery.md +3 -3
- package/assets/skills-zh/comet/reference/intent-frame.md +2 -2
- package/assets/skills-zh/comet/reference/scripts.md +2 -2
- package/assets/skills-zh/comet-any/SKILL.md +4 -4
- package/assets/skills-zh/comet-any/reference/authoring-subagents.md +1 -1
- package/assets/skills-zh/comet-any/reference/bundle-authoring.md +1 -1
- package/assets/skills-zh/comet-any/reference/subagents/reference-author.md +1 -1
- package/assets/skills-zh/comet-any/reference/subagents/script-author.md +1 -1
- package/assets/skills-zh/comet-any/reference/subagents/skill-core-author.md +1 -1
- package/assets/skills-zh/comet-any/reference/subagents/skill-reviewer.md +2 -2
- package/assets/skills-zh/comet-any/reference/subagents/workflow-entry-author.md +3 -3
- package/assets/skills-zh/comet-archive/SKILL.md +1 -2
- package/assets/skills-zh/comet-build/SKILL.md +1 -1
- package/assets/skills-zh/comet-classic/SKILL.md +281 -0
- package/assets/skills-zh/comet-design/SKILL.md +1 -1
- package/assets/skills-zh/comet-hotfix/SKILL.md +4 -4
- package/assets/skills-zh/comet-native/SKILL.md +257 -0
- package/assets/skills-zh/comet-native/reference/artifacts.md +206 -0
- package/assets/skills-zh/comet-native/reference/commands.md +118 -0
- package/assets/skills-zh/comet-native/reference/recovery.md +143 -0
- package/assets/skills-zh/comet-open/SKILL.md +4 -4
- package/assets/skills-zh/comet-tweak/SKILL.md +7 -7
- package/dist/app/cli/index.js +59 -13
- package/dist/app/cli/index.js.map +1 -1
- package/dist/app/commands/command-result.d.ts +6 -0
- package/dist/app/commands/command-result.d.ts.map +1 -0
- package/dist/app/commands/command-result.js +4 -0
- package/dist/app/commands/command-result.js.map +1 -0
- package/dist/app/commands/doctor.d.ts +1 -0
- package/dist/app/commands/doctor.d.ts.map +1 -1
- package/dist/app/commands/doctor.js +201 -26
- package/dist/app/commands/doctor.js.map +1 -1
- package/dist/app/commands/eval.d.ts +3 -1
- package/dist/app/commands/eval.d.ts.map +1 -1
- package/dist/app/commands/eval.js +17 -7
- package/dist/app/commands/eval.js.map +1 -1
- package/dist/app/commands/i18n.d.ts +1 -1
- package/dist/app/commands/i18n.d.ts.map +1 -1
- package/dist/app/commands/i18n.js +6 -2
- package/dist/app/commands/i18n.js.map +1 -1
- package/dist/app/commands/init.d.ts +10 -2
- package/dist/app/commands/init.d.ts.map +1 -1
- package/dist/app/commands/init.js +331 -50
- package/dist/app/commands/init.js.map +1 -1
- package/dist/app/commands/native.d.ts +2 -0
- package/dist/app/commands/native.d.ts.map +1 -0
- package/dist/app/commands/native.js +10 -0
- package/dist/app/commands/native.js.map +1 -0
- package/dist/app/commands/project-scope-selection.d.ts.map +1 -1
- package/dist/app/commands/project-scope-selection.js +2 -0
- package/dist/app/commands/project-scope-selection.js.map +1 -1
- package/dist/app/commands/resume-probe.d.ts.map +1 -1
- package/dist/app/commands/resume-probe.js +8 -2
- package/dist/app/commands/resume-probe.js.map +1 -1
- package/dist/app/commands/status.d.ts +0 -32
- package/dist/app/commands/status.d.ts.map +1 -1
- package/dist/app/commands/status.js +44 -177
- package/dist/app/commands/status.js.map +1 -1
- package/dist/app/commands/update.d.ts +20 -4
- package/dist/app/commands/update.d.ts.map +1 -1
- package/dist/app/commands/update.js +698 -90
- package/dist/app/commands/update.js.map +1 -1
- package/dist/app/commands/workflow.d.ts +6 -0
- package/dist/app/commands/workflow.d.ts.map +1 -0
- package/dist/app/commands/workflow.js +11 -0
- package/dist/app/commands/workflow.js.map +1 -0
- package/dist/config/repository-layout.json +21 -1
- package/dist/domains/bundle/bundle-platform.d.ts.map +1 -1
- package/dist/domains/bundle/bundle-platform.js +1 -1
- package/dist/domains/bundle/bundle-platform.js.map +1 -1
- package/dist/domains/comet-classic/classic-archive.d.ts.map +1 -1
- package/dist/domains/comet-classic/classic-archive.js +3 -0
- package/dist/domains/comet-classic/classic-archive.js.map +1 -1
- package/dist/domains/comet-classic/classic-current-change.d.ts +3 -5
- package/dist/domains/comet-classic/classic-current-change.d.ts.map +1 -1
- package/dist/domains/comet-classic/classic-current-change.js +31 -57
- package/dist/domains/comet-classic/classic-current-change.js.map +1 -1
- package/dist/domains/comet-classic/classic-guard.d.ts.map +1 -1
- package/dist/domains/comet-classic/classic-guard.js +3 -2
- package/dist/domains/comet-classic/classic-guard.js.map +1 -1
- package/dist/domains/comet-classic/classic-hook-guard.d.ts +9 -0
- package/dist/domains/comet-classic/classic-hook-guard.d.ts.map +1 -1
- package/dist/domains/comet-classic/classic-hook-guard.js +84 -11
- package/dist/domains/comet-classic/classic-hook-guard.js.map +1 -1
- package/dist/domains/comet-classic/classic-project-config.d.ts.map +1 -1
- package/dist/domains/comet-classic/classic-project-config.js +12 -8
- package/dist/domains/comet-classic/classic-project-config.js.map +1 -1
- package/dist/domains/comet-classic/classic-runtime-run.d.ts.map +1 -1
- package/dist/domains/comet-classic/classic-runtime-run.js +9 -1
- package/dist/domains/comet-classic/classic-runtime-run.js.map +1 -1
- package/dist/domains/comet-entry/current-selection-repair.d.ts +17 -0
- package/dist/domains/comet-entry/current-selection-repair.d.ts.map +1 -0
- package/dist/domains/comet-entry/current-selection-repair.js +19 -0
- package/dist/domains/comet-entry/current-selection-repair.js.map +1 -0
- package/dist/domains/comet-entry/current-selection.d.ts +28 -0
- package/dist/domains/comet-entry/current-selection.d.ts.map +1 -0
- package/dist/domains/comet-entry/current-selection.js +116 -0
- package/dist/domains/comet-entry/current-selection.js.map +1 -0
- package/dist/domains/comet-entry/entry-runtime-entry.d.ts +2 -0
- package/dist/domains/comet-entry/entry-runtime-entry.d.ts.map +1 -0
- package/dist/domains/comet-entry/entry-runtime-entry.js +3 -0
- package/dist/domains/comet-entry/entry-runtime-entry.js.map +1 -0
- package/dist/domains/comet-entry/entry-runtime.d.ts +7 -0
- package/dist/domains/comet-entry/entry-runtime.d.ts.map +1 -0
- package/dist/domains/comet-entry/entry-runtime.js +55 -0
- package/dist/domains/comet-entry/entry-runtime.js.map +1 -0
- package/dist/domains/comet-entry/hook-adapter.d.ts +6 -0
- package/dist/domains/comet-entry/hook-adapter.d.ts.map +1 -0
- package/dist/domains/comet-entry/hook-adapter.js +202 -0
- package/dist/domains/comet-entry/hook-adapter.js.map +1 -0
- package/dist/domains/comet-entry/hook-router-entry.d.ts +8 -0
- package/dist/domains/comet-entry/hook-router-entry.d.ts.map +1 -0
- package/dist/domains/comet-entry/hook-router-entry.js +92 -0
- package/dist/domains/comet-entry/hook-router-entry.js.map +1 -0
- package/dist/domains/comet-entry/hook-router.d.ts +32 -0
- package/dist/domains/comet-entry/hook-router.d.ts.map +1 -0
- package/dist/domains/comet-entry/hook-router.js +133 -0
- package/dist/domains/comet-entry/hook-router.js.map +1 -0
- package/dist/domains/comet-entry/hook-types.d.ts +20 -0
- package/dist/domains/comet-entry/hook-types.d.ts.map +1 -0
- package/dist/domains/comet-entry/hook-types.js +2 -0
- package/dist/domains/comet-entry/hook-types.js.map +1 -0
- package/dist/domains/comet-entry/init-workflow.d.ts +16 -0
- package/dist/domains/comet-entry/init-workflow.d.ts.map +1 -0
- package/dist/domains/comet-entry/init-workflow.js +103 -0
- package/dist/domains/comet-entry/init-workflow.js.map +1 -0
- package/dist/domains/comet-entry/project-status.d.ts +3 -0
- package/dist/domains/comet-entry/project-status.d.ts.map +1 -0
- package/dist/domains/comet-entry/project-status.js +222 -0
- package/dist/domains/comet-entry/project-status.js.map +1 -0
- package/dist/domains/comet-entry/resolve-entry.d.ts +3 -0
- package/dist/domains/comet-entry/resolve-entry.d.ts.map +1 -0
- package/dist/domains/comet-entry/resolve-entry.js +22 -0
- package/dist/domains/comet-entry/resolve-entry.js.map +1 -0
- package/dist/domains/comet-entry/resume-probe.d.ts +34 -0
- package/dist/domains/comet-entry/resume-probe.d.ts.map +1 -0
- package/dist/domains/comet-entry/resume-probe.js +458 -0
- package/dist/domains/comet-entry/resume-probe.js.map +1 -0
- package/dist/domains/comet-entry/types.d.ts +59 -0
- package/dist/domains/comet-entry/types.d.ts.map +1 -0
- package/dist/domains/comet-entry/types.js +2 -0
- package/dist/domains/comet-entry/types.js.map +1 -0
- package/dist/domains/comet-entry/workflow-resolution.d.ts +8 -0
- package/dist/domains/comet-entry/workflow-resolution.d.ts.map +1 -0
- package/dist/domains/comet-entry/workflow-resolution.js +16 -0
- package/dist/domains/comet-entry/workflow-resolution.js.map +1 -0
- package/dist/domains/comet-native/index.d.ts +35 -0
- package/dist/domains/comet-native/index.d.ts.map +1 -0
- package/dist/domains/comet-native/index.js +35 -0
- package/dist/domains/comet-native/index.js.map +1 -0
- package/dist/domains/comet-native/native-acceptance.d.ts +42 -0
- package/dist/domains/comet-native/native-acceptance.d.ts.map +1 -0
- package/dist/domains/comet-native/native-acceptance.js +503 -0
- package/dist/domains/comet-native/native-acceptance.js.map +1 -0
- package/dist/domains/comet-native/native-archive-content.d.ts +26 -0
- package/dist/domains/comet-native/native-archive-content.d.ts.map +1 -0
- package/dist/domains/comet-native/native-archive-content.js +159 -0
- package/dist/domains/comet-native/native-archive-content.js.map +1 -0
- package/dist/domains/comet-native/native-archive-inspection.d.ts +9 -0
- package/dist/domains/comet-native/native-archive-inspection.d.ts.map +1 -0
- package/dist/domains/comet-native/native-archive-inspection.js +94 -0
- package/dist/domains/comet-native/native-archive-inspection.js.map +1 -0
- package/dist/domains/comet-native/native-archive-preflight.d.ts +52 -0
- package/dist/domains/comet-native/native-archive-preflight.d.ts.map +1 -0
- package/dist/domains/comet-native/native-archive-preflight.js +214 -0
- package/dist/domains/comet-native/native-archive-preflight.js.map +1 -0
- package/dist/domains/comet-native/native-archive-transaction.d.ts +19 -0
- package/dist/domains/comet-native/native-archive-transaction.d.ts.map +1 -0
- package/dist/domains/comet-native/native-archive-transaction.js +1180 -0
- package/dist/domains/comet-native/native-archive-transaction.js.map +1 -0
- package/dist/domains/comet-native/native-archive.d.ts +36 -0
- package/dist/domains/comet-native/native-archive.d.ts.map +1 -0
- package/dist/domains/comet-native/native-archive.js +403 -0
- package/dist/domains/comet-native/native-archive.js.map +1 -0
- package/dist/domains/comet-native/native-artifacts.d.ts +10 -0
- package/dist/domains/comet-native/native-artifacts.d.ts.map +1 -0
- package/dist/domains/comet-native/native-artifacts.js +204 -0
- package/dist/domains/comet-native/native-artifacts.js.map +1 -0
- package/dist/domains/comet-native/native-atomic-file.d.ts +10 -0
- package/dist/domains/comet-native/native-atomic-file.d.ts.map +1 -0
- package/dist/domains/comet-native/native-atomic-file.js +179 -0
- package/dist/domains/comet-native/native-atomic-file.js.map +1 -0
- package/dist/domains/comet-native/native-bounded-file.d.ts +19 -0
- package/dist/domains/comet-native/native-bounded-file.d.ts.map +1 -0
- package/dist/domains/comet-native/native-bounded-file.js +184 -0
- package/dist/domains/comet-native/native-bounded-file.js.map +1 -0
- package/dist/domains/comet-native/native-build-evidence.d.ts +42 -0
- package/dist/domains/comet-native/native-build-evidence.d.ts.map +1 -0
- package/dist/domains/comet-native/native-build-evidence.js +257 -0
- package/dist/domains/comet-native/native-build-evidence.js.map +1 -0
- package/dist/domains/comet-native/native-canonical-hash.d.ts +10 -0
- package/dist/domains/comet-native/native-canonical-hash.d.ts.map +1 -0
- package/dist/domains/comet-native/native-canonical-hash.js +100 -0
- package/dist/domains/comet-native/native-canonical-hash.js.map +1 -0
- package/dist/domains/comet-native/native-change-recovery.d.ts +11 -0
- package/dist/domains/comet-native/native-change-recovery.d.ts.map +1 -0
- package/dist/domains/comet-native/native-change-recovery.js +15 -0
- package/dist/domains/comet-native/native-change-recovery.js.map +1 -0
- package/dist/domains/comet-native/native-change.d.ts +61 -0
- package/dist/domains/comet-native/native-change.d.ts.map +1 -0
- package/dist/domains/comet-native/native-change.js +696 -0
- package/dist/domains/comet-native/native-change.js.map +1 -0
- package/dist/domains/comet-native/native-check-receipt-model.d.ts +63 -0
- package/dist/domains/comet-native/native-check-receipt-model.d.ts.map +1 -0
- package/dist/domains/comet-native/native-check-receipt-model.js +347 -0
- package/dist/domains/comet-native/native-check-receipt-model.js.map +1 -0
- package/dist/domains/comet-native/native-check-receipt-storage.d.ts +10 -0
- package/dist/domains/comet-native/native-check-receipt-storage.d.ts.map +1 -0
- package/dist/domains/comet-native/native-check-receipt-storage.js +200 -0
- package/dist/domains/comet-native/native-check-receipt-storage.js.map +1 -0
- package/dist/domains/comet-native/native-check-receipt.d.ts +19 -0
- package/dist/domains/comet-native/native-check-receipt.d.ts.map +1 -0
- package/dist/domains/comet-native/native-check-receipt.js +449 -0
- package/dist/domains/comet-native/native-check-receipt.js.map +1 -0
- package/dist/domains/comet-native/native-check.d.ts +18 -0
- package/dist/domains/comet-native/native-check.d.ts.map +1 -0
- package/dist/domains/comet-native/native-check.js +29 -0
- package/dist/domains/comet-native/native-check.js.map +1 -0
- package/dist/domains/comet-native/native-checkpoint-journal.d.ts +13 -0
- package/dist/domains/comet-native/native-checkpoint-journal.d.ts.map +1 -0
- package/dist/domains/comet-native/native-checkpoint-journal.js +53 -0
- package/dist/domains/comet-native/native-checkpoint-journal.js.map +1 -0
- package/dist/domains/comet-native/native-checkpoint-storage.d.ts +43 -0
- package/dist/domains/comet-native/native-checkpoint-storage.d.ts.map +1 -0
- package/dist/domains/comet-native/native-checkpoint-storage.js +487 -0
- package/dist/domains/comet-native/native-checkpoint-storage.js.map +1 -0
- package/dist/domains/comet-native/native-cli-entry.d.ts +2 -0
- package/dist/domains/comet-native/native-cli-entry.d.ts.map +1 -0
- package/dist/domains/comet-native/native-cli-entry.js +18 -0
- package/dist/domains/comet-native/native-cli-entry.js.map +1 -0
- package/dist/domains/comet-native/native-cli.d.ts +7 -0
- package/dist/domains/comet-native/native-cli.d.ts.map +1 -0
- package/dist/domains/comet-native/native-cli.js +642 -0
- package/dist/domains/comet-native/native-cli.js.map +1 -0
- package/dist/domains/comet-native/native-config.d.ts +15 -0
- package/dist/domains/comet-native/native-config.d.ts.map +1 -0
- package/dist/domains/comet-native/native-config.js +281 -0
- package/dist/domains/comet-native/native-config.js.map +1 -0
- package/dist/domains/comet-native/native-conflict-inspection.d.ts +17 -0
- package/dist/domains/comet-native/native-conflict-inspection.d.ts.map +1 -0
- package/dist/domains/comet-native/native-conflict-inspection.js +90 -0
- package/dist/domains/comet-native/native-conflict-inspection.js.map +1 -0
- package/dist/domains/comet-native/native-conflict-radar.d.ts +75 -0
- package/dist/domains/comet-native/native-conflict-radar.d.ts.map +1 -0
- package/dist/domains/comet-native/native-conflict-radar.js +326 -0
- package/dist/domains/comet-native/native-conflict-radar.js.map +1 -0
- package/dist/domains/comet-native/native-continuation.d.ts +9 -0
- package/dist/domains/comet-native/native-continuation.d.ts.map +1 -0
- package/dist/domains/comet-native/native-continuation.js +146 -0
- package/dist/domains/comet-native/native-continuation.js.map +1 -0
- package/dist/domains/comet-native/native-contract-files.d.ts +24 -0
- package/dist/domains/comet-native/native-contract-files.d.ts.map +1 -0
- package/dist/domains/comet-native/native-contract-files.js +66 -0
- package/dist/domains/comet-native/native-contract-files.js.map +1 -0
- package/dist/domains/comet-native/native-contract.d.ts +41 -0
- package/dist/domains/comet-native/native-contract.d.ts.map +1 -0
- package/dist/domains/comet-native/native-contract.js +141 -0
- package/dist/domains/comet-native/native-contract.js.map +1 -0
- package/dist/domains/comet-native/native-diagnostics.d.ts +18 -0
- package/dist/domains/comet-native/native-diagnostics.d.ts.map +1 -0
- package/dist/domains/comet-native/native-diagnostics.js +522 -0
- package/dist/domains/comet-native/native-diagnostics.js.map +1 -0
- package/dist/domains/comet-native/native-doctor.d.ts +12 -0
- package/dist/domains/comet-native/native-doctor.d.ts.map +1 -0
- package/dist/domains/comet-native/native-doctor.js +768 -0
- package/dist/domains/comet-native/native-doctor.js.map +1 -0
- package/dist/domains/comet-native/native-evidence-retention.d.ts +37 -0
- package/dist/domains/comet-native/native-evidence-retention.d.ts.map +1 -0
- package/dist/domains/comet-native/native-evidence-retention.js +887 -0
- package/dist/domains/comet-native/native-evidence-retention.js.map +1 -0
- package/dist/domains/comet-native/native-evidence-storage.d.ts +41 -0
- package/dist/domains/comet-native/native-evidence-storage.d.ts.map +1 -0
- package/dist/domains/comet-native/native-evidence-storage.js +355 -0
- package/dist/domains/comet-native/native-evidence-storage.js.map +1 -0
- package/dist/domains/comet-native/native-file-identity.d.ts +9 -0
- package/dist/domains/comet-native/native-file-identity.d.ts.map +1 -0
- package/dist/domains/comet-native/native-file-identity.js +21 -0
- package/dist/domains/comet-native/native-file-identity.js.map +1 -0
- package/dist/domains/comet-native/native-findings.d.ts +8 -0
- package/dist/domains/comet-native/native-findings.d.ts.map +1 -0
- package/dist/domains/comet-native/native-findings.js +238 -0
- package/dist/domains/comet-native/native-findings.js.map +1 -0
- package/dist/domains/comet-native/native-guards.d.ts +7 -0
- package/dist/domains/comet-native/native-guards.d.ts.map +1 -0
- package/dist/domains/comet-native/native-guards.js +126 -0
- package/dist/domains/comet-native/native-guards.js.map +1 -0
- package/dist/domains/comet-native/native-hash.d.ts +3 -0
- package/dist/domains/comet-native/native-hash.d.ts.map +1 -0
- package/dist/domains/comet-native/native-hash.js +12 -0
- package/dist/domains/comet-native/native-hash.js.map +1 -0
- package/dist/domains/comet-native/native-hook-guard.d.ts +17 -0
- package/dist/domains/comet-native/native-hook-guard.d.ts.map +1 -0
- package/dist/domains/comet-native/native-hook-guard.js +154 -0
- package/dist/domains/comet-native/native-hook-guard.js.map +1 -0
- package/dist/domains/comet-native/native-lock.d.ts +47 -0
- package/dist/domains/comet-native/native-lock.d.ts.map +1 -0
- package/dist/domains/comet-native/native-lock.js +398 -0
- package/dist/domains/comet-native/native-lock.js.map +1 -0
- package/dist/domains/comet-native/native-mutation-lock.d.ts +5 -0
- package/dist/domains/comet-native/native-mutation-lock.d.ts.map +1 -0
- package/dist/domains/comet-native/native-mutation-lock.js +66 -0
- package/dist/domains/comet-native/native-mutation-lock.js.map +1 -0
- package/dist/domains/comet-native/native-paths.d.ts +10 -0
- package/dist/domains/comet-native/native-paths.d.ts.map +1 -0
- package/dist/domains/comet-native/native-paths.js +171 -0
- package/dist/domains/comet-native/native-paths.js.map +1 -0
- package/dist/domains/comet-native/native-progress-checkpoint.d.ts +13 -0
- package/dist/domains/comet-native/native-progress-checkpoint.d.ts.map +1 -0
- package/dist/domains/comet-native/native-progress-checkpoint.js +104 -0
- package/dist/domains/comet-native/native-progress-checkpoint.js.map +1 -0
- package/dist/domains/comet-native/native-protected-file.d.ts +113 -0
- package/dist/domains/comet-native/native-protected-file.d.ts.map +1 -0
- package/dist/domains/comet-native/native-protected-file.js +510 -0
- package/dist/domains/comet-native/native-protected-file.js.map +1 -0
- package/dist/domains/comet-native/native-redaction.d.ts +2 -0
- package/dist/domains/comet-native/native-redaction.d.ts.map +1 -0
- package/dist/domains/comet-native/native-redaction.js +18 -0
- package/dist/domains/comet-native/native-redaction.js.map +1 -0
- package/dist/domains/comet-native/native-repair-integration.d.ts +42 -0
- package/dist/domains/comet-native/native-repair-integration.d.ts.map +1 -0
- package/dist/domains/comet-native/native-repair-integration.js +184 -0
- package/dist/domains/comet-native/native-repair-integration.js.map +1 -0
- package/dist/domains/comet-native/native-repair-runtime.d.ts +73 -0
- package/dist/domains/comet-native/native-repair-runtime.d.ts.map +1 -0
- package/dist/domains/comet-native/native-repair-runtime.js +509 -0
- package/dist/domains/comet-native/native-repair-runtime.js.map +1 -0
- package/dist/domains/comet-native/native-repair-stagnation.d.ts +85 -0
- package/dist/domains/comet-native/native-repair-stagnation.d.ts.map +1 -0
- package/dist/domains/comet-native/native-repair-stagnation.js +256 -0
- package/dist/domains/comet-native/native-repair-stagnation.js.map +1 -0
- package/dist/domains/comet-native/native-resume-view.d.ts +14 -0
- package/dist/domains/comet-native/native-resume-view.d.ts.map +1 -0
- package/dist/domains/comet-native/native-resume-view.js +105 -0
- package/dist/domains/comet-native/native-resume-view.js.map +1 -0
- package/dist/domains/comet-native/native-revision.d.ts +20 -0
- package/dist/domains/comet-native/native-revision.d.ts.map +1 -0
- package/dist/domains/comet-native/native-revision.js +32 -0
- package/dist/domains/comet-native/native-revision.js.map +1 -0
- package/dist/domains/comet-native/native-root-move.d.ts +20 -0
- package/dist/domains/comet-native/native-root-move.d.ts.map +1 -0
- package/dist/domains/comet-native/native-root-move.js +812 -0
- package/dist/domains/comet-native/native-root-move.js.map +1 -0
- package/dist/domains/comet-native/native-run-consistency.d.ts +3 -0
- package/dist/domains/comet-native/native-run-consistency.d.ts.map +1 -0
- package/dist/domains/comet-native/native-run-consistency.js +136 -0
- package/dist/domains/comet-native/native-run-consistency.js.map +1 -0
- package/dist/domains/comet-native/native-run-store.d.ts +41 -0
- package/dist/domains/comet-native/native-run-store.d.ts.map +1 -0
- package/dist/domains/comet-native/native-run-store.js +452 -0
- package/dist/domains/comet-native/native-run-store.js.map +1 -0
- package/dist/domains/comet-native/native-runtime-package.d.ts +16 -0
- package/dist/domains/comet-native/native-runtime-package.d.ts.map +1 -0
- package/dist/domains/comet-native/native-runtime-package.js +72 -0
- package/dist/domains/comet-native/native-runtime-package.js.map +1 -0
- package/dist/domains/comet-native/native-schema-migration.d.ts +11 -0
- package/dist/domains/comet-native/native-schema-migration.d.ts.map +1 -0
- package/dist/domains/comet-native/native-schema-migration.js +899 -0
- package/dist/domains/comet-native/native-schema-migration.js.map +1 -0
- package/dist/domains/comet-native/native-selection.d.ts +12 -0
- package/dist/domains/comet-native/native-selection.d.ts.map +1 -0
- package/dist/domains/comet-native/native-selection.js +52 -0
- package/dist/domains/comet-native/native-selection.js.map +1 -0
- package/dist/domains/comet-native/native-sensitive-paths.d.ts +7 -0
- package/dist/domains/comet-native/native-sensitive-paths.d.ts.map +1 -0
- package/dist/domains/comet-native/native-sensitive-paths.js +69 -0
- package/dist/domains/comet-native/native-sensitive-paths.js.map +1 -0
- package/dist/domains/comet-native/native-snapshot.d.ts +74 -0
- package/dist/domains/comet-native/native-snapshot.d.ts.map +1 -0
- package/dist/domains/comet-native/native-snapshot.js +2312 -0
- package/dist/domains/comet-native/native-snapshot.js.map +1 -0
- package/dist/domains/comet-native/native-specs.d.ts +12 -0
- package/dist/domains/comet-native/native-specs.d.ts.map +1 -0
- package/dist/domains/comet-native/native-specs.js +270 -0
- package/dist/domains/comet-native/native-specs.js.map +1 -0
- package/dist/domains/comet-native/native-trajectory-limits.d.ts +7 -0
- package/dist/domains/comet-native/native-trajectory-limits.d.ts.map +1 -0
- package/dist/domains/comet-native/native-trajectory-limits.js +13 -0
- package/dist/domains/comet-native/native-trajectory-limits.js.map +1 -0
- package/dist/domains/comet-native/native-trajectory-recovery.d.ts +37 -0
- package/dist/domains/comet-native/native-trajectory-recovery.d.ts.map +1 -0
- package/dist/domains/comet-native/native-trajectory-recovery.js +133 -0
- package/dist/domains/comet-native/native-trajectory-recovery.js.map +1 -0
- package/dist/domains/comet-native/native-trajectory.d.ts +16 -0
- package/dist/domains/comet-native/native-trajectory.d.ts.map +1 -0
- package/dist/domains/comet-native/native-trajectory.js +27 -0
- package/dist/domains/comet-native/native-trajectory.js.map +1 -0
- package/dist/domains/comet-native/native-transaction.d.ts +45 -0
- package/dist/domains/comet-native/native-transaction.d.ts.map +1 -0
- package/dist/domains/comet-native/native-transaction.js +846 -0
- package/dist/domains/comet-native/native-transaction.js.map +1 -0
- package/dist/domains/comet-native/native-transition-evidence.d.ts +4 -0
- package/dist/domains/comet-native/native-transition-evidence.d.ts.map +1 -0
- package/dist/domains/comet-native/native-transition-evidence.js +20 -0
- package/dist/domains/comet-native/native-transition-evidence.js.map +1 -0
- package/dist/domains/comet-native/native-transition-journal.d.ts +30 -0
- package/dist/domains/comet-native/native-transition-journal.d.ts.map +1 -0
- package/dist/domains/comet-native/native-transition-journal.js +819 -0
- package/dist/domains/comet-native/native-transition-journal.js.map +1 -0
- package/dist/domains/comet-native/native-transitions.d.ts +13 -0
- package/dist/domains/comet-native/native-transitions.d.ts.map +1 -0
- package/dist/domains/comet-native/native-transitions.js +632 -0
- package/dist/domains/comet-native/native-transitions.js.map +1 -0
- package/dist/domains/comet-native/native-types.d.ts +547 -0
- package/dist/domains/comet-native/native-types.d.ts.map +1 -0
- package/dist/domains/comet-native/native-types.js +8 -0
- package/dist/domains/comet-native/native-types.js.map +1 -0
- package/dist/domains/comet-native/native-verification-evidence.d.ts +86 -0
- package/dist/domains/comet-native/native-verification-evidence.d.ts.map +1 -0
- package/dist/domains/comet-native/native-verification-evidence.js +480 -0
- package/dist/domains/comet-native/native-verification-evidence.js.map +1 -0
- package/dist/domains/comet-native/native-verification-runtime.d.ts +58 -0
- package/dist/domains/comet-native/native-verification-runtime.d.ts.map +1 -0
- package/dist/domains/comet-native/native-verification-runtime.js +321 -0
- package/dist/domains/comet-native/native-verification-runtime.js.map +1 -0
- package/dist/domains/comet-native/native-verification-scope.d.ts +117 -0
- package/dist/domains/comet-native/native-verification-scope.d.ts.map +1 -0
- package/dist/domains/comet-native/native-verification-scope.js +1040 -0
- package/dist/domains/comet-native/native-verification-scope.js.map +1 -0
- package/dist/domains/comet-native/native-workspace.d.ts +46 -0
- package/dist/domains/comet-native/native-workspace.d.ts.map +1 -0
- package/dist/domains/comet-native/native-workspace.js +276 -0
- package/dist/domains/comet-native/native-workspace.js.map +1 -0
- package/dist/domains/dashboard/collector.d.ts.map +1 -1
- package/dist/domains/dashboard/collector.js +9 -1
- package/dist/domains/dashboard/collector.js.map +1 -1
- package/dist/domains/dashboard/native-adapter.d.ts +157 -0
- package/dist/domains/dashboard/native-adapter.d.ts.map +1 -0
- package/dist/domains/dashboard/native-adapter.js +302 -0
- package/dist/domains/dashboard/native-adapter.js.map +1 -0
- package/dist/domains/dashboard/native-collector.d.ts +6 -0
- package/dist/domains/dashboard/native-collector.d.ts.map +1 -0
- package/dist/domains/dashboard/native-collector.js +285 -0
- package/dist/domains/dashboard/native-collector.js.map +1 -0
- package/dist/domains/dashboard/types.d.ts +5 -0
- package/dist/domains/dashboard/types.d.ts.map +1 -1
- package/dist/domains/dashboard/web/assets/{arc-TGVUG2WN.js → arc-CxdiLJ50.js} +1 -1
- package/dist/domains/dashboard/web/assets/{architectureDiagram-3BPJPVTR-ASHmiHoP.js → architectureDiagram-3BPJPVTR-D5YCQu4e.js} +1 -1
- package/dist/domains/dashboard/web/assets/{blockDiagram-GPEHLZMM-CwASxBKe.js → blockDiagram-GPEHLZMM-8NfdCOQ8.js} +1 -1
- package/dist/domains/dashboard/web/assets/{c4Diagram-AAUBKEIU-BU8z_WNk.js → c4Diagram-AAUBKEIU-B3cFnNT7.js} +1 -1
- package/dist/domains/dashboard/web/assets/channel-B6AFly29.js +1 -0
- package/dist/domains/dashboard/web/assets/{chunk-2J33WTMH-CBGlUx4_.js → chunk-2J33WTMH-CBNfW-Dr.js} +1 -1
- package/dist/domains/dashboard/web/assets/{chunk-4BX2VUAB-CS-iGcFz.js → chunk-4BX2VUAB-vWyemRBr.js} +1 -1
- package/dist/domains/dashboard/web/assets/{chunk-55IACEB6-BUtCVJat.js → chunk-55IACEB6-H6haOo2C.js} +1 -1
- package/dist/domains/dashboard/web/assets/{chunk-727SXJPM-C94tj_pf.js → chunk-727SXJPM-D02UjGND.js} +1 -1
- package/dist/domains/dashboard/web/assets/{chunk-AQP2D5EJ-BFOKcpdG.js → chunk-AQP2D5EJ-wKsbOw_T.js} +1 -1
- package/dist/domains/dashboard/web/assets/{chunk-FMBD7UC4-Dmx8B-st.js → chunk-FMBD7UC4-eYFEZimP.js} +1 -1
- package/dist/domains/dashboard/web/assets/{chunk-ND2GUHAM-DYV00CNn.js → chunk-ND2GUHAM-fMaIDDI2.js} +1 -1
- package/dist/domains/dashboard/web/assets/{chunk-QZHKN3VN-DMJJyQnw.js → chunk-QZHKN3VN-C9rhF6Hm.js} +1 -1
- package/dist/domains/dashboard/web/assets/classDiagram-4FO5ZUOK-BcnrxOTt.js +1 -0
- package/dist/domains/dashboard/web/assets/classDiagram-v2-Q7XG4LA2-BcnrxOTt.js +1 -0
- package/dist/domains/dashboard/web/assets/{cose-bilkent-S5V4N54A-P4nE2rea.js → cose-bilkent-S5V4N54A-CbaShPub.js} +1 -1
- package/dist/domains/dashboard/web/assets/{cynefin-VYW2F7L2-C-R1HXDv.js → cynefin-VYW2F7L2-DN4emAQv.js} +1 -1
- package/dist/domains/dashboard/web/assets/{dagre-BM42HDAG-BH8d-C9z.js → dagre-BM42HDAG-BpSzbk_V.js} +1 -1
- package/dist/domains/dashboard/web/assets/demo-BuVau32v.js +25 -0
- package/dist/domains/dashboard/web/assets/{diagram-2AECGRRQ-DpXTgQIH.js → diagram-2AECGRRQ-CfXwGGpQ.js} +1 -1
- package/dist/domains/dashboard/web/assets/{diagram-5GNKFQAL-DSiFJzSw.js → diagram-5GNKFQAL-DMp0Zmot.js} +1 -1
- package/dist/domains/dashboard/web/assets/{diagram-KO2AKTUF-DULMkben.js → diagram-KO2AKTUF-C7ZvofsM.js} +1 -1
- package/dist/domains/dashboard/web/assets/{diagram-LMA3HP47-Pi8m_NO9.js → diagram-LMA3HP47-BjmxFnap.js} +1 -1
- package/dist/domains/dashboard/web/assets/{diagram-OG6HWLK6-BSe6bCWm.js → diagram-OG6HWLK6-DFBG1Iqy.js} +1 -1
- package/dist/domains/dashboard/web/assets/{erDiagram-TEJ5UH35-zb-FEcbq.js → erDiagram-TEJ5UH35-MpNT7dzt.js} +1 -1
- package/dist/domains/dashboard/web/assets/{flowDiagram-I6XJVG4X-Dr-Gk0z2.js → flowDiagram-I6XJVG4X-_1wAR6c1.js} +1 -1
- package/dist/domains/dashboard/web/assets/{ganttDiagram-6RSMTGT7-qs6Zb2A6.js → ganttDiagram-6RSMTGT7-DJKjDeWl.js} +1 -1
- package/dist/domains/dashboard/web/assets/{gitGraphDiagram-PVQCEYII-BQ314orT.js → gitGraphDiagram-PVQCEYII-BL7yUEn-.js} +1 -1
- package/dist/domains/dashboard/web/assets/index-CjE9mHGb.js +24 -0
- package/dist/domains/dashboard/web/assets/index-D-AGxvI4.css +1 -0
- package/dist/domains/dashboard/web/assets/{infoDiagram-5YYISTIA-O0bQp98A.js → infoDiagram-5YYISTIA-Bt5fqrOd.js} +1 -1
- package/dist/domains/dashboard/web/assets/{ishikawaDiagram-YF4QCWOH-BPC7wbKh.js → ishikawaDiagram-YF4QCWOH-COCMcjBM.js} +1 -1
- package/dist/domains/dashboard/web/assets/{journeyDiagram-JHISSGLW-ohViOd4b.js → journeyDiagram-JHISSGLW-DGcpcWny.js} +1 -1
- package/dist/domains/dashboard/web/assets/{kanban-definition-UN3LZRKU-CHleNdbG.js → kanban-definition-UN3LZRKU-QSBNsWMP.js} +1 -1
- package/dist/domains/dashboard/web/assets/{linear-CyqiW--Q.js → linear-9uyeeBZf.js} +1 -1
- package/dist/domains/dashboard/web/assets/{mermaid.core-qS9XvtaK.js → mermaid.core-CkNdB471.js} +4 -4
- package/dist/domains/dashboard/web/assets/{mindmap-definition-RKZ34NQL-oaGkqO9l.js → mindmap-definition-RKZ34NQL-CiNxI0gP.js} +1 -1
- package/dist/domains/dashboard/web/assets/{pieDiagram-4H26LBE5-CPcB-AFh.js → pieDiagram-4H26LBE5-BLJ6QnRO.js} +1 -1
- package/dist/domains/dashboard/web/assets/{quadrantDiagram-W4KKPZXB-LFy52Qtj.js → quadrantDiagram-W4KKPZXB-CflW-eFH.js} +1 -1
- package/dist/domains/dashboard/web/assets/{requirementDiagram-4Y6WPE33-BdjeaYP_.js → requirementDiagram-4Y6WPE33-B9HLmRWH.js} +1 -1
- package/dist/domains/dashboard/web/assets/{sankeyDiagram-5OEKKPKP-DDJ4styL.js → sankeyDiagram-5OEKKPKP-6xnco5k5.js} +1 -1
- package/dist/domains/dashboard/web/assets/{sequenceDiagram-3UESZ5HK-BL5bFNoz.js → sequenceDiagram-3UESZ5HK-Dp1rujNZ.js} +1 -1
- package/dist/domains/dashboard/web/assets/{stateDiagram-AJRCARHV-BNUfikui.js → stateDiagram-AJRCARHV-C1Xc90ek.js} +1 -1
- package/dist/domains/dashboard/web/assets/stateDiagram-v2-BHNVJYJU-p-qmzXyE.js +1 -0
- package/dist/domains/dashboard/web/assets/{timeline-definition-PNZ67QCA--ps2uu6L.js → timeline-definition-PNZ67QCA-g9s-m5Z4.js} +1 -1
- package/dist/domains/dashboard/web/assets/{vennDiagram-CIIHVFJN-C2q8q26c.js → vennDiagram-CIIHVFJN-rkEyhyCc.js} +1 -1
- package/dist/domains/dashboard/web/assets/{wardleyDiagram-YWT4CUSO-BUH9gXqd.js → wardleyDiagram-YWT4CUSO-CJLrWGXc.js} +1 -1
- package/dist/domains/dashboard/web/assets/{xychartDiagram-2RQKCTM6-B-DielEy.js → xychartDiagram-2RQKCTM6-BxJvPNqo.js} +1 -1
- package/dist/domains/dashboard/web/index.html +16 -16
- package/dist/domains/engine/storage-layout.d.ts +14 -0
- package/dist/domains/engine/storage-layout.d.ts.map +1 -0
- package/dist/domains/engine/storage-layout.js +32 -0
- package/dist/domains/engine/storage-layout.js.map +1 -0
- package/dist/domains/engine/storage-run.d.ts +9 -0
- package/dist/domains/engine/storage-run.d.ts.map +1 -0
- package/dist/domains/engine/storage-run.js +116 -0
- package/dist/domains/engine/storage-run.js.map +1 -0
- package/dist/domains/factory/package.d.ts.map +1 -1
- package/dist/domains/factory/package.js +5 -3
- package/dist/domains/factory/package.js.map +1 -1
- package/dist/domains/integrations/openspec.d.ts.map +1 -1
- package/dist/domains/integrations/openspec.js +8 -6
- package/dist/domains/integrations/openspec.js.map +1 -1
- package/dist/domains/skill/platform-inspect.d.ts +6 -2
- package/dist/domains/skill/platform-inspect.d.ts.map +1 -1
- package/dist/domains/skill/platform-inspect.js +52 -8
- package/dist/domains/skill/platform-inspect.js.map +1 -1
- package/dist/domains/skill/platform-install.d.ts +18 -7
- package/dist/domains/skill/platform-install.d.ts.map +1 -1
- package/dist/domains/skill/platform-install.js +338 -71
- 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 +12 -6
- package/dist/domains/skill/project-instructions.js.map +1 -1
- package/dist/domains/skill/uninstall.d.ts.map +1 -1
- package/dist/domains/skill/uninstall.js +15 -3
- package/dist/domains/skill/uninstall.js.map +1 -1
- package/dist/domains/workflow-contract/builtins.d.ts +6 -0
- package/dist/domains/workflow-contract/builtins.d.ts.map +1 -1
- package/dist/domains/workflow-contract/builtins.js +137 -0
- package/dist/domains/workflow-contract/builtins.js.map +1 -1
- package/dist/domains/workflow-contract/index.d.ts +3 -1
- package/dist/domains/workflow-contract/index.d.ts.map +1 -1
- package/dist/domains/workflow-contract/index.js +2 -1
- package/dist/domains/workflow-contract/index.js.map +1 -1
- package/dist/domains/workflow-contract/normalize.d.ts.map +1 -1
- package/dist/domains/workflow-contract/normalize.js +28 -10
- package/dist/domains/workflow-contract/normalize.js.map +1 -1
- package/dist/domains/workflow-contract/project-config.d.ts +7 -0
- package/dist/domains/workflow-contract/project-config.d.ts.map +1 -0
- package/dist/domains/workflow-contract/project-config.js +110 -0
- package/dist/domains/workflow-contract/project-config.js.map +1 -0
- package/dist/domains/workflow-contract/types.d.ts +4 -2
- package/dist/domains/workflow-contract/types.d.ts.map +1 -1
- package/dist/domains/workflow-contract/validation.d.ts.map +1 -1
- package/dist/domains/workflow-contract/validation.js +14 -5
- package/dist/domains/workflow-contract/validation.js.map +1 -1
- package/dist/platform/install/detect.d.ts +3 -1
- package/dist/platform/install/detect.d.ts.map +1 -1
- package/dist/platform/install/detect.js +4 -1
- package/dist/platform/install/detect.js.map +1 -1
- package/dist/platform/install/project-registry.js +2 -2
- package/dist/platform/install/project-registry.js.map +1 -1
- package/dist/platform/paths/repository-layout.d.ts +16 -0
- package/dist/platform/paths/repository-layout.d.ts.map +1 -1
- package/dist/platform/paths/repository-layout.js.map +1 -1
- package/eval/local/README.md +50 -0
- package/eval/local/scripts/compare_baselines.py +127 -1
- package/eval/local/scripts/regression_check.py +3 -4
- package/eval/local/tasks/comet-native-clarification/environment/Dockerfile +27 -0
- package/eval/local/tasks/comet-native-clarification/environment/test_wordcount.py +25 -0
- package/eval/local/tasks/comet-native-clarification/environment/wordcount.py +36 -0
- package/eval/local/tasks/comet-native-clarification/instruction.md +7 -0
- package/eval/local/tasks/comet-native-clarification/task.toml +40 -0
- package/eval/local/tasks/comet-native-clarification/validation/test_native_clarification.py +193 -0
- package/eval/local/tasks/comet-native-clarification-modes/environment/Dockerfile +27 -0
- package/eval/local/tasks/comet-native-clarification-modes/environment/test_wordcount.py +25 -0
- package/eval/local/tasks/comet-native-clarification-modes/environment/wordcount.py +36 -0
- package/eval/local/tasks/comet-native-clarification-modes/instruction.md +13 -0
- package/eval/local/tasks/comet-native-clarification-modes/task.toml +39 -0
- package/eval/local/tasks/comet-native-clarification-modes/validation/test_native_clarification_modes.py +235 -0
- package/eval/local/tasks/comet-native-interrupted-transition/environment/.comet/config.yaml +6 -0
- package/eval/local/tasks/comet-native-interrupted-transition/environment/Dockerfile +28 -0
- package/eval/local/tasks/comet-native-interrupted-transition/environment/docs/comet/changes/add-character-counting/brief.md +17 -0
- package/eval/local/tasks/comet-native-interrupted-transition/environment/docs/comet/changes/add-character-counting/comet-state.yaml +12 -0
- package/eval/local/tasks/comet-native-interrupted-transition/environment/docs/comet/changes/add-character-counting/runtime/transition.json +69 -0
- package/eval/local/tasks/comet-native-interrupted-transition/environment/docs/comet/changes/add-character-counting/specs/character-counting/spec.md +3 -0
- package/eval/local/tasks/comet-native-interrupted-transition/environment/test_wordcount.py +20 -0
- package/eval/local/tasks/comet-native-interrupted-transition/environment/wordcount.py +27 -0
- package/eval/local/tasks/comet-native-interrupted-transition/instruction.md +7 -0
- package/eval/local/tasks/comet-native-interrupted-transition/task.toml +35 -0
- package/eval/local/tasks/comet-native-interrupted-transition/validation/test_native_interrupted_transition.py +63 -0
- package/eval/local/tasks/comet-native-repository-fact/environment/Dockerfile +26 -0
- package/eval/local/tasks/comet-native-repository-fact/environment/PRODUCT_RULES.md +3 -0
- package/eval/local/tasks/comet-native-repository-fact/environment/test_wordcount.py +20 -0
- package/eval/local/tasks/comet-native-repository-fact/environment/wordcount.py +27 -0
- package/eval/local/tasks/comet-native-repository-fact/instruction.md +7 -0
- package/eval/local/tasks/comet-native-repository-fact/task.toml +35 -0
- package/eval/local/tasks/comet-native-repository-fact/validation/test_native_repository_fact.py +53 -0
- package/eval/local/tasks/comet-native-wave-b-decision-resume/environment/Dockerfile +27 -0
- package/eval/local/tasks/comet-native-wave-b-decision-resume/environment/test_wordcount.py +27 -0
- package/eval/local/tasks/comet-native-wave-b-decision-resume/environment/wordcount.py +36 -0
- package/eval/local/tasks/comet-native-wave-b-decision-resume/instruction.md +9 -0
- package/eval/local/tasks/comet-native-wave-b-decision-resume/task.toml +40 -0
- package/eval/local/tasks/comet-native-wave-b-decision-resume/validation/test_native_wave_b_decision_resume.py +208 -0
- package/eval/local/tasks/comet-native-wave-c-verification-integrity/environment/Dockerfile +27 -0
- package/eval/local/tasks/comet-native-wave-c-verification-integrity/environment/test_wordcount.py +27 -0
- package/eval/local/tasks/comet-native-wave-c-verification-integrity/environment/wordcount.py +36 -0
- package/eval/local/tasks/comet-native-wave-c-verification-integrity/instruction.md +11 -0
- package/eval/local/tasks/comet-native-wave-c-verification-integrity/task.toml +36 -0
- package/eval/local/tasks/comet-native-wave-c-verification-integrity/validation/test_native_wave_c_verification_integrity.py +344 -0
- package/eval/local/tasks/comet-native-wave-d-stagnation-stop/environment/Dockerfile +27 -0
- package/eval/local/tasks/comet-native-wave-d-stagnation-stop/environment/test_wordcount.py +27 -0
- package/eval/local/tasks/comet-native-wave-d-stagnation-stop/environment/wordcount.py +36 -0
- package/eval/local/tasks/comet-native-wave-d-stagnation-stop/instruction.md +11 -0
- package/eval/local/tasks/comet-native-wave-d-stagnation-stop/task.toml +37 -0
- package/eval/local/tasks/comet-native-wave-d-stagnation-stop/validation/test_native_wave_d_stagnation_stop.py +284 -0
- package/eval/local/tasks/comet-native-wave-e-parallel-safety/environment/.include-trusted-native-runtime +1 -0
- package/eval/local/tasks/comet-native-wave-e-parallel-safety/environment/Dockerfile +27 -0
- package/eval/local/tasks/comet-native-wave-e-parallel-safety/environment/test_wordcount.py +27 -0
- package/eval/local/tasks/comet-native-wave-e-parallel-safety/environment/wordcount.py +36 -0
- package/eval/local/tasks/comet-native-wave-e-parallel-safety/instruction.md +9 -0
- package/eval/local/tasks/comet-native-wave-e-parallel-safety/task.toml +37 -0
- package/eval/local/tasks/comet-native-wave-e-parallel-safety/validation/test_native_wave_e_parallel_safety.py +309 -0
- package/eval/local/tasks/comet-native-wave-f-dashboard-readonly/environment/.include-current-comet-cli +1 -0
- package/eval/local/tasks/comet-native-wave-f-dashboard-readonly/environment/Dockerfile +31 -0
- package/eval/local/tasks/comet-native-wave-f-dashboard-readonly/environment/current-comet-package.json +18 -0
- package/eval/local/tasks/comet-native-wave-f-dashboard-readonly/environment/current-comet.sh +14 -0
- package/eval/local/tasks/comet-native-wave-f-dashboard-readonly/environment/test_wordcount.py +27 -0
- package/eval/local/tasks/comet-native-wave-f-dashboard-readonly/environment/wordcount.py +36 -0
- package/eval/local/tasks/comet-native-wave-f-dashboard-readonly/instruction.md +7 -0
- package/eval/local/tasks/comet-native-wave-f-dashboard-readonly/task.toml +36 -0
- package/eval/local/tasks/comet-native-wave-f-dashboard-readonly/validation/test_native_wave_f_dashboard_readonly.py +469 -0
- package/eval/local/tasks/comet-native-workflow/environment/Dockerfile +27 -0
- package/eval/local/tasks/comet-native-workflow/environment/test_wordcount.py +25 -0
- package/eval/local/tasks/comet-native-workflow/environment/wordcount.py +36 -0
- package/eval/local/tasks/comet-native-workflow/instruction.md +14 -0
- package/eval/local/tasks/comet-native-workflow/task.toml +36 -0
- package/eval/local/tasks/comet-native-workflow/validation/test_native_workflow.py +149 -0
- package/eval/local/tasks/index.yaml +61 -0
- package/eval/local/tests/conftest.py +407 -22
- package/eval/local/tests/scaffold/test_aligned_comparison.py +1012 -0
- package/eval/local/tests/scaffold/test_conftest_helpers.py +187 -7
- package/eval/local/tests/scaffold/test_logging.py +135 -1
- package/eval/local/tests/scaffold/test_manifests.py +2 -0
- package/eval/local/tests/scaffold/test_native_aligned_eval.py +346 -0
- package/eval/local/tests/scaffold/test_native_wave_evaluations.py +1889 -0
- package/eval/local/tests/scaffold/test_profiles.py +33 -0
- package/eval/local/tests/scaffold/test_sample_quality.py +57 -0
- package/eval/local/tests/scaffold/test_tasks.py +377 -1
- package/eval/local/tests/scaffold/test_treatments.py +59 -13
- package/eval/local/tests/scaffold/test_utils.py +277 -4
- package/eval/local/tests/tasks/__init__.py +1 -0
- package/eval/local/tests/tasks/test_tasks.py +104 -78
- package/eval/local/tests/tasks/test_validation_scripts.py +37 -0
- package/eval/local/treatments/comet/comet_native_phase1.yaml +66 -0
- package/eval/pyproject.toml +4 -4
- package/eval/scaffold/python/aligned_comparison.py +1631 -0
- package/eval/scaffold/python/generic_llm_judge.py +3 -1
- package/eval/scaffold/python/llm_judge.py +0 -3
- package/eval/scaffold/python/logging.py +115 -16
- package/eval/scaffold/python/manifests.py +2 -0
- package/eval/scaffold/python/native_eval.py +138 -0
- package/eval/scaffold/python/pass_at_k.py +0 -1
- package/eval/scaffold/python/report_outputs.py +226 -44
- package/eval/scaffold/python/sample_quality.py +45 -2
- package/eval/scaffold/python/tasks.py +11 -0
- package/eval/scaffold/python/utils.py +108 -20
- package/eval/scaffold/python/validation/__init__.py +15 -5
- package/eval/scaffold/python/validation/comet_workflow.py +10 -5
- package/eval/scaffold/python/validation/generic_rubric.py +1 -2
- package/eval/scaffold/python/validation/native_wave.py +2154 -0
- package/eval/scaffold/python/validation/native_workflow.py +252 -0
- package/eval/scaffold/python/validation/rubric.py +0 -2
- package/eval/scaffold/shell/completion-point.sh +18 -0
- package/eval/scaffold/shell/decision-point.sh +35 -0
- package/eval/scaffold/shell/docker.sh +150 -9
- package/eval/scaffold/shell/run-claude-loop.sh +57 -27
- package/package.json +26 -5
- package/dist/domains/dashboard/web/assets/channel-kp6Ng2JJ.js +0 -1
- package/dist/domains/dashboard/web/assets/classDiagram-4FO5ZUOK-yBDjgMY_.js +0 -1
- package/dist/domains/dashboard/web/assets/classDiagram-v2-Q7XG4LA2-yBDjgMY_.js +0 -1
- package/dist/domains/dashboard/web/assets/demo-DQyfozbi.js +0 -1
- package/dist/domains/dashboard/web/assets/index-BLAfkNTu.css +0 -1
- package/dist/domains/dashboard/web/assets/index-DtLUOcAW.js +0 -24
- package/dist/domains/dashboard/web/assets/stateDiagram-v2-BHNVJYJU-DGrsmWts.js +0 -1
- package/eval/.env.example +0 -52
- package/eval/langsmith/.env.example +0 -16
|
@@ -0,0 +1,2312 @@
|
|
|
1
|
+
import { createHash } from 'crypto';
|
|
2
|
+
import { spawn } from 'node:child_process';
|
|
3
|
+
import { promises as fs } from 'fs';
|
|
4
|
+
import path from 'path';
|
|
5
|
+
import { atomicWriteJson } from './native-atomic-file.js';
|
|
6
|
+
import { sha256Text } from './native-hash.js';
|
|
7
|
+
import { hasComparableNativeFileObject, sameNativeFileObject } from './native-file-identity.js';
|
|
8
|
+
import { isInsidePath, resolveContainedNativePath } from './native-paths.js';
|
|
9
|
+
import { readNativeProtectedTextFile } from './native-protected-file.js';
|
|
10
|
+
import { nativeSensitiveRelativePathReason } from './native-sensitive-paths.js';
|
|
11
|
+
export const DEFAULT_NATIVE_SNAPSHOT_LIMITS = {
|
|
12
|
+
maxFiles: 10_000,
|
|
13
|
+
maxFileBytes: 5 * 1024 * 1024,
|
|
14
|
+
maxTotalBytes: 64 * 1024 * 1024,
|
|
15
|
+
maxManifestBytes: 1024 * 1024,
|
|
16
|
+
};
|
|
17
|
+
const MAX_RECORDED_OMISSIONS = 1_000;
|
|
18
|
+
const NATIVE_SNAPSHOT_MANIFEST_HARD_MAX_BYTES = 8 * 1024 * 1024;
|
|
19
|
+
const CHANGE_NAME_PATTERN = /^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/u;
|
|
20
|
+
const MANIFEST_KEYS = new Set([
|
|
21
|
+
'schema',
|
|
22
|
+
'origin',
|
|
23
|
+
'capture',
|
|
24
|
+
'createdAt',
|
|
25
|
+
'complete',
|
|
26
|
+
'limits',
|
|
27
|
+
'entries',
|
|
28
|
+
'omitted',
|
|
29
|
+
'omittedCount',
|
|
30
|
+
'omissionOverflow',
|
|
31
|
+
]);
|
|
32
|
+
const LIMIT_KEYS = new Set(['maxFiles', 'maxFileBytes', 'maxTotalBytes', 'maxManifestBytes']);
|
|
33
|
+
const CAPTURE_KEYS = new Set(['provider', 'gitSelection', 'physicalSelection', 'projection']);
|
|
34
|
+
const GIT_PROJECTION_KEYS = new Set(['provider', 'selection']);
|
|
35
|
+
const GIT_SELECTION_KEYS = new Set([
|
|
36
|
+
'schema',
|
|
37
|
+
'status',
|
|
38
|
+
'stageBefore',
|
|
39
|
+
'combined',
|
|
40
|
+
'stageAfter',
|
|
41
|
+
'finalStageBefore',
|
|
42
|
+
'finalCombined',
|
|
43
|
+
'finalStageAfter',
|
|
44
|
+
]);
|
|
45
|
+
const GIT_SELECTION_STREAM_KEYS = new Set([
|
|
46
|
+
'hash',
|
|
47
|
+
'recordCount',
|
|
48
|
+
'storedRecordCount',
|
|
49
|
+
'stdoutBytes',
|
|
50
|
+
'overflow',
|
|
51
|
+
]);
|
|
52
|
+
const PHYSICAL_SELECTION_KEYS = new Set(['schema', 'status', 'before', 'after']);
|
|
53
|
+
const PHYSICAL_SELECTION_STREAM_KEYS = new Set([
|
|
54
|
+
'hash',
|
|
55
|
+
'visitedNodeCount',
|
|
56
|
+
'recordCount',
|
|
57
|
+
'storedRecordCount',
|
|
58
|
+
'encodedBytes',
|
|
59
|
+
'overflow',
|
|
60
|
+
'unstable',
|
|
61
|
+
]);
|
|
62
|
+
const ENTRY_KEYS = new Set(['path', 'hash', 'size', 'type']);
|
|
63
|
+
const OMISSION_KEYS = new Set(['path', 'size', 'type', 'reason']);
|
|
64
|
+
const OMISSION_OVERFLOW_KEYS = new Set(['ref', 'hash', 'count']);
|
|
65
|
+
const SNAPSHOT_ORIGINS = new Set([
|
|
66
|
+
'change-created',
|
|
67
|
+
'legacy-migration',
|
|
68
|
+
'explicit',
|
|
69
|
+
]);
|
|
70
|
+
const OMISSION_TYPES = new Set(['file', 'directory', 'other']);
|
|
71
|
+
const OMISSION_REASONS = new Set([
|
|
72
|
+
'file-size',
|
|
73
|
+
'file-count',
|
|
74
|
+
'total-size',
|
|
75
|
+
'manifest-size',
|
|
76
|
+
'changed-during-read',
|
|
77
|
+
'unreadable',
|
|
78
|
+
'gitlink-unavailable',
|
|
79
|
+
'gitlink-dirty',
|
|
80
|
+
'gitlink-changed',
|
|
81
|
+
'legacy-gitlink-boundary',
|
|
82
|
+
'git-enumeration-limit',
|
|
83
|
+
'git-selection-changed',
|
|
84
|
+
'physical-enumeration-limit',
|
|
85
|
+
'physical-selection-changed',
|
|
86
|
+
]);
|
|
87
|
+
const HASH_PATTERN = /^[a-f0-9]{64}$/u;
|
|
88
|
+
const GIT_OBJECT_ID_PATTERN = /^(?:[a-f0-9]{40}|[a-f0-9]{64})$/u;
|
|
89
|
+
const UNREADABLE_ERROR_CODES = new Set(['EACCES', 'EPERM']);
|
|
90
|
+
const GIT_LIST_STDERR_LIMIT = 64 * 1024;
|
|
91
|
+
const GIT_TEXT_STDOUT_LIMIT = 64 * 1024;
|
|
92
|
+
const DEFAULT_NATIVE_SNAPSHOT_EXECUTION_BUDGET_MS = 60_000;
|
|
93
|
+
const WINDOWS_TASKKILL_ATTEMPT_MS = 1_000;
|
|
94
|
+
const WINDOWS_TASKKILL_ATTEMPTS = 2;
|
|
95
|
+
const DEFAULT_NATIVE_GIT_SELECTION_LIMITS = {
|
|
96
|
+
maxRecords: 20_000,
|
|
97
|
+
maxBytes: 8 * 1024 * 1024,
|
|
98
|
+
maxRecordBytes: 64 * 1024,
|
|
99
|
+
};
|
|
100
|
+
const DEFAULT_NATIVE_PHYSICAL_SELECTION_LIMITS = {
|
|
101
|
+
maxNodes: 20_000,
|
|
102
|
+
maxBytes: 8 * 1024 * 1024,
|
|
103
|
+
maxPathBytes: 64 * 1024,
|
|
104
|
+
};
|
|
105
|
+
function createNativeSnapshotExecution(options) {
|
|
106
|
+
const deadlineMs = options.deadlineMs ?? DEFAULT_NATIVE_SNAPSHOT_EXECUTION_BUDGET_MS;
|
|
107
|
+
if (!Number.isSafeInteger(deadlineMs) || deadlineMs < 1) {
|
|
108
|
+
throw new Error('Native snapshot execution budget must be a positive integer');
|
|
109
|
+
}
|
|
110
|
+
return {
|
|
111
|
+
deadlineAt: Date.now() + deadlineMs,
|
|
112
|
+
gitProcess: options.gitProcess ?? { command: 'git' },
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
function remainingNativeSnapshotTime(execution) {
|
|
116
|
+
return Math.max(0, execution.deadlineAt - Date.now());
|
|
117
|
+
}
|
|
118
|
+
function nativeSnapshotExecutionHasBudget(execution) {
|
|
119
|
+
return remainingNativeSnapshotTime(execution) >= 1;
|
|
120
|
+
}
|
|
121
|
+
function nativeGitSnapshotTimeoutError(cause) {
|
|
122
|
+
const error = cause === undefined
|
|
123
|
+
? new Error('Native snapshot deadline exceeded while waiting for Git')
|
|
124
|
+
: new Error('Native snapshot deadline exceeded while waiting for Git', { cause });
|
|
125
|
+
return Object.assign(error, { code: 'GIT_SNAPSHOT_TIMEOUT' });
|
|
126
|
+
}
|
|
127
|
+
function isNativeGitSnapshotTimeout(error) {
|
|
128
|
+
return error.code === 'GIT_SNAPSHOT_TIMEOUT';
|
|
129
|
+
}
|
|
130
|
+
function resolveNativePhysicalSelectionLimits(values) {
|
|
131
|
+
const limits = { ...DEFAULT_NATIVE_PHYSICAL_SELECTION_LIMITS, ...values };
|
|
132
|
+
if (!Number.isSafeInteger(limits.maxNodes) ||
|
|
133
|
+
limits.maxNodes < 1 ||
|
|
134
|
+
!Number.isSafeInteger(limits.maxBytes) ||
|
|
135
|
+
limits.maxBytes < 1 ||
|
|
136
|
+
!Number.isSafeInteger(limits.maxPathBytes) ||
|
|
137
|
+
limits.maxPathBytes < 1 ||
|
|
138
|
+
limits.maxPathBytes > limits.maxBytes) {
|
|
139
|
+
throw new Error('Native physical selection limits must be positive bounded integers');
|
|
140
|
+
}
|
|
141
|
+
return limits;
|
|
142
|
+
}
|
|
143
|
+
async function terminateNativeProcessTree(child, adapter) {
|
|
144
|
+
if (adapter.terminateTree) {
|
|
145
|
+
await adapter.terminateTree(child);
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
const pid = child.pid;
|
|
149
|
+
if (pid === undefined) {
|
|
150
|
+
child.kill('SIGKILL');
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
if (process.platform === 'win32') {
|
|
154
|
+
const configuredSystemRoot = process.env.SystemRoot ?? process.env.WINDIR;
|
|
155
|
+
const systemRoot = configuredSystemRoot && path.win32.isAbsolute(configuredSystemRoot)
|
|
156
|
+
? path.win32.resolve(configuredSystemRoot)
|
|
157
|
+
: 'C:\\Windows';
|
|
158
|
+
const taskkill = path.win32.join(systemRoot, 'System32', 'taskkill.exe');
|
|
159
|
+
if (!path.win32.isAbsolute(taskkill)) {
|
|
160
|
+
child.kill('SIGKILL');
|
|
161
|
+
child.stdin?.destroy();
|
|
162
|
+
child.stdout?.destroy();
|
|
163
|
+
child.stderr?.destroy();
|
|
164
|
+
throw Object.assign(new Error('Native Git taskkill path is not trusted'), {
|
|
165
|
+
code: 'GIT_SNAPSHOT_TERMINATION_UNCONFIRMED',
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
const runTaskkillAttempt = () => new Promise((resolve) => {
|
|
169
|
+
let finished = false;
|
|
170
|
+
let killer = null;
|
|
171
|
+
let fallbackTimer = null;
|
|
172
|
+
const finish = (confirmed, terminateKiller = false) => {
|
|
173
|
+
if (finished)
|
|
174
|
+
return;
|
|
175
|
+
finished = true;
|
|
176
|
+
if (fallbackTimer)
|
|
177
|
+
clearTimeout(fallbackTimer);
|
|
178
|
+
if (terminateKiller)
|
|
179
|
+
killer?.kill('SIGKILL');
|
|
180
|
+
resolve(confirmed);
|
|
181
|
+
};
|
|
182
|
+
try {
|
|
183
|
+
killer = spawn(taskkill, ['/pid', String(pid), '/t', '/f'], {
|
|
184
|
+
stdio: 'ignore',
|
|
185
|
+
windowsHide: true,
|
|
186
|
+
});
|
|
187
|
+
killer.once('error', () => finish(false));
|
|
188
|
+
killer.once('close', (code) => finish(code === 0));
|
|
189
|
+
fallbackTimer = setTimeout(() => finish(false, true), WINDOWS_TASKKILL_ATTEMPT_MS);
|
|
190
|
+
fallbackTimer.unref();
|
|
191
|
+
}
|
|
192
|
+
catch {
|
|
193
|
+
finish(false);
|
|
194
|
+
}
|
|
195
|
+
});
|
|
196
|
+
for (let attempt = 0; attempt < WINDOWS_TASKKILL_ATTEMPTS; attempt += 1) {
|
|
197
|
+
if (await runTaskkillAttempt()) {
|
|
198
|
+
child.kill('SIGKILL');
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
child.kill('SIGKILL');
|
|
203
|
+
child.stdin?.destroy();
|
|
204
|
+
child.stdout?.destroy();
|
|
205
|
+
child.stderr?.destroy();
|
|
206
|
+
throw Object.assign(new Error('Native Git process-tree termination could not be confirmed on Windows'), { code: 'GIT_SNAPSHOT_TERMINATION_UNCONFIRMED' });
|
|
207
|
+
}
|
|
208
|
+
try {
|
|
209
|
+
process.kill(-pid, 'SIGKILL');
|
|
210
|
+
}
|
|
211
|
+
catch (error) {
|
|
212
|
+
if (error.code !== 'ESRCH')
|
|
213
|
+
throw error;
|
|
214
|
+
}
|
|
215
|
+
child.kill('SIGKILL');
|
|
216
|
+
}
|
|
217
|
+
function startNativeGitProcess(execution, projectRoot, args, input) {
|
|
218
|
+
const remaining = remainingNativeSnapshotTime(execution);
|
|
219
|
+
if (remaining < 1)
|
|
220
|
+
throw nativeGitSnapshotTimeoutError();
|
|
221
|
+
const adapter = execution.gitProcess;
|
|
222
|
+
const child = spawn(adapter.command, [...(adapter.argsPrefix ?? []), '-C', projectRoot, ...args], {
|
|
223
|
+
stdio: [input ? 'pipe' : 'ignore', 'pipe', 'pipe'],
|
|
224
|
+
windowsHide: true,
|
|
225
|
+
detached: process.platform !== 'win32',
|
|
226
|
+
});
|
|
227
|
+
let spawnError = null;
|
|
228
|
+
let timedOut = false;
|
|
229
|
+
let termination = null;
|
|
230
|
+
child.once('error', (error) => {
|
|
231
|
+
spawnError = error;
|
|
232
|
+
});
|
|
233
|
+
const close = new Promise((resolve) => {
|
|
234
|
+
child.once('close', resolve);
|
|
235
|
+
});
|
|
236
|
+
const timer = setTimeout(() => {
|
|
237
|
+
timedOut = true;
|
|
238
|
+
termination = terminateNativeProcessTree(child, adapter).then(() => ({ error: null }), (error) => ({ error }));
|
|
239
|
+
}, remaining);
|
|
240
|
+
timer.unref();
|
|
241
|
+
const completion = close.then(async (code) => {
|
|
242
|
+
clearTimeout(timer);
|
|
243
|
+
if (timedOut) {
|
|
244
|
+
const result = await termination;
|
|
245
|
+
if (result?.error)
|
|
246
|
+
throw nativeGitSnapshotTimeoutError(result.error);
|
|
247
|
+
throw nativeGitSnapshotTimeoutError();
|
|
248
|
+
}
|
|
249
|
+
return { code, spawnError };
|
|
250
|
+
});
|
|
251
|
+
return { child, completion };
|
|
252
|
+
}
|
|
253
|
+
function resolveNativeGitSelectionLimits(values) {
|
|
254
|
+
const limits = { ...DEFAULT_NATIVE_GIT_SELECTION_LIMITS, ...values };
|
|
255
|
+
if (!Number.isSafeInteger(limits.maxRecords) ||
|
|
256
|
+
limits.maxRecords < 1 ||
|
|
257
|
+
!Number.isSafeInteger(limits.maxBytes) ||
|
|
258
|
+
limits.maxBytes < 1 ||
|
|
259
|
+
!Number.isSafeInteger(limits.maxRecordBytes) ||
|
|
260
|
+
limits.maxRecordBytes < 1 ||
|
|
261
|
+
limits.maxRecordBytes > limits.maxBytes) {
|
|
262
|
+
throw new Error('Native Git selection limits must be positive bounded integers');
|
|
263
|
+
}
|
|
264
|
+
return limits;
|
|
265
|
+
}
|
|
266
|
+
async function runGitBoundedOutput(execution, projectRoot, args, maxBytes = GIT_TEXT_STDOUT_LIMIT) {
|
|
267
|
+
const { child, completion } = startNativeGitProcess(execution, projectRoot, args, false);
|
|
268
|
+
const stdout = [];
|
|
269
|
+
const stderr = [];
|
|
270
|
+
let stderrBytes = 0;
|
|
271
|
+
let stdoutBytes = 0;
|
|
272
|
+
let stdoutOverflow = false;
|
|
273
|
+
child.stdout.on('data', (chunk) => {
|
|
274
|
+
const remaining = Math.max(0, maxBytes - stdoutBytes);
|
|
275
|
+
if (remaining > 0)
|
|
276
|
+
stdout.push(Buffer.from(chunk).subarray(0, remaining));
|
|
277
|
+
stdoutBytes += chunk.byteLength;
|
|
278
|
+
if (stdoutBytes > maxBytes)
|
|
279
|
+
stdoutOverflow = true;
|
|
280
|
+
});
|
|
281
|
+
child.stderr.on('data', (chunk) => {
|
|
282
|
+
if (stderrBytes >= GIT_LIST_STDERR_LIMIT)
|
|
283
|
+
return;
|
|
284
|
+
const remaining = GIT_LIST_STDERR_LIMIT - stderrBytes;
|
|
285
|
+
const bounded = Buffer.from(chunk).subarray(0, remaining);
|
|
286
|
+
stderr.push(bounded);
|
|
287
|
+
stderrBytes += bounded.byteLength;
|
|
288
|
+
});
|
|
289
|
+
const { code, spawnError } = await completion;
|
|
290
|
+
if (code === 0 && spawnError === null && !stdoutOverflow)
|
|
291
|
+
return Buffer.concat(stdout);
|
|
292
|
+
throw Object.assign(new Error(`git ${args.join(' ')} failed${spawnError ? `: ${spawnError.message}` : stderr.length > 0 ? `: ${Buffer.concat(stderr).toString('utf8').trim()}` : ''}`), { code: 'GIT_SNAPSHOT_UNAVAILABLE' });
|
|
293
|
+
}
|
|
294
|
+
function gitSelectionStreamEvidence(result) {
|
|
295
|
+
return {
|
|
296
|
+
hash: result.digest,
|
|
297
|
+
recordCount: result.recordCount,
|
|
298
|
+
storedRecordCount: result.records.length,
|
|
299
|
+
stdoutBytes: result.stdoutBytes,
|
|
300
|
+
overflow: result.overflow,
|
|
301
|
+
};
|
|
302
|
+
}
|
|
303
|
+
function runGitNullRecords(execution, projectRoot, args, options) {
|
|
304
|
+
if (options.outputChunkBytes !== undefined &&
|
|
305
|
+
(!Number.isSafeInteger(options.outputChunkBytes) || options.outputChunkBytes < 1)) {
|
|
306
|
+
throw new Error('Native Git output chunk size must be a positive integer');
|
|
307
|
+
}
|
|
308
|
+
return new Promise((resolve, reject) => {
|
|
309
|
+
const { child, completion } = startNativeGitProcess(execution, projectRoot, args, options.stdin !== undefined);
|
|
310
|
+
const records = [];
|
|
311
|
+
const digest = createHash('sha256');
|
|
312
|
+
const stderr = [];
|
|
313
|
+
let stderrBytes = 0;
|
|
314
|
+
let stdoutBytes = 0;
|
|
315
|
+
let storedBytes = 0;
|
|
316
|
+
let recordCount = 0;
|
|
317
|
+
let pending = Buffer.alloc(0);
|
|
318
|
+
let droppingRecord = false;
|
|
319
|
+
let overflow = false;
|
|
320
|
+
let malformed = false;
|
|
321
|
+
const consumeChunk = (chunk) => {
|
|
322
|
+
let offset = 0;
|
|
323
|
+
while (offset < chunk.byteLength) {
|
|
324
|
+
const separator = chunk.indexOf(0, offset);
|
|
325
|
+
const end = separator < 0 ? chunk.byteLength : separator;
|
|
326
|
+
const part = chunk.subarray(offset, end);
|
|
327
|
+
if (!droppingRecord && part.byteLength > 0) {
|
|
328
|
+
if (pending.byteLength + part.byteLength > options.maxRecordBytes) {
|
|
329
|
+
pending = Buffer.alloc(0);
|
|
330
|
+
droppingRecord = true;
|
|
331
|
+
overflow = true;
|
|
332
|
+
}
|
|
333
|
+
else {
|
|
334
|
+
pending = Buffer.concat([pending, part]);
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
if (separator < 0)
|
|
338
|
+
break;
|
|
339
|
+
recordCount += 1;
|
|
340
|
+
if (pending.byteLength === 0 && !droppingRecord)
|
|
341
|
+
malformed = true;
|
|
342
|
+
const recordBytes = pending.byteLength + 1;
|
|
343
|
+
if (!droppingRecord &&
|
|
344
|
+
recordCount <= options.maxRecords &&
|
|
345
|
+
storedBytes + recordBytes <= options.maxBytes) {
|
|
346
|
+
records.push(pending);
|
|
347
|
+
storedBytes += recordBytes;
|
|
348
|
+
}
|
|
349
|
+
else {
|
|
350
|
+
overflow = true;
|
|
351
|
+
}
|
|
352
|
+
pending = Buffer.alloc(0);
|
|
353
|
+
droppingRecord = false;
|
|
354
|
+
offset = separator + 1;
|
|
355
|
+
}
|
|
356
|
+
};
|
|
357
|
+
child.stdout.on('data', (chunk) => {
|
|
358
|
+
digest.update(chunk);
|
|
359
|
+
stdoutBytes += chunk.byteLength;
|
|
360
|
+
if (stdoutBytes > options.maxBytes)
|
|
361
|
+
overflow = true;
|
|
362
|
+
const outputChunkBytes = options.outputChunkBytes;
|
|
363
|
+
if (outputChunkBytes === undefined) {
|
|
364
|
+
consumeChunk(chunk);
|
|
365
|
+
return;
|
|
366
|
+
}
|
|
367
|
+
for (let offset = 0; offset < chunk.byteLength; offset += outputChunkBytes) {
|
|
368
|
+
consumeChunk(chunk.subarray(offset, Math.min(chunk.byteLength, offset + outputChunkBytes)));
|
|
369
|
+
}
|
|
370
|
+
});
|
|
371
|
+
child.stderr.on('data', (chunk) => {
|
|
372
|
+
if (stderrBytes >= GIT_LIST_STDERR_LIMIT)
|
|
373
|
+
return;
|
|
374
|
+
const remaining = GIT_LIST_STDERR_LIMIT - stderrBytes;
|
|
375
|
+
const bounded = Buffer.from(chunk).subarray(0, remaining);
|
|
376
|
+
stderr.push(bounded);
|
|
377
|
+
stderrBytes += bounded.byteLength;
|
|
378
|
+
});
|
|
379
|
+
child.stdin?.on('error', () => {
|
|
380
|
+
// `close` reports the authoritative command result; ignore an early pipe close here.
|
|
381
|
+
});
|
|
382
|
+
void completion.then(({ code, spawnError }) => {
|
|
383
|
+
const acceptedExitCodes = options.acceptedExitCodes ?? [0];
|
|
384
|
+
if (code !== null &&
|
|
385
|
+
acceptedExitCodes.includes(code) &&
|
|
386
|
+
spawnError === null &&
|
|
387
|
+
!malformed &&
|
|
388
|
+
pending.byteLength === 0 &&
|
|
389
|
+
!droppingRecord) {
|
|
390
|
+
resolve({
|
|
391
|
+
records,
|
|
392
|
+
digest: digest.digest('hex'),
|
|
393
|
+
overflow,
|
|
394
|
+
recordCount,
|
|
395
|
+
stdoutBytes,
|
|
396
|
+
});
|
|
397
|
+
return;
|
|
398
|
+
}
|
|
399
|
+
reject(new Error(`git ${args.join(' ')} failed${spawnError ? `: ${spawnError.message}` : malformed || pending.byteLength > 0 || droppingRecord ? ': malformed NUL-delimited output' : stderr.length > 0 ? `: ${Buffer.concat(stderr).toString('utf8').trim()}` : ''}`));
|
|
400
|
+
}, reject);
|
|
401
|
+
if (options.stdin)
|
|
402
|
+
child.stdin?.end(options.stdin);
|
|
403
|
+
});
|
|
404
|
+
}
|
|
405
|
+
function decodeGitRecord(value) {
|
|
406
|
+
const decoded = value.toString('utf8');
|
|
407
|
+
if (!Buffer.from(decoded, 'utf8').equals(value)) {
|
|
408
|
+
throw new Error('Native Git snapshot provider returned non-UTF-8 path data');
|
|
409
|
+
}
|
|
410
|
+
return decoded;
|
|
411
|
+
}
|
|
412
|
+
async function runGitCheckIgnore(execution, projectRoot, paths) {
|
|
413
|
+
if (paths.length === 0)
|
|
414
|
+
return new Set();
|
|
415
|
+
const result = await runGitNullRecords(execution, projectRoot, ['check-ignore', '--no-index', '-z', '--stdin'], {
|
|
416
|
+
...DEFAULT_NATIVE_GIT_SELECTION_LIMITS,
|
|
417
|
+
maxRecords: Math.max(1, paths.length),
|
|
418
|
+
acceptedExitCodes: [0, 1],
|
|
419
|
+
stdin: Buffer.from(`${paths.join('\0')}\0`, 'utf8'),
|
|
420
|
+
});
|
|
421
|
+
if (result.overflow) {
|
|
422
|
+
throw new Error('Native Git check-ignore output exceeded its safety budget');
|
|
423
|
+
}
|
|
424
|
+
return new Set(result.records.map((record) => {
|
|
425
|
+
const value = decodeGitRecord(record);
|
|
426
|
+
return value.endsWith('/') ? value.slice(0, -1) : value;
|
|
427
|
+
}));
|
|
428
|
+
}
|
|
429
|
+
async function runGitHasOutput(execution, projectRoot, args) {
|
|
430
|
+
const { child, completion } = startNativeGitProcess(execution, projectRoot, args, false);
|
|
431
|
+
const stderr = [];
|
|
432
|
+
let stderrBytes = 0;
|
|
433
|
+
let hasOutput = false;
|
|
434
|
+
child.stdout.on('data', (chunk) => {
|
|
435
|
+
if (chunk.byteLength > 0)
|
|
436
|
+
hasOutput = true;
|
|
437
|
+
});
|
|
438
|
+
child.stderr.on('data', (chunk) => {
|
|
439
|
+
if (stderrBytes >= GIT_LIST_STDERR_LIMIT)
|
|
440
|
+
return;
|
|
441
|
+
const remaining = GIT_LIST_STDERR_LIMIT - stderrBytes;
|
|
442
|
+
const bounded = Buffer.from(chunk).subarray(0, remaining);
|
|
443
|
+
stderr.push(bounded);
|
|
444
|
+
stderrBytes += bounded.byteLength;
|
|
445
|
+
});
|
|
446
|
+
const { code, spawnError } = await completion;
|
|
447
|
+
if (code === 0 && spawnError === null)
|
|
448
|
+
return hasOutput;
|
|
449
|
+
throw new Error(`git ${args.join(' ')} failed${spawnError ? `: ${spawnError.message}` : stderr.length > 0 ? `: ${Buffer.concat(stderr).toString('utf8').trim()}` : ''}`);
|
|
450
|
+
}
|
|
451
|
+
function safeGitProjectPath(value) {
|
|
452
|
+
const withoutDirectoryMarker = value.endsWith('/') ? value.slice(0, -1) : value;
|
|
453
|
+
if (withoutDirectoryMarker.length === 0 ||
|
|
454
|
+
withoutDirectoryMarker.includes('\\') ||
|
|
455
|
+
path.posix.isAbsolute(withoutDirectoryMarker) ||
|
|
456
|
+
/^[A-Za-z]:/u.test(withoutDirectoryMarker) ||
|
|
457
|
+
path.posix.normalize(withoutDirectoryMarker) !== withoutDirectoryMarker ||
|
|
458
|
+
withoutDirectoryMarker === '..' ||
|
|
459
|
+
withoutDirectoryMarker.startsWith('../') ||
|
|
460
|
+
withoutDirectoryMarker.includes('\0')) {
|
|
461
|
+
return null;
|
|
462
|
+
}
|
|
463
|
+
return withoutDirectoryMarker;
|
|
464
|
+
}
|
|
465
|
+
function requireSafeGitProjectPaths(values, source) {
|
|
466
|
+
return values.map((value) => {
|
|
467
|
+
const relative = safeGitProjectPath(value);
|
|
468
|
+
if (relative === null) {
|
|
469
|
+
throw new Error(`Native Git snapshot provider returned an unsafe ${source} path`);
|
|
470
|
+
}
|
|
471
|
+
return relative;
|
|
472
|
+
});
|
|
473
|
+
}
|
|
474
|
+
async function hasGitMetadataBoundary(projectRoot) {
|
|
475
|
+
let cursor = path.resolve(projectRoot);
|
|
476
|
+
while (true) {
|
|
477
|
+
try {
|
|
478
|
+
await fs.lstat(path.join(cursor, '.git'));
|
|
479
|
+
return true;
|
|
480
|
+
}
|
|
481
|
+
catch (error) {
|
|
482
|
+
if (error.code !== 'ENOENT')
|
|
483
|
+
throw error;
|
|
484
|
+
}
|
|
485
|
+
const parent = path.dirname(cursor);
|
|
486
|
+
if (parent === cursor)
|
|
487
|
+
return false;
|
|
488
|
+
cursor = parent;
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
async function readNativeGitSelectionResults(execution, projectRoot, limits, hooks = {}) {
|
|
492
|
+
const options = {
|
|
493
|
+
...limits,
|
|
494
|
+
...(hooks.outputChunkBytes === undefined ? {} : { outputChunkBytes: hooks.outputChunkBytes }),
|
|
495
|
+
};
|
|
496
|
+
const stagedBefore = await runGitNullRecords(execution, projectRoot, ['ls-files', '--stage', '-z'], options);
|
|
497
|
+
await hooks.afterStageBefore?.();
|
|
498
|
+
const combined = await runGitNullRecords(execution, projectRoot, ['ls-files', '--cached', '--others', '--exclude-standard', '-z'], options);
|
|
499
|
+
await hooks.afterCombined?.();
|
|
500
|
+
const stagedAfter = await runGitNullRecords(execution, projectRoot, ['ls-files', '--stage', '-z'], options);
|
|
501
|
+
return { stagedBefore, combined, stagedAfter };
|
|
502
|
+
}
|
|
503
|
+
function gitSelectionFence(results) {
|
|
504
|
+
return {
|
|
505
|
+
stageBefore: gitSelectionStreamEvidence(results.stagedBefore),
|
|
506
|
+
combined: gitSelectionStreamEvidence(results.combined),
|
|
507
|
+
stageAfter: gitSelectionStreamEvidence(results.stagedAfter),
|
|
508
|
+
};
|
|
509
|
+
}
|
|
510
|
+
async function nativeGitSnapshotSelection(execution, projectRoot, limits = DEFAULT_NATIVE_GIT_SELECTION_LIMITS, hooks = {}) {
|
|
511
|
+
if (!(await hasGitMetadataBoundary(projectRoot)))
|
|
512
|
+
return null;
|
|
513
|
+
let insideWorktree;
|
|
514
|
+
try {
|
|
515
|
+
insideWorktree = await runGitBoundedOutput(execution, projectRoot, [
|
|
516
|
+
'rev-parse',
|
|
517
|
+
'--is-inside-work-tree',
|
|
518
|
+
]);
|
|
519
|
+
}
|
|
520
|
+
catch (error) {
|
|
521
|
+
if (isNativeGitSnapshotTimeout(error))
|
|
522
|
+
throw error;
|
|
523
|
+
throw new Error('Native Git snapshot provider could not inspect the repository boundary', {
|
|
524
|
+
cause: error,
|
|
525
|
+
});
|
|
526
|
+
}
|
|
527
|
+
if (insideWorktree.toString('utf8').trim() !== 'true') {
|
|
528
|
+
throw new Error('Native Git snapshot provider found .git metadata outside a working tree');
|
|
529
|
+
}
|
|
530
|
+
let results;
|
|
531
|
+
try {
|
|
532
|
+
results = await readNativeGitSelectionResults(execution, projectRoot, limits, hooks);
|
|
533
|
+
}
|
|
534
|
+
catch (error) {
|
|
535
|
+
if (isNativeGitSnapshotTimeout(error))
|
|
536
|
+
throw error;
|
|
537
|
+
throw new Error('Native Git snapshot provider failed after repository detection', {
|
|
538
|
+
cause: error,
|
|
539
|
+
});
|
|
540
|
+
}
|
|
541
|
+
const { stagedBefore, combined, stagedAfter } = results;
|
|
542
|
+
const tracked = new Set();
|
|
543
|
+
const gitlinks = new Set();
|
|
544
|
+
const addStagedRecords = (records) => {
|
|
545
|
+
for (const encoded of records) {
|
|
546
|
+
const record = decodeGitRecord(encoded);
|
|
547
|
+
const separator = record.indexOf('\t');
|
|
548
|
+
if (separator < 0) {
|
|
549
|
+
throw new Error('Native Git snapshot provider returned a malformed staged record');
|
|
550
|
+
}
|
|
551
|
+
const header = /^(?<mode>[0-7]{6}) (?<objectId>[a-f0-9]{40}|[a-f0-9]{64}) (?<stage>[0-3])$/u.exec(record.slice(0, separator))?.groups;
|
|
552
|
+
if (!header) {
|
|
553
|
+
throw new Error('Native Git snapshot provider returned a malformed staged header');
|
|
554
|
+
}
|
|
555
|
+
const relative = safeGitProjectPath(record.slice(separator + 1));
|
|
556
|
+
if (relative === null) {
|
|
557
|
+
throw new Error('Native Git snapshot provider returned an unsafe staged path');
|
|
558
|
+
}
|
|
559
|
+
tracked.add(relative);
|
|
560
|
+
if (header.mode === '160000' && header.stage === '0')
|
|
561
|
+
gitlinks.add(relative);
|
|
562
|
+
}
|
|
563
|
+
};
|
|
564
|
+
addStagedRecords(stagedBefore.records);
|
|
565
|
+
addStagedRecords(stagedAfter.records);
|
|
566
|
+
const combinedRecords = combined.records.map(decodeGitRecord);
|
|
567
|
+
const combinedPaths = requireSafeGitProjectPaths(combinedRecords, 'combined');
|
|
568
|
+
const untracked = new Set(combinedPaths.filter((relative) => !tracked.has(relative)));
|
|
569
|
+
const nestedRepositories = new Set(combinedPaths.filter((relative, index) => !tracked.has(relative) && combinedRecords[index].endsWith('/')));
|
|
570
|
+
for (const gitlink of gitlinks)
|
|
571
|
+
nestedRepositories.delete(gitlink);
|
|
572
|
+
return {
|
|
573
|
+
tracked,
|
|
574
|
+
untracked,
|
|
575
|
+
gitlinks,
|
|
576
|
+
nestedRepositories,
|
|
577
|
+
omissions: [],
|
|
578
|
+
overflow: null,
|
|
579
|
+
evidence: null,
|
|
580
|
+
initialFence: gitSelectionFence(results),
|
|
581
|
+
};
|
|
582
|
+
}
|
|
583
|
+
function sameGitSelectionStream(left, right) {
|
|
584
|
+
return (left.hash === right.hash &&
|
|
585
|
+
left.recordCount === right.recordCount &&
|
|
586
|
+
left.storedRecordCount === right.storedRecordCount &&
|
|
587
|
+
left.stdoutBytes === right.stdoutBytes &&
|
|
588
|
+
left.overflow === right.overflow);
|
|
589
|
+
}
|
|
590
|
+
function gitSelectionFenceOverflow(fence) {
|
|
591
|
+
return fence.stageBefore.overflow || fence.combined.overflow || fence.stageAfter.overflow;
|
|
592
|
+
}
|
|
593
|
+
function gitSelectionChanged(initial, final) {
|
|
594
|
+
return (!sameGitSelectionStream(initial.stageBefore, initial.stageAfter) ||
|
|
595
|
+
!sameGitSelectionStream(initial.stageAfter, final.stageBefore) ||
|
|
596
|
+
!sameGitSelectionStream(initial.combined, final.combined) ||
|
|
597
|
+
!sameGitSelectionStream(final.stageBefore, final.stageAfter));
|
|
598
|
+
}
|
|
599
|
+
async function finalizeNativeGitSnapshotSelection(execution, projectRoot, limits, selection, outputChunkBytes) {
|
|
600
|
+
let finalResults;
|
|
601
|
+
try {
|
|
602
|
+
finalResults = await readNativeGitSelectionResults(execution, projectRoot, limits, {
|
|
603
|
+
...(outputChunkBytes === undefined ? {} : { outputChunkBytes }),
|
|
604
|
+
});
|
|
605
|
+
}
|
|
606
|
+
catch (error) {
|
|
607
|
+
if (isNativeGitSnapshotTimeout(error))
|
|
608
|
+
throw error;
|
|
609
|
+
throw new Error('Native Git snapshot provider failed during its final selection fence', {
|
|
610
|
+
cause: error,
|
|
611
|
+
});
|
|
612
|
+
}
|
|
613
|
+
const initial = selection.initialFence;
|
|
614
|
+
const final = gitSelectionFence(finalResults);
|
|
615
|
+
const hasOverflow = gitSelectionFenceOverflow(initial) || gitSelectionFenceOverflow(final);
|
|
616
|
+
const changed = gitSelectionChanged(initial, final);
|
|
617
|
+
if (hasOverflow) {
|
|
618
|
+
selection.omissions.push({
|
|
619
|
+
path: '.',
|
|
620
|
+
size: null,
|
|
621
|
+
type: 'directory',
|
|
622
|
+
reason: 'git-enumeration-limit',
|
|
623
|
+
});
|
|
624
|
+
}
|
|
625
|
+
if (changed) {
|
|
626
|
+
selection.omissions.push({
|
|
627
|
+
path: '.',
|
|
628
|
+
size: null,
|
|
629
|
+
type: 'directory',
|
|
630
|
+
reason: 'git-selection-changed',
|
|
631
|
+
});
|
|
632
|
+
}
|
|
633
|
+
selection.evidence =
|
|
634
|
+
hasOverflow || changed
|
|
635
|
+
? {
|
|
636
|
+
schema: 'comet.native.git-selection.v1',
|
|
637
|
+
status: hasOverflow && changed ? 'overflow-and-changed' : hasOverflow ? 'overflow' : 'changed',
|
|
638
|
+
...initial,
|
|
639
|
+
finalStageBefore: final.stageBefore,
|
|
640
|
+
finalCombined: final.combined,
|
|
641
|
+
finalStageAfter: final.stageAfter,
|
|
642
|
+
}
|
|
643
|
+
: null;
|
|
644
|
+
if (initial.combined.overflow || final.combined.overflow) {
|
|
645
|
+
selection.overflow = {
|
|
646
|
+
count: Math.max(1, initial.combined.recordCount - initial.combined.storedRecordCount, final.combined.recordCount - final.combined.storedRecordCount),
|
|
647
|
+
hash: sha256Text(`comet.native.git-selection-overflow.v2\n${JSON.stringify({
|
|
648
|
+
initial: initial.combined,
|
|
649
|
+
final: final.combined,
|
|
650
|
+
})}`),
|
|
651
|
+
};
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
const PHYSICAL_SELECTION_SUM_MASK = (1n << 256n) - 1n;
|
|
655
|
+
function physicalSelectionRecordType(stat) {
|
|
656
|
+
if (stat.isFile())
|
|
657
|
+
return 'file';
|
|
658
|
+
if (stat.isDirectory())
|
|
659
|
+
return 'directory';
|
|
660
|
+
if (stat.isSymbolicLink())
|
|
661
|
+
return 'symlink';
|
|
662
|
+
return 'other';
|
|
663
|
+
}
|
|
664
|
+
async function nativePhysicalSnapshotSelection(options) {
|
|
665
|
+
// This is deliberately a cooperative execution budget, not a promise-race timeout. Node's fs
|
|
666
|
+
// promises do not accept AbortSignal; abandoning one would let I/O or a Dir handle live past the
|
|
667
|
+
// snapshot. Every production fs operation is therefore checked on both sides, and any crossing
|
|
668
|
+
// makes the whole physical selection non-overridable overflow evidence.
|
|
669
|
+
const projectRoot = path.resolve(options.paths.projectRoot);
|
|
670
|
+
const nativeRoot = path.resolve(options.paths.nativeRoot);
|
|
671
|
+
const configFile = path.resolve(options.paths.configFile);
|
|
672
|
+
const selectionFile = path.join(projectRoot, '.comet', 'current-change.json');
|
|
673
|
+
const records = [];
|
|
674
|
+
const omissions = [];
|
|
675
|
+
const xor = Buffer.alloc(32);
|
|
676
|
+
let sum = 0n;
|
|
677
|
+
let visitedNodeCount = 0;
|
|
678
|
+
let recordCount = 0;
|
|
679
|
+
let encodedBytes = 0;
|
|
680
|
+
let overflow = false;
|
|
681
|
+
let unstable = false;
|
|
682
|
+
let stopped = false;
|
|
683
|
+
const hasExecutionBudget = () => {
|
|
684
|
+
if (remainingNativeSnapshotTime(options.execution) >= 1)
|
|
685
|
+
return true;
|
|
686
|
+
overflow = true;
|
|
687
|
+
stopped = true;
|
|
688
|
+
return false;
|
|
689
|
+
};
|
|
690
|
+
const addRecord = (record) => {
|
|
691
|
+
const encoded = Buffer.from(`${record.type}\0${record.path}`, 'utf8');
|
|
692
|
+
const digest = createHash('sha256')
|
|
693
|
+
.update('comet.native.physical-selection-record.v1\0')
|
|
694
|
+
.update(encoded)
|
|
695
|
+
.digest();
|
|
696
|
+
for (let index = 0; index < xor.length; index += 1)
|
|
697
|
+
xor[index] ^= digest[index];
|
|
698
|
+
sum = (sum + BigInt(`0x${digest.toString('hex')}`)) & PHYSICAL_SELECTION_SUM_MASK;
|
|
699
|
+
recordCount += 1;
|
|
700
|
+
encodedBytes += encoded.byteLength;
|
|
701
|
+
if (Buffer.byteLength(record.path, 'utf8') > options.limits.maxPathBytes ||
|
|
702
|
+
encodedBytes > options.limits.maxBytes) {
|
|
703
|
+
overflow = true;
|
|
704
|
+
stopped = true;
|
|
705
|
+
return;
|
|
706
|
+
}
|
|
707
|
+
records.push(record);
|
|
708
|
+
};
|
|
709
|
+
const visit = async (directory) => {
|
|
710
|
+
if (stopped)
|
|
711
|
+
return;
|
|
712
|
+
if (!hasExecutionBudget())
|
|
713
|
+
return;
|
|
714
|
+
let handle;
|
|
715
|
+
try {
|
|
716
|
+
handle = await fs.opendir(directory);
|
|
717
|
+
}
|
|
718
|
+
catch (error) {
|
|
719
|
+
if (!hasExecutionBudget())
|
|
720
|
+
return;
|
|
721
|
+
if (!isUnreadableError(error) && !isChangedDuringReadError(error))
|
|
722
|
+
throw error;
|
|
723
|
+
if (directory === projectRoot && isUnreadableError(error))
|
|
724
|
+
throw error;
|
|
725
|
+
unstable ||= isChangedDuringReadError(error);
|
|
726
|
+
overflow ||= isUnreadableError(error);
|
|
727
|
+
stopped = true;
|
|
728
|
+
return;
|
|
729
|
+
}
|
|
730
|
+
if (!hasExecutionBudget()) {
|
|
731
|
+
try {
|
|
732
|
+
await handle.close();
|
|
733
|
+
}
|
|
734
|
+
catch (error) {
|
|
735
|
+
if (error.code !== 'ERR_DIR_CLOSED')
|
|
736
|
+
throw error;
|
|
737
|
+
}
|
|
738
|
+
return;
|
|
739
|
+
}
|
|
740
|
+
let traversalFailed = false;
|
|
741
|
+
let traversalError;
|
|
742
|
+
try {
|
|
743
|
+
while (!stopped) {
|
|
744
|
+
if (!hasExecutionBudget())
|
|
745
|
+
break;
|
|
746
|
+
let child;
|
|
747
|
+
try {
|
|
748
|
+
child = await handle.read();
|
|
749
|
+
}
|
|
750
|
+
catch (error) {
|
|
751
|
+
if (!hasExecutionBudget())
|
|
752
|
+
break;
|
|
753
|
+
if (!isUnreadableError(error) && !isChangedDuringReadError(error))
|
|
754
|
+
throw error;
|
|
755
|
+
if (directory === projectRoot && isUnreadableError(error))
|
|
756
|
+
throw error;
|
|
757
|
+
unstable ||= isChangedDuringReadError(error);
|
|
758
|
+
overflow ||= isUnreadableError(error);
|
|
759
|
+
stopped = true;
|
|
760
|
+
break;
|
|
761
|
+
}
|
|
762
|
+
if (!hasExecutionBudget() || child === null)
|
|
763
|
+
break;
|
|
764
|
+
visitedNodeCount += 1;
|
|
765
|
+
if (visitedNodeCount > options.limits.maxNodes) {
|
|
766
|
+
overflow = true;
|
|
767
|
+
stopped = true;
|
|
768
|
+
break;
|
|
769
|
+
}
|
|
770
|
+
const target = path.join(directory, child.name);
|
|
771
|
+
const relative = portableRelative(projectRoot, target);
|
|
772
|
+
if (target === configFile ||
|
|
773
|
+
target === selectionFile ||
|
|
774
|
+
sameOrInside(nativeRoot, target) ||
|
|
775
|
+
options.denylist.some((denied) => sameOrInside(denied, target)) ||
|
|
776
|
+
nativeSensitiveRelativePathReason(relative) !== null) {
|
|
777
|
+
continue;
|
|
778
|
+
}
|
|
779
|
+
if (!hasExecutionBudget())
|
|
780
|
+
break;
|
|
781
|
+
let stat;
|
|
782
|
+
try {
|
|
783
|
+
stat = await fs.lstat(target);
|
|
784
|
+
}
|
|
785
|
+
catch (error) {
|
|
786
|
+
if (!hasExecutionBudget())
|
|
787
|
+
break;
|
|
788
|
+
if (!isUnreadableError(error) && !isChangedDuringReadError(error))
|
|
789
|
+
throw error;
|
|
790
|
+
unstable = true;
|
|
791
|
+
addRecord({ path: relative, type: 'other' });
|
|
792
|
+
omissions.push({
|
|
793
|
+
path: relative,
|
|
794
|
+
size: null,
|
|
795
|
+
type: 'file',
|
|
796
|
+
reason: isUnreadableError(error) ? 'unreadable' : 'changed-during-read',
|
|
797
|
+
});
|
|
798
|
+
await options.hooks?.afterNode?.(relative);
|
|
799
|
+
if (stopped)
|
|
800
|
+
break;
|
|
801
|
+
if (!hasExecutionBudget())
|
|
802
|
+
break;
|
|
803
|
+
continue;
|
|
804
|
+
}
|
|
805
|
+
if (!hasExecutionBudget())
|
|
806
|
+
break;
|
|
807
|
+
const type = physicalSelectionRecordType(stat);
|
|
808
|
+
if (type === 'directory') {
|
|
809
|
+
if (!hasExecutionBudget())
|
|
810
|
+
break;
|
|
811
|
+
let realDirectory;
|
|
812
|
+
try {
|
|
813
|
+
realDirectory = await fs.realpath(target);
|
|
814
|
+
}
|
|
815
|
+
catch (error) {
|
|
816
|
+
if (!hasExecutionBudget())
|
|
817
|
+
break;
|
|
818
|
+
if (!isUnreadableError(error) && !isChangedDuringReadError(error))
|
|
819
|
+
throw error;
|
|
820
|
+
unstable = true;
|
|
821
|
+
addRecord({ path: relative, type });
|
|
822
|
+
omissions.push({
|
|
823
|
+
path: relative,
|
|
824
|
+
size: null,
|
|
825
|
+
type: 'directory',
|
|
826
|
+
reason: isUnreadableError(error) ? 'unreadable' : 'changed-during-read',
|
|
827
|
+
});
|
|
828
|
+
await options.hooks?.afterNode?.(relative);
|
|
829
|
+
if (stopped)
|
|
830
|
+
break;
|
|
831
|
+
if (!hasExecutionBudget())
|
|
832
|
+
break;
|
|
833
|
+
continue;
|
|
834
|
+
}
|
|
835
|
+
if (!hasExecutionBudget())
|
|
836
|
+
break;
|
|
837
|
+
if (!isInsidePath(options.physicalProjectRoot, realDirectory) ||
|
|
838
|
+
sameOrInside(options.physicalNativeRoot, realDirectory)) {
|
|
839
|
+
continue;
|
|
840
|
+
}
|
|
841
|
+
addRecord({ path: relative, type });
|
|
842
|
+
await options.hooks?.afterNode?.(relative);
|
|
843
|
+
if (!hasExecutionBudget())
|
|
844
|
+
break;
|
|
845
|
+
await visit(target);
|
|
846
|
+
if (stopped)
|
|
847
|
+
break;
|
|
848
|
+
continue;
|
|
849
|
+
}
|
|
850
|
+
addRecord({ path: relative, type });
|
|
851
|
+
await options.hooks?.afterNode?.(relative);
|
|
852
|
+
if (!hasExecutionBudget())
|
|
853
|
+
break;
|
|
854
|
+
if (stopped)
|
|
855
|
+
break;
|
|
856
|
+
}
|
|
857
|
+
}
|
|
858
|
+
catch (error) {
|
|
859
|
+
traversalFailed = true;
|
|
860
|
+
traversalError = error;
|
|
861
|
+
}
|
|
862
|
+
let closeError;
|
|
863
|
+
try {
|
|
864
|
+
await handle.close();
|
|
865
|
+
}
|
|
866
|
+
catch (error) {
|
|
867
|
+
closeError = error;
|
|
868
|
+
}
|
|
869
|
+
const withinBudget = hasExecutionBudget();
|
|
870
|
+
if (traversalFailed)
|
|
871
|
+
throw traversalError;
|
|
872
|
+
if (withinBudget &&
|
|
873
|
+
closeError !== undefined &&
|
|
874
|
+
closeError.code !== 'ERR_DIR_CLOSED') {
|
|
875
|
+
throw closeError;
|
|
876
|
+
}
|
|
877
|
+
};
|
|
878
|
+
await visit(projectRoot);
|
|
879
|
+
hasExecutionBudget();
|
|
880
|
+
if (overflow) {
|
|
881
|
+
// The tail is intentionally unbound once a hard enumeration budget is crossed. Discard the
|
|
882
|
+
// order-dependent prefix entirely; callers must treat this evidence as non-overridable.
|
|
883
|
+
records.length = 0;
|
|
884
|
+
recordCount = 0;
|
|
885
|
+
encodedBytes = 0;
|
|
886
|
+
}
|
|
887
|
+
else {
|
|
888
|
+
records.sort((left, right) => left.path.localeCompare(right.path, 'en'));
|
|
889
|
+
omissions.sort((left, right) => left.path.localeCompare(right.path, 'en'));
|
|
890
|
+
}
|
|
891
|
+
const hash = overflow
|
|
892
|
+
? sha256Text(`comet.native.physical-selection-incomplete.v1\n${JSON.stringify({
|
|
893
|
+
maxNodes: options.limits.maxNodes,
|
|
894
|
+
maxBytes: options.limits.maxBytes,
|
|
895
|
+
maxPathBytes: options.limits.maxPathBytes,
|
|
896
|
+
})}`)
|
|
897
|
+
: sha256Text(`comet.native.physical-selection.v1\n${JSON.stringify({
|
|
898
|
+
visitedNodeCount,
|
|
899
|
+
recordCount,
|
|
900
|
+
encodedBytes,
|
|
901
|
+
xor: xor.toString('hex'),
|
|
902
|
+
sum: sum.toString(16).padStart(64, '0'),
|
|
903
|
+
})}`);
|
|
904
|
+
return {
|
|
905
|
+
records,
|
|
906
|
+
omissions,
|
|
907
|
+
evidence: {
|
|
908
|
+
hash,
|
|
909
|
+
visitedNodeCount,
|
|
910
|
+
recordCount,
|
|
911
|
+
storedRecordCount: records.length,
|
|
912
|
+
encodedBytes,
|
|
913
|
+
overflow,
|
|
914
|
+
unstable,
|
|
915
|
+
},
|
|
916
|
+
};
|
|
917
|
+
}
|
|
918
|
+
function samePhysicalSelectionStream(left, right) {
|
|
919
|
+
return (left.hash === right.hash &&
|
|
920
|
+
left.visitedNodeCount === right.visitedNodeCount &&
|
|
921
|
+
left.recordCount === right.recordCount &&
|
|
922
|
+
left.storedRecordCount === right.storedRecordCount &&
|
|
923
|
+
left.encodedBytes === right.encodedBytes &&
|
|
924
|
+
left.overflow === right.overflow &&
|
|
925
|
+
left.unstable === right.unstable);
|
|
926
|
+
}
|
|
927
|
+
function finalizeNativePhysicalSelection(before, after) {
|
|
928
|
+
const hasOverflow = before.overflow || after.overflow;
|
|
929
|
+
const changed = before.unstable || after.unstable || !samePhysicalSelectionStream(before, after);
|
|
930
|
+
const omissions = [];
|
|
931
|
+
if (hasOverflow) {
|
|
932
|
+
omissions.push({
|
|
933
|
+
path: '.',
|
|
934
|
+
size: null,
|
|
935
|
+
type: 'directory',
|
|
936
|
+
reason: 'physical-enumeration-limit',
|
|
937
|
+
});
|
|
938
|
+
}
|
|
939
|
+
if (changed) {
|
|
940
|
+
omissions.push({
|
|
941
|
+
path: '.',
|
|
942
|
+
size: null,
|
|
943
|
+
type: 'directory',
|
|
944
|
+
reason: 'physical-selection-changed',
|
|
945
|
+
});
|
|
946
|
+
}
|
|
947
|
+
return {
|
|
948
|
+
omissions,
|
|
949
|
+
evidence: hasOverflow || changed
|
|
950
|
+
? {
|
|
951
|
+
schema: 'comet.native.physical-selection.v1',
|
|
952
|
+
status: hasOverflow && changed
|
|
953
|
+
? 'overflow-and-changed'
|
|
954
|
+
: hasOverflow
|
|
955
|
+
? 'overflow'
|
|
956
|
+
: 'changed',
|
|
957
|
+
before,
|
|
958
|
+
after,
|
|
959
|
+
}
|
|
960
|
+
: null,
|
|
961
|
+
};
|
|
962
|
+
}
|
|
963
|
+
function selectionPaths(selection) {
|
|
964
|
+
return [...new Set([...selection.tracked, ...selection.untracked])].sort((left, right) => left.localeCompare(right, 'en'));
|
|
965
|
+
}
|
|
966
|
+
async function readGitlinkWorkingTreeHeadHash(execution, target) {
|
|
967
|
+
const output = await runGitBoundedOutput(execution, target, ['rev-parse', '--verify', 'HEAD']);
|
|
968
|
+
const objectId = output.toString('utf8').trim().toLowerCase();
|
|
969
|
+
if (!GIT_OBJECT_ID_PATTERN.test(objectId)) {
|
|
970
|
+
throw new Error('Native Git snapshot provider received an invalid submodule HEAD');
|
|
971
|
+
}
|
|
972
|
+
return sha256Text(`gitlink:${objectId}`);
|
|
973
|
+
}
|
|
974
|
+
async function inspectGitlinkWorkingTree(execution, target) {
|
|
975
|
+
const before = await readGitlinkWorkingTreeHeadHash(execution, target);
|
|
976
|
+
const dirty = await runGitHasOutput(execution, target, [
|
|
977
|
+
'status',
|
|
978
|
+
'--porcelain=v1',
|
|
979
|
+
'-z',
|
|
980
|
+
'--untracked-files=normal',
|
|
981
|
+
]);
|
|
982
|
+
const after = await readGitlinkWorkingTreeHeadHash(execution, target);
|
|
983
|
+
if (after !== before) {
|
|
984
|
+
throw new Error('Native Git snapshot provider observed a changing submodule HEAD');
|
|
985
|
+
}
|
|
986
|
+
return { hash: after, dirty };
|
|
987
|
+
}
|
|
988
|
+
function isSnapshotProjectRef(paths, relative) {
|
|
989
|
+
if (nativeSensitiveRelativePathReason(relative) !== null)
|
|
990
|
+
return false;
|
|
991
|
+
const target = path.resolve(paths.projectRoot, ...relative.split('/'));
|
|
992
|
+
return !sameOrInside(path.resolve(paths.nativeRoot), target);
|
|
993
|
+
}
|
|
994
|
+
export function inspectNativeContentSnapshotHealth(value, options = {}) {
|
|
995
|
+
const manifest = parseNativeContentSnapshotManifest(value);
|
|
996
|
+
const maxRecordedPaths = options.maxRecordedPaths ?? 20;
|
|
997
|
+
if (!Number.isSafeInteger(maxRecordedPaths) || maxRecordedPaths < 0) {
|
|
998
|
+
throw new Error('Native snapshot health maxRecordedPaths must be a non-negative integer');
|
|
999
|
+
}
|
|
1000
|
+
const samplePaths = manifest.omitted.slice(0, maxRecordedPaths).map((omission) => omission.path);
|
|
1001
|
+
return {
|
|
1002
|
+
complete: manifest.complete,
|
|
1003
|
+
omittedCount: manifest.omittedCount,
|
|
1004
|
+
recordedOmissionCount: manifest.omitted.length,
|
|
1005
|
+
overflowCount: manifest.omissionOverflow?.count ?? 0,
|
|
1006
|
+
samplePaths,
|
|
1007
|
+
sampleTruncated: samplePaths.length < manifest.omittedCount,
|
|
1008
|
+
};
|
|
1009
|
+
}
|
|
1010
|
+
function portableRelative(root, target) {
|
|
1011
|
+
return path.relative(root, target).split(path.sep).join('/');
|
|
1012
|
+
}
|
|
1013
|
+
function normalizedDenylist(projectRoot, values) {
|
|
1014
|
+
return values.map((value) => path.resolve(projectRoot, ...value.split(/[\\/]/u)));
|
|
1015
|
+
}
|
|
1016
|
+
function sameOrInside(root, target) {
|
|
1017
|
+
const normalizedRoot = path.resolve(root);
|
|
1018
|
+
const normalizedTarget = path.resolve(target);
|
|
1019
|
+
return normalizedTarget === normalizedRoot || isInsidePath(normalizedRoot, normalizedTarget);
|
|
1020
|
+
}
|
|
1021
|
+
function isUnreadableError(error) {
|
|
1022
|
+
return UNREADABLE_ERROR_CODES.has(error.code ?? '');
|
|
1023
|
+
}
|
|
1024
|
+
function isChangedDuringReadError(error) {
|
|
1025
|
+
return error.code === 'ENOENT';
|
|
1026
|
+
}
|
|
1027
|
+
function serializedManifestBytes(manifest) {
|
|
1028
|
+
return Buffer.byteLength(JSON.stringify(manifest, null, 2) + '\n');
|
|
1029
|
+
}
|
|
1030
|
+
function foldSnapshotOverflowHash(previous, kind, value) {
|
|
1031
|
+
const payload = JSON.stringify(value);
|
|
1032
|
+
return sha256Text(`${previous}\n${Buffer.byteLength(kind)}:${kind}\n${Buffer.byteLength(payload)}:${payload}`);
|
|
1033
|
+
}
|
|
1034
|
+
function isSelectionIntegrityOmission(omission) {
|
|
1035
|
+
return (omission.reason === 'git-enumeration-limit' ||
|
|
1036
|
+
omission.reason === 'git-selection-changed' ||
|
|
1037
|
+
omission.reason === 'physical-enumeration-limit' ||
|
|
1038
|
+
omission.reason === 'physical-selection-changed');
|
|
1039
|
+
}
|
|
1040
|
+
function takeLastCompactableOmission(omissions) {
|
|
1041
|
+
for (let index = omissions.length - 1; index >= 0; index -= 1) {
|
|
1042
|
+
const omission = omissions[index];
|
|
1043
|
+
if (isSelectionIntegrityOmission(omission))
|
|
1044
|
+
continue;
|
|
1045
|
+
omissions.splice(index, 1);
|
|
1046
|
+
return omission;
|
|
1047
|
+
}
|
|
1048
|
+
return null;
|
|
1049
|
+
}
|
|
1050
|
+
function sameFileIdentity(left, right) {
|
|
1051
|
+
const leftObject = { ...left, birthtime: left.birthtimeMs };
|
|
1052
|
+
const rightObject = { ...right, birthtime: right.birthtimeMs };
|
|
1053
|
+
if (hasComparableNativeFileObject(leftObject, rightObject)) {
|
|
1054
|
+
return sameNativeFileObject(leftObject, rightObject);
|
|
1055
|
+
}
|
|
1056
|
+
return (sameNativeFileObject(leftObject, rightObject) &&
|
|
1057
|
+
left.birthtimeMs === right.birthtimeMs &&
|
|
1058
|
+
left.ctimeMs === right.ctimeMs &&
|
|
1059
|
+
left.size === right.size);
|
|
1060
|
+
}
|
|
1061
|
+
async function sha256FileBounded(file, maxBytes, expected, execution) {
|
|
1062
|
+
if (!nativeSnapshotExecutionHasBudget(execution))
|
|
1063
|
+
return { status: 'budget-exhausted' };
|
|
1064
|
+
let handle;
|
|
1065
|
+
try {
|
|
1066
|
+
handle = await fs.open(file, 'r');
|
|
1067
|
+
}
|
|
1068
|
+
catch (error) {
|
|
1069
|
+
if (!nativeSnapshotExecutionHasBudget(execution))
|
|
1070
|
+
return { status: 'budget-exhausted' };
|
|
1071
|
+
throw error;
|
|
1072
|
+
}
|
|
1073
|
+
if (!nativeSnapshotExecutionHasBudget(execution)) {
|
|
1074
|
+
await handle.close();
|
|
1075
|
+
return { status: 'budget-exhausted' };
|
|
1076
|
+
}
|
|
1077
|
+
const hash = createHash('sha256');
|
|
1078
|
+
const buffer = Buffer.allocUnsafe(Math.min(64 * 1024, maxBytes + 1));
|
|
1079
|
+
let bytes = 0;
|
|
1080
|
+
try {
|
|
1081
|
+
if (!nativeSnapshotExecutionHasBudget(execution))
|
|
1082
|
+
return { status: 'budget-exhausted' };
|
|
1083
|
+
const opened = await handle.stat();
|
|
1084
|
+
if (!nativeSnapshotExecutionHasBudget(execution))
|
|
1085
|
+
return { status: 'budget-exhausted' };
|
|
1086
|
+
if (!opened.isFile() || !sameFileIdentity(expected, opened))
|
|
1087
|
+
return { status: 'changed' };
|
|
1088
|
+
while (true) {
|
|
1089
|
+
if (!nativeSnapshotExecutionHasBudget(execution))
|
|
1090
|
+
return { status: 'budget-exhausted' };
|
|
1091
|
+
const remaining = maxBytes + 1 - bytes;
|
|
1092
|
+
if (remaining < 1)
|
|
1093
|
+
return { status: 'changed' };
|
|
1094
|
+
const result = await handle.read(buffer, 0, Math.min(buffer.length, remaining), null);
|
|
1095
|
+
if (!nativeSnapshotExecutionHasBudget(execution))
|
|
1096
|
+
return { status: 'budget-exhausted' };
|
|
1097
|
+
if (result.bytesRead === 0) {
|
|
1098
|
+
if (!nativeSnapshotExecutionHasBudget(execution))
|
|
1099
|
+
return { status: 'budget-exhausted' };
|
|
1100
|
+
const finalStat = await handle.stat();
|
|
1101
|
+
if (!nativeSnapshotExecutionHasBudget(execution)) {
|
|
1102
|
+
return { status: 'budget-exhausted' };
|
|
1103
|
+
}
|
|
1104
|
+
if (!finalStat.isFile() || !sameFileIdentity(opened, finalStat)) {
|
|
1105
|
+
return { status: 'changed' };
|
|
1106
|
+
}
|
|
1107
|
+
return { status: 'complete', hash: hash.digest('hex'), bytes, finalStat };
|
|
1108
|
+
}
|
|
1109
|
+
if (bytes + result.bytesRead > maxBytes)
|
|
1110
|
+
return { status: 'changed' };
|
|
1111
|
+
hash.update(buffer.subarray(0, result.bytesRead));
|
|
1112
|
+
bytes += result.bytesRead;
|
|
1113
|
+
}
|
|
1114
|
+
}
|
|
1115
|
+
finally {
|
|
1116
|
+
await handle.close();
|
|
1117
|
+
}
|
|
1118
|
+
}
|
|
1119
|
+
function record(value, label) {
|
|
1120
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) {
|
|
1121
|
+
throw new Error(`${label} must be an object`);
|
|
1122
|
+
}
|
|
1123
|
+
return value;
|
|
1124
|
+
}
|
|
1125
|
+
function rejectUnknown(value, keys, label) {
|
|
1126
|
+
const unknown = Object.keys(value).find((key) => !keys.has(key));
|
|
1127
|
+
if (unknown)
|
|
1128
|
+
throw new Error(`${label} contains unknown field: ${unknown}`);
|
|
1129
|
+
}
|
|
1130
|
+
function positiveInteger(value, label) {
|
|
1131
|
+
if (!Number.isSafeInteger(value) || value < 1) {
|
|
1132
|
+
throw new Error(`${label} must be a positive integer`);
|
|
1133
|
+
}
|
|
1134
|
+
return value;
|
|
1135
|
+
}
|
|
1136
|
+
function nonNegativeInteger(value, label) {
|
|
1137
|
+
if (!Number.isSafeInteger(value) || value < 0) {
|
|
1138
|
+
throw new Error(`${label} must be a non-negative integer`);
|
|
1139
|
+
}
|
|
1140
|
+
return value;
|
|
1141
|
+
}
|
|
1142
|
+
function snapshotPath(value, label) {
|
|
1143
|
+
if (typeof value !== 'string' ||
|
|
1144
|
+
value.length === 0 ||
|
|
1145
|
+
value.includes('\\') ||
|
|
1146
|
+
value.includes('\0')) {
|
|
1147
|
+
throw new Error(`${label} must be a normalized project-relative path`);
|
|
1148
|
+
}
|
|
1149
|
+
const normalized = path.posix.normalize(value);
|
|
1150
|
+
if (normalized !== value ||
|
|
1151
|
+
path.posix.isAbsolute(value) ||
|
|
1152
|
+
normalized === '..' ||
|
|
1153
|
+
normalized.startsWith('../')) {
|
|
1154
|
+
throw new Error(`${label} must stay inside the project root`);
|
|
1155
|
+
}
|
|
1156
|
+
return value;
|
|
1157
|
+
}
|
|
1158
|
+
function parseEntry(value, index) {
|
|
1159
|
+
const entry = record(value, `Native snapshot entry ${index}`);
|
|
1160
|
+
rejectUnknown(entry, ENTRY_KEYS, `Native snapshot entry ${index}`);
|
|
1161
|
+
const entryPath = snapshotPath(entry.path, `Native snapshot entry ${index} path`);
|
|
1162
|
+
if (typeof entry.hash !== 'string' || !HASH_PATTERN.test(entry.hash)) {
|
|
1163
|
+
throw new Error(`Native snapshot entry ${index} hash is invalid`);
|
|
1164
|
+
}
|
|
1165
|
+
if (entry.type !== 'file')
|
|
1166
|
+
throw new Error(`Native snapshot entry ${index} type is invalid`);
|
|
1167
|
+
return {
|
|
1168
|
+
path: entryPath,
|
|
1169
|
+
hash: entry.hash,
|
|
1170
|
+
size: nonNegativeInteger(entry.size, `Native snapshot entry ${index} size`),
|
|
1171
|
+
type: 'file',
|
|
1172
|
+
};
|
|
1173
|
+
}
|
|
1174
|
+
function parseOmission(value, index) {
|
|
1175
|
+
const omission = record(value, `Native snapshot omission ${index}`);
|
|
1176
|
+
rejectUnknown(omission, OMISSION_KEYS, `Native snapshot omission ${index}`);
|
|
1177
|
+
if (!OMISSION_TYPES.has(omission.type)) {
|
|
1178
|
+
throw new Error(`Native snapshot omission ${index} type is invalid`);
|
|
1179
|
+
}
|
|
1180
|
+
if (!OMISSION_REASONS.has(omission.reason)) {
|
|
1181
|
+
throw new Error(`Native snapshot omission ${index} reason is invalid`);
|
|
1182
|
+
}
|
|
1183
|
+
return {
|
|
1184
|
+
path: snapshotPath(omission.path, `Native snapshot omission ${index} path`),
|
|
1185
|
+
size: omission.size === null
|
|
1186
|
+
? null
|
|
1187
|
+
: nonNegativeInteger(omission.size, `Native snapshot omission ${index} size`),
|
|
1188
|
+
type: omission.type,
|
|
1189
|
+
reason: omission.reason,
|
|
1190
|
+
};
|
|
1191
|
+
}
|
|
1192
|
+
function parseOmissionOverflow(value) {
|
|
1193
|
+
const overflow = record(value, 'Native snapshot omission overflow');
|
|
1194
|
+
rejectUnknown(overflow, OMISSION_OVERFLOW_KEYS, 'Native snapshot omission overflow');
|
|
1195
|
+
if (typeof overflow.hash !== 'string' || !HASH_PATTERN.test(overflow.hash)) {
|
|
1196
|
+
throw new Error('Native snapshot omission overflow hash is invalid');
|
|
1197
|
+
}
|
|
1198
|
+
const expectedRef = `native-snapshot://omitted-overflow/${overflow.hash}`;
|
|
1199
|
+
if (overflow.ref !== expectedRef) {
|
|
1200
|
+
throw new Error('Native snapshot omission overflow ref is invalid');
|
|
1201
|
+
}
|
|
1202
|
+
return {
|
|
1203
|
+
ref: expectedRef,
|
|
1204
|
+
hash: overflow.hash,
|
|
1205
|
+
count: positiveInteger(overflow.count, 'Native snapshot omission overflow count'),
|
|
1206
|
+
};
|
|
1207
|
+
}
|
|
1208
|
+
function parseGitSelectionStreamEvidence(value, label) {
|
|
1209
|
+
const stream = record(value, label);
|
|
1210
|
+
rejectUnknown(stream, GIT_SELECTION_STREAM_KEYS, label);
|
|
1211
|
+
if (typeof stream.hash !== 'string' || !HASH_PATTERN.test(stream.hash)) {
|
|
1212
|
+
throw new Error(`${label} hash is invalid`);
|
|
1213
|
+
}
|
|
1214
|
+
if (typeof stream.overflow !== 'boolean') {
|
|
1215
|
+
throw new Error(`${label} overflow flag is invalid`);
|
|
1216
|
+
}
|
|
1217
|
+
const recordCount = nonNegativeInteger(stream.recordCount, `${label} recordCount`);
|
|
1218
|
+
const storedRecordCount = nonNegativeInteger(stream.storedRecordCount, `${label} storedRecordCount`);
|
|
1219
|
+
const stdoutBytes = nonNegativeInteger(stream.stdoutBytes, `${label} stdoutBytes`);
|
|
1220
|
+
if (storedRecordCount > recordCount || (!stream.overflow && storedRecordCount !== recordCount)) {
|
|
1221
|
+
throw new Error(`${label} stored record count is inconsistent`);
|
|
1222
|
+
}
|
|
1223
|
+
return {
|
|
1224
|
+
hash: stream.hash,
|
|
1225
|
+
recordCount,
|
|
1226
|
+
storedRecordCount,
|
|
1227
|
+
stdoutBytes,
|
|
1228
|
+
overflow: stream.overflow,
|
|
1229
|
+
};
|
|
1230
|
+
}
|
|
1231
|
+
function parseGitSelectionEvidence(value) {
|
|
1232
|
+
const selection = record(value, 'Native Git selection evidence');
|
|
1233
|
+
rejectUnknown(selection, GIT_SELECTION_KEYS, 'Native Git selection evidence');
|
|
1234
|
+
if (selection.schema !== 'comet.native.git-selection.v1') {
|
|
1235
|
+
throw new Error('Native Git selection evidence schema is invalid');
|
|
1236
|
+
}
|
|
1237
|
+
if (selection.status !== 'overflow' &&
|
|
1238
|
+
selection.status !== 'changed' &&
|
|
1239
|
+
selection.status !== 'overflow-and-changed') {
|
|
1240
|
+
throw new Error('Native Git selection evidence status is invalid');
|
|
1241
|
+
}
|
|
1242
|
+
const stageBefore = parseGitSelectionStreamEvidence(selection.stageBefore, 'Native Git selection stageBefore');
|
|
1243
|
+
const combined = parseGitSelectionStreamEvidence(selection.combined, 'Native Git selection combined');
|
|
1244
|
+
const stageAfter = parseGitSelectionStreamEvidence(selection.stageAfter, 'Native Git selection stageAfter');
|
|
1245
|
+
const finalStageBefore = parseGitSelectionStreamEvidence(selection.finalStageBefore, 'Native Git selection finalStageBefore');
|
|
1246
|
+
const finalCombined = parseGitSelectionStreamEvidence(selection.finalCombined, 'Native Git selection finalCombined');
|
|
1247
|
+
const finalStageAfter = parseGitSelectionStreamEvidence(selection.finalStageAfter, 'Native Git selection finalStageAfter');
|
|
1248
|
+
const hasOverflow = [
|
|
1249
|
+
stageBefore,
|
|
1250
|
+
combined,
|
|
1251
|
+
stageAfter,
|
|
1252
|
+
finalStageBefore,
|
|
1253
|
+
finalCombined,
|
|
1254
|
+
finalStageAfter,
|
|
1255
|
+
].some((stream) => stream.overflow);
|
|
1256
|
+
const changed = !sameGitSelectionStream(stageBefore, stageAfter) ||
|
|
1257
|
+
!sameGitSelectionStream(stageAfter, finalStageBefore) ||
|
|
1258
|
+
!sameGitSelectionStream(combined, finalCombined) ||
|
|
1259
|
+
!sameGitSelectionStream(finalStageBefore, finalStageAfter);
|
|
1260
|
+
const expectedStatus = hasOverflow && changed ? 'overflow-and-changed' : hasOverflow ? 'overflow' : 'changed';
|
|
1261
|
+
if (!hasOverflow && !changed) {
|
|
1262
|
+
throw new Error('Native Git selection evidence must describe an exceptional selection');
|
|
1263
|
+
}
|
|
1264
|
+
if (selection.status !== expectedStatus) {
|
|
1265
|
+
throw new Error('Native Git selection evidence status is inconsistent');
|
|
1266
|
+
}
|
|
1267
|
+
return {
|
|
1268
|
+
schema: 'comet.native.git-selection.v1',
|
|
1269
|
+
status: expectedStatus,
|
|
1270
|
+
stageBefore,
|
|
1271
|
+
combined,
|
|
1272
|
+
stageAfter,
|
|
1273
|
+
finalStageBefore,
|
|
1274
|
+
finalCombined,
|
|
1275
|
+
finalStageAfter,
|
|
1276
|
+
};
|
|
1277
|
+
}
|
|
1278
|
+
function parsePhysicalSelectionStreamEvidence(value, label) {
|
|
1279
|
+
const stream = record(value, label);
|
|
1280
|
+
rejectUnknown(stream, PHYSICAL_SELECTION_STREAM_KEYS, label);
|
|
1281
|
+
if (typeof stream.hash !== 'string' || !HASH_PATTERN.test(stream.hash)) {
|
|
1282
|
+
throw new Error(`${label} hash is invalid`);
|
|
1283
|
+
}
|
|
1284
|
+
if (typeof stream.overflow !== 'boolean' || typeof stream.unstable !== 'boolean') {
|
|
1285
|
+
throw new Error(`${label} flags are invalid`);
|
|
1286
|
+
}
|
|
1287
|
+
const visitedNodeCount = nonNegativeInteger(stream.visitedNodeCount, `${label} visitedNodeCount`);
|
|
1288
|
+
const recordCount = nonNegativeInteger(stream.recordCount, `${label} recordCount`);
|
|
1289
|
+
const storedRecordCount = nonNegativeInteger(stream.storedRecordCount, `${label} storedRecordCount`);
|
|
1290
|
+
const encodedBytes = nonNegativeInteger(stream.encodedBytes, `${label} encodedBytes`);
|
|
1291
|
+
if (storedRecordCount > recordCount || (!stream.overflow && storedRecordCount !== recordCount)) {
|
|
1292
|
+
throw new Error(`${label} stored record count is inconsistent`);
|
|
1293
|
+
}
|
|
1294
|
+
return {
|
|
1295
|
+
hash: stream.hash,
|
|
1296
|
+
visitedNodeCount,
|
|
1297
|
+
recordCount,
|
|
1298
|
+
storedRecordCount,
|
|
1299
|
+
encodedBytes,
|
|
1300
|
+
overflow: stream.overflow,
|
|
1301
|
+
unstable: stream.unstable,
|
|
1302
|
+
};
|
|
1303
|
+
}
|
|
1304
|
+
function parsePhysicalSelectionEvidence(value) {
|
|
1305
|
+
const selection = record(value, 'Native physical selection evidence');
|
|
1306
|
+
rejectUnknown(selection, PHYSICAL_SELECTION_KEYS, 'Native physical selection evidence');
|
|
1307
|
+
if (selection.schema !== 'comet.native.physical-selection.v1') {
|
|
1308
|
+
throw new Error('Native physical selection evidence schema is invalid');
|
|
1309
|
+
}
|
|
1310
|
+
if (selection.status !== 'overflow' &&
|
|
1311
|
+
selection.status !== 'changed' &&
|
|
1312
|
+
selection.status !== 'overflow-and-changed') {
|
|
1313
|
+
throw new Error('Native physical selection evidence status is invalid');
|
|
1314
|
+
}
|
|
1315
|
+
const before = parsePhysicalSelectionStreamEvidence(selection.before, 'Native physical selection before');
|
|
1316
|
+
const after = parsePhysicalSelectionStreamEvidence(selection.after, 'Native physical selection after');
|
|
1317
|
+
const hasOverflow = before.overflow || after.overflow;
|
|
1318
|
+
const changed = before.unstable || after.unstable || !samePhysicalSelectionStream(before, after);
|
|
1319
|
+
if (!hasOverflow && !changed) {
|
|
1320
|
+
throw new Error('Native physical selection evidence must describe an exceptional selection');
|
|
1321
|
+
}
|
|
1322
|
+
const expectedStatus = hasOverflow && changed ? 'overflow-and-changed' : hasOverflow ? 'overflow' : 'changed';
|
|
1323
|
+
if (selection.status !== expectedStatus) {
|
|
1324
|
+
throw new Error('Native physical selection evidence status is inconsistent');
|
|
1325
|
+
}
|
|
1326
|
+
return {
|
|
1327
|
+
schema: 'comet.native.physical-selection.v1',
|
|
1328
|
+
status: expectedStatus,
|
|
1329
|
+
before,
|
|
1330
|
+
after,
|
|
1331
|
+
};
|
|
1332
|
+
}
|
|
1333
|
+
export function parseNativeContentSnapshotManifest(value) {
|
|
1334
|
+
const manifest = record(value, 'Native content snapshot manifest');
|
|
1335
|
+
rejectUnknown(manifest, MANIFEST_KEYS, 'Native content snapshot manifest');
|
|
1336
|
+
if (manifest.schema !== 'comet.native.content-snapshot.v1') {
|
|
1337
|
+
throw new Error('Unsupported Native content snapshot schema');
|
|
1338
|
+
}
|
|
1339
|
+
if (!SNAPSHOT_ORIGINS.has(manifest.origin)) {
|
|
1340
|
+
throw new Error('Native content snapshot origin is invalid');
|
|
1341
|
+
}
|
|
1342
|
+
let capture;
|
|
1343
|
+
if (manifest.capture !== undefined) {
|
|
1344
|
+
const captureValue = record(manifest.capture, 'Native content snapshot capture');
|
|
1345
|
+
rejectUnknown(captureValue, CAPTURE_KEYS, 'Native content snapshot capture');
|
|
1346
|
+
if (captureValue.provider !== 'git' && captureValue.provider !== 'physical-tree') {
|
|
1347
|
+
throw new Error('Native content snapshot capture provider is invalid');
|
|
1348
|
+
}
|
|
1349
|
+
const gitSelection = captureValue.gitSelection === undefined
|
|
1350
|
+
? undefined
|
|
1351
|
+
: parseGitSelectionEvidence(captureValue.gitSelection);
|
|
1352
|
+
const physicalSelection = captureValue.physicalSelection === undefined
|
|
1353
|
+
? undefined
|
|
1354
|
+
: parsePhysicalSelectionEvidence(captureValue.physicalSelection);
|
|
1355
|
+
let projection = null;
|
|
1356
|
+
if (captureValue.projection !== undefined) {
|
|
1357
|
+
const projectionValue = record(captureValue.projection, 'Native content snapshot projection');
|
|
1358
|
+
rejectUnknown(projectionValue, GIT_PROJECTION_KEYS, 'Native content snapshot projection');
|
|
1359
|
+
if (projectionValue.provider !== 'git') {
|
|
1360
|
+
throw new Error('Native content snapshot projection provider is invalid');
|
|
1361
|
+
}
|
|
1362
|
+
projection = {
|
|
1363
|
+
provider: 'git',
|
|
1364
|
+
...(projectionValue.selection === undefined
|
|
1365
|
+
? {}
|
|
1366
|
+
: { selection: parseGitSelectionEvidence(projectionValue.selection) }),
|
|
1367
|
+
};
|
|
1368
|
+
}
|
|
1369
|
+
if (captureValue.provider === 'git') {
|
|
1370
|
+
if (physicalSelection || projection) {
|
|
1371
|
+
throw new Error('Native Git capture cannot include physical or projection evidence');
|
|
1372
|
+
}
|
|
1373
|
+
capture = {
|
|
1374
|
+
provider: 'git',
|
|
1375
|
+
...(gitSelection ? { gitSelection } : {}),
|
|
1376
|
+
};
|
|
1377
|
+
}
|
|
1378
|
+
else {
|
|
1379
|
+
if (gitSelection) {
|
|
1380
|
+
throw new Error('Native physical-tree capture cannot include direct Git evidence');
|
|
1381
|
+
}
|
|
1382
|
+
if (physicalSelection && projection) {
|
|
1383
|
+
throw new Error('Native physical-tree capture cannot combine selection and projection');
|
|
1384
|
+
}
|
|
1385
|
+
capture = projection
|
|
1386
|
+
? { provider: 'physical-tree', projection }
|
|
1387
|
+
: {
|
|
1388
|
+
provider: 'physical-tree',
|
|
1389
|
+
...(physicalSelection ? { physicalSelection } : {}),
|
|
1390
|
+
};
|
|
1391
|
+
}
|
|
1392
|
+
}
|
|
1393
|
+
if (typeof manifest.createdAt !== 'string' || Number.isNaN(Date.parse(manifest.createdAt))) {
|
|
1394
|
+
throw new Error('Native content snapshot timestamp is invalid');
|
|
1395
|
+
}
|
|
1396
|
+
if (typeof manifest.complete !== 'boolean') {
|
|
1397
|
+
throw new Error('Native content snapshot complete flag is invalid');
|
|
1398
|
+
}
|
|
1399
|
+
const limitValue = record(manifest.limits, 'Native content snapshot limits');
|
|
1400
|
+
rejectUnknown(limitValue, LIMIT_KEYS, 'Native content snapshot limits');
|
|
1401
|
+
const limits = {
|
|
1402
|
+
maxFiles: positiveInteger(limitValue.maxFiles, 'Native snapshot maxFiles'),
|
|
1403
|
+
maxFileBytes: positiveInteger(limitValue.maxFileBytes, 'Native snapshot maxFileBytes'),
|
|
1404
|
+
maxTotalBytes: positiveInteger(limitValue.maxTotalBytes, 'Native snapshot maxTotalBytes'),
|
|
1405
|
+
maxManifestBytes: positiveInteger(limitValue.maxManifestBytes, 'Native snapshot maxManifestBytes'),
|
|
1406
|
+
};
|
|
1407
|
+
if (!Array.isArray(manifest.entries) || !Array.isArray(manifest.omitted)) {
|
|
1408
|
+
throw new Error('Native content snapshot entries and omissions must be arrays');
|
|
1409
|
+
}
|
|
1410
|
+
const entries = manifest.entries.map(parseEntry);
|
|
1411
|
+
const omitted = manifest.omitted.map(parseOmission);
|
|
1412
|
+
const omittedCount = nonNegativeInteger(manifest.omittedCount, 'Native content snapshot omittedCount');
|
|
1413
|
+
const omissionOverflow = manifest.omissionOverflow === undefined
|
|
1414
|
+
? undefined
|
|
1415
|
+
: parseOmissionOverflow(manifest.omissionOverflow);
|
|
1416
|
+
if (entries.length > limits.maxFiles) {
|
|
1417
|
+
throw new Error('Native content snapshot exceeds its file-count limit');
|
|
1418
|
+
}
|
|
1419
|
+
if (entries.some((entry) => entry.size > limits.maxFileBytes) ||
|
|
1420
|
+
entries.reduce((total, entry) => total + entry.size, 0) > limits.maxTotalBytes) {
|
|
1421
|
+
throw new Error('Native content snapshot exceeds its byte limits');
|
|
1422
|
+
}
|
|
1423
|
+
if (new Set(entries.map((entry) => entry.path)).size !== entries.length) {
|
|
1424
|
+
throw new Error('Native content snapshot contains duplicate paths');
|
|
1425
|
+
}
|
|
1426
|
+
if (omitted.length > MAX_RECORDED_OMISSIONS || omittedCount < omitted.length) {
|
|
1427
|
+
throw new Error('Native content snapshot omission count is invalid');
|
|
1428
|
+
}
|
|
1429
|
+
const overflowCount = omittedCount - omitted.length;
|
|
1430
|
+
if ((overflowCount === 0 && omissionOverflow) ||
|
|
1431
|
+
(overflowCount > 0 && omissionOverflow?.count !== overflowCount)) {
|
|
1432
|
+
throw new Error('Native content snapshot omission overflow is inconsistent');
|
|
1433
|
+
}
|
|
1434
|
+
if (manifest.complete !== (omittedCount === 0)) {
|
|
1435
|
+
throw new Error('Native content snapshot completeness is inconsistent');
|
|
1436
|
+
}
|
|
1437
|
+
const enumerationOmissions = omitted.filter((omission) => omission.reason === 'git-enumeration-limit');
|
|
1438
|
+
const selectionChangedOmissions = omitted.filter((omission) => omission.reason === 'git-selection-changed');
|
|
1439
|
+
for (const omission of [...enumerationOmissions, ...selectionChangedOmissions]) {
|
|
1440
|
+
if (omission.path !== '.' || omission.size !== null || omission.type !== 'directory') {
|
|
1441
|
+
throw new Error('Native Git selection omission must use the project-root sentinel');
|
|
1442
|
+
}
|
|
1443
|
+
}
|
|
1444
|
+
if (enumerationOmissions.length > 1 || selectionChangedOmissions.length > 1) {
|
|
1445
|
+
throw new Error('Native Git selection omissions must not be duplicated');
|
|
1446
|
+
}
|
|
1447
|
+
const gitSelection = capture?.provider === 'git' ? capture.gitSelection : capture?.projection?.selection;
|
|
1448
|
+
const evidenceHasOverflow = gitSelection?.status === 'overflow' || gitSelection?.status === 'overflow-and-changed';
|
|
1449
|
+
const evidenceHasSelectionChange = gitSelection?.status === 'changed' || gitSelection?.status === 'overflow-and-changed';
|
|
1450
|
+
if (evidenceHasOverflow !== (enumerationOmissions.length === 1)) {
|
|
1451
|
+
throw new Error('Native Git enumeration omission and selection evidence are inconsistent');
|
|
1452
|
+
}
|
|
1453
|
+
if (evidenceHasSelectionChange !== (selectionChangedOmissions.length === 1)) {
|
|
1454
|
+
throw new Error('Native Git selection-change omission and selection evidence are inconsistent');
|
|
1455
|
+
}
|
|
1456
|
+
const physicalEnumerationOmissions = omitted.filter((omission) => omission.reason === 'physical-enumeration-limit');
|
|
1457
|
+
const physicalChangedOmissions = omitted.filter((omission) => omission.reason === 'physical-selection-changed');
|
|
1458
|
+
for (const omission of [...physicalEnumerationOmissions, ...physicalChangedOmissions]) {
|
|
1459
|
+
if (omission.path !== '.' || omission.size !== null || omission.type !== 'directory') {
|
|
1460
|
+
throw new Error('Native physical selection omission must use the project-root sentinel');
|
|
1461
|
+
}
|
|
1462
|
+
}
|
|
1463
|
+
if (physicalEnumerationOmissions.length > 1 || physicalChangedOmissions.length > 1) {
|
|
1464
|
+
throw new Error('Native physical selection omissions must not be duplicated');
|
|
1465
|
+
}
|
|
1466
|
+
const physicalSelection = capture?.physicalSelection;
|
|
1467
|
+
const physicalEvidenceHasOverflow = physicalSelection?.status === 'overflow' ||
|
|
1468
|
+
physicalSelection?.status === 'overflow-and-changed';
|
|
1469
|
+
const physicalEvidenceHasChange = physicalSelection?.status === 'changed' || physicalSelection?.status === 'overflow-and-changed';
|
|
1470
|
+
if (physicalEvidenceHasOverflow !== (physicalEnumerationOmissions.length === 1)) {
|
|
1471
|
+
throw new Error('Native physical enumeration omission and evidence are inconsistent');
|
|
1472
|
+
}
|
|
1473
|
+
if (physicalEvidenceHasChange !== (physicalChangedOmissions.length === 1)) {
|
|
1474
|
+
throw new Error('Native physical selection-change omission and evidence are inconsistent');
|
|
1475
|
+
}
|
|
1476
|
+
const parsed = {
|
|
1477
|
+
schema: 'comet.native.content-snapshot.v1',
|
|
1478
|
+
origin: manifest.origin,
|
|
1479
|
+
...(capture ? { capture } : {}),
|
|
1480
|
+
createdAt: manifest.createdAt,
|
|
1481
|
+
complete: manifest.complete,
|
|
1482
|
+
limits,
|
|
1483
|
+
entries,
|
|
1484
|
+
omitted,
|
|
1485
|
+
omittedCount,
|
|
1486
|
+
...(omissionOverflow ? { omissionOverflow } : {}),
|
|
1487
|
+
};
|
|
1488
|
+
if (serializedManifestBytes(parsed) > limits.maxManifestBytes) {
|
|
1489
|
+
throw new Error('Native content snapshot exceeds its manifest byte limit');
|
|
1490
|
+
}
|
|
1491
|
+
return parsed;
|
|
1492
|
+
}
|
|
1493
|
+
/**
|
|
1494
|
+
* Projects a legacy physical-tree snapshot onto the same Git-owned universe used by new
|
|
1495
|
+
* snapshots. This keeps existing v1 baselines usable after ignored directories and nested
|
|
1496
|
+
* repositories stop participating in Native evidence.
|
|
1497
|
+
*/
|
|
1498
|
+
export async function filterNativeContentSnapshotToProjectScope(paths, value, options = {}) {
|
|
1499
|
+
const manifest = parseNativeContentSnapshotManifest(value);
|
|
1500
|
+
if (manifest.capture?.provider === 'git' ||
|
|
1501
|
+
manifest.capture?.projection ||
|
|
1502
|
+
manifest.capture?.physicalSelection) {
|
|
1503
|
+
return manifest;
|
|
1504
|
+
}
|
|
1505
|
+
const projectRoot = path.resolve(paths.projectRoot);
|
|
1506
|
+
const execution = createNativeSnapshotExecution(options);
|
|
1507
|
+
const gitSelectionLimits = resolveNativeGitSelectionLimits(options.gitSelectionLimits);
|
|
1508
|
+
const selection = await nativeGitSnapshotSelection(execution, projectRoot, gitSelectionLimits, options.gitSelectionHooks);
|
|
1509
|
+
if (selection === null)
|
|
1510
|
+
return manifest;
|
|
1511
|
+
const selected = new Set(selectionPaths(selection).filter((relative) => isSnapshotProjectRef(paths, relative)));
|
|
1512
|
+
const gitlinkPaths = [...selection.gitlinks].filter((relative) => selected.has(relative));
|
|
1513
|
+
const nestedRepositoryPaths = [...selection.nestedRepositories].filter((relative) => isSnapshotProjectRef(paths, relative));
|
|
1514
|
+
const atOrBeneath = (relative, boundaries) => boundaries.some((boundary) => relative === boundary || relative.startsWith(`${boundary}/`));
|
|
1515
|
+
const beneathGitlink = (relative) => atOrBeneath(relative, gitlinkPaths);
|
|
1516
|
+
const beneathNestedRepository = (relative) => atOrBeneath(relative, nestedRepositoryPaths);
|
|
1517
|
+
const ignoredCandidates = [
|
|
1518
|
+
...manifest.entries.map((entry) => entry.path),
|
|
1519
|
+
...manifest.omitted.flatMap((omission) => omission.type === 'directory' ? [omission.path, `${omission.path}/`] : [omission.path]),
|
|
1520
|
+
].filter((relative) => !selected.has(relative) && !beneathGitlink(relative) && !beneathNestedRepository(relative));
|
|
1521
|
+
const ignored = await runGitCheckIgnore(execution, projectRoot, ignoredCandidates);
|
|
1522
|
+
const entries = manifest.entries.filter((entry) => !beneathGitlink(entry.path) &&
|
|
1523
|
+
!beneathNestedRepository(entry.path) &&
|
|
1524
|
+
isSnapshotProjectRef(paths, entry.path) &&
|
|
1525
|
+
(selected.has(entry.path) || !ignored.has(entry.path)));
|
|
1526
|
+
await finalizeNativeGitSnapshotSelection(execution, projectRoot, gitSelectionLimits, selection, options.gitSelectionHooks?.outputChunkBytes);
|
|
1527
|
+
const boundaryOmissions = [...selection.omissions];
|
|
1528
|
+
for (const gitlink of gitlinkPaths) {
|
|
1529
|
+
// A physical-tree baseline recorded file contents below the old boundary, not the
|
|
1530
|
+
// submodule commit, and it did not record empty directories. Therefore no current
|
|
1531
|
+
// gitlink can be proven new or unchanged. Injecting today's HEAD would make the
|
|
1532
|
+
// historical baseline lie, so preserve every such boundary as explicit uncertainty.
|
|
1533
|
+
boundaryOmissions.push({
|
|
1534
|
+
path: gitlink,
|
|
1535
|
+
size: null,
|
|
1536
|
+
type: 'directory',
|
|
1537
|
+
reason: 'legacy-gitlink-boundary',
|
|
1538
|
+
});
|
|
1539
|
+
}
|
|
1540
|
+
entries.sort((left, right) => left.path.localeCompare(right.path, 'en'));
|
|
1541
|
+
const omitted = manifest.omitted.filter((omission) => {
|
|
1542
|
+
if (beneathGitlink(omission.path))
|
|
1543
|
+
return false;
|
|
1544
|
+
if (beneathNestedRepository(omission.path))
|
|
1545
|
+
return false;
|
|
1546
|
+
if (!isSnapshotProjectRef(paths, omission.path))
|
|
1547
|
+
return false;
|
|
1548
|
+
if (!ignored.has(omission.path))
|
|
1549
|
+
return true;
|
|
1550
|
+
return (selected.has(omission.path) ||
|
|
1551
|
+
[...selected].some((relative) => relative.startsWith(`${omission.path}/`)));
|
|
1552
|
+
});
|
|
1553
|
+
for (const omission of boundaryOmissions) {
|
|
1554
|
+
if (!omitted.some((current) => current.path === omission.path && current.reason === omission.reason)) {
|
|
1555
|
+
omitted.push(omission);
|
|
1556
|
+
}
|
|
1557
|
+
}
|
|
1558
|
+
omitted.sort((left, right) => left.path.localeCompare(right.path, 'en'));
|
|
1559
|
+
let overflowCount = manifest.omissionOverflow?.count ?? 0;
|
|
1560
|
+
let overflowHash = manifest.omissionOverflow?.hash ?? sha256Text('comet.native.snapshot-omission-overflow.v1');
|
|
1561
|
+
if (selection.overflow) {
|
|
1562
|
+
overflowCount += selection.overflow.count;
|
|
1563
|
+
overflowHash = foldSnapshotOverflowHash(overflowHash, 'git-selection', {
|
|
1564
|
+
source: 'git-selection',
|
|
1565
|
+
...selection.overflow,
|
|
1566
|
+
});
|
|
1567
|
+
}
|
|
1568
|
+
const foldOverflow = (omission) => {
|
|
1569
|
+
overflowCount += 1;
|
|
1570
|
+
overflowHash = foldSnapshotOverflowHash(overflowHash, 'omission', omission);
|
|
1571
|
+
};
|
|
1572
|
+
while (omitted.length > MAX_RECORDED_OMISSIONS) {
|
|
1573
|
+
const omission = takeLastCompactableOmission(omitted);
|
|
1574
|
+
if (omission === null) {
|
|
1575
|
+
throw new Error('Projected Native snapshot has too many required selection-integrity omissions');
|
|
1576
|
+
}
|
|
1577
|
+
foldOverflow(omission);
|
|
1578
|
+
}
|
|
1579
|
+
const buildProjection = () => ({
|
|
1580
|
+
...manifest,
|
|
1581
|
+
capture: {
|
|
1582
|
+
provider: 'physical-tree',
|
|
1583
|
+
projection: {
|
|
1584
|
+
provider: 'git',
|
|
1585
|
+
...(selection.evidence ? { selection: selection.evidence } : {}),
|
|
1586
|
+
},
|
|
1587
|
+
},
|
|
1588
|
+
complete: omitted.length + overflowCount === 0,
|
|
1589
|
+
entries,
|
|
1590
|
+
omitted,
|
|
1591
|
+
omittedCount: omitted.length + overflowCount,
|
|
1592
|
+
...(overflowCount > 0
|
|
1593
|
+
? {
|
|
1594
|
+
omissionOverflow: {
|
|
1595
|
+
ref: `native-snapshot://omitted-overflow/${overflowHash}`,
|
|
1596
|
+
hash: overflowHash,
|
|
1597
|
+
count: overflowCount,
|
|
1598
|
+
},
|
|
1599
|
+
}
|
|
1600
|
+
: {}),
|
|
1601
|
+
});
|
|
1602
|
+
let projected = buildProjection();
|
|
1603
|
+
while (serializedManifestBytes(projected) > manifest.limits.maxManifestBytes) {
|
|
1604
|
+
const omission = takeLastCompactableOmission(omitted);
|
|
1605
|
+
if (omission === null) {
|
|
1606
|
+
throw new Error('Projected Native snapshot cannot fit its manifest byte limit');
|
|
1607
|
+
}
|
|
1608
|
+
foldOverflow(omission);
|
|
1609
|
+
projected = buildProjection();
|
|
1610
|
+
}
|
|
1611
|
+
return parseNativeContentSnapshotManifest(projected);
|
|
1612
|
+
}
|
|
1613
|
+
export function nativeBaselineManifestFile(paths, name) {
|
|
1614
|
+
if (!CHANGE_NAME_PATTERN.test(name))
|
|
1615
|
+
throw new Error(`Invalid Native change name: ${name}`);
|
|
1616
|
+
const changeDir = path.join(paths.changesDir, name);
|
|
1617
|
+
if (!isInsidePath(paths.changesDir, changeDir))
|
|
1618
|
+
throw new Error('Native change path escaped');
|
|
1619
|
+
return path.join(changeDir, 'runtime', 'baseline-manifest.json');
|
|
1620
|
+
}
|
|
1621
|
+
export async function createNativeContentSnapshot(paths, options = {}) {
|
|
1622
|
+
const execution = createNativeSnapshotExecution(options);
|
|
1623
|
+
const limits = {
|
|
1624
|
+
maxFiles: options.limits?.maxFiles ?? DEFAULT_NATIVE_SNAPSHOT_LIMITS.maxFiles,
|
|
1625
|
+
maxFileBytes: options.limits?.maxFileBytes ?? DEFAULT_NATIVE_SNAPSHOT_LIMITS.maxFileBytes,
|
|
1626
|
+
maxTotalBytes: options.limits?.maxTotalBytes ?? DEFAULT_NATIVE_SNAPSHOT_LIMITS.maxTotalBytes,
|
|
1627
|
+
maxManifestBytes: options.limits?.maxManifestBytes ?? DEFAULT_NATIVE_SNAPSHOT_LIMITS.maxManifestBytes,
|
|
1628
|
+
};
|
|
1629
|
+
const gitSelectionLimits = resolveNativeGitSelectionLimits(options.gitSelectionLimits);
|
|
1630
|
+
const physicalSelectionLimits = resolveNativePhysicalSelectionLimits(options.physicalSelectionLimits);
|
|
1631
|
+
if (limits.maxFiles < 1 ||
|
|
1632
|
+
limits.maxFileBytes < 1 ||
|
|
1633
|
+
limits.maxTotalBytes < 1 ||
|
|
1634
|
+
limits.maxManifestBytes < 1) {
|
|
1635
|
+
throw new Error('Native snapshot limits must be positive');
|
|
1636
|
+
}
|
|
1637
|
+
const projectRoot = path.resolve(paths.projectRoot);
|
|
1638
|
+
const physicalProjectRoot = await fs.realpath(projectRoot);
|
|
1639
|
+
const nativeRoot = path.resolve(paths.nativeRoot);
|
|
1640
|
+
const physicalNativeRoot = await fs.realpath(nativeRoot);
|
|
1641
|
+
const configFile = path.resolve(paths.configFile);
|
|
1642
|
+
const selectionFile = path.join(projectRoot, '.comet', 'current-change.json');
|
|
1643
|
+
const denylist = normalizedDenylist(projectRoot, options.denylist ?? []);
|
|
1644
|
+
const entries = [];
|
|
1645
|
+
const omitted = [];
|
|
1646
|
+
const capturedEntryValidations = new Map();
|
|
1647
|
+
const capturedTrackedAbsences = new Map();
|
|
1648
|
+
let omittedCount = 0;
|
|
1649
|
+
let overflowCount = 0;
|
|
1650
|
+
let overflowHash = sha256Text('comet.native.snapshot-omission-overflow.v1');
|
|
1651
|
+
let totalBytes = 0;
|
|
1652
|
+
let notifiedFirstEntry = false;
|
|
1653
|
+
let physicalSelectionEvidence = null;
|
|
1654
|
+
const foldOverflow = (value) => {
|
|
1655
|
+
overflowCount += 1;
|
|
1656
|
+
overflowHash = foldSnapshotOverflowHash(overflowHash, 'omission', value);
|
|
1657
|
+
};
|
|
1658
|
+
const omit = (value) => {
|
|
1659
|
+
omittedCount += 1;
|
|
1660
|
+
if (omitted.length < MAX_RECORDED_OMISSIONS) {
|
|
1661
|
+
omitted.push(value);
|
|
1662
|
+
return;
|
|
1663
|
+
}
|
|
1664
|
+
if (isSelectionIntegrityOmission(value)) {
|
|
1665
|
+
const displaced = takeLastCompactableOmission(omitted);
|
|
1666
|
+
if (displaced === null) {
|
|
1667
|
+
throw new Error('Native snapshot has too many required selection-integrity omissions');
|
|
1668
|
+
}
|
|
1669
|
+
foldOverflow(displaced);
|
|
1670
|
+
omitted.push(value);
|
|
1671
|
+
return;
|
|
1672
|
+
}
|
|
1673
|
+
foldOverflow(value);
|
|
1674
|
+
};
|
|
1675
|
+
const foldGitSelectionOverflow = (value) => {
|
|
1676
|
+
omittedCount += value.count;
|
|
1677
|
+
overflowCount += value.count;
|
|
1678
|
+
overflowHash = foldSnapshotOverflowHash(overflowHash, 'git-selection', {
|
|
1679
|
+
source: 'git-selection',
|
|
1680
|
+
...value,
|
|
1681
|
+
});
|
|
1682
|
+
};
|
|
1683
|
+
const foldManifestEntryOverflow = (entry) => {
|
|
1684
|
+
overflowCount += 1;
|
|
1685
|
+
overflowHash = foldSnapshotOverflowHash(overflowHash, 'manifest-entry', {
|
|
1686
|
+
reason: 'manifest-size',
|
|
1687
|
+
entry,
|
|
1688
|
+
});
|
|
1689
|
+
};
|
|
1690
|
+
const recordCapturedEntry = async (entry, validation) => {
|
|
1691
|
+
entries.push(entry);
|
|
1692
|
+
totalBytes += entry.size;
|
|
1693
|
+
capturedEntryValidations.set(entry.path, validation);
|
|
1694
|
+
if (!notifiedFirstEntry) {
|
|
1695
|
+
notifiedFirstEntry = true;
|
|
1696
|
+
await options.gitSelectionHooks?.afterFirstEntryCaptured?.(entry.path);
|
|
1697
|
+
}
|
|
1698
|
+
};
|
|
1699
|
+
const invalidateCapturedEntry = (relative, omission) => {
|
|
1700
|
+
const index = entries.findIndex((entry) => entry.path === relative);
|
|
1701
|
+
if (index < 0)
|
|
1702
|
+
return;
|
|
1703
|
+
const [entry] = entries.splice(index, 1);
|
|
1704
|
+
totalBytes -= entry.size;
|
|
1705
|
+
capturedEntryValidations.delete(relative);
|
|
1706
|
+
omit(omission);
|
|
1707
|
+
};
|
|
1708
|
+
const captureFile = async (target, relative, before) => {
|
|
1709
|
+
if (!before.isFile() || before.isSymbolicLink())
|
|
1710
|
+
return;
|
|
1711
|
+
if (!nativeSnapshotExecutionHasBudget(execution))
|
|
1712
|
+
return;
|
|
1713
|
+
let realTarget;
|
|
1714
|
+
try {
|
|
1715
|
+
realTarget = await fs.realpath(target);
|
|
1716
|
+
}
|
|
1717
|
+
catch (error) {
|
|
1718
|
+
if (!nativeSnapshotExecutionHasBudget(execution))
|
|
1719
|
+
return;
|
|
1720
|
+
if (!isUnreadableError(error) && !isChangedDuringReadError(error))
|
|
1721
|
+
throw error;
|
|
1722
|
+
omit({
|
|
1723
|
+
path: relative,
|
|
1724
|
+
size: before.size,
|
|
1725
|
+
type: 'file',
|
|
1726
|
+
reason: isChangedDuringReadError(error) ? 'changed-during-read' : 'unreadable',
|
|
1727
|
+
});
|
|
1728
|
+
return;
|
|
1729
|
+
}
|
|
1730
|
+
if (!nativeSnapshotExecutionHasBudget(execution))
|
|
1731
|
+
return;
|
|
1732
|
+
if (!isInsidePath(physicalProjectRoot, realTarget) ||
|
|
1733
|
+
sameOrInside(physicalNativeRoot, realTarget)) {
|
|
1734
|
+
return;
|
|
1735
|
+
}
|
|
1736
|
+
if (entries.length >= limits.maxFiles) {
|
|
1737
|
+
omit({ path: relative, size: before.size, type: 'file', reason: 'file-count' });
|
|
1738
|
+
return;
|
|
1739
|
+
}
|
|
1740
|
+
if (before.size > limits.maxFileBytes) {
|
|
1741
|
+
omit({ path: relative, size: before.size, type: 'file', reason: 'file-size' });
|
|
1742
|
+
return;
|
|
1743
|
+
}
|
|
1744
|
+
if (totalBytes + before.size > limits.maxTotalBytes) {
|
|
1745
|
+
omit({ path: relative, size: before.size, type: 'file', reason: 'total-size' });
|
|
1746
|
+
return;
|
|
1747
|
+
}
|
|
1748
|
+
let boundedHash;
|
|
1749
|
+
let after;
|
|
1750
|
+
let afterRealTarget;
|
|
1751
|
+
try {
|
|
1752
|
+
boundedHash = await sha256FileBounded(realTarget, before.size, before, execution);
|
|
1753
|
+
if (boundedHash.status === 'budget-exhausted' ||
|
|
1754
|
+
!nativeSnapshotExecutionHasBudget(execution)) {
|
|
1755
|
+
return;
|
|
1756
|
+
}
|
|
1757
|
+
if (boundedHash.status === 'changed') {
|
|
1758
|
+
omit({ path: relative, size: null, type: 'file', reason: 'changed-during-read' });
|
|
1759
|
+
return;
|
|
1760
|
+
}
|
|
1761
|
+
if (!nativeSnapshotExecutionHasBudget(execution))
|
|
1762
|
+
return;
|
|
1763
|
+
afterRealTarget = await fs.realpath(target);
|
|
1764
|
+
if (!nativeSnapshotExecutionHasBudget(execution))
|
|
1765
|
+
return;
|
|
1766
|
+
after = await fs.lstat(target);
|
|
1767
|
+
}
|
|
1768
|
+
catch (error) {
|
|
1769
|
+
if (!nativeSnapshotExecutionHasBudget(execution))
|
|
1770
|
+
return;
|
|
1771
|
+
if (!isUnreadableError(error) && !isChangedDuringReadError(error))
|
|
1772
|
+
throw error;
|
|
1773
|
+
omit({
|
|
1774
|
+
path: relative,
|
|
1775
|
+
size: before.size,
|
|
1776
|
+
type: 'file',
|
|
1777
|
+
reason: isChangedDuringReadError(error) ? 'changed-during-read' : 'unreadable',
|
|
1778
|
+
});
|
|
1779
|
+
return;
|
|
1780
|
+
}
|
|
1781
|
+
if (!nativeSnapshotExecutionHasBudget(execution))
|
|
1782
|
+
return;
|
|
1783
|
+
if (boundedHash.bytes !== before.size ||
|
|
1784
|
+
afterRealTarget !== realTarget ||
|
|
1785
|
+
!sameFileIdentity(before, boundedHash.finalStat) ||
|
|
1786
|
+
!sameFileIdentity(boundedHash.finalStat, after) ||
|
|
1787
|
+
!after.isFile() ||
|
|
1788
|
+
after.isSymbolicLink() ||
|
|
1789
|
+
after.size !== before.size ||
|
|
1790
|
+
after.mtimeMs !== before.mtimeMs ||
|
|
1791
|
+
after.ctimeMs !== before.ctimeMs) {
|
|
1792
|
+
omit({
|
|
1793
|
+
path: relative,
|
|
1794
|
+
size: after.isFile() ? after.size : null,
|
|
1795
|
+
type: after.isFile() ? 'file' : 'other',
|
|
1796
|
+
reason: 'changed-during-read',
|
|
1797
|
+
});
|
|
1798
|
+
return;
|
|
1799
|
+
}
|
|
1800
|
+
if (!nativeSnapshotExecutionHasBudget(execution))
|
|
1801
|
+
return;
|
|
1802
|
+
await recordCapturedEntry({ path: relative, hash: boundedHash.hash, size: after.size, type: 'file' }, { kind: 'file', target, realTarget, stat: after });
|
|
1803
|
+
};
|
|
1804
|
+
const captureSymbolicLink = async (target, relative, before) => {
|
|
1805
|
+
if (!nativeSnapshotExecutionHasBudget(execution))
|
|
1806
|
+
return;
|
|
1807
|
+
let firstTarget;
|
|
1808
|
+
let secondTarget;
|
|
1809
|
+
let after;
|
|
1810
|
+
try {
|
|
1811
|
+
firstTarget = await fs.readlink(target, { encoding: 'buffer' });
|
|
1812
|
+
if (!nativeSnapshotExecutionHasBudget(execution))
|
|
1813
|
+
return;
|
|
1814
|
+
after = await fs.lstat(target);
|
|
1815
|
+
if (!nativeSnapshotExecutionHasBudget(execution))
|
|
1816
|
+
return;
|
|
1817
|
+
secondTarget = await fs.readlink(target, { encoding: 'buffer' });
|
|
1818
|
+
}
|
|
1819
|
+
catch (error) {
|
|
1820
|
+
if (!nativeSnapshotExecutionHasBudget(execution))
|
|
1821
|
+
return;
|
|
1822
|
+
if (!isUnreadableError(error) && !isChangedDuringReadError(error))
|
|
1823
|
+
throw error;
|
|
1824
|
+
omit({
|
|
1825
|
+
path: relative,
|
|
1826
|
+
size: null,
|
|
1827
|
+
type: 'other',
|
|
1828
|
+
reason: isChangedDuringReadError(error) ? 'changed-during-read' : 'unreadable',
|
|
1829
|
+
});
|
|
1830
|
+
return;
|
|
1831
|
+
}
|
|
1832
|
+
if (!nativeSnapshotExecutionHasBudget(execution))
|
|
1833
|
+
return;
|
|
1834
|
+
if (!after.isSymbolicLink() ||
|
|
1835
|
+
!sameFileIdentity(before, after) ||
|
|
1836
|
+
after.mtimeMs !== before.mtimeMs ||
|
|
1837
|
+
after.ctimeMs !== before.ctimeMs ||
|
|
1838
|
+
!firstTarget.equals(secondTarget)) {
|
|
1839
|
+
omit({ path: relative, size: null, type: 'other', reason: 'changed-during-read' });
|
|
1840
|
+
return;
|
|
1841
|
+
}
|
|
1842
|
+
const size = firstTarget.byteLength;
|
|
1843
|
+
if (entries.length >= limits.maxFiles) {
|
|
1844
|
+
omit({ path: relative, size, type: 'other', reason: 'file-count' });
|
|
1845
|
+
return;
|
|
1846
|
+
}
|
|
1847
|
+
if (size > limits.maxFileBytes) {
|
|
1848
|
+
omit({ path: relative, size, type: 'other', reason: 'file-size' });
|
|
1849
|
+
return;
|
|
1850
|
+
}
|
|
1851
|
+
if (totalBytes + size > limits.maxTotalBytes) {
|
|
1852
|
+
omit({ path: relative, size, type: 'other', reason: 'total-size' });
|
|
1853
|
+
return;
|
|
1854
|
+
}
|
|
1855
|
+
const hash = createHash('sha256').update('symlink\0').update(firstTarget).digest('hex');
|
|
1856
|
+
if (!nativeSnapshotExecutionHasBudget(execution))
|
|
1857
|
+
return;
|
|
1858
|
+
await recordCapturedEntry({ path: relative, hash, size, type: 'file' }, { kind: 'symlink', target, rawTarget: firstTarget, stat: after });
|
|
1859
|
+
};
|
|
1860
|
+
const revalidateCapturedEntries = async () => {
|
|
1861
|
+
for (const [relative, validation] of [...capturedEntryValidations]) {
|
|
1862
|
+
if (!nativeSnapshotExecutionHasBudget(execution))
|
|
1863
|
+
return;
|
|
1864
|
+
if (validation.kind === 'file') {
|
|
1865
|
+
let realTarget;
|
|
1866
|
+
let stat;
|
|
1867
|
+
try {
|
|
1868
|
+
realTarget = await fs.realpath(validation.target);
|
|
1869
|
+
if (!nativeSnapshotExecutionHasBudget(execution))
|
|
1870
|
+
return;
|
|
1871
|
+
stat = await fs.lstat(validation.target);
|
|
1872
|
+
}
|
|
1873
|
+
catch (error) {
|
|
1874
|
+
if (!nativeSnapshotExecutionHasBudget(execution))
|
|
1875
|
+
return;
|
|
1876
|
+
if (!isUnreadableError(error) && !isChangedDuringReadError(error))
|
|
1877
|
+
throw error;
|
|
1878
|
+
invalidateCapturedEntry(relative, {
|
|
1879
|
+
path: relative,
|
|
1880
|
+
size: null,
|
|
1881
|
+
type: 'file',
|
|
1882
|
+
reason: isUnreadableError(error) ? 'unreadable' : 'changed-during-read',
|
|
1883
|
+
});
|
|
1884
|
+
continue;
|
|
1885
|
+
}
|
|
1886
|
+
if (!nativeSnapshotExecutionHasBudget(execution))
|
|
1887
|
+
return;
|
|
1888
|
+
if (realTarget !== validation.realTarget ||
|
|
1889
|
+
!stat.isFile() ||
|
|
1890
|
+
stat.isSymbolicLink() ||
|
|
1891
|
+
!sameFileIdentity(validation.stat, stat) ||
|
|
1892
|
+
stat.size !== validation.stat.size ||
|
|
1893
|
+
stat.mtimeMs !== validation.stat.mtimeMs ||
|
|
1894
|
+
stat.ctimeMs !== validation.stat.ctimeMs) {
|
|
1895
|
+
invalidateCapturedEntry(relative, {
|
|
1896
|
+
path: relative,
|
|
1897
|
+
size: stat.isFile() ? stat.size : null,
|
|
1898
|
+
type: stat.isFile() ? 'file' : 'other',
|
|
1899
|
+
reason: 'changed-during-read',
|
|
1900
|
+
});
|
|
1901
|
+
}
|
|
1902
|
+
continue;
|
|
1903
|
+
}
|
|
1904
|
+
if (validation.kind === 'symlink') {
|
|
1905
|
+
let stat;
|
|
1906
|
+
let rawTarget;
|
|
1907
|
+
try {
|
|
1908
|
+
stat = await fs.lstat(validation.target);
|
|
1909
|
+
if (!nativeSnapshotExecutionHasBudget(execution))
|
|
1910
|
+
return;
|
|
1911
|
+
rawTarget = await fs.readlink(validation.target, { encoding: 'buffer' });
|
|
1912
|
+
}
|
|
1913
|
+
catch (error) {
|
|
1914
|
+
if (!nativeSnapshotExecutionHasBudget(execution))
|
|
1915
|
+
return;
|
|
1916
|
+
if (!isUnreadableError(error) && !isChangedDuringReadError(error))
|
|
1917
|
+
throw error;
|
|
1918
|
+
invalidateCapturedEntry(relative, {
|
|
1919
|
+
path: relative,
|
|
1920
|
+
size: null,
|
|
1921
|
+
type: 'other',
|
|
1922
|
+
reason: isUnreadableError(error) ? 'unreadable' : 'changed-during-read',
|
|
1923
|
+
});
|
|
1924
|
+
continue;
|
|
1925
|
+
}
|
|
1926
|
+
if (!nativeSnapshotExecutionHasBudget(execution))
|
|
1927
|
+
return;
|
|
1928
|
+
if (!stat.isSymbolicLink() ||
|
|
1929
|
+
!sameFileIdentity(validation.stat, stat) ||
|
|
1930
|
+
stat.mtimeMs !== validation.stat.mtimeMs ||
|
|
1931
|
+
stat.ctimeMs !== validation.stat.ctimeMs ||
|
|
1932
|
+
!validation.rawTarget.equals(rawTarget)) {
|
|
1933
|
+
invalidateCapturedEntry(relative, {
|
|
1934
|
+
path: relative,
|
|
1935
|
+
size: null,
|
|
1936
|
+
type: 'other',
|
|
1937
|
+
reason: 'changed-during-read',
|
|
1938
|
+
});
|
|
1939
|
+
}
|
|
1940
|
+
continue;
|
|
1941
|
+
}
|
|
1942
|
+
let realTarget;
|
|
1943
|
+
let stat;
|
|
1944
|
+
try {
|
|
1945
|
+
realTarget = await fs.realpath(validation.target);
|
|
1946
|
+
if (!nativeSnapshotExecutionHasBudget(execution))
|
|
1947
|
+
return;
|
|
1948
|
+
stat = await fs.lstat(validation.target);
|
|
1949
|
+
}
|
|
1950
|
+
catch {
|
|
1951
|
+
if (!nativeSnapshotExecutionHasBudget(execution))
|
|
1952
|
+
return;
|
|
1953
|
+
invalidateCapturedEntry(relative, {
|
|
1954
|
+
path: relative,
|
|
1955
|
+
size: null,
|
|
1956
|
+
type: 'directory',
|
|
1957
|
+
reason: 'gitlink-unavailable',
|
|
1958
|
+
});
|
|
1959
|
+
continue;
|
|
1960
|
+
}
|
|
1961
|
+
if (!nativeSnapshotExecutionHasBudget(execution))
|
|
1962
|
+
return;
|
|
1963
|
+
const validBoundary = realTarget === validation.realTarget &&
|
|
1964
|
+
stat.isDirectory() &&
|
|
1965
|
+
!stat.isSymbolicLink() &&
|
|
1966
|
+
sameFileIdentity(validation.stat, stat) &&
|
|
1967
|
+
isInsidePath(physicalProjectRoot, realTarget) &&
|
|
1968
|
+
!sameOrInside(physicalNativeRoot, realTarget);
|
|
1969
|
+
if (!validBoundary) {
|
|
1970
|
+
invalidateCapturedEntry(relative, {
|
|
1971
|
+
path: relative,
|
|
1972
|
+
size: null,
|
|
1973
|
+
type: 'directory',
|
|
1974
|
+
reason: 'gitlink-changed',
|
|
1975
|
+
});
|
|
1976
|
+
continue;
|
|
1977
|
+
}
|
|
1978
|
+
let workingTree;
|
|
1979
|
+
try {
|
|
1980
|
+
if (!nativeSnapshotExecutionHasBudget(execution))
|
|
1981
|
+
return;
|
|
1982
|
+
workingTree = await inspectGitlinkWorkingTree(execution, realTarget);
|
|
1983
|
+
}
|
|
1984
|
+
catch (error) {
|
|
1985
|
+
if (isNativeGitSnapshotTimeout(error))
|
|
1986
|
+
throw error;
|
|
1987
|
+
invalidateCapturedEntry(relative, {
|
|
1988
|
+
path: relative,
|
|
1989
|
+
size: null,
|
|
1990
|
+
type: 'directory',
|
|
1991
|
+
reason: 'gitlink-unavailable',
|
|
1992
|
+
});
|
|
1993
|
+
continue;
|
|
1994
|
+
}
|
|
1995
|
+
if (!nativeSnapshotExecutionHasBudget(execution))
|
|
1996
|
+
return;
|
|
1997
|
+
if (workingTree.dirty) {
|
|
1998
|
+
invalidateCapturedEntry(relative, {
|
|
1999
|
+
path: relative,
|
|
2000
|
+
size: null,
|
|
2001
|
+
type: 'directory',
|
|
2002
|
+
reason: 'gitlink-dirty',
|
|
2003
|
+
});
|
|
2004
|
+
}
|
|
2005
|
+
else if (workingTree.hash !== validation.hash) {
|
|
2006
|
+
invalidateCapturedEntry(relative, {
|
|
2007
|
+
path: relative,
|
|
2008
|
+
size: null,
|
|
2009
|
+
type: 'directory',
|
|
2010
|
+
reason: 'gitlink-changed',
|
|
2011
|
+
});
|
|
2012
|
+
}
|
|
2013
|
+
}
|
|
2014
|
+
for (const [relative, target] of capturedTrackedAbsences) {
|
|
2015
|
+
if (!nativeSnapshotExecutionHasBudget(execution))
|
|
2016
|
+
return;
|
|
2017
|
+
try {
|
|
2018
|
+
const stat = await fs.lstat(target);
|
|
2019
|
+
if (!nativeSnapshotExecutionHasBudget(execution))
|
|
2020
|
+
return;
|
|
2021
|
+
omit({
|
|
2022
|
+
path: relative,
|
|
2023
|
+
size: stat.isFile() ? stat.size : null,
|
|
2024
|
+
type: stat.isFile() ? 'file' : stat.isDirectory() ? 'directory' : 'other',
|
|
2025
|
+
reason: 'changed-during-read',
|
|
2026
|
+
});
|
|
2027
|
+
}
|
|
2028
|
+
catch (error) {
|
|
2029
|
+
if (!nativeSnapshotExecutionHasBudget(execution))
|
|
2030
|
+
return;
|
|
2031
|
+
if (isChangedDuringReadError(error))
|
|
2032
|
+
continue;
|
|
2033
|
+
if (!isUnreadableError(error))
|
|
2034
|
+
throw error;
|
|
2035
|
+
omit({ path: relative, size: null, type: 'file', reason: 'unreadable' });
|
|
2036
|
+
}
|
|
2037
|
+
}
|
|
2038
|
+
};
|
|
2039
|
+
const gitSelection = await nativeGitSnapshotSelection(execution, projectRoot, gitSelectionLimits, options.gitSelectionHooks);
|
|
2040
|
+
if (gitSelection === null) {
|
|
2041
|
+
const before = await nativePhysicalSnapshotSelection({
|
|
2042
|
+
execution,
|
|
2043
|
+
paths,
|
|
2044
|
+
physicalProjectRoot,
|
|
2045
|
+
physicalNativeRoot,
|
|
2046
|
+
denylist,
|
|
2047
|
+
limits: physicalSelectionLimits,
|
|
2048
|
+
hooks: options.physicalSelectionHooks,
|
|
2049
|
+
});
|
|
2050
|
+
await options.physicalSelectionHooks?.afterInitialSelection?.();
|
|
2051
|
+
for (const record of before.records) {
|
|
2052
|
+
if (record.type !== 'file' && record.type !== 'symlink')
|
|
2053
|
+
continue;
|
|
2054
|
+
if (remainingNativeSnapshotTime(execution) < 1)
|
|
2055
|
+
break;
|
|
2056
|
+
const target = path.resolve(projectRoot, ...record.path.split('/'));
|
|
2057
|
+
let stat;
|
|
2058
|
+
try {
|
|
2059
|
+
stat = await fs.lstat(target);
|
|
2060
|
+
}
|
|
2061
|
+
catch (error) {
|
|
2062
|
+
if (!nativeSnapshotExecutionHasBudget(execution))
|
|
2063
|
+
break;
|
|
2064
|
+
if (!isUnreadableError(error) && !isChangedDuringReadError(error))
|
|
2065
|
+
throw error;
|
|
2066
|
+
omit({
|
|
2067
|
+
path: record.path,
|
|
2068
|
+
size: null,
|
|
2069
|
+
type: 'file',
|
|
2070
|
+
reason: isChangedDuringReadError(error) ? 'changed-during-read' : 'unreadable',
|
|
2071
|
+
});
|
|
2072
|
+
continue;
|
|
2073
|
+
}
|
|
2074
|
+
if (remainingNativeSnapshotTime(execution) < 1)
|
|
2075
|
+
break;
|
|
2076
|
+
if (record.type === 'symlink' && stat.isSymbolicLink()) {
|
|
2077
|
+
await captureSymbolicLink(target, record.path, stat);
|
|
2078
|
+
if (remainingNativeSnapshotTime(execution) < 1)
|
|
2079
|
+
break;
|
|
2080
|
+
continue;
|
|
2081
|
+
}
|
|
2082
|
+
if (record.type !== 'file' || !stat.isFile() || stat.isSymbolicLink()) {
|
|
2083
|
+
omit({
|
|
2084
|
+
path: record.path,
|
|
2085
|
+
size: stat.isFile() ? stat.size : null,
|
|
2086
|
+
type: stat.isDirectory() ? 'directory' : stat.isFile() ? 'file' : 'other',
|
|
2087
|
+
reason: 'changed-during-read',
|
|
2088
|
+
});
|
|
2089
|
+
continue;
|
|
2090
|
+
}
|
|
2091
|
+
await captureFile(target, record.path, stat);
|
|
2092
|
+
if (remainingNativeSnapshotTime(execution) < 1)
|
|
2093
|
+
break;
|
|
2094
|
+
}
|
|
2095
|
+
if (remainingNativeSnapshotTime(execution) >= 1)
|
|
2096
|
+
await revalidateCapturedEntries();
|
|
2097
|
+
const after = await nativePhysicalSnapshotSelection({
|
|
2098
|
+
execution,
|
|
2099
|
+
paths,
|
|
2100
|
+
physicalProjectRoot,
|
|
2101
|
+
physicalNativeRoot,
|
|
2102
|
+
denylist,
|
|
2103
|
+
limits: physicalSelectionLimits,
|
|
2104
|
+
});
|
|
2105
|
+
const finalized = finalizeNativePhysicalSelection(before.evidence, after.evidence);
|
|
2106
|
+
physicalSelectionEvidence = finalized.evidence;
|
|
2107
|
+
for (const omission of [...before.omissions, ...after.omissions, ...finalized.omissions]) {
|
|
2108
|
+
if (omitted.some((current) => current.path === omission.path && current.reason === omission.reason)) {
|
|
2109
|
+
continue;
|
|
2110
|
+
}
|
|
2111
|
+
omit(omission);
|
|
2112
|
+
}
|
|
2113
|
+
}
|
|
2114
|
+
else {
|
|
2115
|
+
await options.gitSelectionHooks?.afterInitialSelection?.();
|
|
2116
|
+
for (const relative of selectionPaths(gitSelection)) {
|
|
2117
|
+
if (!isSnapshotProjectRef(paths, relative))
|
|
2118
|
+
continue;
|
|
2119
|
+
const target = path.resolve(projectRoot, ...relative.split('/'));
|
|
2120
|
+
if (target === configFile ||
|
|
2121
|
+
target === selectionFile ||
|
|
2122
|
+
denylist.some((denied) => sameOrInside(denied, target))) {
|
|
2123
|
+
continue;
|
|
2124
|
+
}
|
|
2125
|
+
if (gitSelection.gitlinks.has(relative)) {
|
|
2126
|
+
if (entries.length >= limits.maxFiles) {
|
|
2127
|
+
omit({ path: relative, size: 0, type: 'file', reason: 'file-count' });
|
|
2128
|
+
continue;
|
|
2129
|
+
}
|
|
2130
|
+
let realGitlink;
|
|
2131
|
+
let gitlinkStat;
|
|
2132
|
+
try {
|
|
2133
|
+
[realGitlink, gitlinkStat] = await Promise.all([fs.realpath(target), fs.lstat(target)]);
|
|
2134
|
+
}
|
|
2135
|
+
catch {
|
|
2136
|
+
omit({
|
|
2137
|
+
path: relative,
|
|
2138
|
+
size: null,
|
|
2139
|
+
type: 'directory',
|
|
2140
|
+
reason: 'gitlink-unavailable',
|
|
2141
|
+
});
|
|
2142
|
+
continue;
|
|
2143
|
+
}
|
|
2144
|
+
if (gitlinkStat.isSymbolicLink() ||
|
|
2145
|
+
!gitlinkStat.isDirectory() ||
|
|
2146
|
+
!isInsidePath(physicalProjectRoot, realGitlink) ||
|
|
2147
|
+
sameOrInside(physicalNativeRoot, realGitlink)) {
|
|
2148
|
+
omit({
|
|
2149
|
+
path: relative,
|
|
2150
|
+
size: null,
|
|
2151
|
+
type: 'directory',
|
|
2152
|
+
reason: 'gitlink-unavailable',
|
|
2153
|
+
});
|
|
2154
|
+
continue;
|
|
2155
|
+
}
|
|
2156
|
+
try {
|
|
2157
|
+
const workingTree = await inspectGitlinkWorkingTree(execution, realGitlink);
|
|
2158
|
+
if (workingTree.dirty) {
|
|
2159
|
+
omit({
|
|
2160
|
+
path: relative,
|
|
2161
|
+
size: null,
|
|
2162
|
+
type: 'directory',
|
|
2163
|
+
reason: 'gitlink-dirty',
|
|
2164
|
+
});
|
|
2165
|
+
continue;
|
|
2166
|
+
}
|
|
2167
|
+
await recordCapturedEntry({
|
|
2168
|
+
path: relative,
|
|
2169
|
+
hash: workingTree.hash,
|
|
2170
|
+
size: 0,
|
|
2171
|
+
type: 'file',
|
|
2172
|
+
}, {
|
|
2173
|
+
kind: 'gitlink',
|
|
2174
|
+
target,
|
|
2175
|
+
realTarget: realGitlink,
|
|
2176
|
+
stat: gitlinkStat,
|
|
2177
|
+
hash: workingTree.hash,
|
|
2178
|
+
});
|
|
2179
|
+
}
|
|
2180
|
+
catch (error) {
|
|
2181
|
+
if (isNativeGitSnapshotTimeout(error))
|
|
2182
|
+
throw error;
|
|
2183
|
+
// The index knows this is a gitlink, but an uninitialized, unreadable, or
|
|
2184
|
+
// concurrently changing checkout has no trustworthy working-tree pointer.
|
|
2185
|
+
omit({
|
|
2186
|
+
path: relative,
|
|
2187
|
+
size: null,
|
|
2188
|
+
type: 'directory',
|
|
2189
|
+
reason: 'gitlink-unavailable',
|
|
2190
|
+
});
|
|
2191
|
+
}
|
|
2192
|
+
continue;
|
|
2193
|
+
}
|
|
2194
|
+
let before;
|
|
2195
|
+
try {
|
|
2196
|
+
before = await fs.lstat(target);
|
|
2197
|
+
}
|
|
2198
|
+
catch (error) {
|
|
2199
|
+
if (isChangedDuringReadError(error) && gitSelection.tracked.has(relative)) {
|
|
2200
|
+
capturedTrackedAbsences.set(relative, target);
|
|
2201
|
+
continue;
|
|
2202
|
+
}
|
|
2203
|
+
if (!isUnreadableError(error) && !isChangedDuringReadError(error))
|
|
2204
|
+
throw error;
|
|
2205
|
+
omit({
|
|
2206
|
+
path: relative,
|
|
2207
|
+
size: null,
|
|
2208
|
+
type: 'file',
|
|
2209
|
+
reason: isChangedDuringReadError(error) ? 'changed-during-read' : 'unreadable',
|
|
2210
|
+
});
|
|
2211
|
+
continue;
|
|
2212
|
+
}
|
|
2213
|
+
if (before.isSymbolicLink()) {
|
|
2214
|
+
await captureSymbolicLink(target, relative, before);
|
|
2215
|
+
continue;
|
|
2216
|
+
}
|
|
2217
|
+
if (!before.isFile()) {
|
|
2218
|
+
if (!gitSelection.nestedRepositories.has(relative)) {
|
|
2219
|
+
omit({ path: relative, size: null, type: 'other', reason: 'changed-during-read' });
|
|
2220
|
+
}
|
|
2221
|
+
continue;
|
|
2222
|
+
}
|
|
2223
|
+
await captureFile(target, relative, before);
|
|
2224
|
+
}
|
|
2225
|
+
await revalidateCapturedEntries();
|
|
2226
|
+
await finalizeNativeGitSnapshotSelection(execution, projectRoot, gitSelectionLimits, gitSelection, options.gitSelectionHooks?.outputChunkBytes);
|
|
2227
|
+
for (const omission of gitSelection.omissions)
|
|
2228
|
+
omit(omission);
|
|
2229
|
+
if (gitSelection.overflow)
|
|
2230
|
+
foldGitSelectionOverflow(gitSelection.overflow);
|
|
2231
|
+
}
|
|
2232
|
+
entries.sort((left, right) => left.path.localeCompare(right.path, 'en'));
|
|
2233
|
+
omitted.sort((left, right) => left.path.localeCompare(right.path, 'en'));
|
|
2234
|
+
const capture = gitSelection === null
|
|
2235
|
+
? {
|
|
2236
|
+
provider: 'physical-tree',
|
|
2237
|
+
...(physicalSelectionEvidence ? { physicalSelection: physicalSelectionEvidence } : {}),
|
|
2238
|
+
}
|
|
2239
|
+
: {
|
|
2240
|
+
provider: 'git',
|
|
2241
|
+
...(gitSelection.evidence ? { gitSelection: gitSelection.evidence } : {}),
|
|
2242
|
+
};
|
|
2243
|
+
const buildManifest = () => ({
|
|
2244
|
+
schema: 'comet.native.content-snapshot.v1',
|
|
2245
|
+
origin: options.origin ?? 'explicit',
|
|
2246
|
+
capture,
|
|
2247
|
+
createdAt: (options.now ?? new Date()).toISOString(),
|
|
2248
|
+
complete: omittedCount === 0,
|
|
2249
|
+
limits,
|
|
2250
|
+
entries,
|
|
2251
|
+
omitted,
|
|
2252
|
+
omittedCount,
|
|
2253
|
+
...(overflowCount > 0
|
|
2254
|
+
? {
|
|
2255
|
+
omissionOverflow: {
|
|
2256
|
+
ref: `native-snapshot://omitted-overflow/${overflowHash}`,
|
|
2257
|
+
hash: overflowHash,
|
|
2258
|
+
count: overflowCount,
|
|
2259
|
+
},
|
|
2260
|
+
}
|
|
2261
|
+
: {}),
|
|
2262
|
+
});
|
|
2263
|
+
let manifest = buildManifest();
|
|
2264
|
+
while (serializedManifestBytes(manifest) > limits.maxManifestBytes) {
|
|
2265
|
+
const compactableOmissionCount = omitted.filter((omission) => !isSelectionIntegrityOmission(omission)).length;
|
|
2266
|
+
if (compactableOmissionCount > 0) {
|
|
2267
|
+
const removeCount = Math.max(1, Math.ceil(omitted.length / 4));
|
|
2268
|
+
for (let removed = 0; removed < removeCount; removed += 1) {
|
|
2269
|
+
const omission = takeLastCompactableOmission(omitted);
|
|
2270
|
+
if (omission === null)
|
|
2271
|
+
break;
|
|
2272
|
+
foldOverflow(omission);
|
|
2273
|
+
}
|
|
2274
|
+
}
|
|
2275
|
+
else if (entries.length > 0) {
|
|
2276
|
+
const removeCount = Math.max(1, Math.ceil(entries.length / 4));
|
|
2277
|
+
for (const entry of entries.splice(-removeCount)) {
|
|
2278
|
+
omittedCount += 1;
|
|
2279
|
+
foldManifestEntryOverflow(entry);
|
|
2280
|
+
}
|
|
2281
|
+
}
|
|
2282
|
+
else {
|
|
2283
|
+
throw new Error('Native snapshot manifest byte limit is too small for its metadata');
|
|
2284
|
+
}
|
|
2285
|
+
manifest = buildManifest();
|
|
2286
|
+
}
|
|
2287
|
+
return manifest;
|
|
2288
|
+
}
|
|
2289
|
+
export async function writeNativeBaselineManifest(paths, name, manifest) {
|
|
2290
|
+
const file = nativeBaselineManifestFile(paths, name);
|
|
2291
|
+
await resolveContainedNativePath(paths.nativeRoot, file);
|
|
2292
|
+
await atomicWriteJson(file, parseNativeContentSnapshotManifest(manifest));
|
|
2293
|
+
}
|
|
2294
|
+
export async function readNativeBaselineManifest(paths, name) {
|
|
2295
|
+
const file = nativeBaselineManifestFile(paths, name);
|
|
2296
|
+
await resolveContainedNativePath(paths.nativeRoot, file);
|
|
2297
|
+
try {
|
|
2298
|
+
const source = await readNativeProtectedTextFile({
|
|
2299
|
+
root: paths.nativeRoot,
|
|
2300
|
+
file,
|
|
2301
|
+
maxBytes: NATIVE_SNAPSHOT_MANIFEST_HARD_MAX_BYTES,
|
|
2302
|
+
label: 'Native baseline snapshot manifest',
|
|
2303
|
+
});
|
|
2304
|
+
return parseNativeContentSnapshotManifest(JSON.parse(source.text));
|
|
2305
|
+
}
|
|
2306
|
+
catch (error) {
|
|
2307
|
+
if (error.code === 'ENOENT')
|
|
2308
|
+
return null;
|
|
2309
|
+
throw error;
|
|
2310
|
+
}
|
|
2311
|
+
}
|
|
2312
|
+
//# sourceMappingURL=native-snapshot.js.map
|