@fitlab-ai/agent-infra 0.9.2 → 0.9.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +12 -9
- package/README.zh-CN.md +12 -9
- package/bin/cli.ts +12 -0
- package/dist/bin/cli.js +14 -0
- package/dist/bin/internal-cli.js +134 -108
- package/dist/lib/agent-clients/adapter.js +137 -4
- package/dist/lib/agent-clients/adapters/antigravity-cli.js +64 -0
- package/dist/lib/{sandbox/credentials.js → agent-clients/adapters/claude-code-credentials.js} +4 -14
- package/dist/lib/agent-clients/adapters/claude-code-refresh.js +85 -0
- package/dist/lib/agent-clients/adapters/claude-code-sandbox.js +247 -0
- package/dist/lib/agent-clients/adapters/claude-code.js +27 -4
- package/dist/lib/agent-clients/adapters/codex.js +9 -0
- package/dist/lib/agent-clients/adapters/index.js +2 -2
- package/dist/lib/agent-clients/adapters/opencode-sandbox.js +108 -0
- package/dist/lib/agent-clients/adapters/opencode.js +24 -3
- package/dist/lib/agent-clients/adapters/runtimes/claude-code.dockerfile +65 -0
- package/dist/lib/agent-clients/config.js +42 -4
- package/dist/lib/agent-clients/next-steps.js +11 -2
- package/dist/lib/agent-clients/reconcile.js +2 -1
- package/dist/lib/agent-clients/registry.js +16 -2
- package/dist/lib/agent-clients/schema.js +18 -0
- package/dist/lib/agent-clients/tokens.js +49 -0
- package/dist/lib/agent-clients/types.js +1 -1
- package/dist/lib/defaults.json +1 -1
- package/dist/lib/git/workflow.js +54 -1
- package/dist/lib/internal/agent-client.js +61 -4
- package/dist/lib/internal/git-workflow.js +6 -1
- package/dist/lib/internal/platform-checks.js +3 -3
- package/dist/lib/internal/platform-comment.js +29 -2
- package/dist/lib/internal/platform-issue.js +7 -0
- package/dist/lib/internal/platform-pr-review.js +132 -0
- package/dist/lib/internal/platform-pr.js +20 -2
- package/dist/lib/internal/platform-release-notes.js +52 -16
- package/dist/lib/internal/pr-review-grade.js +140 -0
- package/dist/lib/internal/release-workflow.js +13 -6
- package/dist/lib/internal/sandbox-control.js +24 -0
- package/dist/lib/internal/task-activity.js +105 -0
- package/dist/lib/internal/task-event.js +17 -1
- package/dist/lib/internal/task-ledger.js +12 -3
- package/dist/lib/internal/task-lifecycle.js +26 -1
- package/dist/lib/internal/task-orchestration.js +167 -18
- package/dist/lib/internal/task-review.js +8 -3
- package/dist/lib/platform/adapters.js +8 -1
- package/dist/lib/platform/completion-backfill.js +122 -0
- package/dist/lib/platform/github-client.js +19 -2
- package/dist/lib/platform/issue-comments.js +3 -2
- package/dist/lib/platform/merged-pr-equivalence.js +115 -5
- package/dist/lib/platform/pr-checks.js +65 -42
- package/dist/lib/platform/pr-review.js +166 -0
- package/dist/lib/platform/pull-requests.js +371 -4
- package/dist/lib/platform/release-notes.js +85 -3
- package/dist/lib/platform/releases.js +13 -5
- package/dist/lib/pr-review/evidence-grading.js +229 -0
- package/dist/lib/process-data/index.js +239 -0
- package/dist/lib/process-data/normalize.js +198 -0
- package/dist/lib/process-data/privacy.js +33 -0
- package/dist/lib/process-data/reconcile.js +95 -0
- package/dist/lib/process-data/repair.js +58 -0
- package/dist/lib/process-data/sources.js +345 -0
- package/dist/lib/process-data/store.js +282 -0
- package/dist/lib/process-data/types.js +2 -0
- package/dist/lib/run/index.js +4 -3
- package/dist/lib/run/tui.js +10 -13
- package/dist/lib/sandbox/agent-client-reconciler.js +26 -2
- package/dist/lib/sandbox/capture.js +7 -2
- package/dist/lib/sandbox/commands/create.js +109 -248
- package/dist/lib/sandbox/commands/enter.js +15 -47
- package/dist/lib/sandbox/commands/list-running.js +16 -6
- package/dist/lib/sandbox/commands/ls.js +10 -6
- package/dist/lib/sandbox/commands/rebuild.js +4 -3
- package/dist/lib/sandbox/commands/refresh.js +4 -84
- package/dist/lib/sandbox/commands/rm.js +24 -3
- package/dist/lib/sandbox/commands/show.js +13 -4
- package/dist/lib/sandbox/commands/start.js +7 -4
- package/dist/lib/sandbox/config.js +2 -0
- package/dist/lib/sandbox/constants.js +6 -0
- package/dist/lib/sandbox/control/client.js +44 -0
- package/dist/lib/sandbox/control/protocol.js +35 -0
- package/dist/lib/sandbox/control/server.js +109 -0
- package/dist/lib/sandbox/dockerfile.js +36 -13
- package/dist/lib/sandbox/mounts.js +17 -4
- package/dist/lib/sandbox/recovery.js +128 -5
- package/dist/lib/sandbox/redaction.js +12 -0
- package/dist/lib/sandbox/runtimes/base.dockerfile +24 -74
- package/dist/lib/sandbox/workspace-identity.js +95 -0
- package/dist/lib/sandbox/workspace-view.js +103 -0
- package/dist/lib/task/activity-intent.js +307 -0
- package/dist/lib/task/artifact-lifecycle.js +2 -1
- package/dist/lib/task/commands/log.js +15 -14
- package/dist/lib/task/commit-finalization.js +267 -0
- package/dist/lib/task/commit-intent.js +165 -0
- package/dist/lib/task/decision-intents.js +31 -17
- package/dist/lib/task/delegation-receipts.js +25 -4
- package/dist/lib/task/events.js +67 -28
- package/dist/lib/task/finalization-artifacts.js +35 -0
- package/dist/lib/task/implementation-inputs.js +54 -7
- package/dist/lib/task/ledger-intents.js +35 -1
- package/dist/lib/task/lifecycle-execution.js +46 -0
- package/dist/lib/task/orchestration.js +753 -54
- package/dist/lib/task/resolve-ref.js +41 -1
- package/dist/lib/task/review-finalization.js +29 -9
- package/dist/lib/task/task-execution-lock.js +231 -0
- package/dist/lib/task/verification-checks.js +1 -1
- package/dist/lib/task/verification-engine.js +218 -2
- package/dist/lib/task/verification.js +4 -3
- package/dist/lib/task/workspace-snapshot.js +17 -5
- package/lib/agent-clients/adapter.ts +201 -4
- package/lib/agent-clients/adapters/antigravity-cli.ts +65 -0
- package/lib/{sandbox/credentials.ts → agent-clients/adapters/claude-code-credentials.ts} +3 -18
- package/lib/agent-clients/adapters/claude-code-refresh.ts +128 -0
- package/lib/agent-clients/adapters/claude-code-sandbox.ts +295 -0
- package/lib/agent-clients/adapters/claude-code.ts +31 -4
- package/lib/agent-clients/adapters/codex.ts +9 -0
- package/lib/agent-clients/adapters/index.ts +2 -2
- package/lib/agent-clients/adapters/opencode-sandbox.ts +136 -0
- package/lib/agent-clients/adapters/opencode.ts +27 -3
- package/lib/agent-clients/adapters/runtimes/claude-code.dockerfile +65 -0
- package/lib/agent-clients/config.ts +49 -4
- package/lib/agent-clients/next-steps.ts +15 -2
- package/lib/agent-clients/reconcile.ts +2 -1
- package/lib/agent-clients/registry.ts +19 -1
- package/lib/agent-clients/schema.ts +18 -0
- package/lib/agent-clients/tokens.ts +53 -0
- package/lib/agent-clients/types.ts +15 -1
- package/lib/defaults.json +1 -1
- package/lib/git/workflow.ts +66 -2
- package/lib/internal/agent-client.ts +59 -4
- package/lib/internal/git-workflow.ts +5 -1
- package/lib/internal/platform-checks.ts +4 -4
- package/lib/internal/platform-comment.ts +17 -2
- package/lib/internal/platform-issue.ts +4 -0
- package/lib/internal/platform-pr-review.ts +104 -0
- package/lib/internal/platform-pr.ts +14 -1
- package/lib/internal/platform-release-notes.ts +59 -16
- package/lib/internal/pr-review-grade.ts +130 -0
- package/lib/internal/release-workflow.ts +13 -6
- package/lib/internal/sandbox-control.ts +24 -0
- package/lib/internal/task-activity.ts +91 -0
- package/lib/internal/task-event.ts +9 -1
- package/lib/internal/task-ledger.ts +12 -3
- package/lib/internal/task-lifecycle.ts +26 -1
- package/lib/internal/task-orchestration.ts +123 -14
- package/lib/internal/task-review.ts +8 -3
- package/lib/platform/adapters.ts +29 -2
- package/lib/platform/completion-backfill.ts +134 -0
- package/lib/platform/github-client.ts +20 -2
- package/lib/platform/issue-comments.ts +3 -2
- package/lib/platform/merged-pr-equivalence.ts +127 -5
- package/lib/platform/pr-checks.ts +73 -46
- package/lib/platform/pr-review.ts +201 -0
- package/lib/platform/pull-requests.ts +444 -3
- package/lib/platform/release-notes.ts +100 -4
- package/lib/platform/releases.ts +16 -4
- package/lib/pr-review/evidence-grading.ts +318 -0
- package/lib/process-data/index.ts +224 -0
- package/lib/process-data/normalize.ts +202 -0
- package/lib/process-data/privacy.ts +41 -0
- package/lib/process-data/reconcile.ts +110 -0
- package/lib/process-data/repair.ts +64 -0
- package/lib/process-data/sources.ts +354 -0
- package/lib/process-data/store.ts +315 -0
- package/lib/process-data/types.ts +135 -0
- package/lib/run/index.ts +5 -3
- package/lib/run/tui.ts +14 -12
- package/lib/sandbox/agent-client-reconciler.ts +44 -2
- package/lib/sandbox/capture.ts +9 -2
- package/lib/sandbox/commands/create.ts +126 -280
- package/lib/sandbox/commands/enter.ts +13 -59
- package/lib/sandbox/commands/list-running.ts +23 -6
- package/lib/sandbox/commands/ls.ts +22 -6
- package/lib/sandbox/commands/rebuild.ts +4 -3
- package/lib/sandbox/commands/refresh.ts +4 -126
- package/lib/sandbox/commands/rm.ts +24 -3
- package/lib/sandbox/commands/show.ts +30 -4
- package/lib/sandbox/commands/start.ts +6 -4
- package/lib/sandbox/config.ts +4 -0
- package/lib/sandbox/constants.ts +8 -0
- package/lib/sandbox/control/client.ts +55 -0
- package/lib/sandbox/control/protocol.ts +75 -0
- package/lib/sandbox/control/server.ts +117 -0
- package/lib/sandbox/dockerfile.ts +66 -13
- package/lib/sandbox/mounts.ts +24 -4
- package/lib/sandbox/recovery.ts +163 -6
- package/lib/sandbox/redaction.ts +14 -0
- package/lib/sandbox/runtimes/base.dockerfile +24 -74
- package/lib/sandbox/tool-types.ts +16 -0
- package/lib/sandbox/workspace-identity.ts +123 -0
- package/lib/sandbox/workspace-view.ts +143 -0
- package/lib/server/server.schema.json +2 -2
- package/lib/task/activity-intent.ts +417 -0
- package/lib/task/artifact-lifecycle.ts +2 -1
- package/lib/task/commands/log.ts +15 -14
- package/lib/task/commit-finalization.ts +356 -0
- package/lib/task/commit-intent.ts +230 -0
- package/lib/task/decision-intents.ts +32 -12
- package/lib/task/delegation-receipts.ts +34 -5
- package/lib/task/events.ts +75 -30
- package/lib/task/finalization-artifacts.ts +58 -0
- package/lib/task/implementation-inputs.ts +62 -8
- package/lib/task/ledger-intents.ts +43 -3
- package/lib/task/lifecycle-execution.ts +106 -0
- package/lib/task/orchestration.ts +1019 -72
- package/lib/task/resolve-ref.ts +46 -1
- package/lib/task/review-finalization.ts +40 -9
- package/lib/task/task-execution-lock.ts +296 -0
- package/lib/task/verification-checks.ts +1 -1
- package/lib/task/verification-engine.ts +249 -2
- package/lib/task/verification.ts +5 -3
- package/lib/task/workspace-snapshot.ts +26 -5
- package/package.json +3 -3
- package/templates/.agents/QUICKSTART.en.md +25 -8
- package/templates/.agents/QUICKSTART.zh-CN.md +25 -8
- package/templates/.agents/README.en.md +10 -7
- package/templates/.agents/README.zh-CN.md +10 -7
- package/templates/.agents/hooks/lifecycle-delegation.js +15 -4
- package/templates/.agents/rules/README.en.md +1 -1
- package/templates/.agents/rules/README.zh-CN.md +1 -1
- package/templates/.agents/rules/create-issue.github.en.md +4 -2
- package/templates/.agents/rules/create-issue.github.zh-CN.md +4 -2
- package/templates/.agents/rules/human-decision-context.en.md +11 -2
- package/templates/.agents/rules/human-decision-context.zh-CN.md +11 -2
- package/templates/.agents/rules/issue-fields.github.en.md +3 -1
- package/templates/.agents/rules/issue-fields.github.zh-CN.md +3 -1
- package/templates/.agents/rules/issue-pr-commands.en.md +5 -3
- package/templates/.agents/rules/issue-pr-commands.github.en.md +5 -3
- package/templates/.agents/rules/issue-pr-commands.github.zh-CN.md +5 -3
- package/templates/.agents/rules/issue-pr-commands.zh-CN.md +5 -3
- package/templates/.agents/rules/issue-sync.github.en.md +7 -3
- package/templates/.agents/rules/issue-sync.github.zh-CN.md +8 -4
- package/templates/.agents/rules/lifecycle-orchestration.en.md +5 -2
- package/templates/.agents/rules/lifecycle-orchestration.zh-CN.md +5 -2
- package/templates/.agents/rules/milestone-inference.github.en.md +4 -2
- package/templates/.agents/rules/milestone-inference.github.zh-CN.md +4 -2
- package/templates/.agents/rules/next-step-output.en.md +3 -1
- package/templates/.agents/rules/next-step-output.zh-CN.md +3 -1
- package/templates/.agents/rules/no-mid-flow-questions.en.md +17 -0
- package/templates/.agents/rules/no-mid-flow-questions.zh-CN.md +17 -0
- package/templates/.agents/rules/pr-checks-commands.en.md +3 -3
- package/templates/.agents/rules/pr-checks-commands.github.en.md +2 -2
- package/templates/.agents/rules/pr-checks-commands.github.zh-CN.md +3 -3
- package/templates/.agents/rules/pr-checks-commands.zh-CN.md +3 -3
- package/templates/.agents/rules/pr-sync.en.md +3 -1
- package/templates/.agents/rules/pr-sync.github.en.md +11 -1
- package/templates/.agents/rules/pr-sync.github.zh-CN.md +11 -1
- package/templates/.agents/rules/pr-sync.zh-CN.md +3 -1
- package/templates/.agents/rules/release-commands.en.md +1 -1
- package/templates/.agents/rules/release-commands.github.en.md +12 -2
- package/templates/.agents/rules/release-commands.github.zh-CN.md +12 -2
- package/templates/.agents/rules/release-commands.zh-CN.md +1 -1
- package/templates/.agents/rules/review-handshake.zh-CN.md +1 -1
- package/templates/.agents/rules/task-management.en.md +13 -0
- package/templates/.agents/rules/task-management.zh-CN.md +13 -0
- package/templates/.agents/skills/analyze-task/SKILL.en.md +10 -7
- package/templates/.agents/skills/analyze-task/SKILL.zh-CN.md +10 -7
- package/templates/.agents/skills/block-task/SKILL.en.md +7 -5
- package/templates/.agents/skills/block-task/SKILL.zh-CN.md +7 -5
- package/templates/.agents/skills/cancel-task/SKILL.en.md +6 -4
- package/templates/.agents/skills/cancel-task/SKILL.zh-CN.md +6 -4
- package/templates/.agents/skills/close-codescan/SKILL.en.md +3 -1
- package/templates/.agents/skills/close-codescan/SKILL.zh-CN.md +3 -1
- package/templates/.agents/skills/close-dependabot/SKILL.en.md +3 -1
- package/templates/.agents/skills/close-dependabot/SKILL.zh-CN.md +3 -1
- package/templates/.agents/skills/code-task/SKILL.en.md +10 -7
- package/templates/.agents/skills/code-task/SKILL.zh-CN.md +12 -9
- package/templates/.agents/skills/commit/SKILL.en.md +23 -11
- package/templates/.agents/skills/commit/SKILL.zh-CN.md +29 -11
- package/templates/.agents/skills/commit/reference/commit-message.en.md +1 -1
- package/templates/.agents/skills/commit/reference/commit-message.zh-CN.md +1 -1
- package/templates/.agents/skills/commit/reference/commit-orchestration.en.md +49 -0
- package/templates/.agents/skills/commit/reference/commit-orchestration.zh-CN.md +55 -0
- package/templates/.agents/skills/commit/reference/issue-metadata-sync.en.md +1 -1
- package/templates/.agents/skills/commit/reference/issue-metadata-sync.zh-CN.md +1 -1
- package/templates/.agents/skills/commit/reference/pr-summary-sync.en.md +1 -1
- package/templates/.agents/skills/commit/reference/pr-summary-sync.zh-CN.md +1 -1
- package/templates/.agents/skills/commit/reference/task-status-update.en.md +4 -4
- package/templates/.agents/skills/commit/reference/task-status-update.zh-CN.md +4 -4
- package/templates/.agents/skills/complete-manual-validation/SKILL.en.md +6 -4
- package/templates/.agents/skills/complete-manual-validation/SKILL.zh-CN.md +6 -4
- package/templates/.agents/skills/complete-manual-validation/reference/summary-update.en.md +1 -1
- package/templates/.agents/skills/complete-manual-validation/reference/summary-update.zh-CN.md +1 -1
- package/templates/.agents/skills/complete-task/SKILL.en.md +22 -7
- package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +22 -7
- package/templates/.agents/skills/complete-task/config/verify.en.json +1 -0
- package/templates/.agents/skills/complete-task/config/verify.zh-CN.json +1 -0
- package/templates/.agents/skills/complete-task/reference/external-delivery.en.md +17 -0
- package/templates/.agents/skills/complete-task/reference/external-delivery.zh-CN.md +17 -0
- package/templates/.agents/skills/create-pr/SKILL.en.md +7 -5
- package/templates/.agents/skills/create-pr/SKILL.zh-CN.md +7 -5
- package/templates/.agents/skills/create-pr/reference/comment-publish.en.md +1 -1
- package/templates/.agents/skills/create-pr/reference/comment-publish.zh-CN.md +1 -1
- package/templates/.agents/skills/create-release-note/SKILL.en.md +17 -16
- package/templates/.agents/skills/create-release-note/SKILL.zh-CN.md +17 -16
- package/templates/.agents/skills/create-task/SKILL.en.md +5 -3
- package/templates/.agents/skills/create-task/SKILL.zh-CN.md +5 -3
- package/templates/.agents/skills/create-task/reference/context-capture.en.md +34 -0
- package/templates/.agents/skills/create-task/reference/context-capture.zh-CN.md +34 -0
- package/templates/.agents/skills/import-issue/SKILL.en.md +5 -3
- package/templates/.agents/skills/import-issue/SKILL.zh-CN.md +5 -3
- package/templates/.agents/skills/plan-task/SKILL.en.md +8 -5
- package/templates/.agents/skills/plan-task/SKILL.zh-CN.md +8 -5
- package/templates/.agents/skills/post-release/SKILL.en.md +2 -0
- package/templates/.agents/skills/post-release/SKILL.zh-CN.md +2 -0
- package/templates/.agents/skills/release/SKILL.en.md +16 -10
- package/templates/.agents/skills/release/SKILL.zh-CN.md +16 -10
- package/templates/.agents/skills/restore-task/SKILL.en.md +4 -1
- package/templates/.agents/skills/restore-task/SKILL.zh-CN.md +4 -1
- package/templates/.agents/skills/review-analysis/SKILL.en.md +7 -4
- package/templates/.agents/skills/review-analysis/SKILL.zh-CN.md +7 -4
- package/templates/.agents/skills/review-code/SKILL.en.md +11 -8
- package/templates/.agents/skills/review-code/SKILL.zh-CN.md +11 -8
- package/templates/.agents/skills/review-code/reference/output-templates.en.md +1 -1
- package/templates/.agents/skills/review-code/reference/output-templates.zh-CN.md +1 -1
- package/templates/.agents/skills/review-plan/SKILL.en.md +7 -4
- package/templates/.agents/skills/review-plan/SKILL.zh-CN.md +7 -4
- package/templates/.agents/skills/review-pr/SKILL.en.md +186 -0
- package/templates/.agents/skills/review-pr/SKILL.zh-CN.md +186 -0
- package/templates/.agents/skills/review-pr/config/verify.en.json +37 -0
- package/templates/.agents/skills/review-pr/config/verify.zh-CN.json +37 -0
- package/templates/.agents/skills/review-pr/reference/evidence-grading.en.md +65 -0
- package/templates/.agents/skills/review-pr/reference/evidence-grading.zh-CN.md +65 -0
- package/templates/.agents/skills/review-pr/reference/output-guidance.en.md +18 -0
- package/templates/.agents/skills/review-pr/reference/output-guidance.zh-CN.md +18 -0
- package/templates/.agents/skills/review-pr/reference/report-template.en.md +85 -0
- package/templates/.agents/skills/review-pr/reference/report-template.zh-CN.md +85 -0
- package/templates/.agents/skills/run-task/SKILL.en.md +8 -8
- package/templates/.agents/skills/run-task/SKILL.zh-CN.md +8 -8
- package/templates/.agents/skills/run-task/config/verify.en.json +2 -1
- package/templates/.agents/skills/run-task/config/verify.zh-CN.json +2 -1
- package/templates/.agents/skills/run-task/reference/host-validation.en.md +20 -0
- package/templates/.agents/skills/run-task/reference/host-validation.zh-CN.md +20 -0
- package/templates/.agents/skills/update-agent-infra/scripts/sync-templates.js +98 -107
- package/templates/.agents/skills/watch-pr/SKILL.en.md +13 -13
- package/templates/.agents/skills/watch-pr/SKILL.zh-CN.md +13 -13
- package/templates/.agents/skills/watch-pr/reference/monitor-and-heal.en.md +27 -6
- package/templates/.agents/skills/watch-pr/reference/monitor-and-heal.zh-CN.md +26 -5
- package/templates/.agents/templates/task.en.md +2 -2
- package/templates/.agents/templates/task.zh-CN.md +2 -2
- package/templates/.agents/workflows/bug-fix.en.yaml +3 -3
- package/templates/.agents/workflows/bug-fix.zh-CN.yaml +3 -3
- package/templates/.agents/workflows/code-review.en.yaml +1 -1
- package/templates/.agents/workflows/code-review.zh-CN.yaml +1 -1
- package/templates/.agents/workflows/feature-development.en.yaml +4 -4
- package/templates/.agents/workflows/feature-development.zh-CN.yaml +4 -4
- package/templates/.agents/workflows/refactoring.en.yaml +3 -3
- package/templates/.agents/workflows/refactoring.zh-CN.yaml +3 -3
- package/templates/.claude/commands/post-release.en.md +1 -0
- package/templates/.claude/commands/post-release.zh-CN.md +1 -0
- package/templates/.claude/commands/review-pr.en.md +8 -0
- package/templates/.claude/commands/review-pr.zh-CN.md +8 -0
- package/templates/.claude/commands/watch-pr.en.md +1 -1
- package/templates/.claude/commands/watch-pr.zh-CN.md +1 -1
- package/templates/.claude/rules/tool-preferences.en.md +20 -0
- package/templates/.claude/rules/tool-preferences.zh-CN.md +20 -0
- package/templates/.opencode/commands/post-release.en.md +2 -0
- package/templates/.opencode/commands/post-release.zh-CN.md +2 -0
- package/templates/.opencode/commands/review-pr.en.md +11 -0
- package/templates/.opencode/commands/review-pr.zh-CN.md +11 -0
- package/templates/.opencode/commands/watch-pr.en.md +1 -1
- package/templates/.opencode/commands/watch-pr.zh-CN.md +1 -1
- package/dist/lib/agent-clients/adapters/gemini-cli.js +0 -64
- package/lib/agent-clients/adapters/gemini-cli.ts +0 -65
- package/templates/.gemini/commands/_project_/analyze-task.en.toml +0 -10
- package/templates/.gemini/commands/_project_/analyze-task.zh-CN.toml +0 -10
- package/templates/.gemini/commands/_project_/archive-tasks.en.toml +0 -10
- package/templates/.gemini/commands/_project_/archive-tasks.zh-CN.toml +0 -10
- package/templates/.gemini/commands/_project_/block-task.en.toml +0 -10
- package/templates/.gemini/commands/_project_/block-task.zh-CN.toml +0 -10
- package/templates/.gemini/commands/_project_/cancel-task.en.toml +0 -10
- package/templates/.gemini/commands/_project_/cancel-task.zh-CN.toml +0 -10
- package/templates/.gemini/commands/_project_/check-task.en.toml +0 -10
- package/templates/.gemini/commands/_project_/check-task.zh-CN.toml +0 -10
- package/templates/.gemini/commands/_project_/close-codescan.en.toml +0 -8
- package/templates/.gemini/commands/_project_/close-codescan.zh-CN.toml +0 -8
- package/templates/.gemini/commands/_project_/close-dependabot.en.toml +0 -8
- package/templates/.gemini/commands/_project_/close-dependabot.zh-CN.toml +0 -8
- package/templates/.gemini/commands/_project_/code-task.en.toml +0 -10
- package/templates/.gemini/commands/_project_/code-task.zh-CN.toml +0 -10
- package/templates/.gemini/commands/_project_/commit.en.toml +0 -10
- package/templates/.gemini/commands/_project_/commit.zh-CN.toml +0 -10
- package/templates/.gemini/commands/_project_/complete-manual-validation.en.toml +0 -10
- package/templates/.gemini/commands/_project_/complete-manual-validation.zh-CN.toml +0 -10
- package/templates/.gemini/commands/_project_/complete-task.en.toml +0 -10
- package/templates/.gemini/commands/_project_/complete-task.zh-CN.toml +0 -10
- package/templates/.gemini/commands/_project_/create-pr.en.toml +0 -10
- package/templates/.gemini/commands/_project_/create-pr.zh-CN.toml +0 -10
- package/templates/.gemini/commands/_project_/create-release-note.en.toml +0 -8
- package/templates/.gemini/commands/_project_/create-release-note.zh-CN.toml +0 -8
- package/templates/.gemini/commands/_project_/create-task.en.toml +0 -8
- package/templates/.gemini/commands/_project_/create-task.zh-CN.toml +0 -8
- package/templates/.gemini/commands/_project_/import-codescan.en.toml +0 -8
- package/templates/.gemini/commands/_project_/import-codescan.zh-CN.toml +0 -8
- package/templates/.gemini/commands/_project_/import-dependabot.en.toml +0 -8
- package/templates/.gemini/commands/_project_/import-dependabot.zh-CN.toml +0 -8
- package/templates/.gemini/commands/_project_/import-issue.en.toml +0 -8
- package/templates/.gemini/commands/_project_/import-issue.zh-CN.toml +0 -8
- package/templates/.gemini/commands/_project_/init-labels.en.toml +0 -8
- package/templates/.gemini/commands/_project_/init-labels.zh-CN.toml +0 -8
- package/templates/.gemini/commands/_project_/init-milestones.en.toml +0 -10
- package/templates/.gemini/commands/_project_/init-milestones.zh-CN.toml +0 -10
- package/templates/.gemini/commands/_project_/plan-task.en.toml +0 -10
- package/templates/.gemini/commands/_project_/plan-task.zh-CN.toml +0 -10
- package/templates/.gemini/commands/_project_/post-release.en.toml +0 -6
- package/templates/.gemini/commands/_project_/post-release.zh-CN.toml +0 -6
- package/templates/.gemini/commands/_project_/refine-title.en.toml +0 -8
- package/templates/.gemini/commands/_project_/refine-title.zh-CN.toml +0 -8
- package/templates/.gemini/commands/_project_/release.en.toml +0 -8
- package/templates/.gemini/commands/_project_/release.zh-CN.toml +0 -8
- package/templates/.gemini/commands/_project_/restore-task.en.toml +0 -8
- package/templates/.gemini/commands/_project_/restore-task.zh-CN.toml +0 -8
- package/templates/.gemini/commands/_project_/review-analysis.en.toml +0 -10
- package/templates/.gemini/commands/_project_/review-analysis.zh-CN.toml +0 -10
- package/templates/.gemini/commands/_project_/review-code.en.toml +0 -10
- package/templates/.gemini/commands/_project_/review-code.zh-CN.toml +0 -10
- package/templates/.gemini/commands/_project_/review-plan.en.toml +0 -10
- package/templates/.gemini/commands/_project_/review-plan.zh-CN.toml +0 -10
- package/templates/.gemini/commands/_project_/run-task.en.toml +0 -8
- package/templates/.gemini/commands/_project_/run-task.zh-CN.toml +0 -8
- package/templates/.gemini/commands/_project_/test-integration.en.toml +0 -6
- package/templates/.gemini/commands/_project_/test-integration.zh-CN.toml +0 -6
- package/templates/.gemini/commands/_project_/test.en.toml +0 -6
- package/templates/.gemini/commands/_project_/test.zh-CN.toml +0 -6
- package/templates/.gemini/commands/_project_/update-agent-infra.en.toml +0 -6
- package/templates/.gemini/commands/_project_/update-agent-infra.zh-CN.toml +0 -6
- package/templates/.gemini/commands/_project_/upgrade-dependency.en.toml +0 -8
- package/templates/.gemini/commands/_project_/upgrade-dependency.zh-CN.toml +0 -8
- package/templates/.gemini/commands/_project_/watch-pr.en.toml +0 -10
- package/templates/.gemini/commands/_project_/watch-pr.zh-CN.toml +0 -10
- package/templates/.gemini/settings.json +0 -3
|
@@ -0,0 +1,354 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
|
|
4
|
+
import { createGitHubClient } from '../platform/github-client.ts';
|
|
5
|
+
import { enumerateAllTaskDirs } from '../task/resolve-ref.ts';
|
|
6
|
+
import { captureText, inspectPrivacy, redactExcerpt } from './privacy.ts';
|
|
7
|
+
import { canonicalJsonBytes, sha256 } from './store.ts';
|
|
8
|
+
|
|
9
|
+
import type { GitHubClient } from '../platform/github-client.ts';
|
|
10
|
+
import type {
|
|
11
|
+
CapturedObject,
|
|
12
|
+
ProcessResult,
|
|
13
|
+
RestCollectionEvidence,
|
|
14
|
+
RestPageEvidence
|
|
15
|
+
} from './types.ts';
|
|
16
|
+
|
|
17
|
+
type RestPage = { evidence: RestPageEvidence; bytes: Buffer };
|
|
18
|
+
type RestCollection<T> = { items: T[]; evidence: RestCollectionEvidence; pageObjects: CapturedObject[] };
|
|
19
|
+
|
|
20
|
+
type FetchRestCollectionOptions<T> = {
|
|
21
|
+
client: GitHubClient;
|
|
22
|
+
endpoint: string;
|
|
23
|
+
identify: (item: T) => string;
|
|
24
|
+
select?: (item: T) => T;
|
|
25
|
+
perPage?: number;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
type GitHubCapture = {
|
|
29
|
+
repository: string;
|
|
30
|
+
objects: CapturedObject[];
|
|
31
|
+
endpoints: RestCollectionEvidence[];
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
function endpointForPage(endpoint: string, page: number, perPage: number): string {
|
|
35
|
+
const parsed = new URL(endpoint, 'https://api.github.invalid/');
|
|
36
|
+
if (parsed.searchParams.has('page') || parsed.searchParams.has('per_page')) {
|
|
37
|
+
throw new Error('endpoint must not predefine page or per_page');
|
|
38
|
+
}
|
|
39
|
+
parsed.searchParams.set('per_page', String(perPage));
|
|
40
|
+
parsed.searchParams.set('page', String(page));
|
|
41
|
+
return `${parsed.pathname.replace(/^\//, '')}${parsed.search}`;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function fetchRestCollection<T>(options: FetchRestCollectionOptions<T>): ProcessResult<RestCollection<T>> {
|
|
45
|
+
const perPage = options.perPage ?? 100;
|
|
46
|
+
if (!Number.isInteger(perPage) || perPage < 1 || perPage > 100) {
|
|
47
|
+
return { ok: false, error: { code: 'PAGINATION_INVALID', message: 'perPage must be between 1 and 100' } };
|
|
48
|
+
}
|
|
49
|
+
let pageIndex = 1;
|
|
50
|
+
let previousHash: string | null = null;
|
|
51
|
+
const identities = new Set<string>();
|
|
52
|
+
const items: T[] = [];
|
|
53
|
+
const pages: RestPage[] = [];
|
|
54
|
+
|
|
55
|
+
while (true) {
|
|
56
|
+
let url: string;
|
|
57
|
+
try {
|
|
58
|
+
url = endpointForPage(options.endpoint, pageIndex, perPage);
|
|
59
|
+
} catch (error) {
|
|
60
|
+
return { ok: false, error: { code: 'PAGINATION_INVALID', message: String(error) } };
|
|
61
|
+
}
|
|
62
|
+
const response = options.client.json<unknown>(['api', url]);
|
|
63
|
+
if (!response.ok) return { ok: false, error: response.error };
|
|
64
|
+
if (!Array.isArray(response.value) || response.value.length > perPage) {
|
|
65
|
+
return {
|
|
66
|
+
ok: false,
|
|
67
|
+
error: { code: 'INVALID_PLATFORM_RESPONSE', message: `GitHub page ${pageIndex} is not a valid array page` }
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
let selected: T[];
|
|
71
|
+
try {
|
|
72
|
+
selected = (response.value as T[]).map((entry) => options.select ? options.select(entry) : entry);
|
|
73
|
+
} catch (error) {
|
|
74
|
+
return { ok: false, error: { code: 'INVALID_PLATFORM_RESPONSE', message: String(error) } };
|
|
75
|
+
}
|
|
76
|
+
let bytes: Buffer;
|
|
77
|
+
try {
|
|
78
|
+
bytes = canonicalJsonBytes(selected);
|
|
79
|
+
} catch (error) {
|
|
80
|
+
return { ok: false, error: { code: 'INVALID_PLATFORM_RESPONSE', message: String(error) } };
|
|
81
|
+
}
|
|
82
|
+
const canonicalSha256 = sha256(bytes);
|
|
83
|
+
if (previousHash === canonicalSha256 && response.value.length > 0) {
|
|
84
|
+
return {
|
|
85
|
+
ok: false,
|
|
86
|
+
error: { code: 'PAGINATION_UNSTABLE', message: `GitHub page ${pageIndex} repeats the previous page` }
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
for (const entry of selected) {
|
|
90
|
+
const identity = options.identify(entry);
|
|
91
|
+
if (!identity || identities.has(identity)) {
|
|
92
|
+
return {
|
|
93
|
+
ok: false,
|
|
94
|
+
error: { code: 'PAGINATION_UNSTABLE', message: `duplicate or empty identity on page ${pageIndex}` }
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
identities.add(identity);
|
|
98
|
+
items.push(entry);
|
|
99
|
+
}
|
|
100
|
+
pages.push({ evidence: { index: pageIndex, itemCount: response.value.length, canonicalSha256 }, bytes });
|
|
101
|
+
previousHash = canonicalSha256;
|
|
102
|
+
if (response.value.length < perPage) break;
|
|
103
|
+
pageIndex += 1;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const evidence: RestCollectionEvidence = {
|
|
107
|
+
endpoint: options.endpoint,
|
|
108
|
+
requestCount: pages.length,
|
|
109
|
+
dataPageCount: pages.filter((page) => page.evidence.itemCount > 0).length,
|
|
110
|
+
itemCount: items.length,
|
|
111
|
+
termination: 'short-page',
|
|
112
|
+
pages: pages.map((page) => page.evidence)
|
|
113
|
+
};
|
|
114
|
+
return {
|
|
115
|
+
ok: true,
|
|
116
|
+
value: {
|
|
117
|
+
items,
|
|
118
|
+
evidence,
|
|
119
|
+
pageObjects: pages.map((page) => ({
|
|
120
|
+
sourceKind: 'github-rest',
|
|
121
|
+
sourceIdentity: `${options.endpoint}#page=${page.evidence.index}`,
|
|
122
|
+
sha256: page.evidence.canonicalSha256,
|
|
123
|
+
bytes: page.bytes.length,
|
|
124
|
+
content: page.bytes.toString('utf8'),
|
|
125
|
+
disposition: { state: 'included' }
|
|
126
|
+
}))
|
|
127
|
+
}
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function relativeIdentity(repoRoot: string, filePath: string): string {
|
|
132
|
+
const relative = path.relative(repoRoot, filePath);
|
|
133
|
+
if (!relative || relative.startsWith('..') || path.isAbsolute(relative)) throw new Error(`path escapes repository: ${filePath}`);
|
|
134
|
+
return relative.split(path.sep).join('/');
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function readStableFile(repoRoot: string, filePath: string, sourceKind: CapturedObject['sourceKind']): CapturedObject {
|
|
138
|
+
const before = fs.lstatSync(filePath);
|
|
139
|
+
if (!before.isFile() || before.isSymbolicLink()) throw new Error(`unsafe source file: ${filePath}`);
|
|
140
|
+
const content = fs.readFileSync(filePath, 'utf8');
|
|
141
|
+
const after = fs.lstatSync(filePath);
|
|
142
|
+
if (before.dev !== after.dev || before.ino !== after.ino || before.size !== after.size || before.mtimeMs !== after.mtimeMs) {
|
|
143
|
+
throw new Error(`source changed while reading: ${filePath}`);
|
|
144
|
+
}
|
|
145
|
+
return captureText(sourceKind, relativeIdentity(repoRoot, filePath), content);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function listFiles(directory: string): string[] {
|
|
149
|
+
if (!fs.existsSync(directory)) return [];
|
|
150
|
+
const output: string[] = [];
|
|
151
|
+
const walk = (current: string) => {
|
|
152
|
+
for (const entry of fs.readdirSync(current, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name))) {
|
|
153
|
+
const candidate = path.join(current, entry.name);
|
|
154
|
+
if (entry.isSymbolicLink()) throw new Error(`symlink source is not allowed: ${candidate}`);
|
|
155
|
+
if (entry.isDirectory()) walk(candidate);
|
|
156
|
+
else if (entry.isFile()) output.push(candidate);
|
|
157
|
+
}
|
|
158
|
+
};
|
|
159
|
+
walk(directory);
|
|
160
|
+
return output;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
function structuredReceipt(repoRoot: string, filePath: string): CapturedObject {
|
|
164
|
+
const raw = readStableFile(repoRoot, filePath, 'structured-telemetry');
|
|
165
|
+
if (!raw.content) return raw;
|
|
166
|
+
try {
|
|
167
|
+
const parsed = JSON.parse(raw.content) as Record<string, unknown>;
|
|
168
|
+
const fields = ['client', 'model', 'reasoning', 'stage', 'toolCategory', 'time', 'status', 'retry', 'error', 'fallback'];
|
|
169
|
+
const selected = Object.fromEntries(
|
|
170
|
+
fields.filter((field) => parsed[field] !== undefined).map((field) => [field, jsonClone(parsed[field])])
|
|
171
|
+
);
|
|
172
|
+
selected.sourceSha256 = raw.sha256;
|
|
173
|
+
return captureText('structured-telemetry', raw.sourceIdentity, canonicalJsonBytes(selected).toString('utf8'));
|
|
174
|
+
} catch {
|
|
175
|
+
return { ...raw, content: undefined, disposition: { state: 'unavailable', reason: 'invalid receipt JSON' } };
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
function unavailableLog(repoRoot: string, filePath: string, includeExcerpts: boolean): CapturedObject {
|
|
180
|
+
const raw = readStableFile(repoRoot, filePath, 'local-file');
|
|
181
|
+
if (raw.disposition?.state === 'excluded-sensitive') return raw;
|
|
182
|
+
const recognizedBody = /\/(?:sessions?|tools?)(?:\/|[-_])/i.test(raw.sourceIdentity);
|
|
183
|
+
if (includeExcerpts && recognizedBody && raw.content) {
|
|
184
|
+
const excerpt = redactExcerpt(raw.content);
|
|
185
|
+
if (excerpt !== null) return captureText('local-file', `${raw.sourceIdentity}#excerpt`, excerpt);
|
|
186
|
+
}
|
|
187
|
+
return {
|
|
188
|
+
sourceKind: 'local-file',
|
|
189
|
+
sourceIdentity: raw.sourceIdentity,
|
|
190
|
+
sha256: raw.sha256,
|
|
191
|
+
bytes: raw.bytes,
|
|
192
|
+
disposition: { state: 'unavailable', reason: recognizedBody ? 'body-excerpts-disabled' : 'unknown-log-format' }
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
function collectLocalObjects(repoRoot: string, options: { includeExcerpts?: boolean } = {}): ProcessResult<CapturedObject[]> {
|
|
197
|
+
try {
|
|
198
|
+
const objects: CapturedObject[] = [];
|
|
199
|
+
for (const task of enumerateAllTaskDirs(repoRoot)) {
|
|
200
|
+
for (const filePath of listFiles(task.taskDir)) {
|
|
201
|
+
if (!/\.(?:md|json)$/i.test(filePath)) continue;
|
|
202
|
+
objects.push(readStableFile(repoRoot, filePath, 'local-file'));
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
const logsRoot = path.join(repoRoot, '.agents', 'workspace', 'logs');
|
|
206
|
+
for (const filePath of listFiles(logsRoot)) {
|
|
207
|
+
if (!/\.(?:md|json)$/i.test(filePath)) continue;
|
|
208
|
+
const identity = relativeIdentity(repoRoot, filePath);
|
|
209
|
+
if (/\/entropy-check\/.*\.md$/i.test(identity)) {
|
|
210
|
+
objects.push(readStableFile(repoRoot, filePath, 'operational-report'));
|
|
211
|
+
} else if (/\/(?:delegation|orchestration)(?:\/|[-_]).*\.json$/i.test(identity)) {
|
|
212
|
+
objects.push(structuredReceipt(repoRoot, filePath));
|
|
213
|
+
} else {
|
|
214
|
+
objects.push(unavailableLog(repoRoot, filePath, options.includeExcerpts === true));
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
return { ok: true, value: objects.sort((a, b) => a.sourceIdentity.localeCompare(b.sourceIdentity)) };
|
|
218
|
+
} catch (error) {
|
|
219
|
+
return { ok: false, error: { code: 'LOCAL_SOURCE_UNSTABLE', message: error instanceof Error ? error.message : String(error) } };
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
function githubIdentity(item: unknown): string {
|
|
224
|
+
if (!item || typeof item !== 'object') return '';
|
|
225
|
+
const record = item as Record<string, unknown>;
|
|
226
|
+
for (const key of ['id', 'node_id', 'sha', 'number']) {
|
|
227
|
+
if (typeof record[key] === 'string' || typeof record[key] === 'number') return `${key}:${String(record[key])}`;
|
|
228
|
+
}
|
|
229
|
+
return '';
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
function jsonClone(value: unknown): unknown {
|
|
233
|
+
return JSON.parse(JSON.stringify(value)) as unknown;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
function projectFields(value: unknown, fields: string[]): Record<string, unknown> | undefined {
|
|
237
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) return undefined;
|
|
238
|
+
const record = value as Record<string, unknown>;
|
|
239
|
+
return Object.fromEntries(
|
|
240
|
+
fields.filter((field) => record[field] !== undefined).map((field) => [field, jsonClone(record[field])])
|
|
241
|
+
);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
function projectSensitiveText(value: unknown): unknown {
|
|
245
|
+
if (typeof value !== 'string') return undefined;
|
|
246
|
+
const disposition = inspectPrivacy(value);
|
|
247
|
+
return disposition.state === 'included'
|
|
248
|
+
? value
|
|
249
|
+
: { state: 'unavailable', reason: 'privacy-excluded', ruleId: disposition.ruleId, sha256: sha256(value) };
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
function projectGitHubItem(item: unknown): unknown {
|
|
253
|
+
if (!item || typeof item !== 'object' || Array.isArray(item)) throw new Error('GitHub item must be an object');
|
|
254
|
+
const source = item as Record<string, unknown>;
|
|
255
|
+
const output: Record<string, unknown> = {};
|
|
256
|
+
const scalarFields = [
|
|
257
|
+
'id', 'node_id', 'number', 'html_url', 'url', 'state', 'title', 'created_at', 'updated_at', 'closed_at',
|
|
258
|
+
'merged_at', 'review_state', 'commit_id', 'sha', 'merge_commit_sha', 'milestone'
|
|
259
|
+
];
|
|
260
|
+
for (const field of scalarFields) {
|
|
261
|
+
if (source[field] !== undefined) output[field] = jsonClone(source[field]);
|
|
262
|
+
}
|
|
263
|
+
for (const field of ['user', 'head', 'base', 'author', 'committer']) {
|
|
264
|
+
const projected = projectFields(source[field], ['id', 'login', 'sha', 'ref']);
|
|
265
|
+
if (projected) output[field] = projected;
|
|
266
|
+
}
|
|
267
|
+
for (const field of ['labels', 'assignees']) {
|
|
268
|
+
if (Array.isArray(source[field])) output[field] = jsonClone(source[field]);
|
|
269
|
+
}
|
|
270
|
+
for (const field of ['body', 'message']) {
|
|
271
|
+
const projected = projectSensitiveText(source[field]);
|
|
272
|
+
if (projected !== undefined) output[field] = projected;
|
|
273
|
+
}
|
|
274
|
+
if (Array.isArray(source.parents)) {
|
|
275
|
+
output.parents = source.parents.flatMap((parent) => {
|
|
276
|
+
const projected = projectFields(parent, ['sha']);
|
|
277
|
+
return projected ? [projected] : [];
|
|
278
|
+
});
|
|
279
|
+
}
|
|
280
|
+
const commit = projectFields(source.commit, []);
|
|
281
|
+
if (commit) {
|
|
282
|
+
const sourceCommit = source.commit as Record<string, unknown>;
|
|
283
|
+
const projectedCommit: Record<string, unknown> = {};
|
|
284
|
+
const message = projectSensitiveText(sourceCommit.message);
|
|
285
|
+
if (message !== undefined) projectedCommit.message = message;
|
|
286
|
+
const tree = projectFields(sourceCommit.tree, ['sha']);
|
|
287
|
+
if (tree) projectedCommit.tree = tree;
|
|
288
|
+
for (const field of ['author', 'committer']) {
|
|
289
|
+
const actor = projectFields(sourceCommit[field], ['name', 'email', 'date']);
|
|
290
|
+
if (actor) projectedCommit[field] = actor;
|
|
291
|
+
}
|
|
292
|
+
output.commit = projectedCommit;
|
|
293
|
+
}
|
|
294
|
+
return output;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
function collectGitHubObjects(repoRoot: string, client: GitHubClient = createGitHubClient()): ProcessResult<GitHubCapture> {
|
|
298
|
+
const repoResult = client.json<{ nameWithOwner?: string }>(['repo', 'view', '--json', 'nameWithOwner'], { cwd: repoRoot });
|
|
299
|
+
if (!repoResult.ok) return { ok: false, error: repoResult.error };
|
|
300
|
+
const repository = repoResult.value.nameWithOwner;
|
|
301
|
+
if (!repository || !/^[^/]+\/[^/]+$/.test(repository)) {
|
|
302
|
+
return { ok: false, error: { code: 'INVALID_PLATFORM_RESPONSE', message: 'GitHub repository identity is missing' } };
|
|
303
|
+
}
|
|
304
|
+
const objects: CapturedObject[] = [];
|
|
305
|
+
const evidence: RestCollectionEvidence[] = [];
|
|
306
|
+
const captureEndpoint = (endpoint: string) => {
|
|
307
|
+
const result = fetchRestCollection({
|
|
308
|
+
client,
|
|
309
|
+
endpoint,
|
|
310
|
+
identify: githubIdentity,
|
|
311
|
+
select: (item) => projectGitHubItem(item) as unknown
|
|
312
|
+
});
|
|
313
|
+
if (!result.ok) return result;
|
|
314
|
+
objects.push(...result.value.pageObjects);
|
|
315
|
+
evidence.push(result.value.evidence);
|
|
316
|
+
return result;
|
|
317
|
+
};
|
|
318
|
+
|
|
319
|
+
const issues = captureEndpoint(`repos/${repository}/issues?state=all&sort=created&direction=asc`);
|
|
320
|
+
if (!issues.ok) return issues;
|
|
321
|
+
const pulls = captureEndpoint(`repos/${repository}/pulls?state=all&sort=created&direction=asc`);
|
|
322
|
+
if (!pulls.ok) return pulls;
|
|
323
|
+
|
|
324
|
+
for (const issue of issues.value.items) {
|
|
325
|
+
const number = (issue as Record<string, unknown>).number;
|
|
326
|
+
if (typeof number !== 'number') return { ok: false, error: { code: 'INVALID_PLATFORM_RESPONSE', message: 'Issue number is missing' } };
|
|
327
|
+
const comments = captureEndpoint(`repos/${repository}/issues/${number}/comments`);
|
|
328
|
+
if (!comments.ok) return comments;
|
|
329
|
+
}
|
|
330
|
+
for (const pull of pulls.value.items) {
|
|
331
|
+
const record = pull as Record<string, unknown>;
|
|
332
|
+
const number = record.number;
|
|
333
|
+
const head = record.head as Record<string, unknown> | undefined;
|
|
334
|
+
const headSha = head?.sha;
|
|
335
|
+
if (typeof number !== 'number' || typeof headSha !== 'string') {
|
|
336
|
+
return { ok: false, error: { code: 'INVALID_PLATFORM_RESPONSE', message: 'Pull request identity or head SHA is missing' } };
|
|
337
|
+
}
|
|
338
|
+
for (const suffix of ['comments', 'reviews', 'commits']) {
|
|
339
|
+
const nested = captureEndpoint(`repos/${repository}/pulls/${number}/${suffix}`);
|
|
340
|
+
if (!nested.ok) return nested;
|
|
341
|
+
}
|
|
342
|
+
const current = client.json<unknown>(['api', `repos/${repository}/pulls/${number}`]);
|
|
343
|
+
if (!current.ok) return { ok: false, error: current.error };
|
|
344
|
+
const projected = projectGitHubItem(current.value) as Record<string, unknown>;
|
|
345
|
+
const currentHead = projected.head as Record<string, unknown> | undefined;
|
|
346
|
+
if (currentHead?.sha !== headSha) {
|
|
347
|
+
return { ok: false, error: { code: 'SOURCE_DRIFT', message: `Pull request ${number} head changed during capture` } };
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
return { ok: true, value: { repository, objects, endpoints: evidence } };
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
export { collectGitHubObjects, collectLocalObjects, fetchRestCollection, projectGitHubItem };
|
|
354
|
+
export type { FetchRestCollectionOptions, GitHubCapture, RestCollection };
|
|
@@ -0,0 +1,315 @@
|
|
|
1
|
+
import crypto from 'node:crypto';
|
|
2
|
+
import fs from 'node:fs';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
|
|
5
|
+
import type {
|
|
6
|
+
JsonValue,
|
|
7
|
+
NormalizedRecord,
|
|
8
|
+
QualityFinding,
|
|
9
|
+
RepairAction,
|
|
10
|
+
RestCollectionEvidence,
|
|
11
|
+
SnapshotManifest,
|
|
12
|
+
StoredObjectEvidence
|
|
13
|
+
} from './types.ts';
|
|
14
|
+
|
|
15
|
+
type ObjectWrite = { sha256: string; path: string; created: boolean };
|
|
16
|
+
type ObjectStore = { put(bytes: Buffer): ObjectWrite; pathFor(sha256: string): string };
|
|
17
|
+
|
|
18
|
+
type PublishInput = {
|
|
19
|
+
scope: 'all' | 'local' | 'github';
|
|
20
|
+
repository: string;
|
|
21
|
+
observedFrom: string;
|
|
22
|
+
observedTo: string;
|
|
23
|
+
objects: StoredObjectEvidence[];
|
|
24
|
+
endpoints: RestCollectionEvidence[];
|
|
25
|
+
excerptsEnabled: boolean;
|
|
26
|
+
records: NormalizedRecord[];
|
|
27
|
+
quality: QualityFinding[];
|
|
28
|
+
repairs: RepairAction[];
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
type VerificationResult = {
|
|
32
|
+
ok: boolean;
|
|
33
|
+
snapshotId: string;
|
|
34
|
+
snapshotPath: string | null;
|
|
35
|
+
errors: string[];
|
|
36
|
+
manifest?: SnapshotManifest;
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
function sha256(bytes: Buffer | string): string {
|
|
40
|
+
return crypto.createHash('sha256').update(bytes).digest('hex');
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function compareCodePoints(left: string, right: string): number {
|
|
44
|
+
const a = Array.from(left, (char) => char.codePointAt(0)!);
|
|
45
|
+
const b = Array.from(right, (char) => char.codePointAt(0)!);
|
|
46
|
+
for (let index = 0; index < Math.min(a.length, b.length); index += 1) {
|
|
47
|
+
if (a[index] !== b[index]) return a[index]! - b[index]!;
|
|
48
|
+
}
|
|
49
|
+
return a.length - b.length;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function canonicalString(value: unknown, seen: Set<object>): string {
|
|
53
|
+
if (value === null || typeof value === 'boolean' || typeof value === 'string') {
|
|
54
|
+
return JSON.stringify(value);
|
|
55
|
+
}
|
|
56
|
+
if (typeof value === 'number') {
|
|
57
|
+
if (!Number.isFinite(value)) throw new Error('canonical JSON accepts only finite JSON values');
|
|
58
|
+
return JSON.stringify(value);
|
|
59
|
+
}
|
|
60
|
+
if (Array.isArray(value)) {
|
|
61
|
+
if (seen.has(value)) throw new Error('canonical JSON accepts only acyclic JSON values');
|
|
62
|
+
seen.add(value);
|
|
63
|
+
const output = `[${value.map((entry) => canonicalString(entry, seen)).join(',')}]`;
|
|
64
|
+
seen.delete(value);
|
|
65
|
+
return output;
|
|
66
|
+
}
|
|
67
|
+
if (typeof value === 'object') {
|
|
68
|
+
if (seen.has(value)) throw new Error('canonical JSON accepts only acyclic JSON values');
|
|
69
|
+
seen.add(value);
|
|
70
|
+
const record = value as Record<string, unknown>;
|
|
71
|
+
const fields = Object.keys(record)
|
|
72
|
+
.sort(compareCodePoints)
|
|
73
|
+
.map((key) => `${JSON.stringify(key)}:${canonicalString(record[key], seen)}`);
|
|
74
|
+
seen.delete(value);
|
|
75
|
+
return `{${fields.join(',')}}`;
|
|
76
|
+
}
|
|
77
|
+
throw new Error('canonical JSON accepts only JSON values');
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function canonicalJsonBytes(value: JsonValue | unknown): Buffer {
|
|
81
|
+
return Buffer.from(canonicalString(value, new Set()), 'utf8');
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function ensurePrivateDirectory(directory: string): void {
|
|
85
|
+
fs.mkdirSync(directory, { recursive: true, mode: 0o700 });
|
|
86
|
+
if (process.platform !== 'win32') fs.chmodSync(directory, 0o700);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function writeExclusive(filePath: string, bytes: Buffer): boolean {
|
|
90
|
+
ensurePrivateDirectory(path.dirname(filePath));
|
|
91
|
+
try {
|
|
92
|
+
const fd = fs.openSync(filePath, 'wx', 0o600);
|
|
93
|
+
try {
|
|
94
|
+
fs.writeFileSync(fd, bytes);
|
|
95
|
+
fs.fsyncSync(fd);
|
|
96
|
+
} finally {
|
|
97
|
+
fs.closeSync(fd);
|
|
98
|
+
}
|
|
99
|
+
return true;
|
|
100
|
+
} catch (error) {
|
|
101
|
+
if ((error as NodeJS.ErrnoException).code !== 'EEXIST') throw error;
|
|
102
|
+
return false;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function createObjectStore(root: string): ObjectStore {
|
|
107
|
+
const objectRoot = path.join(root, 'objects', 'sha256');
|
|
108
|
+
ensurePrivateDirectory(objectRoot);
|
|
109
|
+
return {
|
|
110
|
+
pathFor(digest: string) {
|
|
111
|
+
if (!/^[a-f0-9]{64}$/.test(digest)) throw new Error(`invalid SHA-256: ${digest}`);
|
|
112
|
+
return path.join(objectRoot, digest.slice(0, 2), digest);
|
|
113
|
+
},
|
|
114
|
+
put(bytes: Buffer) {
|
|
115
|
+
const digest = sha256(bytes);
|
|
116
|
+
const objectPath = path.join(objectRoot, digest.slice(0, 2), digest);
|
|
117
|
+
const created = writeExclusive(objectPath, bytes);
|
|
118
|
+
const stored = fs.readFileSync(objectPath);
|
|
119
|
+
if (sha256(stored) !== digest) throw new Error(`CAS hash mismatch for ${digest}`);
|
|
120
|
+
return { sha256: digest, path: objectPath, created };
|
|
121
|
+
}
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function dispositionCounts(objects: StoredObjectEvidence[]): SnapshotManifest['dispositionCounts'] {
|
|
126
|
+
const counts: SnapshotManifest['dispositionCounts'] = { included: 0, 'excluded-sensitive': 0, unavailable: 0 };
|
|
127
|
+
for (const object of objects) counts[object.disposition?.state ?? 'included'] += 1;
|
|
128
|
+
return counts;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function jsonLine(value: JsonValue | unknown): string {
|
|
132
|
+
return `${canonicalJsonBytes(value).toString('utf8')}\n`;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function writePrivateFile(filePath: string, content: string | Buffer): void {
|
|
136
|
+
fs.writeFileSync(filePath, content, { mode: 0o600, flag: 'wx' });
|
|
137
|
+
if (process.platform !== 'win32') fs.chmodSync(filePath, 0o600);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function manifestDigest(manifest: Omit<SnapshotManifest, 'manifestSha256'>): string {
|
|
141
|
+
return sha256(canonicalJsonBytes(manifest));
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function publishSnapshot(root: string, input: PublishInput): { snapshotId: string; path: string; created: boolean } {
|
|
145
|
+
ensurePrivateDirectory(root);
|
|
146
|
+
ensurePrivateDirectory(path.join(root, 'repairs'));
|
|
147
|
+
const date = new Date(input.observedTo);
|
|
148
|
+
if (!Number.isFinite(date.getTime())) throw new Error('observedTo must be an ISO timestamp');
|
|
149
|
+
|
|
150
|
+
const identity = {
|
|
151
|
+
scope: input.scope,
|
|
152
|
+
repository: input.repository,
|
|
153
|
+
observedFrom: input.observedFrom,
|
|
154
|
+
observedTo: input.observedTo,
|
|
155
|
+
objects: input.objects,
|
|
156
|
+
endpoints: input.endpoints,
|
|
157
|
+
excerptsEnabled: input.excerptsEnabled,
|
|
158
|
+
dispositionCounts: dispositionCounts(input.objects),
|
|
159
|
+
recordCount: input.records.length,
|
|
160
|
+
findingCount: input.quality.length,
|
|
161
|
+
repairCount: input.repairs.length
|
|
162
|
+
};
|
|
163
|
+
const identityDigest = sha256(canonicalJsonBytes(identity));
|
|
164
|
+
const stamp = input.observedTo.replace(/[-:]/g, '').replace(/\.\d{3}Z$/, 'Z');
|
|
165
|
+
const snapshotId = `${stamp}-${identityDigest.slice(0, 12)}`;
|
|
166
|
+
const partialManifest: Omit<SnapshotManifest, 'manifestSha256'> = {
|
|
167
|
+
schema: 'raw-manifest/v1',
|
|
168
|
+
snapshotId,
|
|
169
|
+
...identity,
|
|
170
|
+
privacyPolicyVersion: 'process-data-privacy/v1'
|
|
171
|
+
};
|
|
172
|
+
const manifest: SnapshotManifest = { ...partialManifest, manifestSha256: manifestDigest(partialManifest) };
|
|
173
|
+
|
|
174
|
+
const stagingRoot = path.join(root, '.staging');
|
|
175
|
+
ensurePrivateDirectory(stagingRoot);
|
|
176
|
+
const staging = fs.mkdtempSync(path.join(stagingRoot, `${snapshotId}-`));
|
|
177
|
+
if (process.platform !== 'win32') fs.chmodSync(staging, 0o700);
|
|
178
|
+
try {
|
|
179
|
+
writePrivateFile(path.join(staging, 'manifest.json'), jsonLine(manifest));
|
|
180
|
+
writePrivateFile(
|
|
181
|
+
path.join(staging, 'normalized.jsonl'),
|
|
182
|
+
input.records.map((record) => jsonLine({ schema: 'normalized/v1', ...record })).join('')
|
|
183
|
+
);
|
|
184
|
+
writePrivateFile(path.join(staging, 'quality.json'), jsonLine({ schema: 'quality/v1', findings: input.quality }));
|
|
185
|
+
writePrivateFile(
|
|
186
|
+
path.join(staging, 'repair-manifest.jsonl'),
|
|
187
|
+
input.repairs.map((repair) => jsonLine({ schema: 'repair/v1', ...repair })).join('')
|
|
188
|
+
);
|
|
189
|
+
|
|
190
|
+
const destination = path.join(
|
|
191
|
+
root,
|
|
192
|
+
'snapshots',
|
|
193
|
+
String(date.getUTCFullYear()),
|
|
194
|
+
String(date.getUTCMonth() + 1).padStart(2, '0'),
|
|
195
|
+
String(date.getUTCDate()).padStart(2, '0'),
|
|
196
|
+
snapshotId
|
|
197
|
+
);
|
|
198
|
+
ensurePrivateDirectory(path.dirname(destination));
|
|
199
|
+
if (fs.existsSync(destination)) {
|
|
200
|
+
const existing = fs.readFileSync(path.join(destination, 'manifest.json'), 'utf8');
|
|
201
|
+
if (existing !== jsonLine(manifest)) throw new Error(`snapshot collision: ${snapshotId}`);
|
|
202
|
+
fs.rmSync(staging, { recursive: true, force: true });
|
|
203
|
+
return { snapshotId, path: destination, created: false };
|
|
204
|
+
}
|
|
205
|
+
fs.renameSync(staging, destination);
|
|
206
|
+
return { snapshotId, path: destination, created: true };
|
|
207
|
+
} catch (error) {
|
|
208
|
+
fs.rmSync(staging, { recursive: true, force: true });
|
|
209
|
+
throw error;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
function findSnapshot(root: string, snapshotId: string): string | null {
|
|
214
|
+
const snapshotRoot = path.join(root, 'snapshots');
|
|
215
|
+
if (!fs.existsSync(snapshotRoot)) return null;
|
|
216
|
+
const stack = [snapshotRoot];
|
|
217
|
+
while (stack.length > 0) {
|
|
218
|
+
const current = stack.pop()!;
|
|
219
|
+
for (const entry of fs.readdirSync(current, { withFileTypes: true })) {
|
|
220
|
+
if (!entry.isDirectory()) continue;
|
|
221
|
+
const candidate = path.join(current, entry.name);
|
|
222
|
+
if (entry.name === snapshotId && fs.existsSync(path.join(candidate, 'manifest.json'))) return candidate;
|
|
223
|
+
stack.push(candidate);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
return null;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
function verifySnapshot(root: string, snapshotId: string): VerificationResult {
|
|
230
|
+
const snapshotPath = findSnapshot(root, snapshotId);
|
|
231
|
+
if (!snapshotPath) return { ok: false, snapshotId, snapshotPath: null, errors: ['snapshot not found'] };
|
|
232
|
+
const errors: string[] = [];
|
|
233
|
+
const checkMode = (target: string, expected: number, label: string) => {
|
|
234
|
+
if (process.platform === 'win32' || !fs.existsSync(target)) return;
|
|
235
|
+
const actual = fs.statSync(target).mode & 0o777;
|
|
236
|
+
if (actual !== expected) errors.push(`${label} permission mismatch: expected ${expected.toString(8)}, got ${actual.toString(8)}`);
|
|
237
|
+
};
|
|
238
|
+
checkMode(snapshotPath, 0o700, 'snapshot directory');
|
|
239
|
+
for (const file of ['manifest.json', 'normalized.jsonl', 'quality.json', 'repair-manifest.jsonl']) {
|
|
240
|
+
checkMode(path.join(snapshotPath, file), 0o600, file);
|
|
241
|
+
}
|
|
242
|
+
let manifest: SnapshotManifest;
|
|
243
|
+
try {
|
|
244
|
+
manifest = JSON.parse(fs.readFileSync(path.join(snapshotPath, 'manifest.json'), 'utf8')) as SnapshotManifest;
|
|
245
|
+
} catch {
|
|
246
|
+
return { ok: false, snapshotId, snapshotPath, errors: ['manifest is invalid JSON'] };
|
|
247
|
+
}
|
|
248
|
+
if (manifest.schema !== 'raw-manifest/v1') errors.push(`unsupported manifest schema: ${manifest.schema}`);
|
|
249
|
+
if (manifest.snapshotId !== snapshotId) errors.push('snapshot identity mismatch');
|
|
250
|
+
if (typeof manifest.excerptsEnabled !== 'boolean') errors.push('manifest excerpts opt-in is invalid');
|
|
251
|
+
if (!manifest.dispositionCounts || canonicalJsonBytes(manifest.dispositionCounts).toString('utf8') !== canonicalJsonBytes(dispositionCounts(manifest.objects)).toString('utf8')) {
|
|
252
|
+
errors.push('manifest disposition counts mismatch');
|
|
253
|
+
}
|
|
254
|
+
const { manifestSha256, ...partial } = manifest;
|
|
255
|
+
if (manifestDigest(partial) !== manifestSha256) errors.push('manifest hash mismatch');
|
|
256
|
+
const store = createObjectStore(root);
|
|
257
|
+
try {
|
|
258
|
+
const lines = fs.readFileSync(path.join(snapshotPath, 'normalized.jsonl'), 'utf8').split('\n').filter(Boolean);
|
|
259
|
+
const ids = new Set<string>();
|
|
260
|
+
for (const line of lines) {
|
|
261
|
+
const record = JSON.parse(line) as { schema?: string; recordId?: string };
|
|
262
|
+
if (record.schema !== 'normalized/v1' || typeof record.recordId !== 'string') {
|
|
263
|
+
errors.push('normalized record schema is invalid');
|
|
264
|
+
continue;
|
|
265
|
+
}
|
|
266
|
+
if (ids.has(record.recordId)) errors.push(`duplicate normalized record: ${record.recordId}`);
|
|
267
|
+
ids.add(record.recordId);
|
|
268
|
+
}
|
|
269
|
+
if (lines.length !== manifest.recordCount) errors.push('normalized record count mismatch');
|
|
270
|
+
const quality = JSON.parse(fs.readFileSync(path.join(snapshotPath, 'quality.json'), 'utf8')) as { schema?: string; findings?: unknown[] };
|
|
271
|
+
if (quality.schema !== 'quality/v1' || !Array.isArray(quality.findings)) errors.push('quality schema is invalid');
|
|
272
|
+
else if (quality.findings.length !== manifest.findingCount) errors.push('quality finding count mismatch');
|
|
273
|
+
const repairLines = fs.readFileSync(path.join(snapshotPath, 'repair-manifest.jsonl'), 'utf8').split('\n').filter(Boolean);
|
|
274
|
+
for (const line of repairLines) {
|
|
275
|
+
const repair = JSON.parse(line) as { schema?: string; repairId?: string };
|
|
276
|
+
if (repair.schema !== 'repair/v1' || typeof repair.repairId !== 'string') errors.push('repair manifest schema is invalid');
|
|
277
|
+
}
|
|
278
|
+
if (repairLines.length !== manifest.repairCount) errors.push('repair manifest count mismatch');
|
|
279
|
+
} catch {
|
|
280
|
+
errors.push('snapshot derived files are missing or invalid');
|
|
281
|
+
}
|
|
282
|
+
for (const object of manifest.objects) {
|
|
283
|
+
if (object.disposition?.state && object.disposition.state !== 'included') continue;
|
|
284
|
+
try {
|
|
285
|
+
const objectPath = store.pathFor(object.sha256);
|
|
286
|
+
checkMode(path.dirname(objectPath), 0o700, `object directory ${object.sha256.slice(0, 2)}`);
|
|
287
|
+
checkMode(objectPath, 0o600, `object ${object.sourceIdentity}`);
|
|
288
|
+
const bytes = fs.readFileSync(objectPath);
|
|
289
|
+
if (sha256(bytes) !== object.sha256) errors.push(`object hash mismatch: ${object.sourceIdentity}`);
|
|
290
|
+
if (bytes.length !== object.bytes) errors.push(`object byte count mismatch: ${object.sourceIdentity}`);
|
|
291
|
+
} catch {
|
|
292
|
+
errors.push(`object missing: ${object.sourceIdentity}`);
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
for (const page of manifest.endpoints.flatMap((endpoint) => endpoint.pages)) {
|
|
296
|
+
try {
|
|
297
|
+
if (sha256(fs.readFileSync(store.pathFor(page.canonicalSha256))) !== page.canonicalSha256) {
|
|
298
|
+
errors.push(`page hash mismatch: ${page.index}`);
|
|
299
|
+
}
|
|
300
|
+
} catch {
|
|
301
|
+
errors.push(`page object missing: ${page.canonicalSha256}`);
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
return { ok: errors.length === 0, snapshotId, snapshotPath, errors, manifest };
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
export {
|
|
308
|
+
canonicalJsonBytes,
|
|
309
|
+
createObjectStore,
|
|
310
|
+
findSnapshot,
|
|
311
|
+
publishSnapshot,
|
|
312
|
+
sha256,
|
|
313
|
+
verifySnapshot
|
|
314
|
+
};
|
|
315
|
+
export type { ObjectStore, ObjectWrite, PublishInput, VerificationResult };
|