@hybridlabor-api/aos 4.4.1 → 4.4.2-beta.1

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 +471 -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,606 +0,0 @@
1
- """Version-on-mutate hook + destructive-action registry (C6).
2
-
3
- Wraps each top-level compound tool function in `src/server.py` so that any
4
- destructive sub-action automatically:
5
-
6
- 1. Resolves the project root via the configured project-root provider.
7
- 2. Calls `timeline_versioning.ensure_versioned_before_mutation()` for the run.
8
- 3. Captures the declared metric's `before_value` (if `metric` was passed in params).
9
- 4. Runs the original handler.
10
- 5. Captures the declared metric's `after_value` (and gap stats by default).
11
- 6. Logs a `brain_edits` row.
12
-
13
- If anything in the hook fails — Resolve isn't connected, no current project,
14
- project_root can't be resolved, the DB can't be opened — the wrapper degrades
15
- silently and the underlying handler still runs. The user's edit must never be
16
- blocked by a versioning failure. (Failures are logged at WARNING.)
17
-
18
- ## Registering destructive actions
19
-
20
- Add `(tool_name, action_name)` entries to `DESTRUCTIVE_ACTIONS_BY_TOOL`. The
21
- enforcement test in `tests/test_destructive_decorator_coverage.py` confirms
22
- every tool that owns destructive actions is decorated.
23
- """
24
-
25
- from __future__ import annotations
26
-
27
- import functools
28
- import logging
29
- from typing import Any, Callable, Dict, FrozenSet, Optional, Tuple
30
-
31
- from src.utils import analysis_runs, brain_edits, media_pool_changes, timeline_versioning
32
-
33
- logger = logging.getLogger("resolve-mcp.destructive-hook")
34
-
35
-
36
- # ── Destructive action registry ──────────────────────────────────────────────
37
- #
38
- # Tool name → frozenset of action strings that mutate the working timeline (or
39
- # the timeline-item subtree). Adding a new destructive action means: (a) add it
40
- # here; (b) optionally add a metric-capture entry below if it has a sensible
41
- # default metric.
42
-
43
- # Each action is keyed under the tool whose @_destructive_op wrapper actually
44
- # DISPATCHES it. Historically many entries were filed under the wrong tool key
45
- # (e.g. create_timeline/auto_sync_audio under "timeline" though they are media_pool
46
- # actions; set_cdl/copy_grades under "timeline_item" though timeline_item_color
47
- # dispatches them; *_fusion_comp / *_take stale names that never matched), so
48
- # is_destructive() returned False and version-on-mutate archiving silently did not
49
- # fire. EX-REG re-filed every action under its real dispatcher and dropped inert
50
- # entries whose tool is not @_destructive_op-wrapped (e.g. media_pool_item
51
- # replace_clip/link_*). The test_destructive_registry_drift guard asserts every
52
- # string here is a real handler so this can't regress.
53
- DESTRUCTIVE_ACTIONS_BY_TOOL: Dict[str, FrozenSet[str]] = {
54
- "media_pool": frozenset({
55
- "delete_clips",
56
- "delete_folders",
57
- "move_clips",
58
- "move_folders",
59
- "delete_clip_mattes",
60
- "delete_timelines",
61
- "create_timeline",
62
- "create_timeline_from_clips",
63
- "append_to_timeline",
64
- "setup_multicam_timeline",
65
- "create_stereo_clip",
66
- "auto_sync_audio",
67
- "set_clip_marks",
68
- "clear_clip_marks",
69
- }),
70
- "edit_engine": frozenset({
71
- "execute_selects",
72
- "execute_tighten",
73
- "execute_swap",
74
- }),
75
- "timeline": frozenset({
76
- "delete_clips",
77
- "move_clips",
78
- "duplicate_clips",
79
- "copy_clips",
80
- "copy_range",
81
- "duplicate_range",
82
- "overwrite_range",
83
- "lift_range",
84
- "apply_cuts",
85
- "create_compound_clip",
86
- "create_fusion_clip",
87
- "convert_to_stereo",
88
- "set_clips_linked",
89
- "duplicate",
90
- "insert_generator",
91
- "insert_fusion_generator",
92
- "insert_fusion_composition",
93
- "insert_ofx_generator",
94
- "insert_title",
95
- "insert_fusion_title",
96
- "add_track",
97
- "delete_track",
98
- "set_track_lock",
99
- "set_track_enable",
100
- "set_track_name",
101
- "set_voice_isolation_state",
102
- "set_name",
103
- "set_start_timecode",
104
- "set_setting",
105
- "set_mark_in_out",
106
- "clear_mark_in_out",
107
- "set_title_text",
108
- "import_into_timeline",
109
- }),
110
- "timeline_markers": frozenset({
111
- "add",
112
- "delete_at_frame",
113
- "delete_by_custom_data",
114
- "delete_by_color",
115
- "update_custom_data",
116
- }),
117
- "timeline_ai": frozenset({
118
- "detect_scene_cuts",
119
- "analyze_dolby_vision",
120
- "create_subtitles",
121
- }),
122
- "timeline_item": frozenset({
123
- "set_clip_enabled",
124
- "set_property",
125
- "set_name",
126
- "set_voice_isolation_state",
127
- "update_sidecar",
128
- "set_transform",
129
- "set_crop",
130
- "set_retime",
131
- "set_composite",
132
- "set_audio",
133
- }),
134
- "timeline_item_markers": frozenset({
135
- "add",
136
- "add_flag",
137
- "clear_flags",
138
- "set_clip_color",
139
- "clear_clip_color",
140
- "delete_at_frame",
141
- "delete_by_custom_data",
142
- "delete_by_color",
143
- "update_custom_data",
144
- }),
145
- "timeline_item_fusion": frozenset({
146
- "add_comp",
147
- "delete_comp",
148
- "import_comp",
149
- "load_comp",
150
- "rename_comp",
151
- }),
152
- "timeline_item_color": frozenset({
153
- "set_cdl",
154
- "copy_grades",
155
- "reset_all_node_colors",
156
- "assign_color_group",
157
- "remove_from_color_group",
158
- "create_magic_mask",
159
- "regenerate_magic_mask",
160
- "smart_reframe",
161
- "stabilize",
162
- "export_lut",
163
- "add_version",
164
- "delete_version",
165
- "load_version",
166
- "rename_version",
167
- "set_color_cache",
168
- "set_fusion_cache",
169
- }),
170
- "timeline_item_takes": frozenset({
171
- "add",
172
- "delete",
173
- "select",
174
- "finalize",
175
- }),
176
- "graph": frozenset({
177
- "set_lut",
178
- "set_node_enabled",
179
- "apply_arri_cdl_lut",
180
- "apply_grade_from_drx",
181
- "reset_all_grades",
182
- }),
183
- }
184
-
185
-
186
- # ── No-archive filters ──────────────────────────────────────────────────────
187
- #
188
- # Some destructive *actions* are technically mutations but their *payloads* are
189
- # free-text metadata that nobody wants to roll back ("Notes" updates, comment
190
- # tweaks, etc.). Registering them in NO_ARCHIVE_ON_KEYS means: if the action's
191
- # `key` param is in this set, skip versioning. Other key choices still archive.
192
- #
193
- # Shape: (tool, action) → {keys_that_skip_archiving}
194
- # - empty set or missing entry → always archive
195
- # - non-empty set → only the listed keys skip archiving
196
-
197
- NO_ARCHIVE_ON_KEYS: Dict[Tuple[str, str], frozenset] = {
198
- ("timeline_item", "set_property"): frozenset({"Notes"}),
199
- # set_clip_property on timeline can target Name/Notes/Comments/etc. — only
200
- # Notes/Comments are noise; Name changes are real edits and stay archived.
201
- ("timeline", "set_clip_property"): frozenset({"Notes", "Comments"}),
202
- }
203
-
204
-
205
- # ── Strict-mode allowlist ───────────────────────────────────────────────────
206
- #
207
- # Actions in this set REFUSE to run if the version-on-mutate archive fails. For
208
- # everything else (the vast majority), the underlying handler still runs even
209
- # when archive fails — preferring user progress over perfect history. But
210
- # `delete_timelines` permanently destroys timelines; if we couldn't archive
211
- # first, the user almost certainly wants to know before losing data.
212
-
213
- STRICT_DEFAULT_ACTIONS: frozenset = frozenset({
214
- ("timeline", "delete_track"),
215
- # NOTE: delete_timelines is a media_pool action (EX-REG re-keyed it); it is
216
- # archive+confirm-token gated (EX3) rather than strict, per the decision that
217
- # strict over-blocks deletes when there is nothing to archive.
218
- # delete_clips with ripple=True is destructive in a way single-clip delete
219
- # isn't — handled separately in is_strict_required() because it depends on
220
- # the params payload, not just the action name.
221
- })
222
-
223
-
224
- def _payload_only_touches_no_archive_keys(
225
- tool_name: str, action: str, params: Optional[Dict[str, Any]],
226
- ) -> bool:
227
- """Return True iff the destructive action's payload is filtered out."""
228
- allowlist = NO_ARCHIVE_ON_KEYS.get((tool_name, action))
229
- if not allowlist or not isinstance(params, dict):
230
- return False
231
- # Only filter if `key` param is present (set_property style). Otherwise
232
- # default to archiving.
233
- key = params.get("key")
234
- if not key:
235
- return False
236
- return str(key) in allowlist
237
-
238
-
239
- def is_destructive(tool_name: str, action: str, params: Optional[Dict[str, Any]] = None) -> bool:
240
- """Should this (tool, action, payload) trigger version-on-mutate?
241
-
242
- Checks the registry AND the payload-aware no-archive filter. Idempotent.
243
- """
244
- if action not in DESTRUCTIVE_ACTIONS_BY_TOOL.get(tool_name, frozenset()):
245
- return False
246
- if _payload_only_touches_no_archive_keys(tool_name, action, params):
247
- return False
248
- return True
249
-
250
-
251
- def is_strict_required(tool_name: str, action: str, params: Optional[Dict[str, Any]]) -> bool:
252
- """True if the call must refuse to run when archive fails.
253
-
254
- Triggers on (a) STRICT_DEFAULT_ACTIONS membership, (b) ripple delete on
255
- timeline.delete_clips, or (c) explicit `strict=True` in params.
256
- """
257
- if isinstance(params, dict) and params.get("strict") is True:
258
- return True
259
- if (tool_name, action) in STRICT_DEFAULT_ACTIONS:
260
- return True
261
- if (
262
- tool_name == "timeline"
263
- and action == "delete_clips"
264
- and isinstance(params, dict)
265
- and bool(params.get("ripple"))
266
- ):
267
- return True
268
- return False
269
-
270
-
271
- # ── Provider hooks ───────────────────────────────────────────────────────────
272
-
273
- _ProjectRootProvider = Callable[[], Optional[Tuple[Any, Any, str, Optional[str]]]]
274
- """Returns (resolve_handle, project_handle, project_root, project_name) or None."""
275
-
276
- _PROVIDER: Optional[_ProjectRootProvider] = None
277
-
278
- _PreferenceProvider = Callable[[str], Any]
279
- """Reads a named preference (e.g. timeline_versioning_auto_save_after_archive)."""
280
-
281
- _PREFERENCE_PROVIDER: Optional[_PreferenceProvider] = None
282
-
283
- _PendingConfirmCheck = Callable[[str, str, Optional[Dict[str, Any]]], bool]
284
- """Returns True iff this (tool, action, params) call will be short-circuited by
285
- the confirm-token gate to issue a fresh token (no mutation will run). When True
286
- the wrapper skips the pre-mutation archive — there's nothing yet to archive.
287
- """
288
-
289
- _PENDING_CONFIRM_CHECK: Optional[_PendingConfirmCheck] = None
290
-
291
-
292
- def register_project_root_provider(fn: _ProjectRootProvider) -> None:
293
- """Install the provider used by the hook to locate the active project root.
294
-
295
- `src/server.py` registers a provider during startup that consults its
296
- `get_resolve()` helper + the analysis output-root resolver.
297
- """
298
- global _PROVIDER
299
- _PROVIDER = fn
300
-
301
-
302
- def register_preference_provider(fn: _PreferenceProvider) -> None:
303
- """Install the provider used by the hook to read C6 preferences."""
304
- global _PREFERENCE_PROVIDER
305
- _PREFERENCE_PROVIDER = fn
306
-
307
-
308
- def register_pending_confirm_check(fn: _PendingConfirmCheck) -> None:
309
- """Install the predicate used to detect token-issuance calls.
310
-
311
- When the predicate returns True, the wrapper bypasses the pre-mutation
312
- archive and the brain_edit row entirely — the underlying handler is still
313
- invoked so it can mint and return the confirm_token, but no state has
314
- changed yet. This avoids one wasted archive per token-issuance call (F4).
315
- """
316
- global _PENDING_CONFIRM_CHECK
317
- _PENDING_CONFIRM_CHECK = fn
318
-
319
-
320
- def _read_preference(key: str, default: Any = None) -> Any:
321
- if _PREFERENCE_PROVIDER is None:
322
- return default
323
- try:
324
- value = _PREFERENCE_PROVIDER(key)
325
- except Exception as exc:
326
- logger.debug("preference provider for %s raised: %s", key, exc)
327
- return default
328
- return default if value is None else value
329
-
330
-
331
- def _resolve_versioning_context() -> Optional[Tuple[Any, Any, str, Optional[str]]]:
332
- if _PROVIDER is None:
333
- return None
334
- try:
335
- return _PROVIDER()
336
- except Exception as exc:
337
- logger.debug("project-root provider raised: %s", exc)
338
- return None
339
-
340
-
341
- # ── Action analysis_run_id source ────────────────────────────────────────────
342
-
343
-
344
- def _extract_analysis_run_id(params: Optional[Dict[str, Any]], project_root: Optional[str] = None) -> str:
345
- """Resolve the analysis_run_id for this call.
346
-
347
- Order:
348
- 1. explicit param `analysis_run_id` or `run_id`
349
- 2. process-level current run (active begin_run/auto-run; idle timer applied)
350
- 3. open a fresh auto-run for this destructive op (B3)
351
-
352
- The auto-run path (step 3) sets initiator='auto' and is closed by either
353
- the next idle-timeout firing on a later destructive op, or an explicit
354
- end_run call.
355
- """
356
- if isinstance(params, dict):
357
- rid = params.get("analysis_run_id") or params.get("run_id")
358
- if rid:
359
- return str(rid)
360
- if project_root:
361
- try:
362
- timeout = float(_read_preference("versioning_auto_run_idle_timeout_seconds", default=90))
363
- except Exception:
364
- timeout = 90.0
365
- try:
366
- return analysis_runs.ensure_auto_run_for_destructive(
367
- project_root=project_root, idle_timeout_seconds=timeout,
368
- )
369
- except Exception as exc:
370
- logger.debug("auto-run open failed; falling back to ephemeral id: %s", exc)
371
- active = analysis_runs.current_run_id()
372
- if active:
373
- analysis_runs.bump_idle_timer()
374
- return active
375
- return timeline_versioning.new_analysis_run_id()
376
-
377
-
378
- def _extract_initiator(params: Optional[Dict[str, Any]]) -> Optional[str]:
379
- """Provenance label for the row. Explicit param > current run initiator > None."""
380
- if isinstance(params, dict) and params.get("initiator"):
381
- return str(params["initiator"])
382
- return analysis_runs.current_run_initiator()
383
-
384
-
385
- def _extract_metric(params: Optional[Dict[str, Any]]) -> Tuple[Optional[str], Optional[str], Optional[str]]:
386
- if not isinstance(params, dict):
387
- return (None, None, None)
388
- return (
389
- params.get("metric"),
390
- params.get("direction"),
391
- params.get("rationale"),
392
- )
393
-
394
-
395
- # ── Decorator ────────────────────────────────────────────────────────────────
396
-
397
-
398
- def destructive_op(tool_name: str) -> Callable[[Callable[..., Any]], Callable[..., Any]]:
399
- """Wrap a top-level tool function with the version-on-mutate hook.
400
-
401
- The wrapped function must accept `action: str` as its first positional
402
- argument and `params: Optional[Dict]` as its second (matches the existing
403
- compound-tool signature).
404
- """
405
-
406
- def decorator(fn: Callable[..., Any]) -> Callable[..., Any]:
407
- @functools.wraps(fn)
408
- def wrapper(action: str, params: Optional[Dict[str, Any]] = None, *args, **kwargs) -> Any:
409
- if not is_destructive(tool_name, action, params):
410
- return fn(action, params, *args, **kwargs)
411
-
412
- # F4 — token-issuance calls don't mutate; skip the archive entirely
413
- # so that token preview/cancel paths don't litter the version chain.
414
- # The wrapper still annotates `_versioning` on the result so callers
415
- # can see *why* nothing was archived.
416
- if _PENDING_CONFIRM_CHECK is not None:
417
- try:
418
- will_gate = bool(_PENDING_CONFIRM_CHECK(tool_name, action, params))
419
- except Exception as exc:
420
- logger.debug("pending-confirm check raised: %s", exc)
421
- will_gate = False
422
- if will_gate:
423
- result = fn(action, params, *args, **kwargs)
424
- if isinstance(result, dict):
425
- result.setdefault("_versioning", {
426
- "analysis_run_id": None,
427
- "archived": False,
428
- "skipped_reason": "pending_confirm_token",
429
- })
430
- return result
431
-
432
- strict = is_strict_required(tool_name, action, params)
433
-
434
- ctx = _resolve_versioning_context()
435
- if ctx is None:
436
- if strict:
437
- return {
438
- "success": False,
439
- "error": (
440
- f"strict mode: '{tool_name}.{action}' refuses to run because the "
441
- "version-on-mutate context (project_root) couldn't be resolved. "
442
- "Open a project in Resolve, or pass strict=false to override."
443
- ),
444
- }
445
- # No Resolve / no project — let the underlying handler run; it
446
- # will either succeed (e.g. in dry-run) or surface its own error.
447
- return fn(action, params, *args, **kwargs)
448
-
449
- resolve_h, project_h, project_root, project_name = ctx
450
- run_id = _extract_analysis_run_id(params, project_root=project_root)
451
- metric, direction, rationale = _extract_metric(params)
452
- initiator = _extract_initiator(params)
453
-
454
- # Media-pool destructive ops don't archive a timeline; they log to
455
- # a separate provenance table. Branch here so the rest of the
456
- # wrapper only handles the timeline path.
457
- if tool_name == "media_pool":
458
- result = fn(action, params, *args, **kwargs)
459
- try:
460
- media_pool_changes.log_media_pool_change(
461
- project_root=project_root,
462
- analysis_run_id=run_id,
463
- action=action,
464
- params=params if isinstance(params, dict) else None,
465
- after_state=_summarise_result(result),
466
- initiator=initiator,
467
- )
468
- except Exception as exc:
469
- logger.warning("media_pool_change log failed: %s", exc)
470
- if isinstance(result, dict):
471
- result.setdefault("_versioning", {
472
- "analysis_run_id": run_id,
473
- "category": "media_pool",
474
- "initiator": initiator,
475
- })
476
- return result
477
-
478
- before_value: Optional[float] = None
479
- timeline_before_name: Optional[str] = None
480
- try:
481
- current_tl = project_h.GetCurrentTimeline()
482
- if current_tl is not None:
483
- timeline_before_name = current_tl.GetName()
484
- if metric:
485
- before_value = brain_edits.capture_metric(metric, current_tl)
486
- except Exception as exc:
487
- logger.debug("pre-capture failed: %s", exc)
488
-
489
- archive_result: Dict[str, Any] = {"archived": False}
490
- archive_exc: Optional[Exception] = None
491
- auto_save = bool(_read_preference(
492
- "timeline_versioning_auto_save_after_archive", False,
493
- ))
494
- try:
495
- archive_result = timeline_versioning.ensure_versioned_before_mutation(
496
- resolve=resolve_h,
497
- project=project_h,
498
- project_root=project_root,
499
- analysis_run_id=run_id,
500
- reason=f"{tool_name}.{action}",
501
- initiator=initiator,
502
- auto_save=auto_save,
503
- )
504
- except Exception as exc:
505
- archive_exc = exc
506
- logger.warning(
507
- "version-on-mutate hook failed (%sstrict mode): %s",
508
- "blocking under " if strict else "continuing past in non-",
509
- exc,
510
- )
511
-
512
- # Strict mode: archive must have either landed OR been a legitimate
513
- # no-op (already_archived_for_run | no_current_timeline). If the
514
- # hook raised, OR returned archived=False with an unexpected reason,
515
- # refuse the underlying call.
516
- if strict:
517
- skipped_ok = (
518
- archive_result.get("archived") is False
519
- and archive_result.get("skipped_reason") in {
520
- "already_archived_for_run",
521
- }
522
- )
523
- if archive_exc is not None or (
524
- not archive_result.get("archived") and not skipped_ok
525
- ):
526
- return {
527
- "success": False,
528
- "error": (
529
- f"strict mode: refused '{tool_name}.{action}' because the "
530
- "pre-mutation archive could not be created. "
531
- f"reason={archive_result.get('skipped_reason') or 'archive_failed'} "
532
- f"exception={type(archive_exc).__name__ if archive_exc else 'none'}"
533
- ),
534
- "_versioning": {
535
- "analysis_run_id": run_id,
536
- "archived": False,
537
- "strict_block": True,
538
- },
539
- }
540
-
541
- # Run the underlying handler regardless of hook outcome.
542
- result = fn(action, params, *args, **kwargs)
543
-
544
- after_value: Optional[float] = None
545
- timeline_after_name: Optional[str] = timeline_before_name
546
- try:
547
- current_tl = project_h.GetCurrentTimeline()
548
- if current_tl is not None:
549
- timeline_after_name = current_tl.GetName()
550
- if metric:
551
- after_value = brain_edits.capture_metric(metric, current_tl)
552
- except Exception as exc:
553
- logger.debug("post-capture failed: %s", exc)
554
-
555
- try:
556
- brain_edits.log_brain_edit(
557
- project_root=project_root,
558
- analysis_run_id=run_id,
559
- edit_type=f"{tool_name}.{action}",
560
- tool_name=tool_name,
561
- action_name=action,
562
- timeline_before=timeline_before_name,
563
- timeline_after=timeline_after_name,
564
- target_metric=metric,
565
- metric_direction=direction,
566
- before_value=before_value,
567
- after_value=after_value,
568
- rationale=rationale,
569
- params=params if isinstance(params, dict) else None,
570
- result_summary=_summarise_result(result),
571
- project_name=project_name,
572
- initiator=initiator,
573
- )
574
- except Exception as exc:
575
- logger.warning("brain_edit log failed: %s", exc)
576
-
577
- # Annotate the result with versioning context so the caller (and the
578
- # control panel) can show what happened. Don't shadow existing keys.
579
- if isinstance(result, dict):
580
- result.setdefault("_versioning", {
581
- "analysis_run_id": run_id,
582
- "archived": archive_result.get("archived"),
583
- "archived_version": archive_result.get("version"),
584
- "metric": metric,
585
- "before_value": before_value,
586
- "after_value": after_value,
587
- })
588
- return result
589
-
590
- wrapper.__wrapped_tool_name__ = tool_name # type: ignore[attr-defined]
591
- wrapper.__is_destructive_wrapped__ = True # type: ignore[attr-defined]
592
- return wrapper
593
-
594
- return decorator
595
-
596
-
597
- def _summarise_result(result: Any) -> Optional[Dict[str, Any]]:
598
- """Compact result summary suitable for the brain_edit row."""
599
- if not isinstance(result, dict):
600
- return None
601
- keys = ("success", "error", "name", "count", "deleted", "inserted", "updated")
602
- summary: Dict[str, Any] = {}
603
- for key in keys:
604
- if key in result:
605
- summary[key] = result[key]
606
- return summary or None