@playcraft/cli 0.1.42 → 0.1.43-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/image.js +2 -6
- 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/skills.js +4 -0
- package/dist/commands/tools-3d.js +11 -0
- package/dist/commands/tools-batch.js +87 -0
- package/dist/commands/tools-generation.js +69 -137
- package/dist/commands/tools-utils.js +0 -12
- package/dist/commands/tools.js +2 -2
- 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 +40 -3
- 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 +233 -0
- package/dist/workspace/transfer-errors.js +48 -0
- package/dist/workspace-runtime/codex/app-server.js +1040 -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/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 +70 -0
- package/dist/workspace-runtime/codex/thread-image.js +38 -0
- package/dist/workspace-runtime/codex/ui-instructions.js +4 -0
- package/dist/workspace-runtime/codex/ui-tools.js +186 -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/output.js +36 -0
- package/dist/workspace-runtime/previews/publish.js +262 -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/production/content-changes.js +36 -0
- package/dist/workspace-runtime/production/contracts.js +19 -0
- package/dist/workspace-runtime/production/controller.js +225 -0
- package/dist/workspace-runtime/production/dependencies.js +60 -0
- package/dist/workspace-runtime/production/native.js +239 -0
- package/dist/workspace-runtime/production/processes.js +102 -0
- package/dist/workspace-runtime/production/save-command.js +35 -0
- package/dist/workspace-runtime/production/source-archive.js +137 -0
- package/dist/workspace-runtime/production/source-copy.js +92 -0
- package/dist/workspace-runtime/production/stages.js +354 -0
- package/dist/workspace-runtime/production/storage.js +75 -0
- package/dist/workspace-runtime/serve.js +281 -0
- package/dist/workspace-runtime/server/auth.js +21 -0
- package/dist/workspace-runtime/server/connectivity.js +201 -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 +1335 -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 +1291 -0
- package/dist/workspace-runtime/server/lock.js +53 -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/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/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 +366 -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 +112 -0
- package/dist/workspace-runtime/workspaces/cli-context.js +23 -0
- package/dist/workspace-runtime/workspaces/cli-credentials.js +111 -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/dependencies.js +264 -0
- package/dist/workspace-runtime/workspaces/developer-command.js +64 -0
- package/dist/workspace-runtime/workspaces/directories.js +137 -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-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 +170 -0
- package/dist/workspace-runtime/workspaces/library-attachments.js +268 -0
- package/dist/workspace-runtime/workspaces/optional-platform-skills.js +14 -0
- package/dist/workspace-runtime/workspaces/parameters.js +272 -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/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/agents/refs/ta-image-generation-detail.md +123 -67
- package/project-template/.claude/skills/playcraft-image-generation/SKILL.md +63 -135
- package/project-template/.claude/skills/playcraft-image-generation/reference/gen-batch-sprite.template.mjs +33 -42
- package/project-template/.claude/skills/playcraft-image-generation/reference/gen-batch.template.mjs +20 -18
- package/project-template/.claude/skills/playcraft-image-generation/reference/process-batch.template.mjs +7 -6
- package/project-template/.claude/skills/playcraft-image-generation/reference/prompt-cookbook.md +39 -39
- 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 +298 -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 +324 -0
- package/project-template-v2/.claude/skills/playcraft-image-generation/reference/gen-batch.template.mjs +97 -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 +137 -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/project-templates/phaser/.claude/agents/refs/ta-image-generation-detail.md +59 -59
- package/project-templates/phaser/.claude/skills/playcraft-image-generation/SKILL.md +63 -135
- package/project-templates/phaser/.claude/skills/playcraft-image-generation/reference/gen-batch-sprite.template.mjs +33 -42
- package/project-templates/phaser/.claude/skills/playcraft-image-generation/reference/gen-batch.template.mjs +20 -18
- package/project-templates/phaser/.claude/skills/playcraft-image-generation/reference/process-batch.template.mjs +7 -6
- package/project-templates/phaser/.claude/skills/playcraft-image-generation/reference/prompt-cookbook.md +39 -39
- package/project-templates/threejs/.claude/agents/refs/ta-image-generation-detail.md +59 -59
- package/project-templates/threejs/.claude/skills/playcraft-image-generation/SKILL.md +63 -135
- package/project-templates/threejs/.claude/skills/playcraft-image-generation/reference/gen-batch-sprite.template.mjs +33 -42
- package/project-templates/threejs/.claude/skills/playcraft-image-generation/reference/gen-batch.template.mjs +20 -18
- package/project-templates/threejs/.claude/skills/playcraft-image-generation/reference/process-batch.template.mjs +7 -6
- package/project-templates/threejs/.claude/skills/playcraft-image-generation/reference/prompt-cookbook.md +39 -39
- package/dist/commands/tools-image-context.js +0 -29
- package/dist/utils/image-asset-context.js +0 -228
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
const messages = {
|
|
2
|
+
en: {
|
|
3
|
+
compare: 'Compare a recorded playable with its reference and extract paired evidence',
|
|
4
|
+
processing: 'static | agentic (defaults to static)',
|
|
5
|
+
reference: 'Original local reference video', analysis: 'Completed reference analysis.json',
|
|
6
|
+
candidate: 'Locally recorded candidate video', recording: 'Recording metadata.json with input and control evidence',
|
|
7
|
+
policy: 'Optional versioned comparison policy JSON', kind: 'understanding | comparison (defaults to understanding)',
|
|
8
|
+
invalidComparison: 'Comparison arguments or policy are invalid.',
|
|
9
|
+
unsafeOutput: 'The comparison output contains a linked or invalid directory or artifact. Choose a safe output directory.',
|
|
10
|
+
invalidRecording: 'Recording metadata, source hashes, input logs, or controls are invalid.',
|
|
11
|
+
staleRecording: 'The project changed after recording. Make a fresh recording before comparison.',
|
|
12
|
+
comparisonContext: 'The reference analysis does not match the reference video.',
|
|
13
|
+
comparisonResume: 'The saved comparison request is missing or does not match this run and workspace.',
|
|
14
|
+
comparisonUnavailable: 'No authorized model supports the requested comparison.',
|
|
15
|
+
comparisonResult: 'The comparison service returned invalid or incomplete evidence.',
|
|
16
|
+
comparisonFailed: 'Comparison failed. Inspect its saved request and task before retrying.',
|
|
17
|
+
summary: 'Comparison', decision: 'Policy decision', alignment: 'Model event alignment',
|
|
18
|
+
differences: 'Model-observed differences and modification suggestions', coverage: 'Coverage and uncertainty',
|
|
19
|
+
evidence: 'Paired evidence', controls: 'Model interpretation of interaction controls',
|
|
20
|
+
reportStatus: 'Result', reportRequiredKinds: 'Required event kinds', reportMissingKinds: 'Missing event kinds',
|
|
21
|
+
reportMissingEvidence: 'Missing or uncertain evidence', reportTimingChecks: 'Deterministic timing checks', reportNone: 'none',
|
|
22
|
+
modelObservations: 'Model observations', modelSummary: 'Model summary', modelDimensions: 'Model dimension judgments',
|
|
23
|
+
modelObservationNotice: 'These are model observations and interpretations. A model pass does not override the policy decision or missing evidence above.',
|
|
24
|
+
policyPassedNotice: 'Policy acceptance passed for this recorded scenario.',
|
|
25
|
+
policyNotPassedNotice: 'Policy acceptance has not passed. Gameplay equivalence has not been established.',
|
|
26
|
+
deterministicNotice: 'Coverage and timing checks below are deterministic. Missing evidence remains unverified even when a model observation says pass.',
|
|
27
|
+
interactionUnverified: 'Interaction is not fully verified: required causal or control evidence is missing or uncertain.',
|
|
28
|
+
timingUnverified: 'Timing is not fully verified: evidence precision or coverage is insufficient for the policy tolerance.',
|
|
29
|
+
reasonReferenceKindMissing: 'Reference analysis lacks a required event kind.',
|
|
30
|
+
reasonEventPairMissing: 'Reference and candidate lack a sufficiently confident event pair.',
|
|
31
|
+
reasonDimensionUncertain: 'The model judgment for this dimension is uncertain or below the required confidence.',
|
|
32
|
+
reasonDimensionEventMissing: 'This dimension does not cover a required event.',
|
|
33
|
+
reasonProgressionEvidenceMissing: 'Evidence for event progression timing is missing or insufficient.',
|
|
34
|
+
reasonTimingPrecisionInsufficient: 'Timing precision or coverage cannot resolve the policy tolerance.',
|
|
35
|
+
reasonTimingFailureUnverified: 'The model timing failure is not confirmed by deterministic timing checks.',
|
|
36
|
+
reasonRequiredEffectsMissing: 'Required gameplay results are not linked to recorded input steps.',
|
|
37
|
+
reasonControlRuntimeUncertain: 'A control recording contains runtime errors or failed requests.',
|
|
38
|
+
reasonControlEvidenceMissing: 'Required control evidence is missing, uncertain, or does not cover the candidate result window.',
|
|
39
|
+
reasonRequestsFailed: 'The candidate recording contains failed network requests.',
|
|
40
|
+
reportScope: 'Scope', reportModel: 'Model', reportPolicy: 'Policy', reportRecording: 'Recording',
|
|
41
|
+
reportExpected: 'Expected', reportObserved: 'Observed', reportSuggestedFix: 'Suggested fix',
|
|
42
|
+
reportConfidence: 'Confidence', reportEvidence: 'evidence', reportReference: 'reference', reportCandidate: 'candidate',
|
|
43
|
+
reportRequired: 'Required', reportEvaluated: 'Evaluated', reportMissing: 'Missing', reportTiming: 'Timing',
|
|
44
|
+
reportTolerance: 'tolerance', reportRequested: 'requested', reportDecoded: 'decoded', reportFrame: 'frame',
|
|
45
|
+
coverageNotice: 'This result covers only the recorded scenario. Audio and unshown gameplay branches were not evaluated.',
|
|
46
|
+
},
|
|
47
|
+
zh: {
|
|
48
|
+
compare: '比较复刻玩法录像与参考视频,并抽取成对证据',
|
|
49
|
+
processing: 'static | agentic,默认 static',
|
|
50
|
+
reference: '本地原始参考视频', analysis: '已完成的参考分析 analysis.json',
|
|
51
|
+
candidate: '本地录制的候选视频', recording: '包含输入和对照证据的录制 metadata.json',
|
|
52
|
+
policy: '可选的版本化比较策略 JSON', kind: 'understanding | comparison,默认 understanding',
|
|
53
|
+
invalidComparison: '比较参数或策略无效。',
|
|
54
|
+
unsafeOutput: '比较输出目录或制品包含符号链接或无效文件,请使用安全的输出目录。',
|
|
55
|
+
invalidRecording: '录制元数据、视频摘要、输入日志或对照证据无效。',
|
|
56
|
+
staleRecording: '工程在录制后发生了变化,请重新录制再比较。',
|
|
57
|
+
comparisonContext: '参考分析与参考视频不一致。',
|
|
58
|
+
comparisonResume: '比较请求不存在,或与任务、工作区不一致。',
|
|
59
|
+
comparisonUnavailable: '没有有权使用且支持所需比较模式的模型。',
|
|
60
|
+
comparisonResult: '比较服务返回的证据无效或不完整。',
|
|
61
|
+
comparisonFailed: '比较失败,请先核对已保存的请求和任务再重试。',
|
|
62
|
+
summary: '比较报告', decision: '策略结论', alignment: '模型事件配对',
|
|
63
|
+
differences: '模型观察到的差异和修改建议', coverage: '覆盖范围与未知项', evidence: '成对证据',
|
|
64
|
+
controls: '模型对交互对照的解释', coverageNotice: '结论仅覆盖本次录制场景;音频及未展示的玩法分支尚未评估。',
|
|
65
|
+
reportStatus: '结论', reportRequiredKinds: '必需事件类型', reportMissingKinds: '缺失事件类型',
|
|
66
|
+
reportMissingEvidence: '缺失或不确定的证据', reportTimingChecks: '确定性时序检查', reportNone: '无',
|
|
67
|
+
modelObservations: '模型观察', modelSummary: '模型摘要', modelDimensions: '模型维度判断',
|
|
68
|
+
modelObservationNotice: '以下内容是模型的观察和解释。模型判断为 pass,不会覆盖前面的策略结论或证据缺口。',
|
|
69
|
+
policyPassedNotice: '本次录制场景已通过策略验收。',
|
|
70
|
+
policyNotPassedNotice: '策略验收尚未通过,玩法等价性尚未得到验证。',
|
|
71
|
+
deterministicNotice: '以下覆盖范围和时序检查由确定性规则计算。即使模型判断为 pass,缺失证据的部分仍未得到验证。',
|
|
72
|
+
interactionUnverified: '交互尚未完整验证:必需的因果或对照证据缺失或不确定。',
|
|
73
|
+
timingUnverified: '时序尚未完整验证:证据精度或覆盖不足,无法满足策略容差。',
|
|
74
|
+
reasonReferenceKindMissing: '参考分析缺少必需的事件类型。',
|
|
75
|
+
reasonEventPairMissing: '参考视频与候选视频缺少达到置信度要求的事件配对。',
|
|
76
|
+
reasonDimensionUncertain: '模型对该维度的判断不确定,或未达到置信度要求。',
|
|
77
|
+
reasonDimensionEventMissing: '该维度尚未覆盖必需事件。',
|
|
78
|
+
reasonProgressionEvidenceMissing: '事件进展的时序证据缺失或不足。',
|
|
79
|
+
reasonTimingPrecisionInsufficient: '时序证据的精度或覆盖不足,无法按策略容差得出结论。',
|
|
80
|
+
reasonTimingFailureUnverified: '模型报告的时序失败尚未得到确定性时序检查的确认。',
|
|
81
|
+
reasonRequiredEffectsMissing: '必需的玩法结果尚未关联到录制输入步骤。',
|
|
82
|
+
reasonControlRuntimeUncertain: '对照录制存在运行错误或网络请求失败。',
|
|
83
|
+
reasonControlEvidenceMissing: '必需的对照证据缺失、不确定,或未覆盖候选结果的时间窗口。',
|
|
84
|
+
reasonRequestsFailed: '候选录制存在网络请求失败。',
|
|
85
|
+
reportScope: '范围', reportModel: '模型', reportPolicy: '策略', reportRecording: '录制',
|
|
86
|
+
reportExpected: '预期', reportObserved: '实际', reportSuggestedFix: '修改建议',
|
|
87
|
+
reportConfidence: '置信度', reportEvidence: '证据', reportReference: '参考', reportCandidate: '候选',
|
|
88
|
+
reportRequired: '必需', reportEvaluated: '已评估', reportMissing: '缺失', reportTiming: '时序',
|
|
89
|
+
reportTolerance: '容差', reportRequested: '请求时刻', reportDecoded: '解码时刻', reportFrame: '帧',
|
|
90
|
+
},
|
|
91
|
+
};
|
|
92
|
+
export function comparisonMessage(key) {
|
|
93
|
+
const locale = process.env.PLAYCRAFT_LANGUAGE || process.env.LANG || 'en';
|
|
94
|
+
return messages[locale.startsWith('zh') ? 'zh' : 'en'][key];
|
|
95
|
+
}
|
|
96
|
+
export class ComparisonCliError extends Error {
|
|
97
|
+
code;
|
|
98
|
+
exitCode;
|
|
99
|
+
constructor(code, exitCode = 4) {
|
|
100
|
+
super(comparisonMessage(code));
|
|
101
|
+
this.code = code;
|
|
102
|
+
this.exitCode = exitCode;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { randomUUID } from 'node:crypto';
|
|
2
|
+
import { lstat, mkdir, realpath, rename, rm, writeFile } from 'node:fs/promises';
|
|
3
|
+
import { dirname, extname, isAbsolute, join, parse, resolve, sep } from 'node:path';
|
|
4
|
+
import { ComparisonCliError } from './comparison-messages.js';
|
|
5
|
+
export async function comparisonOutputRoot(root, create = false) {
|
|
6
|
+
const absolute = resolve(root), prefix = parse(absolute).root;
|
|
7
|
+
let current = prefix;
|
|
8
|
+
for (const part of absolute.slice(prefix.length).split(sep).filter(Boolean)) {
|
|
9
|
+
current = join(current, part);
|
|
10
|
+
if (create)
|
|
11
|
+
await mkdir(current, { mode: 0o700 }).catch(error => { if (error.code !== 'EEXIST')
|
|
12
|
+
throw error; });
|
|
13
|
+
const stat = await lstat(current);
|
|
14
|
+
if (stat.isSymbolicLink()) {
|
|
15
|
+
// macOS exposes system-managed temporary directories through these aliases.
|
|
16
|
+
const systemAlias = process.platform === 'darwin' && ['/tmp', '/var'].includes(current)
|
|
17
|
+
&& await realpath(current) === `/private${current}`;
|
|
18
|
+
if (!systemAlias)
|
|
19
|
+
throw new ComparisonCliError('unsafeOutput', 64);
|
|
20
|
+
current = await realpath(current);
|
|
21
|
+
}
|
|
22
|
+
else if (!stat.isDirectory())
|
|
23
|
+
throw new ComparisonCliError('unsafeOutput', 64);
|
|
24
|
+
}
|
|
25
|
+
return realpath(current);
|
|
26
|
+
}
|
|
27
|
+
/** Refuse linked output directories and files, including artifacts left before resume. */
|
|
28
|
+
export async function comparisonOutputPath(root, relative) {
|
|
29
|
+
if (isAbsolute(relative) || relative.includes('\\') || relative.split('/').some(part => !part || part === '.' || part === '..'))
|
|
30
|
+
throw new ComparisonCliError('unsafeOutput', 64);
|
|
31
|
+
let current = await comparisonOutputRoot(root);
|
|
32
|
+
const parts = relative.split('/');
|
|
33
|
+
for (const part of parts.slice(0, -1)) {
|
|
34
|
+
current = join(current, part);
|
|
35
|
+
await mkdir(current, { mode: 0o700 }).catch(error => { if (error.code !== 'EEXIST')
|
|
36
|
+
throw error; });
|
|
37
|
+
const stat = await lstat(current);
|
|
38
|
+
if (!stat.isDirectory() || stat.isSymbolicLink())
|
|
39
|
+
throw new ComparisonCliError('unsafeOutput', 64);
|
|
40
|
+
}
|
|
41
|
+
const path = join(current, parts.at(-1));
|
|
42
|
+
const stat = await lstat(path).catch(error => { if (error.code === 'ENOENT')
|
|
43
|
+
return undefined; throw error; });
|
|
44
|
+
if (stat && (!stat.isFile() || stat.isSymbolicLink()))
|
|
45
|
+
throw new ComparisonCliError('unsafeOutput', 64);
|
|
46
|
+
return path;
|
|
47
|
+
}
|
|
48
|
+
/** Writers use a private random sibling and replace the destination without following it. */
|
|
49
|
+
export async function writeComparisonArtifact(root, relative, writer) {
|
|
50
|
+
const target = await comparisonOutputPath(root, relative);
|
|
51
|
+
const temporary = join(dirname(target), `.comparison-${randomUUID()}.tmp${extname(target)}`);
|
|
52
|
+
try {
|
|
53
|
+
await writer(temporary);
|
|
54
|
+
await comparisonOutputPath(root, relative);
|
|
55
|
+
await rename(temporary, target);
|
|
56
|
+
}
|
|
57
|
+
finally {
|
|
58
|
+
await rm(temporary, { force: true });
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
export async function writeComparisonJson(root, relative, value) {
|
|
62
|
+
await writeComparisonArtifact(root, relative, temporary => writeFile(temporary, `${JSON.stringify(value, null, 2)}\n`, { mode: 0o600, flag: 'wx' }));
|
|
63
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { readFile } from 'node:fs/promises';
|
|
2
|
+
import { dirname, resolve } from 'node:path';
|
|
3
|
+
import { canonicalReplicaJson, replicaRecordingMetadataSchema, videoUnderstandingAnalysisSchema, validateVideoUnderstandingContent, } from '@playcraft/common/agent-tools';
|
|
4
|
+
import { computeProjectInputDigest } from '../recording/project-digest.js';
|
|
5
|
+
import { probeVideo } from './media.js';
|
|
6
|
+
import { ComparisonCliError } from './comparison-messages.js';
|
|
7
|
+
import { validateComparisonConsoleLog, validateComparisonInputLog } from './comparison-log.js';
|
|
8
|
+
import { comparisonSha256, readComparisonJson, recordingArtifactPath, verifyRecordingArtifact } from './comparison-files.js';
|
|
9
|
+
function sameSource(actual, expected) {
|
|
10
|
+
return actual.sha256 === expected.sha256 && actual.sizeBytes === expected.sizeBytes && actual.mimeType === expected.mimeType
|
|
11
|
+
&& Math.abs(actual.durationMs - expected.durationMs) < 1 && actual.width === expected.width && actual.height === expected.height;
|
|
12
|
+
}
|
|
13
|
+
/** Validate hashes and the live checkout before any managed upload or model submission. */
|
|
14
|
+
export async function preflightComparison(input) {
|
|
15
|
+
const probe = input.probe ?? probeVideo;
|
|
16
|
+
let recording;
|
|
17
|
+
let analysis;
|
|
18
|
+
try {
|
|
19
|
+
recording = replicaRecordingMetadataSchema.parse(await readComparisonJson(input.recording));
|
|
20
|
+
analysis = videoUnderstandingAnalysisSchema.parse(await readComparisonJson(input.analysis));
|
|
21
|
+
validateVideoUnderstandingContent({ summary: analysis.summary, observations: analysis.observations, events: analysis.events, evidence: analysis.evidence, inferences: analysis.inferences, unknowns: analysis.unknowns, reconstructionHints: analysis.reconstructionHints }, analysis.source);
|
|
22
|
+
}
|
|
23
|
+
catch {
|
|
24
|
+
throw new ComparisonCliError('invalidRecording', 64);
|
|
25
|
+
}
|
|
26
|
+
if (comparisonSha256(canonicalReplicaJson(recording.scenario)) !== recording.scenarioSha256)
|
|
27
|
+
throw new ComparisonCliError('invalidRecording', 64);
|
|
28
|
+
const cwd = resolve(input.cwd ?? process.cwd());
|
|
29
|
+
const root = dirname(resolve(input.recording));
|
|
30
|
+
const current = await computeProjectInputDigest(cwd, [root, resolve(input.out)]);
|
|
31
|
+
if (resolve(recording.project.root) !== current.root || recording.project.sha256 !== current.sha256 || recording.project.fileCount !== current.fileCount)
|
|
32
|
+
throw new ComparisonCliError('staleRecording', 64);
|
|
33
|
+
const reference = await probe(resolve(input.reference), input.signal);
|
|
34
|
+
if (!sameSource(reference.source, analysis.source))
|
|
35
|
+
throw new ComparisonCliError('comparisonContext', 64);
|
|
36
|
+
const candidatePath = await recordingArtifactPath(root, recording.candidate.video.path);
|
|
37
|
+
const candidate = await probe(resolve(input.candidate), input.signal);
|
|
38
|
+
if (candidate.path !== candidatePath || !sameSource(candidate.source, recording.candidate.video.source))
|
|
39
|
+
throw new ComparisonCliError('invalidRecording', 64);
|
|
40
|
+
const inputs = [{ id: 'reference', role: 'reference', video: reference }, { id: recording.candidate.id, role: 'candidate', video: candidate }];
|
|
41
|
+
for (const artifact of [recording.candidate, ...recording.controls]) {
|
|
42
|
+
input.signal.throwIfAborted();
|
|
43
|
+
const events = await verifyRecordingArtifact(root, artifact.events);
|
|
44
|
+
const consolePath = await verifyRecordingArtifact(root, artifact.console);
|
|
45
|
+
for (const shot of artifact.screenshots ?? [])
|
|
46
|
+
await verifyRecordingArtifact(root, shot, 16 * 1024 * 1024);
|
|
47
|
+
// Logs must contain structured records; an empty candidate input log cannot establish real interaction.
|
|
48
|
+
const text = await readFile(events, 'utf8');
|
|
49
|
+
try {
|
|
50
|
+
const records = text.trim() ? text.trim().split('\n').map(line => JSON.parse(line)) : [];
|
|
51
|
+
validateComparisonInputLog(records, recording.scenario, artifact);
|
|
52
|
+
const consoleText = await readFile(consolePath, 'utf8');
|
|
53
|
+
validateComparisonConsoleLog(consoleText.trim() ? consoleText.trim().split('\n').map(line => JSON.parse(line)) : [], artifact);
|
|
54
|
+
}
|
|
55
|
+
catch {
|
|
56
|
+
throw new ComparisonCliError('invalidRecording', 64);
|
|
57
|
+
}
|
|
58
|
+
if (artifact.kind !== 'candidate') {
|
|
59
|
+
const video = await probe(await recordingArtifactPath(root, artifact.video.path), input.signal);
|
|
60
|
+
if (!sameSource(video.source, artifact.video.source))
|
|
61
|
+
throw new ComparisonCliError('invalidRecording', 64);
|
|
62
|
+
inputs.push({ id: artifact.id, role: 'control', video });
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return { recording, analysis, inputs };
|
|
66
|
+
}
|
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
import { randomUUID } from 'node:crypto';
|
|
2
|
+
import { readdir } from 'node:fs/promises';
|
|
3
|
+
import { join, resolve } from 'node:path';
|
|
4
|
+
import { setTimeout as delay } from 'node:timers/promises';
|
|
5
|
+
import { canonicalReplicaJson, evaluateReplicaComparison, replicaDefaultPolicy, replicaPolicySchema, replicaValidationResultSchema, validateReplicaComparisonContext, validateReplicaComparisonContent, videoComparisonRequestSchema, videoComparisonRunSchema, videoModelCatalogSchema, } from '@playcraft/common/agent-tools';
|
|
6
|
+
import { ensureVideoEvidenceGitExcluded } from '../utils/video-evidence-git.js';
|
|
7
|
+
import { requireVideoId, resolveVideoWorkspace, uploadVideo } from './client.js';
|
|
8
|
+
import { VideoCliError, videoMessage } from './messages.js';
|
|
9
|
+
import { describeVideoFailure, remoteVideoFailure } from './diagnostics.js';
|
|
10
|
+
import { comparisonMessage, ComparisonCliError } from './comparison-messages.js';
|
|
11
|
+
import { comparisonSha256, readComparisonJson } from './comparison-files.js';
|
|
12
|
+
import { preflightComparison } from './comparison-preflight.js';
|
|
13
|
+
import { comparisonOutputPath, comparisonOutputRoot, writeComparisonJson } from './comparison-output.js';
|
|
14
|
+
async function prepareComparisonOutput(out, runId, resume) {
|
|
15
|
+
if (!(await ensureVideoEvidenceGitExcluded(process.cwd())))
|
|
16
|
+
console.error(videoMessage('gitExcludeWarning'));
|
|
17
|
+
await comparisonOutputRoot(out, true);
|
|
18
|
+
await comparisonOutputPath(out, 'request.json');
|
|
19
|
+
const names = await readdir(out);
|
|
20
|
+
if (!names.length && !resume)
|
|
21
|
+
return;
|
|
22
|
+
try {
|
|
23
|
+
const saved = await readComparisonJson(join(out, 'request.json'));
|
|
24
|
+
if (resume && saved.schemaVersion === 'playcraft.video-comparison-local-request.v1' && saved.runId === runId && saved.out === out)
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
catch { /* Preserve unrelated output directories. */ }
|
|
28
|
+
throw new VideoCliError('outputConflict', 64);
|
|
29
|
+
}
|
|
30
|
+
export async function readComparisonRun(client, runId, workspaceId, signal, cancel = false) {
|
|
31
|
+
const value = cancel ? await client.post(`/video-comparison/${runId}/cancel`, { workspaceId, runId }, { signal })
|
|
32
|
+
: await client.get(`/video-comparison/${runId}`, { workspaceId }, { signal });
|
|
33
|
+
let run;
|
|
34
|
+
try {
|
|
35
|
+
run = videoComparisonRunSchema.parse(value);
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
throw new ComparisonCliError('comparisonResult');
|
|
39
|
+
}
|
|
40
|
+
if (run.runId !== runId || run.workspaceId !== workspaceId || run.comparison && run.comparison.runId !== runId)
|
|
41
|
+
throw new ComparisonCliError('comparisonResult');
|
|
42
|
+
return run;
|
|
43
|
+
}
|
|
44
|
+
export function comparisonRunResult(command, run) {
|
|
45
|
+
const status = run.comparison?.status;
|
|
46
|
+
const exitCode = run.state === 'uncertain' ? 75 : ['failed', 'cancelled'].includes(run.state) ? 4
|
|
47
|
+
: status === 'fail' ? 2 : status === 'inconclusive' ? 3 : 0;
|
|
48
|
+
const error = remoteVideoFailure(run.state, run.error);
|
|
49
|
+
return { exitCode, envelope: { schemaVersion: 'playcraft.video-cli.v1', command, runId: run.runId, state: run.state,
|
|
50
|
+
ok: run.state === 'completed' && status === 'pass', result: run,
|
|
51
|
+
...(error && { error }) } };
|
|
52
|
+
}
|
|
53
|
+
export function validateComparisonResult(value, request, local) {
|
|
54
|
+
try {
|
|
55
|
+
const result = replicaValidationResultSchema.parse(value);
|
|
56
|
+
if (result.runId !== request.runId || result.model.configId !== request.modelConfigId || result.model.processing !== request.processing
|
|
57
|
+
|| result.recordingHash !== comparisonSha256(canonicalReplicaJson(request.recording))
|
|
58
|
+
|| result.policySha256 !== comparisonSha256(canonicalReplicaJson(request.policy))
|
|
59
|
+
|| canonicalReplicaJson(result.policy) !== canonicalReplicaJson(request.policy))
|
|
60
|
+
throw new Error('identity');
|
|
61
|
+
const expectedInputs = local.inputs.map(item => ({ id: item.id, role: item.role, source: item.video.source }));
|
|
62
|
+
if (canonicalReplicaJson([...result.inputs].sort((a, b) => a.id.localeCompare(b.id))) !== canonicalReplicaJson([...expectedInputs].sort((a, b) => a.id.localeCompare(b.id))))
|
|
63
|
+
throw new Error('sources');
|
|
64
|
+
const context = { inputs: result.inputs, analysis: request.analysis, recording: request.recording, policy: request.policy, processing: request.processing, fps: request.fps };
|
|
65
|
+
const content = validateReplicaComparisonContent({ summary: result.summary, evidence: result.evidence, alignments: result.alignments,
|
|
66
|
+
dimensions: result.dimensions, findings: result.findings, controlChecks: result.controlChecks,
|
|
67
|
+
timings: result.timings, missingEvidence: result.missingEvidence }, context);
|
|
68
|
+
// Recompute acceptance, including all missing evidence reported by the service.
|
|
69
|
+
const computed = evaluateReplicaComparison(content, context);
|
|
70
|
+
if (result.status !== computed.status || result.ok !== computed.ok || result.scope !== request.policy.scope
|
|
71
|
+
|| canonicalReplicaJson(result.coverage) !== canonicalReplicaJson(computed.coverage)
|
|
72
|
+
|| canonicalReplicaJson(result.timingChecks) !== canonicalReplicaJson(computed.timingChecks))
|
|
73
|
+
throw new Error('decision');
|
|
74
|
+
return result;
|
|
75
|
+
}
|
|
76
|
+
catch {
|
|
77
|
+
throw new ComparisonCliError('comparisonResult');
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
async function recheckComparisonInputs(saved, deps, signal) {
|
|
81
|
+
const actual = await preflightComparison({ reference: saved.referencePath, analysis: saved.analysisPath, candidate: saved.candidatePath,
|
|
82
|
+
recording: saved.recordingPath, out: saved.out, signal, probe: deps.probe });
|
|
83
|
+
if (canonicalReplicaJson(actual.recording) !== canonicalReplicaJson(saved.local.recording)
|
|
84
|
+
|| canonicalReplicaJson(actual.analysis) !== canonicalReplicaJson(saved.local.analysis)
|
|
85
|
+
|| canonicalReplicaJson(actual.inputs.map(item => ({ id: item.id, role: item.role, source: item.video.source })))
|
|
86
|
+
!== canonicalReplicaJson(saved.local.inputs.map(item => ({ id: item.id, role: item.role, source: item.video.source }))))
|
|
87
|
+
throw new ComparisonCliError('invalidRecording', 64);
|
|
88
|
+
return actual;
|
|
89
|
+
}
|
|
90
|
+
export async function executeVideoComparison(options, signal, deps) {
|
|
91
|
+
let saved;
|
|
92
|
+
let runId;
|
|
93
|
+
let out;
|
|
94
|
+
let submitted = false;
|
|
95
|
+
let stage = 'prepare';
|
|
96
|
+
try {
|
|
97
|
+
const workspaceId = await resolveVideoWorkspace(options.workspace);
|
|
98
|
+
if (options.resume && [options.reference, options.analysis, options.candidate, options.recording, options.policy, options.processing, options.fps, options.modelConfig].some(value => value !== undefined))
|
|
99
|
+
throw new ComparisonCliError('invalidComparison', 64);
|
|
100
|
+
if (!options.resume && (!options.reference || !options.analysis || !options.candidate || !options.recording))
|
|
101
|
+
throw new ComparisonCliError('invalidComparison', 64);
|
|
102
|
+
const processing = options.processing ?? 'static';
|
|
103
|
+
const fps = options.fps === undefined ? processing === 'static' ? 4 : undefined : Number(options.fps);
|
|
104
|
+
if (!['static', 'agentic'].includes(processing) || fps !== undefined && ![1, 2, 4, 8].includes(fps) || processing === 'agentic' && fps !== undefined)
|
|
105
|
+
throw new ComparisonCliError('invalidComparison', 64);
|
|
106
|
+
runId = options.resume ? requireVideoId(options.resume) : randomUUID();
|
|
107
|
+
out = resolve(options.out ?? join('.playcraft/video-replication', runId));
|
|
108
|
+
await prepareComparisonOutput(out, runId, !!options.resume);
|
|
109
|
+
let run;
|
|
110
|
+
if (options.resume) {
|
|
111
|
+
try {
|
|
112
|
+
saved = await readComparisonJson(join(out, 'request.json'), 8 * 1024 * 1024);
|
|
113
|
+
if (saved.schemaVersion !== 'playcraft.video-comparison-local-request.v1' || saved.runId !== runId || saved.workspaceId !== workspaceId || saved.out !== out || !saved.request)
|
|
114
|
+
throw new Error('identity');
|
|
115
|
+
saved.request = videoComparisonRequestSchema.parse(saved.request);
|
|
116
|
+
if (saved.request.runId !== runId || saved.request.workspaceId !== workspaceId)
|
|
117
|
+
throw new Error('request');
|
|
118
|
+
}
|
|
119
|
+
catch {
|
|
120
|
+
throw new ComparisonCliError('comparisonResume', 64);
|
|
121
|
+
}
|
|
122
|
+
submitted = true;
|
|
123
|
+
stage = 'probe';
|
|
124
|
+
saved.local = await preflightComparison({ reference: saved.referencePath, analysis: saved.analysisPath, candidate: saved.candidatePath,
|
|
125
|
+
recording: saved.recordingPath, out, signal, probe: deps.probe });
|
|
126
|
+
if (canonicalReplicaJson(saved.local.recording) !== canonicalReplicaJson(saved.request.recording)
|
|
127
|
+
|| canonicalReplicaJson(saved.local.analysis) !== canonicalReplicaJson(saved.request.analysis))
|
|
128
|
+
throw new ComparisonCliError('comparisonResume', 64);
|
|
129
|
+
// Recovery only reads the existing task. It cannot upload or submit another inference.
|
|
130
|
+
stage = 'poll';
|
|
131
|
+
run = await readComparisonRun(deps.client, runId, workspaceId, signal);
|
|
132
|
+
}
|
|
133
|
+
else {
|
|
134
|
+
let policy;
|
|
135
|
+
try {
|
|
136
|
+
policy = options.policy ? replicaPolicySchema.parse(await readComparisonJson(resolve(options.policy))) : replicaDefaultPolicy;
|
|
137
|
+
}
|
|
138
|
+
catch {
|
|
139
|
+
throw new ComparisonCliError('invalidComparison', 64);
|
|
140
|
+
}
|
|
141
|
+
deps.progress('probing');
|
|
142
|
+
stage = 'probe';
|
|
143
|
+
const local = await preflightComparison({ reference: options.reference, analysis: options.analysis, candidate: options.candidate,
|
|
144
|
+
recording: options.recording, out, signal, probe: deps.probe });
|
|
145
|
+
try {
|
|
146
|
+
validateReplicaComparisonContext({ inputs: local.inputs.map(item => ({ id: item.id, role: item.role, source: item.video.source })),
|
|
147
|
+
analysis: local.analysis, recording: local.recording, policy, processing: processing, fps });
|
|
148
|
+
}
|
|
149
|
+
catch {
|
|
150
|
+
throw new ComparisonCliError('invalidComparison', 64);
|
|
151
|
+
}
|
|
152
|
+
stage = 'models';
|
|
153
|
+
const catalog = videoModelCatalogSchema.parse(await deps.client.get('/video-models', { workspaceId }, { signal }));
|
|
154
|
+
const configId = options.modelConfig ? requireVideoId(options.modelConfig) : catalog.defaultConfigId;
|
|
155
|
+
const model = catalog.models.find(item => item.configId === configId);
|
|
156
|
+
if (!model || !model.processing.includes(processing) || model.maxVideos < local.inputs.length)
|
|
157
|
+
throw new ComparisonCliError('comparisonUnavailable', 64);
|
|
158
|
+
saved = { schemaVersion: 'playcraft.video-comparison-local-request.v1', runId, workspaceId, out,
|
|
159
|
+
referencePath: resolve(options.reference), analysisPath: resolve(options.analysis), candidatePath: resolve(options.candidate), recordingPath: resolve(options.recording),
|
|
160
|
+
local, state: 'uploading', createdAt: new Date().toISOString() };
|
|
161
|
+
await writeComparisonJson(out, 'request.json', saved);
|
|
162
|
+
const inputs = [];
|
|
163
|
+
for (const input of local.inputs) {
|
|
164
|
+
deps.progress('uploading');
|
|
165
|
+
const mediaId = await uploadVideo(input.video, workspaceId, deps, signal, value => { stage = value; });
|
|
166
|
+
inputs.push({ id: input.id, role: input.role, mediaId });
|
|
167
|
+
}
|
|
168
|
+
stage = 'submit';
|
|
169
|
+
const request = videoComparisonRequestSchema.parse({ schemaVersion: 'playcraft.video-comparison-request.v1', workspaceId, runId,
|
|
170
|
+
inputs, analysis: local.analysis, recording: local.recording, policy, processing, ...(fps !== undefined && { fps }), modelConfigId: configId });
|
|
171
|
+
saved = { ...saved, request, state: 'submitting' };
|
|
172
|
+
await writeComparisonJson(out, 'request.json', saved);
|
|
173
|
+
await recheckComparisonInputs(saved, deps, signal);
|
|
174
|
+
deps.progress('submitting');
|
|
175
|
+
submitted = true;
|
|
176
|
+
const response = await deps.client.post('/video-comparison', request, { signal, onPrepared: async (operation) => {
|
|
177
|
+
saved = { ...saved, requestId: operation.requestId };
|
|
178
|
+
await writeComparisonJson(out, 'request.json', saved);
|
|
179
|
+
} });
|
|
180
|
+
try {
|
|
181
|
+
run = videoComparisonRunSchema.parse(response);
|
|
182
|
+
}
|
|
183
|
+
catch {
|
|
184
|
+
throw new ComparisonCliError('comparisonResult');
|
|
185
|
+
}
|
|
186
|
+
if (run.runId !== runId || run.workspaceId !== workspaceId)
|
|
187
|
+
throw new ComparisonCliError('comparisonResult');
|
|
188
|
+
}
|
|
189
|
+
stage = 'poll';
|
|
190
|
+
while (['pending', 'uploading', 'submitting', 'running'].includes(run.state)) {
|
|
191
|
+
deps.progress('waiting');
|
|
192
|
+
await delay(Math.max(250, Math.min(run.pollAfterMs, 10_000)), undefined, { signal });
|
|
193
|
+
run = await readComparisonRun(deps.client, runId, workspaceId, signal);
|
|
194
|
+
}
|
|
195
|
+
await writeComparisonJson(out, 'task.json', run);
|
|
196
|
+
if (run.state === 'uncertain' && saved)
|
|
197
|
+
await writeComparisonJson(out, 'request.json', { ...saved, state: 'uncertain' }).catch(() => { });
|
|
198
|
+
if (run.state !== 'completed') {
|
|
199
|
+
const result = comparisonRunResult('video compare', run);
|
|
200
|
+
result.envelope.artifacts = { request: join(out, 'request.json') };
|
|
201
|
+
return result;
|
|
202
|
+
}
|
|
203
|
+
if (!saved.request || !run.comparison)
|
|
204
|
+
throw new ComparisonCliError('comparisonResult');
|
|
205
|
+
await recheckComparisonInputs(saved, deps, signal);
|
|
206
|
+
const comparison = validateComparisonResult(run.comparison, saved.request, saved.local);
|
|
207
|
+
deps.progress('extracting');
|
|
208
|
+
stage = 'extract';
|
|
209
|
+
const artifacts = await deps.materialize({ comparison, request: saved.request, inputs: saved.local.inputs, out, signal });
|
|
210
|
+
await writeComparisonJson(out, 'request.json', { ...saved, state: 'completed' });
|
|
211
|
+
const result = comparisonRunResult('video compare', { ...run, comparison });
|
|
212
|
+
result.envelope.artifacts = artifacts;
|
|
213
|
+
result.envelope.result = { status: comparison.status, scope: comparison.scope, coverage: comparison.coverage };
|
|
214
|
+
return result;
|
|
215
|
+
}
|
|
216
|
+
catch (error) {
|
|
217
|
+
const timeout = signal.aborted && signal.reason?.name === 'TimeoutError';
|
|
218
|
+
const interrupted = signal.aborted && !timeout;
|
|
219
|
+
const known = error instanceof VideoCliError || error instanceof ComparisonCliError;
|
|
220
|
+
const code = timeout ? 'timeoutReached' : interrupted ? 'interrupted' : known ? error.code : submitted ? 'uncertain' : 'comparisonFailed';
|
|
221
|
+
const exitCode = timeout ? 75 : interrupted ? 130 : known ? error.exitCode : submitted ? 75 : 4;
|
|
222
|
+
if (saved && out && submitted && (exitCode === 75 || exitCode === 130))
|
|
223
|
+
await writeComparisonJson(out, 'request.json', { ...saved, state: 'uncertain' }).catch(() => { });
|
|
224
|
+
const message = error instanceof ComparisonCliError ? comparisonMessage(error.code) : error instanceof VideoCliError ? videoMessage(error.code)
|
|
225
|
+
: timeout ? videoMessage('timeoutReached') : interrupted ? videoMessage('interrupted') : submitted ? videoMessage('uncertain') : comparisonMessage('comparisonFailed');
|
|
226
|
+
return { exitCode, envelope: { schemaVersion: 'playcraft.video-cli.v1', command: 'video compare', runId,
|
|
227
|
+
state: exitCode === 75 || exitCode === 130 ? 'uncertain' : 'failed', ok: false,
|
|
228
|
+
...(saved && out && { artifacts: { request: join(out, 'request.json') } }), error: { code, ...describeVideoFailure(message, error, stage), ...(interrupted && { remoteCancellationConfirmed: false }) } } };
|
|
229
|
+
}
|
|
230
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { videoMessage } from './messages.js';
|
|
2
|
+
import { redactDiagnosticText } from '../utils/error-diagnostics.js';
|
|
3
|
+
export { redactDiagnosticText as redactVideoDiagnostic } from '../utils/error-diagnostics.js';
|
|
4
|
+
const stageMessages = {
|
|
5
|
+
prepare: 'preparing', probe: 'probing', models: 'loadingModels',
|
|
6
|
+
'upload-init': 'initializingUpload', 'upload-content': 'uploading', 'upload-finalize': 'finalizingUpload',
|
|
7
|
+
submit: 'submitting', poll: 'waiting', extract: 'extracting', cancel: 'cancel',
|
|
8
|
+
};
|
|
9
|
+
/** Read messages and a bounded cause chain, never arbitrary error/response objects. */
|
|
10
|
+
export function videoFailureDetails(error) {
|
|
11
|
+
const messages = [], seen = new Set();
|
|
12
|
+
let current = error;
|
|
13
|
+
for (let depth = 0; depth < 5 && current != null && !seen.has(current); depth++) {
|
|
14
|
+
seen.add(current);
|
|
15
|
+
const record = typeof current === 'object' ? current : undefined;
|
|
16
|
+
const raw = typeof current === 'string' ? current : typeof record?.message === 'string' ? record.message : '';
|
|
17
|
+
const code = typeof record?.code === 'string' && /^[A-Z][A-Z0-9_]+$/.test(record.code) ? record.code : '';
|
|
18
|
+
const text = redactDiagnosticText(code && !raw.includes(code) ? `${code}: ${raw}` : raw).slice(0, 1600);
|
|
19
|
+
if (text && !messages.some(previous => previous.includes(text)))
|
|
20
|
+
messages.push(text);
|
|
21
|
+
current = record?.cause;
|
|
22
|
+
}
|
|
23
|
+
return messages.join(' <- ').slice(0, 4000) || undefined;
|
|
24
|
+
}
|
|
25
|
+
export function describeVideoFailure(summary, error, stage) {
|
|
26
|
+
const details = videoFailureDetails(error);
|
|
27
|
+
const reason = details?.startsWith(`${summary} <- `) ? details.slice(summary.length + 4)
|
|
28
|
+
: details && details !== summary ? details : undefined;
|
|
29
|
+
return {
|
|
30
|
+
message: [summary, stage && `${videoMessage('failureStage')}: ${videoMessage(stageMessages[stage])}.`,
|
|
31
|
+
reason && `${videoMessage('failureReason')}: ${reason}`].filter(Boolean).join(' '),
|
|
32
|
+
...(reason && { details: reason }), ...(stage && { stage }),
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
export function remoteVideoFailure(state, error) {
|
|
36
|
+
if (!['failed', 'cancelled', 'uncertain'].includes(state))
|
|
37
|
+
return undefined;
|
|
38
|
+
const key = state === 'uncertain' ? 'uncertain' : 'remoteFailed';
|
|
39
|
+
return { code: state === 'uncertain' ? key : error?.code ?? key,
|
|
40
|
+
...describeVideoFailure(videoMessage(key), error?.code, 'poll') };
|
|
41
|
+
}
|