davinci-resolve-mcp 2.57.4 → 2.58.0

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 (328) hide show
  1. package/AGENTS.md +20 -0
  2. package/CHANGELOG.md +197 -2
  3. package/LICENSE +1 -1
  4. package/README.md +73 -1
  5. package/bin/davinci-resolve-advanced-mcp.mjs +20 -0
  6. package/docs/README.md +19 -0
  7. package/docs/SKILL.md +62 -1
  8. package/docs/contributing.md +1 -1
  9. package/docs/guides/control-panel.md +9 -0
  10. package/docs/kernels/README.md +21 -0
  11. package/docs/kernels/audio-fairlight-kernel.md +22 -0
  12. package/docs/kernels/color-grade-kernel.md +51 -0
  13. package/docs/kernels/fusion-composition-kernel.md +17 -0
  14. package/docs/kernels/media-pool-ingest-kernel.md +20 -0
  15. package/docs/kernels/render-deliver-kernel.md +32 -0
  16. package/docs/kernels/timeline-conform-interchange-kernel.md +39 -0
  17. package/docs/kernels/timeline-edit-kernel.md +17 -0
  18. package/docs/process/release-process.md +29 -7
  19. package/docs/reference/api-coverage.md +1 -1
  20. package/docs/reference/api-limitations.md +38 -1
  21. package/install.py +27 -4
  22. package/package.json +23 -2
  23. package/resolve-advanced/README.md +194 -0
  24. package/resolve-advanced/package.json +50 -0
  25. package/resolve-advanced/server/black-balance.mjs +92 -0
  26. package/resolve-advanced/server/capabilities.mjs +86 -0
  27. package/resolve-advanced/server/cdl-io.mjs +76 -0
  28. package/resolve-advanced/server/contrast-normalize.mjs +180 -0
  29. package/resolve-advanced/server/db-patch.mjs +91 -0
  30. package/resolve-advanced/server/deliverable-entities.mjs +86 -0
  31. package/resolve-advanced/server/deliverable-qc.mjs +270 -0
  32. package/resolve-advanced/server/editorial.mjs +370 -0
  33. package/resolve-advanced/server/exposure-level.mjs +129 -0
  34. package/resolve-advanced/server/extract-frames.mjs +196 -0
  35. package/resolve-advanced/server/ffprobe-media.mjs +88 -0
  36. package/resolve-advanced/server/gamut-legal.mjs +112 -0
  37. package/resolve-advanced/server/grade-body-patch.mjs +246 -0
  38. package/resolve-advanced/server/grade-transfer.mjs +66 -0
  39. package/resolve-advanced/server/group-grade-read.mjs +137 -0
  40. package/resolve-advanced/server/index.mjs +105 -0
  41. package/resolve-advanced/server/lib.mjs +84 -0
  42. package/resolve-advanced/server/libs.mjs +65 -0
  43. package/resolve-advanced/server/lineage-db.mjs +479 -0
  44. package/resolve-advanced/server/lut-apply.mjs +61 -0
  45. package/resolve-advanced/server/match-to-reference.mjs +173 -0
  46. package/resolve-advanced/server/media-inventory.mjs +169 -0
  47. package/resolve-advanced/server/media-ops.mjs +243 -0
  48. package/resolve-advanced/server/node-meta-db.mjs +62 -0
  49. package/resolve-advanced/server/node-provenance.mjs +102 -0
  50. package/resolve-advanced/server/offline-ref-db.mjs +420 -0
  51. package/resolve-advanced/server/offline-ref.mjs +179 -0
  52. package/resolve-advanced/server/project-db.mjs +233 -0
  53. package/resolve-advanced/server/provenance-audit.mjs +210 -0
  54. package/resolve-advanced/server/qc-frame.mjs +224 -0
  55. package/resolve-advanced/server/qc-sampler.mjs +67 -0
  56. package/resolve-advanced/server/readback.mjs +77 -0
  57. package/resolve-advanced/server/render-manifest.mjs +93 -0
  58. package/resolve-advanced/server/reverse-clip-db.mjs +270 -0
  59. package/resolve-advanced/server/runner-apply-contract.mjs +64 -0
  60. package/resolve-advanced/server/runner.mjs +244 -0
  61. package/resolve-advanced/server/saturation-match.mjs +108 -0
  62. package/resolve-advanced/server/scope-read.mjs +257 -0
  63. package/resolve-advanced/server/season-look.mjs +96 -0
  64. package/resolve-advanced/server/shot-intent.mjs +82 -0
  65. package/resolve-advanced/server/shot-match.mjs +147 -0
  66. package/resolve-advanced/server/skin-match.mjs +205 -0
  67. package/resolve-advanced/server/spec-compile.mjs +217 -0
  68. package/resolve-advanced/server/tone-curve-transfer.mjs +184 -0
  69. package/resolve-advanced/server/tool-catalog.mjs +265 -0
  70. package/resolve-advanced/server/tools/audio.mjs +54 -0
  71. package/resolve-advanced/server/tools/audio_plan.mjs +58 -0
  72. package/resolve-advanced/server/tools/capabilities.mjs +16 -0
  73. package/resolve-advanced/server/tools/color_trace.mjs +165 -0
  74. package/resolve-advanced/server/tools/conform.mjs +291 -0
  75. package/resolve-advanced/server/tools/deliverable.mjs +105 -0
  76. package/resolve-advanced/server/tools/drp.mjs +232 -0
  77. package/resolve-advanced/server/tools/drt.mjs +200 -0
  78. package/resolve-advanced/server/tools/drx.mjs +943 -0
  79. package/resolve-advanced/server/tools/editorial.mjs +69 -0
  80. package/resolve-advanced/server/tools/fairlight.mjs +108 -0
  81. package/resolve-advanced/server/tools/fusion.mjs +64 -0
  82. package/resolve-advanced/server/tools/media.mjs +102 -0
  83. package/resolve-advanced/server/tools/offline_ref.mjs +123 -0
  84. package/resolve-advanced/server/tools/pipeline.mjs +137 -0
  85. package/resolve-advanced/server/tools/project_db.mjs +200 -0
  86. package/resolve-advanced/server/tools/project_read.mjs +212 -0
  87. package/resolve-advanced/server/tools/provenance.mjs +73 -0
  88. package/resolve-advanced/server/verify-grade.mjs +113 -0
  89. package/resolve-advanced/server/white-balance-match.mjs +122 -0
  90. package/resolve-advanced/vendor/audio/format-converter.js +330 -0
  91. package/resolve-advanced/vendor/audio/split.js +173 -0
  92. package/resolve-advanced/vendor/audio/trim.js +188 -0
  93. package/resolve-advanced/vendor/audio-fairlight/index.js +391 -0
  94. package/resolve-advanced/vendor/conform-qc/adapters/frame-sampler.js +56 -0
  95. package/resolve-advanced/vendor/conform-qc/adapters/local-ffmpeg-sampler.js +65 -0
  96. package/resolve-advanced/vendor/conform-qc/adapters/rendernode-ffmpeg-sampler.js +50 -0
  97. package/resolve-advanced/vendor/conform-qc/adapters/resolve/driver.py +176 -0
  98. package/resolve-advanced/vendor/conform-qc/adapters/resolve-driver.js +61 -0
  99. package/resolve-advanced/vendor/conform-qc/adapters/resolve-headless-driver.js +86 -0
  100. package/resolve-advanced/vendor/conform-qc/adapters/vision-validator.js +75 -0
  101. package/resolve-advanced/vendor/conform-qc/cli.js +63 -0
  102. package/resolve-advanced/vendor/conform-qc/compare/advisory.js +106 -0
  103. package/resolve-advanced/vendor/conform-qc/compare/decode.js +36 -0
  104. package/resolve-advanced/vendor/conform-qc/compare/index.js +61 -0
  105. package/resolve-advanced/vendor/conform-qc/compare/locate.js +70 -0
  106. package/resolve-advanced/vendor/conform-qc/compare/metrics.js +409 -0
  107. package/resolve-advanced/vendor/conform-qc/docs/runbook.md +72 -0
  108. package/resolve-advanced/vendor/conform-qc/index.js +49 -0
  109. package/resolve-advanced/vendor/conform-qc/knowledge/index.js +110 -0
  110. package/resolve-advanced/vendor/conform-qc/ops/conform-core.js +137 -0
  111. package/resolve-advanced/vendor/conform-qc/ops/conform-diff.js +109 -0
  112. package/resolve-advanced/vendor/conform-qc/ops/insert.js +108 -0
  113. package/resolve-advanced/vendor/conform-qc/ops/local-conform.js +61 -0
  114. package/resolve-advanced/vendor/conform-qc/ops/patch.js +80 -0
  115. package/resolve-advanced/vendor/conform-qc/ops/trigger.js +32 -0
  116. package/resolve-advanced/vendor/conform-qc/ops/verify.js +105 -0
  117. package/resolve-advanced/vendor/conform-qc/ops/workflow.js +30 -0
  118. package/resolve-advanced/vendor/conform-qc/oracle/emulate.js +155 -0
  119. package/resolve-advanced/vendor/conform-qc/oracle/index.js +38 -0
  120. package/resolve-advanced/vendor/conform-qc/oracle/resolve.js +196 -0
  121. package/resolve-advanced/vendor/conform-qc/package.json +16 -0
  122. package/resolve-advanced/vendor/conform-qc/packaging/emit-aaf.js +30 -0
  123. package/resolve-advanced/vendor/conform-qc/packaging/emit-fcp7.js +64 -0
  124. package/resolve-advanced/vendor/conform-qc/packaging/index.js +169 -0
  125. package/resolve-advanced/vendor/conform-qc/packaging/media-ops.js +56 -0
  126. package/resolve-advanced/vendor/conform-qc/packaging/otio.js +50 -0
  127. package/resolve-advanced/vendor/conform-qc/packaging/relink.js +50 -0
  128. package/resolve-advanced/vendor/conform-qc/packaging/surgical-relink.js +195 -0
  129. package/resolve-advanced/vendor/conform-qc/parse/index.js +54 -0
  130. package/resolve-advanced/vendor/conform-qc/parse/merge-dto.js +27 -0
  131. package/resolve-advanced/vendor/conform-qc/parse/xmeml-geometry.js +256 -0
  132. package/resolve-advanced/vendor/conform-qc/reference/burnin-ocr.js +100 -0
  133. package/resolve-advanced/vendor/conform-qc/reference/sidecar.js +37 -0
  134. package/resolve-advanced/vendor/conform-qc/repair/analysis.js +62 -0
  135. package/resolve-advanced/vendor/conform-qc/repair/index.js +78 -0
  136. package/resolve-advanced/vendor/conform-qc/repair/media-analysis.js +139 -0
  137. package/resolve-advanced/vendor/conform-qc/repair/media-index.js +135 -0
  138. package/resolve-advanced/vendor/conform-qc/repair/strategies.js +250 -0
  139. package/resolve-advanced/vendor/conform-qc/report/index.js +133 -0
  140. package/resolve-advanced/vendor/conform-qc/roles/index.js +96 -0
  141. package/resolve-advanced/vendor/conform-qc/synthetic/generate.js +233 -0
  142. package/resolve-advanced/vendor/conform-qc/test/advisory.test.js +85 -0
  143. package/resolve-advanced/vendor/conform-qc/test/cli.test.js +39 -0
  144. package/resolve-advanced/vendor/conform-qc/test/compare.test.js +152 -0
  145. package/resolve-advanced/vendor/conform-qc/test/conform-diff.test.js +70 -0
  146. package/resolve-advanced/vendor/conform-qc/test/emulate.test.js +51 -0
  147. package/resolve-advanced/vendor/conform-qc/test/frame-sampler.test.js +30 -0
  148. package/resolve-advanced/vendor/conform-qc/test/geometry.test.js +112 -0
  149. package/resolve-advanced/vendor/conform-qc/test/knowledge.test.js +56 -0
  150. package/resolve-advanced/vendor/conform-qc/test/lifecycle.test.js +110 -0
  151. package/resolve-advanced/vendor/conform-qc/test/local-ffmpeg-sampler.test.js +66 -0
  152. package/resolve-advanced/vendor/conform-qc/test/locate.test.js +63 -0
  153. package/resolve-advanced/vendor/conform-qc/test/media-analysis.test.js +63 -0
  154. package/resolve-advanced/vendor/conform-qc/test/media-index-normalized.test.js +65 -0
  155. package/resolve-advanced/vendor/conform-qc/test/media-index-origin.test.js +53 -0
  156. package/resolve-advanced/vendor/conform-qc/test/metrics-fast.test.js +91 -0
  157. package/resolve-advanced/vendor/conform-qc/test/oracle-reverse.test.js +86 -0
  158. package/resolve-advanced/vendor/conform-qc/test/oracle.test.js +136 -0
  159. package/resolve-advanced/vendor/conform-qc/test/p0-acceptance.test.js +60 -0
  160. package/resolve-advanced/vendor/conform-qc/test/p1-acceptance.test.js +75 -0
  161. package/resolve-advanced/vendor/conform-qc/test/p45-patch-acceptance.test.js +102 -0
  162. package/resolve-advanced/vendor/conform-qc/test/packaging.test.js +133 -0
  163. package/resolve-advanced/vendor/conform-qc/test/parse-stubs.test.js +28 -0
  164. package/resolve-advanced/vendor/conform-qc/test/repair-strategies.test.js +128 -0
  165. package/resolve-advanced/vendor/conform-qc/test/repair.test.js +84 -0
  166. package/resolve-advanced/vendor/conform-qc/test/report.test.js +52 -0
  167. package/resolve-advanced/vendor/conform-qc/test/resolve-readback.test.js +71 -0
  168. package/resolve-advanced/vendor/conform-qc/test/roles.test.js +107 -0
  169. package/resolve-advanced/vendor/conform-qc/test/runbook.test.js +30 -0
  170. package/resolve-advanced/vendor/conform-qc/test/scale-residual.test.js +40 -0
  171. package/resolve-advanced/vendor/conform-qc/test/sidecar.test.js +51 -0
  172. package/resolve-advanced/vendor/conform-qc/test/smoke.test.js +87 -0
  173. package/resolve-advanced/vendor/conform-qc/test/surgical-relink.test.js +73 -0
  174. package/resolve-advanced/vendor/conform-qc/test/synthetic.test.js +98 -0
  175. package/resolve-advanced/vendor/conform-qc/test/verify.test.js +93 -0
  176. package/resolve-advanced/vendor/conform-qc/test/vision-validator.test.js +43 -0
  177. package/resolve-advanced/vendor/conform-qc/test/workflow-driver.test.js +46 -0
  178. package/resolve-advanced/vendor/conform-qc/toolset/index.js +53 -0
  179. package/resolve-advanced/vendor/drp-format/README.md +116 -0
  180. package/resolve-advanced/vendor/drp-format/__tests__/_resolve-verify.js +39 -0
  181. package/resolve-advanced/vendor/drp-format/__tests__/add-media-clip.test.js +30 -0
  182. package/resolve-advanced/vendor/drp-format/__tests__/assemble-timeline.test.js +68 -0
  183. package/resolve-advanced/vendor/drp-format/__tests__/author-project.test.js +57 -0
  184. package/resolve-advanced/vendor/drp-format/__tests__/composition-text.test.js +98 -0
  185. package/resolve-advanced/vendor/drp-format/__tests__/diff.test.js +272 -0
  186. package/resolve-advanced/vendor/drp-format/__tests__/effect-filters-compressed.test.js +143 -0
  187. package/resolve-advanced/vendor/drp-format/__tests__/fixtures/p0-3-session30-empty-effectfilters.drp +0 -0
  188. package/resolve-advanced/vendor/drp-format/__tests__/fixtures/retimed-timemap-50pct.hex +1 -0
  189. package/resolve-advanced/vendor/drp-format/__tests__/fixtures/retimed-timemap-dynamic.hex +1 -0
  190. package/resolve-advanced/vendor/drp-format/__tests__/inject-grades.test.js +238 -0
  191. package/resolve-advanced/vendor/drp-format/__tests__/keyed-dict.test.js +85 -0
  192. package/resolve-advanced/vendor/drp-format/__tests__/lut-refs.test.js +119 -0
  193. package/resolve-advanced/vendor/drp-format/__tests__/media-blobs.test.js +25 -0
  194. package/resolve-advanced/vendor/drp-format/__tests__/media-timemap.test.js +119 -0
  195. package/resolve-advanced/vendor/drp-format/__tests__/place-fusion-title.test.js +52 -0
  196. package/resolve-advanced/vendor/drp-format/__tests__/place-generator.test.js +40 -0
  197. package/resolve-advanced/vendor/drp-format/__tests__/place-transition.test.js +48 -0
  198. package/resolve-advanced/vendor/drp-format/__tests__/protobuf-wire.test.js +64 -0
  199. package/resolve-advanced/vendor/drp-format/__tests__/relink-media.test.js +106 -0
  200. package/resolve-advanced/vendor/drp-format/__tests__/splice-clips.test.js +256 -0
  201. package/resolve-advanced/vendor/drp-format/assemble-timeline.js +65 -0
  202. package/resolve-advanced/vendor/drp-format/author-project.js +118 -0
  203. package/resolve-advanced/vendor/drp-format/composition-text.js +213 -0
  204. package/resolve-advanced/vendor/drp-format/curves-encoder.js +763 -0
  205. package/resolve-advanced/vendor/drp-format/diff.js +310 -0
  206. package/resolve-advanced/vendor/drp-format/drp-packager.js +201 -0
  207. package/resolve-advanced/vendor/drp-format/drp-validator.js +809 -0
  208. package/resolve-advanced/vendor/drp-format/effect-encoder.js +640 -0
  209. package/resolve-advanced/vendor/drp-format/extract-lut-refs.js +119 -0
  210. package/resolve-advanced/vendor/drp-format/grade-encoder.example.js +133 -0
  211. package/resolve-advanced/vendor/drp-format/grade-encoder.js +401 -0
  212. package/resolve-advanced/vendor/drp-format/grade-encoder.test.js +171 -0
  213. package/resolve-advanced/vendor/drp-format/grade-encoder.verify.js +249 -0
  214. package/resolve-advanced/vendor/drp-format/grade-node-extractor.js +495 -0
  215. package/resolve-advanced/vendor/drp-format/grade-parameter-decoder.js +774 -0
  216. package/resolve-advanced/vendor/drp-format/index.js +174 -0
  217. package/resolve-advanced/vendor/drp-format/inject-grades.js +219 -0
  218. package/resolve-advanced/vendor/drp-format/keyed-dict.js +227 -0
  219. package/resolve-advanced/vendor/drp-format/marker-encoder.js +532 -0
  220. package/resolve-advanced/vendor/drp-format/media-blobs.js +53 -0
  221. package/resolve-advanced/vendor/drp-format/media-timemap.js +164 -0
  222. package/resolve-advanced/vendor/drp-format/mp-folder-builder.js +293 -0
  223. package/resolve-advanced/vendor/drp-format/node-tree-encoder.js +1246 -0
  224. package/resolve-advanced/vendor/drp-format/package.json +18 -0
  225. package/resolve-advanced/vendor/drp-format/place-fusion-title.js +131 -0
  226. package/resolve-advanced/vendor/drp-format/place-generator.js +77 -0
  227. package/resolve-advanced/vendor/drp-format/place-transition.js +91 -0
  228. package/resolve-advanced/vendor/drp-format/power-window-encoder.js +776 -0
  229. package/resolve-advanced/vendor/drp-format/protobuf-wire.js +111 -0
  230. package/resolve-advanced/vendor/drp-format/qualifier-encoder.js +706 -0
  231. package/resolve-advanced/vendor/drp-format/relink-media.js +218 -0
  232. package/resolve-advanced/vendor/drp-format/rich-title-encoder.js +447 -0
  233. package/resolve-advanced/vendor/drp-format/seq-container-builder.js +572 -0
  234. package/resolve-advanced/vendor/drp-format/seq-surgery.js +155 -0
  235. package/resolve-advanced/vendor/drp-format/splice-clips.js +445 -0
  236. package/resolve-advanced/vendor/drp-format/templates/empty-project.drp +0 -0
  237. package/resolve-advanced/vendor/drp-format/templates/fusion-title.xml +56 -0
  238. package/resolve-advanced/vendor/drp-format/templates/generator-solid-color.xml +21 -0
  239. package/resolve-advanced/vendor/drp-format/templates/media-clip-h264.drp +0 -0
  240. package/resolve-advanced/vendor/drp-format/templates/transition-cross-dissolve.xml +22 -0
  241. package/resolve-advanced/vendor/drp-format/tool-registry.descriptor.js +80 -0
  242. package/resolve-advanced/vendor/drp-format/utils/safe-archive.js +54 -0
  243. package/resolve-advanced/vendor/drp-format/xml-builder.js +759 -0
  244. package/resolve-advanced/vendor/drt-format/__tests__/_resolve-verify.js +21 -0
  245. package/resolve-advanced/vendor/drt-format/__tests__/build.test.js +113 -0
  246. package/resolve-advanced/vendor/drt-format/__tests__/multi-format.test.js +93 -0
  247. package/resolve-advanced/vendor/drt-format/__tests__/parse-real-resolve21.test.js +51 -0
  248. package/resolve-advanced/vendor/drt-format/__tests__/parse.test.js +134 -0
  249. package/resolve-advanced/vendor/drt-format/__tests__/resolve-versions.test.js +120 -0
  250. package/resolve-advanced/vendor/drt-format/__tests__/schema-fingerprint.test.js +62 -0
  251. package/resolve-advanced/vendor/drt-format/__tests__/smoke.test.js +27 -0
  252. package/resolve-advanced/vendor/drt-format/__tests__/validate.test.js +109 -0
  253. package/resolve-advanced/vendor/drt-format/capabilities.js +69 -0
  254. package/resolve-advanced/vendor/drt-format/capability-domains.js +56 -0
  255. package/resolve-advanced/vendor/drt-format/drt-builder.js +42 -0
  256. package/resolve-advanced/vendor/drt-format/drt-parser.js +164 -0
  257. package/resolve-advanced/vendor/drt-format/drt-validator.js +98 -0
  258. package/resolve-advanced/vendor/drt-format/index.js +45 -0
  259. package/resolve-advanced/vendor/drt-format/package.json +17 -0
  260. package/resolve-advanced/vendor/drt-format/resolve-versions.js +153 -0
  261. package/resolve-advanced/vendor/drt-format/schema-fingerprint.js +74 -0
  262. package/resolve-advanced/vendor/drt-format/templates/drt-project-shell.xml +124 -0
  263. package/resolve-advanced/vendor/drx-codec/__tests__/curves-roundtrip.test.js +148 -0
  264. package/resolve-advanced/vendor/drx-codec/__tests__/fixtures/p2-1-face-refinement.drx +58 -0
  265. package/resolve-advanced/vendor/drx-codec/__tests__/fixtures/p2-1-filmgrain.drx +58 -0
  266. package/resolve-advanced/vendor/drx-codec/__tests__/fixtures/p2-1-glow.drx +58 -0
  267. package/resolve-advanced/vendor/drx-codec/__tests__/fixtures/p2-1-lens-flare.drx +58 -0
  268. package/resolve-advanced/vendor/drx-codec/__tests__/fixtures/p2-3-beauty-25.drx +58 -0
  269. package/resolve-advanced/vendor/drx-codec/__tests__/fixtures/p2-3-beauty-75.drx +58 -0
  270. package/resolve-advanced/vendor/drx-codec/__tests__/fixtures/p5-1-node-no-lut.drx +58 -0
  271. package/resolve-advanced/vendor/drx-codec/__tests__/fixtures/p5-1-node-with-lut.drx +58 -0
  272. package/resolve-advanced/vendor/drx-codec/__tests__/hsl-curves-roundtrip.test.js +117 -0
  273. package/resolve-advanced/vendor/drx-codec/__tests__/lut-refs-roundtrip.test.js +112 -0
  274. package/resolve-advanced/vendor/drx-codec/__tests__/matte-roundtrip.test.js +167 -0
  275. package/resolve-advanced/vendor/drx-codec/__tests__/ofx-roundtrip.test.js +215 -0
  276. package/resolve-advanced/vendor/drx-codec/__tests__/ofx-slugs-discovery.test.js +149 -0
  277. package/resolve-advanced/vendor/drx-codec/__tests__/power-window-roundtrip.test.js +230 -0
  278. package/resolve-advanced/vendor/drx-codec/__tests__/qualifier-roundtrip.test.js +226 -0
  279. package/resolve-advanced/vendor/drx-codec/cdl-exporter.js +470 -0
  280. package/resolve-advanced/vendor/drx-codec/color-nlp-parser.js +833 -0
  281. package/resolve-advanced/vendor/drx-codec/data/color-nlp-action-mappings.json +2232 -0
  282. package/resolve-advanced/vendor/drx-codec/drx-generator.js +4123 -0
  283. package/resolve-advanced/vendor/drx-codec/drx-merger.js +320 -0
  284. package/resolve-advanced/vendor/drx-codec/drx-parser.js +1808 -0
  285. package/resolve-advanced/vendor/drx-codec/extract-custom-curves.js +215 -0
  286. package/resolve-advanced/vendor/drx-codec/extract-hsl-curves.js +180 -0
  287. package/resolve-advanced/vendor/drx-codec/extract-lut-refs.js +158 -0
  288. package/resolve-advanced/vendor/drx-codec/extract-matte-finesse.js +83 -0
  289. package/resolve-advanced/vendor/drx-codec/extract-ofx-params.js +226 -0
  290. package/resolve-advanced/vendor/drx-codec/extract-power-window.js +184 -0
  291. package/resolve-advanced/vendor/drx-codec/extract-qualifier.js +99 -0
  292. package/resolve-advanced/vendor/drx-codec/index.js +109 -0
  293. package/resolve-advanced/vendor/drx-codec/node-layout.js +249 -0
  294. package/resolve-advanced/vendor/drx-codec/resolved-drx-generator.js +393 -0
  295. package/resolve-advanced/vendor/drx-codec/tool-resolver.js +974 -0
  296. package/resolve-advanced/vendor/drx-codec/training-examples.js +548 -0
  297. package/resolve-advanced/vendor/drx-codec/vocabulary-intents.js +539 -0
  298. package/resolve-advanced/vendor/drx-parameters/CALIBRATION-STATUS.md +319 -0
  299. package/resolve-advanced/vendor/drx-parameters/DRX-VALUE-SCALING.md +251 -0
  300. package/resolve-advanced/vendor/drx-parameters/calibrated-ranges.json +17737 -0
  301. package/resolve-advanced/vendor/drx-parameters/corrector-types.js +120 -0
  302. package/resolve-advanced/vendor/drx-parameters/index.js +193 -0
  303. package/resolve-advanced/vendor/drx-parameters/parameter-codec.js +451 -0
  304. package/resolve-advanced/vendor/drx-parameters/parameter-ids.js +1292 -0
  305. package/resolve-advanced/vendor/drx-parameters/parameter-ranges.js +570 -0
  306. package/resolve-advanced/vendor/drx-parameters/parameter-validator.js +392 -0
  307. package/resolve-advanced/vendor/drx-parameters/resolvefx-registry.json +14973 -0
  308. package/resolve-advanced/vendor/drx-parameters/tool-registry.descriptor.js +38 -0
  309. package/resolve-advanced/vendor/drx-parameters/tools/drx-analyzer.js +371 -0
  310. package/resolve-advanced/vendor/fairlight/index.js +833 -0
  311. package/resolve-advanced/vendor/fusion-codec/composition-generator.js +401 -0
  312. package/resolve-advanced/vendor/fusion-codec/templates/blur-region.js +103 -0
  313. package/resolve-advanced/vendor/fusion-codec/templates/color-correct.js +87 -0
  314. package/resolve-advanced/vendor/fusion-codec/templates/film-grain.js +106 -0
  315. package/resolve-advanced/vendor/fusion-codec/templates/lower-third.js +241 -0
  316. package/resolve-advanced/vendor/fusion-codec/templates/picture-in-picture.js +188 -0
  317. package/resolve-advanced/vendor/fusion-codec/templates/text-overlay.js +161 -0
  318. package/resolve-advanced/vendor/fusion-codec/templates/title-card.js +184 -0
  319. package/resolve-advanced/vendor/fusion-codec/templates/vignette.js +107 -0
  320. package/resolve-advanced/vendor/fusion-codec/templates/watermark.js +101 -0
  321. package/scripts/agent-rules/README.md +68 -0
  322. package/src/analysis_dashboard.py +233 -1
  323. package/src/granular/common.py +1 -1
  324. package/src/server.py +479 -29
  325. package/src/utils/api_truth.py +117 -0
  326. package/src/utils/cut_ir.py +1 -1
  327. package/src/utils/failure_tracker.py +0 -2
  328. package/src/utils/timeline_xml.py +405 -0
@@ -0,0 +1,133 @@
1
+ 'use strict';
2
+
3
+ /** Package builder + emitters + delivery — client-free (round-trips through own parser). */
4
+
5
+ const test = require('node:test');
6
+ const assert = require('node:assert/strict');
7
+ const fs = require('node:fs');
8
+ const os = require('node:os');
9
+ const path = require('node:path');
10
+
11
+ const pkgRoot = require('../index');
12
+ const P = require('../packaging');
13
+ const { toOtio, readOtioSourceFrames } = require('../packaging/otio');
14
+ const { toFcp7Xml } = require('../packaging/emit-fcp7');
15
+ const { toAafModel, readAafSourceFrames } = require('../packaging/emit-aaf');
16
+ const { parseGeometry } = require('../parse');
17
+ const oracle = require('../oracle');
18
+
19
+ const GOLDEN = JSON.parse(fs.readFileSync(path.join(pkgRoot.reelFixtureDir(), 'golden_oracle.json'), 'utf8'));
20
+
21
+ // A conformed timeline: each clip resolved to its source frame (= expected_source_start).
22
+ const CONFORMED = {
23
+ sequence: GOLDEN.sequence,
24
+ ticksPerFrame: GOLDEN.ticksPerFrame,
25
+ clips: GOLDEN.clips.map((c) => ({
26
+ cutId: `seq${c.seqstart}`,
27
+ seqstart: c.seqstart,
28
+ seqend: c.seqend,
29
+ sourceFrame: c.expected_source_start,
30
+ sampleFrame: c.expected_sample_frame != null ? c.expected_sample_frame : c.expected_source_start,
31
+ scale: c.expected_scale_corrected,
32
+ source_basename: c.source_basename,
33
+ srcW: c.srcW,
34
+ srcH: c.srcH,
35
+ path: `/vol/online/${c.source_basename}`,
36
+ })),
37
+ };
38
+ const CTX = { ticksPerFrame: GOLDEN.ticksPerFrame, sequenceWidth: GOLDEN.sequence.width };
39
+
40
+ test('package: OTIO canonical round-trips conformed source frames (all 327)', () => {
41
+ const otio = toOtio(CONFORMED);
42
+ const back = readOtioSourceFrames(otio);
43
+ assert.equal(back.length, 327);
44
+ for (let i = 0; i < back.length; i++) assert.equal(back[i].sourceFrame, CONFORMED.clips[i].sourceFrame);
45
+ });
46
+
47
+ test('package: FCP7 XML emit round-trips through the parser (in+ticks consistent, all 327)', () => {
48
+ const xml = toFcp7Xml(CONFORMED);
49
+ const parsed = parseGeometry(xml);
50
+ const bySeq = new Map(parsed.clips.map((c) => [c.seqstart, c]));
51
+ let checked = 0;
52
+ for (const c of CONFORMED.clips) {
53
+ const pc = bySeq.get(c.seqstart);
54
+ assert.ok(pc, `emitted clip @${c.seqstart} must re-parse`);
55
+ // The non-negotiable: <in> and <pproTicksIn> are consistent, so the Oracle
56
+ // re-derives the SAME source frame from the emitted XML.
57
+ assert.equal(oracle.derive(pc, CTX).derivedSourceFrame, c.sourceFrame, `@${c.seqstart} round-trip`);
58
+ checked += 1;
59
+ }
60
+ assert.equal(checked, 327);
61
+ });
62
+
63
+ test('package: AAF model round-trips source frames (binary serialization deferred)', () => {
64
+ const back = readAafSourceFrames(toAafModel(CONFORMED));
65
+ assert.equal(back.length, 327);
66
+ assert.equal(back[0].sourceFrame, CONFORMED.clips[0].sourceFrame);
67
+ });
68
+
69
+ test('package: builder matrix — subset of formats + relink media mode', () => {
70
+ const pkg = P.buildPackage(CONFORMED, { mediaMode: 'relink', formats: ['otio', 'fcp7Xml', 'aaf'] });
71
+ assert.ok(pkg.files.otio && pkg.files.fcp7Xml && pkg.files.aaf);
72
+ assert.equal(pkg.media.mode, 'relink');
73
+ assert.equal(pkg.media.copied, false);
74
+ assert.ok(pkg.media.references.length === 327);
75
+ });
76
+
77
+ test('package: full/consolidate media modes are BLOCKED (require volumes)', () => {
78
+ assert.equal(P.buildPackage(CONFORMED, { mediaMode: 'full' }).media.blocked, true);
79
+ assert.equal(P.buildPackage(CONFORMED, { mediaMode: 'consolidate' }).media.blocked, true);
80
+ });
81
+
82
+ test('package: V2 flag track + provenance manifest + ConformPackage record', () => {
83
+ const v2Flags = [{ cutId: 'seq2983', klass: 'wrong-source', note: 'flagged' }];
84
+ const repairByCut = { seq192: { strategy: 'add-subclip-startoffset', confidence: 1 } };
85
+ const pkg = P.buildPackage(CONFORMED, { formats: ['otio'], v2Flags, repairByCut });
86
+ assert.equal(pkg.v2FlagTrack.length, 1);
87
+ assert.equal(pkg.manifest.clips.length, 327);
88
+ const m192 = pkg.manifest.clips.find((c) => c.cutId === 'seq192');
89
+ assert.equal(m192.repairStrategy, 'add-subclip-startoffset');
90
+ assert.equal(pkg.record.type, 'ConformPackage');
91
+ assert.equal(pkg.record.v2FlagCount, 1);
92
+ });
93
+
94
+ test('package: gated auto-apply applies only auto-apply fixes + records undo', () => {
95
+ const small = { sequence: GOLDEN.sequence, ticksPerFrame: GOLDEN.ticksPerFrame, clips: [
96
+ { cutId: 'a', seqstart: 0, sourceFrame: 100, scale: 175 },
97
+ { cutId: 'b', seqstart: 48, sourceFrame: 200, scale: 175 },
98
+ ] };
99
+ const repairResults = [
100
+ { cutId: 'a', repair: { mode: 'auto-apply', strategy: 'normalize-scale', proposal: { fix: { scale: 100 } } } },
101
+ { cutId: 'b', repair: { mode: 'propose-only', strategy: 'find-true-source', proposal: { fix: { scale: 999 } } } },
102
+ ];
103
+ const out = P.applyGatedAutoApply(small, repairResults);
104
+ assert.equal(out.conformed.clips.find((c) => c.cutId === 'a').scale, 100); // auto-applied
105
+ assert.equal(out.conformed.clips.find((c) => c.cutId === 'b').scale, 175); // NOT applied (propose-only)
106
+ assert.equal(out.applied.length, 1);
107
+ assert.equal(out.undo[0].before.scale, 175); // undo records the prior value
108
+ assert.equal(out.deferred[0].cutId, 'b');
109
+ });
110
+
111
+ test('package: delivery to a local folder writes the files', () => {
112
+ const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'cqc-pkg-'));
113
+ try {
114
+ const pkg = P.buildPackage(CONFORMED, { formats: ['otio', 'fcp7Xml'], v2Flags: [{ cutId: 'x' }] });
115
+ const written = P.deliverToFolder(pkg, dir);
116
+ assert.ok(written.some((p) => p.endsWith('conform.otio.json')));
117
+ assert.ok(written.some((p) => p.endsWith('conform.xml')));
118
+ assert.ok(written.some((p) => p.endsWith('manifest.json')));
119
+ assert.ok(fs.existsSync(path.join(dir, 'v2-flags.json')));
120
+ } finally {
121
+ fs.rmSync(dir, { recursive: true, force: true });
122
+ }
123
+ });
124
+
125
+ test('package: delivery to Download Center enqueues keys via injected sink (mock R2)', async () => {
126
+ const uploaded = {};
127
+ const enqueue = async (key, content) => { uploaded[key] = content; };
128
+ const pkg = P.buildPackage(CONFORMED, { formats: ['otio'] });
129
+ const keys = await P.deliverToDownloadCenter(pkg, { enqueue });
130
+ assert.ok(keys.includes('conform/otio'));
131
+ assert.ok(keys.includes('conform/manifest.json'));
132
+ assert.equal(Object.keys(uploaded).length, keys.length);
133
+ });
@@ -0,0 +1,28 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * parse/ contract + format-stub tests. Client-free (no raw material needed).
5
+ */
6
+
7
+ const test = require('node:test');
8
+ const assert = require('node:assert/strict');
9
+
10
+ const parse = require('../parse');
11
+
12
+ test('parse: captured-geometry contract is the shared shape (XMEML conforms)', () => {
13
+ assert.ok(Array.isArray(parse.CAPTURED_CLIP_FIELDS));
14
+ for (const f of ['seqstart', 'xml_in', 'pproTicksIn', 'is_subclip', 'subclip_startoffset', 'scale_premiere', 'srcW', 'srcH', 'source_basename']) {
15
+ assert.ok(parse.CAPTURED_CLIP_FIELDS.includes(f), `contract must include "${f}"`);
16
+ }
17
+ assert.equal(typeof parse.parseGeometry, 'function');
18
+ });
19
+
20
+ test('parse: AAF capture is a stub that throws (conforms to interface)', () => {
21
+ assert.equal(typeof parse.parseGeometryAAF, 'function');
22
+ assert.throws(() => parse.parseGeometryAAF('<aaf/>'), /AAF geometry capture not implemented/);
23
+ });
24
+
25
+ test('parse: OTIO capture is a stub that throws (conforms to interface)', () => {
26
+ assert.equal(typeof parse.parseGeometryOTIO, 'function');
27
+ assert.throws(() => parse.parseGeometryOTIO('{}'), /OTIO geometry capture not implemented/);
28
+ });
@@ -0,0 +1,128 @@
1
+ 'use strict';
2
+
3
+ /** §9 propose-only strategies, media index, analysis, and P3 acceptance. */
4
+
5
+ const test = require('node:test');
6
+ const assert = require('node:assert/strict');
7
+ const fs = require('node:fs');
8
+ const path = require('node:path');
9
+
10
+ const pkg = require('../index');
11
+ const S = require('../repair/strategies');
12
+ const { MediaIndex } = require('../repair/media-index');
13
+ const { detectDupes, proposeTimeline } = require('../repair/analysis');
14
+ const { makeRepairLadder } = require('../repair');
15
+
16
+ const GOLDEN = JSON.parse(fs.readFileSync(path.join(pkg.reelFixtureDir(), 'golden_oracle.json'), 'utf8'));
17
+ const CTX = { ticksPerFrame: GOLDEN.ticksPerFrame, sequenceWidth: GOLDEN.sequence.width, sequenceHeight: GOLDEN.sequence.height, sequenceRate: GOLDEN.sequence.fps };
18
+
19
+ test('media-index: resolves by basename, reel+TC, and proxy↔original variant', () => {
20
+ const idx = new MediaIndex([
21
+ { path: '/vol/scan/Sample S16mm 12R 4K 0821 CR A01-A03.mp4', basename: 'Sample S16mm 12R 4K 0821 CR A01-A03.mp4', reel: 'A001', startTc: '01:00:00:00' },
22
+ { path: '/vol/other/Decoy.mov', basename: 'Decoy.mov' },
23
+ ]);
24
+ assert.equal(idx.byBasename('Decoy.mov'), '/vol/other/Decoy.mov');
25
+ assert.equal(idx.byReelTc('A001', '01:00:00:00'), '/vol/scan/Sample S16mm 12R 4K 0821 CR A01-A03.mp4');
26
+ // A proxy name with "4K-2K" resolves to the 4K scan (2k is variant-noise).
27
+ const found = idx.findTrueSource('Sample S16mm 12R 4K-2K 0821 CR A01-A03.mov');
28
+ assert.ok(found && found.basename.includes('A01-A03'), `proxy should resolve to the scan: ${JSON.stringify(found)}`);
29
+ });
30
+
31
+ test('repair: wrong-source uses identity + media index to propose the true source', () => {
32
+ const idx = new MediaIndex([
33
+ { path: '/vol/scan/Sample A01-A03.mp4', basename: 'Sample S16mm 12R 4K 0821 CR A01-A03.mp4' },
34
+ { path: '/vol/scan/Other.mp4', basename: 'Totally Different Clip.mp4' },
35
+ ]);
36
+ const cut = { cutId: 'seq2983', verdict: 'WRONG', burnInName: 'Sample S16mm 12R 4K 0821 CR A01-A03.mp4' };
37
+ assert.equal(S.wrongSource.detect(cut, { mediaIndex: idx }), true);
38
+ const p = S.wrongSource.propose(cut, { mediaIndex: idx });
39
+ assert.ok(p.fix && p.fix.source_basename.includes('A01-A03'));
40
+ assert.ok(p.confidence > 0.5);
41
+ });
42
+
43
+ test('repair: per-file offset estimated from samples + applied to that file only', () => {
44
+ const k = S.estimatePerFileOffset([{ derived: 100, matched: 211 }, { derived: 200, matched: 311 }, { derived: 50, matched: 161 }]);
45
+ assert.equal(k, 111); // constant +111 (the SAMPLE-style per-file offset)
46
+ const cut = { fileId: 'file-7' };
47
+ assert.equal(S.perFileOffset.detect(cut, { fileOffsets: { 'file-7': 111 } }), true);
48
+ assert.equal(S.perFileOffset.propose(cut, { fileOffsets: { 'file-7': 111 } }).fix.sourceFrameDelta, 111);
49
+ // A noisy sample set (no majority constant) => no offset.
50
+ assert.equal(S.estimatePerFileOffset([{ derived: 1, matched: 2 }, { derived: 1, matched: 9 }, { derived: 1, matched: 40 }]), null);
51
+ });
52
+
53
+ test('repair: measured residual patches ONLY measured clips (non-negotiable #4)', () => {
54
+ const cut = { cutId: 'c1', measuredOffset: { dx: 4, dy: -2 } };
55
+ assert.equal(S.measuredResidual.detect(cut), true);
56
+ assert.deepEqual(S.measuredResidual.propose(cut).fix.positionPatch, { dx: -4, dy: 2, ds: 0 });
57
+ // applyMeasuredPatches leaves unmeasured cuts untouched.
58
+ const out = S.applyMeasuredPatches([{ cutId: 'a' }, { cutId: 'b' }], { a: { dx: 3, dy: 0 } });
59
+ assert.equal(out.find((x) => x.cutId === 'a').patched, true);
60
+ assert.equal(out.find((x) => x.cutId === 'b').patched, false);
61
+ });
62
+
63
+ test('repair: aspect-variant, mixed-framerate, handle-shortfall, missing-media', () => {
64
+ // Aspect: a 4:3 source in a 16:9 sequence.
65
+ assert.equal(S.aspectVariant.detect({ srcW: 1440, srcH: 1080 }, CTX), true);
66
+ assert.equal(S.aspectVariant.detect({ srcW: 2048, srcH: 1306 }, CTX), false); // S16mm 1.568 vs 5:3 seq — fit by scale, not a reframe
67
+ // Mixed rate.
68
+ const mr = S.mixedFrameRate.propose({ sourceFrame: 100, clipRate: 48 }, { sequenceRate: 24 });
69
+ assert.equal(mr.fix.sourceFrame, 200);
70
+ // Handle shortfall near media end.
71
+ assert.equal(S.handleShortfall.detect({ mediaLength: 100, usedOut: 98, usedIn: 90, handles: 12 }), true);
72
+ assert.equal(S.handleShortfall.propose({ mediaLength: 100, usedOut: 98, usedIn: 90, handles: 12 }).fix.handlesTail, 2);
73
+ // Missing media: no index match, no burn-in.
74
+ assert.equal(S.missingMedia.detect({ pathMissing: true, source_basename: 'Gone.mov' }, {}), true);
75
+ });
76
+
77
+ test('repair: reel-name conform resolves reel+TC', () => {
78
+ const idx = new MediaIndex([{ path: '/vol/A001.mov', basename: 'A001.mov', reel: 'A001', startTc: '01:00:00:00' }]);
79
+ const cut = { reel: 'A001', sourceTc: '01:00:00:00' };
80
+ assert.equal(S.reelNameConform.detect(cut, { mediaIndex: idx }), true);
81
+ assert.equal(S.reelNameConform.propose(cut, { mediaIndex: idx }).fix.path, '/vol/A001.mov');
82
+ });
83
+
84
+ test('check: dupes — repeated-source clusters reported, uniques not flagged', () => {
85
+ const dupes = detectDupes(GOLDEN.clips);
86
+ assert.ok(dupes.length > 0, 'SAMPLE reel reuses sources');
87
+ for (const d of dupes) assert.ok(d.count > 1);
88
+ // A reel of all-unique sources reports nothing.
89
+ assert.deepEqual(detectDupes([{ cutId: 1, source_basename: 'a' }, { cutId: 2, source_basename: 'b' }]), []);
90
+ });
91
+
92
+ test('propose-timeline: auto-applied fixes vs V2 flag track (V1 gap per flag)', () => {
93
+ const results = [
94
+ { cutId: 'c1', seqstart: 0, repair: { mode: 'auto-apply', strategy: 'normalize-scale', proposal: { fix: { scale: 100 } } } },
95
+ { cutId: 'c2', seqstart: 48, repair: { mode: 'propose-only', klass: 'wrong-source', diagnosis: 'needs review', proposal: { fix: { path: '/x' } } } },
96
+ { cutId: 'c3', seqstart: 96, repair: { mode: 'flag-v2', klass: 'unrepairable', diagnosis: 'offline' } },
97
+ ];
98
+ const out = proposeTimeline(results);
99
+ assert.equal(out.summary.autoApplied, 1);
100
+ assert.equal(out.summary.flagged, 2);
101
+ assert.equal(out.correctedTimeline.find((c) => c.cutId === 'c1').v1Gap, false);
102
+ assert.equal(out.correctedTimeline.find((c) => c.cutId === 'c2').v1Gap, true);
103
+ assert.equal(out.v2Flags.length, 2);
104
+ });
105
+
106
+ test('P3 ACCEPTANCE: broken SAMPLE variant — correct fixes proposed, only irreducible flagged', async () => {
107
+ const ladder = makeRepairLadder({});
108
+ // The "broken variant": all 327 clips carry un-normalized scale (the v3 scale mistake).
109
+ const results = [];
110
+ for (const c of GOLDEN.clips) {
111
+ const rep = await ladder.repair({ ...c, cutId: `seq${c.seqstart}` }, CTX);
112
+ results.push({ cutId: `seq${c.seqstart}`, seqstart: c.seqstart, expected: c.expected_scale_corrected, repair: rep });
113
+ }
114
+ // Every scale-broken clip auto-applies to the correct normalized scale.
115
+ for (const r of results) {
116
+ assert.equal(r.repair.mode, 'auto-apply', `@${r.seqstart} should auto-apply`);
117
+ assert.ok(Math.abs(r.repair.proposal.fix.scale - r.expected) < 0.001);
118
+ }
119
+ // Add one genuinely-wrong, no-true-source cut: it must flag, not auto-fix.
120
+ const wrong = await ladder.repair({ cutId: 'seqWRONG', verdict: 'WRONG', pathMissing: true, source_basename: 'Lost.mov', is_subclip: false, xml_in: 0, pproTicksIn: 0, scale_premiere: 100, srcW: CTX.sequenceWidth }, CTX);
121
+ results.push({ cutId: 'seqWRONG', seqstart: -1, repair: wrong });
122
+ const proposed = proposeTimeline(results);
123
+ assert.equal(proposed.summary.autoApplied, 327);
124
+ assert.equal(proposed.summary.flagged, 1);
125
+ assert.equal(proposed.v2Flags[0].cutId, 'seqWRONG');
126
+ // eslint-disable-next-line no-console
127
+ console.log(`[P3] ACCEPTANCE: 327 auto-fixed (scale normalized) + 1 irreducible flagged to V2`);
128
+ });
@@ -0,0 +1,84 @@
1
+ 'use strict';
2
+
3
+ /** Repair ladder + §9 deterministic strategies — verified against golden_oracle. */
4
+
5
+ const test = require('node:test');
6
+ const assert = require('node:assert/strict');
7
+ const fs = require('node:fs');
8
+ const path = require('node:path');
9
+
10
+ const pkg = require('../index');
11
+ const { makeRepairLadder, strategies } = require('../repair');
12
+ const { ConformKnowledge } = require('../knowledge');
13
+
14
+ const GOLDEN = JSON.parse(fs.readFileSync(path.join(pkg.reelFixtureDir(), 'golden_oracle.json'), 'utf8'));
15
+ const CTX = { ticksPerFrame: GOLDEN.ticksPerFrame, sequenceWidth: GOLDEN.sequence.width };
16
+
17
+ test('repair: scale-double-count normalizes per source width for all 327 (== expected)', () => {
18
+ let checked = 0;
19
+ for (const c of GOLDEN.clips) {
20
+ assert.equal(strategies.scaleDoubleCount.detect(c, CTX), true, `@${c.seqstart} should detect double-count`);
21
+ const p = strategies.scaleDoubleCount.propose(c, CTX);
22
+ assert.ok(Math.abs(p.fix.scale - c.expected_scale_corrected) < 0.001, `@${c.seqstart} ${p.fix.scale} != ${c.expected_scale_corrected}`);
23
+ checked += 1;
24
+ }
25
+ assert.equal(checked, 327);
26
+ });
27
+
28
+ test('repair: subclip-offset reproduces source_start for subclip clips', () => {
29
+ const subs = GOLDEN.clips.filter((c) => c.is_subclip);
30
+ assert.ok(subs.length > 0);
31
+ for (const c of subs) {
32
+ assert.equal(strategies.subclipOffset.detect(c), true);
33
+ assert.equal(strategies.subclipOffset.propose(c).fix.sourceFrame, c.expected_source_start);
34
+ }
35
+ });
36
+
37
+ test('repair: ticks-retime detects only retimes and derives the ticks sample frame', () => {
38
+ const retimes = GOLDEN.clips.filter((c) => strategies.ticksRetime.detect(c, CTX));
39
+ assert.ok(retimes.length >= 1 && retimes.length < GOLDEN.clips.length, 'some but not all clips are retimes');
40
+ for (const c of retimes) {
41
+ const p = strategies.ticksRetime.propose(c, CTX);
42
+ assert.equal(p.fix.sampleFrame, Math.round(c.pproTicksIn / CTX.ticksPerFrame));
43
+ assert.notEqual(p.fix.sampleFrame, c.xml_in); // ticks != in for a retime
44
+ }
45
+ });
46
+
47
+ test('repair: stale-relink re-resolves by exact basename via the media index', () => {
48
+ const mediaIndex = { byBasename: (n) => (n === 'A.mov' ? '/vol/online/A.mov' : null) };
49
+ const cut = { source_basename: 'A.mov', pathMissing: true };
50
+ assert.equal(strategies.staleRelink.detect(cut, { mediaIndex }), true);
51
+ assert.equal(strategies.staleRelink.propose(cut, { mediaIndex }).fix.path, '/vol/online/A.mov');
52
+ // No match => no fix.
53
+ const miss = strategies.staleRelink.propose({ source_basename: 'Z.mov', pathMissing: true }, { mediaIndex });
54
+ assert.equal(miss.fix, null);
55
+ });
56
+
57
+ test('repair ladder: auto-applies a deterministic fix and records to ConformKnowledge', async () => {
58
+ const knowledge = new ConformKnowledge();
59
+ const ladder = makeRepairLadder({ knowledge });
60
+ // A subclip cut with scale already normalized (so subclip is the matching strategy).
61
+ const cut = { cutId: 'c1', is_subclip: true, subclip_startoffset: 5146, xml_in: 406, pproTicksIn: 406 * CTX.ticksPerFrame, scale_premiere: 100, srcW: CTX.sequenceWidth };
62
+ const res = await ladder.repair(cut, CTX);
63
+ assert.equal(res.klass, 'subclip-offset');
64
+ assert.equal(res.accepted, true);
65
+ assert.equal(res.mode, 'auto-apply');
66
+ assert.equal(res.proposal.fix.sourceFrame, 5552); // 5146 + 406
67
+ assert.ok(knowledge.query({ class: 'subclip-offset' }).confidence > 0.5);
68
+ });
69
+
70
+ test('repair ladder: an unmatched cut flags to V2', async () => {
71
+ const ladder = makeRepairLadder({});
72
+ const clean = { cutId: 'ok', is_subclip: false, xml_in: 1000, pproTicksIn: 1000 * CTX.ticksPerFrame, scale_premiere: 100, srcW: CTX.sequenceWidth };
73
+ const res = await ladder.repair(clean, CTX);
74
+ assert.equal(res.mode, 'flag-v2');
75
+ assert.equal(res.klass, 'unrepairable');
76
+ });
77
+
78
+ test('repair ladder: re-verify can reject a deterministic fix (propose -> auto-rejected)', async () => {
79
+ const ladder = makeRepairLadder({ reverify: async () => ({ ok: false, confidence: 0.2 }) });
80
+ const cut = { cutId: 'c', is_subclip: true, subclip_startoffset: 100, xml_in: 5, pproTicksIn: 5 * CTX.ticksPerFrame, scale_premiere: 100, srcW: CTX.sequenceWidth };
81
+ const res = await ladder.repair(cut, CTX);
82
+ assert.equal(res.accepted, false, 'a fix that fails re-verify must not auto-apply');
83
+ assert.equal(res.mode, 'auto-rejected');
84
+ });
@@ -0,0 +1,52 @@
1
+ 'use strict';
2
+
3
+ /** QC report model tests — client-free. */
4
+
5
+ const test = require('node:test');
6
+ const assert = require('node:assert/strict');
7
+
8
+ const { makeReport, addCut, badgeFor, toMetadataQc } = require('../report');
9
+
10
+ test('report: summary tallies per-cut verdicts', () => {
11
+ const r = makeReport({ target: 'resolve', tier: 'B' });
12
+ addCut(r, { cutId: 'c1', status: 'MATCH', hasReference: true, refScore: 0.99 });
13
+ addCut(r, { cutId: 'c2', status: 'OFFSET', hasReference: true, refScore: 0.95, offset: { dx: 2, dy: 0, ds: 0 } });
14
+ addCut(r, { cutId: 'c3', status: 'WRONG', hasReference: true, refScore: 0.4 });
15
+ addCut(r, { cutId: 'c4', status: 'REVIEW', hasReference: true, escalated: true });
16
+ addCut(r, { cutId: 'c5', status: 'MATH-VERIFIED', hasReference: false, derivedSourceFrame: 1000 });
17
+ assert.deepEqual(r.summary, { matched: 1, offset: 1, wrong: 1, flagged: 2, review: 1, mathVerified: 1, unreadable: 0 });
18
+ assert.equal(r.perCut.length, 5);
19
+ });
20
+
21
+ test('report: BADGE RULE — no picture, no content-verified', () => {
22
+ // Tier C (no reference) can only be math-verified, never content-verified.
23
+ assert.equal(badgeFor({ status: 'MATH-VERIFIED', hasReference: false }), 'math-verified');
24
+ assert.equal(badgeFor({ status: 'MATCH', hasReference: false }), 'math-verified');
25
+ // With a reference + MATCH => content-verified.
26
+ assert.equal(badgeFor({ status: 'MATCH', hasReference: true }), 'content-verified');
27
+ // A reference that did NOT verify => no positive badge.
28
+ assert.equal(badgeFor({ status: 'WRONG', hasReference: true }), null);
29
+ assert.equal(badgeFor({ status: 'REVIEW', hasReference: true }), null);
30
+ // The guard hard-fails any attempt to mint content-verified without a picture.
31
+ const r = makeReport();
32
+ const entry = addCut(r, { cutId: 'x', status: 'MATH-VERIFIED', hasReference: false });
33
+ assert.equal(entry.badge, 'math-verified');
34
+ });
35
+
36
+ test('report: unknown status is rejected', () => {
37
+ const r = makeReport();
38
+ assert.throws(() => addCut(r, { cutId: 'bad', status: 'PROBABLY', hasReference: true }), /unknown status/);
39
+ });
40
+
41
+ test('report: serializes to the metadata.editorial.qc shape (§11)', () => {
42
+ const r = makeReport({ target: 'resolve', oracleVersion: '1', tier: 'C', runAt: '2026-06-16T00:00:00Z' });
43
+ addCut(r, { cutId: 'seq192', status: 'MATH-VERIFIED', hasReference: false, derivedSourceFrame: 47962, scale: 100 });
44
+ const m = toMetadataQc(r);
45
+ for (const k of ['runAt', 'target', 'oracleVersion', 'tier', 'summary', 'perCut', 'packageKeys']) {
46
+ assert.ok(k in m, `metadata.editorial.qc must have "${k}"`);
47
+ }
48
+ assert.equal(m.perCut[0].cutId, 'seq192');
49
+ assert.equal(m.perCut[0].derivedFrame, 47962);
50
+ assert.equal(m.perCut[0].badge, 'math-verified');
51
+ assert.equal(m.summary.mathVerified, 1);
52
+ });
@@ -0,0 +1,71 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * P2 — Resolve-in-the-loop, verified against the REAL tool (non-negotiable #1).
5
+ *
6
+ * Gated on a reachable headless Resolve AND the raw turnover.xml (git-ignored).
7
+ * Imports the SAMPLE turnover headless (media OFFLINE), reads back each clip's
8
+ * conformed source_start via GetLeftOffset, and asserts it equals the Oracle for
9
+ * all 327 — the calibration that PROVES the Oracle ruleset. Then exports a DRP
10
+ * and re-opens it.
11
+ */
12
+
13
+ const test = require('node:test');
14
+ const assert = require('node:assert/strict');
15
+ const fs = require('node:fs');
16
+ const os = require('node:os');
17
+ const path = require('node:path');
18
+
19
+ const pkg = require('../index');
20
+ const oracle = require('../oracle');
21
+ const { isResolveDriver } = require('../adapters/resolve-driver');
22
+ const { HeadlessResolveDriver } = require('../adapters/resolve-headless-driver');
23
+
24
+ const SAMPLE = pkg.reelFixtureDir();
25
+ const XML = path.join(SAMPLE, 'turnover.xml');
26
+ const GOLDEN = JSON.parse(fs.readFileSync(path.join(SAMPLE, 'golden_oracle.json'), 'utf8'));
27
+ const CTX = { ticksPerFrame: GOLDEN.ticksPerFrame, sequenceWidth: GOLDEN.sequence.width };
28
+
29
+ const driver = new HeadlessResolveDriver();
30
+ const resolveUp = !!driver.ping();
31
+ const haveXml = fs.existsSync(XML);
32
+ const SKIP = resolveUp && haveXml ? false : `Resolve ${resolveUp ? 'up' : 'unreachable'} / turnover ${haveXml ? 'present' : 'absent'} — skipping P2`;
33
+
34
+ test('P2: HeadlessResolveDriver satisfies the ResolveDriver interface', () => {
35
+ assert.equal(isResolveDriver(driver), true);
36
+ });
37
+
38
+ test('P2 read-back + ACCEPTANCE: Resolve clip_where == Oracle for all 327 (offline import)', { skip: SKIP }, async () => {
39
+ await driver.importTimeline(XML);
40
+ const rows = await driver.clipWhere();
41
+ const bySeq = new Map(rows.map((r) => [r.seqstart, r]));
42
+
43
+ let checked = 0;
44
+ const mismatches = [];
45
+ for (const c of GOLDEN.clips) {
46
+ const row = bySeq.get(c.seqstart);
47
+ assert.ok(row, `Resolve read-back missing clip @seq${c.seqstart}`);
48
+ const oracleSrc = oracle.derive(c, CTX).derivedSourceFrame;
49
+ // The real tool == the golden == the Oracle.
50
+ if (row.source_start !== c.expected_source_start || oracleSrc !== row.source_start) {
51
+ mismatches.push({ seq: c.seqstart, resolve: row.source_start, golden: c.expected_source_start, oracle: oracleSrc });
52
+ }
53
+ checked += 1;
54
+ }
55
+ assert.equal(checked, 327);
56
+ assert.deepEqual(mismatches, [], `Oracle-vs-Resolve mismatches: ${JSON.stringify(mismatches.slice(0, 5))}`);
57
+ // eslint-disable-next-line no-console
58
+ console.log(`[P2] CALIBRATION: Resolve clip_where == Oracle for 327/327 (headless, media offline)`);
59
+ }, { timeout: 180000 });
60
+
61
+ test('P2 DRP export: import -> export .drp -> re-open clean', { skip: SKIP }, async () => {
62
+ const out = path.join(os.tmpdir(), `conformqc_${process.pid}.drp`);
63
+ try {
64
+ const res = await driver.authorDrp(XML, out);
65
+ assert.ok(res.drpPath, 'a .drp path is returned');
66
+ assert.equal(res.validArchive, true, 'the exported .drp is a well-formed archive');
67
+ assert.ok(res.entryCount > 0 && res.size > 0, 'the .drp is non-trivial');
68
+ } finally {
69
+ fs.rmSync(out, { force: true });
70
+ }
71
+ }, { timeout: 180000 });
@@ -0,0 +1,107 @@
1
+ 'use strict';
2
+
3
+ /** P5 roles/§10 resolution + trigger model + agent toolset + geometry merge-dto. */
4
+
5
+ const test = require('node:test');
6
+ const assert = require('node:assert/strict');
7
+
8
+ const R = require('../roles');
9
+ const trigger = require('../ops/trigger');
10
+ const toolset = require('../toolset');
11
+ const { mergeGeometry } = require('../parse/merge-dto');
12
+
13
+ test('roles: editorialRole enum + a version carries a role and serializes', () => {
14
+ assert.ok(R.EDITORIAL_ROLES.includes('finishing'));
15
+ assert.equal(R.isValidRole('proxy'), true);
16
+ assert.equal(R.isValidRole('bogus'), false);
17
+ const version = { id: 'v1', width: 4096, editorialRole: 'finishing' };
18
+ assert.equal(JSON.parse(JSON.stringify(version)).editorialRole, 'finishing');
19
+ });
20
+
21
+ test('roles: MediaRelationship (proxy↔scan, frameOffset) persists + is queryable', () => {
22
+ const store = new R.MediaRelationshipStore();
23
+ store.add({ fromAssetId: 'proxyA', toAssetId: 'scanA', kind: 'scan_of', frameOffset: 111, notes: 'SAMPLE proxy->scan' });
24
+ const got = store.forAsset('proxyA');
25
+ assert.equal(got.length, 1);
26
+ assert.equal(got[0].kind, 'scan_of');
27
+ assert.equal(got[0].frameOffset, 111);
28
+ assert.throws(() => R.makeMediaRelationship({ fromAssetId: 'a', toAssetId: 'b', kind: 'nope' }), /kind must be one of/);
29
+ });
30
+
31
+ test('roles: resolution order — relationship > override > role > naming > highest-res', () => {
32
+ const ref = {
33
+ assetId: 'proxyA',
34
+ versions: [
35
+ { name: 'X 4K-2K.mov', width: 2048, path: '/proxy.mov' },
36
+ { name: 'X 4K.mov', width: 4096, path: '/scan-named.mov', role: 'finishing' },
37
+ ],
38
+ };
39
+ const relStore = new R.MediaRelationshipStore([{ fromAssetId: 'proxyA', toAssetId: 'scanAsset', kind: 'scan_of', frameOffset: 111 }]);
40
+ const resolveAssetPath = (id) => (id === 'scanAsset' ? '/vol/scan/X.mov' : null);
41
+
42
+ // 1. relationship wins.
43
+ let r = R.resolveMedia(ref, { relationships: relStore, resolveAssetPath, projectOverride: { proxyA: '/override.mov' } });
44
+ assert.equal(r.via, 'relationship');
45
+ assert.equal(r.path, '/vol/scan/X.mov');
46
+ assert.equal(r.frameOffset, 111);
47
+ // 2. no relationship => project override wins.
48
+ r = R.resolveMedia(ref, { projectOverride: { proxyA: '/override.mov' } });
49
+ assert.equal(r.via, 'project-override');
50
+ // 3. no override => version role (finishing).
51
+ r = R.resolveMedia(ref, {});
52
+ assert.equal(r.via, 'role');
53
+ assert.equal(r.path, '/scan-named.mov');
54
+ // 4. no role => naming (4K not 4K-2K).
55
+ const noRole = { assetId: 'p', versions: [{ name: 'X 4K-2K.mov', width: 2048, path: '/p.mov' }, { name: 'X 4K.mov', width: 4096, path: '/s.mov' }] };
56
+ assert.equal(R.resolveMedia(noRole, {}).via, 'naming');
57
+ // 5. nothing distinctive => highest-res.
58
+ const plain = { assetId: 'p', versions: [{ name: 'a', width: 1920, path: '/a' }, { name: 'b', width: 3840, path: '/b' }] };
59
+ const top = R.resolveMedia(plain, {});
60
+ assert.equal(top.via, 'highest-res');
61
+ assert.equal(top.path, '/b');
62
+ });
63
+
64
+ test('roles: ingest heuristic seeds proxy role + proxy↔scan relationship', () => {
65
+ const seeded = R.ingestHeuristic({ id: 'proxyA', name: 'Sample 4K-2K 0508.mov', width: 2048 }, { finishingAssetId: 'scanA', frameOffset: 111 });
66
+ assert.equal(seeded.role, 'proxy');
67
+ assert.equal(seeded.relationship.kind, 'scan_of');
68
+ assert.equal(seeded.relationship.toAssetId, 'scanA');
69
+ assert.equal(R.ingestHeuristic({ id: 's', name: 'Sample 4K 0821.mp4', width: 4096 }).role, 'finishing');
70
+ });
71
+
72
+ test('trigger: auto Tier-C on sidecar upload vs full conform on-demand', () => {
73
+ const up = trigger.onSidecarUpload();
74
+ assert.equal(up.trigger, 'auto-on-upload');
75
+ assert.equal(up.tier, 'C');
76
+ assert.equal(up.badge, 'math-verified');
77
+ const conform = trigger.onConformAction({ surface: 'cloud' });
78
+ assert.equal(conform.trigger, 'on-demand');
79
+ assert.equal(conform.runsOn, 'render-node');
80
+ assert.equal(trigger.onConformAction({ surface: 'local' }).runsOn, 'post-assistant');
81
+ });
82
+
83
+ test('toolset: agent-callable tool schemas validate + dispatch round-trips to verify', async () => {
84
+ assert.ok(toolset.TOOLS.length >= 2);
85
+ for (const t of toolset.TOOLS) {
86
+ assert.ok(t.name && t.description && t.input_schema);
87
+ assert.match(t.description, /deterministic|advisory/i, 'tool desc must state verdicts are deterministic / vision advisory');
88
+ }
89
+ // dispatch round-trips to an injected verify (no real engine needed for the binding).
90
+ let got = null;
91
+ await toolset.dispatch('conform_qc_verify', { model: { sequence: {}, clips: [] } }, { verify: async (m) => { got = m; return { ok: true }; } });
92
+ assert.ok(got);
93
+ await assert.rejects(() => toolset.dispatch('nope', {}), /unknown tool/);
94
+ });
95
+
96
+ test('parse: geometry merge-dto is additive behind a flag (base unchanged when off)', () => {
97
+ const dto = [{ seqstart: 0, name: 'a' }, { seqstart: 48, name: 'b' }];
98
+ const geo = [{ seqstart: 0, pproTicksIn: 123, scale_premiere: 100 }];
99
+ // Flag off: returns the SAME array (untouched).
100
+ assert.equal(mergeGeometry(dto, geo, { attach: false }), dto);
101
+ // Flag on: geometry attached by seqstart; base fields intact.
102
+ const merged = mergeGeometry(dto, geo, { attach: true });
103
+ assert.notEqual(merged, dto);
104
+ assert.equal(merged[0].name, 'a');
105
+ assert.equal(merged[0].conformGeometry.pproTicksIn, 123);
106
+ assert.equal(merged[1].conformGeometry, undefined); // no geometry for seq48
107
+ });
@@ -0,0 +1,30 @@
1
+ 'use strict';
2
+
3
+ /** Operator runbook content check (P5-docs-runbook) — documents every closed decision. */
4
+
5
+ const test = require('node:test');
6
+ const assert = require('node:assert/strict');
7
+ const fs = require('node:fs');
8
+ const path = require('node:path');
9
+
10
+ const DOC = fs.readFileSync(path.join(__dirname, '..', 'docs', 'runbook.md'), 'utf8');
11
+
12
+ test('runbook: documents both surfaces, tiers, package options, and the closed decisions', () => {
13
+ const must = [
14
+ /two surfaces/i,
15
+ /Cloud/, /Local/,
16
+ /A — burned reference/i, /B — clean ref/i, /C — no reference/i, /math-verified/,
17
+ /no picture, no `?content-verified/i,
18
+ /brightness/i,
19
+ /ADVISORY ONLY/i,
20
+ /never.*(clear a flag|flip a deterministic)/i,
21
+ /host-injected `?VisionValidator|supplied by the host/i, // optional vision seam, no bundled LLM
22
+ /SSIM ≥ 0\.90|PSNR ≥ 25/,
23
+ /VFX alignment \*\*never\*\* auto-applies|VFX alignment never/i,
24
+ /MediaRelationship.*→.*project override.*→.*editorialRole|resolution/i, // §10 order
25
+ /OTIO is the internal canonical/i,
26
+ /<in>.*<pproTicksIn>.*consistent|Resolve reads ticks/i,
27
+ /trigger model/i,
28
+ ];
29
+ for (const re of must) assert.match(DOC, re, `runbook must document: ${re}`);
30
+ });