@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,81 +0,0 @@
1
- import { constants } from 'node:fs';
2
- import { lstat, mkdir, open, realpath } from 'node:fs/promises';
3
- import { dirname, join } from 'node:path';
4
- const ENTRY = '/.playcraft/video-replication/';
5
- /** Keep local analysis evidence out of a repository without changing its tracked ignore rules. */
6
- export async function ensureVideoEvidenceGitExcluded(directory) {
7
- let root = await realpath(directory);
8
- while (true) {
9
- const marker = await lstat(join(root, '.git')).catch((error) => {
10
- if (error.code === 'ENOENT')
11
- return undefined;
12
- throw error;
13
- });
14
- if (marker) {
15
- // Cloud workspaces own a real .git directory; never follow a redirected Git metadata tree.
16
- if (!marker.isDirectory() || marker.isSymbolicLink())
17
- return false;
18
- try {
19
- await appendExclude(root);
20
- return true;
21
- }
22
- catch (error) {
23
- if (['EACCES', 'EPERM', 'EROFS'].includes(error.code ?? ''))
24
- return false;
25
- throw error;
26
- }
27
- }
28
- const parent = dirname(root);
29
- if (parent === root)
30
- return true;
31
- root = parent;
32
- }
33
- }
34
- async function appendExclude(root) {
35
- const flags = constants.O_RDONLY | constants.O_DIRECTORY | constants.O_NOFOLLOW;
36
- const handles = [];
37
- let file;
38
- try {
39
- const directory = await open(root, flags);
40
- handles.push(directory);
41
- const pinned = (handle, fallback) => process.platform === 'linux' ? `/proc/self/fd/${handle.fd}` : fallback;
42
- const git = await open(join(pinned(directory, root), '.git'), flags);
43
- handles.push(git);
44
- const gitPath = pinned(git, join(root, '.git'));
45
- await mkdir(join(gitPath, 'info')).catch((error) => {
46
- if (error.code !== 'EEXIST')
47
- throw error;
48
- });
49
- const info = await open(join(gitPath, 'info'), flags);
50
- handles.push(info);
51
- const exclude = join(pinned(info, join(root, '.git/info')), 'exclude');
52
- const readable = await open(exclude, constants.O_RDONLY | constants.O_NOFOLLOW | constants.O_NONBLOCK)
53
- .catch((error) => { if (error.code === 'ENOENT')
54
- return undefined; throw error; });
55
- if (readable) {
56
- try {
57
- const stat = await readable.stat();
58
- if (!stat.isFile() || stat.nlink !== 1 || stat.size > 1024 * 1024)
59
- throw new Error('invalid_git_exclude');
60
- if ((await readable.readFile('utf8')).split('\n').some(line => line.trim() === ENTRY))
61
- return;
62
- }
63
- finally {
64
- await readable.close();
65
- }
66
- }
67
- file = await open(exclude, constants.O_RDWR | constants.O_CREAT | constants.O_APPEND | constants.O_NOFOLLOW | constants.O_NONBLOCK, 0o666);
68
- const stat = await file.stat();
69
- if (!stat.isFile() || stat.nlink !== 1 || stat.size > 1024 * 1024)
70
- throw new Error('invalid_git_exclude');
71
- const current = await file.readFile('utf8');
72
- if (!current.split('\n').some(line => line.trim() === ENTRY)) {
73
- await file.writeFile(`${current && !current.endsWith('\n') ? '\n' : ''}${ENTRY}\n`);
74
- }
75
- }
76
- finally {
77
- await file?.close();
78
- for (const handle of handles.reverse())
79
- await handle.close();
80
- }
81
- }
@@ -1,222 +0,0 @@
1
- import { randomUUID } from 'node:crypto';
2
- import { readFile, stat } from 'node:fs/promises';
3
- import { basename, dirname, join, resolve } from 'node:path';
4
- import { setTimeout as delay } from 'node:timers/promises';
5
- import { videoAnalysisRequestSchema, videoAnalysisRunSchema, videoMediaInitResultSchema, videoMediaResultSchema, videoModelCatalogSchema, videoUnderstandingAnalysisSchema, } from '@playcraft/common/agent-tools';
6
- import { VideoCliError, videoMessage } from './messages.js';
7
- import { openVideo, verifyVideo } from './media.js';
8
- import { loadSavedRequest, prepareArtifactDirectory, writeJsonAtomic, } from './evidence.js';
9
- const uuid = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
10
- export function requireVideoId(value) {
11
- if (!uuid.test(value))
12
- throw new VideoCliError('invalidArguments', 64);
13
- return value.toLowerCase();
14
- }
15
- export async function resolveVideoWorkspace(explicit, cwd = process.cwd()) {
16
- let context;
17
- for (let path = resolve(cwd);; path = dirname(path)) {
18
- try {
19
- const file = join(path, '.playcraft/workspace-context.json');
20
- if ((await stat(file)).size > 8192)
21
- throw new VideoCliError('workspaceMissing', 64);
22
- const value = JSON.parse(await readFile(file, 'utf8'));
23
- if (value.schemaVersion !== 'playcraft.workspace-context.v1' || typeof value.workspaceId !== 'string')
24
- throw new VideoCliError('workspaceMissing', 64);
25
- context = requireVideoId(value.workspaceId);
26
- break;
27
- }
28
- catch (error) {
29
- if (error.code !== 'ENOENT')
30
- throw error;
31
- }
32
- if (dirname(path) === path)
33
- break;
34
- }
35
- const workspace = explicit === undefined ? context : requireVideoId(explicit);
36
- if (!workspace)
37
- throw new VideoCliError('workspaceMissing', 64);
38
- if (context && explicit && context !== workspace)
39
- throw new VideoCliError('workspaceMismatch', 64);
40
- return workspace;
41
- }
42
- export function parseVideoPolicy(options) {
43
- const processing = options.processing ?? 'auto';
44
- if (!['auto', 'agentic', 'static'].includes(processing))
45
- throw new VideoCliError('invalidArguments', 64);
46
- const fps = options.fps === undefined ? undefined : Number(options.fps);
47
- if (fps !== undefined && ![1, 2, 4, 8].includes(fps))
48
- throw new VideoCliError('invalidArguments', 64);
49
- let clip;
50
- if (options.clip !== undefined) {
51
- if (!/^\d+(?:\.\d+)?:\d+(?:\.\d+)?$/.test(options.clip))
52
- throw new VideoCliError('invalidArguments', 64);
53
- const [startMs, endMs] = options.clip.split(':').map(value => Number(value) * 1000);
54
- if (!Number.isFinite(endMs) || endMs <= startMs)
55
- throw new VideoCliError('invalidArguments', 64);
56
- clip = { startMs, endMs };
57
- }
58
- if (processing === 'agentic' && (fps !== undefined || clip))
59
- throw new VideoCliError('invalidArguments', 64);
60
- return { processing: processing, ...(fps !== undefined && { fps: fps }), ...(clip && { clip }) };
61
- }
62
- export function videoTimeout(options) {
63
- const seconds = options.timeout === undefined ? 600 : Number(options.timeout);
64
- if (!Number.isFinite(seconds) || seconds <= 0 || seconds > 86_400)
65
- throw new VideoCliError('invalidArguments', 64);
66
- return seconds * 1000;
67
- }
68
- async function readContext(path, video) {
69
- if (!path)
70
- return undefined;
71
- try {
72
- if ((await stat(path)).size > 2 * 1024 * 1024)
73
- throw new Error('large');
74
- const analysis = videoUnderstandingAnalysisSchema.parse(JSON.parse(await readFile(path, 'utf8')));
75
- if (analysis.source.sha256 !== video.source.sha256)
76
- throw new Error('different video');
77
- return analysis;
78
- }
79
- catch {
80
- throw new VideoCliError('invalidContext', 64);
81
- }
82
- }
83
- export async function uploadVideo(video, workspaceId, deps, signal) {
84
- const mediaId = randomUUID();
85
- const init = videoMediaInitResultSchema.parse(await deps.client.post('/video-media/init', {
86
- workspaceId, mediaId, filename: basename(video.path), sha256: video.source.sha256,
87
- sizeBytes: video.source.sizeBytes, mimeType: video.source.mimeType, contentMd5: video.contentMd5,
88
- }, { signal }));
89
- if (init.mediaId !== mediaId || Date.parse(init.upload.expiresAt) <= Date.now())
90
- throw new VideoCliError('uploadFailed');
91
- const file = await openVideo(video.path);
92
- try {
93
- await verifyVideo(file, video.source);
94
- const stream = file.createReadStream({ start: 0, autoClose: false });
95
- if (init.upload.authenticated) {
96
- await deps.client.putVideoMedia(init.upload.url, stream, init.upload.headers, signal);
97
- }
98
- else {
99
- const url = new URL(init.upload.url);
100
- const allowed = new Set(['content-type', 'content-md5', 'content-length', 'x-cos-acl']);
101
- if (url.protocol !== 'https:' || url.username || url.password || url.hash || Object.entries(init.upload.headers).some(([key, value]) => !allowed.has(key.toLowerCase()) || (key.toLowerCase() === 'x-cos-acl' && value !== 'private')))
102
- throw new VideoCliError('uploadFailed');
103
- const response = await fetch(url, { method: 'PUT', redirect: 'error', signal, headers: init.upload.headers, body: stream, duplex: 'half' });
104
- if (!response.ok) {
105
- await response.body?.cancel();
106
- throw new VideoCliError('uploadFailed');
107
- }
108
- await response.body?.cancel();
109
- }
110
- await verifyVideo(file, video.source);
111
- }
112
- finally {
113
- await file.close();
114
- }
115
- const finalized = videoMediaResultSchema.parse(await deps.client.post('/video-media/finalize', { workspaceId, mediaId }, { signal }));
116
- if (finalized.mediaId !== mediaId || finalized.source.sha256 !== video.source.sha256 || finalized.source.sizeBytes !== video.source.sizeBytes)
117
- throw new VideoCliError('sourceChanged');
118
- return mediaId;
119
- }
120
- export async function readVideoRun(client, runId, workspaceId, signal) {
121
- const run = videoAnalysisRunSchema.parse(await client.get(`/video-analysis/${runId}`, { workspaceId }, { signal }));
122
- if (run.runId !== runId || run.workspaceId !== workspaceId || (run.analysis && run.analysis.runId !== runId))
123
- throw new VideoCliError('invalidResult');
124
- return run;
125
- }
126
- async function waitForVideoRun(run, deps, signal) {
127
- while (['pending', 'uploading', 'submitting', 'running'].includes(run.state)) {
128
- deps.progress('waiting');
129
- await delay(Math.max(250, Math.min(run.pollAfterMs, 10_000)), undefined, { signal });
130
- run = await readVideoRun(deps.client, run.runId, run.workspaceId, signal);
131
- }
132
- return run;
133
- }
134
- export async function executeVideoUnderstanding(options, signal, deps) {
135
- let runId;
136
- let out;
137
- let saved;
138
- let submitted = false;
139
- try {
140
- const workspaceId = await resolveVideoWorkspace(options.workspace);
141
- const policy = parseVideoPolicy(options);
142
- if (options.question !== undefined && (!options.question.trim() || options.question.length > 8000))
143
- throw new VideoCliError('invalidArguments', 64);
144
- if (options.resume && [options.video, options.question, options.context, options.processing, options.fps, options.clip, options.modelConfig].some(value => value !== undefined))
145
- throw new VideoCliError('invalidArguments', 64);
146
- if (!options.resume && !options.video)
147
- throw new VideoCliError('invalidArguments', 64);
148
- runId = options.resume ? requireVideoId(options.resume) : randomUUID();
149
- out = resolve(options.out ?? join('.playcraft/video-replication', runId));
150
- await prepareArtifactDirectory(out, runId, !!options.resume);
151
- let run;
152
- if (options.resume) {
153
- saved = await loadSavedRequest(out, runId, workspaceId);
154
- submitted = true;
155
- // Recovery only reads the existing task. No upload or POST can be triggered here.
156
- run = await readVideoRun(deps.client, runId, workspaceId, signal);
157
- if (saved.request && run.mediaId !== saved.request.mediaId)
158
- throw new VideoCliError('invalidResult');
159
- }
160
- else {
161
- deps.progress('probing');
162
- const video = await deps.probe(resolve(options.video), signal);
163
- if (policy.clip && policy.clip.endMs > video.source.durationMs)
164
- throw new VideoCliError('invalidArguments', 64);
165
- const context = await readContext(options.context, video);
166
- const catalog = videoModelCatalogSchema.parse(await deps.client.get('/video-models', { workspaceId }, { signal }));
167
- const configId = options.modelConfig ? requireVideoId(options.modelConfig) : catalog.defaultConfigId;
168
- const model = catalog.models.find(item => item.configId === configId);
169
- const mode = policy.processing === 'auto'
170
- ? policy.fps !== undefined || policy.clip || !model?.processing.includes('agentic') ? 'static' : 'agentic'
171
- : policy.processing;
172
- if (!model || !model.processing.includes(mode))
173
- throw new VideoCliError('modelUnavailable', 64);
174
- saved = { schemaVersion: 'playcraft.video-request.v1', runId, workspaceId, videoPath: video.path, out, state: 'uploading', createdAt: new Date().toISOString() };
175
- await writeJsonAtomic(join(out, 'request.json'), saved);
176
- deps.progress('uploading');
177
- const mediaId = await uploadVideo(video, workspaceId, deps, signal);
178
- const request = videoAnalysisRequestSchema.parse({
179
- workspaceId, runId, mediaId, modelConfigId: configId, ...policy,
180
- ...(options.question && { question: options.question }), ...(context && { context }),
181
- });
182
- saved = { ...saved, request, state: 'submitting' };
183
- await writeJsonAtomic(join(out, 'request.json'), saved);
184
- deps.progress('submitting');
185
- submitted = true;
186
- run = videoAnalysisRunSchema.parse(await deps.client.post('/video-analysis', request, {
187
- signal,
188
- onPrepared: async (operation) => {
189
- saved = { ...saved, requestId: operation.requestId };
190
- await writeJsonAtomic(join(out, 'request.json'), saved);
191
- },
192
- }));
193
- if (run.runId !== runId || run.workspaceId !== workspaceId || run.mediaId !== mediaId)
194
- throw new VideoCliError('invalidResult');
195
- }
196
- run = await waitForVideoRun(run, deps, signal);
197
- await writeJsonAtomic(join(out, 'task.json'), run);
198
- if (run.state === 'uncertain')
199
- throw new VideoCliError('uncertain', 75);
200
- if (run.state !== 'completed')
201
- return {
202
- exitCode: 4, envelope: { schemaVersion: 'playcraft.video-cli.v1', command: 'video understand', runId, state: run.state, ok: false, artifacts: { request: join(out, 'request.json') }, error: { code: run.error?.code ?? 'remoteFailed', message: videoMessage('remoteFailed') } },
203
- };
204
- if (!run.analysis || !saved || run.analysis.runId !== runId ||
205
- (saved.request?.modelConfigId && run.analysis.model.configId !== saved.request.modelConfigId))
206
- throw new VideoCliError('invalidResult');
207
- deps.progress('extracting');
208
- const artifacts = await deps.materialize({ analysis: run.analysis, sourcePath: saved.videoPath, out, request: saved.request, signal });
209
- await writeJsonAtomic(join(out, 'request.json'), { ...saved, state: 'completed' });
210
- return { exitCode: 0, envelope: { schemaVersion: 'playcraft.video-cli.v1', command: 'video understand', runId, state: 'completed', ok: true, artifacts } };
211
- }
212
- catch (error) {
213
- const timeout = signal.aborted && signal.reason?.name === 'TimeoutError';
214
- const interrupted = signal.aborted && !timeout;
215
- const known = error instanceof VideoCliError;
216
- const code = timeout ? 'timeoutReached' : interrupted ? 'interrupted' : known ? error.code : submitted ? 'uncertain' : 'failed';
217
- const exitCode = timeout ? 75 : interrupted ? 130 : known ? error.exitCode : submitted ? 75 : 4;
218
- if (saved && out && submitted && (exitCode === 75 || exitCode === 130))
219
- await writeJsonAtomic(join(out, 'request.json'), { ...saved, state: 'uncertain' }).catch(() => { });
220
- return { exitCode, envelope: { schemaVersion: 'playcraft.video-cli.v1', command: 'video understand', runId, state: exitCode === 75 || exitCode === 130 ? 'uncertain' : 'failed', ok: false, ...(saved && out && { artifacts: { request: join(out, 'request.json') } }), error: { code, message: videoMessage(code), ...(interrupted && { remoteCancellationConfirmed: false }) } } };
221
- }
222
- }
@@ -1,149 +0,0 @@
1
- import { readFile, writeFile } from 'node:fs/promises';
2
- import { join } from 'node:path';
3
- import sharp from 'sharp';
4
- import { replicaValidationResultSchema, validateReplicaComparisonContent, } from '@playcraft/common/agent-tools';
5
- import { openVideo, runMediaTool, verifyVideo } from './media.js';
6
- import { comparisonOutputPath, writeComparisonArtifact, writeComparisonJson } from './comparison-output.js';
7
- import { comparisonMessage as t, ComparisonCliError } from './comparison-messages.js';
8
- import { comparisonSha256 } from './comparison-files.js';
9
- const xml = (text) => text.replace(/[&<>"']/g, value => ({ '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&apos;' })[value]);
10
- const md = (text) => text.replace(/([\\`*_[\]<>])/g, '\\$1');
11
- const reasonMessages = {
12
- 'video.eventPairMissing': 'reasonEventPairMissing', 'video.dimensionUncertain': 'reasonDimensionUncertain',
13
- 'video.dimensionEventMissing': 'reasonDimensionEventMissing', 'video.progressionEvidenceMissing': 'reasonProgressionEvidenceMissing',
14
- 'video.timingPrecisionInsufficient': 'reasonTimingPrecisionInsufficient', 'video.timingFailureUnverified': 'reasonTimingFailureUnverified',
15
- 'video.requiredEffectsMissing': 'reasonRequiredEffectsMissing', 'video.controlRuntimeUncertain': 'reasonControlRuntimeUncertain',
16
- 'video.controlEvidenceMissing': 'reasonControlEvidenceMissing', 'video.requestsFailed': 'reasonRequestsFailed',
17
- };
18
- const renderReason = (reason) => {
19
- const key = reason.startsWith('video.referenceKindMissing:') ? 'reasonReferenceKindMissing'
20
- : Object.prototype.hasOwnProperty.call(reasonMessages, reason) ? reasonMessages[reason] : undefined;
21
- return md(key ? `${t(key)} (${reason})` : reason);
22
- };
23
- async function tile(frame, out) {
24
- const image = await sharp(join(out, frame.path)).resize(260, 190, { fit: 'contain', background: '#f4f4f4' }).png().toBuffer();
25
- const label = Buffer.from(`<svg width="260" height="34"><rect width="100%" height="100%" fill="white"/><text x="8" y="14" font-size="11">${xml(frame.inputId)} · ${xml(frame.evidenceId)}</text><text x="8" y="29" font-size="11">${(frame.timeMs / 1000).toFixed(3)}s</text></svg>`);
26
- return sharp({ create: { width: 260, height: 224, channels: 3, background: '#fff' } }).composite([{ input: image, top: 0, left: 0 }, { input: label, top: 190, left: 0 }]).png().toBuffer();
27
- }
28
- export function renderComparisonReport(comparison, frames) {
29
- const frameMap = new Map(frames.map(frame => [frame.evidenceId, frame]));
30
- const refs = (ids) => ids.map(id => `[${md(id)}](${frameMap.get(id)?.path})`).join(', ');
31
- const list = (values) => values.length ? values.map(md).join(', ') : t('reportNone');
32
- const timingStatus = comparison.timingChecks.some(check => check.status === 'fail') ? 'fail'
33
- : !comparison.timingChecks.length || comparison.timingChecks.some(check => check.status === 'inconclusive') ? 'inconclusive' : 'pass';
34
- const lines = [`# ${t('summary')}: ${comparison.status}`, '', t('coverageNotice'), '',
35
- `## ${t('decision')}`, '', `- ${t('reportStatus')}: **${comparison.status}**`, `- ${t('reportScope')}: ${comparison.scope}`,
36
- `- ${t('reportPolicy')}: ${comparison.policySha256}`, `- ${t('reportRecording')}: ${comparison.recordingHash}`, ''];
37
- lines.push(t(comparison.status === 'pass' ? 'policyPassedNotice' : 'policyNotPassedNotice'), '', `## ${t('coverage')}`, '', t('deterministicNotice'), '', `- ${t('reportRequired')}: ${list(comparison.coverage.requiredEventIds)}`, `- ${t('reportEvaluated')}: ${list(comparison.coverage.evaluatedEventIds)}`, `- ${t('reportMissing')}: ${list(comparison.coverage.missingEventIds)}`, `- ${t('reportRequiredKinds')}: ${list(comparison.coverage.requiredKinds)}`, `- ${t('reportMissingKinds')}: ${list(comparison.coverage.missingKinds)}`, '', `### ${t('reportMissingEvidence')}`, '');
38
- if (comparison.missingEvidence.some(item => item.dimension === 'interaction'))
39
- lines.push(t('interactionUnverified'), '');
40
- for (const item of comparison.missingEvidence)
41
- lines.push(`- ${item.eventId ? `${md(item.eventId)}: ` : ''}${item.dimension ? `${item.dimension}: ` : ''}${renderReason(item.reason)}`);
42
- if (!comparison.missingEvidence.length)
43
- lines.push(`- ${t('reportNone')}`);
44
- lines.push('', `### ${t('reportTimingChecks')}: ${timingStatus}`, '');
45
- if (timingStatus === 'inconclusive')
46
- lines.push(t('timingUnverified'), '');
47
- for (const check of comparison.timingChecks)
48
- lines.push(`- ${t('reportTiming')} ${md(check.eventId)} (${check.measurement}): ${check.status}; ${t('reportReference')} ${check.referenceRangeMs.join('–')} ms; ${t('reportCandidate')} ${check.candidateRangeMs.join('–')} ms; ${t('reportTolerance')} ${check.toleranceMs} ms`);
49
- lines.push('', `## ${t('modelObservations')}`, '', t('modelObservationNotice'), '', `- ${t('reportModel')}: ${comparison.model.provider}/${comparison.model.model} (${comparison.model.processing})`, '', `### ${t('modelSummary')}`, '', md(comparison.summary), '', `### ${t('modelDimensions')}`, '');
50
- for (const [dimension, judgment] of Object.entries(comparison.dimensions))
51
- lines.push(`- **${dimension}: ${judgment.status}** (${judgment.confidence}) — ${md(judgment.reason)} ${refs(judgment.evidenceIds)}`);
52
- lines.push('', `## ${t('differences')}`, '');
53
- for (const finding of comparison.findings)
54
- lines.push(`### ${md(finding.id)} · ${finding.severity} · ${finding.dimension}`, '', `- ${t('reportExpected')}: ${md(finding.expected)}`, `- ${t('reportObserved')}: ${md(finding.observed)}`, `- ${t('reportSuggestedFix')}: ${md(finding.suggestedFix)}`, `- ${t('reportConfidence')}: ${finding.confidence}; ${t('reportEvidence')}: ${refs(finding.evidenceIds)}`, '');
55
- lines.push(`## ${t('alignment')}`, '');
56
- for (const alignment of comparison.alignments) {
57
- const reference = frameMap.get(alignment.referenceEvidenceId);
58
- const candidate = frameMap.get(alignment.candidateEvidenceId);
59
- lines.push(`- **${md(alignment.eventId)}** (${alignment.confidence}): ${t('reportReference')} ${reference?.timeMs} ms ↔ ${t('reportCandidate')} ${candidate?.timeMs} ms; ${refs([alignment.referenceEvidenceId, alignment.candidateEvidenceId])}`);
60
- }
61
- lines.push('', `## ${t('controls')}`, '');
62
- for (const check of comparison.controlChecks)
63
- lines.push(`- ${md(check.stepId)} → ${md(check.eventId)}; ${md(check.controlInputId)}: ${check.effect} (${check.confidence}); ${refs([check.candidateEvidenceId, check.controlEvidenceId])}`);
64
- lines.push('', `## ${t('evidence')}`, '');
65
- for (const frame of frames)
66
- lines.push(`- ${md(frame.evidenceId)} · ${md(frame.inputId)}: ${t('reportRequested')} ${frame.requestedTimeMs} ms, ${t('reportDecoded')} ${frame.timeMs} ms; [${t('reportFrame')}](${frame.path})`);
67
- return `${lines.join('\n')}\n`;
68
- }
69
- /** Extract real source frames for each validated input; model output never supplies local paths. */
70
- export async function materializeComparisonEvidence(input) {
71
- let comparison;
72
- try {
73
- comparison = replicaValidationResultSchema.parse(input.comparison);
74
- validateReplicaComparisonContent({ summary: comparison.summary, evidence: comparison.evidence, alignments: comparison.alignments,
75
- dimensions: comparison.dimensions, findings: comparison.findings, controlChecks: comparison.controlChecks, timings: comparison.timings, missingEvidence: comparison.missingEvidence }, { inputs: comparison.inputs, analysis: input.request.analysis, recording: input.request.recording, policy: input.request.policy, processing: input.request.processing, fps: input.request.fps });
76
- }
77
- catch {
78
- throw new ComparisonCliError('comparisonResult');
79
- }
80
- // Validate every destination before replacing any prior artifact.
81
- for (const path of [...comparison.evidence.map(item => `keyframes/${item.inputId}/${item.id}.png`), 'comparison.json', 'comparison.md', 'manifest.json', 'contact-sheet.jpg', 'paired-contact-sheet.jpg'])
82
- await comparisonOutputPath(input.out, path);
83
- const frames = [];
84
- for (const source of comparison.inputs) {
85
- const local = input.inputs.find(item => item.id === source.id && item.role === source.role);
86
- if (!local)
87
- throw new ComparisonCliError('comparisonResult');
88
- const evidence = comparison.evidence.filter(item => item.inputId === source.id);
89
- if (!evidence.length)
90
- continue;
91
- const file = await openVideo(local.video.path);
92
- try {
93
- await verifyVideo(file, source.source);
94
- const output = await runMediaTool('ffprobe', ['-v', 'error', '-protocol_whitelist', 'file,pipe', '-format_whitelist', 'mov,matroska,webm',
95
- '-select_streams', 'v:0', '-show_entries', 'frame=best_effort_timestamp_time', '-of', 'csv=p=0', '/dev/fd/3'], file.fd, input.signal);
96
- const raw = output.split('\n').map(line => Number.parseFloat(line) * 1000).filter(Number.isFinite);
97
- const origin = raw[0] ?? 0;
98
- const times = raw.map(time => time - origin).filter(time => time >= 0 && time < source.source.durationMs);
99
- if (!times.length)
100
- throw new ComparisonCliError('comparisonResult');
101
- for (const item of evidence) {
102
- input.signal?.throwIfAborted();
103
- const requestedTimeMs = (item.startMs + item.endMs) / 2;
104
- const timeMs = times.reduce((best, time) => Math.abs(time - requestedTimeMs) < Math.abs(best - requestedTimeMs) ? time : best);
105
- const path = `keyframes/${source.id}/${item.id}.png`;
106
- await writeComparisonArtifact(input.out, path, temporary => runMediaTool('ffmpeg', ['-v', 'error', '-nostdin', '-y', '-protocol_whitelist', 'file,pipe', '-format_whitelist', 'mov,matroska,webm',
107
- '-ss', (Math.max(0, timeMs - 0.01) / 1000).toFixed(6), '-i', '/dev/fd/3', '-map', '0:v:0', '-frames:v', '1',
108
- '-vf', 'scale=min(1280\\,iw):-2', '-threads', '1', temporary], file.fd, input.signal));
109
- const bytes = await readFile(join(input.out, path));
110
- const metadata = await sharp(bytes).metadata();
111
- if (!metadata.width || !metadata.height)
112
- throw new ComparisonCliError('comparisonResult');
113
- frames.push({ evidenceId: item.id, inputId: source.id, requestedTimeMs, timeMs, path, sha256: comparisonSha256(bytes) });
114
- }
115
- await verifyVideo(file, source.source);
116
- }
117
- finally {
118
- await file.close();
119
- }
120
- }
121
- if (frames.length !== comparison.evidence.length)
122
- throw new ComparisonCliError('comparisonResult');
123
- const tiles = await Promise.all(frames.map(frame => tile(frame, input.out)));
124
- const columns = Math.min(4, tiles.length);
125
- const contactSheet = join(input.out, 'contact-sheet.jpg');
126
- await writeComparisonArtifact(input.out, 'contact-sheet.jpg', temporary => sharp({ create: { width: columns * 260, height: Math.ceil(tiles.length / columns) * 224, channels: 3, background: '#ddd' } })
127
- .composite(tiles.map((value, index) => ({ input: value, left: index % columns * 260, top: Math.floor(index / columns) * 224 }))).jpeg({ quality: 85 }).toFile(temporary));
128
- const pairs = comparison.alignments.flatMap(alignment => [alignment.referenceEvidenceId, alignment.candidateEvidenceId]);
129
- const selected = pairs.length ? pairs.map(id => tiles[frames.findIndex(frame => frame.evidenceId === id)])
130
- : ['reference', 'candidate'].flatMap(inputId => { const index = frames.findIndex(frame => frame.inputId === inputId); return index >= 0 ? [tiles[index]] : []; });
131
- if (!selected.length)
132
- throw new ComparisonCliError('comparisonResult');
133
- const pairedContactSheet = join(input.out, 'paired-contact-sheet.jpg');
134
- await writeComparisonArtifact(input.out, 'paired-contact-sheet.jpg', temporary => sharp({ create: { width: 520, height: Math.ceil(selected.length / 2) * 224, channels: 3, background: '#ddd' } })
135
- .composite(selected.map((value, index) => ({ input: value, left: index % 2 * 260, top: Math.floor(index / 2) * 224 }))).jpeg({ quality: 85 }).toFile(temporary));
136
- const resultPath = join(input.out, 'comparison.json');
137
- const markdown = join(input.out, 'comparison.md');
138
- await writeComparisonJson(input.out, 'comparison.json', comparison);
139
- await writeComparisonArtifact(input.out, 'comparison.md', temporary => writeFile(temporary, renderComparisonReport(comparison, frames), { mode: 0o600, flag: 'wx' }));
140
- const files = [];
141
- for (const path of ['comparison.json', 'comparison.md', 'contact-sheet.jpg', 'paired-contact-sheet.jpg'])
142
- files.push({ path, sha256: comparisonSha256(await readFile(join(input.out, path))) });
143
- const manifest = join(input.out, 'manifest.json');
144
- await writeComparisonJson(input.out, 'manifest.json', { schemaVersion: 'playcraft.replica-evidence.v1', runId: comparison.runId, state: 'completed', status: comparison.status,
145
- recordingHash: comparison.recordingHash, policySha256: comparison.policySha256, model: comparison.model, inputs: comparison.inputs,
146
- comparison: 'comparison.json', markdown: 'comparison.md', contactSheet: 'contact-sheet.jpg', pairedContactSheet: 'paired-contact-sheet.jpg',
147
- files, frames, frameTimeSemantics: 'nearest-decoded-original-video-frame', audio: 'not_evaluated' });
148
- return { manifest, comparison: resultPath, markdown, contactSheet, pairedContactSheet, keyframes: frames.map(frame => join(input.out, frame.path)) };
149
- }
@@ -1,57 +0,0 @@
1
- import { createHash } from 'node:crypto';
2
- import { lstat, open, realpath } from 'node:fs/promises';
3
- import { isAbsolute, relative, resolve, sep } from 'node:path';
4
- import { ComparisonCliError } from './comparison-messages.js';
5
- export function comparisonSha256(value) {
6
- return createHash('sha256').update(value).digest('hex');
7
- }
8
- export async function readComparisonJson(path, maxBytes = 2 * 1024 * 1024) {
9
- const file = await open(path, 'r');
10
- try {
11
- const stat = await file.stat();
12
- if (!stat.isFile() || stat.size <= 0 || stat.size > maxBytes)
13
- throw new ComparisonCliError('invalidRecording', 64);
14
- return JSON.parse(await file.readFile('utf8'));
15
- }
16
- finally {
17
- await file.close();
18
- }
19
- }
20
- /** Only recorder-owned, regular files under the exact recording directory may become evidence. */
21
- export async function recordingArtifactPath(root, path) {
22
- if (!path || isAbsolute(path) || path.includes('\\') || path.split('/').some(part => !part || part === '.' || part === '..'))
23
- throw new ComparisonCliError('invalidRecording', 64);
24
- const base = await realpath(root);
25
- const target = resolve(base, path);
26
- const local = relative(base, target);
27
- if (!local || local === '..' || local.startsWith(`..${sep}`) || isAbsolute(local))
28
- throw new ComparisonCliError('invalidRecording', 64);
29
- let current = base;
30
- for (const part of local.split(sep)) {
31
- current = resolve(current, part);
32
- const stat = await lstat(current);
33
- if (stat.isSymbolicLink())
34
- throw new ComparisonCliError('invalidRecording', 64);
35
- }
36
- if (await realpath(target) !== target || !(await lstat(target)).isFile())
37
- throw new ComparisonCliError('invalidRecording', 64);
38
- return target;
39
- }
40
- export async function verifyRecordingArtifact(root, artifact, maxBytes = 8 * 1024 * 1024) {
41
- const path = await recordingArtifactPath(root, artifact.path);
42
- const file = await open(path, 'r');
43
- try {
44
- const stat = await file.stat();
45
- if (!stat.isFile() || stat.size > maxBytes)
46
- throw new ComparisonCliError('invalidRecording', 64);
47
- const hash = createHash('sha256');
48
- for await (const chunk of file.createReadStream({ autoClose: false, start: 0 }))
49
- hash.update(chunk);
50
- if (hash.digest('hex') !== artifact.sha256)
51
- throw new ComparisonCliError('invalidRecording', 64);
52
- }
53
- finally {
54
- await file.close();
55
- }
56
- return path;
57
- }
@@ -1,72 +0,0 @@
1
- import { canonicalReplicaJson } from '@playcraft/common/agent-tools';
2
- import { ComparisonCliError } from './comparison-messages.js';
3
- /** Completed capture and every real/omitted scenario step must be represented in the hashed log. */
4
- export function validateComparisonInputLog(records, scenario, artifact) {
5
- const invalid = () => { throw new ComparisonCliError('invalidRecording', 64); };
6
- const finiteTime = (value) => typeof value === 'number' && Number.isFinite(value) && value >= 0;
7
- if (records.some(value => value === null || typeof value !== 'object' || Array.isArray(value)))
8
- invalid();
9
- const log = records;
10
- const starts = log.filter(record => record.kind === 'recording-start'), ends = log.filter(record => record.kind === 'recording-end');
11
- if (starts.length !== 1 || ends.length !== 1 || log[0] !== starts[0] || log.at(-1) !== ends[0])
12
- invalid();
13
- const start = starts[0], end = ends[0];
14
- if (start.plannedAtMs !== 0 || start.actualAtMs !== 0 || start.device !== scenario.pointer || canonicalReplicaJson(start.reset) !== canonicalReplicaJson(scenario.reset)
15
- || end.plannedAtMs !== scenario.durationMs || !finiteTime(end.actualAtMs) || end.actualAtMs < scenario.durationMs || end.status !== 'completed')
16
- invalid();
17
- if (log.some(record => record.status === 'failed' || !finiteTime(record.actualAtMs) || !finiteTime(record.plannedAtMs)))
18
- invalid();
19
- const finals = log.filter(record => ['tap', 'drag'].includes(String(record.kind)) && ['dispatched', 'omitted'].includes(String(record.status)));
20
- if (finals.length !== scenario.steps.length)
21
- invalid();
22
- let previousEnd = -1;
23
- for (const step of scenario.steps) {
24
- const matching = finals.filter(record => record.stepId === step.id);
25
- if (matching.length !== 1)
26
- invalid();
27
- const record = matching[0], omitted = artifact.omittedStepIds.includes(step.id);
28
- if (record.kind !== step.type || record.plannedAtMs !== step.atMs || record.device !== scenario.pointer || record.status !== (omitted ? 'omitted' : 'dispatched')
29
- || !finiteTime(record.actualAtMs) || record.actualAtMs + 2 < step.atMs || record.actualAtMs < previousEnd)
30
- invalid();
31
- const expected = (step.type === 'tap' ? [{ offsetMs: 0, x: step.x, y: step.y }] : step.path).map(point => ({ ...point, actual: {
32
- x: (scenario.contentRect.x + point.x * scenario.contentRect.width) * scenario.viewport.width,
33
- y: (scenario.contentRect.y + point.y * scenario.contentRect.height) * scenario.viewport.height,
34
- } }));
35
- if (canonicalReplicaJson(record.points) !== canonicalReplicaJson(expected))
36
- invalid();
37
- if (!omitted) {
38
- if (!finiteTime(record.completedAtMs) || record.completedAtMs < record.actualAtMs
39
- || record.completedAtMs > artifact.video.source.durationMs + artifact.timing.uncertaintyMs)
40
- invalid();
41
- const pointers = log.filter(pointer => pointer.stepId === step.id && pointer.kind === 'pointer' && pointer.status === 'dispatched');
42
- if (step.type === 'tap' ? !pointers.some(pointer => pointer.phase === 'tap') : !['start', 'move', 'end'].every(phase => pointers.some(pointer => pointer.phase === phase)))
43
- invalid();
44
- previousEnd = record.completedAtMs;
45
- }
46
- else
47
- previousEnd = record.actualAtMs;
48
- }
49
- }
50
- /** Runtime counters must agree with the hashed console and browser event evidence. */
51
- export function validateComparisonConsoleLog(records, artifact) {
52
- const counts = { pageErrors: 0, consoleErrors: 0, failedRequests: 0 };
53
- for (const value of records) {
54
- if (value === null || typeof value !== 'object' || Array.isArray(value))
55
- throw new ComparisonCliError('invalidRecording', 64);
56
- const record = value;
57
- if (typeof record.atMs !== 'number' || !Number.isFinite(record.atMs) || record.atMs < 0)
58
- throw new ComparisonCliError('invalidRecording', 64);
59
- if (record.kind === 'console') {
60
- if (record.level === 'error')
61
- counts.consoleErrors++;
62
- }
63
- else if (['pageerror', 'popup', 'dialog'].includes(String(record.kind)))
64
- counts.pageErrors++;
65
- else if (record.kind === 'requestfailed')
66
- counts.failedRequests++;
67
- else
68
- throw new ComparisonCliError('invalidRecording', 64);
69
- }
70
- if (canonicalReplicaJson(counts) !== canonicalReplicaJson(artifact.runtime))
71
- throw new ComparisonCliError('invalidRecording', 64);
72
- }