@playcraft/cli 0.1.40-beta → 0.1.41-prod

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.
Files changed (368) hide show
  1. package/README.md +3 -58
  2. package/dist/agent/local-backend.js +1 -1
  3. package/dist/cli-root-help.js +0 -2
  4. package/dist/commands/build-all.js +19 -5
  5. package/dist/commands/build.js +28 -15
  6. package/dist/commands/create.js +33 -26
  7. package/dist/commands/image.js +6 -2
  8. package/dist/commands/login.js +42 -84
  9. package/dist/commands/skills.js +0 -4
  10. package/dist/commands/tools-3d.js +0 -11
  11. package/dist/commands/tools-generation.js +137 -69
  12. package/dist/commands/tools-image-context.js +29 -0
  13. package/dist/commands/tools-utils.js +12 -0
  14. package/dist/commands/tools.js +2 -2
  15. package/dist/config.js +4 -59
  16. package/dist/index.js +3 -40
  17. package/dist/remix/clone.js +0 -2
  18. package/dist/remix/init-template.js +0 -2
  19. package/dist/remix/pull.js +0 -2
  20. package/dist/remix/push.js +1 -21
  21. package/dist/utils/agent-api-client.js +30 -185
  22. package/dist/utils/image-asset-context.js +228 -0
  23. package/dist/utils/playcraft-http-client.js +4 -6
  24. package/package.json +3 -13
  25. package/project-template/.claude/agents/refs/ta-image-generation-detail.md +67 -123
  26. package/project-template/.claude/skills/playcraft-image-generation/SKILL.md +135 -63
  27. package/project-template/.claude/skills/playcraft-image-generation/reference/gen-batch-sprite.template.mjs +42 -33
  28. package/project-template/.claude/skills/playcraft-image-generation/reference/gen-batch.template.mjs +18 -20
  29. package/project-template/.claude/skills/playcraft-image-generation/reference/process-batch.template.mjs +6 -7
  30. package/project-template/.claude/skills/playcraft-image-generation/reference/prompt-cookbook.md +39 -39
  31. package/project-templates/phaser/.claude/agents/refs/ta-image-generation-detail.md +59 -59
  32. package/project-templates/phaser/.claude/skills/playcraft-image-generation/SKILL.md +135 -63
  33. package/project-templates/phaser/.claude/skills/playcraft-image-generation/reference/gen-batch-sprite.template.mjs +42 -33
  34. package/project-templates/phaser/.claude/skills/playcraft-image-generation/reference/gen-batch.template.mjs +18 -20
  35. package/project-templates/phaser/.claude/skills/playcraft-image-generation/reference/process-batch.template.mjs +6 -7
  36. package/project-templates/phaser/.claude/skills/playcraft-image-generation/reference/prompt-cookbook.md +39 -39
  37. package/project-templates/threejs/.claude/agents/refs/ta-image-generation-detail.md +59 -59
  38. package/project-templates/threejs/.claude/skills/playcraft-image-generation/SKILL.md +135 -63
  39. package/project-templates/threejs/.claude/skills/playcraft-image-generation/reference/gen-batch-sprite.template.mjs +42 -33
  40. package/project-templates/threejs/.claude/skills/playcraft-image-generation/reference/gen-batch.template.mjs +18 -20
  41. package/project-templates/threejs/.claude/skills/playcraft-image-generation/reference/process-batch.template.mjs +6 -7
  42. package/project-templates/threejs/.claude/skills/playcraft-image-generation/reference/prompt-cookbook.md +39 -39
  43. package/SKILLS_REGISTRY.md +0 -149
  44. package/dist/auth/account-activation.js +0 -157
  45. package/dist/auth/messages.js +0 -30
  46. package/dist/commands/build-temp.js +0 -26
  47. package/dist/commands/platform-skills.generated.js +0 -19
  48. package/dist/commands/playable.js +0 -53
  49. package/dist/commands/tools-batch.js +0 -87
  50. package/dist/commands/video.js +0 -111
  51. package/dist/commands/workspace-runtime.js +0 -54
  52. package/dist/commands/workspace.js +0 -35
  53. package/dist/project-skills/builtin-import.js +0 -225
  54. package/dist/project-skills/commands.js +0 -77
  55. package/dist/project-skills/lifecycle.js +0 -28
  56. package/dist/project-skills/local-run.js +0 -112
  57. package/dist/project-skills/local-store.js +0 -221
  58. package/dist/project-skills/messages.js +0 -80
  59. package/dist/project-skills/reconcile.js +0 -62
  60. package/dist/project-skills/remote-cache.js +0 -51
  61. package/dist/project-skills/validation.js +0 -19
  62. package/dist/recording/messages.js +0 -40
  63. package/dist/recording/project-digest.js +0 -85
  64. package/dist/recording/recorder.js +0 -293
  65. package/dist/recording/scenario.js +0 -50
  66. package/dist/skill-registry/client.js +0 -345
  67. package/dist/skill-registry/commands.js +0 -180
  68. package/dist/skill-registry/evaluation-client.js +0 -156
  69. package/dist/skill-registry/evaluation-commands.js +0 -174
  70. package/dist/skill-registry/messages.js +0 -177
  71. package/dist/skill-registry/package.js +0 -275
  72. package/dist/skill-registry/platform-package.js +0 -100
  73. package/dist/utils/environment-endpoints.js +0 -17
  74. package/dist/utils/tool-operation-journal.js +0 -124
  75. package/dist/utils/video-evidence-git.js +0 -81
  76. package/dist/video-replication/client.js +0 -222
  77. package/dist/video-replication/comparison-evidence.js +0 -149
  78. package/dist/video-replication/comparison-files.js +0 -57
  79. package/dist/video-replication/comparison-log.js +0 -72
  80. package/dist/video-replication/comparison-messages.js +0 -104
  81. package/dist/video-replication/comparison-output.js +0 -63
  82. package/dist/video-replication/comparison-preflight.js +0 -66
  83. package/dist/video-replication/comparison.js +0 -216
  84. package/dist/video-replication/evidence.js +0 -165
  85. package/dist/video-replication/media.js +0 -111
  86. package/dist/video-replication/messages.js +0 -89
  87. package/dist/workspace/messages.js +0 -35
  88. package/dist/workspace/transfer-client.js +0 -174
  89. package/dist/workspace-runtime/codex/app-server.js +0 -1040
  90. package/dist/workspace-runtime/codex/compact.js +0 -46
  91. package/dist/workspace-runtime/codex/config-toml.js +0 -107
  92. package/dist/workspace-runtime/codex/jsonrpc-stdio.js +0 -145
  93. package/dist/workspace-runtime/codex/launcher-client.js +0 -177
  94. package/dist/workspace-runtime/codex/loopback.js +0 -146
  95. package/dist/workspace-runtime/codex/mcp-config.js +0 -39
  96. package/dist/workspace-runtime/codex/model-broker.js +0 -263
  97. package/dist/workspace-runtime/codex/native-adapter.js +0 -9
  98. package/dist/workspace-runtime/codex/permissions.js +0 -70
  99. package/dist/workspace-runtime/codex/thread-image.js +0 -38
  100. package/dist/workspace-runtime/codex/ui-instructions.js +0 -4
  101. package/dist/workspace-runtime/codex/ui-tools.js +0 -183
  102. package/dist/workspace-runtime/codex/work-mode.js +0 -15
  103. package/dist/workspace-runtime/main.js +0 -10
  104. package/dist/workspace-runtime/persistence/journal.js +0 -365
  105. package/dist/workspace-runtime/persistence/native-subagents.js +0 -162
  106. package/dist/workspace-runtime/persistence/sqlite-client.js +0 -77
  107. package/dist/workspace-runtime/persistence/sqlite-worker.js +0 -1161
  108. package/dist/workspace-runtime/persistence/stopped-execution.js +0 -77
  109. package/dist/workspace-runtime/previews/output.js +0 -36
  110. package/dist/workspace-runtime/previews/publish.js +0 -262
  111. package/dist/workspace-runtime/previews/single-file.js +0 -43
  112. package/dist/workspace-runtime/processes/managed-writes.js +0 -69
  113. package/dist/workspace-runtime/production/content-changes.js +0 -36
  114. package/dist/workspace-runtime/production/contracts.js +0 -19
  115. package/dist/workspace-runtime/production/controller.js +0 -218
  116. package/dist/workspace-runtime/production/dependencies.js +0 -60
  117. package/dist/workspace-runtime/production/native.js +0 -212
  118. package/dist/workspace-runtime/production/processes.js +0 -102
  119. package/dist/workspace-runtime/production/save-command.js +0 -35
  120. package/dist/workspace-runtime/production/source-archive.js +0 -137
  121. package/dist/workspace-runtime/production/source-copy.js +0 -91
  122. package/dist/workspace-runtime/production/stages.js +0 -334
  123. package/dist/workspace-runtime/production/storage.js +0 -75
  124. package/dist/workspace-runtime/serve.js +0 -214
  125. package/dist/workspace-runtime/server/auth.js +0 -21
  126. package/dist/workspace-runtime/server/connectivity.js +0 -201
  127. package/dist/workspace-runtime/server/desktop-host.js +0 -429
  128. package/dist/workspace-runtime/server/desktop-preview-config.js +0 -81
  129. package/dist/workspace-runtime/server/desktop-preview-service.js +0 -157
  130. package/dist/workspace-runtime/server/desktop-preview-static.js +0 -53
  131. package/dist/workspace-runtime/server/desktop-preview.js +0 -189
  132. package/dist/workspace-runtime/server/dispatch.js +0 -1217
  133. package/dist/workspace-runtime/server/endpoints.js +0 -21
  134. package/dist/workspace-runtime/server/execution-group.js +0 -90
  135. package/dist/workspace-runtime/server/http.js +0 -1243
  136. package/dist/workspace-runtime/server/lock.js +0 -53
  137. package/dist/workspace-runtime/server/rfb-watch-gate.js +0 -168
  138. package/dist/workspace-runtime/server/source-uploader.js +0 -138
  139. package/dist/workspace-runtime/server/terminal-host.js +0 -366
  140. package/dist/workspace-runtime/server/types.js +0 -1
  141. package/dist/workspace-runtime/server/workspace-transfer.js +0 -99
  142. package/dist/workspace-runtime/workspaces/archive.js +0 -207
  143. package/dist/workspace-runtime/workspaces/artifact-events.js +0 -55
  144. package/dist/workspace-runtime/workspaces/artifact-files.js +0 -197
  145. package/dist/workspace-runtime/workspaces/artifact-receipts.js +0 -89
  146. package/dist/workspace-runtime/workspaces/artifacts.js +0 -282
  147. package/dist/workspace-runtime/workspaces/attachment-content.js +0 -123
  148. package/dist/workspace-runtime/workspaces/attachments.js +0 -185
  149. package/dist/workspace-runtime/workspaces/batch-commit.js +0 -142
  150. package/dist/workspace-runtime/workspaces/cli-context.js +0 -23
  151. package/dist/workspace-runtime/workspaces/cli-credentials.js +0 -111
  152. package/dist/workspace-runtime/workspaces/composer-context.js +0 -147
  153. package/dist/workspace-runtime/workspaces/context-error.js +0 -2
  154. package/dist/workspace-runtime/workspaces/dependencies.js +0 -264
  155. package/dist/workspace-runtime/workspaces/developer-command.js +0 -64
  156. package/dist/workspace-runtime/workspaces/directories.js +0 -133
  157. package/dist/workspace-runtime/workspaces/directory-access.js +0 -37
  158. package/dist/workspace-runtime/workspaces/directory-attachments.js +0 -245
  159. package/dist/workspace-runtime/workspaces/directory-maintenance.js +0 -111
  160. package/dist/workspace-runtime/workspaces/directory-stats.js +0 -49
  161. package/dist/workspace-runtime/workspaces/disk-snapshot.js +0 -31
  162. package/dist/workspace-runtime/workspaces/empty-template.js +0 -42
  163. package/dist/workspace-runtime/workspaces/files-search.js +0 -116
  164. package/dist/workspace-runtime/workspaces/files.js +0 -138
  165. package/dist/workspace-runtime/workspaces/git-credential-broker.js +0 -312
  166. package/dist/workspace-runtime/workspaces/git-credential-helper.js +0 -68
  167. package/dist/workspace-runtime/workspaces/git-direct-store.js +0 -155
  168. package/dist/workspace-runtime/workspaces/git.js +0 -670
  169. package/dist/workspace-runtime/workspaces/instructions.js +0 -56
  170. package/dist/workspace-runtime/workspaces/json5-edit.js +0 -170
  171. package/dist/workspace-runtime/workspaces/library-attachments.js +0 -268
  172. package/dist/workspace-runtime/workspaces/optional-platform-skills.js +0 -14
  173. package/dist/workspace-runtime/workspaces/parameters.js +0 -272
  174. package/dist/workspace-runtime/workspaces/prepare.js +0 -278
  175. package/dist/workspace-runtime/workspaces/registry.js +0 -251
  176. package/dist/workspace-runtime/workspaces/remix-references.js +0 -301
  177. package/dist/workspace-runtime/workspaces/revisions.js +0 -31
  178. package/dist/workspace-runtime/workspaces/session-image.js +0 -104
  179. package/dist/workspace-runtime/workspaces/shared-git.js +0 -40
  180. package/dist/workspace-runtime/workspaces/skill-artifact-loader.js +0 -95
  181. package/dist/workspace-runtime/workspaces/skills.js +0 -191
  182. package/dist/workspace-runtime/workspaces/transfer.js +0 -288
  183. package/dist/workspace-runtime/workspaces/turn-changes.js +0 -75
  184. package/dist/workspace-runtime/workspaces/video-attachments.js +0 -49
  185. package/project-template/.claude/skills/playcraft-replica-validation/SKILL.md +0 -25
  186. package/project-template/.claude/skills/playcraft-replica-validation/references/contracts.md +0 -79
  187. package/project-template/.claude/skills/playcraft-video-understanding/SKILL.md +0 -29
  188. package/project-template/.claude/skills/playcraft-video-understanding/references/contracts.md +0 -37
  189. package/project-template-v2/.claude/agents/artist.md +0 -82
  190. package/project-template-v2/.claude/agents/developer.md +0 -153
  191. package/project-template-v2/.claude/agents/game-designer.md +0 -264
  192. package/project-template-v2/.claude/agents/refs/artist-art-style-catalog.md +0 -533
  193. package/project-template-v2/.claude/agents/refs/artist-color-audio-recipes.md +0 -153
  194. package/project-template-v2/.claude/agents/refs/artist-dimension-axis.md +0 -27
  195. package/project-template-v2/.claude/agents/refs/artist-master-composite-recipes.md +0 -208
  196. package/project-template-v2/.claude/agents/refs/atom-skill-library.md +0 -81
  197. package/project-template-v2/.claude/agents/refs/developer-impl-cookbook.md +0 -432
  198. package/project-template-v2/.claude/agents/refs/framework-5-component-filter.md +0 -252
  199. package/project-template-v2/.claude/agents/refs/framework-game-feel-juice.md +0 -266
  200. package/project-template-v2/.claude/agents/refs/framework-mda.md +0 -147
  201. package/project-template-v2/.claude/agents/refs/game-designer-gameplay-sufficiency.md +0 -123
  202. package/project-template-v2/.claude/agents/refs/ta-3d-flip-recipe.md +0 -88
  203. package/project-template-v2/.claude/agents/refs/ta-atlas-deliverable-standard.md +0 -67
  204. package/project-template-v2/.claude/agents/refs/ta-batch-pipeline-recipes.md +0 -120
  205. package/project-template-v2/.claude/agents/refs/ta-image-generation-detail.md +0 -300
  206. package/project-template-v2/.claude/agents/refs/ta-image-ops-reference.md +0 -495
  207. package/project-template-v2/.claude/agents/refs/ta-pipeline-cookbook.md +0 -1141
  208. package/project-template-v2/.claude/agents/refs/ta-tools-reference.md +0 -111
  209. package/project-template-v2/.claude/agents/refs/ta-vfx-preset-catalog.md +0 -365
  210. package/project-template-v2/.claude/agents/refs/threejs-cannon-pitfalls.md +0 -412
  211. package/project-template-v2/.claude/agents/reviewer.md +0 -75
  212. package/project-template-v2/.claude/agents/technical-artist.md +0 -86
  213. package/project-template-v2/.claude/hooks/snapshot-milestone.mjs +0 -243
  214. package/project-template-v2/.claude/hooks/user-prompt.mjs +0 -133
  215. package/project-template-v2/.claude/settings.json +0 -33
  216. package/project-template-v2/.claude/skills/brainstorming/SKILL.md +0 -161
  217. package/project-template-v2/.claude/skills/brainstorming/scripts/frame-template.html +0 -270
  218. package/project-template-v2/.claude/skills/brainstorming/scripts/helper.js +0 -177
  219. package/project-template-v2/.claude/skills/brainstorming/scripts/server.cjs +0 -354
  220. package/project-template-v2/.claude/skills/brainstorming/scripts/start-server.sh +0 -148
  221. package/project-template-v2/.claude/skills/brainstorming/scripts/stop-server.sh +0 -56
  222. package/project-template-v2/.claude/skills/brainstorming/scripts/wait-for-selection.sh +0 -62
  223. package/project-template-v2/.claude/skills/brainstorming/spec-document-reviewer-prompt.md +0 -49
  224. package/project-template-v2/.claude/skills/brainstorming/visual-companion.md +0 -309
  225. package/project-template-v2/.claude/skills/playcraft-ad-psychology/SKILL.md +0 -182
  226. package/project-template-v2/.claude/skills/playcraft-art-style-guide/SKILL.md +0 -123
  227. package/project-template-v2/.claude/skills/playcraft-asset-state-sheet/SKILL.md +0 -205
  228. package/project-template-v2/.claude/skills/playcraft-audio-generation/SKILL.md +0 -280
  229. package/project-template-v2/.claude/skills/playcraft-batch-pipeline/SKILL.md +0 -184
  230. package/project-template-v2/.claude/skills/playcraft-build-optimizer/SKILL.md +0 -306
  231. package/project-template-v2/.claude/skills/playcraft-image-generation/SKILL.md +0 -298
  232. package/project-template-v2/.claude/skills/playcraft-image-generation/reference/build-sprite-sheet.template.mjs +0 -123
  233. package/project-template-v2/.claude/skills/playcraft-image-generation/reference/compare-style.template.mjs +0 -254
  234. package/project-template-v2/.claude/skills/playcraft-image-generation/reference/gen-batch-sprite.template.mjs +0 -324
  235. package/project-template-v2/.claude/skills/playcraft-image-generation/reference/gen-batch.template.mjs +0 -97
  236. package/project-template-v2/.claude/skills/playcraft-image-generation/reference/gen-edit-variants.template.mjs +0 -118
  237. package/project-template-v2/.claude/skills/playcraft-image-generation/reference/process-batch.template.mjs +0 -137
  238. package/project-template-v2/.claude/skills/playcraft-image-generation/reference/prompt-cookbook.md +0 -397
  239. package/project-template-v2/.claude/skills/playcraft-image-generation/reference/validate-sprite-sheet.template.mjs +0 -296
  240. package/project-template-v2/.claude/skills/playcraft-image-ops/SKILL.md +0 -122
  241. package/project-template-v2/.claude/skills/playcraft-image-processing/SKILL.md +0 -219
  242. package/project-template-v2/.claude/skills/playcraft-masking/SKILL.md +0 -373
  243. package/project-template-v2/.claude/skills/playcraft-playable-optimization/SKILL.md +0 -161
  244. package/project-template-v2/.claude/skills/playcraft-research/SKILL.md +0 -215
  245. package/project-template-v2/.claude/skills/playcraft-skill-recommender/SKILL.md +0 -382
  246. package/project-template-v2/.claude/skills/playcraft-sprite-generation/SKILL.md +0 -423
  247. package/project-template-v2/.claude/skills/playcraft-sprite-remix/SKILL.md +0 -158
  248. package/project-template-v2/.claude/skills/playcraft-sprite-sheet/SKILL.md +0 -100
  249. package/project-template-v2/.claude/skills/playcraft-storyboard/SKILL.md +0 -167
  250. package/project-template-v2/.claude/skills/playcraft-style-qa/SKILL.md +0 -270
  251. package/project-template-v2/.claude/skills/playcraft-text-rendering/SKILL.md +0 -236
  252. package/project-template-v2/.claude/skills/playcraft-vfx-animation/SKILL.md +0 -130
  253. package/project-template-v2/.claude/skills/playwright-cli/SKILL.md +0 -390
  254. package/project-template-v2/.claude/skills/playwright-cli/references/element-attributes.md +0 -23
  255. package/project-template-v2/.claude/skills/playwright-cli/references/playwright-tests.md +0 -39
  256. package/project-template-v2/.claude/skills/playwright-cli/references/request-mocking.md +0 -87
  257. package/project-template-v2/.claude/skills/playwright-cli/references/running-code.md +0 -240
  258. package/project-template-v2/.claude/skills/playwright-cli/references/session-management.md +0 -226
  259. package/project-template-v2/.claude/skills/playwright-cli/references/spec-driven-testing.md +0 -308
  260. package/project-template-v2/.claude/skills/playwright-cli/references/storage-state.md +0 -275
  261. package/project-template-v2/.claude/skills/playwright-cli/references/test-generation.md +0 -134
  262. package/project-template-v2/.claude/skills/playwright-cli/references/tracing.md +0 -142
  263. package/project-template-v2/.claude/skills/playwright-cli/references/video-recording.md +0 -153
  264. package/project-template-v2/.claude/skills/session-analyzer/SKILL.md +0 -386
  265. package/project-template-v2/.claude/skills/session-analyzer/scripts/execution-breakdown.mjs +0 -182
  266. package/project-template-v2/.claude/skills/session-analyzer/scripts/find-turns.mjs +0 -72
  267. package/project-template-v2/.claude/skills/session-analyzer/scripts/heavy-output.mjs +0 -121
  268. package/project-template-v2/.claude/skills/session-analyzer/scripts/resolve-session.mjs +0 -102
  269. package/project-template-v2/.claude/skills/session-analyzer/scripts/subagent-stats.mjs +0 -127
  270. package/project-template-v2/.claude/skills/session-analyzer/scripts/subagent-tool-timeline.mjs +0 -106
  271. package/project-template-v2/.claude/skills/session-analyzer/scripts/time-gaps.mjs +0 -128
  272. package/project-template-v2/.claude/skills/session-analyzer/scripts/turn-timeline.mjs +0 -67
  273. package/project-template-v2/.claude/snapshot.mjs +0 -263
  274. package/project-template-v2/.playcraft/skills.lock.json +0 -152
  275. package/project-template-v2/CLAUDE.md +0 -146
  276. package/project-template-v2/assets/audio/bgm/.gitkeep +0 -0
  277. package/project-template-v2/assets/audio/sfx/.gitkeep +0 -0
  278. package/project-template-v2/assets/bundles/.gitkeep +0 -0
  279. package/project-template-v2/assets/images/bg/.gitkeep +0 -0
  280. package/project-template-v2/assets/images/reference/.gitkeep +0 -0
  281. package/project-template-v2/assets/images/storyboard/.gitkeep +0 -0
  282. package/project-template-v2/assets/images/tiles/.gitkeep +0 -0
  283. package/project-template-v2/assets/images/ui/.gitkeep +0 -0
  284. package/project-template-v2/assets/images/vfx/.gitkeep +0 -0
  285. package/project-template-v2/assets/models/.gitkeep +0 -0
  286. package/project-template-v2/docs/harness/iteration-1/context-flow.md +0 -254
  287. package/project-template-v2/docs/harness/iteration-1/generate-flow.md +0 -91
  288. package/project-template-v2/docs/harness/iteration-1/ideate-flow.md +0 -214
  289. package/project-template-v2/docs/harness/iteration-1/optimize-flow.md +0 -75
  290. package/project-template-v2/docs/harness/iteration-1/wrapup-flow.md +0 -63
  291. package/project-template-v2/docs/harness/iteration-2/context-flow.md +0 -223
  292. package/project-template-v2/docs/harness/iteration-2/generate-flow.md +0 -129
  293. package/project-template-v2/docs/harness/iteration-2/ideate-flow.md +0 -267
  294. package/project-template-v2/docs/harness/iteration-2/optimize-flow.md +0 -164
  295. package/project-template-v2/docs/harness/iteration-2/wrapup-flow.md +0 -115
  296. package/project-template-v2/docs/harness/orchestrator-flow.md +0 -364
  297. package/project-template-v2/docs/project-state.json +0 -60
  298. package/project-template-v2/docs/project-state.md +0 -72
  299. package/project-template-v2/docs/standards/README.md +0 -225
  300. package/project-template-v2/docs/standards/agent-behavior-standards.md +0 -174
  301. package/project-template-v2/docs/standards/artifacts/design-brief.md +0 -19
  302. package/project-template-v2/docs/standards/artifacts/design.md +0 -22
  303. package/project-template-v2/docs/standards/artifacts/game-code.md +0 -41
  304. package/project-template-v2/docs/standards/artifacts/todo-list.md +0 -41
  305. package/project-template-v2/docs/standards/iter1-agent-behavior-standards.md +0 -343
  306. package/project-template-v2/game/index.ts +0 -18
  307. package/project-template-v2/globals.d.ts +0 -51
  308. package/project-template-v2/index.css +0 -34
  309. package/project-template-v2/index.html +0 -18
  310. package/project-template-v2/main.ts +0 -9
  311. package/project-template-v2/package.json +0 -46
  312. package/project-template-v2/skills/_shared/scripts/dispatch-clear.mjs +0 -31
  313. package/project-template-v2/skills/_shared/scripts/dispatch-set.mjs +0 -86
  314. package/project-template-v2/skills/_shared/scripts/dod-check.mjs +0 -153
  315. package/project-template-v2/skills/_shared/scripts/handoff-append.mjs +0 -70
  316. package/project-template-v2/skills/_shared/scripts/lib/validator-artifacts.mjs +0 -91
  317. package/project-template-v2/skills/_shared/scripts/pipeline/dod-config.mjs +0 -131
  318. package/project-template-v2/skills/_shared/scripts/pipeline/index.mjs +0 -80
  319. package/project-template-v2/skills/_shared/scripts/pipeline/iteration-1-core.mjs +0 -90
  320. package/project-template-v2/skills/_shared/scripts/pipeline/iteration-2-wrap.mjs +0 -93
  321. package/project-template-v2/skills/_shared/scripts/pipeline/iteration-3-visual.mjs +0 -23
  322. package/project-template-v2/skills/_shared/scripts/render-project-state.mjs +0 -230
  323. package/project-template-v2/skills/_shared/scripts/state-advance-stage.mjs +0 -58
  324. package/project-template-v2/skills/_shared/scripts/state-get.mjs +0 -339
  325. package/project-template-v2/skills/_shared/scripts/state-handoff.mjs +0 -39
  326. package/project-template-v2/skills/_shared/scripts/state-set.mjs +0 -94
  327. package/project-template-v2/skills/_shared/scripts/state-store.mjs +0 -783
  328. package/project-template-v2/skills/_shared/scripts/todo-add.mjs +0 -88
  329. package/project-template-v2/skills/_shared/scripts/todo-get.mjs +0 -130
  330. package/project-template-v2/skills/_shared/scripts/todo-remove.mjs +0 -33
  331. package/project-template-v2/skills/_shared/scripts/todo-set.mjs +0 -47
  332. package/project-template-v2/skills/_shared/scripts/verify-asset-code-sync.mjs +0 -268
  333. package/project-template-v2/skills/_shared/scripts/verify-env.mjs +0 -285
  334. package/project-template-v2/skills/_shared/scripts/verify-placeholders.mjs +0 -161
  335. package/project-template-v2/skills/playable-autoplay/SKILL.md +0 -176
  336. package/project-template-v2/skills/playable-autoplay/agents/openai.yaml +0 -4
  337. package/project-template-v2/skills/playable-debug/SKILL.md +0 -116
  338. package/project-template-v2/skills/playable-debug/agents/openai.yaml +0 -4
  339. package/project-template-v2/skills/playable-debug/references/debug-config.md +0 -40
  340. package/project-template-v2/skills/playable-record/SKILL.md +0 -140
  341. package/project-template-v2/skills/playable-record/scripts/lib/dev-server.mjs +0 -104
  342. package/project-template-v2/skills/playable-record/scripts/lib/record-audio-bridge.js +0 -141
  343. package/project-template-v2/skills/playable-record/scripts/record-playable.mjs +0 -425
  344. package/project-template-v2/skills/playable-record/scripts/verify-contract.mjs +0 -261
  345. package/project-template-v2/skills/playable-record/scripts/verify-firstwin.mjs +0 -398
  346. package/project-template-v2/skills/playable-record/scripts/verify-fusion.mjs +0 -322
  347. package/project-template-v2/skills/playable-record/scripts/verify-lifecycle.mjs +0 -105
  348. package/project-template-v2/skills/playable-record/scripts/verify-vlm-video.mjs +0 -230
  349. package/project-template-v2/skills/playable-validate/SKILL.md +0 -61
  350. package/project-template-v2/skills/playable-validate/validation-rules.md +0 -14
  351. package/project-template-v2/skills/playable-verify-ui/SKILL.md +0 -63
  352. package/project-template-v2/skills/playable-verify-ui/scripts/lib/__init__.py +0 -1
  353. package/project-template-v2/skills/playable-verify-ui/scripts/lib/config_expr.js +0 -218
  354. package/project-template-v2/skills/playable-verify-ui/scripts/lib/node_utils.js +0 -51
  355. package/project-template-v2/skills/playable-verify-ui/scripts/lib/profile_loader.py +0 -59
  356. package/project-template-v2/skills/playable-verify-ui/scripts/lib/report.py +0 -106
  357. package/project-template-v2/skills/playable-verify-ui/scripts/lib/runtime_contract.js +0 -116
  358. package/project-template-v2/skills/playable-verify-ui/scripts/verify_fetch_antipatterns.py +0 -59
  359. package/project-template-v2/skills/playable-verify-ui/scripts/verify_hardcoded_layout.js +0 -64
  360. package/project-template-v2/skills/playable-verify-ui/scripts/verify_runtime_contract.js +0 -58
  361. package/project-template-v2/skills/playable-verify-ui/scripts/verify_text_overlap.py +0 -333
  362. package/project-template-v2/ta-workspace/scripts/.gitkeep +0 -0
  363. package/project-template-v2/tsconfig.json +0 -20
  364. package/project-template-v2/vite.config.ts +0 -27
  365. package/project-templates/batch-locks/phaser.yaml +0 -922
  366. package/project-templates/batch-locks/threejs.yaml +0 -978
  367. package/project-templates/empty/AGENTS.md +0 -41
  368. package/project-templates/empty/gitignore +0 -33
@@ -1,216 +0,0 @@
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 { comparisonMessage, ComparisonCliError } from './comparison-messages.js';
10
- import { comparisonSha256, readComparisonJson } from './comparison-files.js';
11
- import { preflightComparison } from './comparison-preflight.js';
12
- import { comparisonOutputPath, comparisonOutputRoot, writeComparisonJson } from './comparison-output.js';
13
- async function prepareComparisonOutput(out, runId, resume) {
14
- if (!(await ensureVideoEvidenceGitExcluded(process.cwd())))
15
- console.error(videoMessage('gitExcludeWarning'));
16
- await comparisonOutputRoot(out, true);
17
- await comparisonOutputPath(out, 'request.json');
18
- const names = await readdir(out);
19
- if (!names.length && !resume)
20
- return;
21
- try {
22
- const saved = await readComparisonJson(join(out, 'request.json'));
23
- if (resume && saved.schemaVersion === 'playcraft.video-comparison-local-request.v1' && saved.runId === runId && saved.out === out)
24
- return;
25
- }
26
- catch { /* Preserve unrelated output directories. */ }
27
- throw new VideoCliError('outputConflict', 64);
28
- }
29
- export async function readComparisonRun(client, runId, workspaceId, signal, cancel = false) {
30
- const value = cancel ? await client.post(`/video-comparison/${runId}/cancel`, { workspaceId, runId }, { signal })
31
- : await client.get(`/video-comparison/${runId}`, { workspaceId }, { signal });
32
- let run;
33
- try {
34
- run = videoComparisonRunSchema.parse(value);
35
- }
36
- catch {
37
- throw new ComparisonCliError('comparisonResult');
38
- }
39
- if (run.runId !== runId || run.workspaceId !== workspaceId || run.comparison && run.comparison.runId !== runId)
40
- throw new ComparisonCliError('comparisonResult');
41
- return run;
42
- }
43
- export function comparisonRunResult(command, run) {
44
- const status = run.comparison?.status;
45
- const exitCode = run.state === 'uncertain' ? 75 : ['failed', 'cancelled'].includes(run.state) ? 4
46
- : status === 'fail' ? 2 : status === 'inconclusive' ? 3 : 0;
47
- return { exitCode, envelope: { schemaVersion: 'playcraft.video-cli.v1', command, runId: run.runId, state: run.state,
48
- ok: run.state === 'completed' && status === 'pass', result: run } };
49
- }
50
- export function validateComparisonResult(value, request, local) {
51
- try {
52
- const result = replicaValidationResultSchema.parse(value);
53
- if (result.runId !== request.runId || result.model.configId !== request.modelConfigId || result.model.processing !== request.processing
54
- || result.recordingHash !== comparisonSha256(canonicalReplicaJson(request.recording))
55
- || result.policySha256 !== comparisonSha256(canonicalReplicaJson(request.policy))
56
- || canonicalReplicaJson(result.policy) !== canonicalReplicaJson(request.policy))
57
- throw new Error('identity');
58
- const expectedInputs = local.inputs.map(item => ({ id: item.id, role: item.role, source: item.video.source }));
59
- if (canonicalReplicaJson([...result.inputs].sort((a, b) => a.id.localeCompare(b.id))) !== canonicalReplicaJson([...expectedInputs].sort((a, b) => a.id.localeCompare(b.id))))
60
- throw new Error('sources');
61
- const context = { inputs: result.inputs, analysis: request.analysis, recording: request.recording, policy: request.policy, processing: request.processing, fps: request.fps };
62
- const content = validateReplicaComparisonContent({ summary: result.summary, evidence: result.evidence, alignments: result.alignments,
63
- dimensions: result.dimensions, findings: result.findings, controlChecks: result.controlChecks,
64
- timings: result.timings, missingEvidence: result.missingEvidence }, context);
65
- // Recompute acceptance, including all missing evidence reported by the service.
66
- const computed = evaluateReplicaComparison(content, context);
67
- if (result.status !== computed.status || result.ok !== computed.ok || result.scope !== request.policy.scope
68
- || canonicalReplicaJson(result.coverage) !== canonicalReplicaJson(computed.coverage)
69
- || canonicalReplicaJson(result.timingChecks) !== canonicalReplicaJson(computed.timingChecks))
70
- throw new Error('decision');
71
- return result;
72
- }
73
- catch {
74
- throw new ComparisonCliError('comparisonResult');
75
- }
76
- }
77
- async function recheckComparisonInputs(saved, deps, signal) {
78
- const actual = await preflightComparison({ reference: saved.referencePath, analysis: saved.analysisPath, candidate: saved.candidatePath,
79
- recording: saved.recordingPath, out: saved.out, signal, probe: deps.probe });
80
- if (canonicalReplicaJson(actual.recording) !== canonicalReplicaJson(saved.local.recording)
81
- || canonicalReplicaJson(actual.analysis) !== canonicalReplicaJson(saved.local.analysis)
82
- || canonicalReplicaJson(actual.inputs.map(item => ({ id: item.id, role: item.role, source: item.video.source })))
83
- !== canonicalReplicaJson(saved.local.inputs.map(item => ({ id: item.id, role: item.role, source: item.video.source }))))
84
- throw new ComparisonCliError('invalidRecording', 64);
85
- return actual;
86
- }
87
- export async function executeVideoComparison(options, signal, deps) {
88
- let saved;
89
- let runId;
90
- let out;
91
- let submitted = false;
92
- try {
93
- const workspaceId = await resolveVideoWorkspace(options.workspace);
94
- if (options.resume && [options.reference, options.analysis, options.candidate, options.recording, options.policy, options.processing, options.fps, options.modelConfig].some(value => value !== undefined))
95
- throw new ComparisonCliError('invalidComparison', 64);
96
- if (!options.resume && (!options.reference || !options.analysis || !options.candidate || !options.recording))
97
- throw new ComparisonCliError('invalidComparison', 64);
98
- const processing = options.processing ?? 'static';
99
- const fps = options.fps === undefined ? processing === 'static' ? 4 : undefined : Number(options.fps);
100
- if (!['static', 'agentic'].includes(processing) || fps !== undefined && ![1, 2, 4, 8].includes(fps) || processing === 'agentic' && fps !== undefined)
101
- throw new ComparisonCliError('invalidComparison', 64);
102
- runId = options.resume ? requireVideoId(options.resume) : randomUUID();
103
- out = resolve(options.out ?? join('.playcraft/video-replication', runId));
104
- await prepareComparisonOutput(out, runId, !!options.resume);
105
- let run;
106
- if (options.resume) {
107
- try {
108
- saved = await readComparisonJson(join(out, 'request.json'), 8 * 1024 * 1024);
109
- if (saved.schemaVersion !== 'playcraft.video-comparison-local-request.v1' || saved.runId !== runId || saved.workspaceId !== workspaceId || saved.out !== out || !saved.request)
110
- throw new Error('identity');
111
- saved.request = videoComparisonRequestSchema.parse(saved.request);
112
- if (saved.request.runId !== runId || saved.request.workspaceId !== workspaceId)
113
- throw new Error('request');
114
- }
115
- catch {
116
- throw new ComparisonCliError('comparisonResume', 64);
117
- }
118
- submitted = true;
119
- saved.local = await preflightComparison({ reference: saved.referencePath, analysis: saved.analysisPath, candidate: saved.candidatePath,
120
- recording: saved.recordingPath, out, signal, probe: deps.probe });
121
- if (canonicalReplicaJson(saved.local.recording) !== canonicalReplicaJson(saved.request.recording)
122
- || canonicalReplicaJson(saved.local.analysis) !== canonicalReplicaJson(saved.request.analysis))
123
- throw new ComparisonCliError('comparisonResume', 64);
124
- // Recovery only reads the existing task. It cannot upload or submit another inference.
125
- run = await readComparisonRun(deps.client, runId, workspaceId, signal);
126
- }
127
- else {
128
- let policy;
129
- try {
130
- policy = options.policy ? replicaPolicySchema.parse(await readComparisonJson(resolve(options.policy))) : replicaDefaultPolicy;
131
- }
132
- catch {
133
- throw new ComparisonCliError('invalidComparison', 64);
134
- }
135
- deps.progress('probing');
136
- const local = await preflightComparison({ reference: options.reference, analysis: options.analysis, candidate: options.candidate,
137
- recording: options.recording, out, signal, probe: deps.probe });
138
- try {
139
- validateReplicaComparisonContext({ inputs: local.inputs.map(item => ({ id: item.id, role: item.role, source: item.video.source })),
140
- analysis: local.analysis, recording: local.recording, policy, processing: processing, fps });
141
- }
142
- catch {
143
- throw new ComparisonCliError('invalidComparison', 64);
144
- }
145
- const catalog = videoModelCatalogSchema.parse(await deps.client.get('/video-models', { workspaceId }, { signal }));
146
- const configId = options.modelConfig ? requireVideoId(options.modelConfig) : catalog.defaultConfigId;
147
- const model = catalog.models.find(item => item.configId === configId);
148
- if (!model || !model.processing.includes(processing) || model.maxVideos < local.inputs.length)
149
- throw new ComparisonCliError('comparisonUnavailable', 64);
150
- saved = { schemaVersion: 'playcraft.video-comparison-local-request.v1', runId, workspaceId, out,
151
- referencePath: resolve(options.reference), analysisPath: resolve(options.analysis), candidatePath: resolve(options.candidate), recordingPath: resolve(options.recording),
152
- local, state: 'uploading', createdAt: new Date().toISOString() };
153
- await writeComparisonJson(out, 'request.json', saved);
154
- const inputs = [];
155
- for (const input of local.inputs) {
156
- deps.progress('uploading');
157
- const mediaId = await uploadVideo(input.video, workspaceId, deps, signal);
158
- inputs.push({ id: input.id, role: input.role, mediaId });
159
- }
160
- const request = videoComparisonRequestSchema.parse({ schemaVersion: 'playcraft.video-comparison-request.v1', workspaceId, runId,
161
- inputs, analysis: local.analysis, recording: local.recording, policy, processing, ...(fps !== undefined && { fps }), modelConfigId: configId });
162
- saved = { ...saved, request, state: 'submitting' };
163
- await writeComparisonJson(out, 'request.json', saved);
164
- await recheckComparisonInputs(saved, deps, signal);
165
- deps.progress('submitting');
166
- submitted = true;
167
- const response = await deps.client.post('/video-comparison', request, { signal, onPrepared: async (operation) => {
168
- saved = { ...saved, requestId: operation.requestId };
169
- await writeComparisonJson(out, 'request.json', saved);
170
- } });
171
- try {
172
- run = videoComparisonRunSchema.parse(response);
173
- }
174
- catch {
175
- throw new ComparisonCliError('comparisonResult');
176
- }
177
- if (run.runId !== runId || run.workspaceId !== workspaceId)
178
- throw new ComparisonCliError('comparisonResult');
179
- }
180
- while (['pending', 'uploading', 'submitting', 'running'].includes(run.state)) {
181
- deps.progress('waiting');
182
- await delay(Math.max(250, Math.min(run.pollAfterMs, 10_000)), undefined, { signal });
183
- run = await readComparisonRun(deps.client, runId, workspaceId, signal);
184
- }
185
- await writeComparisonJson(out, 'task.json', run);
186
- if (run.state === 'uncertain')
187
- throw new VideoCliError('uncertain', 75);
188
- if (run.state !== 'completed')
189
- return comparisonRunResult('video compare', run);
190
- if (!saved.request || !run.comparison)
191
- throw new ComparisonCliError('comparisonResult');
192
- await recheckComparisonInputs(saved, deps, signal);
193
- const comparison = validateComparisonResult(run.comparison, saved.request, saved.local);
194
- deps.progress('extracting');
195
- const artifacts = await deps.materialize({ comparison, request: saved.request, inputs: saved.local.inputs, out, signal });
196
- await writeComparisonJson(out, 'request.json', { ...saved, state: 'completed' });
197
- const result = comparisonRunResult('video compare', { ...run, comparison });
198
- result.envelope.artifacts = artifacts;
199
- result.envelope.result = { status: comparison.status, scope: comparison.scope, coverage: comparison.coverage };
200
- return result;
201
- }
202
- catch (error) {
203
- const timeout = signal.aborted && signal.reason?.name === 'TimeoutError';
204
- const interrupted = signal.aborted && !timeout;
205
- const known = error instanceof VideoCliError || error instanceof ComparisonCliError;
206
- const code = timeout ? 'timeoutReached' : interrupted ? 'interrupted' : known ? error.code : submitted ? 'uncertain' : 'comparisonFailed';
207
- const exitCode = timeout ? 75 : interrupted ? 130 : known ? error.exitCode : submitted ? 75 : 4;
208
- if (saved && out && submitted && (exitCode === 75 || exitCode === 130))
209
- await writeComparisonJson(out, 'request.json', { ...saved, state: 'uncertain' }).catch(() => { });
210
- const message = error instanceof ComparisonCliError ? comparisonMessage(error.code) : error instanceof VideoCliError ? videoMessage(error.code)
211
- : timeout ? videoMessage('timeoutReached') : interrupted ? videoMessage('interrupted') : submitted ? videoMessage('uncertain') : comparisonMessage('comparisonFailed');
212
- return { exitCode, envelope: { schemaVersion: 'playcraft.video-cli.v1', command: 'video compare', runId,
213
- state: exitCode === 75 || exitCode === 130 ? 'uncertain' : 'failed', ok: false,
214
- ...(saved && out && { artifacts: { request: join(out, 'request.json') } }), error: { code, message, ...(interrupted && { remoteCancellationConfirmed: false }) } } };
215
- }
216
- }
@@ -1,165 +0,0 @@
1
- import { createHash } from 'node:crypto';
2
- import { readFile, readdir, writeFile } from 'node:fs/promises';
3
- import { basename, dirname, join, resolve } from 'node:path';
4
- import sharp from 'sharp';
5
- import { videoUnderstandingAnalysisSchema, validateVideoUnderstandingContent, } from '@playcraft/common/agent-tools';
6
- import { openVideo, runMediaTool, verifyVideo } from './media.js';
7
- import { VideoCliError, videoMessage } from './messages.js';
8
- import { ensureVideoEvidenceGitExcluded } from '../utils/video-evidence-git.js';
9
- import { comparisonOutputPath, comparisonOutputRoot, writeComparisonArtifact, writeComparisonJson } from './comparison-output.js';
10
- import { ComparisonCliError } from './comparison-messages.js';
11
- async function videoOutput(action) {
12
- try {
13
- return await action();
14
- }
15
- catch (error) {
16
- if (error instanceof ComparisonCliError && error.code === 'unsafeOutput')
17
- throw new VideoCliError('unsafeOutput', 64);
18
- throw error;
19
- }
20
- }
21
- export async function writeJsonAtomic(path, value) {
22
- await videoOutput(async () => {
23
- await comparisonOutputRoot(dirname(path), true);
24
- await writeComparisonJson(dirname(path), basename(path), value);
25
- });
26
- }
27
- export async function prepareArtifactDirectory(out, runId, resume) {
28
- if (!(await ensureVideoEvidenceGitExcluded(process.cwd())))
29
- console.error(videoMessage('gitExcludeWarning'));
30
- await videoOutput(async () => {
31
- await comparisonOutputRoot(out, true);
32
- await comparisonOutputPath(out, 'request.json');
33
- });
34
- const names = await readdir(out);
35
- if (!names.length && !resume)
36
- return;
37
- try {
38
- const saved = JSON.parse(await readFile(join(out, 'request.json'), 'utf8'));
39
- if (resume && saved.schemaVersion === 'playcraft.video-request.v1' && saved.runId === runId && saved.out === resolve(out))
40
- return;
41
- }
42
- catch { /* An unrelated or incomplete output directory must not be overwritten. */ }
43
- throw new VideoCliError('outputConflict', 64);
44
- }
45
- export async function loadSavedRequest(out, runId, workspaceId) {
46
- try {
47
- await videoOutput(() => comparisonOutputPath(out, 'request.json'));
48
- const value = JSON.parse(await readFile(join(out, 'request.json'), 'utf8'));
49
- if (value.schemaVersion !== 'playcraft.video-request.v1' || value.runId !== runId || value.workspaceId !== workspaceId || value.out !== resolve(out) || typeof value.videoPath !== 'string')
50
- throw new Error('invalid');
51
- return value;
52
- }
53
- catch {
54
- throw new VideoCliError('resumeMissing', 64);
55
- }
56
- }
57
- const escapeXml = (text) => text.replace(/[&<>"']/g, c => ({ '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&apos;' })[c]);
58
- const escapeMarkdown = (text) => text.replace(/([\\`*_[\]<>])/g, '\\$1');
59
- export function renderVideoAnalysis(analysis, frames) {
60
- const lines = [`# ${videoMessage('summary')}`, '', escapeMarkdown(analysis.summary), '', videoMessage('transcriptNotice'), ''];
61
- for (const [key, items] of [
62
- ['observations', analysis.observations], ['inferences', analysis.inferences], ['unknowns', analysis.unknowns],
63
- ]) {
64
- lines.push(`## ${videoMessage(key)}`, '');
65
- for (const item of items) {
66
- const confidence = 'confidence' in item ? ` (${item.confidence})` : '';
67
- lines.push(`- **${escapeMarkdown(item.id)}**${confidence}: ${escapeMarkdown(item.text)}`);
68
- }
69
- lines.push('');
70
- }
71
- lines.push(`## ${videoMessage('events')}`, '');
72
- for (const event of analysis.events)
73
- lines.push(`- ${escapeMarkdown(event.id)} [${event.startMs}–${event.endMs} ms]: ${event.kind}`);
74
- lines.push('', `## ${videoMessage('hints')}`, '');
75
- for (const [key, items] of Object.entries(analysis.reconstructionHints)) {
76
- lines.push(`### ${videoMessage(key === 'candidateRules' ? 'rules' : key)}`, '');
77
- for (const item of items)
78
- lines.push(`- ${escapeMarkdown(item)}`);
79
- lines.push('');
80
- }
81
- lines.push(`## ${videoMessage('evidence')}`, '');
82
- for (const evidence of analysis.evidence)
83
- lines.push(`- ${escapeMarkdown(evidence.id)} [${evidence.startMs}–${evidence.endMs} ms]: [frame](${frames.get(evidence.id)})`);
84
- return `${lines.join('\n')}\n`;
85
- }
86
- /** Materialize only validated original-video timestamps; the model never supplies file paths. */
87
- export async function materializeVideoEvidence(input) {
88
- let analysis;
89
- try {
90
- analysis = videoUnderstandingAnalysisSchema.parse(input.analysis);
91
- validateVideoUnderstandingContent({
92
- summary: analysis.summary, observations: analysis.observations, events: analysis.events,
93
- evidence: analysis.evidence, inferences: analysis.inferences, unknowns: analysis.unknowns,
94
- reconstructionHints: analysis.reconstructionHints,
95
- }, analysis.source, input.request?.clip);
96
- }
97
- catch {
98
- throw new VideoCliError('invalidResult');
99
- }
100
- // Refuse any redirected destination before replacing evidence from a previous attempt.
101
- await videoOutput(async () => {
102
- await comparisonOutputRoot(input.out, true);
103
- for (const path of [...analysis.evidence.map(item => `keyframes/${item.id}.png`), 'analysis.json', 'analysis.md', 'manifest.json', 'contact-sheet.jpg'])
104
- await comparisonOutputPath(input.out, path);
105
- });
106
- const file = await openVideo(input.sourcePath);
107
- const frames = new Map();
108
- const manifestFrames = [];
109
- const tiles = [];
110
- const width = 240;
111
- const height = 200;
112
- const columns = Math.min(4, analysis.evidence.length);
113
- try {
114
- await verifyVideo(file, analysis.source);
115
- const frameOutput = await runMediaTool('ffprobe', [
116
- '-v', 'error', '-protocol_whitelist', 'file,pipe', '-format_whitelist', 'mov,matroska,webm',
117
- '-select_streams', 'v:0', '-show_entries', 'frame=best_effort_timestamp_time', '-of', 'csv=p=0', '/dev/fd/3',
118
- ], file.fd, input.signal);
119
- const rawFrameTimes = frameOutput.split('\n').map(line => Number.parseFloat(line) * 1000).filter(Number.isFinite);
120
- const firstFrameTime = rawFrameTimes[0] ?? 0;
121
- const frameTimes = rawFrameTimes.map(time => time - firstFrameTime).filter(time => time >= 0 && time < analysis.source.durationMs);
122
- if (!frameTimes.length)
123
- throw new VideoCliError('invalidResult');
124
- for (const [index, evidence] of analysis.evidence.entries()) {
125
- input.signal?.throwIfAborted();
126
- const path = `keyframes/${evidence.id}.png`;
127
- // Quantize the model's suggested midpoint to a real decoded frame, including the final frame.
128
- const requestedTimeMs = (evidence.startMs + evidence.endMs) / 2;
129
- const timeMs = frameTimes.reduce((best, candidate) => Math.abs(candidate - requestedTimeMs) < Math.abs(best - requestedTimeMs) ? candidate : best);
130
- await videoOutput(() => writeComparisonArtifact(input.out, path, temporary => runMediaTool('ffmpeg', [
131
- '-v', 'error', '-nostdin', '-y', '-protocol_whitelist', 'file,pipe', '-format_whitelist', 'mov,matroska,webm',
132
- '-ss', (Math.max(0, timeMs - 0.01) / 1000).toFixed(6), '-i', '/dev/fd/3', '-map', '0:v:0',
133
- '-frames:v', '1', '-vf', 'scale=min(1280\\,iw):-2', '-threads', '1', temporary,
134
- ], file.fd, input.signal)));
135
- const bytes = await readFile(join(input.out, path));
136
- const metadata = await sharp(bytes).metadata();
137
- if (!metadata.width || !metadata.height)
138
- throw new VideoCliError('invalidResult');
139
- frames.set(evidence.id, path);
140
- manifestFrames.push({ evidenceId: evidence.id, timeMs, requestedTimeMs, path, sha256: createHash('sha256').update(bytes).digest('hex') });
141
- const thumbnail = await sharp(bytes).resize(width, height - 30, { fit: 'contain', background: '#f4f4f4' }).png().toBuffer();
142
- const label = Buffer.from(`<svg width="${width}" height="30"><rect width="100%" height="100%" fill="white"/><text x="8" y="20" font-size="12">${escapeXml(evidence.id)} ${(timeMs / 1000).toFixed(2)}s</text></svg>`);
143
- const tile = await sharp({ create: { width, height, channels: 3, background: '#ffffff' } }).composite([{ input: thumbnail, top: 0, left: 0 }, { input: label, top: height - 30, left: 0 }]).png().toBuffer();
144
- tiles.push({ input: tile, left: (index % columns) * width, top: Math.floor(index / columns) * height });
145
- }
146
- await verifyVideo(file, analysis.source);
147
- }
148
- finally {
149
- await file.close();
150
- }
151
- const contactSheet = join(input.out, 'contact-sheet.jpg');
152
- await videoOutput(() => writeComparisonArtifact(input.out, 'contact-sheet.jpg', temporary => sharp({ create: { width: columns * width, height: Math.ceil(tiles.length / columns) * height, channels: 3, background: '#dddddd' } }).composite(tiles).jpeg({ quality: 85 }).toFile(temporary)));
153
- // Keep analysis.json compatible with the shared schema; local frame references live in the manifest.
154
- const analysisPath = join(input.out, 'analysis.json');
155
- const markdown = join(input.out, 'analysis.md');
156
- await writeJsonAtomic(analysisPath, analysis);
157
- await videoOutput(() => writeComparisonArtifact(input.out, 'analysis.md', temporary => writeFile(temporary, renderVideoAnalysis(analysis, frames), { mode: 0o600, flag: 'wx' })));
158
- const manifest = join(input.out, 'manifest.json');
159
- await writeJsonAtomic(manifest, {
160
- schemaVersion: 'playcraft.video-evidence.v1', runId: analysis.runId, state: 'completed', source: analysis.source, model: analysis.model,
161
- analysis: 'analysis.json', markdown: 'analysis.md', contactSheet: 'contact-sheet.jpg',
162
- frames: manifestFrames, frameTimeSemantics: 'nearest-decoded-original-video-frame',
163
- });
164
- return { manifest, analysis: analysisPath, markdown, contactSheet, keyframes: manifestFrames.map(frame => join(input.out, frame.path)) };
165
- }
@@ -1,111 +0,0 @@
1
- import { spawn } from 'node:child_process';
2
- import { createHash } from 'node:crypto';
3
- import { open, realpath } from 'node:fs/promises';
4
- import { isAbsolute } from 'node:path';
5
- import { VIDEO_MAX_BYTES, videoSourceSchema, } from '@playcraft/common/agent-tools';
6
- import { VideoCliError } from './messages.js';
7
- /** No shell, external protocols, or unbounded child-process output. */
8
- export function runMediaTool(tool, args, inputFd, signal) {
9
- return new Promise((resolve, reject) => {
10
- const override = process.env[tool === 'ffmpeg' ? 'FFMPEG_PATH' : 'FFPROBE_PATH'];
11
- if (override && !isAbsolute(override)) {
12
- reject(new VideoCliError('mediaTool'));
13
- return;
14
- }
15
- const child = spawn(override || tool, args, { stdio: ['ignore', 'pipe', 'pipe', inputFd], signal });
16
- let output = '';
17
- let settled = false;
18
- const finish = (error) => {
19
- if (settled)
20
- return;
21
- settled = true;
22
- clearTimeout(timer);
23
- if (error)
24
- reject(error);
25
- else
26
- resolve(output);
27
- };
28
- const timer = setTimeout(() => {
29
- child.kill('SIGKILL');
30
- finish(new VideoCliError('mediaTool'));
31
- }, 30_000);
32
- child.stdout.on('data', chunk => {
33
- output += chunk.toString();
34
- if (output.length > 1024 * 1024) {
35
- child.kill('SIGKILL');
36
- finish(new VideoCliError('videoFormat'));
37
- }
38
- });
39
- child.stderr.on('data', () => { });
40
- child.once('error', error => finish(error.name === 'AbortError' ? error : new VideoCliError('mediaTool')));
41
- child.once('close', code => finish(code === 0 ? undefined : new VideoCliError('videoFormat')));
42
- });
43
- }
44
- export async function openVideo(path) {
45
- try {
46
- const file = await open(path, 'r');
47
- const stat = await file.stat();
48
- if (!stat.isFile()) {
49
- await file.close();
50
- throw new VideoCliError('videoMissing', 64);
51
- }
52
- if (stat.size <= 0 || stat.size > VIDEO_MAX_BYTES) {
53
- await file.close();
54
- throw new VideoCliError('videoQuota', 64);
55
- }
56
- return file;
57
- }
58
- catch (error) {
59
- if (error instanceof VideoCliError)
60
- throw error;
61
- throw new VideoCliError('videoMissing', 64);
62
- }
63
- }
64
- async function digests(file) {
65
- const sha = createHash('sha256');
66
- const md5 = createHash('md5');
67
- for await (const chunk of file.createReadStream({ autoClose: false, start: 0 })) {
68
- sha.update(chunk);
69
- md5.update(chunk);
70
- }
71
- return { sha256: sha.digest('hex'), contentMd5: md5.digest('base64') };
72
- }
73
- export async function probeVideo(path, signal) {
74
- const absolute = await realpath(path).catch(() => { throw new VideoCliError('videoMissing', 64); });
75
- const file = await openVideo(absolute);
76
- try {
77
- const stat = await file.stat();
78
- const magic = Buffer.alloc(16);
79
- await file.read(magic, 0, magic.length, 0);
80
- const isIso = magic.subarray(4, 8).toString('ascii') === 'ftyp';
81
- const isWebm = magic.readUInt32BE(0) === 0x1a45dfa3;
82
- if (!isIso && !isWebm)
83
- throw new VideoCliError('videoFormat', 64);
84
- const output = await runMediaTool('ffprobe', [
85
- '-v', 'error', '-protocol_whitelist', 'file,pipe', '-format_whitelist', 'mov,matroska,webm',
86
- '-show_entries', 'format=duration,format_name:stream=codec_type,width,height,duration',
87
- '-of', 'json', '/dev/fd/3',
88
- ], file.fd, signal);
89
- const result = JSON.parse(output);
90
- const video = result.streams?.find(stream => stream.codec_type === 'video');
91
- if (!video)
92
- throw new VideoCliError('videoFormat', 64);
93
- const durationMs = Number(result.format?.duration ?? video.duration) * 1000;
94
- const hashes = await digests(file);
95
- const parsed = videoSourceSchema.safeParse({
96
- sha256: hashes.sha256, sizeBytes: stat.size, durationMs, width: video.width, height: video.height,
97
- mimeType: isWebm ? 'video/webm' : magic.subarray(8, 12).toString('ascii') === 'qt ' ? 'video/quicktime' : 'video/mp4',
98
- });
99
- if (!parsed.success)
100
- throw new VideoCliError('videoQuota', 64);
101
- return { path: absolute, source: parsed.data, contentMd5: hashes.contentMd5 };
102
- }
103
- finally {
104
- await file.close();
105
- }
106
- }
107
- export async function verifyVideo(file, expected) {
108
- const stat = await file.stat();
109
- if (stat.size !== expected.sizeBytes || (await digests(file)).sha256 !== expected.sha256)
110
- throw new VideoCliError('sourceChanged', 64);
111
- }
@@ -1,89 +0,0 @@
1
- const messages = {
2
- en: {
3
- topic: 'Video understanding and replica comparison with evidence frames',
4
- models: 'List authorized video models and supported processing modes',
5
- understand: 'Understand a local reference video using a managed video model',
6
- jobs: 'Inspect or cancel an existing analysis without submitting new inference',
7
- get: 'Read an existing video task state', cancel: 'Explicitly request remote cancellation',
8
- workspace: 'Workspace UUID (defaults to the nearest workspace context file)',
9
- json: 'Print machine-readable JSON (always enabled)', video: 'Local MP4, MOV or WebM file',
10
- question: 'Focused question about the video', context: 'Prior analysis.json for this video',
11
- processing: 'auto | agentic | static', fps: 'Static sampling: 1 | 2 | 4 | 8',
12
- clip: 'Static clip in original video seconds: start:end',
13
- model: 'Authorized model configuration UUID', out: 'Artifact directory',
14
- timeout: 'Maximum local wait in seconds; the remote task remains recoverable',
15
- resume: 'Resume this existing run UUID; cannot be combined with new inputs',
16
- invalidArguments: 'Invalid video command arguments.',
17
- workspaceMissing: 'Specify --workspace or run inside a Cloud Workspace checkout.',
18
- workspaceMismatch: 'The explicit workspace does not match the checkout context.',
19
- videoMissing: 'The video must be a readable regular file.',
20
- videoQuota: 'The video exceeds 100 MiB or 180 seconds, or has unsupported dimensions.',
21
- videoFormat: 'The file is not a supported MP4, MOV or WebM video.',
22
- mediaTool: 'FFmpeg and ffprobe are required. Check the managed runtime installation.',
23
- outputConflict: 'The artifact directory belongs to another run. Choose an empty directory.',
24
- unsafeOutput: 'The artifact directory contains a linked or unsupported output path.',
25
- gitExcludeWarning: 'The local Git ignore rule could not be updated. Video analysis will continue; do not commit .playcraft/video-replication evidence to the repository.',
26
- invalidContext: 'The context is invalid or belongs to a different video.',
27
- invalidResult: 'The video service returned invalid or incomplete evidence.',
28
- sourceChanged: 'The video changed since it was probed or the task was created.',
29
- uploadFailed: 'Video upload could not be completed.',
30
- modelUnavailable: 'No authorized model supports the requested video processing mode.',
31
- resumeMissing: 'The saved request is missing or does not match this run and workspace.',
32
- timeoutReached: 'Local waiting ended. Resume this run to check its existing result.',
33
- interrupted: 'Interrupted. Remote cancellation was not requested or confirmed.',
34
- uncertain: 'The remote outcome is unknown. Check this run; do not submit another analysis.',
35
- remoteFailed: 'The remote analysis failed or was cancelled.',
36
- failed: 'Video understanding failed. Inspect the saved request and task before retrying.',
37
- probing: 'Inspecting the video', uploading: 'Uploading the video',
38
- submitting: 'Submitting the analysis', waiting: 'Waiting for the existing analysis',
39
- extracting: 'Extracting evidence frames',
40
- summary: 'Summary', observations: 'Observed facts', events: 'Events',
41
- inferences: 'Inferences', unknowns: 'Unknowns', hints: 'Reconstruction suggestions',
42
- evidence: 'Evidence', entities: 'Entities', assets: 'Assets', rules: 'Candidate rules',
43
- transcriptNotice: 'Observed events cover only the demonstrated video. Inferred controls and hidden rules remain uncertain.',
44
- },
45
- zh: {
46
- topic: '视频理解与复刻比较,抽取证据关键帧',
47
- models: '列出有权使用的视频模型和处理模式',
48
- understand: '使用受管视频模型理解本地参考视频',
49
- jobs: '核对或取消已有任务,不新建推理', get: '读取已有视频任务状态', cancel: '明确请求远端取消',
50
- workspace: '工作区编号,默认读取最近的工作区上下文文件',
51
- json: '输出机器可读的 JSON(始终启用)', video: '本地 MP4、MOV 或 WebM 文件',
52
- question: '需要确认的视频问题', context: '同一视频的已有 analysis.json',
53
- processing: 'auto | agentic | static', fps: '静态抽样:1 | 2 | 4 | 8',
54
- clip: '原视频上的静态片段秒数:开始:结束', model: '有权使用的模型配置编号', out: '制品输出目录',
55
- timeout: '本地等待的最长秒数,远端任务仍可恢复', resume: '恢复已有任务编号,不能与新输入组合使用',
56
- invalidArguments: '视频命令参数无效。', workspaceMissing: '请指定 --workspace,或在云端工作区目录内运行。',
57
- workspaceMismatch: '指定的工作区与当前目录的工作区上下文不一致。',
58
- videoMissing: '视频必须是可读的普通文件。', videoQuota: '视频超出 100 MiB、180 秒或支持的画面尺寸。',
59
- videoFormat: '文件不是支持的 MP4、MOV 或 WebM 视频。', mediaTool: '需要 FFmpeg 和 ffprobe,请检查受管运行环境安装。',
60
- outputConflict: '输出目录属于另一轮任务,请使用空目录。', invalidContext: '上下文无效,或属于另一份视频。',
61
- unsafeOutput: '制品目录包含符号链接或不支持的输出路径。',
62
- gitExcludeWarning: '无法更新 Git 本地忽略规则。视频分析将继续,请勿将 .playcraft/video-replication 证据提交到仓库。',
63
- invalidResult: '视频服务返回的证据无效或不完整。', sourceChanged: '视频在探测后或任务创建后发生了变化。',
64
- uploadFailed: '视频上传未完成。', modelUnavailable: '没有有权使用且支持所需视频模式的模型。',
65
- resumeMissing: '找不到保存的请求,或请求与任务、工作区不一致。',
66
- timeoutReached: '本地等待结束,请恢复此任务核对已有结果。',
67
- interrupted: '操作已中断,尚未请求或确认远端取消。',
68
- uncertain: '远端结果未知,请核对此任务,不要另建分析。', remoteFailed: '远端分析失败或已取消。',
69
- failed: '视频理解失败,请先核对已保存的请求和任务再重试。',
70
- probing: '正在探测视频', uploading: '正在上传视频', submitting: '正在提交分析',
71
- waiting: '正在等待已有分析', extracting: '正在抽取证据关键帧',
72
- summary: '概述', observations: '观察事实', events: '事件', inferences: '推断', unknowns: '未知项',
73
- hints: '复刻建议', evidence: '证据', entities: '实体', assets: '素材', rules: '候选规则',
74
- transcriptNotice: '观察事件仅覆盖视频展示的路径;操作方式和隐藏规则的推断仍需确认。',
75
- },
76
- };
77
- export function videoMessage(key) {
78
- const locale = process.env.PLAYCRAFT_LANGUAGE || process.env.LANG || 'en';
79
- return messages[locale.startsWith('zh') ? 'zh' : 'en'][key];
80
- }
81
- export class VideoCliError extends Error {
82
- code;
83
- exitCode;
84
- constructor(code, exitCode = 4) {
85
- super(videoMessage(code));
86
- this.code = code;
87
- this.exitCode = exitCode;
88
- }
89
- }
@@ -1,35 +0,0 @@
1
- const messages = {
2
- en: {
3
- description: 'Download cloud workspaces and push local commits back',
4
- clone: 'Clone the current workspace files, including uncommitted changes',
5
- push: 'Push local commits back; reject concurrent cloud changes',
6
- workspaceId: 'Cloud workspace identifier', directory: 'Engineering directory, or root for the whole workspace',
7
- target: 'Local checkout directory', url: 'PlayCraft service base address, including any environment prefix',
8
- token: 'Personal access token', json: 'Print structured output',
9
- invalidTarget: 'Invalid workspace identifier, directory, or service address.',
10
- directoryExists: 'The target directory already exists. Choose an empty new directory.',
11
- metadataMissing: 'No workspace checkout metadata found. Run workspace clone first.',
12
- cleanRequired: 'Commit or discard local changes before running workspace push.',
13
- notReady: 'Workspace access is not ready. Open the workspace and retry.',
14
- conflict: 'Cloud files changed since this checkout. Clone again and merge your changes before pushing.',
15
- failed: 'Workspace transfer failed', cloned: 'Workspace cloned:', pushed: 'Local commits pushed to the cloud workspace.',
16
- tooLarge: 'Workspace transfer exceeds the 256 MiB (Mebibyte, binary megabyte) or 20,000 file limit. Use direct download for larger workspaces.',
17
- login: 'Log in to the target environment using playcraft login --url',
18
- },
19
- zh: {
20
- description: '下载云工作区,并将本地提交推送回来', clone: '克隆当前工作区文件,包含尚未提交的修改',
21
- push: '把本地提交推送回来;云端有新修改时拒绝覆盖', workspaceId: '云工作区标识',
22
- directory: '工程目录;root 表示整个工作区', target: '本地克隆目录',
23
- url: 'PlayCraft 服务基址,保留环境路径前缀', token: '个人访问令牌', json: '输出结构化结果',
24
- invalidTarget: '工作区标识、目录或服务地址无效。', directoryExists: '目标目录已存在,请选择新的目录。',
25
- metadataMissing: '未找到工作区克隆信息,请先运行 workspace clone。', cleanRequired: '请先提交或丢弃本地修改,再运行 workspace push。',
26
- notReady: '工作区访问尚未就绪,请打开工作区后重试。', conflict: '云端文件在克隆后发生变化,请重新克隆并合并本地修改后再推送。',
27
- failed: '工作区传输失败', cloned: '工作区已克隆到:', pushed: '本地提交已推送回云工作区。',
28
- tooLarge: '工作区传输超过 256 MiB(Mebibyte,二进制兆字节)或 20,000 个文件的限制。较大的工作区请使用直接下载。',
29
- login: '请使用 playcraft login --url 登录目标环境',
30
- },
31
- };
32
- export function workspaceMessage(key) {
33
- const locale = process.env.PLAYCRAFT_LANGUAGE || process.env.LANG || 'en';
34
- return messages[locale.startsWith('zh') ? 'zh' : 'en'][key];
35
- }