@playcraft/cli 0.1.52 → 0.1.53-beta
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 +62 -3
- package/SKILLS_REGISTRY.md +149 -0
- package/dist/agent/local-backend.js +1 -1
- package/dist/auth/account-activation.js +157 -0
- package/dist/auth/messages.js +30 -0
- package/dist/cli-root-help.js +2 -0
- package/dist/commands/build-all.js +5 -19
- package/dist/commands/build-temp.js +26 -0
- package/dist/commands/build.js +15 -28
- package/dist/commands/create.js +26 -33
- package/dist/commands/login.js +84 -42
- package/dist/commands/platform-skills.generated.js +19 -0
- package/dist/commands/playable.js +53 -0
- package/dist/commands/preview-runtime.js +21 -0
- package/dist/commands/skills.js +4 -0
- package/dist/commands/tools-3d.js +11 -0
- package/dist/commands/tools-batch.js +113 -0
- package/dist/commands/tools-generation.js +6 -0
- package/dist/commands/tools.js +2 -0
- package/dist/commands/video.js +122 -0
- package/dist/commands/workspace-runtime.js +54 -0
- package/dist/commands/workspace.js +40 -0
- package/dist/config.js +59 -4
- package/dist/index.js +42 -3
- package/dist/preview-runtime/distribution.js +77 -0
- package/dist/project-skills/builtin-import.js +225 -0
- package/dist/project-skills/commands.js +77 -0
- package/dist/project-skills/lifecycle.js +28 -0
- package/dist/project-skills/local-run.js +112 -0
- package/dist/project-skills/local-store.js +221 -0
- package/dist/project-skills/messages.js +80 -0
- package/dist/project-skills/reconcile.js +62 -0
- package/dist/project-skills/remote-cache.js +51 -0
- package/dist/project-skills/validation.js +19 -0
- package/dist/recording/messages.js +40 -0
- package/dist/recording/project-digest.js +85 -0
- package/dist/recording/recorder.js +293 -0
- package/dist/recording/scenario.js +50 -0
- package/dist/remix/clone.js +2 -0
- package/dist/remix/init-template.js +2 -0
- package/dist/remix/pull.js +2 -0
- package/dist/remix/push.js +21 -1
- package/dist/skill-registry/client.js +345 -0
- package/dist/skill-registry/commands.js +180 -0
- package/dist/skill-registry/evaluation-client.js +156 -0
- package/dist/skill-registry/evaluation-commands.js +174 -0
- package/dist/skill-registry/messages.js +177 -0
- package/dist/skill-registry/package.js +275 -0
- package/dist/skill-registry/platform-package.js +100 -0
- package/dist/utils/agent-api-client.js +210 -30
- package/dist/utils/environment-endpoints.js +17 -0
- package/dist/utils/error-diagnostics.js +20 -0
- package/dist/utils/playcraft-http-client.js +6 -4
- package/dist/utils/tool-operation-journal.js +124 -0
- package/dist/utils/video-evidence-git.js +81 -0
- package/dist/video-replication/client.js +234 -0
- package/dist/video-replication/comparison-evidence.js +149 -0
- package/dist/video-replication/comparison-files.js +57 -0
- package/dist/video-replication/comparison-log.js +72 -0
- package/dist/video-replication/comparison-messages.js +104 -0
- package/dist/video-replication/comparison-output.js +63 -0
- package/dist/video-replication/comparison-preflight.js +66 -0
- package/dist/video-replication/comparison.js +230 -0
- package/dist/video-replication/diagnostics.js +41 -0
- package/dist/video-replication/evidence.js +165 -0
- package/dist/video-replication/media.js +112 -0
- package/dist/video-replication/messages.js +97 -0
- package/dist/workspace/messages.js +67 -0
- package/dist/workspace/transfer-client.js +237 -0
- package/dist/workspace/transfer-errors.js +48 -0
- package/dist/workspace-runtime/batch/content-changes.js +30 -0
- package/dist/workspace-runtime/batch/control.js +86 -0
- package/dist/workspace-runtime/batch/deliver.js +173 -0
- package/dist/workspace-runtime/batch/freeze.js +101 -0
- package/dist/workspace-runtime/batch/generate.js +249 -0
- package/dist/workspace-runtime/batch/git.js +40 -0
- package/dist/workspace-runtime/batch/occupancy.js +8 -0
- package/dist/workspace-runtime/batch/policy.js +70 -0
- package/dist/workspace-runtime/batch/prepare.js +317 -0
- package/dist/workspace-runtime/batch/processes.js +72 -0
- package/dist/workspace-runtime/batch/records.js +113 -0
- package/dist/workspace-runtime/batch/routes.js +61 -0
- package/dist/workspace-runtime/batch/source-candidate.js +86 -0
- package/dist/workspace-runtime/batch/source-capture.js +146 -0
- package/dist/workspace-runtime/batch/source-failure.js +43 -0
- package/dist/workspace-runtime/batch/source-publish.js +130 -0
- package/dist/workspace-runtime/codex/app-server.js +1065 -0
- package/dist/workspace-runtime/codex/compact.js +46 -0
- package/dist/workspace-runtime/codex/config-toml.js +107 -0
- package/dist/workspace-runtime/codex/delivery-tools.js +144 -0
- package/dist/workspace-runtime/codex/jsonrpc-stdio.js +145 -0
- package/dist/workspace-runtime/codex/launcher-client.js +177 -0
- package/dist/workspace-runtime/codex/loopback.js +146 -0
- package/dist/workspace-runtime/codex/mcp-config.js +39 -0
- package/dist/workspace-runtime/codex/model-broker.js +263 -0
- package/dist/workspace-runtime/codex/native-adapter.js +9 -0
- package/dist/workspace-runtime/codex/permissions.js +79 -0
- package/dist/workspace-runtime/codex/thread-image.js +38 -0
- package/dist/workspace-runtime/codex/tool-response.js +10 -0
- package/dist/workspace-runtime/codex/ui-instructions.js +4 -0
- package/dist/workspace-runtime/codex/ui-tools.js +203 -0
- package/dist/workspace-runtime/codex/work-mode.js +15 -0
- package/dist/workspace-runtime/main.js +10 -0
- package/dist/workspace-runtime/persistence/journal.js +365 -0
- package/dist/workspace-runtime/persistence/native-subagents.js +162 -0
- package/dist/workspace-runtime/persistence/sqlite-client.js +77 -0
- package/dist/workspace-runtime/persistence/sqlite-worker.js +1161 -0
- package/dist/workspace-runtime/persistence/stopped-execution.js +77 -0
- package/dist/workspace-runtime/previews/diagnostics.js +44 -0
- package/dist/workspace-runtime/previews/output.js +36 -0
- package/dist/workspace-runtime/previews/publish.js +283 -0
- package/dist/workspace-runtime/previews/runtime-preview.js +251 -0
- package/dist/workspace-runtime/previews/single-file.js +43 -0
- package/dist/workspace-runtime/processes/managed-writes.js +69 -0
- package/dist/workspace-runtime/serve.js +441 -0
- package/dist/workspace-runtime/server/accept-operation.js +61 -0
- package/dist/workspace-runtime/server/asset-transfers.js +63 -0
- package/dist/workspace-runtime/server/auth.js +21 -0
- package/dist/workspace-runtime/server/connectivity.js +211 -0
- package/dist/workspace-runtime/server/desktop-host.js +429 -0
- package/dist/workspace-runtime/server/desktop-preview-config.js +81 -0
- package/dist/workspace-runtime/server/desktop-preview-service.js +157 -0
- package/dist/workspace-runtime/server/desktop-preview-static.js +53 -0
- package/dist/workspace-runtime/server/desktop-preview.js +189 -0
- package/dist/workspace-runtime/server/dispatch.js +1655 -0
- package/dist/workspace-runtime/server/endpoints.js +21 -0
- package/dist/workspace-runtime/server/execution-group.js +90 -0
- package/dist/workspace-runtime/server/http.js +1339 -0
- package/dist/workspace-runtime/server/lock.js +53 -0
- package/dist/workspace-runtime/server/operation-record.js +33 -0
- package/dist/workspace-runtime/server/rfb-watch-gate.js +168 -0
- package/dist/workspace-runtime/server/session-restore.js +84 -0
- package/dist/workspace-runtime/server/source-uploader.js +138 -0
- package/dist/workspace-runtime/server/submit-operation.js +41 -0
- package/dist/workspace-runtime/server/terminal-host.js +366 -0
- package/dist/workspace-runtime/server/types.js +1 -0
- package/dist/workspace-runtime/server/workspace-transfer.js +168 -0
- package/dist/workspace-runtime/verify-artifact.js +59 -0
- package/dist/workspace-runtime/workspaces/append-only-log.js +62 -0
- package/dist/workspace-runtime/workspaces/archive.js +207 -0
- package/dist/workspace-runtime/workspaces/artifact-events.js +55 -0
- package/dist/workspace-runtime/workspaces/artifact-files.js +199 -0
- package/dist/workspace-runtime/workspaces/artifact-receipts.js +89 -0
- package/dist/workspace-runtime/workspaces/artifacts.js +282 -0
- package/dist/workspace-runtime/workspaces/asset-library-import.js +387 -0
- package/dist/workspace-runtime/workspaces/asset-publication-access.js +25 -0
- package/dist/workspace-runtime/workspaces/asset-transfer-dependencies.js +81 -0
- package/dist/workspace-runtime/workspaces/asset-transfer-publish.js +438 -0
- package/dist/workspace-runtime/workspaces/asset-transfer-storage.js +389 -0
- package/dist/workspace-runtime/workspaces/attachment-content.js +73 -0
- package/dist/workspace-runtime/workspaces/attachments.js +185 -0
- package/dist/workspace-runtime/workspaces/batch-commit.js +142 -0
- package/dist/workspace-runtime/workspaces/binary-content.js +65 -0
- package/dist/workspace-runtime/workspaces/binary-write.js +117 -0
- package/dist/workspace-runtime/workspaces/cli-context.js +23 -0
- package/dist/workspace-runtime/workspaces/cli-credentials.js +115 -0
- package/dist/workspace-runtime/workspaces/composer-context.js +147 -0
- package/dist/workspace-runtime/workspaces/context-error.js +2 -0
- package/dist/workspace-runtime/workspaces/delivery-build.js +331 -0
- package/dist/workspace-runtime/workspaces/delivery-store.js +168 -0
- package/dist/workspace-runtime/workspaces/delivery.js +64 -0
- package/dist/workspace-runtime/workspaces/dependencies.js +279 -0
- package/dist/workspace-runtime/workspaces/developer-command.js +98 -0
- package/dist/workspace-runtime/workspaces/directories.js +132 -0
- package/dist/workspace-runtime/workspaces/directory-access.js +37 -0
- package/dist/workspace-runtime/workspaces/directory-attachments.js +245 -0
- package/dist/workspace-runtime/workspaces/directory-create.js +57 -0
- package/dist/workspace-runtime/workspaces/directory-maintenance.js +111 -0
- package/dist/workspace-runtime/workspaces/directory-mutations.js +238 -0
- package/dist/workspace-runtime/workspaces/directory-revisions.js +56 -0
- package/dist/workspace-runtime/workspaces/directory-stats.js +49 -0
- package/dist/workspace-runtime/workspaces/disk-snapshot.js +31 -0
- package/dist/workspace-runtime/workspaces/empty-template.js +42 -0
- package/dist/workspace-runtime/workspaces/file-content.js +48 -0
- package/dist/workspace-runtime/workspaces/files-search.js +116 -0
- package/dist/workspace-runtime/workspaces/files.js +150 -0
- package/dist/workspace-runtime/workspaces/git-credential-broker.js +312 -0
- package/dist/workspace-runtime/workspaces/git-credential-helper.js +68 -0
- package/dist/workspace-runtime/workspaces/git-direct-store.js +155 -0
- package/dist/workspace-runtime/workspaces/git.js +670 -0
- package/dist/workspace-runtime/workspaces/instructions.js +56 -0
- package/dist/workspace-runtime/workspaces/json5-edit.js +313 -0
- package/dist/workspace-runtime/workspaces/library-attachments.js +268 -0
- package/dist/workspace-runtime/workspaces/operation-log.js +23 -0
- package/dist/workspace-runtime/workspaces/optional-platform-skills.js +14 -0
- package/dist/workspace-runtime/workspaces/parameter-agent-inputs.js +97 -0
- package/dist/workspace-runtime/workspaces/parameter-agent-protocol.js +40 -0
- package/dist/workspace-runtime/workspaces/parameter-agent-store.js +58 -0
- package/dist/workspace-runtime/workspaces/parameter-agent.js +284 -0
- package/dist/workspace-runtime/workspaces/parameter-assets.js +189 -0
- package/dist/workspace-runtime/workspaces/parameters.js +329 -0
- package/dist/workspace-runtime/workspaces/prepare.js +278 -0
- package/dist/workspace-runtime/workspaces/registry.js +251 -0
- package/dist/workspace-runtime/workspaces/remix-references.js +301 -0
- package/dist/workspace-runtime/workspaces/revisions.js +31 -0
- package/dist/workspace-runtime/workspaces/session-image.js +104 -0
- package/dist/workspace-runtime/workspaces/shared-git.js +40 -0
- package/dist/workspace-runtime/workspaces/skill-artifact-loader.js +95 -0
- package/dist/workspace-runtime/workspaces/skills.js +191 -0
- package/dist/workspace-runtime/workspaces/theme-asset-index.js +157 -0
- package/dist/workspace-runtime/workspaces/transfer-quota.js +69 -0
- package/dist/workspace-runtime/workspaces/transfer.js +471 -0
- package/dist/workspace-runtime/workspaces/turn-changes.js +75 -0
- package/dist/workspace-runtime/workspaces/video-attachments.js +49 -0
- package/package.json +15 -5
- package/project-template/.claude/skills/playcraft-replica-validation/SKILL.md +25 -0
- package/project-template/.claude/skills/playcraft-replica-validation/references/contracts.md +79 -0
- package/project-template/.claude/skills/playcraft-video-understanding/SKILL.md +29 -0
- package/project-template/.claude/skills/playcraft-video-understanding/references/contracts.md +37 -0
- package/project-template-v2/.claude/agents/artist.md +82 -0
- package/project-template-v2/.claude/agents/developer.md +153 -0
- package/project-template-v2/.claude/agents/game-designer.md +264 -0
- package/project-template-v2/.claude/agents/refs/artist-art-style-catalog.md +533 -0
- package/project-template-v2/.claude/agents/refs/artist-color-audio-recipes.md +153 -0
- package/project-template-v2/.claude/agents/refs/artist-dimension-axis.md +27 -0
- package/project-template-v2/.claude/agents/refs/artist-master-composite-recipes.md +208 -0
- package/project-template-v2/.claude/agents/refs/atom-skill-library.md +81 -0
- package/project-template-v2/.claude/agents/refs/developer-impl-cookbook.md +432 -0
- package/project-template-v2/.claude/agents/refs/framework-5-component-filter.md +252 -0
- package/project-template-v2/.claude/agents/refs/framework-game-feel-juice.md +266 -0
- package/project-template-v2/.claude/agents/refs/framework-mda.md +147 -0
- package/project-template-v2/.claude/agents/refs/game-designer-gameplay-sufficiency.md +123 -0
- package/project-template-v2/.claude/agents/refs/ta-3d-flip-recipe.md +88 -0
- package/project-template-v2/.claude/agents/refs/ta-atlas-deliverable-standard.md +67 -0
- package/project-template-v2/.claude/agents/refs/ta-batch-pipeline-recipes.md +120 -0
- package/project-template-v2/.claude/agents/refs/ta-image-generation-detail.md +300 -0
- package/project-template-v2/.claude/agents/refs/ta-image-ops-reference.md +495 -0
- package/project-template-v2/.claude/agents/refs/ta-pipeline-cookbook.md +1141 -0
- package/project-template-v2/.claude/agents/refs/ta-tools-reference.md +111 -0
- package/project-template-v2/.claude/agents/refs/ta-vfx-preset-catalog.md +365 -0
- package/project-template-v2/.claude/agents/refs/threejs-cannon-pitfalls.md +412 -0
- package/project-template-v2/.claude/agents/reviewer.md +75 -0
- package/project-template-v2/.claude/agents/technical-artist.md +86 -0
- package/project-template-v2/.claude/hooks/snapshot-milestone.mjs +243 -0
- package/project-template-v2/.claude/hooks/user-prompt.mjs +133 -0
- package/project-template-v2/.claude/settings.json +33 -0
- package/project-template-v2/.claude/skills/brainstorming/SKILL.md +161 -0
- package/project-template-v2/.claude/skills/brainstorming/scripts/frame-template.html +270 -0
- package/project-template-v2/.claude/skills/brainstorming/scripts/helper.js +177 -0
- package/project-template-v2/.claude/skills/brainstorming/scripts/server.cjs +354 -0
- package/project-template-v2/.claude/skills/brainstorming/scripts/start-server.sh +148 -0
- package/project-template-v2/.claude/skills/brainstorming/scripts/stop-server.sh +56 -0
- package/project-template-v2/.claude/skills/brainstorming/scripts/wait-for-selection.sh +62 -0
- package/project-template-v2/.claude/skills/brainstorming/spec-document-reviewer-prompt.md +49 -0
- package/project-template-v2/.claude/skills/brainstorming/visual-companion.md +309 -0
- package/project-template-v2/.claude/skills/playcraft-ad-psychology/SKILL.md +182 -0
- package/project-template-v2/.claude/skills/playcraft-art-style-guide/SKILL.md +123 -0
- package/project-template-v2/.claude/skills/playcraft-asset-state-sheet/SKILL.md +205 -0
- package/project-template-v2/.claude/skills/playcraft-audio-generation/SKILL.md +280 -0
- package/project-template-v2/.claude/skills/playcraft-batch-pipeline/SKILL.md +184 -0
- package/project-template-v2/.claude/skills/playcraft-build-optimizer/SKILL.md +306 -0
- package/project-template-v2/.claude/skills/playcraft-image-generation/SKILL.md +370 -0
- package/project-template-v2/.claude/skills/playcraft-image-generation/reference/build-sprite-sheet.template.mjs +123 -0
- package/project-template-v2/.claude/skills/playcraft-image-generation/reference/compare-style.template.mjs +254 -0
- package/project-template-v2/.claude/skills/playcraft-image-generation/reference/gen-batch-sprite.template.mjs +333 -0
- package/project-template-v2/.claude/skills/playcraft-image-generation/reference/gen-batch.template.mjs +95 -0
- package/project-template-v2/.claude/skills/playcraft-image-generation/reference/gen-edit-variants.template.mjs +118 -0
- package/project-template-v2/.claude/skills/playcraft-image-generation/reference/process-batch.template.mjs +136 -0
- package/project-template-v2/.claude/skills/playcraft-image-generation/reference/prompt-cookbook.md +397 -0
- package/project-template-v2/.claude/skills/playcraft-image-generation/reference/validate-sprite-sheet.template.mjs +296 -0
- package/project-template-v2/.claude/skills/playcraft-image-ops/SKILL.md +122 -0
- package/project-template-v2/.claude/skills/playcraft-image-processing/SKILL.md +219 -0
- package/project-template-v2/.claude/skills/playcraft-masking/SKILL.md +373 -0
- package/project-template-v2/.claude/skills/playcraft-playable-optimization/SKILL.md +161 -0
- package/project-template-v2/.claude/skills/playcraft-research/SKILL.md +215 -0
- package/project-template-v2/.claude/skills/playcraft-skill-recommender/SKILL.md +382 -0
- package/project-template-v2/.claude/skills/playcraft-sprite-generation/SKILL.md +423 -0
- package/project-template-v2/.claude/skills/playcraft-sprite-remix/SKILL.md +158 -0
- package/project-template-v2/.claude/skills/playcraft-sprite-sheet/SKILL.md +100 -0
- package/project-template-v2/.claude/skills/playcraft-storyboard/SKILL.md +167 -0
- package/project-template-v2/.claude/skills/playcraft-style-qa/SKILL.md +270 -0
- package/project-template-v2/.claude/skills/playcraft-text-rendering/SKILL.md +236 -0
- package/project-template-v2/.claude/skills/playcraft-vfx-animation/SKILL.md +130 -0
- package/project-template-v2/.claude/skills/playwright-cli/SKILL.md +390 -0
- package/project-template-v2/.claude/skills/playwright-cli/references/element-attributes.md +23 -0
- package/project-template-v2/.claude/skills/playwright-cli/references/playwright-tests.md +39 -0
- package/project-template-v2/.claude/skills/playwright-cli/references/request-mocking.md +87 -0
- package/project-template-v2/.claude/skills/playwright-cli/references/running-code.md +240 -0
- package/project-template-v2/.claude/skills/playwright-cli/references/session-management.md +226 -0
- package/project-template-v2/.claude/skills/playwright-cli/references/spec-driven-testing.md +308 -0
- package/project-template-v2/.claude/skills/playwright-cli/references/storage-state.md +275 -0
- package/project-template-v2/.claude/skills/playwright-cli/references/test-generation.md +134 -0
- package/project-template-v2/.claude/skills/playwright-cli/references/tracing.md +142 -0
- package/project-template-v2/.claude/skills/playwright-cli/references/video-recording.md +153 -0
- package/project-template-v2/.claude/skills/session-analyzer/SKILL.md +386 -0
- package/project-template-v2/.claude/skills/session-analyzer/scripts/execution-breakdown.mjs +182 -0
- package/project-template-v2/.claude/skills/session-analyzer/scripts/find-turns.mjs +72 -0
- package/project-template-v2/.claude/skills/session-analyzer/scripts/heavy-output.mjs +121 -0
- package/project-template-v2/.claude/skills/session-analyzer/scripts/resolve-session.mjs +102 -0
- package/project-template-v2/.claude/skills/session-analyzer/scripts/subagent-stats.mjs +127 -0
- package/project-template-v2/.claude/skills/session-analyzer/scripts/subagent-tool-timeline.mjs +106 -0
- package/project-template-v2/.claude/skills/session-analyzer/scripts/time-gaps.mjs +128 -0
- package/project-template-v2/.claude/skills/session-analyzer/scripts/turn-timeline.mjs +67 -0
- package/project-template-v2/.claude/snapshot.mjs +263 -0
- package/project-template-v2/.playcraft/skills.lock.json +152 -0
- package/project-template-v2/CLAUDE.md +146 -0
- 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 +254 -0
- package/project-template-v2/docs/harness/iteration-1/generate-flow.md +91 -0
- package/project-template-v2/docs/harness/iteration-1/ideate-flow.md +214 -0
- package/project-template-v2/docs/harness/iteration-1/optimize-flow.md +75 -0
- package/project-template-v2/docs/harness/iteration-1/wrapup-flow.md +63 -0
- package/project-template-v2/docs/harness/iteration-2/context-flow.md +223 -0
- package/project-template-v2/docs/harness/iteration-2/generate-flow.md +129 -0
- package/project-template-v2/docs/harness/iteration-2/ideate-flow.md +267 -0
- package/project-template-v2/docs/harness/iteration-2/optimize-flow.md +164 -0
- package/project-template-v2/docs/harness/iteration-2/wrapup-flow.md +115 -0
- package/project-template-v2/docs/harness/orchestrator-flow.md +364 -0
- package/project-template-v2/docs/project-state.json +60 -0
- package/project-template-v2/docs/project-state.md +72 -0
- package/project-template-v2/docs/standards/README.md +225 -0
- package/project-template-v2/docs/standards/agent-behavior-standards.md +174 -0
- package/project-template-v2/docs/standards/artifacts/design-brief.md +19 -0
- package/project-template-v2/docs/standards/artifacts/design.md +22 -0
- package/project-template-v2/docs/standards/artifacts/game-code.md +41 -0
- package/project-template-v2/docs/standards/artifacts/todo-list.md +41 -0
- package/project-template-v2/docs/standards/iter1-agent-behavior-standards.md +343 -0
- package/project-template-v2/game/index.ts +18 -0
- package/project-template-v2/globals.d.ts +51 -0
- package/project-template-v2/index.css +34 -0
- package/project-template-v2/index.html +18 -0
- package/project-template-v2/main.ts +9 -0
- package/project-template-v2/package.json +46 -0
- package/project-template-v2/skills/_shared/scripts/dispatch-clear.mjs +31 -0
- package/project-template-v2/skills/_shared/scripts/dispatch-set.mjs +86 -0
- package/project-template-v2/skills/_shared/scripts/dod-check.mjs +153 -0
- package/project-template-v2/skills/_shared/scripts/handoff-append.mjs +70 -0
- package/project-template-v2/skills/_shared/scripts/lib/validator-artifacts.mjs +91 -0
- package/project-template-v2/skills/_shared/scripts/pipeline/dod-config.mjs +131 -0
- package/project-template-v2/skills/_shared/scripts/pipeline/index.mjs +80 -0
- package/project-template-v2/skills/_shared/scripts/pipeline/iteration-1-core.mjs +90 -0
- package/project-template-v2/skills/_shared/scripts/pipeline/iteration-2-wrap.mjs +93 -0
- package/project-template-v2/skills/_shared/scripts/pipeline/iteration-3-visual.mjs +23 -0
- package/project-template-v2/skills/_shared/scripts/render-project-state.mjs +230 -0
- package/project-template-v2/skills/_shared/scripts/state-advance-stage.mjs +58 -0
- package/project-template-v2/skills/_shared/scripts/state-get.mjs +339 -0
- package/project-template-v2/skills/_shared/scripts/state-handoff.mjs +39 -0
- package/project-template-v2/skills/_shared/scripts/state-set.mjs +94 -0
- package/project-template-v2/skills/_shared/scripts/state-store.mjs +783 -0
- package/project-template-v2/skills/_shared/scripts/todo-add.mjs +88 -0
- package/project-template-v2/skills/_shared/scripts/todo-get.mjs +130 -0
- package/project-template-v2/skills/_shared/scripts/todo-remove.mjs +33 -0
- package/project-template-v2/skills/_shared/scripts/todo-set.mjs +47 -0
- package/project-template-v2/skills/_shared/scripts/verify-asset-code-sync.mjs +268 -0
- package/project-template-v2/skills/_shared/scripts/verify-env.mjs +285 -0
- package/project-template-v2/skills/_shared/scripts/verify-placeholders.mjs +161 -0
- package/project-template-v2/skills/playable-autoplay/SKILL.md +176 -0
- package/project-template-v2/skills/playable-autoplay/agents/openai.yaml +4 -0
- package/project-template-v2/skills/playable-debug/SKILL.md +116 -0
- package/project-template-v2/skills/playable-debug/agents/openai.yaml +4 -0
- package/project-template-v2/skills/playable-debug/references/debug-config.md +40 -0
- package/project-template-v2/skills/playable-record/SKILL.md +140 -0
- package/project-template-v2/skills/playable-record/scripts/lib/dev-server.mjs +104 -0
- package/project-template-v2/skills/playable-record/scripts/lib/record-audio-bridge.js +141 -0
- package/project-template-v2/skills/playable-record/scripts/record-playable.mjs +425 -0
- package/project-template-v2/skills/playable-record/scripts/verify-contract.mjs +261 -0
- package/project-template-v2/skills/playable-record/scripts/verify-firstwin.mjs +398 -0
- package/project-template-v2/skills/playable-record/scripts/verify-fusion.mjs +322 -0
- package/project-template-v2/skills/playable-record/scripts/verify-lifecycle.mjs +105 -0
- package/project-template-v2/skills/playable-record/scripts/verify-vlm-video.mjs +230 -0
- package/project-template-v2/skills/playable-validate/SKILL.md +61 -0
- package/project-template-v2/skills/playable-validate/validation-rules.md +14 -0
- package/project-template-v2/skills/playable-verify-ui/SKILL.md +63 -0
- package/project-template-v2/skills/playable-verify-ui/scripts/lib/__init__.py +1 -0
- package/project-template-v2/skills/playable-verify-ui/scripts/lib/config_expr.js +218 -0
- package/project-template-v2/skills/playable-verify-ui/scripts/lib/node_utils.js +51 -0
- package/project-template-v2/skills/playable-verify-ui/scripts/lib/profile_loader.py +59 -0
- package/project-template-v2/skills/playable-verify-ui/scripts/lib/report.py +106 -0
- package/project-template-v2/skills/playable-verify-ui/scripts/lib/runtime_contract.js +116 -0
- package/project-template-v2/skills/playable-verify-ui/scripts/verify_fetch_antipatterns.py +59 -0
- package/project-template-v2/skills/playable-verify-ui/scripts/verify_hardcoded_layout.js +64 -0
- package/project-template-v2/skills/playable-verify-ui/scripts/verify_runtime_contract.js +58 -0
- package/project-template-v2/skills/playable-verify-ui/scripts/verify_text_overlap.py +333 -0
- package/project-template-v2/ta-workspace/scripts/.gitkeep +0 -0
- package/project-template-v2/tsconfig.json +20 -0
- package/project-template-v2/vite.config.ts +27 -0
- package/project-templates/batch-locks/phaser.yaml +922 -0
- package/project-templates/batch-locks/threejs.yaml +978 -0
- package/project-templates/empty/AGENTS.md +41 -0
- package/project-templates/empty/gitignore +33 -0
- package/templates/preview-runtime/v1/README.md +71 -0
- package/templates/preview-runtime/v1/build-preview.mjs +1324 -0
- package/templates/preview-runtime/v1/manifest.json +17 -0
- package/templates/preview-runtime/v1/release-guard.mjs +87 -0
- package/templates/preview-runtime/v1/runtime.d.mts +3 -0
- package/templates/preview-runtime/v1/runtime.d.ts +3 -0
- package/templates/preview-runtime/v1/runtime.mjs +1121 -0
- package/templates/preview-runtime/v1/types/adapters.d.ts +26 -0
- package/templates/preview-runtime/v1/types/preview.d.ts +23 -0
- package/templates/preview-runtime/v1/types/protocol.d.ts +94 -0
- package/templates/preview-runtime/v1/types/runtime.d.ts +14 -0
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
import { randomUUID } from 'node:crypto';
|
|
2
|
+
import { createReadStream, createWriteStream, existsSync } from 'node:fs';
|
|
3
|
+
import { mkdir, mkdtemp, readFile, rm, stat, statfs, writeFile } from 'node:fs/promises';
|
|
4
|
+
import { tmpdir } from 'node:os';
|
|
5
|
+
import { join, resolve } from 'node:path';
|
|
6
|
+
import { Readable, Transform } from 'node:stream';
|
|
7
|
+
import { pipeline } from 'node:stream/promises';
|
|
8
|
+
import { RESOURCE_GRANT_ACTIONS, workspaceSessionScopeSchema } from '@playcraft/common/cloud-workspace/runtime';
|
|
9
|
+
import { PlayCraftHttpClient } from '../utils/playcraft-http-client.js';
|
|
10
|
+
import { createWorkspacePushTransfer, inspectWorkspaceCommit, RAW_ATTRIBUTES, runTransferGit, transferGit } from '../workspace-runtime/workspaces/transfer.js';
|
|
11
|
+
import { workspaceMessage as message } from './messages.js';
|
|
12
|
+
import { normalizeWorkspaceTransferError, transferByteCount, workspaceTransferFailure } from './transfer-errors.js';
|
|
13
|
+
const META_FILE = '.playcraft-workspace.json';
|
|
14
|
+
export function workspaceServiceUrl(baseUrl, path) {
|
|
15
|
+
const base = new URL(baseUrl);
|
|
16
|
+
if (!['http:', 'https:'].includes(base.protocol) || base.username || base.password || base.search || base.hash)
|
|
17
|
+
throw new Error(message('invalidTarget'));
|
|
18
|
+
return `${base.toString().replace(/\/+$/, '')}/${path.replace(/^\/+/, '')}`;
|
|
19
|
+
}
|
|
20
|
+
async function request(client, path, init = {}) {
|
|
21
|
+
const headers = new Headers(init.headers);
|
|
22
|
+
headers.set('Authorization', `Bearer ${client.token}`);
|
|
23
|
+
headers.set('X-Request-Id', client.requestId);
|
|
24
|
+
let response;
|
|
25
|
+
try {
|
|
26
|
+
response = await fetch(workspaceServiceUrl(client.baseUrl, path), {
|
|
27
|
+
...init, headers, redirect: 'error', signal: AbortSignal.timeout(15 * 60_000),
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
catch (error) {
|
|
31
|
+
throw normalizeWorkspaceTransferError(error);
|
|
32
|
+
}
|
|
33
|
+
if (response.ok)
|
|
34
|
+
return response;
|
|
35
|
+
const body = await response.json().catch(() => ({}));
|
|
36
|
+
const value = body && typeof body === 'object' ? body : {};
|
|
37
|
+
const nested = value.error && typeof value.error === 'object' ? value.error : {};
|
|
38
|
+
const code = String(nested.code ?? value.reason ?? value.code ?? value.message ?? value.error ?? response.status);
|
|
39
|
+
const rawDetails = nested.details ?? value.details;
|
|
40
|
+
const details = rawDetails && typeof rawDetails === 'object' ? rawDetails : undefined;
|
|
41
|
+
if (response.status === 401)
|
|
42
|
+
throw new Error(`${message('login')} ${client.baseUrl}`);
|
|
43
|
+
if (code.startsWith('workspace_transfer_') || code === 'invalid_workspace_transfer')
|
|
44
|
+
throw workspaceTransferFailure(code, details);
|
|
45
|
+
if (response.status === 413)
|
|
46
|
+
throw workspaceTransferFailure('workspace_transfer_proxy_too_large');
|
|
47
|
+
if (response.status === 408 || response.status === 504)
|
|
48
|
+
throw workspaceTransferFailure('workspace_transfer_timeout');
|
|
49
|
+
throw workspaceTransferFailure(code, details);
|
|
50
|
+
}
|
|
51
|
+
async function json(client, path, body) {
|
|
52
|
+
const response = await request(client, path, body === undefined ? {} : {
|
|
53
|
+
method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify(body),
|
|
54
|
+
});
|
|
55
|
+
const value = await response.json();
|
|
56
|
+
return (value?.result ?? value);
|
|
57
|
+
}
|
|
58
|
+
async function ensureAccess(client, workspaceId, directoryName) {
|
|
59
|
+
const workspace = await json(client, `/api/cloud-workspaces/${workspaceId}`);
|
|
60
|
+
const scope = workspace.projectId
|
|
61
|
+
? `/api/projects/${encodeURIComponent(workspace.projectId)}/cloud-workspaces/${workspaceId}`
|
|
62
|
+
: `/api/cloud-workspaces/${workspaceId}`;
|
|
63
|
+
let grant = await json(client, `${scope}/runtime-grants`, {
|
|
64
|
+
clientRequestId: `workspace-cli-${randomUUID()}`, actions: RESOURCE_GRANT_ACTIONS,
|
|
65
|
+
});
|
|
66
|
+
for (let attempt = 0; grant.grantStatus === 'pending' && grant.grantRequestId && attempt < 60; attempt++) {
|
|
67
|
+
await new Promise(resolve => setTimeout(resolve, 500));
|
|
68
|
+
grant = await json(client, `${scope}/runtime-grants/${encodeURIComponent(grant.grantRequestId)}`);
|
|
69
|
+
}
|
|
70
|
+
if (grant.grantStatus !== 'ready')
|
|
71
|
+
throw new Error(message('notReady'));
|
|
72
|
+
let operation = await json(client, `/runtime/workspaces/${workspaceId}/ensure`, { clientRequestId: randomUUID(), directoryName: directoryName === 'root' ? 'basic' : directoryName });
|
|
73
|
+
// Ensure may return a queued receipt before the workspace is prepared; a cold container can
|
|
74
|
+
// take minutes, so follow the operation instead of relying on the HTTP response to be final.
|
|
75
|
+
const ensureDeadline = Date.now() + 180_000;
|
|
76
|
+
while (operation.operationId && ['queued', 'dispatched', 'accepted'].includes(operation.status ?? '')
|
|
77
|
+
&& Date.now() < ensureDeadline) {
|
|
78
|
+
await new Promise(resolve => setTimeout(resolve, 500));
|
|
79
|
+
operation = await json(client, `/runtime/operations/${encodeURIComponent(operation.operationId)}`);
|
|
80
|
+
}
|
|
81
|
+
if (operation.status !== 'completed')
|
|
82
|
+
throw new Error(message('notReady'));
|
|
83
|
+
}
|
|
84
|
+
async function localDiskSpace(path, required) {
|
|
85
|
+
const disk = await statfs(path);
|
|
86
|
+
const available = disk.bavail * disk.bsize;
|
|
87
|
+
if (required > available)
|
|
88
|
+
throw workspaceTransferFailure('workspace_transfer_disk_full', { actual: required, limit: available });
|
|
89
|
+
return available;
|
|
90
|
+
}
|
|
91
|
+
async function downloadBundle(response, target, temporary) {
|
|
92
|
+
if (!response.body)
|
|
93
|
+
throw new Error(message('failed'));
|
|
94
|
+
try {
|
|
95
|
+
const available = await localDiskSpace(temporary, Number(response.headers.get('content-length') ?? 0));
|
|
96
|
+
let bytes = 0;
|
|
97
|
+
const guard = new Transform({ transform(chunk, _encoding, callback) {
|
|
98
|
+
bytes += chunk.length;
|
|
99
|
+
callback(bytes > available ? workspaceTransferFailure('workspace_transfer_disk_full', { actual: bytes, limit: available }) : null, chunk);
|
|
100
|
+
} });
|
|
101
|
+
await pipeline(Readable.fromWeb(response.body), guard, createWriteStream(target, { flags: 'wx', mode: 0o600 }));
|
|
102
|
+
const declared = response.headers.get('content-length');
|
|
103
|
+
if (declared && Number(declared) !== bytes)
|
|
104
|
+
throw workspaceTransferFailure('invalid_workspace_transfer');
|
|
105
|
+
}
|
|
106
|
+
finally {
|
|
107
|
+
await response.body.cancel().catch(() => undefined);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
export async function workspaceClone(options) {
|
|
111
|
+
const workspaceId = options.workspaceId.trim(), directoryName = options.directory ?? 'basic';
|
|
112
|
+
if (!/^[a-f0-9-]{36}$/i.test(workspaceId) || !workspaceSessionScopeSchema.safeParse(directoryName).success)
|
|
113
|
+
throw new Error(message('invalidTarget'));
|
|
114
|
+
const target = resolve(options.dir?.trim() || `workspace-${workspaceId}-${directoryName}`);
|
|
115
|
+
if (existsSync(target))
|
|
116
|
+
throw new Error(message('directoryExists'));
|
|
117
|
+
const client = new PlayCraftHttpClient(options);
|
|
118
|
+
await ensureAccess(client, workspaceId, directoryName);
|
|
119
|
+
const temporary = await mkdtemp(join(tmpdir(), 'playcraft-clone-'));
|
|
120
|
+
let created = false;
|
|
121
|
+
try {
|
|
122
|
+
const response = await request(client, `/runtime/workspaces/${workspaceId}/git-bundle?directoryName=${encodeURIComponent(directoryName)}`);
|
|
123
|
+
const tree = response.headers.get('x-playcraft-workspace-tree');
|
|
124
|
+
if (!tree || !/^[a-f0-9]{40,64}$/.test(tree)) {
|
|
125
|
+
await response.body?.cancel();
|
|
126
|
+
throw new Error(message('failed'));
|
|
127
|
+
}
|
|
128
|
+
const bundlePath = join(temporary, 'workspace.bundle');
|
|
129
|
+
await downloadBundle(response, bundlePath, temporary);
|
|
130
|
+
await mkdir(target);
|
|
131
|
+
created = true;
|
|
132
|
+
const contentBytes = Number(response.headers.get('x-playcraft-workspace-content-bytes') ?? 0);
|
|
133
|
+
if (Number.isSafeInteger(contentBytes) && contentBytes > 0)
|
|
134
|
+
await localDiskSpace(target, contentBytes * 2);
|
|
135
|
+
await runTransferGit(temporary, ['clone', '--no-checkout', '--', bundlePath, target]);
|
|
136
|
+
await writeFile(join(target, '.git', 'info', 'attributes'), RAW_ATTRIBUTES);
|
|
137
|
+
await runTransferGit(target, ['checkout', '--force', 'HEAD']);
|
|
138
|
+
if (transferGit(target, ['rev-parse', 'HEAD^{tree}']) !== tree)
|
|
139
|
+
throw new Error(message('failed'));
|
|
140
|
+
transferGit(target, ['config', '--unset-all', 'remote.origin.url']);
|
|
141
|
+
const meta = { schemaVersion: 'playcraft.workspace-checkout.v1', workspaceId, directoryName,
|
|
142
|
+
baseUrl: client.baseUrl, tree };
|
|
143
|
+
await writeFile(join(target, META_FILE), `${JSON.stringify(meta, null, 2)}\n`);
|
|
144
|
+
const excludePath = join(target, '.git', 'info', 'exclude');
|
|
145
|
+
const exclude = await readFile(excludePath, 'utf8').catch(() => '');
|
|
146
|
+
await writeFile(excludePath, `${exclude}\n/${META_FILE}\n`);
|
|
147
|
+
if (options.json)
|
|
148
|
+
console.log(JSON.stringify({ ok: true, command: 'workspace.clone', data: { ...meta, dir: target } }));
|
|
149
|
+
else
|
|
150
|
+
console.log(`${message('cloned')} ${target}`);
|
|
151
|
+
}
|
|
152
|
+
catch (error) {
|
|
153
|
+
if (created)
|
|
154
|
+
await rm(target, { recursive: true, force: true });
|
|
155
|
+
throw normalizeWorkspaceTransferError(error);
|
|
156
|
+
}
|
|
157
|
+
finally {
|
|
158
|
+
await rm(temporary, { recursive: true, force: true });
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
export async function workspacePush(options = {}) {
|
|
162
|
+
const root = resolve(options.dir?.trim() || process.cwd());
|
|
163
|
+
let meta;
|
|
164
|
+
try {
|
|
165
|
+
meta = JSON.parse(await readFile(join(root, META_FILE), 'utf8'));
|
|
166
|
+
if (meta.schemaVersion !== 'playcraft.workspace-checkout.v1' || !/^[a-f0-9-]{36}$/i.test(meta.workspaceId) ||
|
|
167
|
+
!workspaceSessionScopeSchema.safeParse(meta.directoryName).success || !/^[a-f0-9]{40,64}$/.test(meta.tree))
|
|
168
|
+
throw new Error();
|
|
169
|
+
workspaceServiceUrl(meta.baseUrl, '/');
|
|
170
|
+
}
|
|
171
|
+
catch {
|
|
172
|
+
throw new Error(message('metadataMissing'));
|
|
173
|
+
}
|
|
174
|
+
if (transferGit(root, ['status', '--porcelain']).length > 0)
|
|
175
|
+
throw new Error(message('cleanRequired'));
|
|
176
|
+
const client = new PlayCraftHttpClient({ url: options.url || meta.baseUrl, token: options.token });
|
|
177
|
+
if (workspaceServiceUrl(client.baseUrl, '/') !== workspaceServiceUrl(meta.baseUrl, '/'))
|
|
178
|
+
throw new Error(message('invalidTarget'));
|
|
179
|
+
await ensureAccess(client, meta.workspaceId, meta.directoryName);
|
|
180
|
+
const targetTree = transferGit(root, ['rev-parse', 'HEAD^{tree}']);
|
|
181
|
+
const stats = await inspectWorkspaceCommit(root, targetTree).catch(error => { throw normalizeWorkspaceTransferError(error); });
|
|
182
|
+
const path = `/runtime/workspaces/${meta.workspaceId}/git-bundle?directoryName=${encodeURIComponent(meta.directoryName)}`;
|
|
183
|
+
const preflightResponse = await request(client, `${path}&preflight=1&contentBytes=${stats.bytes}&fileCount=${stats.files}&expectedTree=${meta.tree}&targetTree=${targetTree}`);
|
|
184
|
+
if (!preflightResponse.headers.get('content-type')?.includes('application/json')) {
|
|
185
|
+
await preflightResponse.body?.cancel();
|
|
186
|
+
throw workspaceTransferFailure('workspace_transfer_upgrade_required');
|
|
187
|
+
}
|
|
188
|
+
const preflight = await preflightResponse.json();
|
|
189
|
+
if (preflight.protocolVersion !== 2)
|
|
190
|
+
throw workspaceTransferFailure('workspace_transfer_upgrade_required');
|
|
191
|
+
if (preflight.alreadyApplied && preflight.tree === targetTree) {
|
|
192
|
+
meta.tree = targetTree;
|
|
193
|
+
await writeFile(join(root, META_FILE), `${JSON.stringify(meta, null, 2)}\n`);
|
|
194
|
+
if (options.json)
|
|
195
|
+
console.log(JSON.stringify({ ok: true, command: 'workspace.push', data: { tree: targetTree, changedPaths: [] } }));
|
|
196
|
+
else
|
|
197
|
+
console.log(message('pushed'));
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
if (!options.json)
|
|
201
|
+
console.error(message('preflight', { files: stats.files, bytes: transferByteCount(stats.bytes) }));
|
|
202
|
+
let snapshot;
|
|
203
|
+
try {
|
|
204
|
+
snapshot = await createWorkspacePushTransfer(root, meta.tree);
|
|
205
|
+
if (snapshot.tree !== targetTree)
|
|
206
|
+
throw workspaceTransferFailure('workspace_transfer_conflict');
|
|
207
|
+
const bundleBytes = (await stat(snapshot.bundlePath)).size;
|
|
208
|
+
if (!options.json)
|
|
209
|
+
console.error(message('upload', { bytes: transferByteCount(bundleBytes) }));
|
|
210
|
+
const stream = createReadStream(snapshot.bundlePath);
|
|
211
|
+
let response;
|
|
212
|
+
try {
|
|
213
|
+
response = await request(client, path, {
|
|
214
|
+
method: 'POST', headers: { 'content-type': 'application/octet-stream', 'content-length': String(bundleBytes),
|
|
215
|
+
'x-playcraft-workspace-tree': meta.tree }, body: stream, duplex: 'half',
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
finally {
|
|
219
|
+
stream.destroy();
|
|
220
|
+
}
|
|
221
|
+
const result = await response.json();
|
|
222
|
+
if (!/^[a-f0-9]{40,64}$/.test(result.tree))
|
|
223
|
+
throw new Error(message('failed'));
|
|
224
|
+
meta.tree = result.tree;
|
|
225
|
+
await writeFile(join(root, META_FILE), `${JSON.stringify(meta, null, 2)}\n`);
|
|
226
|
+
if (options.json)
|
|
227
|
+
console.log(JSON.stringify({ ok: true, command: 'workspace.push', data: result }));
|
|
228
|
+
else
|
|
229
|
+
console.log(message('pushed'));
|
|
230
|
+
}
|
|
231
|
+
catch (error) {
|
|
232
|
+
throw normalizeWorkspaceTransferError(error);
|
|
233
|
+
}
|
|
234
|
+
finally {
|
|
235
|
+
await snapshot?.cleanup();
|
|
236
|
+
}
|
|
237
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { WorkspaceTransferError } from '../workspace-runtime/workspaces/transfer.js';
|
|
2
|
+
import { workspaceMessage as message } from './messages.js';
|
|
3
|
+
export class WorkspaceCliTransferError extends Error {
|
|
4
|
+
code;
|
|
5
|
+
details;
|
|
6
|
+
constructor(code, text, details) {
|
|
7
|
+
super(text);
|
|
8
|
+
this.code = code;
|
|
9
|
+
this.details = details;
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
export function transferByteCount(value) {
|
|
13
|
+
return typeof value === 'number' && Number.isFinite(value)
|
|
14
|
+
? message('byteCount', { value: value.toLocaleString('en-US') }) : message('byteCount', { value: '—' });
|
|
15
|
+
}
|
|
16
|
+
export function workspaceTransferFailure(code, details) {
|
|
17
|
+
const key = code === 'workspace_transfer_quota_exceeded' ? 'quotaExceeded'
|
|
18
|
+
: code === 'workspace_transfer_disk_full' ? typeof details?.actual === 'number' && typeof details.limit === 'number' ? 'diskFull' : 'diskFullUnknown'
|
|
19
|
+
: code === 'workspace_transfer_storage_unavailable' ? 'diskUnavailable'
|
|
20
|
+
: code === 'workspace_transfer_proxy_too_large' ? 'proxyTooLarge'
|
|
21
|
+
: code === 'workspace_transfer_too_large' ? 'tooLarge'
|
|
22
|
+
: code.includes('timeout') ? 'timeout'
|
|
23
|
+
: code === 'workspace_transfer_network' ? 'network'
|
|
24
|
+
: code === 'workspace_transfer_conflict' ? 'conflict'
|
|
25
|
+
: code === 'workspace_transfer_busy' ? 'busy'
|
|
26
|
+
: code === 'workspace_transfer_git_missing' ? 'gitMissing'
|
|
27
|
+
: code === 'workspace_transfer_base_missing' ? 'baseMissing'
|
|
28
|
+
: code.startsWith('workspace_transfer_git_') ? 'gitFailed'
|
|
29
|
+
: code === 'invalid_workspace_transfer' || code === 'workspace_transfer_symlink' ? 'invalidTransfer'
|
|
30
|
+
: code === 'workspace_transfer_upgrade_required' ? 'upgradeRequired' : 'failed';
|
|
31
|
+
const text = message(key, { actual: transferByteCount(details?.actual), limit: transferByteCount(details?.limit) });
|
|
32
|
+
return new WorkspaceCliTransferError(code, key === 'failed' ? `${text}: ${code}` : text, details);
|
|
33
|
+
}
|
|
34
|
+
export function normalizeWorkspaceTransferError(error) {
|
|
35
|
+
if (error instanceof WorkspaceCliTransferError)
|
|
36
|
+
return error;
|
|
37
|
+
if (error instanceof WorkspaceTransferError)
|
|
38
|
+
return workspaceTransferFailure(error.code, error.details);
|
|
39
|
+
const value = (error && typeof error === 'object' ? error : {});
|
|
40
|
+
if (value.code === 'ENOSPC')
|
|
41
|
+
return workspaceTransferFailure('workspace_transfer_disk_full');
|
|
42
|
+
if (/Timeout|Abort/.test(value.name ?? '') || /TIMEOUT/.test(value.cause?.code ?? ''))
|
|
43
|
+
return workspaceTransferFailure('workspace_transfer_timeout');
|
|
44
|
+
if (/UND_ERR_SOCKET|ECONNRESET|ECONNREFUSED|ENOTFOUND|EPIPE/.test(value.cause?.code ?? value.code ?? '') ||
|
|
45
|
+
error instanceof TypeError && /fetch|network|terminated/i.test(error.message))
|
|
46
|
+
return workspaceTransferFailure('workspace_transfer_network');
|
|
47
|
+
return error instanceof Error ? error : new Error(String(error));
|
|
48
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { gitRead } from './git.js';
|
|
2
|
+
const reference = /^(?:[a-f0-9]{40}|[a-f0-9]{64})$/;
|
|
3
|
+
const excludedDirectories = new Set(['node_modules', 'dist', 'build', 'coverage', 'docs', 'test', 'tests', '__tests__']);
|
|
4
|
+
const contentExtension = /\.(?:[cm]?[jt]sx?|vue|svelte|html?|css|s[ac]ss|less|json5?|ya?ml|xml|glsl|wgsl|vert|frag|lua|py|svg|png|jpe?g|webp|avif|gif|bmp|mp[34]|wav|ogg|m4a|webm|glb|gltf|fbx|obj|bin|atlas|woff2?|ttf|otf)$/i;
|
|
5
|
+
function contentPath(path) {
|
|
6
|
+
const parts = path.split('/');
|
|
7
|
+
if (parts.some(part => part.startsWith('.') || excludedDirectories.has(part)))
|
|
8
|
+
return false;
|
|
9
|
+
const name = parts.at(-1);
|
|
10
|
+
if (/^(?:package(?:-lock)?\.json|npm-shrinkwrap\.json|pnpm-lock\.yaml|yarn\.lock|bun\.lockb?|tsconfig(?:\.[^.]+)?\.json|(?:vite|vitest|webpack|eslint|prettier)\.config\..+)$/i.test(name))
|
|
11
|
+
return false;
|
|
12
|
+
return contentExtension.test(name);
|
|
13
|
+
}
|
|
14
|
+
/** A completed batch member must change code, runtime configuration, or assets. */
|
|
15
|
+
export async function requireBatchContentChanges(root, before, after) {
|
|
16
|
+
if (!reference.test(before) || !reference.test(after))
|
|
17
|
+
throw new Error('batch_change_reference_invalid');
|
|
18
|
+
const stdout = await gitRead(root, ['diff', '--raw', '--no-abbrev', '--no-renames', '-z', before, after, '--']);
|
|
19
|
+
const records = stdout.split('\0'), paths = [];
|
|
20
|
+
for (let index = 0; index + 1 < records.length; index += 2) {
|
|
21
|
+
const [oldMode, newMode, oldBlob, newBlob] = records[index].slice(1).split(' ');
|
|
22
|
+
const path = records[index + 1];
|
|
23
|
+
if ((oldMode === '100644' || oldMode === '100755' || newMode === '100644' || newMode === '100755')
|
|
24
|
+
&& oldBlob !== newBlob && contentPath(path))
|
|
25
|
+
paths.push(path);
|
|
26
|
+
}
|
|
27
|
+
if (!paths.length)
|
|
28
|
+
throw new Error('batch_generation_no_content_change');
|
|
29
|
+
return paths;
|
|
30
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { setTimeout as delay } from 'node:timers/promises';
|
|
2
|
+
import { sandboxBatchMemberStopSchema } from '@playcraft/common/cloud-workspace/runtime';
|
|
3
|
+
import { groupExited, stopProcessGroup } from './processes.js';
|
|
4
|
+
import { settleObservedGenerate } from './generate.js';
|
|
5
|
+
import { memberStatus, readMember, serializeRun, writeMember, writeStopMarker } from './records.js';
|
|
6
|
+
async function prove(record) {
|
|
7
|
+
let stopped = true;
|
|
8
|
+
for (const process of record.processes) {
|
|
9
|
+
if (process.stopped || groupExited(process.pid))
|
|
10
|
+
process.stopped = true;
|
|
11
|
+
else {
|
|
12
|
+
process.stopped = await stopProcessGroup(process);
|
|
13
|
+
if (!process.stopped)
|
|
14
|
+
stopped = false;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
return stopped;
|
|
18
|
+
}
|
|
19
|
+
function sessionBusy(state, record) {
|
|
20
|
+
const session = record.sessionId ? state.journal.getSession(record.sessionId) : undefined;
|
|
21
|
+
if (!session)
|
|
22
|
+
return false;
|
|
23
|
+
return session.turnBlock === 'uncertain' || session.turnStatus === 'starting' || session.turnStatus === 'running';
|
|
24
|
+
}
|
|
25
|
+
/** Read-only besides reconciling process proof. Does not start work or advance a phase. */
|
|
26
|
+
export async function readBatchMemberStatus(state, runId) {
|
|
27
|
+
const record = await readMember(state, runId);
|
|
28
|
+
if (!record)
|
|
29
|
+
return undefined;
|
|
30
|
+
if (record.phase === 'generate' && record.state === 'running') {
|
|
31
|
+
const settled = await settleObservedGenerate(state, record);
|
|
32
|
+
if (settled.state !== 'running')
|
|
33
|
+
return memberStatus(settled);
|
|
34
|
+
}
|
|
35
|
+
const processes = record.processes.map(process => ({ ...process, stopped: process.stopped || groupExited(process.pid) }));
|
|
36
|
+
const live = processes.some(process => !process.stopped);
|
|
37
|
+
let next = { ...record, processes };
|
|
38
|
+
if (live && (record.writersStopped || record.state === 'completed' || record.state === 'stopped' || record.state === 'needs_user' || record.state === 'failed')) {
|
|
39
|
+
next = { ...next, writersStopped: false, state: record.stopRequested ? 'stopping' : 'unknown', holding: true };
|
|
40
|
+
}
|
|
41
|
+
else if (!live && record.state === 'running') {
|
|
42
|
+
const turnLive = sessionBusy(state, record);
|
|
43
|
+
next = { ...next, writersStopped: !turnLive, state: record.stopRequested ? 'stopped' : turnLive ? 'running' : 'unknown', holding: turnLive || !record.stopRequested };
|
|
44
|
+
}
|
|
45
|
+
if (JSON.stringify(next) !== JSON.stringify(record))
|
|
46
|
+
next = await writeMember(state, next);
|
|
47
|
+
return memberStatus(next);
|
|
48
|
+
}
|
|
49
|
+
export async function stopBatchMember(state, runId, body) {
|
|
50
|
+
const parsed = sandboxBatchMemberStopSchema.safeParse({ ...(body && typeof body === 'object' ? body : {}), runId });
|
|
51
|
+
if (!parsed.success)
|
|
52
|
+
throw new Error('invalid_batch_request');
|
|
53
|
+
const command = parsed.data;
|
|
54
|
+
return serializeRun(runId, async () => {
|
|
55
|
+
const existing = await readMember(state, runId);
|
|
56
|
+
if (existing && command.ownerFence < existing.ownerFence)
|
|
57
|
+
throw new Error('stale_owner');
|
|
58
|
+
if (existing?.state === 'completed' && existing.phase === 'deliver' && existing.evidence && existing.writersStopped)
|
|
59
|
+
return memberStatus(existing);
|
|
60
|
+
await writeStopMarker(state, runId, command.ownerFence);
|
|
61
|
+
const session = existing?.sessionId ? state.journal.getSession(existing.sessionId) : undefined;
|
|
62
|
+
if (session?.threadId && existing?.sessionId) {
|
|
63
|
+
await state.native?.interrupt?.({ sessionId: existing.sessionId, threadId: session.threadId }).catch(() => undefined);
|
|
64
|
+
const deadline = Date.now() + 2_000;
|
|
65
|
+
while (sessionBusy(state, existing) && Date.now() < deadline)
|
|
66
|
+
await delay(15);
|
|
67
|
+
}
|
|
68
|
+
const record = existing ?? {
|
|
69
|
+
runId, ownerFence: command.ownerFence, occupancyId: '00000000-0000-0000-0000-000000000000', workspaceId: '00000000-0000-0000-0000-000000000000',
|
|
70
|
+
directoryName: 'basic', phase: 'prepare', state: 'stopping', writersStopped: false, holding: true, stopRequested: true, processes: [],
|
|
71
|
+
updatedAt: new Date().toISOString(),
|
|
72
|
+
};
|
|
73
|
+
const writersStopped = await prove(record) && !sessionBusy(state, record);
|
|
74
|
+
const saved = await writeMember(state, {
|
|
75
|
+
...record, ownerFence: command.ownerFence, stopRequested: true, state: writersStopped ? 'stopped' : 'stopping',
|
|
76
|
+
writersStopped, holding: !writersStopped,
|
|
77
|
+
});
|
|
78
|
+
return memberStatus(saved);
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
export async function rememberProcess(state, runId, process) {
|
|
82
|
+
const record = await readMember(state, runId);
|
|
83
|
+
if (!record)
|
|
84
|
+
return;
|
|
85
|
+
await writeMember(state, { ...record, processes: [...record.processes.filter(item => item.pid !== process.pid), process] });
|
|
86
|
+
}
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
import { constants } from 'node:fs';
|
|
2
|
+
import { lstat, open } from 'node:fs/promises';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
import { hashOperationPayload, sandboxBatchMemberDeliverSchema, } from '@playcraft/common/cloud-workspace/runtime';
|
|
5
|
+
import { workspaceRoot } from '../workspaces/registry.js';
|
|
6
|
+
import { groupExited, runProcessGroup, stopProcessGroup } from './processes.js';
|
|
7
|
+
import { memberStatus, readMember, readStopMarker, serializeRun, writeMember, } from './records.js';
|
|
8
|
+
/** Base build writes dist/index.html. Channel packaging stays on export playable (`--mode full`). */
|
|
9
|
+
function buildCommand() {
|
|
10
|
+
return (process.env.PLAYCRAFT_BATCH_BUILD_COMMAND ?? 'CI=1 playcraft build --mode base --output dist .');
|
|
11
|
+
}
|
|
12
|
+
async function readBuiltHtml(root) {
|
|
13
|
+
const htmlPath = join(root, 'dist', 'index.html');
|
|
14
|
+
const info = await lstat(htmlPath).catch(() => undefined);
|
|
15
|
+
if (!info?.isFile() || info.isSymbolicLink())
|
|
16
|
+
throw new Error('batch_build_failed');
|
|
17
|
+
const handle = await open(htmlPath, constants.O_RDONLY | constants.O_NOFOLLOW).catch(() => {
|
|
18
|
+
throw new Error('batch_build_failed');
|
|
19
|
+
});
|
|
20
|
+
await handle.close();
|
|
21
|
+
}
|
|
22
|
+
async function prove(record) {
|
|
23
|
+
let stopped = true;
|
|
24
|
+
for (const process of record.processes) {
|
|
25
|
+
if (!(process.stopped || groupExited(process.pid))) {
|
|
26
|
+
process.stopped = await stopProcessGroup(process);
|
|
27
|
+
if (!process.stopped)
|
|
28
|
+
stopped = false;
|
|
29
|
+
}
|
|
30
|
+
else
|
|
31
|
+
process.stopped = true;
|
|
32
|
+
}
|
|
33
|
+
return stopped;
|
|
34
|
+
}
|
|
35
|
+
export async function deliverBatchMember(state, body) {
|
|
36
|
+
if (!body ||
|
|
37
|
+
typeof body !== 'object' ||
|
|
38
|
+
!('occupancyId' in body) ||
|
|
39
|
+
!body.occupancyId)
|
|
40
|
+
throw new Error('occupancy_required');
|
|
41
|
+
const parsed = sandboxBatchMemberDeliverSchema.safeParse(body);
|
|
42
|
+
if (!parsed.success)
|
|
43
|
+
throw new Error('invalid_batch_request');
|
|
44
|
+
const command = parsed.data;
|
|
45
|
+
const hash = hashOperationPayload(command);
|
|
46
|
+
return serializeRun(command.runId, async () => {
|
|
47
|
+
const existing = await readMember(state, command.runId);
|
|
48
|
+
if (existing && command.ownerFence < existing.ownerFence)
|
|
49
|
+
throw new Error('stale_owner');
|
|
50
|
+
if (existing?.state === 'completed' &&
|
|
51
|
+
existing.phase === 'deliver' &&
|
|
52
|
+
existing.evidence &&
|
|
53
|
+
existing.commandHash?.deliver === hash &&
|
|
54
|
+
existing.ownerFence === command.ownerFence) {
|
|
55
|
+
return memberStatus(existing);
|
|
56
|
+
}
|
|
57
|
+
if ((await readStopMarker(state, command.runId)) &&
|
|
58
|
+
existing?.state === 'completed' &&
|
|
59
|
+
existing.evidence)
|
|
60
|
+
return memberStatus(existing);
|
|
61
|
+
if (await readStopMarker(state, command.runId))
|
|
62
|
+
return memberStatus(await markStopped(state, command, existing));
|
|
63
|
+
if (existing &&
|
|
64
|
+
existing.ownerFence === command.ownerFence &&
|
|
65
|
+
existing.commandHash?.deliver &&
|
|
66
|
+
existing.commandHash.deliver !== hash &&
|
|
67
|
+
existing.state === 'completed') {
|
|
68
|
+
throw new Error('batch_command_conflict');
|
|
69
|
+
}
|
|
70
|
+
const root = await workspaceRoot(state, command.workspaceId, command.directoryName);
|
|
71
|
+
let record = await writeMember(state, {
|
|
72
|
+
runId: command.runId,
|
|
73
|
+
ownerFence: command.ownerFence,
|
|
74
|
+
occupancyId: command.occupancyId,
|
|
75
|
+
workspaceId: command.workspaceId,
|
|
76
|
+
directoryName: command.directoryName,
|
|
77
|
+
phase: 'deliver',
|
|
78
|
+
state: 'running',
|
|
79
|
+
writersStopped: false,
|
|
80
|
+
holding: true,
|
|
81
|
+
stopRequested: false,
|
|
82
|
+
processes: existing?.processes ?? [],
|
|
83
|
+
sessionId: existing?.sessionId,
|
|
84
|
+
operationId: existing?.operationId,
|
|
85
|
+
commandHash: { ...existing?.commandHash, deliver: hash },
|
|
86
|
+
snapshotDigest: existing?.snapshotDigest,
|
|
87
|
+
branchName: existing?.branchName,
|
|
88
|
+
updatedAt: new Date().toISOString(),
|
|
89
|
+
sourceCommit: existing?.sourceCommit ??
|
|
90
|
+
state.journal
|
|
91
|
+
.getWorkspace(command.workspaceId)
|
|
92
|
+
?.directories?.find((directory) => directory.directoryName === command.directoryName)
|
|
93
|
+
?.sourceCommit,
|
|
94
|
+
beforeTree: existing?.beforeTree,
|
|
95
|
+
});
|
|
96
|
+
try {
|
|
97
|
+
if (await readStopMarker(state, command.runId))
|
|
98
|
+
return memberStatus(await markStopped(state, command, record));
|
|
99
|
+
const built = await runProcessGroup(root, buildCommand(), 'build', async (process) => {
|
|
100
|
+
record = await writeMember(state, { ...record, processes: [...record.processes, process] });
|
|
101
|
+
});
|
|
102
|
+
record = await writeMember(state, {
|
|
103
|
+
...record,
|
|
104
|
+
processes: record.processes.map((process) => process.pid === built.process.pid ? built.process : process),
|
|
105
|
+
});
|
|
106
|
+
if (await readStopMarker(state, command.runId))
|
|
107
|
+
return memberStatus(await markStopped(state, command, record));
|
|
108
|
+
if (built.code !== 0)
|
|
109
|
+
throw new Error('batch_build_failed');
|
|
110
|
+
await readBuiltHtml(root);
|
|
111
|
+
const writersStopped = await prove(record);
|
|
112
|
+
if (!writersStopped)
|
|
113
|
+
return memberStatus(await writeMember(state, {
|
|
114
|
+
...record,
|
|
115
|
+
state: 'stopping',
|
|
116
|
+
writersStopped: false,
|
|
117
|
+
holding: true,
|
|
118
|
+
}));
|
|
119
|
+
if (await readStopMarker(state, command.runId))
|
|
120
|
+
return memberStatus(await markStopped(state, command, record));
|
|
121
|
+
record = await writeMember(state, {
|
|
122
|
+
...record,
|
|
123
|
+
state: 'completed',
|
|
124
|
+
writersStopped: true,
|
|
125
|
+
holding: false,
|
|
126
|
+
error: undefined,
|
|
127
|
+
evidence: { verified: true },
|
|
128
|
+
});
|
|
129
|
+
return memberStatus(record);
|
|
130
|
+
}
|
|
131
|
+
catch (error) {
|
|
132
|
+
const code = error instanceof Error && /^[a-z0-9_]+$/.test(error.message)
|
|
133
|
+
? error.message
|
|
134
|
+
: 'batch_deliver_failed';
|
|
135
|
+
const writersStopped = await prove(record);
|
|
136
|
+
record = await writeMember(state, {
|
|
137
|
+
...record,
|
|
138
|
+
state: writersStopped ? 'failed' : 'stopping',
|
|
139
|
+
writersStopped,
|
|
140
|
+
holding: !writersStopped,
|
|
141
|
+
error: code,
|
|
142
|
+
});
|
|
143
|
+
return memberStatus(record);
|
|
144
|
+
}
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
async function markStopped(state, command, existing) {
|
|
148
|
+
if (existing)
|
|
149
|
+
await prove(existing);
|
|
150
|
+
return writeMember(state, {
|
|
151
|
+
runId: command.runId,
|
|
152
|
+
ownerFence: command.ownerFence,
|
|
153
|
+
occupancyId: existing?.occupancyId ?? command.occupancyId,
|
|
154
|
+
workspaceId: command.workspaceId,
|
|
155
|
+
directoryName: command.directoryName,
|
|
156
|
+
phase: 'deliver',
|
|
157
|
+
state: 'stopped',
|
|
158
|
+
writersStopped: true,
|
|
159
|
+
holding: false,
|
|
160
|
+
stopRequested: true,
|
|
161
|
+
processes: existing?.processes ?? [],
|
|
162
|
+
sessionId: existing?.sessionId,
|
|
163
|
+
operationId: existing?.operationId,
|
|
164
|
+
headCommit: existing?.headCommit,
|
|
165
|
+
evidence: existing?.evidence,
|
|
166
|
+
commandHash: existing?.commandHash,
|
|
167
|
+
snapshotDigest: existing?.snapshotDigest,
|
|
168
|
+
branchName: existing?.branchName,
|
|
169
|
+
updatedAt: new Date().toISOString(),
|
|
170
|
+
sourceCommit: existing?.sourceCommit,
|
|
171
|
+
beforeTree: existing?.beforeTree,
|
|
172
|
+
});
|
|
173
|
+
}
|