@pixel-point/toolcraft 0.0.18 → 0.0.19

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 (384) hide show
  1. package/package.json +1 -1
  2. package/src/generate-demand-only-lifecycle.test.mjs +9 -1
  3. package/src/generate-performance-iteration-execution.test.mjs +3 -3
  4. package/src/generate-test-delivery-architecture-assertions.mjs +11 -73
  5. package/src/generate-test-demand-only-lifecycle.mjs +34 -36
  6. package/src/generate-test-dependency-sandbox.mjs +4 -1
  7. package/src/generate-test-doc-assertions.mjs +55 -53
  8. package/src/generate-test-doc-assertions.test.mjs +1 -1
  9. package/src/generate-test-doc-policy-assertions.mjs +1 -1
  10. package/src/generate-test-export-doc-assertions.mjs +53 -0
  11. package/src/generate-test-file-assertions.mjs +51 -8
  12. package/src/generate-test-integrity-assertions.mjs +5 -2
  13. package/src/generate-test-integrity-manifest-assertions.mjs +16 -5
  14. package/src/generate-test-integrity-ownership-assertions.mjs +36 -4
  15. package/src/generate-test-package-assertions.mjs +4 -0
  16. package/src/generate-test-product-fixture.mjs +1 -1
  17. package/src/generate-test-product-fixture.test.mjs +35 -38
  18. package/src/generate-test-product-iteration.mjs +0 -1
  19. package/src/generate-test-product-iteration.test.mjs +0 -1
  20. package/src/generate-test-runtime-boundary-assertions.mjs +20 -0
  21. package/src/generate-test-standalone-assertions.mjs +0 -16
  22. package/src/generate-test-standalone-delivery-stages.mjs +2 -86
  23. package/src/generate.test.mjs +4 -0
  24. package/src/generated-integrity-manifest.test.mjs +5 -2
  25. package/src/generated-source-policy.test.mjs +0 -4
  26. package/src/package-json.mjs +5 -0
  27. package/src/package-json.test.mjs +21 -0
  28. package/src/package-manager.mjs +2 -0
  29. package/src/package-manager.test.mjs +8 -0
  30. package/src/packaged-template-fallback-helpers.mjs +254 -0
  31. package/src/packaged-template-fallback.test.mjs +121 -46
  32. package/src/packaged-template-runtime-assertions.mjs +150 -0
  33. package/templates/runtime/contracts/component-contracts.choices.test.ts +18 -3
  34. package/templates/runtime/contracts/component-contracts.choices.ts +8 -3
  35. package/templates/runtime/contracts/component-contracts.inputs.ts +2 -1
  36. package/templates/runtime/contracts/component-contracts.runtime-acceptance.test.ts +18 -4
  37. package/templates/runtime/contracts/component-contracts.runtime-shell.test.ts +1 -1
  38. package/templates/runtime/contracts/component-contracts.runtime.ts +7 -4
  39. package/templates/runtime/contracts/component-contracts.visual.test.ts +20 -0
  40. package/templates/runtime/contracts/component-contracts.visual.ts +4 -0
  41. package/templates/runtime/contracts/decision-contracts.test.ts +18 -12
  42. package/templates/runtime/contracts/decision-contracts.ts +4 -4
  43. package/templates/runtime/contracts/performance-verification-policy.test.ts +12 -12
  44. package/templates/runtime/contracts/performance-verification-policy.ts +8 -8
  45. package/templates/runtime/export/artifact-download.ts +1 -0
  46. package/templates/runtime/export/artifact-frame-renderer.ts +2 -15
  47. package/templates/runtime/export/artifact-frame-state.ts +16 -0
  48. package/templates/runtime/export/artifact-scene-frame.test.ts +3 -3
  49. package/templates/runtime/export/artifact-scene-frame.ts +3 -3
  50. package/templates/runtime/export/export-error.ts +5 -0
  51. package/templates/runtime/export/export-renderer-coverage.ts +54 -0
  52. package/templates/runtime/export/image-artifact-export.ts +2 -2
  53. package/templates/runtime/export/index.ts +13 -0
  54. package/templates/runtime/export/product-export-renderer.test.ts +38 -4
  55. package/templates/runtime/export/product-export-renderer.ts +0 -41
  56. package/templates/runtime/export/product-svg-export-renderer.ts +21 -0
  57. package/templates/runtime/export/svg-artifact-export.test.ts +98 -0
  58. package/templates/runtime/export/svg-artifact-export.ts +120 -0
  59. package/templates/runtime/export/svg-document.test.ts +221 -0
  60. package/templates/runtime/export/svg-document.ts +400 -0
  61. package/templates/runtime/export/svg-policy.ts +47 -0
  62. package/templates/runtime/index.ts +1 -0
  63. package/templates/runtime/react/app-shell/runtime-public-api.test.tsx +10 -0
  64. package/templates/runtime/react/app-shell/settings-transfer.test.ts +21 -0
  65. package/templates/runtime/react/app-shell/toolcraft-app.scene-export.test.tsx +46 -1
  66. package/templates/runtime/react/app-shell/toolcraft-app.tsx +7 -2
  67. package/templates/runtime/react/canvas/canvas-shell-interactions.test.tsx +30 -0
  68. package/templates/runtime/react/canvas/canvas-shell-pinch-interactions.test.tsx +155 -0
  69. package/templates/runtime/react/canvas/canvas-shell-rendering.test.tsx +1 -1
  70. package/templates/runtime/react/canvas/canvas-shell.tsx +16 -5
  71. package/templates/runtime/react/canvas/canvas-viewport-geometry.ts +133 -0
  72. package/templates/runtime/react/canvas/use-canvas-viewport-interactions.ts +285 -36
  73. package/templates/runtime/react/controls-panel/__tests__/controls-panel.color-applicability-layout.test.tsx +134 -0
  74. package/templates/runtime/react/controls-panel/__tests__/controls-panel.color-layout.test.tsx +16 -1
  75. package/templates/runtime/react/controls-panel/__tests__/controls-panel.render-isolation.test.tsx +2 -0
  76. package/templates/runtime/react/controls-panel/__tests__/controls-panel.scene-export-actions.test.tsx +39 -0
  77. package/templates/runtime/react/controls-panel/__tests__/controls-panel.setup-controls.test.tsx +3 -3
  78. package/templates/runtime/react/controls-panel/__tests__/controls-panel.source-collection.test.tsx +1 -4
  79. package/templates/runtime/react/controls-panel/__tests__/controls-panel.source-color-bank.test.tsx +2 -2
  80. package/templates/runtime/react/controls-panel/__tests__/controls-panel.vector-interaction.test.tsx +6 -6
  81. package/templates/runtime/react/controls-panel/actions/controls-panel-actions.ts +2 -2
  82. package/templates/runtime/react/controls-panel/actions/export-action-runner.ts +40 -11
  83. package/templates/runtime/react/controls-panel/layout/controls-panel-layout.tsx +31 -2
  84. package/templates/runtime/react/controls-panel/layout/controls-panel-section.tsx +4 -1
  85. package/templates/runtime/react/controls-panel/renderers/controls-panel-action-renderer.tsx +2 -1
  86. package/templates/runtime/react/controls-panel/renderers/controls-panel-basic-renderers.tsx +2 -52
  87. package/templates/runtime/react/controls-panel/values/controls-panel-color-values.ts +1 -9
  88. package/templates/runtime/react/controls-panel/values/controls-panel-spatial-values.ts +7 -2
  89. package/templates/runtime/react/orientation-gizmo/orientation-gizmo-math.ts +9 -69
  90. package/templates/runtime/schema/artifact-export-actions.test.ts +47 -0
  91. package/templates/runtime/schema/artifact-export-actions.ts +49 -0
  92. package/templates/runtime/schema/types.ts +1 -0
  93. package/templates/runtime/state/canvas-background-state.ts +1 -16
  94. package/templates/runtime/state/canvas-state.ts +32 -20
  95. package/templates/runtime/state/control-value-codecs.test.ts +98 -0
  96. package/templates/runtime/state/control-value-codecs.ts +700 -0
  97. package/templates/runtime/state/control-value-normalization.test.ts +1 -1
  98. package/templates/runtime/state/control-value-normalization.ts +127 -135
  99. package/templates/runtime/state/controls-reducer.ts +26 -3
  100. package/templates/runtime/state/create-template-state.test.ts +30 -0
  101. package/templates/runtime/state/create-template-state.ts +52 -31
  102. package/templates/runtime/state/orientation-pose.ts +80 -0
  103. package/templates/runtime/state/persistence-control-values.test.ts +59 -0
  104. package/templates/runtime/state/reducer-controls.test.ts +45 -0
  105. package/templates/runtime/state/reducer-media-reset.test.ts +1 -1
  106. package/templates/runtime/state/reducer-timeline.test.ts +70 -0
  107. package/templates/runtime/state/timeline-reducer.ts +40 -2
  108. package/templates/runtime/testing/performance-schema-queries.ts +2 -2
  109. package/templates/starter/AGENTS.md +21 -22
  110. package/templates/starter/docs/toolcraft/README.md +6 -6
  111. package/templates/starter/docs/toolcraft/acceptance-testing.md +20 -20
  112. package/templates/starter/docs/toolcraft/agent-worklog.md +88 -1
  113. package/templates/starter/docs/toolcraft/assembly-workflow.md +7 -6
  114. package/templates/starter/docs/toolcraft/component-rules.md +2 -2
  115. package/templates/starter/docs/toolcraft/core/layout.md +6 -1
  116. package/templates/starter/docs/toolcraft/core/media-upload.md +1 -1
  117. package/templates/starter/docs/toolcraft/core/performance.md +8 -10
  118. package/templates/starter/docs/toolcraft/core/reference-study.md +80 -19
  119. package/templates/starter/docs/toolcraft/core/runtime-boundary.md +2 -1
  120. package/templates/starter/docs/toolcraft/core/setup-export.md +43 -13
  121. package/templates/starter/docs/toolcraft/decision-contract.md +2 -2
  122. package/templates/starter/docs/toolcraft/performance.md +8 -10
  123. package/templates/starter/docs/toolcraft/renderer-technique.md +2 -2
  124. package/templates/starter/docs/toolcraft/schema-reference.md +8 -8
  125. package/templates/starter/docs/toolcraft/workflow.md +14 -14
  126. package/templates/starter/e2e/app-browser-infinity-canvas-evidence.spec.ts +38 -0
  127. package/templates/starter/e2e/app-browser-motion-reference-requirements.spec.ts +411 -0
  128. package/templates/starter/e2e/app-browser-proof-session.spec.ts +85 -0
  129. package/templates/starter/e2e/app-browser-runtime-requirements.spec.ts +62 -0
  130. package/templates/starter/e2e/app-browser-selection-scope-evidence.spec.ts +137 -0
  131. package/templates/starter/e2e/browser-infinity-canvas-svg-evidence.ts +42 -0
  132. package/templates/starter/e2e/browser-layer-evidence-helpers.ts +15 -36
  133. package/templates/starter/e2e/browser-product-raster-snapshot.ts +213 -0
  134. package/templates/starter/e2e/browser-proof-session.ts +43 -15
  135. package/templates/starter/e2e/browser-proof-surface-action.ts +79 -0
  136. package/templates/starter/e2e/browser-proof-types.ts +15 -0
  137. package/templates/starter/e2e/browser-runtime-evidence-requirements.ts +22 -6
  138. package/templates/starter/e2e/browser-selection-scope-evidence.ts +269 -0
  139. package/templates/starter/e2e/browser-semantic-evidence-test-helpers.ts +97 -0
  140. package/templates/starter/e2e/browser-svg-export-evidence.spec.ts +79 -0
  141. package/templates/starter/e2e/browser-svg-export-evidence.ts +152 -0
  142. package/templates/starter/e2e/export-artifact-helpers.ts +70 -0
  143. package/templates/starter/e2e/product-observable-helpers.ts +8 -129
  144. package/templates/starter/e2e/svg-artifact-inspection.spec.ts +136 -0
  145. package/templates/starter/e2e/svg-artifact-inspection.ts +331 -0
  146. package/templates/starter/e2e/svg-download-test-fixtures.ts +34 -0
  147. package/templates/starter/e2e/toolcraft-delivery-catalog-reporter.ts +198 -15
  148. package/templates/starter/e2e/toolcraft-feature-verification-reporter.spec.ts +149 -0
  149. package/templates/starter/e2e/toolcraft-feature-verification-reporter.ts +223 -0
  150. package/templates/starter/package.json +2 -0
  151. package/templates/starter/scripts/check-toolcraft-docs.mjs +1 -0
  152. package/templates/starter/scripts/create-toolcraft-motion-reference-study.mjs +233 -0
  153. package/templates/starter/scripts/create-toolcraft-motion-reference-study.test.mjs +459 -0
  154. package/templates/starter/scripts/playwright-test-title-selection.d.mts +45 -0
  155. package/templates/starter/scripts/run-browser-performance-test-helpers.mjs +0 -27
  156. package/templates/starter/scripts/run-browser-performance.mjs +0 -6
  157. package/templates/starter/scripts/run-browser-performance.test.mjs +5 -13
  158. package/templates/starter/scripts/run-delivery-verification-test-helpers.mjs +0 -34
  159. package/templates/starter/scripts/run-delivery-verification.test.mjs +135 -31
  160. package/templates/starter/scripts/run-feature-verification.mjs +212 -0
  161. package/templates/starter/scripts/run-feature-verification.test.mjs +226 -0
  162. package/templates/starter/scripts/toolcraft-checkpoint-delivery-state.mjs +21 -8
  163. package/templates/starter/scripts/toolcraft-checkpoint-delivery-state.test.mjs +32 -92
  164. package/templates/starter/scripts/toolcraft-checkpoint-transaction.mjs +28 -6
  165. package/templates/starter/scripts/toolcraft-checkpoint-transaction.test.mjs +8 -63
  166. package/templates/starter/scripts/toolcraft-delivery-anchor.mjs +28 -8
  167. package/templates/starter/scripts/toolcraft-delivery-anchor.test.mjs +50 -61
  168. package/templates/starter/scripts/toolcraft-delivery-architecture-inventory.mjs +0 -4
  169. package/templates/starter/scripts/toolcraft-delivery-architecture-policy.mjs +0 -1
  170. package/templates/starter/scripts/toolcraft-delivery-evidence.test.mjs +2 -3
  171. package/templates/starter/scripts/toolcraft-delivery-executor.mjs +5 -1
  172. package/templates/starter/scripts/toolcraft-delivery-executor.test.mjs +5 -12
  173. package/templates/starter/scripts/toolcraft-delivery-functional-context.d.mts +1 -48
  174. package/templates/starter/scripts/toolcraft-delivery-functional-context.mjs +2 -30
  175. package/templates/starter/scripts/toolcraft-delivery-functional-context.test.mjs +4 -166
  176. package/templates/starter/scripts/toolcraft-delivery-functional-initial.test.mjs +4 -20
  177. package/templates/starter/scripts/toolcraft-delivery-lifecycle.mjs +43 -192
  178. package/templates/starter/scripts/toolcraft-delivery-performance-iteration-lifecycle.test.mjs +79 -440
  179. package/templates/starter/scripts/toolcraft-delivery-plan-authority.test.mjs +102 -297
  180. package/templates/starter/scripts/toolcraft-delivery-plan-construction.mjs +46 -97
  181. package/templates/starter/scripts/toolcraft-delivery-plan-test-helpers.mjs +125 -0
  182. package/templates/starter/scripts/toolcraft-delivery-plan-validation.test.mjs +58 -0
  183. package/templates/starter/scripts/toolcraft-delivery-plan.d.mts +19 -31
  184. package/templates/starter/scripts/toolcraft-delivery-plan.mjs +254 -252
  185. package/templates/starter/scripts/toolcraft-delivery-plan.test.mjs +29 -457
  186. package/templates/starter/scripts/toolcraft-delivery-receipt-performance.test.mjs +1 -1
  187. package/templates/starter/scripts/toolcraft-delivery-receipt-strictness.test.mjs +3 -72
  188. package/templates/starter/scripts/toolcraft-delivery-receipt-test-helpers.mjs +7 -36
  189. package/templates/starter/scripts/toolcraft-delivery-receipt.d.mts +3 -5
  190. package/templates/starter/scripts/toolcraft-delivery-receipt.mjs +15 -31
  191. package/templates/starter/scripts/toolcraft-delivery-receipt.test.mjs +28 -20
  192. package/templates/starter/scripts/toolcraft-delivery-receipt.types.test.mts +22 -30
  193. package/templates/starter/scripts/toolcraft-feature-verification-plan.d.mts +20 -0
  194. package/templates/starter/scripts/toolcraft-feature-verification-plan.mjs +146 -0
  195. package/templates/starter/scripts/toolcraft-feature-verification-plan.test.mjs +96 -0
  196. package/templates/starter/scripts/toolcraft-functional-proof-model.d.mts +3 -18
  197. package/templates/starter/scripts/toolcraft-functional-proof-model.mjs +39 -127
  198. package/templates/starter/scripts/toolcraft-functional-proof-model.test.mjs +25 -362
  199. package/templates/starter/scripts/toolcraft-functional-proof-model.types.test.mts +6 -83
  200. package/templates/starter/scripts/toolcraft-functional-proof-primitives.d.mts +11 -0
  201. package/templates/starter/scripts/toolcraft-functional-proof-primitives.mjs +61 -0
  202. package/templates/starter/scripts/toolcraft-functional-proof-primitives.test.mjs +43 -0
  203. package/templates/starter/scripts/toolcraft-integrity-policy.mjs +13 -0
  204. package/templates/starter/scripts/toolcraft-integrity-test-utils.mjs +1 -1
  205. package/templates/starter/scripts/toolcraft-layer-evidence-facade.test.mjs +58 -22
  206. package/templates/starter/scripts/toolcraft-motion-reference-analysis-protocol.mjs +111 -0
  207. package/templates/starter/scripts/toolcraft-motion-reference-analysis-protocol.test.mjs +116 -0
  208. package/templates/starter/scripts/toolcraft-motion-reference-artifacts.d.mts +37 -0
  209. package/templates/starter/scripts/toolcraft-motion-reference-artifacts.mjs +308 -0
  210. package/templates/starter/scripts/toolcraft-motion-reference-artifacts.test.mjs +420 -0
  211. package/templates/starter/scripts/toolcraft-motion-reference-artifacts.types.test.mts +50 -0
  212. package/templates/starter/scripts/toolcraft-motion-reference-command-options.mjs +195 -0
  213. package/templates/starter/scripts/toolcraft-motion-reference-command-options.test.mjs +224 -0
  214. package/templates/starter/scripts/toolcraft-motion-reference-contact-sheet.mjs +122 -0
  215. package/templates/starter/scripts/toolcraft-motion-reference-contact-sheet.test.mjs +153 -0
  216. package/templates/starter/scripts/toolcraft-motion-reference-evidence-assembly.mjs +270 -0
  217. package/templates/starter/scripts/toolcraft-motion-reference-evidence.test.mjs +432 -0
  218. package/templates/starter/scripts/toolcraft-motion-reference-ffmpeg-output.mjs +275 -0
  219. package/templates/starter/scripts/toolcraft-motion-reference-ffmpeg-output.test.mjs +294 -0
  220. package/templates/starter/scripts/toolcraft-motion-reference-group-inventory.mjs +148 -0
  221. package/templates/starter/scripts/toolcraft-motion-reference-image-filter-graph.mjs +101 -0
  222. package/templates/starter/scripts/toolcraft-motion-reference-image-filter-graph.test.mjs +124 -0
  223. package/templates/starter/scripts/toolcraft-motion-reference-image-source.mjs +259 -0
  224. package/templates/starter/scripts/toolcraft-motion-reference-image-source.test.mjs +338 -0
  225. package/templates/starter/scripts/toolcraft-motion-reference-image-timing.mjs +96 -0
  226. package/templates/starter/scripts/toolcraft-motion-reference-image-timing.test.mjs +81 -0
  227. package/templates/starter/scripts/toolcraft-motion-reference-lock-recovery.test.mjs +144 -0
  228. package/templates/starter/scripts/toolcraft-motion-reference-media-source.mjs +165 -0
  229. package/templates/starter/scripts/toolcraft-motion-reference-media-source.test.mjs +299 -0
  230. package/templates/starter/scripts/toolcraft-motion-reference-publication-discovery.test.mjs +142 -0
  231. package/templates/starter/scripts/toolcraft-motion-reference-publication-isolation.test.mjs +234 -0
  232. package/templates/starter/scripts/toolcraft-motion-reference-publication-journal-schema.mjs +122 -0
  233. package/templates/starter/scripts/toolcraft-motion-reference-publication-journal-store.mjs +89 -0
  234. package/templates/starter/scripts/toolcraft-motion-reference-publication-journal-store.test.mjs +74 -0
  235. package/templates/starter/scripts/toolcraft-motion-reference-publication-journal.mjs +264 -0
  236. package/templates/starter/scripts/toolcraft-motion-reference-publication-journal.test.mjs +377 -0
  237. package/templates/starter/scripts/toolcraft-motion-reference-publication-lease.mjs +254 -0
  238. package/templates/starter/scripts/toolcraft-motion-reference-publication-lease.test.mjs +130 -0
  239. package/templates/starter/scripts/toolcraft-motion-reference-publication-staging.mjs +128 -0
  240. package/templates/starter/scripts/toolcraft-motion-reference-publication-staging.test.mjs +209 -0
  241. package/templates/starter/scripts/toolcraft-motion-reference-publication.fixtures.mjs +88 -0
  242. package/templates/starter/scripts/toolcraft-motion-reference-publication.mjs +249 -0
  243. package/templates/starter/scripts/toolcraft-motion-reference-publication.test.mjs +433 -0
  244. package/templates/starter/scripts/toolcraft-motion-reference-review-output.mjs +38 -0
  245. package/templates/starter/scripts/toolcraft-motion-reference-review-output.test.mjs +29 -0
  246. package/templates/starter/scripts/toolcraft-motion-reference-root-safety.test.mjs +84 -0
  247. package/templates/starter/scripts/toolcraft-motion-reference-roots.mjs +83 -0
  248. package/templates/starter/scripts/toolcraft-motion-reference-sampling.mjs +283 -0
  249. package/templates/starter/scripts/toolcraft-motion-reference-sampling.test.mjs +480 -0
  250. package/templates/starter/scripts/toolcraft-motion-reference-stage-registry.mjs +39 -0
  251. package/templates/starter/scripts/toolcraft-motion-reference-stage-registry.test.mjs +59 -0
  252. package/templates/starter/scripts/toolcraft-motion-reference-stage.mjs +71 -0
  253. package/templates/starter/scripts/toolcraft-motion-reference-stage.test.mjs +61 -0
  254. package/templates/starter/scripts/toolcraft-motion-review-numeric.mjs +74 -0
  255. package/templates/starter/scripts/toolcraft-motion-review-numeric.test.mjs +65 -0
  256. package/templates/starter/scripts/toolcraft-motion-review-partitioning.mjs +268 -0
  257. package/templates/starter/scripts/toolcraft-motion-review-partitioning.test.mjs +329 -0
  258. package/templates/starter/scripts/toolcraft-motion-review-protected-units.mjs +120 -0
  259. package/templates/starter/scripts/toolcraft-motion-review-protected-units.test.mjs +111 -0
  260. package/templates/starter/scripts/toolcraft-performance-escalation-policy.test.mjs +77 -345
  261. package/templates/starter/scripts/toolcraft-performance-receipt-validation.test.mjs +19 -21
  262. package/templates/starter/scripts/toolcraft-product-boundary-ast.mjs +1 -1
  263. package/templates/starter/scripts/toolcraft-product-export-boundary.mjs +10 -1
  264. package/templates/starter/scripts/toolcraft-product-export-boundary.test.mjs +5 -1
  265. package/templates/starter/scripts/toolcraft-product-observable-facade.test.mjs +46 -1
  266. package/templates/starter/scripts/toolcraft-receipt-boundary.test.mjs +0 -1
  267. package/templates/starter/scripts/toolcraft-source-inventory.d.mts +5 -0
  268. package/templates/starter/scripts/toolcraft-source-inventory.mjs +25 -14
  269. package/templates/starter/scripts/toolcraft-source-inventory.test.mjs +48 -0
  270. package/templates/starter/scripts/toolcraft-source-ownership.mjs +0 -1
  271. package/templates/starter/scripts/toolcraft-source-ownership.test.mjs +30 -5
  272. package/templates/starter/scripts/toolcraft-verification-receipt-test-helpers.mjs +5 -26
  273. package/templates/starter/scripts/toolcraft-verification-receipt.d.mts +0 -7
  274. package/templates/starter/scripts/toolcraft-verification-receipt.mjs +3 -47
  275. package/templates/starter/scripts/toolcraft-verification-receipt.test.mjs +6 -87
  276. package/templates/starter/scripts/toolcraft-vitest-runtime-contract.d.mts +19 -0
  277. package/templates/starter/scripts/toolcraft-vitest-runtime-contract.mjs +107 -0
  278. package/templates/starter/scripts/toolcraft-vitest-runtime-contract.test.mjs +114 -0
  279. package/templates/starter/scripts/toolcraft-vitest-runtime-evidence-reporter.mjs +13 -2
  280. package/templates/starter/scripts/toolcraft-vitest-runtime-evidence-reporter.test.mjs +110 -0
  281. package/templates/starter/src/app/acceptance/artifact-export-intent.ts +51 -20
  282. package/templates/starter/src/app/acceptance/canvas-coverage.ts +1 -0
  283. package/templates/starter/src/app/acceptance/color-bank-labels.ts +11 -15
  284. package/templates/starter/src/app/acceptance/color-row-grouping.ts +33 -0
  285. package/templates/starter/src/app/acceptance/control-acceptance-coverage.ts +0 -11
  286. package/templates/starter/src/app/acceptance/control-acceptance-policy.ts +2 -3
  287. package/templates/starter/src/app/acceptance/control-component-rules.ts +0 -10
  288. package/templates/starter/src/app/acceptance/control-labels.ts +30 -1
  289. package/templates/starter/src/app/acceptance/control-layout-section-rules.ts +18 -0
  290. package/templates/starter/src/app/acceptance/export-artifact-coverage.ts +25 -17
  291. package/templates/starter/src/app/acceptance/feature-verification-selection.test.ts +295 -0
  292. package/templates/starter/src/app/acceptance/feature-verification-selection.ts +161 -0
  293. package/templates/starter/src/app/acceptance/infinity-canvas.ts +10 -0
  294. package/templates/starter/src/app/acceptance/interaction-ownership-intent.ts +19 -1
  295. package/templates/starter/src/app/acceptance/interaction-ownership.ts +18 -2
  296. package/templates/starter/src/app/acceptance/motion-reference-evidence-validation.mjs +65 -0
  297. package/templates/starter/src/app/acceptance/motion-reference-evidence-windows.mjs +301 -0
  298. package/templates/starter/src/app/acceptance/motion-reference-evidence.d.mts +11 -0
  299. package/templates/starter/src/app/acceptance/motion-reference-evidence.mjs +145 -0
  300. package/templates/starter/src/app/acceptance/motion-reference-study-behaviors.ts +162 -0
  301. package/templates/starter/src/app/acceptance/motion-reference-study-coverage.ts +206 -0
  302. package/templates/starter/src/app/acceptance/motion-reference-study-identity.ts +129 -0
  303. package/templates/starter/src/app/acceptance/motion-reference-study-partitions.ts +186 -0
  304. package/templates/starter/src/app/acceptance/motion-reference-study.ts +90 -0
  305. package/templates/starter/src/app/acceptance/output-export-actions.test.ts +9 -1
  306. package/templates/starter/src/app/acceptance/output-export-actions.ts +14 -22
  307. package/templates/starter/src/app/acceptance/output-export-layout-rules.ts +5 -1
  308. package/templates/starter/src/app/acceptance/output-export-model.ts +4 -0
  309. package/templates/starter/src/app/acceptance/output-export.ts +4 -1
  310. package/templates/starter/src/app/acceptance/reference-study-types.ts +167 -17
  311. package/templates/starter/src/app/acceptance/runtime-coverage.ts +6 -1
  312. package/templates/starter/src/app/acceptance/selection-scope.test.ts +200 -0
  313. package/templates/starter/src/app/acceptance/selection-scope.ts +151 -0
  314. package/templates/starter/src/app/acceptance/transfer-mode-types.ts +114 -0
  315. package/templates/starter/src/app/acceptance/types.ts +43 -118
  316. package/templates/starter/src/app/acceptance/validate-coverage.ts +14 -3
  317. package/templates/starter/src/app/starter-acceptance-data.ts +1 -0
  318. package/templates/starter/src/app/starter-acceptance.animation-intent.test.ts +3 -0
  319. package/templates/starter/src/app/starter-acceptance.artifact-export-intent.test.ts +81 -8
  320. package/templates/starter/src/app/starter-acceptance.background-export.test.ts +2 -0
  321. package/templates/starter/src/app/starter-acceptance.canvas-sizing.test.ts +2 -0
  322. package/templates/starter/src/app/starter-acceptance.color-row-grouping.test.ts +118 -0
  323. package/templates/starter/src/app/starter-acceptance.compound-part-coverage.test.ts +1 -1
  324. package/templates/starter/src/app/starter-acceptance.contract-fixtures.ts +1 -0
  325. package/templates/starter/src/app/starter-acceptance.control-applicability.test.ts +1 -0
  326. package/templates/starter/src/app/starter-acceptance.control-label-context.test.ts +1 -0
  327. package/templates/starter/src/app/starter-acceptance.control-labels.test.ts +78 -3
  328. package/templates/starter/src/app/starter-acceptance.control-naming-rules.test.ts +77 -1
  329. package/templates/starter/src/app/starter-acceptance.custom-control-fit-check.test.ts +1 -1
  330. package/templates/starter/src/app/starter-acceptance.export-artifact.test.ts +74 -3
  331. package/templates/starter/src/app/starter-acceptance.export-test-utils.ts +5 -1
  332. package/templates/starter/src/app/starter-acceptance.image-export-settings.test.ts +2 -0
  333. package/templates/starter/src/app/starter-acceptance.interaction-ownership.test.ts +19 -3
  334. package/templates/starter/src/app/starter-acceptance.layers.test.ts +2 -1
  335. package/templates/starter/src/app/starter-acceptance.media-upload.test.ts +28 -0
  336. package/templates/starter/src/app/starter-acceptance.motion-reference-evidence.test.ts +17 -0
  337. package/templates/starter/src/app/starter-acceptance.motion-reference-identity.test.ts +163 -0
  338. package/templates/starter/src/app/starter-acceptance.motion-reference-mapping.test.ts +133 -0
  339. package/templates/starter/src/app/starter-acceptance.motion-reference-ownership.test.ts +121 -0
  340. package/templates/starter/src/app/starter-acceptance.motion-reference-partitions.test.ts +210 -0
  341. package/templates/starter/src/app/starter-acceptance.motion-reference-test-utils.ts +313 -0
  342. package/templates/starter/src/app/starter-acceptance.output-export-intent.test.ts +78 -4
  343. package/templates/starter/src/app/starter-acceptance.product-output-export.test.ts +6 -1
  344. package/templates/starter/src/app/starter-acceptance.product-readiness.test.ts +18 -1
  345. package/templates/starter/src/app/starter-acceptance.product-worklog.test.ts +123 -2
  346. package/templates/starter/src/app/starter-acceptance.reference-custom-timeline.test.ts +2 -0
  347. package/templates/starter/src/app/starter-acceptance.reference-foundation.test.ts +8 -0
  348. package/templates/starter/src/app/starter-acceptance.reference-timeline.test.ts +7 -0
  349. package/templates/starter/src/app/starter-acceptance.section-cohesion.test.ts +2 -0
  350. package/templates/starter/src/app/starter-acceptance.timeline-playback.test.ts +5 -0
  351. package/templates/starter/src/app/starter-acceptance.ts +34 -8
  352. package/templates/starter/src/app/starter-acceptance.video-export-settings.test.ts +2 -0
  353. package/templates/starter/src/app/starter-acceptance.video-reference-study.test.ts +34 -236
  354. package/templates/starter/src/app/starter-acceptance.view-interaction.test.ts +4 -0
  355. package/templates/starter/src/app/starter-acceptance.worklog-test-utils.ts +122 -6
  356. package/templates/starter/src/app/starter-acceptance.worklog.test.ts +152 -16
  357. package/templates/starter/src/app/starter-performance.gates.test.ts +20 -18
  358. package/templates/starter/src/app/starter-performance.lifecycle.test.ts +22 -6
  359. package/templates/starter/src/app/test-evidence/browser-runtime-contract.ts +3 -0
  360. package/templates/starter/tsconfig.json +1 -0
  361. package/templates/starter/scripts/toolcraft-browser-impact-selection.mjs +0 -26
  362. package/templates/starter/scripts/toolcraft-delivery-framework-refresh.test.mjs +0 -141
  363. package/templates/starter/scripts/toolcraft-delivery-functional-changed.test.mjs +0 -468
  364. package/templates/starter/scripts/toolcraft-delivery-functional-model-lifecycle.test.mjs +0 -254
  365. package/templates/starter/scripts/toolcraft-delivery-functional-semantic-regression.test.mjs +0 -135
  366. package/templates/starter/scripts/toolcraft-delivery-performance-iteration-durability.test.mjs +0 -329
  367. package/templates/starter/scripts/toolcraft-delivery-plan-errors.test.mjs +0 -430
  368. package/templates/starter/scripts/toolcraft-delivery-plan-functional-model.test.mjs +0 -358
  369. package/templates/starter/scripts/toolcraft-functional-proof-regression-fixtures.mjs +0 -298
  370. package/templates/starter/scripts/toolcraft-verification-impact-inventory.d.mts +0 -62
  371. package/templates/starter/scripts/toolcraft-verification-impact-inventory.mjs +0 -344
  372. package/templates/starter/scripts/toolcraft-verification-impact-inventory.test.mjs +0 -204
  373. package/templates/starter/scripts/toolcraft-verification-impact-performance-candidates.test.mjs +0 -210
  374. package/templates/starter/scripts/toolcraft-verification-impact-resolution.mjs +0 -344
  375. package/templates/starter/scripts/toolcraft-verification-impact-resolution.test.mjs +0 -270
  376. package/templates/starter/scripts/toolcraft-verification-impact-semantic-resolution.test.mjs +0 -449
  377. package/templates/starter/scripts/toolcraft-verification-impact-support-resolution.test.mjs +0 -195
  378. package/templates/starter/scripts/toolcraft-verification-impact.d.mts +0 -132
  379. package/templates/starter/scripts/toolcraft-verification-impact.mjs +0 -169
  380. package/templates/starter/scripts/toolcraft-verification-impact.test.mjs +0 -300
  381. package/templates/starter/scripts/toolcraft-verification-input-roles.mjs +0 -197
  382. package/templates/starter/scripts/toolcraft-verification-input-roles.test.mjs +0 -225
  383. package/templates/starter/src/app/acceptance/video-reference-study.ts +0 -217
  384. package/templates/starter/src/app/starter-verification-impact.json +0 -15
@@ -0,0 +1,420 @@
1
+ import assert from "node:assert/strict";
2
+ import fs from "node:fs/promises";
3
+ import os from "node:os";
4
+ import path from "node:path";
5
+ import test from "node:test";
6
+
7
+ import {
8
+ createToolcraftMotionReferenceGroupId,
9
+ createToolcraftMotionReferenceIdentitySha256,
10
+ createToolcraftMotionReferenceId,
11
+ createToolcraftMotionReferencePlanSha256,
12
+ createToolcraftMotionReferenceStudyId,
13
+ createToolcraftSha256,
14
+ getToolcraftMotionReferenceArtifactErrors,
15
+ getToolcraftMotionReferenceEvidenceGroupErrors,
16
+ } from "./toolcraft-motion-reference-artifacts.mjs";
17
+ import {
18
+ writeCanonicalToolcraftMotionReferenceEvidence,
19
+ } from "../src/app/acceptance/motion-reference-evidence.mjs";
20
+
21
+ const encoder = new TextEncoder();
22
+ const hex = (character) => character.repeat(64);
23
+ function sortJson(value) {
24
+ if (Array.isArray(value)) return value.map(sortJson);
25
+ if (value && typeof value === "object") return Object.fromEntries(
26
+ Object.keys(value).sort().map((key) => [key, sortJson(value[key])]),
27
+ );
28
+ return value;
29
+ }
30
+ const hashValue = (value) => createToolcraftSha256(
31
+ encoder.encode(JSON.stringify(sortJson(value))),
32
+ );
33
+ function sourceIdentity() {
34
+ return {
35
+ evidenceVersion: 1,
36
+ sourceKind: "video",
37
+ sourceSha256: hex("a"),
38
+ timing: { authority: "decoded-timestamps" },
39
+ timingMode: "seconds",
40
+ };
41
+ }
42
+ function studyId(segment) {
43
+ return `motion-v1-${hashValue({ ...sourceIdentity(), ...(segment ? { segment } : {}) })}`;
44
+ }
45
+ function createEvidence({ index = 0, partition, segment } = {}) {
46
+ const id = studyId(segment);
47
+ const indices = segment ? (index === 0 ? [0, 1, 2, 3, 4] : [5, 6, 7, 8]) :
48
+ [0, 1, 2];
49
+ const reviewedFrames = indices.map((sourceFrameIndex, position) => ({
50
+ cell: { column: position, row: 0 },
51
+ frameId: `source-frame:${sourceFrameIndex}`,
52
+ sourceFrameIndex,
53
+ sourceLocator: `reference.mp4#frame=${sourceFrameIndex}`,
54
+ sourceSha256: hex("a"),
55
+ timeSeconds: segment ?
56
+ segment.startSeconds + position *
57
+ (segment.endSeconds - segment.startSeconds) / Math.max(1, indices.length - 1) :
58
+ sourceFrameIndex,
59
+ }));
60
+ const detectedEvents = index === 0 ? [{
61
+ afterFrameId: "source-frame:2",
62
+ beforeFrameId: "source-frame:0",
63
+ eventId: "motion-event:change:0-1-2",
64
+ kind: "change",
65
+ peakFrameId: "source-frame:1",
66
+ score: 0.75,
67
+ windowFrameIds: ["source-frame:0", "source-frame:1", "source-frame:2"],
68
+ }, ...(segment ? [{
69
+ afterFrameId: "source-frame:4",
70
+ beforeFrameId: "source-frame:2",
71
+ eventId: "motion-event:change:2-3-4",
72
+ kind: "change",
73
+ peakFrameId: "source-frame:3",
74
+ score: 0.5,
75
+ windowFrameIds: ["source-frame:2", "source-frame:3", "source-frame:4"],
76
+ }] : [])] : [];
77
+ const focusWindows = index === 0 && segment ? [{
78
+ endSeconds: 1,
79
+ frameIds: ["source-frame:0", "source-frame:1", "source-frame:2"],
80
+ sourceFrameEndIndex: 2,
81
+ sourceFrameStartIndex: 0,
82
+ startSeconds: 0,
83
+ }, {
84
+ endSeconds: 2,
85
+ frameIds: ["source-frame:0", "source-frame:1", "source-frame:2",
86
+ "source-frame:3", "source-frame:4"],
87
+ sourceFrameEndIndex: 4,
88
+ sourceFrameStartIndex: 0,
89
+ startSeconds: 0,
90
+ }] : [];
91
+ return {
92
+ changeMetric: "max-yuva-diff-v1",
93
+ contactSheet: {
94
+ columns: reviewedFrames.length,
95
+ height: 100,
96
+ path: `src/app/reference-studies/${id}/contact-sheet.png`,
97
+ rows: 1,
98
+ sha256: hex("0"),
99
+ width: 300,
100
+ },
101
+ decodedFrameCount: segment ? 9 : 5,
102
+ detectedEvents,
103
+ durationSeconds: 4,
104
+ evidenceVersion: 1,
105
+ ffmpegVersion: "ffmpeg 8.0",
106
+ ffprobeVersion: "ffprobe 8.0",
107
+ focusWindows,
108
+ height: 180,
109
+ maximumSourceFrameGapSeconds: 1,
110
+ overviewFrameIds: reviewedFrames.map(({ frameId }) => frameId),
111
+ overviewTargetFps: 1,
112
+ ...(partition ? { reviewPartition: partition } : {}),
113
+ reviewedFrames,
114
+ scannedFrameCount: segment ? 9 : 5,
115
+ ...(segment ? { segment } : {}),
116
+ sourceBasename: "reference.mp4",
117
+ sourceKind: "video",
118
+ sourceSha256: hex("a"),
119
+ studyId: id,
120
+ timing: { authority: "decoded-timestamps" },
121
+ timingMode: "seconds",
122
+ width: 320,
123
+ };
124
+ }
125
+ function planHash(evidences) {
126
+ return createToolcraftMotionReferencePlanSha256(evidences);
127
+ }
128
+
129
+ test("exports one canonical authority for source, study, and group identities", () => {
130
+ const segment = { endSeconds: 2, startSeconds: 0 };
131
+ const evidence = createEvidence({ segment });
132
+ const scope = { endSeconds: 4, startSeconds: 0 };
133
+ assert.equal(createToolcraftMotionReferenceId(evidence),
134
+ `motion-reference-v1-${hashValue(sourceIdentity())}`);
135
+ assert.equal(createToolcraftMotionReferenceStudyId(evidence), studyId(segment));
136
+ assert.equal(createToolcraftMotionReferenceGroupId(evidence, scope),
137
+ `motion-group-v1-${hashValue({ ...sourceIdentity(), scope })}`);
138
+ });
139
+ async function createFixture(context, { partitioned = false } = {}) {
140
+ const rootDir = await fs.mkdtemp(path.join(os.tmpdir(), "toolcraft-motion-artifacts-"));
141
+ context.after(() => fs.rm(rootDir, { force: true, recursive: true }));
142
+ let evidences;
143
+ if (partitioned) {
144
+ const scope = { endSeconds: 4, startSeconds: 0 };
145
+ const segments = [
146
+ { endSeconds: 2, startSeconds: 0 },
147
+ { endSeconds: 4, startSeconds: 2 },
148
+ ];
149
+ const groupId = `motion-group-v1-${hashValue({ ...sourceIdentity(), scope })}`;
150
+ evidences = segments.map((segment, index) => createEvidence({ index, segment }));
151
+ const sharedPlanHash = planHash(evidences);
152
+ evidences.forEach((evidence, index) => {
153
+ evidence.reviewPartition = {
154
+ count: 2,
155
+ groupId,
156
+ index,
157
+ planSha256: sharedPlanHash,
158
+ scope,
159
+ };
160
+ });
161
+ } else {
162
+ evidences = [createEvidence()];
163
+ }
164
+ const studies = [];
165
+ const sheetPaths = [];
166
+ for (const [index, evidence] of evidences.entries()) {
167
+ const sheetBytes = encoder.encode(`sheet-${index}`);
168
+ evidence.contactSheet.sha256 = createToolcraftSha256(sheetBytes);
169
+ const directory = path.join(rootDir, "src/app/reference-studies", evidence.studyId);
170
+ await fs.mkdir(directory, { recursive: true });
171
+ const evidencePath = path.join(directory, "evidence.json");
172
+ const sheetPath = path.join(directory, "contact-sheet.png");
173
+ await fs.writeFile(evidencePath, writeCanonicalToolcraftMotionReferenceEvidence(evidence));
174
+ await fs.writeFile(sheetPath, sheetBytes);
175
+ studies.push({
176
+ evidence,
177
+ evidencePath: path.posix.join("src/app/reference-studies", evidence.studyId, "evidence.json"),
178
+ events: [],
179
+ phases: [],
180
+ studyId: evidence.studyId,
181
+ });
182
+ sheetPaths.push(sheetPath);
183
+ }
184
+ const referenceInputs = [{
185
+ behaviors: [],
186
+ kind: "motion-reference",
187
+ referenceId: `motion-reference-v1-${hashValue(sourceIdentity())}`,
188
+ studies,
189
+ }];
190
+ return { evidences, referenceInputs, rootDir, sheetPaths };
191
+ }
192
+
193
+ test("verifies canonical evidence and exact contact-sheet content hashes", async (context) => {
194
+ const fixture = await createFixture(context);
195
+ assert.equal(createToolcraftSha256(encoder.encode("sheet-0")),
196
+ fixture.evidences[0].contactSheet.sha256);
197
+ assert.deepEqual(await getToolcraftMotionReferenceArtifactErrors(fixture), []);
198
+ });
199
+
200
+ test("exports pure complete-group validation for publication inventory", async (context) => {
201
+ const fixture = await createFixture(context, { partitioned: true });
202
+ assert.deepEqual(getToolcraftMotionReferenceEvidenceGroupErrors(
203
+ fixture.evidences,
204
+ ), []);
205
+ assert.match(getToolcraftMotionReferenceEvidenceGroupErrors(
206
+ fixture.evidences.slice(0, 1),
207
+ ).join("\n"), /indices 0\.\.1 exactly/iu);
208
+ });
209
+
210
+ test("reports missing and modified contact sheets", async (context) => {
211
+ const missing = await createFixture(context);
212
+ await fs.rm(missing.sheetPaths[0]);
213
+ assert.match((await getToolcraftMotionReferenceArtifactErrors(missing)).join("\n"),
214
+ /contact sheet.*read|ENOENT/iu);
215
+
216
+ const modified = await createFixture(context);
217
+ await fs.writeFile(modified.sheetPaths[0], "modified");
218
+ assert.match((await getToolcraftMotionReferenceArtifactErrors(modified)).join("\n"),
219
+ /contact sheet.*SHA-256/iu);
220
+ });
221
+
222
+ test("rejects wrong directories and embedded/file evidence mismatches", async (context) => {
223
+ const wrongDirectory = await createFixture(context);
224
+ wrongDirectory.referenceInputs[0].studies[0].evidencePath =
225
+ "src/app/reference-studies/wrong/evidence.json";
226
+ assert.match((await getToolcraftMotionReferenceArtifactErrors(wrongDirectory)).join("\n"),
227
+ /canonical study directory/iu);
228
+
229
+ const mismatch = await createFixture(context);
230
+ mismatch.referenceInputs[0].studies[0].evidence.width += 1;
231
+ assert.match((await getToolcraftMotionReferenceArtifactErrors(mismatch)).join("\n"),
232
+ /embedded evidence.*evidence.json/iu);
233
+ });
234
+
235
+ test("rejects malformed and noncanonical evidence JSON", async (context) => {
236
+ const malformed = await createFixture(context);
237
+ await fs.writeFile(path.join(malformed.rootDir,
238
+ malformed.referenceInputs[0].studies[0].evidencePath), "{broken");
239
+ assert.match((await getToolcraftMotionReferenceArtifactErrors(malformed)).join("\n"),
240
+ /valid JSON/iu);
241
+
242
+ const noncanonical = await createFixture(context);
243
+ const evidencePath = path.join(noncanonical.rootDir,
244
+ noncanonical.referenceInputs[0].studies[0].evidencePath);
245
+ const parsed = JSON.parse(await fs.readFile(evidencePath, "utf8"));
246
+ await fs.writeFile(evidencePath, `${JSON.stringify(parsed)}\n`);
247
+ assert.match((await getToolcraftMotionReferenceArtifactErrors(noncanonical)).join("\n"),
248
+ /canonical serialization/iu);
249
+ });
250
+
251
+ test("returns string errors for scalar and null embedded and file evidence", async (context) => {
252
+ for (const value of [null, 7, []]) {
253
+ const embedded = await createFixture(context);
254
+ embedded.referenceInputs[0].studies[0].evidence = value;
255
+ let embeddedErrors;
256
+ await assert.doesNotReject(async () => {
257
+ embeddedErrors = await getToolcraftMotionReferenceArtifactErrors(embedded);
258
+ });
259
+ assert.ok(embeddedErrors.length > 0);
260
+ assert.ok(embeddedErrors.every((error) => typeof error === "string"));
261
+
262
+ const onDisk = await createFixture(context);
263
+ await fs.writeFile(path.join(onDisk.rootDir,
264
+ onDisk.referenceInputs[0].studies[0].evidencePath), `${JSON.stringify(value)}\n`);
265
+ let fileErrors;
266
+ await assert.doesNotReject(async () => {
267
+ fileErrors = await getToolcraftMotionReferenceArtifactErrors(onDisk);
268
+ });
269
+ assert.ok(fileErrors.length > 0);
270
+ assert.ok(fileErrors.every((error) => typeof error === "string"));
271
+ }
272
+ });
273
+
274
+ test("anchors identity and partition plan hashes to fixed canonical payloads", () => {
275
+ const focusA = {
276
+ endSeconds: 1,
277
+ frameIds: ["source-frame:0", "source-frame:1"],
278
+ sourceFrameEndIndex: 1,
279
+ sourceFrameStartIndex: 0,
280
+ startSeconds: 0,
281
+ };
282
+ const focusB = {
283
+ endSeconds: 3,
284
+ frameIds: ["source-frame:2", "source-frame:3"],
285
+ sourceFrameEndIndex: 3,
286
+ sourceFrameStartIndex: 2,
287
+ startSeconds: 2,
288
+ };
289
+ const evidences = [{
290
+ detectedEvents: [{ eventId: "motion-event:change:4-5-6" },
291
+ { eventId: "motion-event:change:0-1-2" }],
292
+ focusWindows: [focusB, focusA],
293
+ reviewedFrames: [{
294
+ frameId: "source-frame:0", sourceFrameIndex: 0, sourceSha256: hex("a"),
295
+ }],
296
+ segment: { endSeconds: 2, startSeconds: 0 },
297
+ }, {
298
+ detectedEvents: [{ eventId: "motion-event:change:0-1-2" }],
299
+ focusWindows: [focusA],
300
+ reviewedFrames: [{
301
+ frameId: "source-frame:7", sourceFrameIndex: 7, sourceSha256: hex("b"),
302
+ }],
303
+ segment: { endSeconds: 4, startSeconds: 2 },
304
+ }];
305
+ assert.equal(createToolcraftMotionReferenceIdentitySha256(sourceIdentity()),
306
+ "a7ae52bf55a62efac7a95f61df736a80d02a2e22a89ead0cd47610b80115df1a");
307
+ assert.equal(createToolcraftMotionReferencePlanSha256(evidences),
308
+ "dcd8a5082f80abf9017186730daed5d0bd464298dc1a3df648431d82c9095641");
309
+ });
310
+
311
+ test("canonicalizes event and equal-start focus permutations as unordered unions", () => {
312
+ const first = createEvidence({ index: 0, segment: { endSeconds: 2, startSeconds: 0 } });
313
+ const second = createEvidence({ index: 1, segment: { endSeconds: 4, startSeconds: 2 } });
314
+ const expected = planHash([first, second]);
315
+ first.detectedEvents.reverse();
316
+ first.focusWindows.reverse();
317
+ assert.equal(planHash([first, second]), expected);
318
+ });
319
+
320
+ test("accepts event and equal-start focus permutations without changing a group plan", async (context) => {
321
+ const fixture = await createFixture(context, { partitioned: true });
322
+ const evidence = fixture.evidences[0];
323
+ const expected = evidence.reviewPartition.planSha256;
324
+ evidence.detectedEvents.reverse();
325
+ evidence.focusWindows.reverse();
326
+ assert.equal(planHash(fixture.evidences), expected);
327
+ await fs.writeFile(path.join(fixture.rootDir,
328
+ fixture.referenceInputs[0].studies[0].evidencePath),
329
+ writeCanonicalToolcraftMotionReferenceEvidence(evidence));
330
+ assert.deepEqual(await getToolcraftMotionReferenceArtifactErrors(fixture), []);
331
+ });
332
+
333
+ test("recomputes study and reference identities", async (context) => {
334
+ const wrongReference = await createFixture(context);
335
+ wrongReference.referenceInputs[0].referenceId = `motion-reference-v1-${hex("f")}`;
336
+ assert.match((await getToolcraftMotionReferenceArtifactErrors(wrongReference)).join("\n"),
337
+ /referenceId.*source identity/iu);
338
+
339
+ const wrongStudy = await createFixture(context);
340
+ const study = wrongStudy.referenceInputs[0].studies[0];
341
+ study.studyId = `motion-v1-${hex("e")}`;
342
+ study.evidence.studyId = study.studyId;
343
+ study.evidence.contactSheet.path =
344
+ `src/app/reference-studies/${study.studyId}/contact-sheet.png`;
345
+ assert.match((await getToolcraftMotionReferenceArtifactErrors(wrongStudy)).join("\n"),
346
+ /studyId/iu);
347
+ });
348
+
349
+ test("checks complete partition membership, group identity, and plan hashes", async (context) => {
350
+ const incomplete = await createFixture(context, { partitioned: true });
351
+ incomplete.referenceInputs[0].studies = incomplete.referenceInputs[0].studies.slice(0, 1);
352
+ assert.match((await getToolcraftMotionReferenceArtifactErrors(incomplete)).join("\n"),
353
+ /indices 0\.\.1 exactly/iu);
354
+
355
+ const forged = await createFixture(context, { partitioned: true });
356
+ forged.evidences.forEach((evidence) => { evidence.reviewPartition.planSha256 = hex("f"); });
357
+ assert.match((await getToolcraftMotionReferenceArtifactErrors(forged)).join("\n"),
358
+ /planSha256.*canonical partition plan/iu);
359
+
360
+ const duplicate = await createFixture(context, { partitioned: true });
361
+ duplicate.referenceInputs[0].studies[1].evidence.reviewPartition.index = 0;
362
+ assert.match((await getToolcraftMotionReferenceArtifactErrors(duplicate)).join("\n"),
363
+ /indices 0\.\.1 exactly/iu);
364
+
365
+ const duplicatedUnit = await createFixture(context, { partitioned: true });
366
+ const secondEvidence = duplicatedUnit.referenceInputs[0].studies[1].evidence;
367
+ secondEvidence.reviewedFrames[0].sourceFrameIndex = 2;
368
+ secondEvidence.reviewedFrames[0].frameId = "source-frame:2";
369
+ secondEvidence.overviewFrameIds[0] = "source-frame:2";
370
+ assert.match((await getToolcraftMotionReferenceArtifactErrors(duplicatedUnit)).join("\n"),
371
+ /duplicate review unit frame:2/iu);
372
+
373
+ const missingOrdinaryFrame = await createFixture(context, { partitioned: true });
374
+ const missingEvidence = missingOrdinaryFrame.referenceInputs[0].studies[1].evidence;
375
+ missingEvidence.reviewedFrames.pop();
376
+ missingEvidence.overviewFrameIds.pop();
377
+ missingEvidence.contactSheet.columns = missingEvidence.reviewedFrames.length;
378
+ await fs.writeFile(
379
+ path.join(missingOrdinaryFrame.rootDir,
380
+ missingOrdinaryFrame.referenceInputs[0].studies[1].evidencePath),
381
+ writeCanonicalToolcraftMotionReferenceEvidence(missingEvidence),
382
+ );
383
+ assert.match(
384
+ (await getToolcraftMotionReferenceArtifactErrors(missingOrdinaryFrame)).join("\n"),
385
+ /planSha256.*canonical partition plan/iu,
386
+ );
387
+ });
388
+
389
+ test("rejects root escapes and symlinked artifacts", async (context) => {
390
+ const lexical = await createFixture(context);
391
+ lexical.referenceInputs[0].studies[0].evidencePath = "../outside/evidence.json";
392
+ assert.match((await getToolcraftMotionReferenceArtifactErrors(lexical)).join("\n"),
393
+ /root boundary/iu);
394
+
395
+ const linked = await createFixture(context);
396
+ const outsideDir = await fs.mkdtemp(path.join(os.tmpdir(), "toolcraft-motion-outside-"));
397
+ context.after(() => fs.rm(outsideDir, { force: true, recursive: true }));
398
+ await fs.writeFile(path.join(outsideDir, "evidence.json"), "{}\n");
399
+ const linkPath = path.join(linked.rootDir, "src/app/reference-studies/linked");
400
+ try {
401
+ await fs.symlink(outsideDir, linkPath);
402
+ } catch (error) {
403
+ if (["EACCES", "ENOSYS", "EPERM"].includes(error?.code)) {
404
+ context.skip(`symbolic links are unavailable: ${error.code}`);
405
+ return;
406
+ }
407
+ throw error;
408
+ }
409
+ linked.referenceInputs[0].studies[0].evidencePath =
410
+ "src/app/reference-studies/linked/evidence.json";
411
+ assert.match((await getToolcraftMotionReferenceArtifactErrors(linked)).join("\n"),
412
+ /root boundary/iu);
413
+ });
414
+
415
+ test("returns immediately when there are no reference inputs", async () => {
416
+ assert.deepEqual(await getToolcraftMotionReferenceArtifactErrors({
417
+ referenceInputs: [],
418
+ rootDir: "/path/that/does/not/exist",
419
+ }), []);
420
+ });
@@ -0,0 +1,50 @@
1
+ import type {
2
+ ToolcraftMotionReferenceEvidence,
3
+ ToolcraftReferenceInput,
4
+ } from "../src/app/acceptance/reference-study-types";
5
+ import {
6
+ createToolcraftMotionReferenceGroupId,
7
+ createToolcraftMotionReferenceId,
8
+ createToolcraftMotionReferenceIdentitySha256,
9
+ createToolcraftMotionReferencePlanSha256,
10
+ createToolcraftMotionReferenceStudyId,
11
+ createToolcraftSha256,
12
+ getToolcraftMotionReferenceArtifactErrors,
13
+ getToolcraftMotionReferenceEvidenceGroupErrors,
14
+ type ToolcraftMotionReferenceGroupId,
15
+ type ToolcraftMotionReferenceId,
16
+ type ToolcraftMotionReferenceScope,
17
+ type ToolcraftMotionReferenceStudyId,
18
+ } from "./toolcraft-motion-reference-artifacts.mjs";
19
+
20
+ declare const evidence: ToolcraftMotionReferenceEvidence;
21
+ declare const evidences: readonly ToolcraftMotionReferenceEvidence[];
22
+ declare const referenceInputs: readonly ToolcraftReferenceInput[];
23
+
24
+ const digest: string = createToolcraftSha256(new Uint8Array());
25
+ const identityDigest: string = createToolcraftMotionReferenceIdentitySha256({
26
+ digest,
27
+ });
28
+ const planDigest: string = createToolcraftMotionReferencePlanSha256(evidences);
29
+ const studyId: ToolcraftMotionReferenceStudyId =
30
+ createToolcraftMotionReferenceStudyId(evidence);
31
+ const referenceId: ToolcraftMotionReferenceId =
32
+ createToolcraftMotionReferenceId(evidence);
33
+ const groupId: ToolcraftMotionReferenceGroupId =
34
+ createToolcraftMotionReferenceGroupId(evidence, {
35
+ endSeconds: 1,
36
+ startSeconds: 0,
37
+ });
38
+ const ordinalScope: ToolcraftMotionReferenceScope = {
39
+ endFrameExclusive: 12,
40
+ startFrameIndex: 0,
41
+ };
42
+ const errors: Promise<string[]> = getToolcraftMotionReferenceArtifactErrors({
43
+ referenceInputs,
44
+ rootDir: "/toolcraft-app",
45
+ });
46
+ const groupErrors: string[] =
47
+ getToolcraftMotionReferenceEvidenceGroupErrors(evidences);
48
+
49
+ void [digest, errors, groupErrors, groupId, identityDigest, ordinalScope, planDigest,
50
+ referenceId, studyId];
@@ -0,0 +1,195 @@
1
+ import fs from "node:fs/promises";
2
+ import path from "node:path";
3
+
4
+ import {
5
+ createToolcraftMotionInputError,
6
+ } from "./toolcraft-motion-reference-stage.mjs";
7
+
8
+ const SOURCE_KINDS = new Set([
9
+ "contact-sheet", "frame-sequence", "gif", "screen-recording", "video",
10
+ ]);
11
+ const TIMED_CONTAINER_EXTENSIONS = new Set([
12
+ ".avi", ".m4v", ".mkv", ".mov", ".mp4", ".mpeg", ".mpg", ".webm",
13
+ ]);
14
+ const SINGLETONS = new Set([
15
+ "--fps", "--grid", "--kind", "--segment", "--source", "--timestamps-file",
16
+ ]);
17
+
18
+ function fail(message) {
19
+ throw createToolcraftMotionInputError(message);
20
+ }
21
+
22
+ function flagValue(argv, index) {
23
+ const value = argv[index + 1];
24
+ if (value === undefined || value.startsWith("--")) {
25
+ fail(`${argv[index]} requires a value; provide one immediately after the flag.`);
26
+ }
27
+ return value;
28
+ }
29
+
30
+ function rangeValue(value, flag) {
31
+ const match = /^([+]?(?:\d+(?:\.\d*)?|\.\d+)):([+]?(?:\d+(?:\.\d*)?|\.\d+))$/u
32
+ .exec(value);
33
+ const startSeconds = match ? Number(match[1]) : NaN;
34
+ const endSeconds = match ? Number(match[2]) : NaN;
35
+ if (!Number.isFinite(startSeconds) || !Number.isFinite(endSeconds) ||
36
+ startSeconds < 0 || endSeconds <= startSeconds) {
37
+ fail(`Provide ${flag} as a finite nonnegative start:end range with end after start.`);
38
+ }
39
+ return { endSeconds, startSeconds };
40
+ }
41
+
42
+ function gridValue(value) {
43
+ const match = /^(\d+)x(\d+)$/u.exec(value);
44
+ const columns = match ? Number(match[1]) : 0;
45
+ const rows = match ? Number(match[2]) : 0;
46
+ if (!Number.isSafeInteger(columns) || columns <= 0 ||
47
+ !Number.isSafeInteger(rows) || rows <= 0) {
48
+ fail("Provide --grid as positive integer columnsxrows, such as 6x4.");
49
+ }
50
+ return { columns, rows };
51
+ }
52
+
53
+ function positiveNumber(value, flag) {
54
+ const number = Number(value);
55
+ if (value.trim().length === 0 || !Number.isFinite(number) || number <= 0) {
56
+ fail(`Provide ${flag} as a positive finite number.`);
57
+ }
58
+ return number;
59
+ }
60
+
61
+ function validateParsedOptions(options) {
62
+ if (!options.source) fail("Provide --source as exactly one required absolute path.");
63
+ if (!path.isAbsolute(options.source)) fail("Provide an absolute path with --source.");
64
+ if (options.timestampsFile && !path.isAbsolute(options.timestampsFile)) {
65
+ fail("Provide an absolute path with --timestamps-file.");
66
+ }
67
+ if (options.framesPerSecond !== undefined && options.timestampsFile) {
68
+ fail("Choose exactly one timing authority for images: --fps or --timestamps-file.");
69
+ }
70
+ if (options.grid && options.kind !== "contact-sheet") {
71
+ fail("Use --grid only with --kind contact-sheet.");
72
+ }
73
+ if (options.kind === "contact-sheet" && !options.grid) {
74
+ fail("--grid is required when --kind is contact-sheet.");
75
+ }
76
+ if ((options.framesPerSecond !== undefined || options.timestampsFile) &&
77
+ options.kind && options.kind !== "frame-sequence" &&
78
+ options.kind !== "contact-sheet") {
79
+ fail("Use timing for images only with frame-sequence or contact-sheet sources.");
80
+ }
81
+ }
82
+
83
+ export function parseToolcraftMotionReferenceArgs(argv) {
84
+ if (!Array.isArray(argv)) fail("Pass command arguments as an array.");
85
+ const args = argv[0] === "--" ? argv.slice(1) : argv;
86
+ const options = {};
87
+ const seen = new Set();
88
+ for (let index = 0; index < args.length; index += 2) {
89
+ const flag = args[index];
90
+ if (typeof flag !== "string" || !flag.startsWith("--")) {
91
+ fail("Use supported --flag value argument pairs only.");
92
+ }
93
+ if (![...SINGLETONS, "--focus"].includes(flag)) {
94
+ fail("Use only --source, --kind, --focus, --segment, --fps, --grid, and --timestamps-file.");
95
+ }
96
+ if (SINGLETONS.has(flag) && seen.has(flag)) {
97
+ fail("Provide each single-value option only once.");
98
+ }
99
+ seen.add(flag);
100
+ const value = flagValue(args, index);
101
+ if (flag === "--source") options.source = value;
102
+ if (flag === "--kind") {
103
+ if (!SOURCE_KINDS.has(value)) {
104
+ fail("Set --kind to video, gif, screen-recording, frame-sequence, or contact-sheet.");
105
+ }
106
+ options.kind = value;
107
+ }
108
+ if (flag === "--focus") {
109
+ options.focus = [...(options.focus ?? []), rangeValue(value, "--focus range")];
110
+ }
111
+ if (flag === "--segment") options.segment = rangeValue(value, "--segment range");
112
+ if (flag === "--grid") options.grid = gridValue(value);
113
+ if (flag === "--fps") options.framesPerSecond = positiveNumber(value, "--fps");
114
+ if (flag === "--timestamps-file") options.timestampsFile = value;
115
+ }
116
+ validateParsedOptions(options);
117
+ return options;
118
+ }
119
+
120
+ export async function resolveToolcraftMotionReferenceSourceKind(
121
+ parsed,
122
+ { stat = fs.stat } = {},
123
+ ) {
124
+ validateParsedOptions(parsed);
125
+ if (parsed.kind) return parsed.kind;
126
+ let status;
127
+ try {
128
+ status = await stat(parsed.source);
129
+ } catch (error) {
130
+ throw createToolcraftMotionInputError(
131
+ "Check that --source points to a readable file or directory.", error,
132
+ );
133
+ }
134
+ if (status.isDirectory()) return "frame-sequence";
135
+ const extension = path.extname(parsed.source).toLowerCase();
136
+ if (extension === ".gif") return "gif";
137
+ if (TIMED_CONTAINER_EXTENSIONS.has(extension)) return "video";
138
+ fail("Explicitly set --kind for sources without a recognized media extension.");
139
+ }
140
+
141
+ function validateTimedRanges(parsed, durationSeconds, timingMode) {
142
+ const ranges = [
143
+ ...(parsed.segment ? [["segment", parsed.segment]] : []),
144
+ ...(parsed.focus ?? []).map((range) => ["focus", range]),
145
+ ];
146
+ if (ranges.length > 0 && timingMode !== "seconds") {
147
+ fail(`--${ranges[0][0]} requires --fps or --timestamps-file as an explicit timing authority for image sources.`);
148
+ }
149
+ for (const [label, range] of ranges) {
150
+ if (!Number.isFinite(durationSeconds) || range.endSeconds > durationSeconds) {
151
+ fail(`Keep --${label} within the inspected source duration.`);
152
+ }
153
+ if (parsed.segment && label === "focus" &&
154
+ (range.startSeconds < parsed.segment.startSeconds ||
155
+ range.endSeconds > parsed.segment.endSeconds)) {
156
+ fail("Keep every --focus range within the selected --segment.");
157
+ }
158
+ }
159
+ }
160
+
161
+ export async function resolveToolcraftMotionReferenceOptions(
162
+ parsed,
163
+ inspectedSource,
164
+ ) {
165
+ validateParsedOptions(parsed);
166
+ if (!inspectedSource || !Number.isSafeInteger(inspectedSource.decodedFrameCount) ||
167
+ inspectedSource.decodedFrameCount <= 0 || !SOURCE_KINDS.has(inspectedSource.sourceKind)) {
168
+ fail("Choose a readable source that contains at least one visual frame.");
169
+ }
170
+ if (parsed.kind && parsed.kind !== inspectedSource.sourceKind) {
171
+ fail("Make --kind agree with the inspected source kind.");
172
+ }
173
+ if ((parsed.framesPerSecond !== undefined || parsed.timestampsFile) &&
174
+ inspectedSource.sourceKind !== "frame-sequence" &&
175
+ inspectedSource.sourceKind !== "contact-sheet") {
176
+ fail("Use timing for images only with frame-sequence or contact-sheet sources.");
177
+ }
178
+ const timing = inspectedSource.timing;
179
+ if (parsed.framesPerSecond !== undefined &&
180
+ (timing?.authority !== "declared-fps" ||
181
+ timing.framesPerSecond !== parsed.framesPerSecond) ||
182
+ parsed.timestampsFile && timing?.authority !== "declared-timestamps") {
183
+ fail("Apply declared image timing through --fps or --timestamps-file before range validation.");
184
+ }
185
+ const timingMode = inspectedSource.timingMode ??
186
+ (["video", "gif", "screen-recording"].includes(inspectedSource.sourceKind) ?
187
+ "seconds" : "ordinal");
188
+ const durationSeconds = inspectedSource.durationSeconds;
189
+ validateTimedRanges(parsed, durationSeconds, timingMode);
190
+ return {
191
+ ...parsed,
192
+ kind: inspectedSource.sourceKind,
193
+ ...(timing ? { timing } : {}),
194
+ };
195
+ }