@hybridlabor-api/aos 4.4.1 → 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 (305) 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.md +573 -0
  20. package/README.de.md +0 -8
  21. package/README.md +0 -8
  22. package/README.pt.md +0 -8
  23. package/assets/brand/ao-ant-mark.svg +91 -0
  24. package/assets/brand/bdb-core-mark.svg +34 -0
  25. package/assets/brand/heimdall-guardian-mark.svg +16 -0
  26. package/assets/brand/memb-crystal-mark.svg +18 -0
  27. package/bin/aos-config.mjs +89 -0
  28. package/bin/aos-dashboard.mjs +608 -0
  29. package/bin/aos-uninstall.mjs +227 -0
  30. package/docs/sessions/audit-2026-09-14-silent-failures.md +114 -0
  31. package/docs/skills_table.md +0 -3
  32. package/installer.js +387 -237
  33. package/mcps/RhinoMCP/cc-plugin/.claude/settings.json +10 -0
  34. package/mcps/after-effects-mcp/build/index.js +840 -0
  35. package/mcps/after-effects-mcp/build/scripts/applyEffect.jsx +153 -0
  36. package/mcps/after-effects-mcp/build/scripts/applyEffectTemplate.jsx +218 -0
  37. package/mcps/after-effects-mcp/build/scripts/createComposition.jsx +71 -0
  38. package/mcps/after-effects-mcp/build/scripts/createShapeLayer.jsx +147 -0
  39. package/mcps/after-effects-mcp/build/scripts/createSolidLayer.jsx +114 -0
  40. package/mcps/after-effects-mcp/build/scripts/createTextLayer.jsx +115 -0
  41. package/mcps/after-effects-mcp/build/scripts/getLayerInfo.jsx +192 -0
  42. package/mcps/after-effects-mcp/build/scripts/getProjectInfo.jsx +90 -0
  43. package/mcps/after-effects-mcp/build/scripts/listCompositions.jsx +50 -0
  44. package/mcps/after-effects-mcp/build/scripts/mcp-bridge-auto.jsx +1773 -0
  45. package/mcps/after-effects-mcp/build/scripts/setLayerProperties.jsx +160 -0
  46. package/mcps/computer-use-mcp/dist/client.d.ts +150 -0
  47. package/mcps/computer-use-mcp/dist/client.js +136 -0
  48. package/mcps/computer-use-mcp/dist/entrypoint.d.ts +16 -0
  49. package/mcps/computer-use-mcp/dist/entrypoint.js +26 -0
  50. package/mcps/computer-use-mcp/dist/native.d.ts +212 -0
  51. package/mcps/computer-use-mcp/dist/native.js +50 -0
  52. package/mcps/computer-use-mcp/dist/server.d.ts +32 -0
  53. package/mcps/computer-use-mcp/dist/server.js +342 -0
  54. package/mcps/computer-use-mcp/dist/session.d.ts +101 -0
  55. package/mcps/computer-use-mcp/dist/session.js +2372 -0
  56. package/package.json +13 -4
  57. package/skills/global_config/aos-project-init/SKILL.md +97 -130
  58. package/skills/global_config/aos-project-init/assets/openwikiignore.template +62 -0
  59. package/skills/global_config/aos-project-init/scripts/aos-project-doctor.mjs +38 -1
  60. package/skills/global_config/aos-setup/SKILL.md +98 -8
  61. package/skills/global_config/aos-setup/scripts/aos-doctor.mjs +21 -2
  62. package/skills/global_config/ask-tim/SKILL.md +2 -7
  63. package/skills/global_config/openwiki-skill/scripts/openwiki_daemon.py +65 -12
  64. package/bin/setup-saas.mjs +0 -792
  65. package/mcps/blender-mcp-server/.dockerignore +0 -19
  66. package/mcps/blender-mcp-server/.github/workflows/ci.yml +0 -67
  67. package/mcps/blender-mcp-server/.github/workflows/publish-pypi.yml +0 -54
  68. package/mcps/blender-mcp-server/CHANGELOG.md +0 -64
  69. package/mcps/blender-mcp-server/CONTRIBUTING.md +0 -107
  70. package/mcps/blender-mcp-server/Dockerfile +0 -19
  71. package/mcps/blender-mcp-server/LICENSE +0 -21
  72. package/mcps/blender-mcp-server/README.md +0 -409
  73. package/mcps/blender-mcp-server/addon/__init__.py +0 -1440
  74. package/mcps/blender-mcp-server/addon/models.py +0 -223
  75. package/mcps/blender-mcp-server/pyproject.toml +0 -120
  76. package/mcps/blender-mcp-server/scripts/blender_bridge_request.py +0 -71
  77. package/mcps/blender-mcp-server/scripts/blender_create_test_cube.py +0 -49
  78. package/mcps/blender-mcp-server/scripts/blender_scene_info.py +0 -38
  79. package/mcps/blender-mcp-server/scripts/build_addon_zip.sh +0 -29
  80. package/mcps/blender-mcp-server/scripts/demos/README.md +0 -186
  81. package/mcps/blender-mcp-server/scripts/demos/dam_break_scene.py +0 -276
  82. package/mcps/blender-mcp-server/scripts/demos/open_dam_break_gui.py +0 -33
  83. package/mcps/blender-mcp-server/scripts/demos/pipe_movement_study.py +0 -518
  84. package/mcps/blender-mcp-server/scripts/demos/pipe_pressure_failure_scene.py +0 -806
  85. package/mcps/blender-mcp-server/scripts/demos/procedural_dam_break_scene.py +0 -600
  86. package/mcps/blender-mcp-server/scripts/demos/run_dam_break.py +0 -510
  87. package/mcps/blender-mcp-server/scripts/launch_blender_gui.py +0 -41
  88. package/mcps/blender-mcp-server/scripts/library/README.md +0 -64
  89. package/mcps/blender-mcp-server/scripts/library/apply_transforms.py +0 -46
  90. package/mcps/blender-mcp-server/scripts/library/camera.py +0 -51
  91. package/mcps/blender-mcp-server/scripts/library/collections.py +0 -65
  92. package/mcps/blender-mcp-server/scripts/library/create_mesh.py +0 -41
  93. package/mcps/blender-mcp-server/scripts/library/effector.py +0 -48
  94. package/mcps/blender-mcp-server/scripts/library/fluid_domain.py +0 -80
  95. package/mcps/blender-mcp-server/scripts/library/fluid_inflow.py +0 -98
  96. package/mcps/blender-mcp-server/scripts/library/frame_range.py +0 -35
  97. package/mcps/blender-mcp-server/scripts/library/keyframes.py +0 -59
  98. package/mcps/blender-mcp-server/scripts/library/rigid_body.py +0 -52
  99. package/mcps/blender-mcp-server/scripts/library/save_blend.py +0 -40
  100. package/mcps/blender-mcp-server/scripts/server_start.sh +0 -26
  101. package/mcps/blender-mcp-server/src/blender_mcp_server/__init__.py +0 -3
  102. package/mcps/blender-mcp-server/src/blender_mcp_server/headless.py +0 -330
  103. package/mcps/blender-mcp-server/src/blender_mcp_server/server.py +0 -606
  104. package/mcps/davinci-mcp-professional/.bandit +0 -35
  105. package/mcps/davinci-mcp-professional/.claude/commands/security-review.md +0 -191
  106. package/mcps/davinci-mcp-professional/.claude/settings.json +0 -1
  107. package/mcps/davinci-mcp-professional/.editorconfig +0 -27
  108. package/mcps/davinci-mcp-professional/.gitattributes +0 -207
  109. package/mcps/davinci-mcp-professional/.github/CODEOWNERS +0 -2
  110. package/mcps/davinci-mcp-professional/.github/ISSUE_TEMPLATE/bug_report.md +0 -33
  111. package/mcps/davinci-mcp-professional/.github/ISSUE_TEMPLATE/feature_request.md +0 -19
  112. package/mcps/davinci-mcp-professional/.github/PULL_REQUEST_TEMPLATE.md +0 -20
  113. package/mcps/davinci-mcp-professional/.github/SECURITY.md +0 -97
  114. package/mcps/davinci-mcp-professional/.github/dependabot.yml +0 -27
  115. package/mcps/davinci-mcp-professional/.github/workflows/ci.yml +0 -39
  116. package/mcps/davinci-mcp-professional/ATTRIBUTION.md +0 -4
  117. package/mcps/davinci-mcp-professional/AUTHORS.md +0 -9
  118. package/mcps/davinci-mcp-professional/BUGS.md +0 -55
  119. package/mcps/davinci-mcp-professional/CLAUDE.md +0 -229
  120. package/mcps/davinci-mcp-professional/CONTRIBUTING.md +0 -51
  121. package/mcps/davinci-mcp-professional/COPYING.md +0 -228
  122. package/mcps/davinci-mcp-professional/Doxyfile +0 -379
  123. package/mcps/davinci-mcp-professional/README.md +0 -143
  124. package/mcps/davinci-mcp-professional/SECURITY.md +0 -40
  125. package/mcps/davinci-mcp-professional/USING.md +0 -301
  126. package/mcps/davinci-mcp-professional/VERSION.md +0 -13
  127. package/mcps/davinci-mcp-professional/build.py +0 -169
  128. package/mcps/davinci-mcp-professional/claude_desktop_config_template.json +0 -11
  129. package/mcps/davinci-mcp-professional/hooks/ci-check.sh +0 -29
  130. package/mcps/davinci-mcp-professional/hooks/pre-push +0 -46
  131. package/mcps/davinci-mcp-professional/main.py +0 -40
  132. package/mcps/davinci-mcp-professional/mcp_server.py +0 -30
  133. package/mcps/davinci-mcp-professional/pyproject.toml +0 -115
  134. package/mcps/davinci-mcp-professional/security_audit.py +0 -379
  135. package/mcps/davinci-mcp-professional/src/davinci_mcp/__init__.py +0 -19
  136. package/mcps/davinci-mcp-professional/src/davinci_mcp/cli.py +0 -138
  137. package/mcps/davinci-mcp-professional/src/davinci_mcp/resolve_client.py +0 -342
  138. package/mcps/davinci-mcp-professional/src/davinci_mcp/resources/__init__.py +0 -60
  139. package/mcps/davinci-mcp-professional/src/davinci_mcp/server.py +0 -185
  140. package/mcps/davinci-mcp-professional/src/davinci_mcp/tools/__init__.py +0 -146
  141. package/mcps/davinci-mcp-professional/src/davinci_mcp/types.py +0 -125
  142. package/mcps/davinci-mcp-professional/src/davinci_mcp/utils/__init__.py +0 -19
  143. package/mcps/davinci-mcp-professional/src/davinci_mcp/utils/platform.py +0 -239
  144. package/mcps/davinci-mcp-professional/test.py +0 -186
  145. package/mcps/davinci-mcp-professional/uv.lock +0 -2073
  146. package/mcps/davinci-resolve-mcp/.claude/skills/media-analysis.md +0 -44
  147. package/mcps/davinci-resolve-mcp/.clinerules +0 -17
  148. package/mcps/davinci-resolve-mcp/.cursorrules +0 -17
  149. package/mcps/davinci-resolve-mcp/.github/copilot-instructions.md +0 -17
  150. package/mcps/davinci-resolve-mcp/.github/workflows/npm-publish.yml +0 -109
  151. package/mcps/davinci-resolve-mcp/.windsurfrules +0 -17
  152. package/mcps/davinci-resolve-mcp/AGENTS.md +0 -114
  153. package/mcps/davinci-resolve-mcp/CHANGELOG.md +0 -2648
  154. package/mcps/davinci-resolve-mcp/CLAUDE.md +0 -15
  155. package/mcps/davinci-resolve-mcp/LICENSE +0 -21
  156. package/mcps/davinci-resolve-mcp/README.md +0 -173
  157. package/mcps/davinci-resolve-mcp/SECURITY.md +0 -53
  158. package/mcps/davinci-resolve-mcp/bin/davinci-resolve-mcp.mjs +0 -450
  159. package/mcps/davinci-resolve-mcp/examples/README.md +0 -53
  160. package/mcps/davinci-resolve-mcp/examples/markers/README.md +0 -81
  161. package/mcps/davinci-resolve-mcp/examples/media/README.md +0 -94
  162. package/mcps/davinci-resolve-mcp/examples/timeline/README.md +0 -98
  163. package/mcps/davinci-resolve-mcp/install.py +0 -1687
  164. package/mcps/davinci-resolve-mcp/package.json +0 -52
  165. package/mcps/davinci-resolve-mcp/release-notes/v2.22.0.md +0 -51
  166. package/mcps/davinci-resolve-mcp/release-notes/v2.23.0.md +0 -51
  167. package/mcps/davinci-resolve-mcp/release-notes/v2.23.1.md +0 -35
  168. package/mcps/davinci-resolve-mcp/release-notes/v2.24.1.md +0 -32
  169. package/mcps/davinci-resolve-mcp/scripts/audit_api_parity.py +0 -275
  170. package/mcps/davinci-resolve-mcp/scripts/audit_readwrite_symmetry.py +0 -80
  171. package/mcps/davinci-resolve-mcp/scripts/doctor.py +0 -227
  172. package/mcps/davinci-resolve-mcp/scripts/gen_api_limitations.py +0 -144
  173. package/mcps/davinci-resolve-mcp/scripts/live_media_analysis_polish_probe.py +0 -65
  174. package/mcps/davinci-resolve-mcp/scripts/measure_bridge_cost.py +0 -65
  175. package/mcps/davinci-resolve-mcp/scripts/regen_panel_screenshots.py +0 -76
  176. package/mcps/davinci-resolve-mcp/src/__init__.py +0 -3
  177. package/mcps/davinci-resolve-mcp/src/analysis_dashboard.py +0 -15505
  178. package/mcps/davinci-resolve-mcp/src/batch_cli.py +0 -540
  179. package/mcps/davinci-resolve-mcp/src/control_panel.py +0 -13
  180. package/mcps/davinci-resolve-mcp/src/granular/__init__.py +0 -17
  181. package/mcps/davinci-resolve-mcp/src/granular/common.py +0 -741
  182. package/mcps/davinci-resolve-mcp/src/granular/folder.py +0 -420
  183. package/mcps/davinci-resolve-mcp/src/granular/gallery.py +0 -306
  184. package/mcps/davinci-resolve-mcp/src/granular/graph.py +0 -309
  185. package/mcps/davinci-resolve-mcp/src/granular/media_pool.py +0 -679
  186. package/mcps/davinci-resolve-mcp/src/granular/media_pool_item.py +0 -1020
  187. package/mcps/davinci-resolve-mcp/src/granular/media_storage.py +0 -179
  188. package/mcps/davinci-resolve-mcp/src/granular/project.py +0 -1654
  189. package/mcps/davinci-resolve-mcp/src/granular/resolve_control.py +0 -538
  190. package/mcps/davinci-resolve-mcp/src/granular/timeline.py +0 -1075
  191. package/mcps/davinci-resolve-mcp/src/granular/timeline_item.py +0 -2251
  192. package/mcps/davinci-resolve-mcp/src/resolve_mcp_server.py +0 -43
  193. package/mcps/davinci-resolve-mcp/src/server.py +0 -24116
  194. package/mcps/davinci-resolve-mcp/src/utils/__init__.py +0 -3
  195. package/mcps/davinci-resolve-mcp/src/utils/actor_identity.py +0 -46
  196. package/mcps/davinci-resolve-mcp/src/utils/analysis_caps.py +0 -678
  197. package/mcps/davinci-resolve-mcp/src/utils/analysis_memory.py +0 -755
  198. package/mcps/davinci-resolve-mcp/src/utils/analysis_runs.py +0 -307
  199. package/mcps/davinci-resolve-mcp/src/utils/analysis_store.py +0 -945
  200. package/mcps/davinci-resolve-mcp/src/utils/api_truth.py +0 -525
  201. package/mcps/davinci-resolve-mcp/src/utils/app_control.py +0 -319
  202. package/mcps/davinci-resolve-mcp/src/utils/audio_fairlight_live_probe.py +0 -263
  203. package/mcps/davinci-resolve-mcp/src/utils/brain_edits.py +0 -387
  204. package/mcps/davinci-resolve-mcp/src/utils/bridge_metrics.py +0 -76
  205. package/mcps/davinci-resolve-mcp/src/utils/cdl.py +0 -20
  206. package/mcps/davinci-resolve-mcp/src/utils/clip_query.py +0 -85
  207. package/mcps/davinci-resolve-mcp/src/utils/cloud_operations.py +0 -192
  208. package/mcps/davinci-resolve-mcp/src/utils/color_grade_live_probe.py +0 -444
  209. package/mcps/davinci-resolve-mcp/src/utils/contracts.py +0 -102
  210. package/mcps/davinci-resolve-mcp/src/utils/cut_ir.py +0 -115
  211. package/mcps/davinci-resolve-mcp/src/utils/dctl_templates.py +0 -368
  212. package/mcps/davinci-resolve-mcp/src/utils/deep_vision.py +0 -643
  213. package/mcps/davinci-resolve-mcp/src/utils/destructive_hook.py +0 -606
  214. package/mcps/davinci-resolve-mcp/src/utils/edit_engine.py +0 -737
  215. package/mcps/davinci-resolve-mcp/src/utils/embeddings.py +0 -922
  216. package/mcps/davinci-resolve-mcp/src/utils/entities.py +0 -579
  217. package/mcps/davinci-resolve-mcp/src/utils/extension_authoring_live_probe.py +0 -292
  218. package/mcps/davinci-resolve-mcp/src/utils/failure_tracker.py +0 -119
  219. package/mcps/davinci-resolve-mcp/src/utils/fuse_templates.py +0 -1968
  220. package/mcps/davinci-resolve-mcp/src/utils/fusion_composition_live_probe.py +0 -284
  221. package/mcps/davinci-resolve-mcp/src/utils/fusion_group_settings.py +0 -334
  222. package/mcps/davinci-resolve-mcp/src/utils/layout_presets.py +0 -333
  223. package/mcps/davinci-resolve-mcp/src/utils/mcp_stdio.py +0 -32
  224. package/mcps/davinci-resolve-mcp/src/utils/mcp_transport.py +0 -128
  225. package/mcps/davinci-resolve-mcp/src/utils/media_analysis.py +0 -7690
  226. package/mcps/davinci-resolve-mcp/src/utils/media_analysis_jobs.py +0 -805
  227. package/mcps/davinci-resolve-mcp/src/utils/media_pool_changes.py +0 -121
  228. package/mcps/davinci-resolve-mcp/src/utils/media_pool_ingest_live_probe.py +0 -592
  229. package/mcps/davinci-resolve-mcp/src/utils/multicam.py +0 -393
  230. package/mcps/davinci-resolve-mcp/src/utils/object_inspection.py +0 -301
  231. package/mcps/davinci-resolve-mcp/src/utils/page_lock.py +0 -75
  232. package/mcps/davinci-resolve-mcp/src/utils/platform.py +0 -157
  233. package/mcps/davinci-resolve-mcp/src/utils/proc.py +0 -26
  234. package/mcps/davinci-resolve-mcp/src/utils/project_cleanup.py +0 -73
  235. package/mcps/davinci-resolve-mcp/src/utils/project_lifecycle_live_probe.py +0 -376
  236. package/mcps/davinci-resolve-mcp/src/utils/project_lint.py +0 -131
  237. package/mcps/davinci-resolve-mcp/src/utils/project_properties.py +0 -601
  238. package/mcps/davinci-resolve-mcp/src/utils/project_spec.py +0 -459
  239. package/mcps/davinci-resolve-mcp/src/utils/readback.py +0 -91
  240. package/mcps/davinci-resolve-mcp/src/utils/render_deliver_live_probe.py +0 -384
  241. package/mcps/davinci-resolve-mcp/src/utils/resolve_ai_governance.py +0 -214
  242. package/mcps/davinci-resolve-mcp/src/utils/resolve_ai_ledger.py +0 -244
  243. package/mcps/davinci-resolve-mcp/src/utils/resolve_busy.py +0 -171
  244. package/mcps/davinci-resolve-mcp/src/utils/resolve_connection.py +0 -77
  245. package/mcps/davinci-resolve-mcp/src/utils/review_annotation_live_probe.py +0 -352
  246. package/mcps/davinci-resolve-mcp/src/utils/script_templates.py +0 -1193
  247. package/mcps/davinci-resolve-mcp/src/utils/shot_relationships.py +0 -544
  248. package/mcps/davinci-resolve-mcp/src/utils/structural_diff.py +0 -175
  249. package/mcps/davinci-resolve-mcp/src/utils/sync_detection.py +0 -889
  250. package/mcps/davinci-resolve-mcp/src/utils/timeline_brain_db.py +0 -826
  251. package/mcps/davinci-resolve-mcp/src/utils/timeline_conform_live_probe.py +0 -280
  252. package/mcps/davinci-resolve-mcp/src/utils/timeline_kernel_live_probe.py +0 -1092
  253. package/mcps/davinci-resolve-mcp/src/utils/timeline_kernel_probe.py +0 -185
  254. package/mcps/davinci-resolve-mcp/src/utils/timeline_title_text.py +0 -87
  255. package/mcps/davinci-resolve-mcp/src/utils/timeline_versioning.py +0 -730
  256. package/mcps/davinci-resolve-mcp/src/utils/update_check.py +0 -685
  257. package/mcps/vectorworks-mcp/.dockerignore +0 -8
  258. package/mcps/vectorworks-mcp/Dockerfile +0 -28
  259. package/mcps/vectorworks-mcp/README.md +0 -156
  260. package/mcps/vectorworks-mcp/app/__init__.py +0 -2
  261. package/mcps/vectorworks-mcp/app/api.py +0 -52
  262. package/mcps/vectorworks-mcp/app/chunking.py +0 -56
  263. package/mcps/vectorworks-mcp/app/config.py +0 -29
  264. package/mcps/vectorworks-mcp/app/embeddings.py +0 -28
  265. package/mcps/vectorworks-mcp/app/indexer.py +0 -106
  266. package/mcps/vectorworks-mcp/app/main.py +0 -33
  267. package/mcps/vectorworks-mcp/app/mcp_server.py +0 -71
  268. package/mcps/vectorworks-mcp/app/search.py +0 -54
  269. package/mcps/vectorworks-mcp/app/utils.py +0 -14
  270. package/mcps/vectorworks-mcp/data/.gitkeep +0 -1
  271. package/mcps/vectorworks-mcp/data/vw-app-help/VW2022_PluginScript.htm +0 -95
  272. package/mcps/vectorworks-mcp/data/vw-app-help/VW2022_ScriptEditor.htm +0 -266
  273. package/mcps/vectorworks-mcp/data/vw-app-help/VW2022_Scripting.htm +0 -106
  274. package/mcps/vectorworks-mcp/data/vw-app-help/VW2023_CreatingScriptedPlugins.htm +0 -327
  275. package/mcps/vectorworks-mcp/data/vw-app-help/VW2024_RunningScripts.htm +0 -116
  276. package/mcps/vectorworks-mcp/data/vw-devwiki/VS_Function_Reference_category.html +0 -2129
  277. package/mcps/vectorworks-mcp/data/vw-jp-ref/ObjectsSymbols.html +0 -2035
  278. package/mcps/vectorworks-mcp/data/vw-jp-ref/ScriptFunctionReference.html +0 -29
  279. package/mcps/vectorworks-mcp/design.md +0 -57
  280. package/mcps/vectorworks-mcp/docker-compose.yml +0 -18
  281. package/mcps/vectorworks-mcp/index/.gitkeep +0 -1
  282. package/mcps/vectorworks-mcp/index/meta.jsonl +0 -3956
  283. package/mcps/vectorworks-mcp/index/vw.faiss +0 -0
  284. package/mcps/vectorworks-mcp/requirements.txt +0 -10
  285. package/mcps/vectorworks-mcp/scripts/fetch_docs_minimal.sh +0 -84
  286. package/mcps/vectorworks-mcp/scripts/fetch_github_vectorworks.sh +0 -85
  287. package/mcps/vectorworks-mcp/templates/index.html +0 -40
  288. package/scripts/ecosystem-health-audit.js +0 -257
  289. package/skills/bdb-dev-os-skill/SKILL.md +0 -49
  290. package/skills/bdb-dev-os-skill/package.json +0 -11
  291. package/skills/bdbsaastraining/SKILL.md +0 -316
  292. package/skills/bdbsaastraining/references/exam-pool.md +0 -313
  293. package/skills/bdbsaastraining/references/interaction-modes.md +0 -94
  294. package/skills/bdbsaastraining/references/track-a-ai-agent.md +0 -111
  295. package/skills/bdbsaastraining/references/track-b-wordpress.md +0 -111
  296. package/skills/bdbsaastraining/references/track-c-mailserver.md +0 -107
  297. package/skills/bdbsaastraining/references/track-d-debian.md +0 -109
  298. package/skills/bdbsaastraining/references/track-e-custom.md +0 -83
  299. package/skills/bdbsaastraining/scripts/build_profile.py +0 -529
  300. package/skills/bdbsaastraining/scripts/generate_certificate.py +0 -161
  301. package/skills/bdbsaastraining/scripts/preflight_check.sh +0 -150
  302. package/skills/bdbsaastraining/templates/certificate_template.html +0 -508
  303. package/skills/global_config/bdb-ecosystem-health/SKILL.md +0 -68
  304. package/skills/global_config/bdbsaas-ops/SKILL.md +0 -16
  305. 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