@hybridlabor-api/aos 4.4.0 → 4.4.2-beta.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 (306) hide show
  1. package/.agents/skills/firecrawl/SKILL.md +149 -0
  2. package/.agents/skills/firecrawl/rules/install.md +82 -0
  3. package/.agents/skills/firecrawl/rules/security.md +26 -0
  4. package/.agents/skills/firecrawl-agent/SKILL.md +58 -0
  5. package/.agents/skills/firecrawl-build/SKILL.md +39 -0
  6. package/.agents/skills/firecrawl-build-interact/SKILL.md +68 -0
  7. package/.agents/skills/firecrawl-build-onboarding/SKILL.md +103 -0
  8. package/.agents/skills/firecrawl-build-onboarding/references/auth-flow.md +39 -0
  9. package/.agents/skills/firecrawl-build-onboarding/references/project-setup.md +20 -0
  10. package/.agents/skills/firecrawl-build-onboarding/references/sdk-installation.md +17 -0
  11. package/.agents/skills/firecrawl-build-scrape/SKILL.md +69 -0
  12. package/.agents/skills/firecrawl-build-search/SKILL.md +69 -0
  13. package/.agents/skills/firecrawl-crawl/SKILL.md +59 -0
  14. package/.agents/skills/firecrawl-download/SKILL.md +70 -0
  15. package/.agents/skills/firecrawl-interact/SKILL.md +84 -0
  16. package/.agents/skills/firecrawl-map/SKILL.md +51 -0
  17. package/.agents/skills/firecrawl-scrape/SKILL.md +69 -0
  18. package/.agents/skills/firecrawl-search/SKILL.md +60 -0
  19. package/.claude/hooks/memb-inject.mjs +6 -0
  20. package/CLAUDE.md +573 -0
  21. package/README.de.md +0 -8
  22. package/README.md +0 -8
  23. package/README.pt.md +0 -8
  24. package/assets/brand/ao-ant-mark.svg +91 -0
  25. package/assets/brand/bdb-core-mark.svg +34 -0
  26. package/assets/brand/heimdall-guardian-mark.svg +16 -0
  27. package/assets/brand/memb-crystal-mark.svg +18 -0
  28. package/bin/aos-config.mjs +89 -0
  29. package/bin/aos-dashboard.mjs +608 -0
  30. package/bin/aos-uninstall.mjs +227 -0
  31. package/docs/sessions/audit-2026-09-14-silent-failures.md +114 -0
  32. package/docs/skills_table.md +0 -3
  33. package/installer.js +428 -237
  34. package/mcps/RhinoMCP/cc-plugin/.claude/settings.json +10 -0
  35. package/mcps/after-effects-mcp/build/index.js +840 -0
  36. package/mcps/after-effects-mcp/build/scripts/applyEffect.jsx +153 -0
  37. package/mcps/after-effects-mcp/build/scripts/applyEffectTemplate.jsx +218 -0
  38. package/mcps/after-effects-mcp/build/scripts/createComposition.jsx +71 -0
  39. package/mcps/after-effects-mcp/build/scripts/createShapeLayer.jsx +147 -0
  40. package/mcps/after-effects-mcp/build/scripts/createSolidLayer.jsx +114 -0
  41. package/mcps/after-effects-mcp/build/scripts/createTextLayer.jsx +115 -0
  42. package/mcps/after-effects-mcp/build/scripts/getLayerInfo.jsx +192 -0
  43. package/mcps/after-effects-mcp/build/scripts/getProjectInfo.jsx +90 -0
  44. package/mcps/after-effects-mcp/build/scripts/listCompositions.jsx +50 -0
  45. package/mcps/after-effects-mcp/build/scripts/mcp-bridge-auto.jsx +1773 -0
  46. package/mcps/after-effects-mcp/build/scripts/setLayerProperties.jsx +160 -0
  47. package/mcps/computer-use-mcp/dist/client.d.ts +150 -0
  48. package/mcps/computer-use-mcp/dist/client.js +136 -0
  49. package/mcps/computer-use-mcp/dist/entrypoint.d.ts +16 -0
  50. package/mcps/computer-use-mcp/dist/entrypoint.js +26 -0
  51. package/mcps/computer-use-mcp/dist/native.d.ts +212 -0
  52. package/mcps/computer-use-mcp/dist/native.js +50 -0
  53. package/mcps/computer-use-mcp/dist/server.d.ts +32 -0
  54. package/mcps/computer-use-mcp/dist/server.js +342 -0
  55. package/mcps/computer-use-mcp/dist/session.d.ts +101 -0
  56. package/mcps/computer-use-mcp/dist/session.js +2372 -0
  57. package/package.json +13 -4
  58. package/skills/global_config/aos-project-init/SKILL.md +97 -130
  59. package/skills/global_config/aos-project-init/assets/openwikiignore.template +62 -0
  60. package/skills/global_config/aos-project-init/scripts/aos-project-doctor.mjs +38 -1
  61. package/skills/global_config/aos-setup/SKILL.md +110 -11
  62. package/skills/global_config/aos-setup/scripts/aos-doctor.mjs +48 -4
  63. package/skills/global_config/ask-tim/SKILL.md +2 -7
  64. package/skills/global_config/openwiki-skill/scripts/openwiki_daemon.py +65 -12
  65. package/bin/setup-saas.mjs +0 -792
  66. package/mcps/blender-mcp-server/.dockerignore +0 -19
  67. package/mcps/blender-mcp-server/.github/workflows/ci.yml +0 -67
  68. package/mcps/blender-mcp-server/.github/workflows/publish-pypi.yml +0 -54
  69. package/mcps/blender-mcp-server/CHANGELOG.md +0 -64
  70. package/mcps/blender-mcp-server/CONTRIBUTING.md +0 -107
  71. package/mcps/blender-mcp-server/Dockerfile +0 -19
  72. package/mcps/blender-mcp-server/LICENSE +0 -21
  73. package/mcps/blender-mcp-server/README.md +0 -409
  74. package/mcps/blender-mcp-server/addon/__init__.py +0 -1440
  75. package/mcps/blender-mcp-server/addon/models.py +0 -223
  76. package/mcps/blender-mcp-server/pyproject.toml +0 -120
  77. package/mcps/blender-mcp-server/scripts/blender_bridge_request.py +0 -71
  78. package/mcps/blender-mcp-server/scripts/blender_create_test_cube.py +0 -49
  79. package/mcps/blender-mcp-server/scripts/blender_scene_info.py +0 -38
  80. package/mcps/blender-mcp-server/scripts/build_addon_zip.sh +0 -29
  81. package/mcps/blender-mcp-server/scripts/demos/README.md +0 -186
  82. package/mcps/blender-mcp-server/scripts/demos/dam_break_scene.py +0 -276
  83. package/mcps/blender-mcp-server/scripts/demos/open_dam_break_gui.py +0 -33
  84. package/mcps/blender-mcp-server/scripts/demos/pipe_movement_study.py +0 -518
  85. package/mcps/blender-mcp-server/scripts/demos/pipe_pressure_failure_scene.py +0 -806
  86. package/mcps/blender-mcp-server/scripts/demos/procedural_dam_break_scene.py +0 -600
  87. package/mcps/blender-mcp-server/scripts/demos/run_dam_break.py +0 -510
  88. package/mcps/blender-mcp-server/scripts/launch_blender_gui.py +0 -41
  89. package/mcps/blender-mcp-server/scripts/library/README.md +0 -64
  90. package/mcps/blender-mcp-server/scripts/library/apply_transforms.py +0 -46
  91. package/mcps/blender-mcp-server/scripts/library/camera.py +0 -51
  92. package/mcps/blender-mcp-server/scripts/library/collections.py +0 -65
  93. package/mcps/blender-mcp-server/scripts/library/create_mesh.py +0 -41
  94. package/mcps/blender-mcp-server/scripts/library/effector.py +0 -48
  95. package/mcps/blender-mcp-server/scripts/library/fluid_domain.py +0 -80
  96. package/mcps/blender-mcp-server/scripts/library/fluid_inflow.py +0 -98
  97. package/mcps/blender-mcp-server/scripts/library/frame_range.py +0 -35
  98. package/mcps/blender-mcp-server/scripts/library/keyframes.py +0 -59
  99. package/mcps/blender-mcp-server/scripts/library/rigid_body.py +0 -52
  100. package/mcps/blender-mcp-server/scripts/library/save_blend.py +0 -40
  101. package/mcps/blender-mcp-server/scripts/server_start.sh +0 -26
  102. package/mcps/blender-mcp-server/src/blender_mcp_server/__init__.py +0 -3
  103. package/mcps/blender-mcp-server/src/blender_mcp_server/headless.py +0 -330
  104. package/mcps/blender-mcp-server/src/blender_mcp_server/server.py +0 -606
  105. package/mcps/davinci-mcp-professional/.bandit +0 -35
  106. package/mcps/davinci-mcp-professional/.claude/commands/security-review.md +0 -191
  107. package/mcps/davinci-mcp-professional/.claude/settings.json +0 -1
  108. package/mcps/davinci-mcp-professional/.editorconfig +0 -27
  109. package/mcps/davinci-mcp-professional/.gitattributes +0 -207
  110. package/mcps/davinci-mcp-professional/.github/CODEOWNERS +0 -2
  111. package/mcps/davinci-mcp-professional/.github/ISSUE_TEMPLATE/bug_report.md +0 -33
  112. package/mcps/davinci-mcp-professional/.github/ISSUE_TEMPLATE/feature_request.md +0 -19
  113. package/mcps/davinci-mcp-professional/.github/PULL_REQUEST_TEMPLATE.md +0 -20
  114. package/mcps/davinci-mcp-professional/.github/SECURITY.md +0 -97
  115. package/mcps/davinci-mcp-professional/.github/dependabot.yml +0 -27
  116. package/mcps/davinci-mcp-professional/.github/workflows/ci.yml +0 -39
  117. package/mcps/davinci-mcp-professional/ATTRIBUTION.md +0 -4
  118. package/mcps/davinci-mcp-professional/AUTHORS.md +0 -9
  119. package/mcps/davinci-mcp-professional/BUGS.md +0 -55
  120. package/mcps/davinci-mcp-professional/CLAUDE.md +0 -229
  121. package/mcps/davinci-mcp-professional/CONTRIBUTING.md +0 -51
  122. package/mcps/davinci-mcp-professional/COPYING.md +0 -228
  123. package/mcps/davinci-mcp-professional/Doxyfile +0 -379
  124. package/mcps/davinci-mcp-professional/README.md +0 -143
  125. package/mcps/davinci-mcp-professional/SECURITY.md +0 -40
  126. package/mcps/davinci-mcp-professional/USING.md +0 -301
  127. package/mcps/davinci-mcp-professional/VERSION.md +0 -13
  128. package/mcps/davinci-mcp-professional/build.py +0 -169
  129. package/mcps/davinci-mcp-professional/claude_desktop_config_template.json +0 -11
  130. package/mcps/davinci-mcp-professional/hooks/ci-check.sh +0 -29
  131. package/mcps/davinci-mcp-professional/hooks/pre-push +0 -46
  132. package/mcps/davinci-mcp-professional/main.py +0 -40
  133. package/mcps/davinci-mcp-professional/mcp_server.py +0 -30
  134. package/mcps/davinci-mcp-professional/pyproject.toml +0 -115
  135. package/mcps/davinci-mcp-professional/security_audit.py +0 -379
  136. package/mcps/davinci-mcp-professional/src/davinci_mcp/__init__.py +0 -19
  137. package/mcps/davinci-mcp-professional/src/davinci_mcp/cli.py +0 -138
  138. package/mcps/davinci-mcp-professional/src/davinci_mcp/resolve_client.py +0 -342
  139. package/mcps/davinci-mcp-professional/src/davinci_mcp/resources/__init__.py +0 -60
  140. package/mcps/davinci-mcp-professional/src/davinci_mcp/server.py +0 -185
  141. package/mcps/davinci-mcp-professional/src/davinci_mcp/tools/__init__.py +0 -146
  142. package/mcps/davinci-mcp-professional/src/davinci_mcp/types.py +0 -125
  143. package/mcps/davinci-mcp-professional/src/davinci_mcp/utils/__init__.py +0 -19
  144. package/mcps/davinci-mcp-professional/src/davinci_mcp/utils/platform.py +0 -239
  145. package/mcps/davinci-mcp-professional/test.py +0 -186
  146. package/mcps/davinci-mcp-professional/uv.lock +0 -2073
  147. package/mcps/davinci-resolve-mcp/.claude/skills/media-analysis.md +0 -44
  148. package/mcps/davinci-resolve-mcp/.clinerules +0 -17
  149. package/mcps/davinci-resolve-mcp/.cursorrules +0 -17
  150. package/mcps/davinci-resolve-mcp/.github/copilot-instructions.md +0 -17
  151. package/mcps/davinci-resolve-mcp/.github/workflows/npm-publish.yml +0 -109
  152. package/mcps/davinci-resolve-mcp/.windsurfrules +0 -17
  153. package/mcps/davinci-resolve-mcp/AGENTS.md +0 -114
  154. package/mcps/davinci-resolve-mcp/CHANGELOG.md +0 -2648
  155. package/mcps/davinci-resolve-mcp/CLAUDE.md +0 -15
  156. package/mcps/davinci-resolve-mcp/LICENSE +0 -21
  157. package/mcps/davinci-resolve-mcp/README.md +0 -173
  158. package/mcps/davinci-resolve-mcp/SECURITY.md +0 -53
  159. package/mcps/davinci-resolve-mcp/bin/davinci-resolve-mcp.mjs +0 -450
  160. package/mcps/davinci-resolve-mcp/examples/README.md +0 -53
  161. package/mcps/davinci-resolve-mcp/examples/markers/README.md +0 -81
  162. package/mcps/davinci-resolve-mcp/examples/media/README.md +0 -94
  163. package/mcps/davinci-resolve-mcp/examples/timeline/README.md +0 -98
  164. package/mcps/davinci-resolve-mcp/install.py +0 -1687
  165. package/mcps/davinci-resolve-mcp/package.json +0 -52
  166. package/mcps/davinci-resolve-mcp/release-notes/v2.22.0.md +0 -51
  167. package/mcps/davinci-resolve-mcp/release-notes/v2.23.0.md +0 -51
  168. package/mcps/davinci-resolve-mcp/release-notes/v2.23.1.md +0 -35
  169. package/mcps/davinci-resolve-mcp/release-notes/v2.24.1.md +0 -32
  170. package/mcps/davinci-resolve-mcp/scripts/audit_api_parity.py +0 -275
  171. package/mcps/davinci-resolve-mcp/scripts/audit_readwrite_symmetry.py +0 -80
  172. package/mcps/davinci-resolve-mcp/scripts/doctor.py +0 -227
  173. package/mcps/davinci-resolve-mcp/scripts/gen_api_limitations.py +0 -144
  174. package/mcps/davinci-resolve-mcp/scripts/live_media_analysis_polish_probe.py +0 -65
  175. package/mcps/davinci-resolve-mcp/scripts/measure_bridge_cost.py +0 -65
  176. package/mcps/davinci-resolve-mcp/scripts/regen_panel_screenshots.py +0 -76
  177. package/mcps/davinci-resolve-mcp/src/__init__.py +0 -3
  178. package/mcps/davinci-resolve-mcp/src/analysis_dashboard.py +0 -15505
  179. package/mcps/davinci-resolve-mcp/src/batch_cli.py +0 -540
  180. package/mcps/davinci-resolve-mcp/src/control_panel.py +0 -13
  181. package/mcps/davinci-resolve-mcp/src/granular/__init__.py +0 -17
  182. package/mcps/davinci-resolve-mcp/src/granular/common.py +0 -741
  183. package/mcps/davinci-resolve-mcp/src/granular/folder.py +0 -420
  184. package/mcps/davinci-resolve-mcp/src/granular/gallery.py +0 -306
  185. package/mcps/davinci-resolve-mcp/src/granular/graph.py +0 -309
  186. package/mcps/davinci-resolve-mcp/src/granular/media_pool.py +0 -679
  187. package/mcps/davinci-resolve-mcp/src/granular/media_pool_item.py +0 -1020
  188. package/mcps/davinci-resolve-mcp/src/granular/media_storage.py +0 -179
  189. package/mcps/davinci-resolve-mcp/src/granular/project.py +0 -1654
  190. package/mcps/davinci-resolve-mcp/src/granular/resolve_control.py +0 -538
  191. package/mcps/davinci-resolve-mcp/src/granular/timeline.py +0 -1075
  192. package/mcps/davinci-resolve-mcp/src/granular/timeline_item.py +0 -2251
  193. package/mcps/davinci-resolve-mcp/src/resolve_mcp_server.py +0 -43
  194. package/mcps/davinci-resolve-mcp/src/server.py +0 -24116
  195. package/mcps/davinci-resolve-mcp/src/utils/__init__.py +0 -3
  196. package/mcps/davinci-resolve-mcp/src/utils/actor_identity.py +0 -46
  197. package/mcps/davinci-resolve-mcp/src/utils/analysis_caps.py +0 -678
  198. package/mcps/davinci-resolve-mcp/src/utils/analysis_memory.py +0 -755
  199. package/mcps/davinci-resolve-mcp/src/utils/analysis_runs.py +0 -307
  200. package/mcps/davinci-resolve-mcp/src/utils/analysis_store.py +0 -945
  201. package/mcps/davinci-resolve-mcp/src/utils/api_truth.py +0 -525
  202. package/mcps/davinci-resolve-mcp/src/utils/app_control.py +0 -319
  203. package/mcps/davinci-resolve-mcp/src/utils/audio_fairlight_live_probe.py +0 -263
  204. package/mcps/davinci-resolve-mcp/src/utils/brain_edits.py +0 -387
  205. package/mcps/davinci-resolve-mcp/src/utils/bridge_metrics.py +0 -76
  206. package/mcps/davinci-resolve-mcp/src/utils/cdl.py +0 -20
  207. package/mcps/davinci-resolve-mcp/src/utils/clip_query.py +0 -85
  208. package/mcps/davinci-resolve-mcp/src/utils/cloud_operations.py +0 -192
  209. package/mcps/davinci-resolve-mcp/src/utils/color_grade_live_probe.py +0 -444
  210. package/mcps/davinci-resolve-mcp/src/utils/contracts.py +0 -102
  211. package/mcps/davinci-resolve-mcp/src/utils/cut_ir.py +0 -115
  212. package/mcps/davinci-resolve-mcp/src/utils/dctl_templates.py +0 -368
  213. package/mcps/davinci-resolve-mcp/src/utils/deep_vision.py +0 -643
  214. package/mcps/davinci-resolve-mcp/src/utils/destructive_hook.py +0 -606
  215. package/mcps/davinci-resolve-mcp/src/utils/edit_engine.py +0 -737
  216. package/mcps/davinci-resolve-mcp/src/utils/embeddings.py +0 -922
  217. package/mcps/davinci-resolve-mcp/src/utils/entities.py +0 -579
  218. package/mcps/davinci-resolve-mcp/src/utils/extension_authoring_live_probe.py +0 -292
  219. package/mcps/davinci-resolve-mcp/src/utils/failure_tracker.py +0 -119
  220. package/mcps/davinci-resolve-mcp/src/utils/fuse_templates.py +0 -1968
  221. package/mcps/davinci-resolve-mcp/src/utils/fusion_composition_live_probe.py +0 -284
  222. package/mcps/davinci-resolve-mcp/src/utils/fusion_group_settings.py +0 -334
  223. package/mcps/davinci-resolve-mcp/src/utils/layout_presets.py +0 -333
  224. package/mcps/davinci-resolve-mcp/src/utils/mcp_stdio.py +0 -32
  225. package/mcps/davinci-resolve-mcp/src/utils/mcp_transport.py +0 -128
  226. package/mcps/davinci-resolve-mcp/src/utils/media_analysis.py +0 -7690
  227. package/mcps/davinci-resolve-mcp/src/utils/media_analysis_jobs.py +0 -805
  228. package/mcps/davinci-resolve-mcp/src/utils/media_pool_changes.py +0 -121
  229. package/mcps/davinci-resolve-mcp/src/utils/media_pool_ingest_live_probe.py +0 -592
  230. package/mcps/davinci-resolve-mcp/src/utils/multicam.py +0 -393
  231. package/mcps/davinci-resolve-mcp/src/utils/object_inspection.py +0 -301
  232. package/mcps/davinci-resolve-mcp/src/utils/page_lock.py +0 -75
  233. package/mcps/davinci-resolve-mcp/src/utils/platform.py +0 -157
  234. package/mcps/davinci-resolve-mcp/src/utils/proc.py +0 -26
  235. package/mcps/davinci-resolve-mcp/src/utils/project_cleanup.py +0 -73
  236. package/mcps/davinci-resolve-mcp/src/utils/project_lifecycle_live_probe.py +0 -376
  237. package/mcps/davinci-resolve-mcp/src/utils/project_lint.py +0 -131
  238. package/mcps/davinci-resolve-mcp/src/utils/project_properties.py +0 -601
  239. package/mcps/davinci-resolve-mcp/src/utils/project_spec.py +0 -459
  240. package/mcps/davinci-resolve-mcp/src/utils/readback.py +0 -91
  241. package/mcps/davinci-resolve-mcp/src/utils/render_deliver_live_probe.py +0 -384
  242. package/mcps/davinci-resolve-mcp/src/utils/resolve_ai_governance.py +0 -214
  243. package/mcps/davinci-resolve-mcp/src/utils/resolve_ai_ledger.py +0 -244
  244. package/mcps/davinci-resolve-mcp/src/utils/resolve_busy.py +0 -171
  245. package/mcps/davinci-resolve-mcp/src/utils/resolve_connection.py +0 -77
  246. package/mcps/davinci-resolve-mcp/src/utils/review_annotation_live_probe.py +0 -352
  247. package/mcps/davinci-resolve-mcp/src/utils/script_templates.py +0 -1193
  248. package/mcps/davinci-resolve-mcp/src/utils/shot_relationships.py +0 -544
  249. package/mcps/davinci-resolve-mcp/src/utils/structural_diff.py +0 -175
  250. package/mcps/davinci-resolve-mcp/src/utils/sync_detection.py +0 -889
  251. package/mcps/davinci-resolve-mcp/src/utils/timeline_brain_db.py +0 -826
  252. package/mcps/davinci-resolve-mcp/src/utils/timeline_conform_live_probe.py +0 -280
  253. package/mcps/davinci-resolve-mcp/src/utils/timeline_kernel_live_probe.py +0 -1092
  254. package/mcps/davinci-resolve-mcp/src/utils/timeline_kernel_probe.py +0 -185
  255. package/mcps/davinci-resolve-mcp/src/utils/timeline_title_text.py +0 -87
  256. package/mcps/davinci-resolve-mcp/src/utils/timeline_versioning.py +0 -730
  257. package/mcps/davinci-resolve-mcp/src/utils/update_check.py +0 -685
  258. package/mcps/vectorworks-mcp/.dockerignore +0 -8
  259. package/mcps/vectorworks-mcp/Dockerfile +0 -28
  260. package/mcps/vectorworks-mcp/README.md +0 -156
  261. package/mcps/vectorworks-mcp/app/__init__.py +0 -2
  262. package/mcps/vectorworks-mcp/app/api.py +0 -52
  263. package/mcps/vectorworks-mcp/app/chunking.py +0 -56
  264. package/mcps/vectorworks-mcp/app/config.py +0 -29
  265. package/mcps/vectorworks-mcp/app/embeddings.py +0 -28
  266. package/mcps/vectorworks-mcp/app/indexer.py +0 -106
  267. package/mcps/vectorworks-mcp/app/main.py +0 -33
  268. package/mcps/vectorworks-mcp/app/mcp_server.py +0 -71
  269. package/mcps/vectorworks-mcp/app/search.py +0 -54
  270. package/mcps/vectorworks-mcp/app/utils.py +0 -14
  271. package/mcps/vectorworks-mcp/data/.gitkeep +0 -1
  272. package/mcps/vectorworks-mcp/data/vw-app-help/VW2022_PluginScript.htm +0 -95
  273. package/mcps/vectorworks-mcp/data/vw-app-help/VW2022_ScriptEditor.htm +0 -266
  274. package/mcps/vectorworks-mcp/data/vw-app-help/VW2022_Scripting.htm +0 -106
  275. package/mcps/vectorworks-mcp/data/vw-app-help/VW2023_CreatingScriptedPlugins.htm +0 -327
  276. package/mcps/vectorworks-mcp/data/vw-app-help/VW2024_RunningScripts.htm +0 -116
  277. package/mcps/vectorworks-mcp/data/vw-devwiki/VS_Function_Reference_category.html +0 -2129
  278. package/mcps/vectorworks-mcp/data/vw-jp-ref/ObjectsSymbols.html +0 -2035
  279. package/mcps/vectorworks-mcp/data/vw-jp-ref/ScriptFunctionReference.html +0 -29
  280. package/mcps/vectorworks-mcp/design.md +0 -57
  281. package/mcps/vectorworks-mcp/docker-compose.yml +0 -18
  282. package/mcps/vectorworks-mcp/index/.gitkeep +0 -1
  283. package/mcps/vectorworks-mcp/index/meta.jsonl +0 -3956
  284. package/mcps/vectorworks-mcp/index/vw.faiss +0 -0
  285. package/mcps/vectorworks-mcp/requirements.txt +0 -10
  286. package/mcps/vectorworks-mcp/scripts/fetch_docs_minimal.sh +0 -84
  287. package/mcps/vectorworks-mcp/scripts/fetch_github_vectorworks.sh +0 -85
  288. package/mcps/vectorworks-mcp/templates/index.html +0 -40
  289. package/scripts/ecosystem-health-audit.js +0 -257
  290. package/skills/bdb-dev-os-skill/SKILL.md +0 -49
  291. package/skills/bdb-dev-os-skill/package.json +0 -11
  292. package/skills/bdbsaastraining/SKILL.md +0 -316
  293. package/skills/bdbsaastraining/references/exam-pool.md +0 -313
  294. package/skills/bdbsaastraining/references/interaction-modes.md +0 -94
  295. package/skills/bdbsaastraining/references/track-a-ai-agent.md +0 -111
  296. package/skills/bdbsaastraining/references/track-b-wordpress.md +0 -111
  297. package/skills/bdbsaastraining/references/track-c-mailserver.md +0 -107
  298. package/skills/bdbsaastraining/references/track-d-debian.md +0 -109
  299. package/skills/bdbsaastraining/references/track-e-custom.md +0 -83
  300. package/skills/bdbsaastraining/scripts/build_profile.py +0 -529
  301. package/skills/bdbsaastraining/scripts/generate_certificate.py +0 -161
  302. package/skills/bdbsaastraining/scripts/preflight_check.sh +0 -150
  303. package/skills/bdbsaastraining/templates/certificate_template.html +0 -508
  304. package/skills/global_config/bdb-ecosystem-health/SKILL.md +0 -68
  305. package/skills/global_config/bdbsaas-ops/SKILL.md +0 -16
  306. package/skills/global_config/bdbsaashost/SKILL.md +0 -125
@@ -1,544 +0,0 @@
1
- """Cross-shot relationships (spec §4 — pattern recognition only).
2
-
3
- Detect → one bounded vision confirm → commit, the Phase D entities pattern:
4
- the cheap part is local (pairwise cosine over the v10 per-shot visual
5
- vectors, plus transcript continuity as a second signal for continues_from);
6
- the expensive part is bounded (the host chat reviews a representative frame
7
- PAIR per candidate). NO editorial suggestions are stored — only the three
8
- spec §4 relationship types:
9
-
10
- - same_setup_as (symmetric) — same camera + framing of the same action
11
- - alt_take_of (symmetric) — different take of the same setup
12
- - continues_from (directional)— the SOURCE shot continues from the TARGET
13
- shot (target precedes source)
14
-
15
- Symmetric rows are stored once with the pair canonically ordered by
16
- (clip_name, shot_index); readers must check both columns. Candidates live
17
- only in the detection-state stash until committed — re-detect overwrites
18
- the stash, so unconfirmed candidates can never linger as ghosts. Commit
19
- supersedes any current machine row for the same (pair, type) before
20
- inserting, so human corrections (a later C4-style author='human' row)
21
- always stay newest.
22
- """
23
-
24
- from __future__ import annotations
25
-
26
- import json
27
- import os
28
- import time
29
- from typing import Any, Dict, List, Optional, Tuple
30
-
31
- from src.utils import analysis_memory, embeddings, timeline_brain_db
32
-
33
- RELATIONSHIP_VISION_SOURCE = "vision_relationship_v1"
34
- RELATIONSHIP_SCHEMA_REFERENCE = "davinci_resolve_mcp.relationship_confirmation.v1"
35
- RELATIONSHIP_TYPES = ("same_setup_as", "continues_from", "alt_take_of")
36
-
37
- # Tuned on the sample root (34-shot montage) during Phase 4 live validation.
38
- DEFAULT_SETUP_THRESHOLD = 0.90 # same-clip non-adjacent or cross-clip: same_setup_as
39
- DEFAULT_ALT_TAKE_THRESHOLD = 0.88 # cross-clip + comparable duration: alt_take_of
40
- DEFAULT_CONTINUES_BAND = (0.70, 0.90) # adjacent same-clip shots: continues_from
41
- DEFAULT_MAX_CANDIDATES = 24
42
- ALT_TAKE_DURATION_RATIO = 2.0 # durations within 2x of each other
43
-
44
- RELATIONSHIP_SCHEMA = {
45
- "relationships": [
46
- {
47
- "candidate_index": "<int — from the payload's candidates list>",
48
- "verdict": "confirm|reject",
49
- "relationship_type": "same_setup_as|continues_from|alt_take_of (override the suggestion when the frames say otherwise)",
50
- "confidence": "low|medium|high",
51
- }
52
- ]
53
- }
54
-
55
- RELATIONSHIP_PROMPT = (
56
- "Each candidate below pairs two shots that the local heuristics think are "
57
- "related. Look at BOTH frames of each pair and judge the suggested "
58
- "relationship: same_setup_as = same camera setup + framing of the same "
59
- "action; alt_take_of = a different take of the same setup; continues_from "
60
- "= the first shot visibly continues the second shot's action across a "
61
- "cut. Confirm only what the frames actually show — reject lookalikes "
62
- "(similar palette or subject is NOT the same setup). You may override the "
63
- "suggested type when the frames support a different one. Return strict "
64
- "JSON matching `schema`."
65
- )
66
-
67
-
68
- def _now() -> str:
69
- return time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime())
70
-
71
-
72
- def _now_precise() -> str:
73
- """Microsecond timestamps — `timestamp` is part of the row's UNIQUE key,
74
- so two commits of the same pair within one second must not collide."""
75
- now = time.time()
76
- return time.strftime("%Y-%m-%dT%H:%M:%S", time.gmtime(now)) + f".{int((now % 1) * 1e6):06d}Z"
77
-
78
-
79
- def _ma():
80
- from src.utils import media_analysis
81
-
82
- return media_analysis
83
-
84
-
85
- def _state_path(project_root: str) -> str:
86
- return os.path.join(analysis_memory.memory_dir(project_root), "relationship_detection_state.json")
87
-
88
-
89
- def _write_state(project_root: str, token: str, candidates: List[Dict[str, Any]]) -> None:
90
- analysis_memory.ensure_memory_structure(project_root)
91
- path = _state_path(project_root)
92
- tmp = path + ".tmp"
93
- with open(tmp, "w", encoding="utf-8") as handle:
94
- json.dump({"vision_token": token, "candidates": candidates, "written_at": _now()}, handle, indent=2)
95
- os.replace(tmp, path)
96
-
97
-
98
- def _read_state(project_root: str) -> Optional[Dict[str, Any]]:
99
- try:
100
- with open(_state_path(project_root), "r", encoding="utf-8") as handle:
101
- return json.load(handle)
102
- except (OSError, json.JSONDecodeError):
103
- return None
104
-
105
-
106
- def _shot_rows(conn) -> Dict[str, Dict[str, Any]]:
107
- rows = conn.execute(
108
- """
109
- SELECT s.shot_uuid, s.clip_uuid, s.shot_index, s.time_seconds_start,
110
- s.time_seconds_end, s.description, c.clip_name
111
- FROM shots s LEFT JOIN clips c ON c.clip_uuid = s.clip_uuid
112
- """
113
- ).fetchall()
114
- return {str(r["shot_uuid"]): dict(r) for r in rows}
115
-
116
-
117
- def _rep_frame_path(conn, shot_uuid: str) -> Optional[str]:
118
- """The shot's MIDDLE sampled frame — first/last frames often catch fades
119
- or transition black, which makes pair review impossible."""
120
- rows = conn.execute(
121
- """
122
- SELECT frame_path FROM frames
123
- WHERE shot_uuid = ? AND frame_path IS NOT NULL
124
- ORDER BY frame_index
125
- """,
126
- (str(shot_uuid),),
127
- ).fetchall()
128
- if not rows:
129
- return None
130
- return str(rows[len(rows) // 2]["frame_path"])
131
-
132
-
133
- def _transcript_spans_boundary(conn, clip_uuid: str, boundary_seconds: float) -> bool:
134
- """A transcript segment spanning the cut boundary is continuity evidence."""
135
- row = conn.execute(
136
- """
137
- SELECT 1 FROM transcript_segments
138
- WHERE clip_uuid = ? AND start_seconds < ? AND end_seconds > ?
139
- LIMIT 1
140
- """,
141
- (clip_uuid, float(boundary_seconds), float(boundary_seconds)),
142
- ).fetchone()
143
- return row is not None
144
-
145
-
146
- def _canonical_pair(a: Dict[str, Any], b: Dict[str, Any]) -> Tuple[Dict[str, Any], Dict[str, Any]]:
147
- key = lambda s: (str(s.get("clip_name") or ""), int(s.get("shot_index") or 0)) # noqa: E731
148
- return (a, b) if key(a) <= key(b) else (b, a)
149
-
150
-
151
- def detect_shot_relationships(
152
- project_root: str,
153
- *,
154
- setup_threshold: float = DEFAULT_SETUP_THRESHOLD,
155
- alt_take_threshold: float = DEFAULT_ALT_TAKE_THRESHOLD,
156
- continues_band: Tuple[float, float] = DEFAULT_CONTINUES_BAND,
157
- max_candidates: int = DEFAULT_MAX_CANDIDATES,
158
- job_id: Optional[str] = None,
159
- ) -> Dict[str, Any]:
160
- """Pairwise heuristics over per-shot visual vectors → deferred
161
- confirmation payload with a representative frame PAIR per candidate."""
162
- ma = _ma()
163
- conn = timeline_brain_db.connect(project_root)
164
- vec_rows = conn.execute(
165
- """
166
- SELECT entity_uuid, vector FROM embeddings
167
- WHERE embedding_kind = 'visual' AND entity_type = 'shot'
168
- """
169
- ).fetchall()
170
- if not vec_rows:
171
- return {
172
- "success": False,
173
- "error": (
174
- "No per-shot visual embeddings yet — run "
175
- "media_analysis(action='build_embeddings', params={'kinds': ['visual']}) first."
176
- ),
177
- }
178
- shots = _shot_rows(conn)
179
- uuids = [str(r["entity_uuid"]) for r in vec_rows if str(r["entity_uuid"]) in shots]
180
- vectors = {u: embeddings.unpack_vector(r["vector"]) for u, r in zip(
181
- [str(r["entity_uuid"]) for r in vec_rows], vec_rows) if u in shots}
182
-
183
- low_cont, high_cont = float(continues_band[0]), float(continues_band[1])
184
- candidates: List[Dict[str, Any]] = []
185
- for i in range(len(uuids)):
186
- for j in range(i + 1, len(uuids)):
187
- a, b = shots[uuids[i]], shots[uuids[j]]
188
- sim = embeddings.cosine_similarity(vectors[uuids[i]], vectors[uuids[j]])
189
- same_clip = a["clip_uuid"] == b["clip_uuid"]
190
- suggestion = None
191
- evidence: List[str] = []
192
- if same_clip:
193
- index_gap = abs(int(a["shot_index"]) - int(b["shot_index"]))
194
- if index_gap == 1 and low_cont <= sim < high_cont:
195
- suggestion = "continues_from"
196
- earlier, later = (a, b) if int(a["shot_index"]) < int(b["shot_index"]) else (b, a)
197
- boundary = earlier.get("time_seconds_end")
198
- if boundary is not None and _transcript_spans_boundary(
199
- conn, str(a["clip_uuid"]), float(boundary)
200
- ):
201
- evidence.append("a transcript segment spans the cut boundary")
202
- elif index_gap > 1 and sim >= float(setup_threshold):
203
- suggestion = "same_setup_as"
204
- else:
205
- dur_a = (a.get("time_seconds_end") or 0) - (a.get("time_seconds_start") or 0)
206
- dur_b = (b.get("time_seconds_end") or 0) - (b.get("time_seconds_start") or 0)
207
- comparable = (
208
- dur_a > 0 and dur_b > 0
209
- and max(dur_a, dur_b) / max(min(dur_a, dur_b), 0.001) <= ALT_TAKE_DURATION_RATIO
210
- )
211
- if sim >= float(alt_take_threshold) and comparable:
212
- suggestion = "alt_take_of"
213
- elif sim >= float(setup_threshold):
214
- suggestion = "same_setup_as"
215
- if not suggestion:
216
- continue
217
- evidence.insert(0, f"visual cosine {round(float(sim), 3)} between per-shot vectors")
218
- if suggestion == "continues_from":
219
- # Directional: the LATER shot continues from the EARLIER one.
220
- earlier, later = (a, b) if int(a["shot_index"]) < int(b["shot_index"]) else (b, a)
221
- source, target = later, earlier
222
- else:
223
- source, target = _canonical_pair(a, b)
224
- candidates.append({
225
- "source_shot_uuid": source["shot_uuid"],
226
- "target_shot_uuid": target["shot_uuid"],
227
- "suggested_type": suggestion,
228
- "similarity": round(float(sim), 4),
229
- "evidence": evidence,
230
- "_sim": float(sim),
231
- })
232
-
233
- candidates.sort(key=lambda c: -c["_sim"])
234
- candidates = candidates[: max(1, int(max_candidates))]
235
- for candidate in candidates:
236
- candidate.pop("_sim", None)
237
- if not candidates:
238
- return {
239
- "success": True,
240
- "status": "no_candidates",
241
- "shot_count": len(uuids),
242
- "note": (
243
- f"No relationship candidates at setup>={setup_threshold}, "
244
- f"alt_take>={alt_take_threshold}, continues {continues_band}."
245
- ),
246
- }
247
-
248
- # Caps: two frames per candidate go to the host.
249
- estimated_tokens = len(candidates) * 2 * ma.AVG_VISION_TOKENS_PER_FRAME
250
- refusal = ma._check_caps_pre_call(
251
- project_root=project_root,
252
- estimated_vision_tokens=estimated_tokens,
253
- clip_id=None,
254
- job_id=job_id,
255
- )
256
- if refusal is not None:
257
- return refusal
258
-
259
- payload: List[Dict[str, Any]] = []
260
- state_rows: List[Dict[str, Any]] = []
261
- for index, candidate in enumerate(candidates, 1):
262
- src = shots[candidate["source_shot_uuid"]]
263
- dst = shots[candidate["target_shot_uuid"]]
264
- src_frame = _rep_frame_path(conn, candidate["source_shot_uuid"])
265
- dst_frame = _rep_frame_path(conn, candidate["target_shot_uuid"])
266
- payload.append({
267
- "candidate_index": index,
268
- "suggested_type": candidate["suggested_type"],
269
- "similarity": candidate["similarity"],
270
- "evidence": candidate["evidence"],
271
- "source_shot": {
272
- "clip_name": src.get("clip_name"), "shot_index": src.get("shot_index"),
273
- "description": src.get("description"), "frame_path": src_frame,
274
- },
275
- "target_shot": {
276
- "clip_name": dst.get("clip_name"), "shot_index": dst.get("shot_index"),
277
- "description": dst.get("description"), "frame_path": dst_frame,
278
- },
279
- })
280
- state_rows.append({
281
- "candidate_index": index,
282
- "source_shot_uuid": candidate["source_shot_uuid"],
283
- "target_shot_uuid": candidate["target_shot_uuid"],
284
- "suggested_type": candidate["suggested_type"],
285
- "similarity": candidate["similarity"],
286
- })
287
-
288
- vision_token = ma.short_hash(
289
- "relationships:" + ",".join(f"{r['source_shot_uuid']}>{r['target_shot_uuid']}" for r in state_rows), 16,
290
- )
291
- # Candidates live ONLY here until committed — re-detect overwrites the
292
- # stash, so unconfirmed candidates never linger as ghost rows.
293
- _write_state(project_root, vision_token, state_rows)
294
- frame_paths = [
295
- p for c in payload for p in (c["source_shot"]["frame_path"], c["target_shot"]["frame_path"]) if p
296
- ]
297
- return {
298
- "success": True,
299
- "status": "pending_host_analysis",
300
- "provider": "host_chat_paths",
301
- "mode": "relationship_confirmation",
302
- "vision_token": vision_token,
303
- "candidate_count": len(payload),
304
- "estimate": {
305
- "frames_to_review": len(frame_paths),
306
- "estimated_vision_tokens": estimated_tokens,
307
- },
308
- "thresholds": {
309
- "setup_threshold": setup_threshold,
310
- "alt_take_threshold": alt_take_threshold,
311
- "continues_band": list(continues_band),
312
- },
313
- "candidates": payload,
314
- "frame_paths": frame_paths,
315
- "schema": json.loads(json.dumps(RELATIONSHIP_SCHEMA)),
316
- "schema_reference": RELATIONSHIP_SCHEMA_REFERENCE,
317
- "prompt": RELATIONSHIP_PROMPT,
318
- "commit_action": {
319
- "tool": "media_analysis",
320
- "action": "commit_shot_relationships",
321
- "params": {
322
- "vision_token": vision_token,
323
- "relationships": "<host chat: fill per `schema`>",
324
- "analysis_root": project_root,
325
- },
326
- },
327
- "instructions": (
328
- "For each candidate read BOTH frame paths as local images, judge "
329
- "the suggested relationship, and return one entry per "
330
- "candidate_index in `relationships` per the schema. Then call the "
331
- "tool in commit_action. Reject anything the frames don't support."
332
- ),
333
- }
334
-
335
-
336
- def commit_shot_relationships(
337
- project_root: str,
338
- *,
339
- relationships_payload: Any,
340
- vision_token: Optional[str] = None,
341
- author: str = "host_chat",
342
- ) -> Dict[str, Any]:
343
- """Write vision-confirmed relationship rows (machine source label).
344
-
345
- Supersedes any current machine row for the same (pair, type) first, so
346
- re-running the pass never duplicates and never outranks human rows added
347
- later (human edits ride the same supersede semantics with author='human')."""
348
- if isinstance(relationships_payload, str):
349
- try:
350
- relationships_payload = json.loads(relationships_payload)
351
- except json.JSONDecodeError as exc:
352
- return {"success": False, "error": f"relationships was a string but not valid JSON: {exc}"}
353
- if isinstance(relationships_payload, dict) and isinstance(relationships_payload.get("relationships"), list):
354
- relationships_payload = relationships_payload["relationships"]
355
- if not isinstance(relationships_payload, list) or not relationships_payload:
356
- return {"success": False, "error": "commit_shot_relationships requires `relationships`: a non-empty array"}
357
-
358
- state = _read_state(project_root)
359
- if not state:
360
- return {"success": False, "error": "No relationship-detection state — run detect_shot_relationships first"}
361
- expected = str(state.get("vision_token") or "")
362
- if vision_token and str(vision_token) != expected:
363
- return {
364
- "success": False,
365
- "error": (
366
- "vision_token mismatch; candidates changed since the payload was "
367
- "issued (re-run detect_shot_relationships)."
368
- ),
369
- "expected_vision_token": expected,
370
- }
371
- by_index = {int(c["candidate_index"]): c for c in state.get("candidates") or []}
372
-
373
- now = _now_precise()
374
- confirmed = 0
375
- rejected = 0
376
- skipped: List[Dict[str, Any]] = []
377
- frames_reviewed = len(by_index) * 2
378
- with timeline_brain_db.transaction(project_root) as txn:
379
- for entry in relationships_payload:
380
- if not isinstance(entry, dict):
381
- continue
382
- try:
383
- candidate = by_index.get(int(entry.get("candidate_index")))
384
- except (TypeError, ValueError):
385
- candidate = None
386
- if candidate is None:
387
- skipped.append({"entry": entry, "reason": "unknown candidate_index"})
388
- continue
389
- verdict = str(entry.get("verdict") or "").strip().lower()
390
- if verdict != "confirm":
391
- rejected += 1
392
- continue
393
- rel_type = str(entry.get("relationship_type") or candidate["suggested_type"])
394
- if rel_type not in RELATIONSHIP_TYPES:
395
- skipped.append({"entry": entry, "reason": f"invalid relationship_type {rel_type!r}"})
396
- continue
397
- source_uuid = str(candidate["source_shot_uuid"])
398
- target_uuid = str(candidate["target_shot_uuid"])
399
- txn.execute(
400
- """
401
- UPDATE shot_relationships SET superseded_at = ?
402
- WHERE relationship_type = ? AND superseded_at IS NULL
403
- AND source = ?
404
- AND ((source_shot_uuid = ? AND target_shot_uuid = ?)
405
- OR (source_shot_uuid = ? AND target_shot_uuid = ?))
406
- """,
407
- (now, rel_type, RELATIONSHIP_VISION_SOURCE,
408
- source_uuid, target_uuid, target_uuid, source_uuid),
409
- )
410
- txn.execute(
411
- """
412
- INSERT INTO shot_relationships
413
- (source_shot_uuid, target_shot_uuid, relationship_type,
414
- confidence, source, author, timestamp, superseded_at)
415
- VALUES (?, ?, ?, ?, ?, ?, ?, NULL)
416
- """,
417
- (
418
- source_uuid,
419
- target_uuid,
420
- rel_type,
421
- entry.get("confidence"),
422
- RELATIONSHIP_VISION_SOURCE if author == "host_chat" else "human",
423
- author,
424
- now,
425
- ),
426
- )
427
- confirmed += 1
428
-
429
- ma = _ma()
430
- ma._record_caps_usage(
431
- project_root=project_root,
432
- clip_id=None,
433
- vision_tokens=frames_reviewed * ma.AVG_VISION_TOKENS_PER_FRAME,
434
- frames_uploaded=frames_reviewed,
435
- )
436
- return {
437
- "success": True,
438
- "confirmed": confirmed,
439
- "rejected": rejected,
440
- "skipped": skipped,
441
- }
442
-
443
-
444
- def list_shot_relationships(
445
- project_root: str,
446
- *,
447
- clip_ref: Optional[str] = None,
448
- shot_uuid: Optional[str] = None,
449
- relationship_type: Optional[str] = None,
450
- include_superseded: bool = False,
451
- ) -> Dict[str, Any]:
452
- """Current relationship rows, hydrated with clip/shot context on both ends."""
453
- conn = timeline_brain_db.connect(project_root)
454
- where = [] if include_superseded else ["r.superseded_at IS NULL"]
455
- args: List[Any] = []
456
- if relationship_type:
457
- where.append("r.relationship_type = ?")
458
- args.append(relationship_type)
459
- if shot_uuid:
460
- where.append("(r.source_shot_uuid = ? OR r.target_shot_uuid = ?)")
461
- args.extend([str(shot_uuid), str(shot_uuid)])
462
- if clip_ref:
463
- from src.utils import analysis_store
464
-
465
- clip_uuid = analysis_store.resolve_clip_uuid(conn, clip_ref)
466
- if not clip_uuid:
467
- return {"success": False, "error": f"No analyzed clip found for {clip_ref!r}"}
468
- where.append(
469
- "(src.clip_uuid = ? OR dst.clip_uuid = ?)"
470
- )
471
- args.extend([clip_uuid, clip_uuid])
472
- where_sql = (" WHERE " + " AND ".join(where)) if where else ""
473
- rows = conn.execute(
474
- f"""
475
- SELECT r.*, src.clip_uuid AS source_clip_uuid, src.shot_index AS source_shot_index,
476
- sc.clip_name AS source_clip_name,
477
- dst.clip_uuid AS target_clip_uuid, dst.shot_index AS target_shot_index,
478
- dc.clip_name AS target_clip_name
479
- FROM shot_relationships r
480
- LEFT JOIN shots src ON src.shot_uuid = r.source_shot_uuid
481
- LEFT JOIN clips sc ON sc.clip_uuid = src.clip_uuid
482
- LEFT JOIN shots dst ON dst.shot_uuid = r.target_shot_uuid
483
- LEFT JOIN clips dc ON dc.clip_uuid = dst.clip_uuid
484
- {where_sql}
485
- ORDER BY r.relationship_type, r.id
486
- """,
487
- args,
488
- ).fetchall()
489
- return {"success": True, "count": len(rows), "relationships": [dict(r) for r in rows]}
490
-
491
-
492
- def relationships_for_shot(conn, shot_uuid: str) -> Dict[str, List[str]]:
493
- """Panel block for one shot: {type: ["Clip · shot N", ...]} from current
494
- rows, reading both directions of the symmetric types. continues_from is
495
- shown only on the SOURCE shot (the one that continues)."""
496
- out: Dict[str, List[str]] = {}
497
- rows = conn.execute(
498
- """
499
- SELECT r.relationship_type, r.source_shot_uuid, r.target_shot_uuid,
500
- src.shot_index AS source_shot_index, sc.clip_name AS source_clip_name,
501
- src.clip_uuid AS source_clip_uuid,
502
- dst.shot_index AS target_shot_index, dc.clip_name AS target_clip_name,
503
- dst.clip_uuid AS target_clip_uuid
504
- FROM shot_relationships r
505
- LEFT JOIN shots src ON src.shot_uuid = r.source_shot_uuid
506
- LEFT JOIN clips sc ON sc.clip_uuid = src.clip_uuid
507
- LEFT JOIN shots dst ON dst.shot_uuid = r.target_shot_uuid
508
- LEFT JOIN clips dc ON dc.clip_uuid = dst.clip_uuid
509
- WHERE r.superseded_at IS NULL
510
- AND (r.source_shot_uuid = ? OR r.target_shot_uuid = ?)
511
- """,
512
- (str(shot_uuid), str(shot_uuid)),
513
- ).fetchall()
514
- me = str(shot_uuid)
515
- for row in rows:
516
- rel_type = str(row["relationship_type"])
517
- is_source = str(row["source_shot_uuid"]) == me
518
- if rel_type == "continues_from" and not is_source:
519
- continue # shown on the continuing shot only
520
- other_index = row["target_shot_index"] if is_source else row["source_shot_index"]
521
- other_clip = row["target_clip_name"] if is_source else row["source_clip_name"]
522
- same_clip = (row["source_clip_uuid"] == row["target_clip_uuid"])
523
- label = f"shot {other_index}" if same_clip else f"{other_clip} · shot {other_index}"
524
- out.setdefault(rel_type, []).append(label)
525
- return out
526
-
527
-
528
- def confirmed_alt_take_shot_uuids(conn, shot_uuid: str) -> List[str]:
529
- """Shot uuids confirmed as alt takes of `shot_uuid` (either direction)."""
530
- rows = conn.execute(
531
- """
532
- SELECT source_shot_uuid, target_shot_uuid FROM shot_relationships
533
- WHERE relationship_type = 'alt_take_of' AND superseded_at IS NULL
534
- AND (source_shot_uuid = ? OR target_shot_uuid = ?)
535
- """,
536
- (str(shot_uuid), str(shot_uuid)),
537
- ).fetchall()
538
- me = str(shot_uuid)
539
- out: List[str] = []
540
- for row in rows:
541
- other = str(row["target_shot_uuid"]) if str(row["source_shot_uuid"]) == me else str(row["source_shot_uuid"])
542
- if other != me and other not in out:
543
- out.append(other)
544
- return out