@playcraft/cli 0.1.33 → 0.1.35-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 (346) hide show
  1. package/README.md +3 -56
  2. package/dist/agent/local-backend.js +1 -1
  3. package/dist/cli-root-help.js +0 -2
  4. package/dist/commands/build.js +4 -9
  5. package/dist/commands/create.js +33 -26
  6. package/dist/commands/image.js +6 -2
  7. package/dist/commands/login.js +42 -84
  8. package/dist/commands/skills.js +0 -2
  9. package/dist/commands/tools-3d.js +0 -11
  10. package/dist/commands/tools-generation.js +109 -68
  11. package/dist/commands/tools-image-context.js +23 -0
  12. package/dist/commands/tools.js +2 -2
  13. package/dist/config.js +4 -59
  14. package/dist/index.js +3 -36
  15. package/dist/remix/clone.js +0 -2
  16. package/dist/remix/init-template.js +0 -2
  17. package/dist/remix/pull.js +0 -2
  18. package/dist/remix/push.js +1 -21
  19. package/dist/utils/agent-api-client.js +30 -185
  20. package/dist/utils/image-asset-context.js +105 -0
  21. package/dist/utils/playcraft-http-client.js +4 -6
  22. package/package.json +5 -14
  23. package/project-template/.claude/agents/refs/ta-image-generation-detail.md +67 -123
  24. package/project-template/.claude/skills/playcraft-image-generation/SKILL.md +105 -44
  25. package/project-template/.claude/skills/playcraft-image-generation/reference/gen-batch-sprite.template.mjs +42 -33
  26. package/project-template/.claude/skills/playcraft-image-generation/reference/gen-batch.template.mjs +18 -20
  27. package/project-template/.claude/skills/playcraft-image-generation/reference/process-batch.template.mjs +6 -7
  28. package/project-template/.claude/skills/playcraft-image-generation/reference/prompt-cookbook.md +36 -39
  29. package/project-templates/phaser/.claude/agents/refs/ta-image-generation-detail.md +59 -59
  30. package/project-templates/phaser/.claude/skills/playcraft-image-generation/SKILL.md +105 -44
  31. package/project-templates/phaser/.claude/skills/playcraft-image-generation/reference/gen-batch-sprite.template.mjs +42 -33
  32. package/project-templates/phaser/.claude/skills/playcraft-image-generation/reference/gen-batch.template.mjs +18 -20
  33. package/project-templates/phaser/.claude/skills/playcraft-image-generation/reference/process-batch.template.mjs +6 -7
  34. package/project-templates/phaser/.claude/skills/playcraft-image-generation/reference/prompt-cookbook.md +36 -39
  35. package/project-templates/threejs/.claude/agents/refs/ta-image-generation-detail.md +59 -59
  36. package/project-templates/threejs/.claude/skills/playcraft-image-generation/SKILL.md +105 -44
  37. package/project-templates/threejs/.claude/skills/playcraft-image-generation/reference/gen-batch-sprite.template.mjs +42 -33
  38. package/project-templates/threejs/.claude/skills/playcraft-image-generation/reference/gen-batch.template.mjs +18 -20
  39. package/project-templates/threejs/.claude/skills/playcraft-image-generation/reference/process-batch.template.mjs +6 -7
  40. package/project-templates/threejs/.claude/skills/playcraft-image-generation/reference/prompt-cookbook.md +36 -39
  41. package/dist/auth/account-activation.js +0 -157
  42. package/dist/auth/messages.js +0 -30
  43. package/dist/commands/platform-skills.generated.js +0 -19
  44. package/dist/commands/playable.js +0 -53
  45. package/dist/commands/tools-batch.js +0 -87
  46. package/dist/commands/video.js +0 -111
  47. package/dist/commands/workspace-runtime.js +0 -54
  48. package/dist/commands/workspace.js +0 -35
  49. package/dist/project-skills/builtin-import.js +0 -225
  50. package/dist/project-skills/commands.js +0 -77
  51. package/dist/project-skills/lifecycle.js +0 -28
  52. package/dist/project-skills/local-run.js +0 -112
  53. package/dist/project-skills/local-store.js +0 -221
  54. package/dist/project-skills/messages.js +0 -80
  55. package/dist/project-skills/reconcile.js +0 -62
  56. package/dist/project-skills/remote-cache.js +0 -51
  57. package/dist/project-skills/validation.js +0 -19
  58. package/dist/recording/messages.js +0 -40
  59. package/dist/recording/project-digest.js +0 -85
  60. package/dist/recording/recorder.js +0 -293
  61. package/dist/recording/scenario.js +0 -50
  62. package/dist/utils/environment-endpoints.js +0 -17
  63. package/dist/utils/tool-operation-journal.js +0 -124
  64. package/dist/utils/video-evidence-git.js +0 -81
  65. package/dist/video-replication/client.js +0 -222
  66. package/dist/video-replication/comparison-evidence.js +0 -149
  67. package/dist/video-replication/comparison-files.js +0 -57
  68. package/dist/video-replication/comparison-log.js +0 -72
  69. package/dist/video-replication/comparison-messages.js +0 -104
  70. package/dist/video-replication/comparison-output.js +0 -63
  71. package/dist/video-replication/comparison-preflight.js +0 -66
  72. package/dist/video-replication/comparison.js +0 -216
  73. package/dist/video-replication/evidence.js +0 -165
  74. package/dist/video-replication/media.js +0 -111
  75. package/dist/video-replication/messages.js +0 -89
  76. package/dist/workspace/messages.js +0 -35
  77. package/dist/workspace/transfer-client.js +0 -174
  78. package/dist/workspace-runtime/codex/app-server.js +0 -965
  79. package/dist/workspace-runtime/codex/compact.js +0 -46
  80. package/dist/workspace-runtime/codex/config-toml.js +0 -107
  81. package/dist/workspace-runtime/codex/jsonrpc-stdio.js +0 -145
  82. package/dist/workspace-runtime/codex/launcher-client.js +0 -177
  83. package/dist/workspace-runtime/codex/loopback.js +0 -146
  84. package/dist/workspace-runtime/codex/mcp-config.js +0 -39
  85. package/dist/workspace-runtime/codex/model-broker.js +0 -263
  86. package/dist/workspace-runtime/codex/native-adapter.js +0 -9
  87. package/dist/workspace-runtime/codex/permissions.js +0 -70
  88. package/dist/workspace-runtime/codex/work-mode.js +0 -15
  89. package/dist/workspace-runtime/main.js +0 -10
  90. package/dist/workspace-runtime/persistence/journal.js +0 -355
  91. package/dist/workspace-runtime/persistence/native-subagents.js +0 -162
  92. package/dist/workspace-runtime/persistence/sqlite-client.js +0 -77
  93. package/dist/workspace-runtime/persistence/sqlite-worker.js +0 -1096
  94. package/dist/workspace-runtime/previews/output.js +0 -36
  95. package/dist/workspace-runtime/previews/publish.js +0 -262
  96. package/dist/workspace-runtime/previews/single-file.js +0 -43
  97. package/dist/workspace-runtime/processes/managed-writes.js +0 -69
  98. package/dist/workspace-runtime/production/contracts.js +0 -17
  99. package/dist/workspace-runtime/production/controller.js +0 -207
  100. package/dist/workspace-runtime/production/dependencies.js +0 -60
  101. package/dist/workspace-runtime/production/native.js +0 -203
  102. package/dist/workspace-runtime/production/processes.js +0 -86
  103. package/dist/workspace-runtime/production/save-command.js +0 -35
  104. package/dist/workspace-runtime/production/source-copy.js +0 -91
  105. package/dist/workspace-runtime/production/stages.js +0 -205
  106. package/dist/workspace-runtime/production/storage.js +0 -60
  107. package/dist/workspace-runtime/serve.js +0 -207
  108. package/dist/workspace-runtime/server/auth.js +0 -21
  109. package/dist/workspace-runtime/server/connectivity.js +0 -201
  110. package/dist/workspace-runtime/server/desktop-host.js +0 -429
  111. package/dist/workspace-runtime/server/desktop-preview-config.js +0 -81
  112. package/dist/workspace-runtime/server/desktop-preview-service.js +0 -157
  113. package/dist/workspace-runtime/server/desktop-preview-static.js +0 -53
  114. package/dist/workspace-runtime/server/desktop-preview.js +0 -189
  115. package/dist/workspace-runtime/server/dispatch.js +0 -1174
  116. package/dist/workspace-runtime/server/endpoints.js +0 -19
  117. package/dist/workspace-runtime/server/execution-group.js +0 -85
  118. package/dist/workspace-runtime/server/http.js +0 -1127
  119. package/dist/workspace-runtime/server/lock.js +0 -53
  120. package/dist/workspace-runtime/server/rfb-watch-gate.js +0 -168
  121. package/dist/workspace-runtime/server/terminal-host.js +0 -366
  122. package/dist/workspace-runtime/server/types.js +0 -1
  123. package/dist/workspace-runtime/server/workspace-transfer.js +0 -99
  124. package/dist/workspace-runtime/workspaces/archive.js +0 -207
  125. package/dist/workspace-runtime/workspaces/artifact-events.js +0 -55
  126. package/dist/workspace-runtime/workspaces/artifact-files.js +0 -197
  127. package/dist/workspace-runtime/workspaces/artifact-receipts.js +0 -89
  128. package/dist/workspace-runtime/workspaces/artifacts.js +0 -282
  129. package/dist/workspace-runtime/workspaces/attachment-content.js +0 -123
  130. package/dist/workspace-runtime/workspaces/attachments.js +0 -181
  131. package/dist/workspace-runtime/workspaces/batch-commit.js +0 -142
  132. package/dist/workspace-runtime/workspaces/cli-context.js +0 -23
  133. package/dist/workspace-runtime/workspaces/cli-credentials.js +0 -111
  134. package/dist/workspace-runtime/workspaces/composer-context.js +0 -125
  135. package/dist/workspace-runtime/workspaces/context-error.js +0 -2
  136. package/dist/workspace-runtime/workspaces/dependencies.js +0 -264
  137. package/dist/workspace-runtime/workspaces/developer-command.js +0 -64
  138. package/dist/workspace-runtime/workspaces/directories.js +0 -123
  139. package/dist/workspace-runtime/workspaces/directory-access.js +0 -37
  140. package/dist/workspace-runtime/workspaces/directory-attachments.js +0 -245
  141. package/dist/workspace-runtime/workspaces/directory-maintenance.js +0 -111
  142. package/dist/workspace-runtime/workspaces/directory-stats.js +0 -49
  143. package/dist/workspace-runtime/workspaces/disk-snapshot.js +0 -31
  144. package/dist/workspace-runtime/workspaces/empty-template.js +0 -40
  145. package/dist/workspace-runtime/workspaces/files-search.js +0 -116
  146. package/dist/workspace-runtime/workspaces/files.js +0 -120
  147. package/dist/workspace-runtime/workspaces/git-credential-broker.js +0 -312
  148. package/dist/workspace-runtime/workspaces/git-credential-helper.js +0 -68
  149. package/dist/workspace-runtime/workspaces/git-direct-store.js +0 -155
  150. package/dist/workspace-runtime/workspaces/git.js +0 -658
  151. package/dist/workspace-runtime/workspaces/instructions.js +0 -56
  152. package/dist/workspace-runtime/workspaces/json5-edit.js +0 -170
  153. package/dist/workspace-runtime/workspaces/library-attachments.js +0 -268
  154. package/dist/workspace-runtime/workspaces/parameters.js +0 -272
  155. package/dist/workspace-runtime/workspaces/prepare.js +0 -264
  156. package/dist/workspace-runtime/workspaces/registry.js +0 -236
  157. package/dist/workspace-runtime/workspaces/revisions.js +0 -31
  158. package/dist/workspace-runtime/workspaces/skill-artifact-loader.js +0 -91
  159. package/dist/workspace-runtime/workspaces/skills.js +0 -189
  160. package/dist/workspace-runtime/workspaces/transfer.js +0 -288
  161. package/dist/workspace-runtime/workspaces/turn-changes.js +0 -72
  162. package/dist/workspace-runtime/workspaces/video-attachments.js +0 -49
  163. package/project-template/.claude/skills/playcraft-replica-validation/SKILL.md +0 -25
  164. package/project-template/.claude/skills/playcraft-replica-validation/references/contracts.md +0 -79
  165. package/project-template/.claude/skills/playcraft-video-understanding/SKILL.md +0 -29
  166. package/project-template/.claude/skills/playcraft-video-understanding/references/contracts.md +0 -37
  167. package/project-template-v2/.claude/agents/artist.md +0 -82
  168. package/project-template-v2/.claude/agents/developer.md +0 -153
  169. package/project-template-v2/.claude/agents/game-designer.md +0 -264
  170. package/project-template-v2/.claude/agents/refs/artist-art-style-catalog.md +0 -533
  171. package/project-template-v2/.claude/agents/refs/artist-color-audio-recipes.md +0 -153
  172. package/project-template-v2/.claude/agents/refs/artist-dimension-axis.md +0 -27
  173. package/project-template-v2/.claude/agents/refs/artist-master-composite-recipes.md +0 -208
  174. package/project-template-v2/.claude/agents/refs/atom-skill-library.md +0 -81
  175. package/project-template-v2/.claude/agents/refs/developer-impl-cookbook.md +0 -432
  176. package/project-template-v2/.claude/agents/refs/framework-5-component-filter.md +0 -252
  177. package/project-template-v2/.claude/agents/refs/framework-game-feel-juice.md +0 -266
  178. package/project-template-v2/.claude/agents/refs/framework-mda.md +0 -147
  179. package/project-template-v2/.claude/agents/refs/game-designer-gameplay-sufficiency.md +0 -123
  180. package/project-template-v2/.claude/agents/refs/ta-3d-flip-recipe.md +0 -88
  181. package/project-template-v2/.claude/agents/refs/ta-atlas-deliverable-standard.md +0 -67
  182. package/project-template-v2/.claude/agents/refs/ta-batch-pipeline-recipes.md +0 -120
  183. package/project-template-v2/.claude/agents/refs/ta-image-generation-detail.md +0 -300
  184. package/project-template-v2/.claude/agents/refs/ta-image-ops-reference.md +0 -495
  185. package/project-template-v2/.claude/agents/refs/ta-pipeline-cookbook.md +0 -1141
  186. package/project-template-v2/.claude/agents/refs/ta-tools-reference.md +0 -111
  187. package/project-template-v2/.claude/agents/refs/ta-vfx-preset-catalog.md +0 -365
  188. package/project-template-v2/.claude/agents/refs/threejs-cannon-pitfalls.md +0 -412
  189. package/project-template-v2/.claude/agents/reviewer.md +0 -75
  190. package/project-template-v2/.claude/agents/technical-artist.md +0 -86
  191. package/project-template-v2/.claude/hooks/snapshot-milestone.mjs +0 -243
  192. package/project-template-v2/.claude/hooks/user-prompt.mjs +0 -133
  193. package/project-template-v2/.claude/settings.json +0 -33
  194. package/project-template-v2/.claude/skills/brainstorming/SKILL.md +0 -161
  195. package/project-template-v2/.claude/skills/brainstorming/scripts/frame-template.html +0 -270
  196. package/project-template-v2/.claude/skills/brainstorming/scripts/helper.js +0 -177
  197. package/project-template-v2/.claude/skills/brainstorming/scripts/server.cjs +0 -354
  198. package/project-template-v2/.claude/skills/brainstorming/scripts/start-server.sh +0 -148
  199. package/project-template-v2/.claude/skills/brainstorming/scripts/stop-server.sh +0 -56
  200. package/project-template-v2/.claude/skills/brainstorming/scripts/wait-for-selection.sh +0 -62
  201. package/project-template-v2/.claude/skills/brainstorming/spec-document-reviewer-prompt.md +0 -49
  202. package/project-template-v2/.claude/skills/brainstorming/visual-companion.md +0 -309
  203. package/project-template-v2/.claude/skills/playcraft-ad-psychology/SKILL.md +0 -182
  204. package/project-template-v2/.claude/skills/playcraft-art-style-guide/SKILL.md +0 -123
  205. package/project-template-v2/.claude/skills/playcraft-asset-state-sheet/SKILL.md +0 -205
  206. package/project-template-v2/.claude/skills/playcraft-audio-generation/SKILL.md +0 -280
  207. package/project-template-v2/.claude/skills/playcraft-batch-pipeline/SKILL.md +0 -184
  208. package/project-template-v2/.claude/skills/playcraft-build-optimizer/SKILL.md +0 -306
  209. package/project-template-v2/.claude/skills/playcraft-image-generation/SKILL.md +0 -298
  210. package/project-template-v2/.claude/skills/playcraft-image-generation/reference/build-sprite-sheet.template.mjs +0 -123
  211. package/project-template-v2/.claude/skills/playcraft-image-generation/reference/compare-style.template.mjs +0 -254
  212. package/project-template-v2/.claude/skills/playcraft-image-generation/reference/gen-batch-sprite.template.mjs +0 -324
  213. package/project-template-v2/.claude/skills/playcraft-image-generation/reference/gen-batch.template.mjs +0 -97
  214. package/project-template-v2/.claude/skills/playcraft-image-generation/reference/gen-edit-variants.template.mjs +0 -118
  215. package/project-template-v2/.claude/skills/playcraft-image-generation/reference/process-batch.template.mjs +0 -137
  216. package/project-template-v2/.claude/skills/playcraft-image-generation/reference/prompt-cookbook.md +0 -397
  217. package/project-template-v2/.claude/skills/playcraft-image-generation/reference/validate-sprite-sheet.template.mjs +0 -296
  218. package/project-template-v2/.claude/skills/playcraft-image-ops/SKILL.md +0 -122
  219. package/project-template-v2/.claude/skills/playcraft-image-processing/SKILL.md +0 -219
  220. package/project-template-v2/.claude/skills/playcraft-masking/SKILL.md +0 -373
  221. package/project-template-v2/.claude/skills/playcraft-playable-optimization/SKILL.md +0 -161
  222. package/project-template-v2/.claude/skills/playcraft-research/SKILL.md +0 -215
  223. package/project-template-v2/.claude/skills/playcraft-skill-recommender/SKILL.md +0 -382
  224. package/project-template-v2/.claude/skills/playcraft-sprite-generation/SKILL.md +0 -423
  225. package/project-template-v2/.claude/skills/playcraft-sprite-remix/SKILL.md +0 -158
  226. package/project-template-v2/.claude/skills/playcraft-sprite-sheet/SKILL.md +0 -100
  227. package/project-template-v2/.claude/skills/playcraft-storyboard/SKILL.md +0 -167
  228. package/project-template-v2/.claude/skills/playcraft-style-qa/SKILL.md +0 -270
  229. package/project-template-v2/.claude/skills/playcraft-text-rendering/SKILL.md +0 -236
  230. package/project-template-v2/.claude/skills/playcraft-vfx-animation/SKILL.md +0 -130
  231. package/project-template-v2/.claude/skills/playwright-cli/SKILL.md +0 -390
  232. package/project-template-v2/.claude/skills/playwright-cli/references/element-attributes.md +0 -23
  233. package/project-template-v2/.claude/skills/playwright-cli/references/playwright-tests.md +0 -39
  234. package/project-template-v2/.claude/skills/playwright-cli/references/request-mocking.md +0 -87
  235. package/project-template-v2/.claude/skills/playwright-cli/references/running-code.md +0 -240
  236. package/project-template-v2/.claude/skills/playwright-cli/references/session-management.md +0 -226
  237. package/project-template-v2/.claude/skills/playwright-cli/references/spec-driven-testing.md +0 -308
  238. package/project-template-v2/.claude/skills/playwright-cli/references/storage-state.md +0 -275
  239. package/project-template-v2/.claude/skills/playwright-cli/references/test-generation.md +0 -134
  240. package/project-template-v2/.claude/skills/playwright-cli/references/tracing.md +0 -142
  241. package/project-template-v2/.claude/skills/playwright-cli/references/video-recording.md +0 -153
  242. package/project-template-v2/.claude/skills/session-analyzer/SKILL.md +0 -386
  243. package/project-template-v2/.claude/skills/session-analyzer/scripts/execution-breakdown.mjs +0 -182
  244. package/project-template-v2/.claude/skills/session-analyzer/scripts/find-turns.mjs +0 -72
  245. package/project-template-v2/.claude/skills/session-analyzer/scripts/heavy-output.mjs +0 -121
  246. package/project-template-v2/.claude/skills/session-analyzer/scripts/resolve-session.mjs +0 -102
  247. package/project-template-v2/.claude/skills/session-analyzer/scripts/subagent-stats.mjs +0 -127
  248. package/project-template-v2/.claude/skills/session-analyzer/scripts/subagent-tool-timeline.mjs +0 -106
  249. package/project-template-v2/.claude/skills/session-analyzer/scripts/time-gaps.mjs +0 -128
  250. package/project-template-v2/.claude/skills/session-analyzer/scripts/turn-timeline.mjs +0 -67
  251. package/project-template-v2/.claude/snapshot.mjs +0 -263
  252. package/project-template-v2/.playcraft/skills.lock.json +0 -152
  253. package/project-template-v2/CLAUDE.md +0 -146
  254. package/project-template-v2/assets/audio/bgm/.gitkeep +0 -0
  255. package/project-template-v2/assets/audio/sfx/.gitkeep +0 -0
  256. package/project-template-v2/assets/bundles/.gitkeep +0 -0
  257. package/project-template-v2/assets/images/bg/.gitkeep +0 -0
  258. package/project-template-v2/assets/images/reference/.gitkeep +0 -0
  259. package/project-template-v2/assets/images/storyboard/.gitkeep +0 -0
  260. package/project-template-v2/assets/images/tiles/.gitkeep +0 -0
  261. package/project-template-v2/assets/images/ui/.gitkeep +0 -0
  262. package/project-template-v2/assets/images/vfx/.gitkeep +0 -0
  263. package/project-template-v2/assets/models/.gitkeep +0 -0
  264. package/project-template-v2/docs/harness/iteration-1/context-flow.md +0 -254
  265. package/project-template-v2/docs/harness/iteration-1/generate-flow.md +0 -91
  266. package/project-template-v2/docs/harness/iteration-1/ideate-flow.md +0 -214
  267. package/project-template-v2/docs/harness/iteration-1/optimize-flow.md +0 -75
  268. package/project-template-v2/docs/harness/iteration-1/wrapup-flow.md +0 -63
  269. package/project-template-v2/docs/harness/iteration-2/context-flow.md +0 -223
  270. package/project-template-v2/docs/harness/iteration-2/generate-flow.md +0 -129
  271. package/project-template-v2/docs/harness/iteration-2/ideate-flow.md +0 -267
  272. package/project-template-v2/docs/harness/iteration-2/optimize-flow.md +0 -164
  273. package/project-template-v2/docs/harness/iteration-2/wrapup-flow.md +0 -115
  274. package/project-template-v2/docs/harness/orchestrator-flow.md +0 -364
  275. package/project-template-v2/docs/project-state.json +0 -60
  276. package/project-template-v2/docs/project-state.md +0 -72
  277. package/project-template-v2/docs/standards/README.md +0 -225
  278. package/project-template-v2/docs/standards/agent-behavior-standards.md +0 -174
  279. package/project-template-v2/docs/standards/artifacts/design-brief.md +0 -19
  280. package/project-template-v2/docs/standards/artifacts/design.md +0 -22
  281. package/project-template-v2/docs/standards/artifacts/game-code.md +0 -41
  282. package/project-template-v2/docs/standards/artifacts/todo-list.md +0 -41
  283. package/project-template-v2/docs/standards/iter1-agent-behavior-standards.md +0 -343
  284. package/project-template-v2/game/index.ts +0 -18
  285. package/project-template-v2/globals.d.ts +0 -51
  286. package/project-template-v2/index.css +0 -34
  287. package/project-template-v2/index.html +0 -18
  288. package/project-template-v2/main.ts +0 -9
  289. package/project-template-v2/package.json +0 -46
  290. package/project-template-v2/skills/_shared/scripts/dispatch-clear.mjs +0 -31
  291. package/project-template-v2/skills/_shared/scripts/dispatch-set.mjs +0 -86
  292. package/project-template-v2/skills/_shared/scripts/dod-check.mjs +0 -153
  293. package/project-template-v2/skills/_shared/scripts/handoff-append.mjs +0 -70
  294. package/project-template-v2/skills/_shared/scripts/lib/validator-artifacts.mjs +0 -91
  295. package/project-template-v2/skills/_shared/scripts/pipeline/dod-config.mjs +0 -131
  296. package/project-template-v2/skills/_shared/scripts/pipeline/index.mjs +0 -80
  297. package/project-template-v2/skills/_shared/scripts/pipeline/iteration-1-core.mjs +0 -90
  298. package/project-template-v2/skills/_shared/scripts/pipeline/iteration-2-wrap.mjs +0 -93
  299. package/project-template-v2/skills/_shared/scripts/pipeline/iteration-3-visual.mjs +0 -23
  300. package/project-template-v2/skills/_shared/scripts/render-project-state.mjs +0 -230
  301. package/project-template-v2/skills/_shared/scripts/state-advance-stage.mjs +0 -58
  302. package/project-template-v2/skills/_shared/scripts/state-get.mjs +0 -339
  303. package/project-template-v2/skills/_shared/scripts/state-handoff.mjs +0 -39
  304. package/project-template-v2/skills/_shared/scripts/state-set.mjs +0 -94
  305. package/project-template-v2/skills/_shared/scripts/state-store.mjs +0 -783
  306. package/project-template-v2/skills/_shared/scripts/todo-add.mjs +0 -88
  307. package/project-template-v2/skills/_shared/scripts/todo-get.mjs +0 -130
  308. package/project-template-v2/skills/_shared/scripts/todo-remove.mjs +0 -33
  309. package/project-template-v2/skills/_shared/scripts/todo-set.mjs +0 -47
  310. package/project-template-v2/skills/_shared/scripts/verify-asset-code-sync.mjs +0 -268
  311. package/project-template-v2/skills/_shared/scripts/verify-env.mjs +0 -285
  312. package/project-template-v2/skills/_shared/scripts/verify-placeholders.mjs +0 -161
  313. package/project-template-v2/skills/playable-autoplay/SKILL.md +0 -176
  314. package/project-template-v2/skills/playable-autoplay/agents/openai.yaml +0 -4
  315. package/project-template-v2/skills/playable-debug/SKILL.md +0 -116
  316. package/project-template-v2/skills/playable-debug/agents/openai.yaml +0 -4
  317. package/project-template-v2/skills/playable-debug/references/debug-config.md +0 -40
  318. package/project-template-v2/skills/playable-record/SKILL.md +0 -140
  319. package/project-template-v2/skills/playable-record/scripts/lib/dev-server.mjs +0 -104
  320. package/project-template-v2/skills/playable-record/scripts/lib/record-audio-bridge.js +0 -141
  321. package/project-template-v2/skills/playable-record/scripts/record-playable.mjs +0 -425
  322. package/project-template-v2/skills/playable-record/scripts/verify-contract.mjs +0 -261
  323. package/project-template-v2/skills/playable-record/scripts/verify-firstwin.mjs +0 -398
  324. package/project-template-v2/skills/playable-record/scripts/verify-fusion.mjs +0 -322
  325. package/project-template-v2/skills/playable-record/scripts/verify-lifecycle.mjs +0 -105
  326. package/project-template-v2/skills/playable-record/scripts/verify-vlm-video.mjs +0 -230
  327. package/project-template-v2/skills/playable-validate/SKILL.md +0 -61
  328. package/project-template-v2/skills/playable-validate/validation-rules.md +0 -14
  329. package/project-template-v2/skills/playable-verify-ui/SKILL.md +0 -63
  330. package/project-template-v2/skills/playable-verify-ui/scripts/lib/__init__.py +0 -1
  331. package/project-template-v2/skills/playable-verify-ui/scripts/lib/config_expr.js +0 -218
  332. package/project-template-v2/skills/playable-verify-ui/scripts/lib/node_utils.js +0 -51
  333. package/project-template-v2/skills/playable-verify-ui/scripts/lib/profile_loader.py +0 -59
  334. package/project-template-v2/skills/playable-verify-ui/scripts/lib/report.py +0 -106
  335. package/project-template-v2/skills/playable-verify-ui/scripts/lib/runtime_contract.js +0 -116
  336. package/project-template-v2/skills/playable-verify-ui/scripts/verify_fetch_antipatterns.py +0 -59
  337. package/project-template-v2/skills/playable-verify-ui/scripts/verify_hardcoded_layout.js +0 -64
  338. package/project-template-v2/skills/playable-verify-ui/scripts/verify_runtime_contract.js +0 -58
  339. package/project-template-v2/skills/playable-verify-ui/scripts/verify_text_overlap.py +0 -333
  340. package/project-template-v2/ta-workspace/scripts/.gitkeep +0 -0
  341. package/project-template-v2/tsconfig.json +0 -20
  342. package/project-template-v2/vite.config.ts +0 -27
  343. package/project-templates/batch-locks/phaser.yaml +0 -922
  344. package/project-templates/batch-locks/threejs.yaml +0 -978
  345. package/project-templates/empty/AGENTS.md +0 -39
  346. package/project-templates/empty/gitignore +0 -33
@@ -1,104 +0,0 @@
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
- }
@@ -1,63 +0,0 @@
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
- }
@@ -1,66 +0,0 @@
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
- }
@@ -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
- }