@playcraft/cli 0.1.53-beta → 0.1.54
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 +3 -62
- package/dist/agent/local-backend.js +1 -1
- package/dist/cli-root-help.js +0 -2
- package/dist/commands/build-all.js +19 -5
- package/dist/commands/build.js +28 -15
- package/dist/commands/create.js +33 -26
- package/dist/commands/login.js +42 -84
- package/dist/commands/skills.js +0 -4
- package/dist/commands/tools-3d.js +0 -11
- package/dist/commands/tools-generation.js +0 -6
- package/dist/commands/tools.js +0 -2
- package/dist/config.js +4 -59
- package/dist/index.js +3 -42
- package/dist/remix/clone.js +0 -2
- package/dist/remix/init-template.js +0 -2
- package/dist/remix/pull.js +0 -2
- package/dist/remix/push.js +1 -21
- package/dist/utils/agent-api-client.js +30 -210
- package/dist/utils/playcraft-http-client.js +4 -6
- package/package.json +5 -15
- package/SKILLS_REGISTRY.md +0 -149
- package/dist/auth/account-activation.js +0 -157
- package/dist/auth/messages.js +0 -30
- package/dist/commands/build-temp.js +0 -26
- package/dist/commands/platform-skills.generated.js +0 -19
- package/dist/commands/playable.js +0 -53
- package/dist/commands/preview-runtime.js +0 -21
- package/dist/commands/tools-batch.js +0 -113
- package/dist/commands/video.js +0 -122
- package/dist/commands/workspace-runtime.js +0 -54
- package/dist/commands/workspace.js +0 -40
- package/dist/preview-runtime/distribution.js +0 -77
- package/dist/project-skills/builtin-import.js +0 -225
- package/dist/project-skills/commands.js +0 -77
- package/dist/project-skills/lifecycle.js +0 -28
- package/dist/project-skills/local-run.js +0 -112
- package/dist/project-skills/local-store.js +0 -221
- package/dist/project-skills/messages.js +0 -80
- package/dist/project-skills/reconcile.js +0 -62
- package/dist/project-skills/remote-cache.js +0 -51
- package/dist/project-skills/validation.js +0 -19
- package/dist/recording/messages.js +0 -40
- package/dist/recording/project-digest.js +0 -85
- package/dist/recording/recorder.js +0 -293
- package/dist/recording/scenario.js +0 -50
- package/dist/skill-registry/client.js +0 -345
- package/dist/skill-registry/commands.js +0 -180
- package/dist/skill-registry/evaluation-client.js +0 -156
- package/dist/skill-registry/evaluation-commands.js +0 -174
- package/dist/skill-registry/messages.js +0 -177
- package/dist/skill-registry/package.js +0 -275
- package/dist/skill-registry/platform-package.js +0 -100
- package/dist/utils/environment-endpoints.js +0 -17
- package/dist/utils/error-diagnostics.js +0 -20
- package/dist/utils/tool-operation-journal.js +0 -124
- package/dist/utils/video-evidence-git.js +0 -81
- package/dist/video-replication/client.js +0 -234
- package/dist/video-replication/comparison-evidence.js +0 -149
- package/dist/video-replication/comparison-files.js +0 -57
- package/dist/video-replication/comparison-log.js +0 -72
- package/dist/video-replication/comparison-messages.js +0 -104
- package/dist/video-replication/comparison-output.js +0 -63
- package/dist/video-replication/comparison-preflight.js +0 -66
- package/dist/video-replication/comparison.js +0 -230
- package/dist/video-replication/diagnostics.js +0 -41
- package/dist/video-replication/evidence.js +0 -165
- package/dist/video-replication/media.js +0 -112
- package/dist/video-replication/messages.js +0 -97
- package/dist/workspace/messages.js +0 -67
- package/dist/workspace/transfer-client.js +0 -237
- package/dist/workspace/transfer-errors.js +0 -48
- package/dist/workspace-runtime/batch/content-changes.js +0 -30
- package/dist/workspace-runtime/batch/control.js +0 -86
- package/dist/workspace-runtime/batch/deliver.js +0 -173
- package/dist/workspace-runtime/batch/freeze.js +0 -101
- package/dist/workspace-runtime/batch/generate.js +0 -249
- package/dist/workspace-runtime/batch/git.js +0 -40
- package/dist/workspace-runtime/batch/occupancy.js +0 -8
- package/dist/workspace-runtime/batch/policy.js +0 -70
- package/dist/workspace-runtime/batch/prepare.js +0 -317
- package/dist/workspace-runtime/batch/processes.js +0 -72
- package/dist/workspace-runtime/batch/records.js +0 -113
- package/dist/workspace-runtime/batch/routes.js +0 -61
- package/dist/workspace-runtime/batch/source-candidate.js +0 -86
- package/dist/workspace-runtime/batch/source-capture.js +0 -146
- package/dist/workspace-runtime/batch/source-failure.js +0 -43
- package/dist/workspace-runtime/batch/source-publish.js +0 -130
- package/dist/workspace-runtime/codex/app-server.js +0 -1065
- package/dist/workspace-runtime/codex/compact.js +0 -46
- package/dist/workspace-runtime/codex/config-toml.js +0 -107
- package/dist/workspace-runtime/codex/delivery-tools.js +0 -144
- package/dist/workspace-runtime/codex/jsonrpc-stdio.js +0 -145
- package/dist/workspace-runtime/codex/launcher-client.js +0 -177
- package/dist/workspace-runtime/codex/loopback.js +0 -146
- package/dist/workspace-runtime/codex/mcp-config.js +0 -39
- package/dist/workspace-runtime/codex/model-broker.js +0 -263
- package/dist/workspace-runtime/codex/native-adapter.js +0 -9
- package/dist/workspace-runtime/codex/permissions.js +0 -79
- package/dist/workspace-runtime/codex/thread-image.js +0 -38
- package/dist/workspace-runtime/codex/tool-response.js +0 -10
- package/dist/workspace-runtime/codex/ui-instructions.js +0 -4
- package/dist/workspace-runtime/codex/ui-tools.js +0 -203
- package/dist/workspace-runtime/codex/work-mode.js +0 -15
- package/dist/workspace-runtime/main.js +0 -10
- package/dist/workspace-runtime/persistence/journal.js +0 -365
- package/dist/workspace-runtime/persistence/native-subagents.js +0 -162
- package/dist/workspace-runtime/persistence/sqlite-client.js +0 -77
- package/dist/workspace-runtime/persistence/sqlite-worker.js +0 -1161
- package/dist/workspace-runtime/persistence/stopped-execution.js +0 -77
- package/dist/workspace-runtime/previews/diagnostics.js +0 -44
- package/dist/workspace-runtime/previews/output.js +0 -36
- package/dist/workspace-runtime/previews/publish.js +0 -283
- package/dist/workspace-runtime/previews/runtime-preview.js +0 -251
- package/dist/workspace-runtime/previews/single-file.js +0 -43
- package/dist/workspace-runtime/processes/managed-writes.js +0 -69
- package/dist/workspace-runtime/serve.js +0 -441
- package/dist/workspace-runtime/server/accept-operation.js +0 -61
- package/dist/workspace-runtime/server/asset-transfers.js +0 -63
- package/dist/workspace-runtime/server/auth.js +0 -21
- package/dist/workspace-runtime/server/connectivity.js +0 -211
- package/dist/workspace-runtime/server/desktop-host.js +0 -429
- package/dist/workspace-runtime/server/desktop-preview-config.js +0 -81
- package/dist/workspace-runtime/server/desktop-preview-service.js +0 -157
- package/dist/workspace-runtime/server/desktop-preview-static.js +0 -53
- package/dist/workspace-runtime/server/desktop-preview.js +0 -189
- package/dist/workspace-runtime/server/dispatch.js +0 -1655
- package/dist/workspace-runtime/server/endpoints.js +0 -21
- package/dist/workspace-runtime/server/execution-group.js +0 -90
- package/dist/workspace-runtime/server/http.js +0 -1339
- package/dist/workspace-runtime/server/lock.js +0 -53
- package/dist/workspace-runtime/server/operation-record.js +0 -33
- package/dist/workspace-runtime/server/rfb-watch-gate.js +0 -168
- package/dist/workspace-runtime/server/session-restore.js +0 -84
- package/dist/workspace-runtime/server/source-uploader.js +0 -138
- package/dist/workspace-runtime/server/submit-operation.js +0 -41
- package/dist/workspace-runtime/server/terminal-host.js +0 -366
- package/dist/workspace-runtime/server/types.js +0 -1
- package/dist/workspace-runtime/server/workspace-transfer.js +0 -168
- package/dist/workspace-runtime/verify-artifact.js +0 -59
- package/dist/workspace-runtime/workspaces/append-only-log.js +0 -62
- package/dist/workspace-runtime/workspaces/archive.js +0 -207
- package/dist/workspace-runtime/workspaces/artifact-events.js +0 -55
- package/dist/workspace-runtime/workspaces/artifact-files.js +0 -199
- package/dist/workspace-runtime/workspaces/artifact-receipts.js +0 -89
- package/dist/workspace-runtime/workspaces/artifacts.js +0 -282
- package/dist/workspace-runtime/workspaces/asset-library-import.js +0 -387
- package/dist/workspace-runtime/workspaces/asset-publication-access.js +0 -25
- package/dist/workspace-runtime/workspaces/asset-transfer-dependencies.js +0 -81
- package/dist/workspace-runtime/workspaces/asset-transfer-publish.js +0 -438
- package/dist/workspace-runtime/workspaces/asset-transfer-storage.js +0 -389
- package/dist/workspace-runtime/workspaces/attachment-content.js +0 -73
- package/dist/workspace-runtime/workspaces/attachments.js +0 -185
- package/dist/workspace-runtime/workspaces/batch-commit.js +0 -142
- package/dist/workspace-runtime/workspaces/binary-content.js +0 -65
- package/dist/workspace-runtime/workspaces/binary-write.js +0 -117
- package/dist/workspace-runtime/workspaces/cli-context.js +0 -23
- package/dist/workspace-runtime/workspaces/cli-credentials.js +0 -115
- package/dist/workspace-runtime/workspaces/composer-context.js +0 -147
- package/dist/workspace-runtime/workspaces/context-error.js +0 -2
- package/dist/workspace-runtime/workspaces/delivery-build.js +0 -331
- package/dist/workspace-runtime/workspaces/delivery-store.js +0 -168
- package/dist/workspace-runtime/workspaces/delivery.js +0 -64
- package/dist/workspace-runtime/workspaces/dependencies.js +0 -279
- package/dist/workspace-runtime/workspaces/developer-command.js +0 -98
- package/dist/workspace-runtime/workspaces/directories.js +0 -132
- package/dist/workspace-runtime/workspaces/directory-access.js +0 -37
- package/dist/workspace-runtime/workspaces/directory-attachments.js +0 -245
- package/dist/workspace-runtime/workspaces/directory-create.js +0 -57
- package/dist/workspace-runtime/workspaces/directory-maintenance.js +0 -111
- package/dist/workspace-runtime/workspaces/directory-mutations.js +0 -238
- package/dist/workspace-runtime/workspaces/directory-revisions.js +0 -56
- package/dist/workspace-runtime/workspaces/directory-stats.js +0 -49
- package/dist/workspace-runtime/workspaces/disk-snapshot.js +0 -31
- package/dist/workspace-runtime/workspaces/empty-template.js +0 -42
- package/dist/workspace-runtime/workspaces/file-content.js +0 -48
- package/dist/workspace-runtime/workspaces/files-search.js +0 -116
- package/dist/workspace-runtime/workspaces/files.js +0 -150
- package/dist/workspace-runtime/workspaces/git-credential-broker.js +0 -312
- package/dist/workspace-runtime/workspaces/git-credential-helper.js +0 -68
- package/dist/workspace-runtime/workspaces/git-direct-store.js +0 -155
- package/dist/workspace-runtime/workspaces/git.js +0 -670
- package/dist/workspace-runtime/workspaces/instructions.js +0 -56
- package/dist/workspace-runtime/workspaces/json5-edit.js +0 -313
- package/dist/workspace-runtime/workspaces/library-attachments.js +0 -268
- package/dist/workspace-runtime/workspaces/operation-log.js +0 -23
- package/dist/workspace-runtime/workspaces/optional-platform-skills.js +0 -14
- package/dist/workspace-runtime/workspaces/parameter-agent-inputs.js +0 -97
- package/dist/workspace-runtime/workspaces/parameter-agent-protocol.js +0 -40
- package/dist/workspace-runtime/workspaces/parameter-agent-store.js +0 -58
- package/dist/workspace-runtime/workspaces/parameter-agent.js +0 -284
- package/dist/workspace-runtime/workspaces/parameter-assets.js +0 -189
- package/dist/workspace-runtime/workspaces/parameters.js +0 -329
- package/dist/workspace-runtime/workspaces/prepare.js +0 -278
- package/dist/workspace-runtime/workspaces/registry.js +0 -251
- package/dist/workspace-runtime/workspaces/remix-references.js +0 -301
- package/dist/workspace-runtime/workspaces/revisions.js +0 -31
- package/dist/workspace-runtime/workspaces/session-image.js +0 -104
- package/dist/workspace-runtime/workspaces/shared-git.js +0 -40
- package/dist/workspace-runtime/workspaces/skill-artifact-loader.js +0 -95
- package/dist/workspace-runtime/workspaces/skills.js +0 -191
- package/dist/workspace-runtime/workspaces/theme-asset-index.js +0 -157
- package/dist/workspace-runtime/workspaces/transfer-quota.js +0 -69
- package/dist/workspace-runtime/workspaces/transfer.js +0 -471
- package/dist/workspace-runtime/workspaces/turn-changes.js +0 -75
- package/dist/workspace-runtime/workspaces/video-attachments.js +0 -49
- package/project-template/.claude/skills/playcraft-replica-validation/SKILL.md +0 -25
- package/project-template/.claude/skills/playcraft-replica-validation/references/contracts.md +0 -79
- package/project-template/.claude/skills/playcraft-video-understanding/SKILL.md +0 -29
- package/project-template/.claude/skills/playcraft-video-understanding/references/contracts.md +0 -37
- package/project-template-v2/.claude/agents/artist.md +0 -82
- package/project-template-v2/.claude/agents/developer.md +0 -153
- package/project-template-v2/.claude/agents/game-designer.md +0 -264
- package/project-template-v2/.claude/agents/refs/artist-art-style-catalog.md +0 -533
- package/project-template-v2/.claude/agents/refs/artist-color-audio-recipes.md +0 -153
- package/project-template-v2/.claude/agents/refs/artist-dimension-axis.md +0 -27
- package/project-template-v2/.claude/agents/refs/artist-master-composite-recipes.md +0 -208
- package/project-template-v2/.claude/agents/refs/atom-skill-library.md +0 -81
- package/project-template-v2/.claude/agents/refs/developer-impl-cookbook.md +0 -432
- package/project-template-v2/.claude/agents/refs/framework-5-component-filter.md +0 -252
- package/project-template-v2/.claude/agents/refs/framework-game-feel-juice.md +0 -266
- package/project-template-v2/.claude/agents/refs/framework-mda.md +0 -147
- package/project-template-v2/.claude/agents/refs/game-designer-gameplay-sufficiency.md +0 -123
- package/project-template-v2/.claude/agents/refs/ta-3d-flip-recipe.md +0 -88
- package/project-template-v2/.claude/agents/refs/ta-atlas-deliverable-standard.md +0 -67
- package/project-template-v2/.claude/agents/refs/ta-batch-pipeline-recipes.md +0 -120
- package/project-template-v2/.claude/agents/refs/ta-image-generation-detail.md +0 -300
- package/project-template-v2/.claude/agents/refs/ta-image-ops-reference.md +0 -495
- package/project-template-v2/.claude/agents/refs/ta-pipeline-cookbook.md +0 -1141
- package/project-template-v2/.claude/agents/refs/ta-tools-reference.md +0 -111
- package/project-template-v2/.claude/agents/refs/ta-vfx-preset-catalog.md +0 -365
- package/project-template-v2/.claude/agents/refs/threejs-cannon-pitfalls.md +0 -412
- package/project-template-v2/.claude/agents/reviewer.md +0 -75
- package/project-template-v2/.claude/agents/technical-artist.md +0 -86
- package/project-template-v2/.claude/hooks/snapshot-milestone.mjs +0 -243
- package/project-template-v2/.claude/hooks/user-prompt.mjs +0 -133
- package/project-template-v2/.claude/settings.json +0 -33
- package/project-template-v2/.claude/skills/brainstorming/SKILL.md +0 -161
- package/project-template-v2/.claude/skills/brainstorming/scripts/frame-template.html +0 -270
- package/project-template-v2/.claude/skills/brainstorming/scripts/helper.js +0 -177
- package/project-template-v2/.claude/skills/brainstorming/scripts/server.cjs +0 -354
- package/project-template-v2/.claude/skills/brainstorming/scripts/start-server.sh +0 -148
- package/project-template-v2/.claude/skills/brainstorming/scripts/stop-server.sh +0 -56
- package/project-template-v2/.claude/skills/brainstorming/scripts/wait-for-selection.sh +0 -62
- package/project-template-v2/.claude/skills/brainstorming/spec-document-reviewer-prompt.md +0 -49
- package/project-template-v2/.claude/skills/brainstorming/visual-companion.md +0 -309
- package/project-template-v2/.claude/skills/playcraft-ad-psychology/SKILL.md +0 -182
- package/project-template-v2/.claude/skills/playcraft-art-style-guide/SKILL.md +0 -123
- package/project-template-v2/.claude/skills/playcraft-asset-state-sheet/SKILL.md +0 -205
- package/project-template-v2/.claude/skills/playcraft-audio-generation/SKILL.md +0 -280
- package/project-template-v2/.claude/skills/playcraft-batch-pipeline/SKILL.md +0 -184
- package/project-template-v2/.claude/skills/playcraft-build-optimizer/SKILL.md +0 -306
- package/project-template-v2/.claude/skills/playcraft-image-generation/SKILL.md +0 -370
- package/project-template-v2/.claude/skills/playcraft-image-generation/reference/build-sprite-sheet.template.mjs +0 -123
- package/project-template-v2/.claude/skills/playcraft-image-generation/reference/compare-style.template.mjs +0 -254
- package/project-template-v2/.claude/skills/playcraft-image-generation/reference/gen-batch-sprite.template.mjs +0 -333
- package/project-template-v2/.claude/skills/playcraft-image-generation/reference/gen-batch.template.mjs +0 -95
- package/project-template-v2/.claude/skills/playcraft-image-generation/reference/gen-edit-variants.template.mjs +0 -118
- package/project-template-v2/.claude/skills/playcraft-image-generation/reference/process-batch.template.mjs +0 -136
- package/project-template-v2/.claude/skills/playcraft-image-generation/reference/prompt-cookbook.md +0 -397
- package/project-template-v2/.claude/skills/playcraft-image-generation/reference/validate-sprite-sheet.template.mjs +0 -296
- package/project-template-v2/.claude/skills/playcraft-image-ops/SKILL.md +0 -122
- package/project-template-v2/.claude/skills/playcraft-image-processing/SKILL.md +0 -219
- package/project-template-v2/.claude/skills/playcraft-masking/SKILL.md +0 -373
- package/project-template-v2/.claude/skills/playcraft-playable-optimization/SKILL.md +0 -161
- package/project-template-v2/.claude/skills/playcraft-research/SKILL.md +0 -215
- package/project-template-v2/.claude/skills/playcraft-skill-recommender/SKILL.md +0 -382
- package/project-template-v2/.claude/skills/playcraft-sprite-generation/SKILL.md +0 -423
- package/project-template-v2/.claude/skills/playcraft-sprite-remix/SKILL.md +0 -158
- package/project-template-v2/.claude/skills/playcraft-sprite-sheet/SKILL.md +0 -100
- package/project-template-v2/.claude/skills/playcraft-storyboard/SKILL.md +0 -167
- package/project-template-v2/.claude/skills/playcraft-style-qa/SKILL.md +0 -270
- package/project-template-v2/.claude/skills/playcraft-text-rendering/SKILL.md +0 -236
- package/project-template-v2/.claude/skills/playcraft-vfx-animation/SKILL.md +0 -130
- package/project-template-v2/.claude/skills/playwright-cli/SKILL.md +0 -390
- package/project-template-v2/.claude/skills/playwright-cli/references/element-attributes.md +0 -23
- package/project-template-v2/.claude/skills/playwright-cli/references/playwright-tests.md +0 -39
- package/project-template-v2/.claude/skills/playwright-cli/references/request-mocking.md +0 -87
- package/project-template-v2/.claude/skills/playwright-cli/references/running-code.md +0 -240
- package/project-template-v2/.claude/skills/playwright-cli/references/session-management.md +0 -226
- package/project-template-v2/.claude/skills/playwright-cli/references/spec-driven-testing.md +0 -308
- package/project-template-v2/.claude/skills/playwright-cli/references/storage-state.md +0 -275
- package/project-template-v2/.claude/skills/playwright-cli/references/test-generation.md +0 -134
- package/project-template-v2/.claude/skills/playwright-cli/references/tracing.md +0 -142
- package/project-template-v2/.claude/skills/playwright-cli/references/video-recording.md +0 -153
- package/project-template-v2/.claude/skills/session-analyzer/SKILL.md +0 -386
- package/project-template-v2/.claude/skills/session-analyzer/scripts/execution-breakdown.mjs +0 -182
- package/project-template-v2/.claude/skills/session-analyzer/scripts/find-turns.mjs +0 -72
- package/project-template-v2/.claude/skills/session-analyzer/scripts/heavy-output.mjs +0 -121
- package/project-template-v2/.claude/skills/session-analyzer/scripts/resolve-session.mjs +0 -102
- package/project-template-v2/.claude/skills/session-analyzer/scripts/subagent-stats.mjs +0 -127
- package/project-template-v2/.claude/skills/session-analyzer/scripts/subagent-tool-timeline.mjs +0 -106
- package/project-template-v2/.claude/skills/session-analyzer/scripts/time-gaps.mjs +0 -128
- package/project-template-v2/.claude/skills/session-analyzer/scripts/turn-timeline.mjs +0 -67
- package/project-template-v2/.claude/snapshot.mjs +0 -263
- package/project-template-v2/.playcraft/skills.lock.json +0 -152
- package/project-template-v2/CLAUDE.md +0 -146
- package/project-template-v2/assets/audio/bgm/.gitkeep +0 -0
- package/project-template-v2/assets/audio/sfx/.gitkeep +0 -0
- package/project-template-v2/assets/bundles/.gitkeep +0 -0
- package/project-template-v2/assets/images/bg/.gitkeep +0 -0
- package/project-template-v2/assets/images/reference/.gitkeep +0 -0
- package/project-template-v2/assets/images/storyboard/.gitkeep +0 -0
- package/project-template-v2/assets/images/tiles/.gitkeep +0 -0
- package/project-template-v2/assets/images/ui/.gitkeep +0 -0
- package/project-template-v2/assets/images/vfx/.gitkeep +0 -0
- package/project-template-v2/assets/models/.gitkeep +0 -0
- package/project-template-v2/docs/harness/iteration-1/context-flow.md +0 -254
- package/project-template-v2/docs/harness/iteration-1/generate-flow.md +0 -91
- package/project-template-v2/docs/harness/iteration-1/ideate-flow.md +0 -214
- package/project-template-v2/docs/harness/iteration-1/optimize-flow.md +0 -75
- package/project-template-v2/docs/harness/iteration-1/wrapup-flow.md +0 -63
- package/project-template-v2/docs/harness/iteration-2/context-flow.md +0 -223
- package/project-template-v2/docs/harness/iteration-2/generate-flow.md +0 -129
- package/project-template-v2/docs/harness/iteration-2/ideate-flow.md +0 -267
- package/project-template-v2/docs/harness/iteration-2/optimize-flow.md +0 -164
- package/project-template-v2/docs/harness/iteration-2/wrapup-flow.md +0 -115
- package/project-template-v2/docs/harness/orchestrator-flow.md +0 -364
- package/project-template-v2/docs/project-state.json +0 -60
- package/project-template-v2/docs/project-state.md +0 -72
- package/project-template-v2/docs/standards/README.md +0 -225
- package/project-template-v2/docs/standards/agent-behavior-standards.md +0 -174
- package/project-template-v2/docs/standards/artifacts/design-brief.md +0 -19
- package/project-template-v2/docs/standards/artifacts/design.md +0 -22
- package/project-template-v2/docs/standards/artifacts/game-code.md +0 -41
- package/project-template-v2/docs/standards/artifacts/todo-list.md +0 -41
- package/project-template-v2/docs/standards/iter1-agent-behavior-standards.md +0 -343
- package/project-template-v2/game/index.ts +0 -18
- package/project-template-v2/globals.d.ts +0 -51
- package/project-template-v2/index.css +0 -34
- package/project-template-v2/index.html +0 -18
- package/project-template-v2/main.ts +0 -9
- package/project-template-v2/package.json +0 -46
- package/project-template-v2/skills/_shared/scripts/dispatch-clear.mjs +0 -31
- package/project-template-v2/skills/_shared/scripts/dispatch-set.mjs +0 -86
- package/project-template-v2/skills/_shared/scripts/dod-check.mjs +0 -153
- package/project-template-v2/skills/_shared/scripts/handoff-append.mjs +0 -70
- package/project-template-v2/skills/_shared/scripts/lib/validator-artifacts.mjs +0 -91
- package/project-template-v2/skills/_shared/scripts/pipeline/dod-config.mjs +0 -131
- package/project-template-v2/skills/_shared/scripts/pipeline/index.mjs +0 -80
- package/project-template-v2/skills/_shared/scripts/pipeline/iteration-1-core.mjs +0 -90
- package/project-template-v2/skills/_shared/scripts/pipeline/iteration-2-wrap.mjs +0 -93
- package/project-template-v2/skills/_shared/scripts/pipeline/iteration-3-visual.mjs +0 -23
- package/project-template-v2/skills/_shared/scripts/render-project-state.mjs +0 -230
- package/project-template-v2/skills/_shared/scripts/state-advance-stage.mjs +0 -58
- package/project-template-v2/skills/_shared/scripts/state-get.mjs +0 -339
- package/project-template-v2/skills/_shared/scripts/state-handoff.mjs +0 -39
- package/project-template-v2/skills/_shared/scripts/state-set.mjs +0 -94
- package/project-template-v2/skills/_shared/scripts/state-store.mjs +0 -783
- package/project-template-v2/skills/_shared/scripts/todo-add.mjs +0 -88
- package/project-template-v2/skills/_shared/scripts/todo-get.mjs +0 -130
- package/project-template-v2/skills/_shared/scripts/todo-remove.mjs +0 -33
- package/project-template-v2/skills/_shared/scripts/todo-set.mjs +0 -47
- package/project-template-v2/skills/_shared/scripts/verify-asset-code-sync.mjs +0 -268
- package/project-template-v2/skills/_shared/scripts/verify-env.mjs +0 -285
- package/project-template-v2/skills/_shared/scripts/verify-placeholders.mjs +0 -161
- package/project-template-v2/skills/playable-autoplay/SKILL.md +0 -176
- package/project-template-v2/skills/playable-autoplay/agents/openai.yaml +0 -4
- package/project-template-v2/skills/playable-debug/SKILL.md +0 -116
- package/project-template-v2/skills/playable-debug/agents/openai.yaml +0 -4
- package/project-template-v2/skills/playable-debug/references/debug-config.md +0 -40
- package/project-template-v2/skills/playable-record/SKILL.md +0 -140
- package/project-template-v2/skills/playable-record/scripts/lib/dev-server.mjs +0 -104
- package/project-template-v2/skills/playable-record/scripts/lib/record-audio-bridge.js +0 -141
- package/project-template-v2/skills/playable-record/scripts/record-playable.mjs +0 -425
- package/project-template-v2/skills/playable-record/scripts/verify-contract.mjs +0 -261
- package/project-template-v2/skills/playable-record/scripts/verify-firstwin.mjs +0 -398
- package/project-template-v2/skills/playable-record/scripts/verify-fusion.mjs +0 -322
- package/project-template-v2/skills/playable-record/scripts/verify-lifecycle.mjs +0 -105
- package/project-template-v2/skills/playable-record/scripts/verify-vlm-video.mjs +0 -230
- package/project-template-v2/skills/playable-validate/SKILL.md +0 -61
- package/project-template-v2/skills/playable-validate/validation-rules.md +0 -14
- package/project-template-v2/skills/playable-verify-ui/SKILL.md +0 -63
- package/project-template-v2/skills/playable-verify-ui/scripts/lib/__init__.py +0 -1
- package/project-template-v2/skills/playable-verify-ui/scripts/lib/config_expr.js +0 -218
- package/project-template-v2/skills/playable-verify-ui/scripts/lib/node_utils.js +0 -51
- package/project-template-v2/skills/playable-verify-ui/scripts/lib/profile_loader.py +0 -59
- package/project-template-v2/skills/playable-verify-ui/scripts/lib/report.py +0 -106
- package/project-template-v2/skills/playable-verify-ui/scripts/lib/runtime_contract.js +0 -116
- package/project-template-v2/skills/playable-verify-ui/scripts/verify_fetch_antipatterns.py +0 -59
- package/project-template-v2/skills/playable-verify-ui/scripts/verify_hardcoded_layout.js +0 -64
- package/project-template-v2/skills/playable-verify-ui/scripts/verify_runtime_contract.js +0 -58
- package/project-template-v2/skills/playable-verify-ui/scripts/verify_text_overlap.py +0 -333
- package/project-template-v2/ta-workspace/scripts/.gitkeep +0 -0
- package/project-template-v2/tsconfig.json +0 -20
- package/project-template-v2/vite.config.ts +0 -27
- package/project-templates/batch-locks/phaser.yaml +0 -922
- package/project-templates/batch-locks/threejs.yaml +0 -978
- package/project-templates/empty/AGENTS.md +0 -41
- package/project-templates/empty/gitignore +0 -33
- package/templates/preview-runtime/v1/README.md +0 -71
- package/templates/preview-runtime/v1/build-preview.mjs +0 -1324
- package/templates/preview-runtime/v1/manifest.json +0 -17
- package/templates/preview-runtime/v1/release-guard.mjs +0 -87
- package/templates/preview-runtime/v1/runtime.d.mts +0 -3
- package/templates/preview-runtime/v1/runtime.d.ts +0 -3
- package/templates/preview-runtime/v1/runtime.mjs +0 -1121
- package/templates/preview-runtime/v1/types/adapters.d.ts +0 -26
- package/templates/preview-runtime/v1/types/preview.d.ts +0 -23
- package/templates/preview-runtime/v1/types/protocol.d.ts +0 -94
- package/templates/preview-runtime/v1/types/runtime.d.ts +0 -14
|
@@ -1,471 +0,0 @@
|
|
|
1
|
-
import { spawn, spawnSync } from 'node:child_process';
|
|
2
|
-
import { createHash, randomUUID } from 'node:crypto';
|
|
3
|
-
import { constants, createWriteStream } from 'node:fs';
|
|
4
|
-
import { chmod, copyFile, lstat, mkdir, mkdtemp, open, readdir, realpath, rename, rm, rmdir, stat, writeFile } from 'node:fs/promises';
|
|
5
|
-
import { tmpdir } from 'node:os';
|
|
6
|
-
import { dirname, join, resolve, sep } from 'node:path';
|
|
7
|
-
import { Transform } from 'node:stream';
|
|
8
|
-
import { pipeline } from 'node:stream/promises';
|
|
9
|
-
import { isCredentialFile } from './git.js';
|
|
10
|
-
const EXCLUDED = new Set(['.git', 'node_modules', '.playcraft-workspace.json', '.playcraft.json']);
|
|
11
|
-
const GIT_TIMEOUT_MS = 15 * 60_000;
|
|
12
|
-
const GIT_OPTIONS = ['-c', 'core.hooksPath=/dev/null', '-c', 'commit.gpgsign=false', '-c', 'core.autocrlf=false',
|
|
13
|
-
'-c', 'user.name=PlayCraft Workspace', '-c', 'user.email=workspace@playcraft.local'];
|
|
14
|
-
const SNAPSHOT_MESSAGE = 'PlayCraft workspace snapshot v1';
|
|
15
|
-
export const RAW_ATTRIBUTES = '* -text -eol -working-tree-encoding -filter -ident\n';
|
|
16
|
-
export class WorkspaceTransferError extends Error {
|
|
17
|
-
code;
|
|
18
|
-
details;
|
|
19
|
-
constructor(code, details) {
|
|
20
|
-
super(code);
|
|
21
|
-
this.code = code;
|
|
22
|
-
this.details = details;
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
function gitEnvironment() {
|
|
26
|
-
return { PATH: process.env.PATH, HOME: tmpdir(), GIT_CONFIG_NOSYSTEM: '1', GIT_CONFIG_GLOBAL: '/dev/null',
|
|
27
|
-
GIT_TERMINAL_PROMPT: '0', GIT_ALLOW_PROTOCOL: 'file', GIT_AUTHOR_NAME: 'PlayCraft Workspace',
|
|
28
|
-
GIT_AUTHOR_EMAIL: 'workspace@playcraft.local', GIT_COMMITTER_NAME: 'PlayCraft Workspace',
|
|
29
|
-
GIT_COMMITTER_EMAIL: 'workspace@playcraft.local', GIT_AUTHOR_DATE: '2000-01-01T00:00:00Z',
|
|
30
|
-
GIT_COMMITTER_DATE: '2000-01-01T00:00:00Z' };
|
|
31
|
-
}
|
|
32
|
-
function excluded(path) {
|
|
33
|
-
const parts = path.split('/');
|
|
34
|
-
return parts.some(part => EXCLUDED.has(part.toLowerCase()) || /^\.playcraft-(?:credential|transfer)-.*\.tmp$/i.test(part)) ||
|
|
35
|
-
parts.some((part, index) => part === '.playcraft' && /^(?:workspace-context(?:\..*)?|video-replication)$/.test(parts[index + 1] ?? '')) ||
|
|
36
|
-
isCredentialFile(path);
|
|
37
|
-
}
|
|
38
|
-
export function transferGit(root, args) {
|
|
39
|
-
const result = spawnSync('git', [...GIT_OPTIONS, ...args], { cwd: root, encoding: 'utf8', env: gitEnvironment(), timeout: GIT_TIMEOUT_MS, maxBuffer: 8 * 1024 * 1024 });
|
|
40
|
-
if (result.status !== 0) {
|
|
41
|
-
const code = result.error?.code;
|
|
42
|
-
throw new WorkspaceTransferError(code === 'ETIMEDOUT' ? 'workspace_transfer_git_timeout'
|
|
43
|
-
: code === 'ENOENT' ? 'workspace_transfer_git_missing' : 'workspace_transfer_git_failed');
|
|
44
|
-
}
|
|
45
|
-
return result.stdout.trim();
|
|
46
|
-
}
|
|
47
|
-
/** Large object operations write directly to disk and remain cancellable. */
|
|
48
|
-
export async function runTransferGit(root, args, options = {}) {
|
|
49
|
-
options.signal?.throwIfAborted();
|
|
50
|
-
return new Promise((resolveResult, reject) => {
|
|
51
|
-
const child = spawn('git', [...GIT_OPTIONS, ...args], { cwd: root, env: gitEnvironment(), stdio: ['ignore', 'pipe', 'pipe'] });
|
|
52
|
-
let stdout = '', stderr = '', failure, timedOut = false;
|
|
53
|
-
const abort = () => { failure = options.signal?.reason ?? new Error('workspace_transfer_cancelled'); child.kill('SIGKILL'); };
|
|
54
|
-
const timeout = setTimeout(() => { timedOut = true; child.kill('SIGKILL'); }, GIT_TIMEOUT_MS);
|
|
55
|
-
options.signal?.addEventListener('abort', abort, { once: true });
|
|
56
|
-
child.stdout.setEncoding('utf8');
|
|
57
|
-
child.stderr.setEncoding('utf8');
|
|
58
|
-
child.stdout.on('data', (chunk) => {
|
|
59
|
-
try {
|
|
60
|
-
if (options.onStdout)
|
|
61
|
-
options.onStdout(chunk);
|
|
62
|
-
else {
|
|
63
|
-
stdout += chunk;
|
|
64
|
-
if (stdout.length > 8 * 1024 * 1024)
|
|
65
|
-
throw new WorkspaceTransferError('workspace_transfer_git_output_too_large');
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
catch (error) {
|
|
69
|
-
failure = error;
|
|
70
|
-
child.kill('SIGKILL');
|
|
71
|
-
}
|
|
72
|
-
});
|
|
73
|
-
child.stderr.on('data', (chunk) => { if (stderr.length < 4096)
|
|
74
|
-
stderr += chunk.slice(0, 4096 - stderr.length); });
|
|
75
|
-
child.once('error', error => {
|
|
76
|
-
failure = new WorkspaceTransferError(error.code === 'ENOENT'
|
|
77
|
-
? 'workspace_transfer_git_missing' : 'workspace_transfer_git_failed');
|
|
78
|
-
});
|
|
79
|
-
child.once('close', code => {
|
|
80
|
-
clearTimeout(timeout);
|
|
81
|
-
options.signal?.removeEventListener('abort', abort);
|
|
82
|
-
if (failure)
|
|
83
|
-
reject(failure);
|
|
84
|
-
else if (timedOut)
|
|
85
|
-
reject(new WorkspaceTransferError('workspace_transfer_git_timeout', { operation: args[0] }));
|
|
86
|
-
else if (code !== 0)
|
|
87
|
-
reject(new WorkspaceTransferError(/no space left|disk full/i.test(stderr)
|
|
88
|
-
? 'workspace_transfer_disk_full' : 'workspace_transfer_git_failed', { operation: args[0], exitCode: code }));
|
|
89
|
-
else
|
|
90
|
-
resolveResult(stdout.trim());
|
|
91
|
-
});
|
|
92
|
-
});
|
|
93
|
-
}
|
|
94
|
-
async function copySnapshot(source, target, signal, hashOnly = false) {
|
|
95
|
-
let bytes = 0, files = 0;
|
|
96
|
-
async function copy(directory, prefix) {
|
|
97
|
-
signal?.throwIfAborted();
|
|
98
|
-
const treeParts = [];
|
|
99
|
-
const directoryInfo = await lstat(directory);
|
|
100
|
-
const directoryReal = await realpath(directory);
|
|
101
|
-
if (!directoryInfo.isDirectory() || directoryInfo.isSymbolicLink() ||
|
|
102
|
-
(directoryReal !== source && !directoryReal.startsWith(`${source}${sep}`)))
|
|
103
|
-
throw new WorkspaceTransferError('workspace_transfer_conflict');
|
|
104
|
-
for (const entry of await readdir(directory, { withFileTypes: true })) {
|
|
105
|
-
const path = prefix ? `${prefix}/${entry.name}` : entry.name;
|
|
106
|
-
if (excluded(path))
|
|
107
|
-
continue;
|
|
108
|
-
signal?.throwIfAborted();
|
|
109
|
-
const from = join(directory, entry.name), to = target ? join(target, path) : undefined;
|
|
110
|
-
const info = await lstat(from);
|
|
111
|
-
if (info.isSymbolicLink())
|
|
112
|
-
continue;
|
|
113
|
-
if (info.isDirectory()) {
|
|
114
|
-
if (to)
|
|
115
|
-
await mkdir(to, { recursive: true });
|
|
116
|
-
const child = await copy(from, path);
|
|
117
|
-
if (child && child !== EMPTY_TREE)
|
|
118
|
-
treeParts.push({ name: entry.name, directory: true,
|
|
119
|
-
value: Buffer.concat([Buffer.from(`40000 ${entry.name}\0`), Buffer.from(child, 'hex')]) });
|
|
120
|
-
}
|
|
121
|
-
else if (info.isFile()) {
|
|
122
|
-
bytes += info.size;
|
|
123
|
-
files++;
|
|
124
|
-
if (!(await realpath(from)).startsWith(`${source}${sep}`))
|
|
125
|
-
throw new WorkspaceTransferError('workspace_transfer_conflict');
|
|
126
|
-
if (!to && !hashOnly)
|
|
127
|
-
continue;
|
|
128
|
-
const handle = await open(from, constants.O_RDONLY | constants.O_NOFOLLOW);
|
|
129
|
-
try {
|
|
130
|
-
const before = await handle.stat();
|
|
131
|
-
if (!before.isFile() || before.dev !== info.dev || before.ino !== info.ino || before.size !== info.size ||
|
|
132
|
-
before.mtimeMs !== info.mtimeMs || before.ctimeMs !== info.ctimeMs)
|
|
133
|
-
throw new WorkspaceTransferError('workspace_transfer_conflict');
|
|
134
|
-
let size = 0;
|
|
135
|
-
const hash = hashOnly ? createHash('sha1').update(`blob ${before.size}\0`) : undefined;
|
|
136
|
-
if (to) {
|
|
137
|
-
const guard = new Transform({ transform(chunk, _encoding, callback) {
|
|
138
|
-
size += chunk.length;
|
|
139
|
-
callback(size > before.size ? new WorkspaceTransferError('workspace_transfer_conflict') : null, chunk);
|
|
140
|
-
} });
|
|
141
|
-
await pipeline(handle.createReadStream({ autoClose: false }), guard, createWriteStream(to, { flags: 'wx', mode: 0o600 }), { signal });
|
|
142
|
-
}
|
|
143
|
-
else {
|
|
144
|
-
for await (const chunk of handle.createReadStream({ autoClose: false })) {
|
|
145
|
-
signal?.throwIfAborted();
|
|
146
|
-
size += chunk.length;
|
|
147
|
-
if (size > before.size)
|
|
148
|
-
throw new WorkspaceTransferError('workspace_transfer_conflict');
|
|
149
|
-
hash.update(chunk);
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
const after = await handle.stat();
|
|
153
|
-
if (size !== before.size || after.size !== before.size || after.mtimeMs !== before.mtimeMs || after.ctimeMs !== before.ctimeMs)
|
|
154
|
-
throw new WorkspaceTransferError('workspace_transfer_conflict');
|
|
155
|
-
if (hash)
|
|
156
|
-
treeParts.push({ name: entry.name, directory: false,
|
|
157
|
-
value: Buffer.concat([Buffer.from(`${info.mode & 0o111 ? '100755' : '100644'} ${entry.name}\0`), hash.digest()]) });
|
|
158
|
-
}
|
|
159
|
-
finally {
|
|
160
|
-
await handle.close();
|
|
161
|
-
}
|
|
162
|
-
if (to)
|
|
163
|
-
await chmod(to, info.mode & 0o111 ? 0o755 : 0o644);
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
|
-
const after = await lstat(directory);
|
|
167
|
-
if (after.dev !== directoryInfo.dev || after.ino !== directoryInfo.ino || after.mtimeMs !== directoryInfo.mtimeMs || after.ctimeMs !== directoryInfo.ctimeMs)
|
|
168
|
-
throw new WorkspaceTransferError('workspace_transfer_conflict');
|
|
169
|
-
if (hashOnly) {
|
|
170
|
-
treeParts.sort((left, right) => Buffer.compare(Buffer.from(left.name + (left.directory ? '/' : '\0')), Buffer.from(right.name + (right.directory ? '/' : '\0'))));
|
|
171
|
-
const length = treeParts.reduce((total, part) => total + part.value.length, 0);
|
|
172
|
-
const hash = createHash('sha1').update(`tree ${length}\0`);
|
|
173
|
-
for (const part of treeParts)
|
|
174
|
-
hash.update(part.value);
|
|
175
|
-
return hash.digest('hex');
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
const tree = await copy(source, '');
|
|
179
|
-
return { bytes, files, ...(tree ? { tree } : {}) };
|
|
180
|
-
}
|
|
181
|
-
const EMPTY_TREE = createHash('sha1').update('tree 0\0').digest('hex');
|
|
182
|
-
export async function inspectWorkspaceTransfer(root, options = {}) {
|
|
183
|
-
return copySnapshot(await realpath(root), undefined, options.signal);
|
|
184
|
-
}
|
|
185
|
-
export async function inspectWorkspaceTree(root, options = {}) {
|
|
186
|
-
const snapshot = await copySnapshot(await realpath(root), undefined, options.signal, true);
|
|
187
|
-
return { bytes: snapshot.bytes, files: snapshot.files, tree: snapshot.tree };
|
|
188
|
-
}
|
|
189
|
-
async function matchesFile(root, path, expected) {
|
|
190
|
-
const target = await safeTarget(root, path);
|
|
191
|
-
const info = await lstat(target).catch(error => { if (['ENOENT', 'ENOTDIR'].includes(error.code))
|
|
192
|
-
return undefined; throw error; });
|
|
193
|
-
if (!expected)
|
|
194
|
-
return !info || info.isDirectory();
|
|
195
|
-
if (!info?.isFile() || Boolean(info.mode & 0o111) !== expected.executable)
|
|
196
|
-
return false;
|
|
197
|
-
const handle = await open(target, constants.O_RDONLY | constants.O_NOFOLLOW);
|
|
198
|
-
try {
|
|
199
|
-
const stat = await handle.stat();
|
|
200
|
-
const hash = createHash(expected.sha.length === 64 ? 'sha256' : 'sha1');
|
|
201
|
-
hash.update(`blob ${stat.size}\0`);
|
|
202
|
-
for await (const chunk of handle.createReadStream({ autoClose: false }))
|
|
203
|
-
hash.update(chunk);
|
|
204
|
-
return hash.digest('hex') === expected.sha;
|
|
205
|
-
}
|
|
206
|
-
finally {
|
|
207
|
-
await handle.close();
|
|
208
|
-
}
|
|
209
|
-
}
|
|
210
|
-
async function removeEmptyParents(root, target) {
|
|
211
|
-
let parent = dirname(target);
|
|
212
|
-
while (parent !== root) {
|
|
213
|
-
if ((await readdir(parent)).length > 0)
|
|
214
|
-
break;
|
|
215
|
-
await rmdir(parent);
|
|
216
|
-
parent = dirname(parent);
|
|
217
|
-
}
|
|
218
|
-
}
|
|
219
|
-
async function installFile(root, path, source, executable) {
|
|
220
|
-
const target = await safeTarget(root, path);
|
|
221
|
-
const existing = await lstat(target).catch(error => { if (error.code === 'ENOENT')
|
|
222
|
-
return undefined; throw error; });
|
|
223
|
-
if (existing?.isDirectory())
|
|
224
|
-
await rmdir(target);
|
|
225
|
-
await mkdir(dirname(target), { recursive: true });
|
|
226
|
-
const stage = join(dirname(target), `.playcraft-transfer-${randomUUID()}.tmp`);
|
|
227
|
-
try {
|
|
228
|
-
await copyFile(source, stage);
|
|
229
|
-
await chmod(stage, executable ? 0o755 : 0o644);
|
|
230
|
-
await rename(stage, target);
|
|
231
|
-
}
|
|
232
|
-
catch (error) {
|
|
233
|
-
await rm(stage, { force: true }).catch(() => undefined);
|
|
234
|
-
await removeEmptyParents(root, target).catch(() => undefined);
|
|
235
|
-
throw error;
|
|
236
|
-
}
|
|
237
|
-
finally {
|
|
238
|
-
await rm(stage, { force: true }).catch(() => undefined);
|
|
239
|
-
}
|
|
240
|
-
}
|
|
241
|
-
/** A fresh history contains only the current portable files, including uncommitted changes. */
|
|
242
|
-
export async function createWorkspaceTransfer(root, options = {}) {
|
|
243
|
-
const temporary = await mkdtemp(join(tmpdir(), 'playcraft-transfer-'));
|
|
244
|
-
const cleanup = () => rm(temporary, { recursive: true, force: true });
|
|
245
|
-
try {
|
|
246
|
-
const checkout = join(temporary, 'checkout');
|
|
247
|
-
await mkdir(checkout);
|
|
248
|
-
const stats = await copySnapshot(await realpath(root), checkout, options.signal);
|
|
249
|
-
await runTransferGit(checkout, ['init', '--initial-branch=main'], options);
|
|
250
|
-
// Portable snapshots preserve bytes even when project attributes request content conversion.
|
|
251
|
-
await writeFile(join(checkout, '.git', 'info', 'attributes'), RAW_ATTRIBUTES);
|
|
252
|
-
await runTransferGit(checkout, ['add', '--force', '--all'], options);
|
|
253
|
-
const tree = await runTransferGit(checkout, ['write-tree'], options);
|
|
254
|
-
const head = await canonicalCommit(checkout, tree, options.signal);
|
|
255
|
-
await runTransferGit(checkout, ['update-ref', 'refs/heads/main', head], options);
|
|
256
|
-
const bundlePath = join(temporary, 'workspace.bundle');
|
|
257
|
-
if (!options.skipBundle)
|
|
258
|
-
await runTransferGit(checkout, ['bundle', 'create', bundlePath, 'HEAD'], options);
|
|
259
|
-
return { bundlePath, tree, head, stats, cleanup };
|
|
260
|
-
}
|
|
261
|
-
catch (error) {
|
|
262
|
-
await cleanup();
|
|
263
|
-
throw error;
|
|
264
|
-
}
|
|
265
|
-
}
|
|
266
|
-
async function canonicalCommit(root, tree, signal) {
|
|
267
|
-
return runTransferGit(root, ['commit-tree', tree, '-m', SNAPSHOT_MESSAGE], { signal });
|
|
268
|
-
}
|
|
269
|
-
/** Only objects introduced since the last cloud snapshot are packed; local history stays local. */
|
|
270
|
-
export async function createWorkspacePushTransfer(root, expectedTree, options = {}) {
|
|
271
|
-
if (!/^[a-f0-9]{40,64}$/.test(expectedTree))
|
|
272
|
-
throw new WorkspaceTransferError('invalid_workspace_transfer');
|
|
273
|
-
const temporary = await mkdtemp(join(tmpdir(), 'playcraft-push-'));
|
|
274
|
-
const cleanup = () => rm(temporary, { recursive: true, force: true });
|
|
275
|
-
try {
|
|
276
|
-
const checkout = join(temporary, 'checkout');
|
|
277
|
-
await runTransferGit(temporary, ['clone', '--shared', '--no-checkout', '--', await realpath(root), checkout], options);
|
|
278
|
-
const tree = await runTransferGit(checkout, ['rev-parse', '--verify', 'HEAD^{tree}'], options);
|
|
279
|
-
const stats = summarizeEntries(await treeEntries(checkout, tree, options.signal));
|
|
280
|
-
try {
|
|
281
|
-
await runTransferGit(checkout, ['cat-file', '-e', `${expectedTree}^{tree}`], options);
|
|
282
|
-
}
|
|
283
|
-
catch (error) {
|
|
284
|
-
if (error instanceof WorkspaceTransferError && error.code === 'workspace_transfer_git_failed')
|
|
285
|
-
throw new WorkspaceTransferError('workspace_transfer_base_missing');
|
|
286
|
-
throw error;
|
|
287
|
-
}
|
|
288
|
-
const base = await canonicalCommit(checkout, expectedTree, options.signal);
|
|
289
|
-
const head = await runTransferGit(checkout, ['commit-tree', tree, '-p', base, '-m', SNAPSHOT_MESSAGE], options);
|
|
290
|
-
const transferRef = `refs/heads/playcraft-transfer-${tree}`;
|
|
291
|
-
await runTransferGit(checkout, ['update-ref', transferRef, head], options);
|
|
292
|
-
await runTransferGit(checkout, ['symbolic-ref', 'HEAD', transferRef], options);
|
|
293
|
-
const bundlePath = join(temporary, 'workspace.bundle');
|
|
294
|
-
await runTransferGit(checkout, ['bundle', 'create', bundlePath, 'HEAD', transferRef, `^${base}`], options);
|
|
295
|
-
return { bundlePath, tree, head, stats, cleanup };
|
|
296
|
-
}
|
|
297
|
-
catch (error) {
|
|
298
|
-
await cleanup();
|
|
299
|
-
throw error;
|
|
300
|
-
}
|
|
301
|
-
}
|
|
302
|
-
async function treeEntries(root, ref = 'HEAD', signal) {
|
|
303
|
-
const entries = [];
|
|
304
|
-
const normalized = new Set();
|
|
305
|
-
let pending = '';
|
|
306
|
-
const parse = (line) => {
|
|
307
|
-
const match = /^(100644|100755) blob ([a-f0-9]{40,64})\s+(\d+)\t(.+)$/s.exec(line);
|
|
308
|
-
if (!match)
|
|
309
|
-
throw new WorkspaceTransferError('invalid_workspace_transfer');
|
|
310
|
-
const path = match[4];
|
|
311
|
-
if (path.length > 1024 || path.includes('\\') || path.startsWith('/') || /[\x00-\x1f\x7f]/.test(path) ||
|
|
312
|
-
path.split('/').some(part => !part || part === '.' || part === '..') || excluded(path))
|
|
313
|
-
throw new WorkspaceTransferError('invalid_workspace_transfer');
|
|
314
|
-
const size = Number(match[3]);
|
|
315
|
-
if (!Number.isSafeInteger(size) || normalized.has(path.toLowerCase()))
|
|
316
|
-
throw new WorkspaceTransferError('invalid_workspace_transfer');
|
|
317
|
-
normalized.add(path.toLowerCase());
|
|
318
|
-
entries.push({ path, sha: match[2], executable: match[1] === '100755', size });
|
|
319
|
-
};
|
|
320
|
-
await runTransferGit(root, ['ls-tree', '-r', '-z', '-l', ref], { signal, onStdout(chunk) {
|
|
321
|
-
pending += chunk;
|
|
322
|
-
let end;
|
|
323
|
-
while ((end = pending.indexOf('\0')) !== -1) {
|
|
324
|
-
parse(pending.slice(0, end));
|
|
325
|
-
pending = pending.slice(end + 1);
|
|
326
|
-
}
|
|
327
|
-
if (pending.length > 1200)
|
|
328
|
-
throw new WorkspaceTransferError('invalid_workspace_transfer');
|
|
329
|
-
} });
|
|
330
|
-
if (pending)
|
|
331
|
-
throw new WorkspaceTransferError('invalid_workspace_transfer');
|
|
332
|
-
return entries;
|
|
333
|
-
}
|
|
334
|
-
function summarizeEntries(entries) {
|
|
335
|
-
const bytes = entries.reduce((total, entry) => total + entry.size, 0);
|
|
336
|
-
if (!Number.isSafeInteger(bytes))
|
|
337
|
-
throw new WorkspaceTransferError('invalid_workspace_transfer');
|
|
338
|
-
return { bytes, files: entries.length };
|
|
339
|
-
}
|
|
340
|
-
export async function inspectWorkspaceCommit(root, ref = 'HEAD', options = {}) {
|
|
341
|
-
const tree = await runTransferGit(root, ['rev-parse', '--verify', '--end-of-options', `${ref}^{tree}`], options);
|
|
342
|
-
return summarizeEntries(await treeEntries(root, tree, options.signal));
|
|
343
|
-
}
|
|
344
|
-
async function safeTarget(root, path) {
|
|
345
|
-
const target = resolve(root, path);
|
|
346
|
-
if (!target.startsWith(`${root}${sep}`))
|
|
347
|
-
throw new WorkspaceTransferError('invalid_workspace_transfer');
|
|
348
|
-
let current = root;
|
|
349
|
-
for (const segment of path.split('/')) {
|
|
350
|
-
current = join(current, segment);
|
|
351
|
-
const info = await lstat(current).catch(error => { if (['ENOENT', 'ENOTDIR'].includes(error.code))
|
|
352
|
-
return undefined; throw error; });
|
|
353
|
-
if (info?.isSymbolicLink())
|
|
354
|
-
throw new WorkspaceTransferError('workspace_transfer_symlink');
|
|
355
|
-
}
|
|
356
|
-
return target;
|
|
357
|
-
}
|
|
358
|
-
/** Compare the entire checkout before applying local commits, so cloud edits are never overwritten. */
|
|
359
|
-
export async function applyWorkspaceTransfer(root, bundle, expectedTree, options = {}) {
|
|
360
|
-
if (!/^[a-f0-9]{40,64}$/.test(expectedTree))
|
|
361
|
-
throw new WorkspaceTransferError('invalid_workspace_transfer');
|
|
362
|
-
options.signal?.throwIfAborted();
|
|
363
|
-
const rootReal = await realpath(root);
|
|
364
|
-
const temporary = await mkdtemp(join(tmpdir(), 'playcraft-import-'));
|
|
365
|
-
let current;
|
|
366
|
-
try {
|
|
367
|
-
const bundlePath = typeof bundle === 'string' ? resolve(bundle) : join(temporary, 'incoming.bundle');
|
|
368
|
-
const incoming = join(temporary, 'checkout');
|
|
369
|
-
if (typeof bundle !== 'string')
|
|
370
|
-
await writeFile(bundlePath, bundle, { mode: 0o600 });
|
|
371
|
-
const bundleInfo = await stat(bundlePath);
|
|
372
|
-
if (!bundleInfo.isFile() || !bundleInfo.size)
|
|
373
|
-
throw new WorkspaceTransferError('invalid_workspace_transfer');
|
|
374
|
-
const advertised = await runTransferGit(temporary, ['bundle', 'list-heads', bundlePath], options);
|
|
375
|
-
const targets = advertised.split('\n').map(line => /^[a-f0-9]{40,64} refs\/heads\/playcraft-transfer-([a-f0-9]{40,64})$/.exec(line)?.[1]).filter(Boolean);
|
|
376
|
-
if (targets.length > 1)
|
|
377
|
-
throw new WorkspaceTransferError('invalid_workspace_transfer');
|
|
378
|
-
const targetTree = targets[0];
|
|
379
|
-
current = await createWorkspaceTransfer(rootReal, { ...options, skipBundle: true });
|
|
380
|
-
// A lost response can be retried after the original prerequisite has left the cloud snapshot.
|
|
381
|
-
if (targetTree === current.tree) {
|
|
382
|
-
await options.validateSnapshot?.(current.stats, current.stats, bundleInfo.size);
|
|
383
|
-
return { tree: current.tree, head: current.head, changedPaths: [] };
|
|
384
|
-
}
|
|
385
|
-
const baseline = join(dirname(current.bundlePath), 'checkout');
|
|
386
|
-
await runTransferGit(temporary, ['clone', '--shared', '--no-checkout', '--', baseline, incoming], options);
|
|
387
|
-
await writeFile(join(incoming, '.git', 'info', 'attributes'), RAW_ATTRIBUTES);
|
|
388
|
-
try {
|
|
389
|
-
await runTransferGit(incoming, ['fetch', '--no-tags', '--', bundlePath, 'HEAD'], options);
|
|
390
|
-
}
|
|
391
|
-
catch (error) {
|
|
392
|
-
if (current.tree !== expectedTree)
|
|
393
|
-
throw new WorkspaceTransferError('workspace_transfer_conflict');
|
|
394
|
-
if (error instanceof WorkspaceTransferError && error.code === 'workspace_transfer_git_failed')
|
|
395
|
-
throw new WorkspaceTransferError('invalid_workspace_transfer');
|
|
396
|
-
throw error;
|
|
397
|
-
}
|
|
398
|
-
const entries = await treeEntries(incoming, 'FETCH_HEAD', options.signal);
|
|
399
|
-
const tree = await runTransferGit(incoming, ['rev-parse', 'FETCH_HEAD^{tree}'], options);
|
|
400
|
-
const head = await runTransferGit(incoming, ['rev-parse', 'FETCH_HEAD'], options);
|
|
401
|
-
if (targetTree && targetTree !== tree)
|
|
402
|
-
throw new WorkspaceTransferError('invalid_workspace_transfer');
|
|
403
|
-
await options.validateSnapshot?.(summarizeEntries(entries), current.stats, bundleInfo.size);
|
|
404
|
-
if (current.tree === tree)
|
|
405
|
-
return { tree, head, changedPaths: [] };
|
|
406
|
-
if (current.tree !== expectedTree)
|
|
407
|
-
throw new WorkspaceTransferError('workspace_transfer_conflict');
|
|
408
|
-
const previous = new Map((await treeEntries(baseline, 'HEAD', options.signal)).map(entry => [entry.path, entry]));
|
|
409
|
-
const next = new Map(entries.map(entry => [entry.path, entry]));
|
|
410
|
-
const changedPaths = [...new Set([...previous.keys(), ...next.keys()])]
|
|
411
|
-
.filter(path => previous.get(path)?.sha !== next.get(path)?.sha || previous.get(path)?.executable !== next.get(path)?.executable);
|
|
412
|
-
for (const path of changedPaths) {
|
|
413
|
-
const target = await safeTarget(rootReal, path);
|
|
414
|
-
const info = await lstat(target).catch(error => { if (['ENOENT', 'ENOTDIR'].includes(error.code))
|
|
415
|
-
return undefined; throw error; });
|
|
416
|
-
if (next.has(path) && info?.isDirectory()) {
|
|
417
|
-
const children = await readdir(target, { recursive: true });
|
|
418
|
-
for (const child of children) {
|
|
419
|
-
const childPath = `${path}/${child.split(sep).join('/')}`;
|
|
420
|
-
const childInfo = await lstat(join(target, child));
|
|
421
|
-
if (!childInfo.isDirectory() && (!previous.has(childPath) || next.has(childPath)))
|
|
422
|
-
throw new WorkspaceTransferError('workspace_transfer_conflict');
|
|
423
|
-
}
|
|
424
|
-
}
|
|
425
|
-
}
|
|
426
|
-
await runTransferGit(incoming, ['checkout', '--force', '--detach', head], options);
|
|
427
|
-
const recheck = await copySnapshot(rootReal, undefined, options.signal, true);
|
|
428
|
-
if (recheck.tree !== expectedTree)
|
|
429
|
-
throw new WorkspaceTransferError('workspace_transfer_conflict');
|
|
430
|
-
const applied = [];
|
|
431
|
-
try {
|
|
432
|
-
// Deleting files first also permits a file to become a directory, or the reverse.
|
|
433
|
-
for (const path of changedPaths.filter(path => !next.has(path)).sort((a, b) => b.length - a.length)) {
|
|
434
|
-
options.signal?.throwIfAborted();
|
|
435
|
-
if (!await matchesFile(rootReal, path, previous.get(path)))
|
|
436
|
-
throw new WorkspaceTransferError('workspace_transfer_conflict');
|
|
437
|
-
await rm(await safeTarget(rootReal, path), { force: true });
|
|
438
|
-
applied.push(path);
|
|
439
|
-
await removeEmptyParents(rootReal, join(rootReal, path));
|
|
440
|
-
}
|
|
441
|
-
for (const path of changedPaths.filter(path => next.has(path))) {
|
|
442
|
-
options.signal?.throwIfAborted();
|
|
443
|
-
if (!await matchesFile(rootReal, path, previous.get(path)))
|
|
444
|
-
throw new WorkspaceTransferError('workspace_transfer_conflict');
|
|
445
|
-
const entry = next.get(path);
|
|
446
|
-
await installFile(rootReal, path, join(incoming, path), entry.executable);
|
|
447
|
-
applied.push(path);
|
|
448
|
-
}
|
|
449
|
-
}
|
|
450
|
-
catch (error) {
|
|
451
|
-
// Roll back only our writes; never replace another writer's intervening content.
|
|
452
|
-
for (const path of applied.reverse()) {
|
|
453
|
-
if (!await matchesFile(rootReal, path, next.get(path)))
|
|
454
|
-
throw new WorkspaceTransferError('workspace_transfer_rollback_conflict');
|
|
455
|
-
const old = previous.get(path);
|
|
456
|
-
if (old)
|
|
457
|
-
await installFile(rootReal, path, join(baseline, path), old.executable);
|
|
458
|
-
else {
|
|
459
|
-
await rm(await safeTarget(rootReal, path), { force: true });
|
|
460
|
-
await removeEmptyParents(rootReal, join(rootReal, path));
|
|
461
|
-
}
|
|
462
|
-
}
|
|
463
|
-
throw error;
|
|
464
|
-
}
|
|
465
|
-
return { tree, head, changedPaths };
|
|
466
|
-
}
|
|
467
|
-
finally {
|
|
468
|
-
await current?.cleanup();
|
|
469
|
-
await rm(temporary, { recursive: true, force: true });
|
|
470
|
-
}
|
|
471
|
-
}
|
|
@@ -1,75 +0,0 @@
|
|
|
1
|
-
import { TURN_FILES_CHANGED_EVENT, } from '@playcraft/common/cloud-workspace/runtime';
|
|
2
|
-
import { diffWorkspaceTrees, retainTurnSnapshots, snapshotWorkspaceTree } from './git.js';
|
|
3
|
-
import { shareWorkspaceGit } from './shared-git.js';
|
|
4
|
-
const TURN_ENDS = new Set(['turn/completed', 'turn.completed', 'turn.interrupted', 'turn.failed']);
|
|
5
|
-
/**
|
|
6
|
-
* Snapshots taken when a session's turn starts, until that turn ends. Kept in memory only: a
|
|
7
|
-
* restart ends the native turn as well, and that turn then simply reports no changes.
|
|
8
|
-
*/
|
|
9
|
-
const started = new WeakMap();
|
|
10
|
-
function turnsOf(state) {
|
|
11
|
-
let turns = started.get(state);
|
|
12
|
-
if (!turns)
|
|
13
|
-
started.set(state, turns = new Map());
|
|
14
|
-
return turns;
|
|
15
|
-
}
|
|
16
|
-
/**
|
|
17
|
-
* Records the work tree before a turn is sent. A directory that cannot be snapshotted (no
|
|
18
|
-
* repository, Git failure) gets no change summary; the turn itself is never held back by it.
|
|
19
|
-
*/
|
|
20
|
-
export async function beginTurnChanges(state, sessionId, input) {
|
|
21
|
-
turnsOf(state).delete(sessionId);
|
|
22
|
-
if (input.directoryName === 'root')
|
|
23
|
-
return;
|
|
24
|
-
await shareWorkspaceGit(state, input.root).catch(() => undefined);
|
|
25
|
-
const before = await snapshotWorkspaceTree(input.root).catch(() => undefined);
|
|
26
|
-
if (before)
|
|
27
|
-
turnsOf(state).set(sessionId, { ...input, before });
|
|
28
|
-
}
|
|
29
|
-
/** A turn that never started has nothing to compare. */
|
|
30
|
-
export function abandonTurnChanges(state, sessionId) {
|
|
31
|
-
turnsOf(state).delete(sessionId);
|
|
32
|
-
}
|
|
33
|
-
/**
|
|
34
|
-
* When a session's turn ends, compares the work tree with its start and records the files that
|
|
35
|
-
* changed for that session's conversation. Returns no event when nothing changed.
|
|
36
|
-
*/
|
|
37
|
-
export async function finishTurnChanges(state, event) {
|
|
38
|
-
const { threadId, turnId } = event;
|
|
39
|
-
if (!TURN_ENDS.has(event.method) || !threadId || !turnId)
|
|
40
|
-
return [];
|
|
41
|
-
const session = state.journal.listSessions().find((item) => item.threadId === threadId);
|
|
42
|
-
const start = session && turnsOf(state).get(session.sessionId);
|
|
43
|
-
if (!session || !start)
|
|
44
|
-
return [];
|
|
45
|
-
turnsOf(state).delete(session.sessionId);
|
|
46
|
-
try {
|
|
47
|
-
await shareWorkspaceGit(state, start.root);
|
|
48
|
-
const after = await snapshotWorkspaceTree(start.root);
|
|
49
|
-
const { files, truncated } = await diffWorkspaceTrees(start.root, start.before, after);
|
|
50
|
-
if (!files.length)
|
|
51
|
-
return [];
|
|
52
|
-
await retainTurnSnapshots(start.root, turnId, start.before, after);
|
|
53
|
-
const changes = { threadId, turnId, before: start.before, after, files, truncated };
|
|
54
|
-
return [await state.journal.allocateSourceEvent({
|
|
55
|
-
runtimeId: state.config.runtimeId,
|
|
56
|
-
generation: state.config.generation,
|
|
57
|
-
runtimeEpoch: state.runtimeEpoch,
|
|
58
|
-
type: TURN_FILES_CHANGED_EVENT,
|
|
59
|
-
payload: {
|
|
60
|
-
...changes,
|
|
61
|
-
sessionId: session.sessionId,
|
|
62
|
-
...(start.workspaceId ? { workspaceId: start.workspaceId } : {}),
|
|
63
|
-
...(start.directoryName ? { directoryName: start.directoryName } : {}),
|
|
64
|
-
},
|
|
65
|
-
occurredAt: new Date().toISOString(),
|
|
66
|
-
sessionId: session.sessionId,
|
|
67
|
-
workspaceId: start.workspaceId,
|
|
68
|
-
threadId,
|
|
69
|
-
turnId,
|
|
70
|
-
})];
|
|
71
|
-
}
|
|
72
|
-
catch {
|
|
73
|
-
return [];
|
|
74
|
-
}
|
|
75
|
-
}
|
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
import { spawn } from 'node:child_process';
|
|
2
|
-
import { createHash } from 'node:crypto';
|
|
3
|
-
import { WORKSPACE_VIDEO_ATTACHMENT_MAX_DURATION_MS, WORKSPACE_VIDEO_ATTACHMENT_MAX_BYTES } from '@playcraft/common/cloud-workspace/runtime';
|
|
4
|
-
/** Probe the opened descriptor so replacing a pathname cannot redirect the media reader. */
|
|
5
|
-
export async function probeAttachmentVideo(handle) {
|
|
6
|
-
const output = await new Promise((resolve, reject) => {
|
|
7
|
-
const child = spawn(process.env.PLAYCRAFT_FFPROBE_PATH ?? 'ffprobe', [
|
|
8
|
-
'-v', 'error', '-protocol_whitelist', 'file,pipe', '-show_entries',
|
|
9
|
-
'format=format_name,duration:stream=codec_type,width,height', '-of', 'json',
|
|
10
|
-
process.platform === 'linux' ? '/proc/self/fd/3' : '/dev/fd/3',
|
|
11
|
-
], { stdio: ['ignore', 'pipe', 'ignore', handle.fd] });
|
|
12
|
-
let text = '';
|
|
13
|
-
const timer = setTimeout(() => { child.kill('SIGKILL'); reject(new Error('invalid_attachment')); }, 15_000);
|
|
14
|
-
timer.unref();
|
|
15
|
-
child.stdout.on('data', (chunk) => {
|
|
16
|
-
text += chunk.toString('utf8');
|
|
17
|
-
if (text.length > 64 * 1024) {
|
|
18
|
-
child.kill('SIGKILL');
|
|
19
|
-
reject(new Error('invalid_attachment'));
|
|
20
|
-
}
|
|
21
|
-
});
|
|
22
|
-
child.once('error', () => { clearTimeout(timer); reject(new Error('video_probe_unavailable')); });
|
|
23
|
-
child.once('close', code => { clearTimeout(timer); code === 0 ? resolve(text) : reject(new Error('invalid_attachment')); });
|
|
24
|
-
});
|
|
25
|
-
const result = JSON.parse(output);
|
|
26
|
-
const stream = result.streams?.find(item => item.codec_type === 'video');
|
|
27
|
-
const durationMs = Math.round(Number(result.format?.duration) * 1000);
|
|
28
|
-
const formats = result.format?.format_name?.split(',') ?? [];
|
|
29
|
-
const mimeType = formats.includes('webm') ? 'video/webm' : formats.includes('mp4') ? 'video/mp4' : formats.includes('mov') ? 'video/quicktime' : undefined;
|
|
30
|
-
if (!stream || !mimeType || !Number.isFinite(durationMs) || durationMs <= 0 || durationMs > WORKSPACE_VIDEO_ATTACHMENT_MAX_DURATION_MS
|
|
31
|
-
|| !Number.isSafeInteger(stream.width) || !Number.isSafeInteger(stream.height) || stream.width <= 0 || stream.height <= 0
|
|
32
|
-
|| stream.width > 16384 || stream.height > 16384)
|
|
33
|
-
throw new Error('invalid_attachment');
|
|
34
|
-
return { mimeType, durationMs, width: stream.width, height: stream.height };
|
|
35
|
-
}
|
|
36
|
-
export async function hashAttachment(handle) {
|
|
37
|
-
const digest = createHash('sha256');
|
|
38
|
-
const chunk = Buffer.allocUnsafe(64 * 1024);
|
|
39
|
-
let position = 0;
|
|
40
|
-
for (;;) {
|
|
41
|
-
const { bytesRead } = await handle.read(chunk, 0, chunk.length, position);
|
|
42
|
-
if (!bytesRead)
|
|
43
|
-
return digest.digest('hex');
|
|
44
|
-
digest.update(chunk.subarray(0, bytesRead));
|
|
45
|
-
position += bytesRead;
|
|
46
|
-
if (position > WORKSPACE_VIDEO_ATTACHMENT_MAX_BYTES)
|
|
47
|
-
throw new Error('invalid_attachment');
|
|
48
|
-
}
|
|
49
|
-
}
|
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: playcraft-replica-validation
|
|
3
|
-
description: Record a reconstructed browser playable with real inputs, compare it with its reference video, and use timestamped differences to improve the replica. Use after implementation or fixes; use video understanding first when the reference has not been analyzed.
|
|
4
|
-
version: 1.0.1
|
|
5
|
-
surfaces:
|
|
6
|
-
- sandbox-agent
|
|
7
|
-
---
|
|
8
|
-
|
|
9
|
-
# Replica validation
|
|
10
|
-
|
|
11
|
-
Use the PlayCraft CLI (Command-Line Interface,命令行接口) to record the running implementation and compare real video evidence. Codex chooses the scenario, reads the evidence, edits the playable, and repeats the checks. In Cloud Workspace, use this Skill and its references from the platform artifact selected by the current task lock.
|
|
12
|
-
|
|
13
|
-
Read [command and evidence contracts](references/contracts.md) before the first recording or comparison. Check `playcraft playable record --help`, `playcraft video compare --help`, and `playcraft video models --json`. If a required command is unavailable, report that the workspace CLI needs upgrading. Provider credentials and model requests belong to Model Proxy; do not call Gemini directly or copy a key into the project.
|
|
14
|
-
|
|
15
|
-
Use a completed reference `analysis.json` from `playcraft-video-understanding`. Inspect its original keyframes and distinguish observed actions from inferred controls. Run from the project root. Write an explicit, versioned scenario for the current implementation, using real browser coordinates and inputs. Include the initial view, first action and result, principal feedback, and ending when demonstrated. Keep hidden rules and unshown branches unknown. Start or restart the current project using its own development command; do not depend on internal auto-play hooks. Confirm that the supplied development address belongs to this process and serves this round's version. If that cannot be confirmed, do not claim validation of the current version. The project digest proves the recorded checkout stayed unchanged, but does not attest which source version an already-running server serves.
|
|
16
|
-
|
|
17
|
-
Record into a fresh directory under `.playcraft/video-replication/`. Keep the original recording, its input logs, scenario, control recordings, and project snapshot together. Do not edit the video or its manifest to repair a failing check. Match the reference viewport when practical and record enough time for every expected result. A completed recording proves capture succeeded; it does not prove the interaction worked.
|
|
18
|
-
|
|
19
|
-
Compare the original reference, its analysis, the candidate video, and its recording manifest. Inspect `comparison.md`, `comparison.json`, paired keyframes, contact sheets, input logs, and controls using available image and file-reading tools. Review event alignment before judging motion or feedback timing. The model can suggest differences; the CLI derives the final status from validated evidence and the comparison policy. A single similarity score is insufficient. Keep the default full comparison policy for delivery; an explicitly limited diagnostic must be reported as partial coverage. Do not loosen thresholds just to obtain a passing result.
|
|
20
|
-
|
|
21
|
-
For `fail`, address supported differences in priority order, then make a fresh recording from the changed project and compare again in a fresh output directory. For `inconclusive`, resolve missing controls, insufficient duration, unpaired required events, low confidence, or unknown input intent before declaring completion. Preserve valid differences even when other dimensions lack evidence. Report the tested path, unsupported branches, and unevaluated audio explicitly.
|
|
22
|
-
|
|
23
|
-
A timeout or uncertain submission must retain its run identifier and saved request. Resume the same comparison with `playcraft video compare --resume <runId> --out <same-directory>`; recovery reads the existing task and never creates another paid request. Explicit cancellation is separate from ending the local wait. Do not resubmit because the command exited early. Treat malformed model output, stale recordings, changed hashes, or missing frames as errors, never as a passing comparison.
|
|
24
|
-
|
|
25
|
-
Deliver the current project with links to the reference evidence, candidate recording, comparison report, and any remaining uncertainty. Keep validation evidence in workspace checkpoints and out of Git and deliverable builds.
|