@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,579 +0,0 @@
1
- """Cross-clip entities (Phase D of the analysis program).
2
-
3
- Recurring people/places/props across a project's analyzed media. The cheap
4
- part is local: union-find clustering over the v10 CLIP frame vectors with a
5
- cosine threshold. The expensive part is bounded: ONE host-vision call per
6
- cluster representative (the deferred-payload pattern), committed via
7
- ``commit_entities`` with kind/label/description per cluster — conservative
8
- labels, per the trust-by-default rule (hedge identity when evidence is thin).
9
-
10
- Bin briefing v2 rides the same pattern at bin level: ``prepare_bin_briefing``
11
- returns entities + per-clip summaries for the host chat to synthesize, and
12
- ``commit_bin_summary`` writes the result into memory/bin_summary.md (the v2.0
13
- aggregate stays below as an appendix).
14
- """
15
-
16
- from __future__ import annotations
17
-
18
- import json
19
- import os
20
- import time
21
- from typing import Any, Dict, List, Optional, Sequence, Tuple
22
-
23
- from src.utils import analysis_memory, embeddings, timeline_brain_db
24
-
25
- ENTITY_VISION_SOURCE = "vision_entity_v1"
26
- ENTITY_SCHEMA_REFERENCE = "davinci_resolve_mcp.entity_confirmation.v1"
27
- DEFAULT_CLUSTER_THRESHOLD = 0.78
28
- DEFAULT_MIN_CLUSTER_SIZE = 2
29
-
30
- ENTITY_SCHEMA = {
31
- "entities": [
32
- {
33
- "entity_index": "<int — from the payload's clusters list>",
34
- "entity_uuid": "<echo the cluster's entity_uuid when convenient>",
35
- "kind": "person|place|object|unknown",
36
- "label": "<short conservative label — 'man in dark hooded jacket', not a name unless visibly slated>",
37
- "description": "<1-2 sentences of what recurs across these frames>",
38
- "confidence": "low|medium|high",
39
- "merge_with": "<optional int entity_index this duplicates, else omit>",
40
- }
41
- ]
42
- }
43
-
44
- ENTITY_PROMPT = (
45
- "Each cluster below groups frames that look visually similar across the "
46
- "project's clips. Look at each cluster's representative frame and decide "
47
- "what recurring entity it shows (a person, place, or object). Use "
48
- "conservative labels — describe what is visible, never guess identity or "
49
- "names unless text on screen states them. If two clusters clearly show "
50
- "the same entity, point the duplicate at the original with merge_with. "
51
- "Return strict JSON matching `schema`."
52
- )
53
-
54
-
55
- def _now() -> str:
56
- return time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime())
57
-
58
-
59
- def _ma():
60
- from src.utils import media_analysis
61
-
62
- return media_analysis
63
-
64
-
65
- class _UnionFind:
66
- def __init__(self, n: int) -> None:
67
- self.parent = list(range(n))
68
-
69
- def find(self, i: int) -> int:
70
- while self.parent[i] != i:
71
- self.parent[i] = self.parent[self.parent[i]]
72
- i = self.parent[i]
73
- return i
74
-
75
- def union(self, a: int, b: int) -> None:
76
- ra, rb = self.find(a), self.find(b)
77
- if ra != rb:
78
- self.parent[rb] = ra
79
-
80
-
81
- def _cluster_vectors(
82
- vectors: List[List[float]], threshold: float
83
- ) -> List[List[int]]:
84
- """Union-find over pairwise cosine similarity. O(n²) — fine at this scale."""
85
- n = len(vectors)
86
- uf = _UnionFind(n)
87
- try:
88
- import numpy as np
89
-
90
- matrix = np.asarray(vectors, dtype="float32")
91
- norms = np.linalg.norm(matrix, axis=1, keepdims=True)
92
- norms[norms == 0] = 1.0
93
- normalized = matrix / norms
94
- sims = normalized @ normalized.T
95
- for i in range(n):
96
- row = sims[i]
97
- for j in range(i + 1, n):
98
- if row[j] >= threshold:
99
- uf.union(i, j)
100
- except ImportError:
101
- for i in range(n):
102
- for j in range(i + 1, n):
103
- if embeddings.cosine_similarity(vectors[i], vectors[j]) >= threshold:
104
- uf.union(i, j)
105
- groups: Dict[int, List[int]] = {}
106
- for i in range(n):
107
- groups.setdefault(uf.find(i), []).append(i)
108
- return sorted(groups.values(), key=len, reverse=True)
109
-
110
-
111
- def _representative(vectors: List[List[float]], members: List[int]) -> Tuple[int, Dict[int, float]]:
112
- """Member with the highest mean similarity to its cluster, plus each
113
- member's similarity to that representative."""
114
- best_index = members[0]
115
- best_mean = -1.0
116
- for i in members:
117
- mean = sum(embeddings.cosine_similarity(vectors[i], vectors[j]) for j in members if j != i)
118
- mean = mean / max(1, len(members) - 1)
119
- if mean > best_mean:
120
- best_mean, best_index = mean, i
121
- sims = {
122
- j: (1.0 if j == best_index else embeddings.cosine_similarity(vectors[best_index], vectors[j]))
123
- for j in members
124
- }
125
- return best_index, sims
126
-
127
-
128
- def _entity_uuid_for(frame_refs: Sequence[str]) -> str:
129
- return _ma().short_hash("entity:" + ",".join(sorted(frame_refs)), 12)
130
-
131
-
132
- def _detection_state_path(project_root: str) -> str:
133
- return os.path.join(analysis_memory.memory_dir(project_root), "entity_detection_state.json")
134
-
135
-
136
- def _write_detection_state(project_root: str, token: str, ordering: List[str]) -> None:
137
- analysis_memory.ensure_memory_structure(project_root)
138
- path = _detection_state_path(project_root)
139
- tmp = path + ".tmp"
140
- with open(tmp, "w", encoding="utf-8") as handle:
141
- json.dump({"vision_token": token, "ordering": ordering, "written_at": _now()}, handle, indent=2)
142
- os.replace(tmp, path)
143
-
144
-
145
- def _read_detection_state(project_root: str) -> Optional[Dict[str, Any]]:
146
- try:
147
- with open(_detection_state_path(project_root), "r", encoding="utf-8") as handle:
148
- return json.load(handle)
149
- except (OSError, json.JSONDecodeError):
150
- return None
151
-
152
-
153
- def detect_entities(
154
- project_root: str,
155
- *,
156
- threshold: float = DEFAULT_CLUSTER_THRESHOLD,
157
- min_cluster_size: int = DEFAULT_MIN_CLUSTER_SIZE,
158
- max_clusters: int = 24,
159
- job_id: Optional[str] = None,
160
- ) -> Dict[str, Any]:
161
- """Cluster visual frame vectors into provisional entities and return the
162
- deferred host-vision confirmation payload (one frame per cluster)."""
163
- ma = _ma()
164
- conn = timeline_brain_db.connect(project_root)
165
- rows = conn.execute(
166
- """
167
- SELECT entity_uuid, vector FROM embeddings
168
- WHERE embedding_kind = 'visual' AND entity_type = 'frame'
169
- """
170
- ).fetchall()
171
- if not rows:
172
- return {
173
- "success": False,
174
- "error": (
175
- "No visual frame embeddings yet — run "
176
- "media_analysis(action='build_embeddings', params={'kinds': ['visual']}) first."
177
- ),
178
- }
179
- frame_refs = [str(r["entity_uuid"]) for r in rows]
180
- vectors = [embeddings.unpack_vector(r["vector"]) for r in rows]
181
-
182
- clusters = [
183
- members for members in _cluster_vectors(vectors, float(threshold))
184
- if len(members) >= int(min_cluster_size)
185
- ][: int(max_clusters)]
186
- if not clusters:
187
- return {
188
- "success": True,
189
- "status": "no_clusters",
190
- "frame_count": len(rows),
191
- "note": f"No clusters of size >= {min_cluster_size} at threshold {threshold}.",
192
- }
193
-
194
- # Hydrate frame rows once: frame_ref -> (clip_uuid, frame_index, path, shot_uuid).
195
- frame_info: Dict[str, Dict[str, Any]] = {}
196
- for ref in frame_refs:
197
- clip_uuid, _, frame_index = ref.rpartition(":")
198
- row = conn.execute(
199
- "SELECT frame_path, shot_uuid FROM frames WHERE clip_uuid = ? AND frame_index = ?",
200
- (clip_uuid, int(frame_index) if frame_index.lstrip("-").isdigit() else -1),
201
- ).fetchone()
202
- frame_info[ref] = {
203
- "clip_uuid": clip_uuid,
204
- "frame_index": frame_index,
205
- "frame_path": str(row["frame_path"]) if row and row["frame_path"] else None,
206
- "shot_uuid": str(row["shot_uuid"]) if row and row["shot_uuid"] else None,
207
- }
208
-
209
- # Caps: one frame per cluster goes to the host.
210
- estimated_tokens = len(clusters) * ma.AVG_VISION_TOKENS_PER_FRAME
211
- refusal = ma._check_caps_pre_call(
212
- project_root=project_root,
213
- estimated_vision_tokens=estimated_tokens,
214
- clip_id=None,
215
- job_id=job_id,
216
- )
217
- if refusal is not None:
218
- return refusal
219
-
220
- now = _now()
221
- cluster_payload: List[Dict[str, Any]] = []
222
- entity_uuids: List[str] = []
223
- with timeline_brain_db.transaction(project_root) as txn:
224
- for index, members in enumerate(clusters, 1):
225
- refs = [frame_refs[i] for i in members]
226
- rep_i, sims = _representative(vectors, members)
227
- rep_ref = frame_refs[rep_i]
228
- rep = frame_info[rep_ref]
229
- entity_uuid = _entity_uuid_for(refs)
230
- entity_uuids.append(entity_uuid)
231
- existing = txn.execute(
232
- "SELECT created_at, kind, label, description, confidence, source FROM entities WHERE entity_uuid = ?",
233
- (entity_uuid,),
234
- ).fetchone()
235
- txn.execute(
236
- """
237
- INSERT OR REPLACE INTO entities
238
- (entity_uuid, kind, label, description, confidence, source,
239
- representative_frame_ref, representative_frame_path,
240
- cluster_size, created_at, updated_at)
241
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
242
- """,
243
- (
244
- entity_uuid,
245
- existing["kind"] if existing else "unknown",
246
- existing["label"] if existing else None,
247
- existing["description"] if existing else None,
248
- existing["confidence"] if existing else None,
249
- existing["source"] if existing else "clustering",
250
- rep_ref,
251
- rep["frame_path"],
252
- len(members),
253
- str(existing["created_at"]) if existing else now,
254
- now,
255
- ),
256
- )
257
- txn.execute("DELETE FROM entity_appearances WHERE entity_uuid = ?", (entity_uuid,))
258
- for i in members:
259
- ref = frame_refs[i]
260
- info = frame_info[ref]
261
- txn.execute(
262
- """
263
- INSERT OR IGNORE INTO entity_appearances
264
- (entity_uuid, clip_uuid, shot_uuid, frame_ref, similarity)
265
- VALUES (?, ?, ?, ?, ?)
266
- """,
267
- (entity_uuid, info["clip_uuid"], info["shot_uuid"], ref, round(float(sims[i]), 4)),
268
- )
269
- clip_count = len({frame_info[r]["clip_uuid"] for r in refs})
270
- shot_count = len({frame_info[r]["shot_uuid"] for r in refs if frame_info[r]["shot_uuid"]})
271
- cluster_payload.append({
272
- "entity_index": index,
273
- "entity_uuid": entity_uuid,
274
- "frame_count": len(members),
275
- "clip_count": clip_count,
276
- "shot_count": shot_count,
277
- "already_labeled": bool(existing and existing["label"]),
278
- "current_label": existing["label"] if existing else None,
279
- "representative_frame_path": rep["frame_path"],
280
- })
281
-
282
- # Prune unlabeled provisional ghosts from earlier runs (a different
283
- # threshold re-shapes clusters → new uuids). Labeled entities persist.
284
- with timeline_brain_db.transaction(project_root) as txn:
285
- placeholders = ",".join("?" for _ in entity_uuids)
286
- txn.execute(
287
- f"""
288
- DELETE FROM entity_appearances WHERE entity_uuid IN (
289
- SELECT entity_uuid FROM entities
290
- WHERE source = 'clustering' AND label IS NULL
291
- AND entity_uuid NOT IN ({placeholders})
292
- )
293
- """,
294
- entity_uuids,
295
- )
296
- txn.execute(
297
- f"""
298
- DELETE FROM entities
299
- WHERE source = 'clustering' AND label IS NULL
300
- AND entity_uuid NOT IN ({placeholders})
301
- """,
302
- entity_uuids,
303
- )
304
-
305
- vision_token = _ma().short_hash("entities:" + ",".join(sorted(entity_uuids)), 16)
306
- _write_detection_state(project_root, vision_token, entity_uuids)
307
- return {
308
- "success": True,
309
- "status": "pending_host_analysis",
310
- "provider": "host_chat_paths",
311
- "mode": "entity_confirmation",
312
- "vision_token": vision_token,
313
- "threshold": threshold,
314
- "frame_count": len(rows),
315
- "cluster_count": len(clusters),
316
- "estimate": {
317
- "frames_to_review": len(clusters),
318
- "estimated_vision_tokens": estimated_tokens,
319
- },
320
- "clusters": cluster_payload,
321
- "frame_paths": [c["representative_frame_path"] for c in cluster_payload if c["representative_frame_path"]],
322
- "schema": json.loads(json.dumps(ENTITY_SCHEMA)),
323
- "schema_reference": ENTITY_SCHEMA_REFERENCE,
324
- "prompt": ENTITY_PROMPT,
325
- "commit_action": {
326
- "tool": "media_analysis",
327
- "action": "commit_entities",
328
- "params": {
329
- "vision_token": vision_token,
330
- "entities": "<host chat: fill per `schema`>",
331
- "analysis_root": project_root,
332
- },
333
- },
334
- "instructions": (
335
- "Read each cluster's representative_frame_path as a local image and "
336
- "produce one entry per entity_index in `entities` per the schema. "
337
- "Then call the tool in commit_action. Clusters marked "
338
- "already_labeled keep their label unless you provide a better one."
339
- ),
340
- }
341
-
342
-
343
- def commit_entities(
344
- project_root: str,
345
- *,
346
- entities_payload: Any,
347
- vision_token: Optional[str] = None,
348
- author: str = "host_chat",
349
- ) -> Dict[str, Any]:
350
- """Apply host-vision labels to provisional entity clusters."""
351
- if isinstance(entities_payload, str):
352
- try:
353
- entities_payload = json.loads(entities_payload)
354
- except json.JSONDecodeError as exc:
355
- return {"success": False, "error": f"entities was a string but not valid JSON: {exc}"}
356
- if isinstance(entities_payload, dict) and isinstance(entities_payload.get("entities"), list):
357
- entities_payload = entities_payload["entities"]
358
- if not isinstance(entities_payload, list) or not entities_payload:
359
- return {"success": False, "error": "commit_entities requires `entities`: a non-empty array"}
360
-
361
- state = _read_detection_state(project_root)
362
- if not state:
363
- return {"success": False, "error": "No entity-detection state — run detect_entities first"}
364
- expected = str(state.get("vision_token") or "")
365
- if vision_token and str(vision_token) != expected:
366
- return {
367
- "success": False,
368
- "error": "vision_token mismatch; entity clusters changed since the payload was issued (re-run detect_entities).",
369
- "expected_vision_token": expected,
370
- }
371
-
372
- # entity_index resolution uses the exact ordering detect_entities issued.
373
- by_index = {i + 1: uuid for i, uuid in enumerate(state.get("ordering") or [])}
374
- now = _now()
375
- updated = 0
376
- merged = 0
377
- with timeline_brain_db.transaction(project_root) as txn:
378
- merges: List[Tuple[str, str]] = []
379
- for entry in entities_payload:
380
- if not isinstance(entry, dict):
381
- continue
382
- uuid = entry.get("entity_uuid") or by_index.get(_safe_int(entry.get("entity_index")))
383
- if not uuid:
384
- continue
385
- merge_target = entry.get("merge_with")
386
- if merge_target is not None:
387
- target_uuid = by_index.get(_safe_int(merge_target))
388
- if target_uuid and target_uuid != uuid:
389
- merges.append((str(uuid), target_uuid))
390
- continue
391
- txn.execute(
392
- """
393
- UPDATE entities
394
- SET kind = ?, label = ?, description = ?, confidence = ?,
395
- source = ?, updated_at = ?
396
- WHERE entity_uuid = ?
397
- """,
398
- (
399
- str(entry.get("kind") or "unknown"),
400
- entry.get("label"),
401
- entry.get("description"),
402
- entry.get("confidence"),
403
- ENTITY_VISION_SOURCE if author == "host_chat" else "human",
404
- now,
405
- str(uuid),
406
- ),
407
- )
408
- updated += 1
409
- for duplicate, target in merges:
410
- txn.execute(
411
- "UPDATE OR IGNORE entity_appearances SET entity_uuid = ? WHERE entity_uuid = ?",
412
- (target, duplicate),
413
- )
414
- txn.execute("DELETE FROM entity_appearances WHERE entity_uuid = ?", (duplicate,))
415
- txn.execute("DELETE FROM entities WHERE entity_uuid = ?", (duplicate,))
416
- size = txn.execute(
417
- "SELECT COUNT(*) FROM entity_appearances WHERE entity_uuid = ?", (target,)
418
- ).fetchone()[0]
419
- txn.execute(
420
- "UPDATE entities SET cluster_size = ?, updated_at = ? WHERE entity_uuid = ?",
421
- (int(size), now, target),
422
- )
423
- merged += 1
424
-
425
- ma = _ma()
426
- ma._record_caps_usage(
427
- project_root=project_root,
428
- clip_id=None,
429
- vision_tokens=updated * ma.AVG_VISION_TOKENS_PER_FRAME,
430
- frames_uploaded=updated,
431
- )
432
- return {"success": True, "entities_updated": updated, "entities_merged": merged}
433
-
434
-
435
- def _safe_int(value: Any) -> Optional[int]:
436
- try:
437
- return int(value)
438
- except (TypeError, ValueError):
439
- return None
440
-
441
-
442
- def list_entities(project_root: str, *, kind: Optional[str] = None) -> Dict[str, Any]:
443
- conn = timeline_brain_db.connect(project_root)
444
- where = " WHERE kind = ?" if kind else ""
445
- args: Tuple[Any, ...] = (kind,) if kind else ()
446
- rows = conn.execute(
447
- f"SELECT * FROM entities{where} ORDER BY cluster_size DESC, label", args
448
- ).fetchall()
449
- entities: List[Dict[str, Any]] = []
450
- for row in rows:
451
- entity = dict(row)
452
- appearances = conn.execute(
453
- """
454
- SELECT a.clip_uuid, c.clip_name, a.shot_uuid, s.shot_index, a.frame_ref, a.similarity
455
- FROM entity_appearances a
456
- LEFT JOIN clips c ON c.clip_uuid = a.clip_uuid
457
- LEFT JOIN shots s ON s.shot_uuid = a.shot_uuid
458
- WHERE a.entity_uuid = ?
459
- ORDER BY a.clip_uuid, s.shot_index
460
- """,
461
- (entity["entity_uuid"],),
462
- ).fetchall()
463
- entity["appearances"] = [dict(a) for a in appearances]
464
- entity["clip_count"] = len({a["clip_uuid"] for a in appearances})
465
- entity["shot_count"] = len({a["shot_uuid"] for a in appearances if a["shot_uuid"]})
466
- entities.append(entity)
467
- return {"success": True, "count": len(entities), "entities": entities}
468
-
469
-
470
- # ── bin briefing v2 ──────────────────────────────────────────────────────────
471
-
472
-
473
- def prepare_bin_briefing(project_root: str) -> Dict[str, Any]:
474
- """Deferred payload for a host-synthesized bin briefing: labeled entities
475
- + per-clip summaries. Text-only — no frames, no vision cost."""
476
- conn = timeline_brain_db.connect(project_root)
477
- clips = [dict(r) for r in conn.execute(
478
- "SELECT clip_uuid, clip_name, summary, duration_seconds, shot_count, overall_motion_level FROM clips ORDER BY clip_name"
479
- ).fetchall()]
480
- if not clips:
481
- return {"success": False, "error": "No analyzed clips in the DB — analyze (or db_ingest) first."}
482
- for clip in clips:
483
- row = conn.execute(
484
- """
485
- SELECT value_json FROM subjective_fields
486
- WHERE entity_type='clip' AND entity_uuid=? AND field_path='clip_summary'
487
- AND superseded_at IS NULL
488
- """,
489
- (clip["clip_uuid"],),
490
- ).fetchone()
491
- if row:
492
- try:
493
- clip["clip_summary"] = json.loads(row["value_json"])
494
- except (TypeError, ValueError):
495
- pass
496
- listed = list_entities(project_root)
497
- entity_lines = [
498
- {
499
- "label": e.get("label") or "(unlabeled cluster)",
500
- "kind": e.get("kind"),
501
- "description": e.get("description"),
502
- "clip_count": e.get("clip_count"),
503
- "shot_count": e.get("shot_count"),
504
- "frame_count": e.get("cluster_size"),
505
- }
506
- for e in listed.get("entities") or []
507
- ]
508
- token = _ma().short_hash(
509
- "briefing:" + ",".join(sorted(c["clip_uuid"] for c in clips)), 16
510
- )
511
- return {
512
- "success": True,
513
- "status": "pending_host_synthesis",
514
- "briefing_token": token,
515
- "clips": clips,
516
- "entities": entity_lines,
517
- "prompt": (
518
- "Write a colleague-style bin briefing in markdown: 2-4 paragraphs "
519
- "covering what this media is, who/what recurs (use the entities "
520
- "list), the strongest material, and anything an editor should know "
521
- "before cutting. Conservative claims only — describe, don't guess. "
522
- "Then call the tool in commit_action with `briefing` set to the markdown."
523
- ),
524
- "commit_action": {
525
- "tool": "media_analysis",
526
- "action": "commit_bin_summary",
527
- "params": {
528
- "briefing": "<host chat: markdown briefing>",
529
- "briefing_token": token,
530
- "analysis_root": project_root,
531
- },
532
- },
533
- }
534
-
535
-
536
- def commit_bin_summary(
537
- project_root: str,
538
- *,
539
- briefing: Any,
540
- briefing_token: Optional[str] = None,
541
- author: str = "host_chat",
542
- ) -> Dict[str, Any]:
543
- """Write the host-synthesized briefing above the v2.0 aggregate."""
544
- if not isinstance(briefing, str) or not briefing.strip():
545
- return {"success": False, "error": "commit_bin_summary requires `briefing` (markdown text)"}
546
- conn = timeline_brain_db.connect(project_root)
547
- clips = [str(r["clip_uuid"]) for r in conn.execute("SELECT clip_uuid FROM clips ORDER BY clip_name").fetchall()]
548
- expected = _ma().short_hash("briefing:" + ",".join(sorted(clips)), 16)
549
- if briefing_token and str(briefing_token) != expected:
550
- return {
551
- "success": False,
552
- "error": "briefing_token mismatch; the clip set changed since the payload was issued (re-run prepare_bin_briefing).",
553
- }
554
- analysis_memory.ensure_memory_structure(project_root)
555
- path = analysis_memory.bin_summary_path(project_root)
556
- appendix = ""
557
- if os.path.isfile(path):
558
- try:
559
- with open(path, "r", encoding="utf-8") as handle:
560
- existing = handle.read()
561
- # Keep only the aggregate section as an appendix (drop a previous
562
- # synthesized briefing if present).
563
- marker = "# Bin summary —"
564
- idx = existing.find(marker)
565
- if idx >= 0:
566
- appendix = existing[idx:]
567
- except OSError:
568
- appendix = ""
569
- content = (
570
- f"# Bin briefing\n\n_Synthesized {_now()} by {author} "
571
- f"(entities + per-clip summaries)._\n\n{briefing.strip()}\n"
572
- )
573
- if appendix:
574
- content += f"\n\n---\n\n{appendix}"
575
- tmp = path + ".tmp"
576
- with open(tmp, "w", encoding="utf-8") as handle:
577
- handle.write(content)
578
- os.replace(tmp, path)
579
- return {"success": True, "path": path, "bytes": len(content)}