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
package/src/server.py CHANGED
@@ -2,7 +2,7 @@
2
2
  """
3
3
  DaVinci Resolve MCP Server (Compound Tools)
4
4
 
5
- 32 compound tools covering 100% of the DaVinci Resolve Scripting API (336 methods)
5
+ 34 compound tools covering 100% of the DaVinci Resolve Scripting API (336 methods)
6
6
  plus Fusion Fuse, DCTL, and Resolve-page Script authoring tools.
7
7
  Each tool groups related operations via an 'action' parameter.
8
8
 
@@ -11,7 +11,7 @@ Usage:
11
11
  python src/server.py --full # Start the 341-tool granular server instead
12
12
  """
13
13
 
14
- VERSION = "2.57.4"
14
+ VERSION = "2.58.0"
15
15
 
16
16
  import base64
17
17
  import os
@@ -104,6 +104,7 @@ from src.utils.timeline_title_text import (
104
104
  timeline_item_get_property_map as _timeline_item_get_property_map,
105
105
  )
106
106
  from src.utils.multicam import build_multicam_setup_plan
107
+ from src.utils.timeline_xml import analyze_timeline_xml, sanitize_timeline_xml
107
108
  from src.utils.fusion_group_settings import (
108
109
  FUSION_COMMIT_CHECKLIST,
109
110
  FUSION_GROUP_GUARDRAILS,
@@ -402,7 +403,7 @@ When finished, report exactly which media_analysis call was made, whether artifa
402
403
 
403
404
 
404
405
  # ─── F2 — MCP Prompts for common multi-step workflows ──────────────────────────
405
- # See local/design/agentic-flow-improvements-gameplan-2.md §3 task F2.
406
+ # (agentic-flow improvement F2: structured confirm-token envelope.)
406
407
  # These surface as slash commands in the host UI (e.g. /davinci-resolve:match-bin-to-hero).
407
408
 
408
409
 
@@ -508,6 +509,224 @@ def prep_color_handoff(output_dir: str = "") -> str:
508
509
  5. Surface the manifest path back to the user. Do not write beside source media."""
509
510
 
510
511
 
512
+ # ─── Per-domain workflow routers ───────────────────────────────────────────────
513
+ # Cross-platform depth: these surface as slash commands in EVERY MCP client
514
+ # (Codex, Cursor, Copilot, Continue, Claude Desktop, …), so per-domain routing is
515
+ # not limited to Claude Code's .claude/skills/. They mirror the repo skills of the
516
+ # same name; keep them in sync with docs/kernels/*.
517
+
518
+
519
+ @mcp.prompt(
520
+ name="color_grade_workflow",
521
+ title="Color / Grade Workflow",
522
+ description="Route a grading/look/shot-match task across the live color tools and the offline advanced grading/QC catalog.",
523
+ )
524
+ def color_grade_workflow() -> str:
525
+ return """Color / grade work spans two servers: the live Python server drives a
526
+ running Resolve; the advanced (offline) server computes grades from frames and
527
+ reads/writes .drx/.drp with no Resolve open. Compute offline, apply live.
528
+
529
+ Frame-first rule (non-negotiable): before applying any grade, look, shot match,
530
+ LUT, CDL, DRX, or copied grade, inspect representative Resolve-rendered frames
531
+ (thumbnails/contact sheet/Gallery stills/marker frames) and compare
532
+ bypass/current/after at matched timecodes; restore prior version/node state after
533
+ a temporary bypass. Never grade from metadata or a style label alone. Preserve a
534
+ recoverable grade version.
535
+
536
+ - Live: timeline_item_color.grade_boundary_report / probe_node_graph /
537
+ safe_set_cdl / safe_apply_drx / grade_version_snapshot|restore; graph;
538
+ gallery_stills; color_group.
539
+ - Offline (advanced server `drx` actions): match_to_reference, level_clips,
540
+ skin_match, shot_match, white_balance_match, contrast_normalize,
541
+ saturation_match, black_balance, cdl_io, grade_transfer, lut_apply,
542
+ author_look|carry_look, scope_read|intent_tags|gamut_legal, verify_grade.
543
+ - Value space: drx generate/merge default to space='ui' (panel units, saturation
544
+ 0-100 neutral 50); a `warnings` array means a hue curve rendered FLAT — surface
545
+ it. safe_apply_drx defaults to V1/item0 — always pass explicit track/item indices
546
+ and back up a still first; ApplyGradeFromDRX replaces the graph.
547
+ - Relayout ("Cleanup Node Graph", no UI API): grab still -> drx relayout ->
548
+ graph.reset_all_grades -> safe_apply_drx (a same-structure apply keeps the old
549
+ layout). Whole project: project_db.relayout_node_graphs.
550
+
551
+ Depth: docs/kernels/color-grade-kernel.md, docs/guides/color-decision-guide.md.
552
+ The advanced grading catalog needs `sharp`; call the advanced `capabilities` tool.
553
+ Never modify/transcode/derive source media (see AGENTS.md)."""
554
+
555
+
556
+ @mcp.prompt(
557
+ name="timeline_edit_workflow",
558
+ title="Timeline Edit Workflow",
559
+ description="Route a cutting/trimming/restructuring/changelist task across the live edit tools and the offline editorial tools.",
560
+ )
561
+ def timeline_edit_workflow() -> str:
562
+ return """Editing spans two servers: the live Python server restructures a
563
+ running timeline; the advanced (offline) server authors/diffs .drt files and
564
+ reasons over editorial interchange with no Resolve open.
565
+
566
+ - Live: timeline duplicate_clips/copy_clips/move_clips (include_linked carries
567
+ linked audio); copy_range/duplicate_range/overwrite_range/lift_range (no public
568
+ razor/split — partial overlaps blocked unless allow_partial_item_delete);
569
+ copy_properties (scope with a group list); edit_engine (selects/tighten/swap,
570
+ plan -> confirm -> execute; tighten can carry audio via keep_ranges/include_audio).
571
+ - Offline (advanced server `editorial` actions): parse_interchange (EDL/OTIO/XMEML;
572
+ AAF = honest refuse), turnover_changelist (moved/retimed/replaced/new/gone with
573
+ timing guards), conform_manifest, marker_roundtrip; `drt` for timeline files.
574
+
575
+ Use the offline tools to answer "what changed between v3 and v4" without opening
576
+ either cut. Edits reference existing Media Pool items — never transcode/proxy/derive
577
+ source media (see AGENTS.md).
578
+
579
+ Depth: docs/kernels/timeline-edit-kernel.md, docs/guides/editorial-decision-guide.md."""
580
+
581
+
582
+ @mcp.prompt(
583
+ name="conform_workflow",
584
+ title="Conform / Interchange Workflow",
585
+ description="Route a conform/relink/finishing-QC/grade-trace task across the live conform tools and the offline conform QC engine.",
586
+ )
587
+ def conform_workflow() -> str:
588
+ return """Conforming spans two servers: the live Python server imports/relinks/
589
+ compares a running conform; the advanced (offline) server does frame-oracle QC,
590
+ reverse-subclip repair, lineage, and grade tracing with no Resolve open.
591
+
592
+ - Live: timeline probe_timeline_structure / detect_gaps_overlaps /
593
+ source_range_report / conform_boundary_report; export_timeline_checked /
594
+ import_timeline_checked (drt is the only lossless project-native round-trip;
595
+ EDL/FCPXML drop relationships); detect_missing_media -> build_relink_plan
596
+ (read-only, bounded) -> media_pool.safe_relink with approved paths only.
597
+ - XML import via the scripting API goes OFFLINE (missing-media/generators abort);
598
+ use import_timeline_checked with media sanitize (FCP7/FCPXML), then exact-path
599
+ relink; restart a running MCP server to pick up the sanitize fix.
600
+ - Offline (advanced server): conform (frame-oracle QC — catches wrong-but-similar
601
+ relinks; reversed source_start = masterFrames-1-endoffset; lineage store+diff;
602
+ per-cut frame QC vs reference render), color_trace (carry grades across a
603
+ re-conform), offline_ref (<OfflineClip> patch — no scripting API), editorial,
604
+ drt, project_db.
605
+ - .drt for Resolve 19.1.3: set DbPrjVer 17 -> 16. project_db patches need the
606
+ project CLOSED + iConfirmProjectClosed:true, auto-backup + read-back verify, and
607
+ a full QUIT+relaunch of Resolve before the change is visible.
608
+
609
+ Depth: docs/kernels/timeline-conform-interchange-kernel.md. better-sqlite3 gates
610
+ lineage/reverse/DB, sharp/ffmpeg gate frame compare — call advanced `capabilities`.
611
+ Relink plans are read-only until executed; never derive source media (see AGENTS.md)."""
612
+
613
+
614
+ @mcp.prompt(
615
+ name="delivery_workflow",
616
+ title="Delivery / Deliverable QC Workflow",
617
+ description="Route a render/deliverable-QC/media-provenance task across the live render tools and the offline deliverable/media/provenance tools.",
618
+ )
619
+ def delivery_workflow() -> str:
620
+ return """Delivery spans two servers: the live Python server plans/validates/runs
621
+ renders in a running Resolve; the advanced (offline) server QCs the FINISHED render
622
+ and manages media/provenance with no Resolve open. Deliverable QC is report-only:
623
+ gate=review, never auto-pass-clear.
624
+
625
+ - Live: render probe_render_matrix -> validate_render_settings ->
626
+ safe_set_render_settings (dry-run) -> prepare_render_job (adds, does not start;
627
+ temp output dirs by default). safe_quick_export forces EnableUpload=False and
628
+ needs allow_render=True. GetRenderSettings readback is version/page dependent.
629
+ - Offline `deliverable` actions: deliverable_qc (ffprobe vs spec, pass/fail per
630
+ field), loudness_qc (ebur128 LUFS/true-peak/LRA), reframe_blanking_check,
631
+ conform_completeness, re_delivery_diff, render_manifest, expand_deliverable
632
+ (texted/textless/stems/slate/leader).
633
+ - Offline `media`: ingest_verify (hash seal/verify/dupes), media_inventory,
634
+ sync (picture<->sound TC + drift/MOS), relink_manifest, rename_plan (refuses
635
+ camera originals) / reel_normalize, turnover_package, project_hygiene.
636
+ - Offline `provenance`: grade_provenance, gallery_lineage, cdl_export/cdl_diff
637
+ (round-trip asserted), revision_tracking, episode_report.
638
+
639
+ QC refuses rather than fabricates; gates never auto-clear — surface per-field
640
+ verdicts to a human. deliverable/media QC needs ffmpeg+ffprobe on PATH (GPL, not
641
+ bundled) — call advanced `capabilities`. Render probes touch only synthetic
642
+ fixtures, never user source media (see AGENTS.md).
643
+
644
+ Depth: docs/kernels/render-deliver-kernel.md."""
645
+
646
+
647
+ @mcp.prompt(
648
+ name="fusion_workflow",
649
+ title="Fusion Composition Workflow",
650
+ description="Route a Fusion comp task across the live fusion_comp tools and the offline .comp authoring tool.",
651
+ )
652
+ def fusion_workflow() -> str:
653
+ return """Fusion work spans two servers: the live Python server builds a comp on a
654
+ running timeline item; the advanced (offline) server authors a .comp from a spec
655
+ or template with no Resolve open. Author offline, apply live.
656
+
657
+ - Live: fusion_comp probe_fusion_comp / probe_fusion_tool / safe_add_tool /
658
+ safe_set_inputs / safe_connect_tools / fusion_boundary_report.
659
+ - Offline (advanced `fusion` tool): generate / generate_from_template /
660
+ list_templates / to_api_calls.
661
+
662
+ Flow: fusion generate|to_api_calls offline -> apply live via safe_add_tool ->
663
+ safe_set_inputs -> safe_connect_tools (to_api_calls maps directly onto those).
664
+ Probe first — tool availability and input readability vary by Resolve/Fusion
665
+ build; some inputs coerce or are write-only; bulk mutation needs timeline scope,
666
+ not the active Fusion page.
667
+
668
+ Depth: docs/kernels/fusion-composition-kernel.md. Never derive source media (AGENTS.md)."""
669
+
670
+
671
+ @mcp.prompt(
672
+ name="audio_workflow",
673
+ title="Audio / Fairlight Workflow",
674
+ description="Route an audio/Fairlight task across the live audio tools and the offline planning/bus-routing tools.",
675
+ )
676
+ def audio_workflow() -> str:
677
+ return """Audio work spans two servers: the live Python server drives audio on a
678
+ running Resolve; the advanced (offline) server plans tracks, routes buses, and
679
+ edits audio files with no Resolve open. Plan/measure offline, apply live.
680
+
681
+ - Live: timeline probe_audio_item|track / safe_set_audio_properties /
682
+ safe_auto_sync_audio / voice_isolation_capabilities / subtitle_generation_probe
683
+ / fairlight_boundary_report.
684
+ - Offline `audio_plan` (pure Node): list_templates, select_template, track_plan,
685
+ analyze_coverage, check_loudness (R128 -23 / ATSC -24 / streaming -14).
686
+ - Offline `fairlight`: bus routing has NO scripting API — patches the
687
+ FLStudioModelBA blob. read_buses_from_blob (offline); read_buses_from_db,
688
+ expand_buses, export_template/import_template, backup, restore (DB path; needs
689
+ better-sqlite3; project CLOSED + quit/relaunch like other DB patches).
690
+ - Offline `audio`: split (silence/TC/intervals) / trim / convert (needs ffmpeg on
691
+ PATH — GPL, not bundled). Align/loudness-measure not yet vendored.
692
+
693
+ Timeline audio SetProperty (e.g. Volume) can return false for some item types;
694
+ the public API exposes no Fairlight automation curves — use fairlight for bus
695
+ structure only. The offline audio ops write NEW files to scratch, never over
696
+ source (AGENTS.md).
697
+
698
+ Depth: docs/kernels/audio-fairlight-kernel.md."""
699
+
700
+
701
+ @mcp.prompt(
702
+ name="media_pool_workflow",
703
+ title="Media Pool / Ingest Workflow",
704
+ description="Route a media-pool ingest/organization task across the live media_pool tools and the offline media front-end tool.",
705
+ )
706
+ def media_pool_workflow() -> str:
707
+ return """Media pool work spans two servers: the live Python server imports and
708
+ organizes media in a running Resolve; the advanced (offline) server verifies and
709
+ inventories a card with no Resolve open. Verify offline, import live.
710
+
711
+ - Live: media_pool safe_import_media|sequence|folder / organize_clips /
712
+ normalize_metadata / safe_relink|unlink / link_proxy_checked / set_clip_marks /
713
+ setup_multicam_timeline.
714
+ - Offline `media` (needs ffmpeg + ffprobe on PATH): ingest_verify (hash
715
+ seal/verify/dupes), media_inventory (fps/codec/colorspace/TC + card gaps), sync
716
+ (picture<->sound TC + drift/MOS), relink_manifest, rename_plan (refuses camera
717
+ originals) / reel_normalize, turnover_package, project_hygiene.
718
+
719
+ Rule of thumb: verify + inventory the card offline BEFORE importing, then import
720
+ and organize live. Non-media files are not imported; the kernel never proxies,
721
+ transcodes, or derives source media. Native multicam clip creation isn't in the
722
+ public API — the setup helper preps a stacked timeline you convert in Resolve's
723
+ UI. Never rename/derive camera originals without explicit approval (AGENTS.md).
724
+ For reading/analyzing footage content use the analyze_media prompt; for
725
+ deliverable-side media QC use delivery_workflow.
726
+
727
+ Depth: docs/kernels/media-pool-ingest-kernel.md, docs/guides/multicam-setup-guide.md."""
728
+
729
+
511
730
  # ─── Python Version Check ────────────────────────────────────────────────────
512
731
 
513
732
  _py_ver = sys.version_info[:2]
@@ -862,13 +1081,15 @@ _media_analysis_module.register_caps_overrides_provider(_caps_overrides_provider
862
1081
  def _resolve_safe_dir(path):
863
1082
  """Redirect sandbox/temp paths that Resolve can't access to ~/Desktop/resolve-stills.
864
1083
 
865
- Covers macOS (/var/folders, /private/var), Linux (/tmp, /var/tmp),
866
- and Windows (AppData\\Local\\Temp) sandbox temp directories.
1084
+ Covers macOS (/var/folders, /private/var, /tmp, /private/tmp), Linux (/tmp,
1085
+ /var/tmp), and Windows (AppData\\Local\\Temp) sandbox temp directories.
867
1086
  """
868
1087
  system_temp = tempfile.gettempdir()
869
1088
  _is_sandbox = False
870
1089
  if platform.system() == "Darwin":
871
- _is_sandbox = path.startswith("/var/") or path.startswith("/private/var/")
1090
+ # /tmp is a symlink to /private/tmp on macOS — Resolve's still/gallery export
1091
+ # silently fails into both (live-verified 2026-07-03), same as /var/folders.
1092
+ _is_sandbox = path.startswith(("/var/", "/private/var/", "/tmp/", "/private/tmp/")) or path in ("/tmp", "/private/tmp")
872
1093
  elif platform.system() == "Linux":
873
1094
  _is_sandbox = path.startswith("/tmp") or path.startswith("/var/tmp")
874
1095
  elif platform.system() == "Windows":
@@ -882,8 +1103,7 @@ def _resolve_safe_dir(path):
882
1103
  return os.path.join(os.path.expanduser("~"), "Documents", "resolve-stills")
883
1104
  return path
884
1105
 
885
- # Error envelope categories — see local/design/agentic-flow-improvements-gameplan.md A1
886
- # and local/design/agentic-flow-improvements-gameplan-2.md D1 for the retryable
1106
+ # Error envelope categories (agentic-flow improvements A1/D1) — see the retryable
887
1107
  # default policy. Lock these names; downstream agents and tests route on them.
888
1108
  ERROR_CATEGORIES = (
889
1109
  "precondition", # missing current timeline/clip/project; tell user what to do
@@ -5100,37 +5320,144 @@ def _export_timeline_checked(tl, p: Dict[str, Any]):
5100
5320
  }
5101
5321
 
5102
5322
 
5323
+ def _timeline_media_coverage(tl) -> Dict[str, Any]:
5324
+ """Count how many timeline items are linked to a Media Pool Item vs. offline.
5325
+
5326
+ An FCP7/FCPXML imported timeline whose media did not link has items with no
5327
+ backing Media Pool Item — this surfaces that honestly instead of claiming a
5328
+ clean import.
5329
+ """
5330
+ total = 0
5331
+ linked = 0
5332
+ for track_type in ("video", "audio"):
5333
+ try:
5334
+ count = int(tl.GetTrackCount(track_type) or 0)
5335
+ except Exception:
5336
+ count = 0
5337
+ for i in range(1, count + 1):
5338
+ for item in (tl.GetItemListInTrack(track_type, i) or []):
5339
+ total += 1
5340
+ try:
5341
+ if item.GetMediaPoolItem():
5342
+ linked += 1
5343
+ except Exception:
5344
+ pass
5345
+ return {"total": total, "linked": linked, "offline": total - linked}
5346
+
5347
+
5103
5348
  def _import_timeline_checked(proj, mp, p: Dict[str, Any]):
5104
5349
  path = p.get("path")
5105
5350
  if not path:
5106
5351
  return _err("path is required")
5107
5352
  if not os.path.exists(path):
5108
5353
  return _err(f"path does not exist: {path}")
5109
- if p.get("require_temp_path", True) and not _render_temp_path_ok(path):
5110
- return _err("path must be under the system temp directory unless require_temp_path=False")
5354
+ # sanitize_media (alias: relink_media) rewrites the XML to drop clipitems that
5355
+ # reference missing media or are generators (slug/solid w/ no pathurl) — both
5356
+ # abort Resolve's scripting-API import and leave the timeline fully offline.
5357
+ # The original is only read; the sanitized copy lands in a temp dir, so the
5358
+ # temp-path guard never applies to it.
5359
+ sanitize = bool(p.get("sanitize_media") or p.get("relink_media"))
5360
+ if not sanitize and p.get("require_temp_path", True) and not _render_temp_path_ok(path):
5361
+ return _err(
5362
+ "path must be under the system temp directory unless require_temp_path=False",
5363
+ remediation="Pass require_temp_path=False to import from this location, or "
5364
+ "sanitize_media=True to rewrite the XML for reliable media linking.",
5365
+ )
5111
5366
  options = dict(p.get("options") or {})
5112
5367
  if p.get("timeline_name") and "timelineName" not in options:
5113
5368
  options["timelineName"] = p["timeline_name"]
5114
5369
  if "import_source_clips" in p and "importSourceClips" not in options:
5115
5370
  options["importSourceClips"] = bool(p["import_source_clips"])
5371
+
5372
+ # Optional fuzzy relink: when search roots are given, missing-at-original-path
5373
+ # clips are matched against on-disk media (exact/ext-agnostic/normalized/reel)
5374
+ # and their pathurl rewritten so they relink instead of being dropped.
5375
+ search_roots = p.get("relink_search_roots") or p.get("search_roots")
5376
+ if isinstance(search_roots, str):
5377
+ search_roots = [search_roots]
5378
+ if search_roots and not sanitize:
5379
+ sanitize = True # relinking is meaningless without the sanitize/rewrite pass
5380
+
5381
+ sanitize_report = None
5382
+ import_path = path
5383
+ if sanitize:
5384
+ san_kwargs = {}
5385
+ if search_roots:
5386
+ san_kwargs["search_roots"] = list(search_roots)
5387
+ if p.get("relink_min_confidence") is not None:
5388
+ san_kwargs["min_confidence"] = float(p["relink_min_confidence"])
5389
+ if p.get("verify_visually") or p.get("reference_movie"):
5390
+ san_kwargs["verify_visually"] = True
5391
+ if p.get("reference_movie"):
5392
+ san_kwargs["reference_movie"] = p["reference_movie"]
5393
+ if p.get("verify_threshold") is not None:
5394
+ san_kwargs["verify_threshold"] = float(p["verify_threshold"])
5395
+ try:
5396
+ sres = sanitize_timeline_xml(path, **san_kwargs)
5397
+ except Exception as e:
5398
+ return _err(f"Failed to sanitize timeline XML: {e}",
5399
+ category="invalid_input")
5400
+ import_path = sres["output_path"]
5401
+ sanitize_report = {
5402
+ k: sres[k] for k in (
5403
+ "output_path", "timeline_name", "clip_total", "kept",
5404
+ "removed_total", "missing_media_count", "generator_count",
5405
+ "missing_media", "generators", "relinked_count", "relinked",
5406
+ "ambiguous_count", "ambiguous", "scan",
5407
+ "verified_count", "flagged_count", "flagged", "unverified_count",
5408
+ ) if k in sres
5409
+ }
5410
+
5116
5411
  if p.get("dry_run"):
5117
- return _ok(path=path, options=options, would_import=True)
5412
+ out = _ok(path=path, import_path=import_path, options=options, would_import=True)
5413
+ if sanitize_report is not None:
5414
+ out["sanitize"] = sanitize_report
5415
+ return out
5416
+
5118
5417
  before_ids = set()
5119
5418
  for index in range(1, int(proj.GetTimelineCount() or 0) + 1):
5120
5419
  tl_existing = proj.GetTimelineByIndex(index)
5121
5420
  if tl_existing:
5122
5421
  before_ids.add(str(tl_existing.GetUniqueId()))
5123
5422
  with long_resolve_op("timeline.import_timeline_checked"):
5124
- imported = mp.ImportTimelineFromFile(path, options)
5423
+ imported = mp.ImportTimelineFromFile(import_path, options)
5424
+
5425
+ # Resolve's API returns None even when it created an (offline) timeline. Detect
5426
+ # the newly-created timeline via the before/after id diff so we never report a
5427
+ # false "Failed to import" for what is really a partial success.
5428
+ api_returned_object = bool(imported)
5429
+ if not imported:
5430
+ for index in range(1, int(proj.GetTimelineCount() or 0) + 1):
5431
+ t = proj.GetTimelineByIndex(index)
5432
+ if t and str(t.GetUniqueId()) not in before_ids:
5433
+ imported = t
5125
5434
  if not imported:
5126
- return _err("Failed to import timeline")
5435
+ return _err(
5436
+ "Failed to import timeline (Resolve created no timeline)",
5437
+ remediation=None if sanitize else
5438
+ "This XML may reference missing media or contain generator clips, which "
5439
+ "abort the scripting-API import. Retry with sanitize_media=True.",
5440
+ )
5441
+
5127
5442
  imported_id = str(imported.GetUniqueId())
5128
- return _ok(
5443
+ media = _timeline_media_coverage(imported)
5444
+ out = _ok(
5129
5445
  name=imported.GetName(),
5130
5446
  id=imported_id,
5131
5447
  created_new=imported_id not in before_ids,
5132
5448
  timeline_count=proj.GetTimelineCount(),
5449
+ api_returned_object=api_returned_object,
5450
+ media=media,
5133
5451
  )
5452
+ if sanitize_report is not None:
5453
+ out["sanitize"] = sanitize_report
5454
+ if media["total"] and media["offline"]:
5455
+ msg = f"{media['offline']} of {media['total']} timeline items are offline (no linked media)."
5456
+ if not sanitize:
5457
+ msg += (" Retry with sanitize_media=True to drop missing-media/generator "
5458
+ "clips so the remaining media links automatically.")
5459
+ out["warning"] = msg
5460
+ return out
5134
5461
 
5135
5462
 
5136
5463
  def _timeline_by_selector(proj, p: Dict[str, Any], *, prefix: str):
@@ -6559,6 +6886,78 @@ def _safe_clip_call(clip, method_name: str, *args):
6559
6886
  return None, str(exc)
6560
6887
 
6561
6888
 
6889
+ # Clip-property keys whose SetClipProperty/SetMetadata write returns True but is
6890
+ # silently dropped by Resolve under common project configurations (issue #77).
6891
+ # For these we read the value back and refuse to report success unless it stuck.
6892
+ _WRITEBACK_VERIFY_KEYS = frozenset({"Reel Name"})
6893
+
6894
+ # Per-key hint explaining the most common cause of a non-persisting write.
6895
+ _WRITEBACK_VERIFY_HINTS = {
6896
+ "Reel Name": (
6897
+ "Reel Name is commonly gated by the project setting "
6898
+ "'General Options > Assist using reel names from the:'. When that is "
6899
+ "set to derive reel names from the source clip / embedding / filename "
6900
+ "pattern, Resolve ignores scripted writes to 'Reel Name' (the call "
6901
+ "still returns True). Set that option to blank/manual, or assign reel "
6902
+ "names in the Resolve UI; the scripting API cannot override the gate."
6903
+ ),
6904
+ }
6905
+
6906
+
6907
+ def _normalize_clip_property_value(value):
6908
+ """Coerce a clip-property value to a comparable, trimmed string."""
6909
+ if value is None:
6910
+ return ""
6911
+ return str(value).strip()
6912
+
6913
+
6914
+ def _verify_clip_property_writeback(clip, key, value):
6915
+ """Confirm a just-written, known-unreliable clip property actually persisted.
6916
+
6917
+ Returns a structured failure dict when Resolve accepted the write (returned
6918
+ True) but the value did not stick on read-back; returns ``None`` when the
6919
+ write verified, the key is not in the watch list, or we cannot read it back
6920
+ (in which case we don't override the original success). See issue #77.
6921
+ """
6922
+ if key not in _WRITEBACK_VERIFY_KEYS:
6923
+ return None
6924
+ actual, err = _safe_clip_call(clip, "GetClipProperty", key)
6925
+ if err is not None:
6926
+ return None # can't read back — don't contradict the reported result
6927
+ if isinstance(actual, dict): # GetClipProperty('') style dict, just in case
6928
+ actual = actual.get(key)
6929
+ if _normalize_clip_property_value(actual) == _normalize_clip_property_value(value):
6930
+ return None
6931
+ return {
6932
+ "success": False,
6933
+ "verified": False,
6934
+ "key": key,
6935
+ "requested": value,
6936
+ "actual": actual,
6937
+ "error": (
6938
+ f"Resolve reported success writing {key!r} but the value did not "
6939
+ f"persist (read-back returned {actual!r})."
6940
+ ),
6941
+ "hint": _WRITEBACK_VERIFY_HINTS.get(key),
6942
+ }
6943
+
6944
+
6945
+ def _verify_writeback(clip, written):
6946
+ """Verify a batch of just-written {key: value} pairs.
6947
+
6948
+ Returns the first silent-revert failure dict, else ``None``. Only keys in
6949
+ ``_WRITEBACK_VERIFY_KEYS`` are checked, so ordinary writes pay nothing.
6950
+ """
6951
+ if not isinstance(written, dict):
6952
+ return None
6953
+ for key, value in written.items():
6954
+ if key in _WRITEBACK_VERIFY_KEYS:
6955
+ fail = _verify_clip_property_writeback(clip, key, value)
6956
+ if fail:
6957
+ return fail
6958
+ return None
6959
+
6960
+
6562
6961
  def _media_pool_item_summary(clip):
6563
6962
  if not clip:
6564
6963
  return None
@@ -10213,7 +10612,17 @@ def _normalize_metadata(root, mp, p: Dict[str, Any]):
10213
10612
  third_party_ok = True
10214
10613
  for key, value in third_party.items():
10215
10614
  third_party_ok = bool(clip.SetThirdPartyMetadata(key, value)) and third_party_ok
10216
- results.append({"clip_id": clip_id, "success": ok and third_party_ok})
10615
+ row = {"clip_id": clip_id, "success": ok and third_party_ok}
10616
+ if ok and metadata:
10617
+ silent = _verify_writeback(clip, metadata)
10618
+ if silent:
10619
+ row["success"] = False
10620
+ row["verified"] = False
10621
+ row["unpersisted"] = {silent["key"]: silent["actual"]}
10622
+ row["error"] = silent["error"]
10623
+ if silent.get("hint"):
10624
+ row["hint"] = silent["hint"]
10625
+ results.append(row)
10217
10626
  return {"success": all(row.get("success") for row in results), "count": len(results), "missing": missing, "results": results}
10218
10627
 
10219
10628
 
@@ -11957,7 +12366,11 @@ def resolve_control(action: str, params: Optional[Dict[str, Any]] = None) -> Dic
11957
12366
 
11958
12367
  Actions:
11959
12368
  launch() -> {success, message} — Launch DaVinci Resolve if not running. Call this FIRST if any tool returns a 'Not connected' error.
11960
- get_version() -> {product, version, version_string}
12369
+ get_version() -> {product, version, version_string, mcp: {version, update, update_decision}}
12370
+ — mcp.update_decision piggybacks the cached update check: if its action is
12371
+ "notify" or "prompt", mention the available MCP update to the user ONCE
12372
+ per session (do not nag, do not auto-apply; updates are applied via
12373
+ install.py or the control panel).
11961
12374
  mcp_update_status(force_check?) -> {version, update, decision}
11962
12375
  set_mcp_update_policy(mode) -> {success, version, update, decision}
11963
12376
  ignore_mcp_update() -> {success, version, update, decision}
@@ -12106,7 +12519,7 @@ def resolve_control(action: str, params: Optional[Dict[str, Any]] = None) -> Dic
12106
12519
  # "value": <any JSON>,
12107
12520
  # "confidence": "low|medium|high",
12108
12521
  # "source": "human" | "vision_v0.2",
12109
- # "author": "sam@bradfordoperations.com",
12522
+ # "author": "editor@example.com",
12110
12523
  # "timestamp": "2026-05-19T...Z"
12111
12524
  # }
12112
12525
  # },
@@ -15696,8 +16109,18 @@ def media_pool_item(action: str, params: Optional[Dict[str, Any]] = None) -> Dic
15696
16109
  return {"metadata": _ser(clip.GetMetadata(p.get("key", "")))}
15697
16110
  elif action == "set_metadata":
15698
16111
  if "metadata" in p:
15699
- return {"success": bool(clip.SetMetadata(p["metadata"]))}
15700
- return {"success": bool(clip.SetMetadata(p["key"], p["value"]))}
16112
+ ok = bool(clip.SetMetadata(p["metadata"]))
16113
+ if ok:
16114
+ silent = _verify_writeback(clip, p["metadata"])
16115
+ if silent:
16116
+ return silent
16117
+ return {"success": ok}
16118
+ ok = bool(clip.SetMetadata(p["key"], p["value"]))
16119
+ if ok:
16120
+ silent = _verify_clip_property_writeback(clip, p["key"], p["value"])
16121
+ if silent:
16122
+ return silent
16123
+ return {"success": ok}
15701
16124
  elif action == "get_third_party_metadata":
15702
16125
  return {"metadata": _ser(clip.GetThirdPartyMetadata(p.get("key", "")))}
15703
16126
  elif action == "set_third_party_metadata":
@@ -15707,7 +16130,12 @@ def media_pool_item(action: str, params: Optional[Dict[str, Any]] = None) -> Dic
15707
16130
  elif action == "get_clip_property":
15708
16131
  return {"properties": _ser(clip.GetClipProperty(p.get("key", "")))}
15709
16132
  elif action == "set_clip_property":
15710
- return {"success": bool(clip.SetClipProperty(p["key"], p["value"]))}
16133
+ ok = bool(clip.SetClipProperty(p["key"], p["value"]))
16134
+ if ok:
16135
+ silent = _verify_clip_property_writeback(clip, p["key"], p["value"])
16136
+ if silent:
16137
+ return silent
16138
+ return {"success": ok}
15711
16139
  elif action == "get_clip_color":
15712
16140
  return {"color": clip.GetClipColor()}
15713
16141
  elif action == "set_clip_color":
@@ -17937,7 +18365,25 @@ def timeline(action: str, params: Optional[Dict[str, Any]] = None) -> Dict[str,
17937
18365
  detect_gaps_overlaps(track_types?, min_gap?) -> {gaps, overlaps}
17938
18366
  source_range_report(handles?, merge?) -> {ranges, occurrences}
17939
18367
  export_timeline_checked(path, format?|type?, subtype?, require_temp_path?, dry_run?) -> {success, path, size}
17940
- import_timeline_checked(path, options?, timeline_name?, import_source_clips?, require_temp_path?, dry_run?) -> {success, name, id}
18368
+ import_timeline_checked(path, options?, timeline_name?, import_source_clips?, sanitize_media?, require_temp_path?, dry_run?) -> {success, name, id, media, sanitize?, warning?}
18369
+ Imports an FCP7 xmeml / FCPXML timeline. `media` reports {total, linked, offline}
18370
+ coverage; `warning` flags offline items. sanitize_media=True (recommended for
18371
+ Premiere/FCP exports) rewrites the XML first — dropping clipitems that reference
18372
+ MISSING media or are generators (slug/solid with no media file), both of which
18373
+ otherwise abort the scripting-API import and leave the whole timeline offline.
18374
+ Returns `sanitize` with the kept/removed breakdown. The original is only read;
18375
+ the sanitized copy goes to a temp dir (no require_temp_path needed when sanitizing).
18376
+ relink_search_roots (str|list) enables fuzzy relink: clips missing at their
18377
+ original path are matched against media under those roots (exact/ext-agnostic/
18378
+ normalized/reel + IDF true-source) and their pathurl rewritten so they relink
18379
+ instead of being dropped. relink_min_confidence (default 0.7) gates matches;
18380
+ `sanitize.relinked` / `sanitize.ambiguous` report the outcome.
18381
+ verify_visually=True (or passing reference_movie) frame-checks each proposed
18382
+ relink against a reference (reference_movie at the clip's record position, else
18383
+ the offline proxy at the original path) using a brightness-robust SSIM-structure
18384
+ metric; a conflicting relink is VETOED — reverted and reported under
18385
+ `sanitize.flagged` for human review, never silently applied. verify_threshold
18386
+ (default 0.90) sets the structural-match bar.
17941
18387
  compare_timelines(right_timeline_id?|right_timeline_index?|left_snapshot?, right_snapshot?) -> {match, differences}
17942
18388
  probe_interchange_roundtrip(format?, output_dir?, cleanup_imported?) -> {success, export, import, comparison}
17943
18389
  detect_missing_media(sanitized?|sanitize_paths?, omit_raw_paths?) -> {missing, missing_count, diagnosis}
@@ -18688,7 +19134,7 @@ def timeline_ai(action: str, params: Optional[Dict[str, Any]] = None) -> Dict[st
18688
19134
  @_destructive_op("timeline_item")
18689
19135
  def timeline_item(action: str, params: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
18690
19136
  """Properties, transforms, speed, keyframes, and metadata for a timeline item.
18691
- Identify by track_type, track_index, item_index.
19137
+ Identify by track_type, track_index, item_index (item_index is 0-BASED: 0 = first clip; track_index is 1-based).
18692
19138
 
18693
19139
  Actions:
18694
19140
  get_name(track_type?, track_index?, item_index?) -> {name}
@@ -18926,7 +19372,7 @@ def timeline_item(action: str, params: Optional[Dict[str, Any]] = None) -> Dict[
18926
19372
  @mcp.tool()
18927
19373
  @_destructive_op("timeline_item_markers")
18928
19374
  def timeline_item_markers(action: str, params: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
18929
- """Markers, flags, and clip color on timeline items. Identify by track_type, track_index, item_index.
19375
+ """Markers, flags, and clip color on timeline items. Identify by track_type, track_index, item_index (item_index is 0-BASED: 0 = first clip; track_index is 1-based).
18930
19376
 
18931
19377
  Actions:
18932
19378
  add(frame|frame_id|frameId, color?, name?, note?, duration?, custom_data?, ...) -> {success, frame}
@@ -19003,7 +19449,7 @@ def timeline_item_markers(action: str, params: Optional[Dict[str, Any]] = None)
19003
19449
  @mcp.tool()
19004
19450
  @_destructive_op("timeline_item_fusion")
19005
19451
  def timeline_item_fusion(action: str, params: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
19006
- """Fusion composition operations on timeline items. Identify by track_type, track_index, item_index.
19452
+ """Fusion composition operations on timeline items. Identify by track_type, track_index, item_index (item_index is 0-BASED: 0 = first clip; track_index is 1-based).
19007
19453
 
19008
19454
  Actions:
19009
19455
  add_comp(...) -> {success}
@@ -20263,7 +20709,7 @@ def _grade_evidence_base(proj, item, p: Dict[str, Any]) -> Dict[str, Any]:
20263
20709
  @mcp.tool()
20264
20710
  @_destructive_op("timeline_item_color")
20265
20711
  def timeline_item_color(action: str, params: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
20266
- """Color grading, versions, LUTs, cache, and AI tools on timeline items. Identify by track_type, track_index, item_index.
20712
+ """Color grading, versions, LUTs, cache, and AI tools on timeline items. Identify by track_type, track_index, item_index (item_index is 0-BASED: 0 = first clip; track_index is 1-based).
20267
20713
 
20268
20714
  <when_to_use>
20269
20715
  - For ANY grade recommendation, lead with action="grade_evidence_base" — composes
@@ -20468,7 +20914,7 @@ def timeline_item_color(action: str, params: Optional[Dict[str, Any]] = None) ->
20468
20914
  @mcp.tool()
20469
20915
  @_destructive_op("timeline_item_takes")
20470
20916
  def timeline_item_takes(action: str, params: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
20471
- """Take management on timeline items. Identify by track_type, track_index, item_index.
20917
+ """Take management on timeline items. Identify by track_type, track_index, item_index (item_index is 0-BASED: 0 = first clip; track_index is 1-based).
20472
20918
 
20473
20919
  Actions:
20474
20920
  add(clip_id, start_frame?, end_frame?, ...) -> {success}
@@ -20753,8 +21199,12 @@ def graph(action: str, params: Optional[Dict[str, Any]] = None) -> Dict[str, Any
20753
21199
  set_node_enabled(node_index, enabled, source?, ...) -> {success}
20754
21200
  apply_grade_from_drx(path, grade_mode?, source?, ...) -> {success}
20755
21201
  CATASTROPHIC. REPLACES the entire node graph. Prefer timeline_item_color.safe_apply_drx
20756
- (which snapshots the current version first). All grade_modes are full replacement
20757
- there is no append mode.
21202
+ (confirm-token gated + validated; it does NOT snapshot grab a still/.drx backup
21203
+ first, and ALWAYS pass track_type/track_index/item_index explicitly: the default
21204
+ target is video track 1 item 0, not the current clip). All grade_modes are full
21205
+ replacement — there is no append mode. Same-structure applies keep the existing
21206
+ node layout (positions in the .drx are ignored unless the graph differs, e.g.
21207
+ after reset_all_grades).
20758
21208
  grade_mode: 0="No keyframes" (default), 1="Source Timecode aligned", 2="Start Frames aligned"
20759
21209
  apply_arri_cdl_lut(source?, ...) -> {success}
20760
21210
  DESTRUCTIVE. Bakes a CDL LUT into the graph.
@@ -23804,7 +24254,7 @@ def script_plugin(action: str, params: Optional[Dict[str, Any]] = None) -> Dict[
23804
24254
 
23805
24255
 
23806
24256
  # ═══════════════════════════════════════════════════════════════════════════════
23807
- # MCP Resources — E1 (see local/design/agentic-flow-improvements-gameplan-2.md)
24257
+ # MCP Resources — agentic-flow improvement E1
23808
24258
  #
23809
24259
  # Resources are read-only state surfaces that hosts can pull WITHOUT consuming
23810
24260
  # a turn (unlike tools, which require a tool_use round-trip). They mirror