@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,805 +0,0 @@
1
- """Single-user batch jobs for source-safe media analysis.
2
-
3
- The job layer is deliberately small and durable: SQLite tracks operational
4
- state, the existing JSON reports remain the analysis source of truth, and each
5
- runner call processes a bounded slice so agents do not need to hold long chat
6
- turns open.
7
- """
8
-
9
- from __future__ import annotations
10
-
11
- import copy
12
- import hashlib
13
- import json
14
- import os
15
- import sqlite3
16
- import time
17
- from pathlib import Path
18
- from typing import Any, Dict, Iterable, List, Optional, Tuple
19
-
20
- from src.utils.media_analysis import (
21
- ANALYSIS_VERSION,
22
- build_analysis_index,
23
- build_plan,
24
- detect_capabilities,
25
- execute_plan,
26
- normalize_path,
27
- plan_requires_capabilities,
28
- resolve_output_root,
29
- stable_clip_directory,
30
- summarize_reports,
31
- )
32
-
33
-
34
- JOBS_DB_FILENAME = "jobs.sqlite"
35
- JOB_SCHEMA_VERSION = 1
36
- JOB_DIR_NAME = "jobs"
37
- MEDIA_EXTENSIONS = {
38
- ".3g2",
39
- ".3gp",
40
- ".aac",
41
- ".aif",
42
- ".aiff",
43
- ".ari",
44
- ".arx",
45
- ".braw",
46
- ".cin",
47
- ".crm",
48
- ".dng",
49
- ".dv",
50
- ".exr",
51
- ".flac",
52
- ".m4a",
53
- ".m4v",
54
- ".mkv",
55
- ".mov",
56
- ".mp3",
57
- ".mp4",
58
- ".mxf",
59
- ".ogg",
60
- ".r3d",
61
- ".rmf",
62
- ".wav",
63
- ".webm",
64
- }
65
-
66
-
67
- def _utc_now() -> str:
68
- return time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime())
69
-
70
-
71
- def _stable_json(value: Any) -> str:
72
- return json.dumps(value, ensure_ascii=False, sort_keys=True, default=str)
73
-
74
-
75
- def _read_json(value: str) -> Dict[str, Any]:
76
- try:
77
- payload = json.loads(value or "{}")
78
- except json.JSONDecodeError:
79
- return {}
80
- return payload if isinstance(payload, dict) else {}
81
-
82
-
83
- def _job_id(name: str, target: Dict[str, Any], created_at: str) -> str:
84
- basis = _stable_json({"name": name, "target": target, "created_at": created_at})
85
- return "job-" + hashlib.sha1(basis.encode("utf-8")).hexdigest()[:16]
86
-
87
-
88
- def _is_relative_to(path: str, parent: str) -> bool:
89
- try:
90
- return os.path.commonpath([path, parent]) == parent
91
- except ValueError:
92
- return False
93
-
94
-
95
- def job_db_path(project_root: str, path: Optional[Any] = None) -> Tuple[Optional[str], Optional[str]]:
96
- root = normalize_path(project_root)
97
- candidate = normalize_path(path) if path else os.path.join(root, JOBS_DB_FILENAME)
98
- if not _is_relative_to(candidate, root):
99
- return None, "jobs database path must be under the project analysis root"
100
- return candidate, None
101
-
102
-
103
- def _connect_jobs(project_root: str, path: Optional[Any] = None) -> sqlite3.Connection:
104
- db_path, err = job_db_path(project_root, path)
105
- if err or not db_path:
106
- raise ValueError(err or "Invalid jobs database path")
107
- os.makedirs(os.path.dirname(db_path), exist_ok=True)
108
- conn = sqlite3.connect(db_path)
109
- conn.row_factory = sqlite3.Row
110
- conn.execute("PRAGMA foreign_keys=ON")
111
- conn.execute("PRAGMA journal_mode=WAL")
112
- _ensure_schema(conn)
113
- return conn
114
-
115
-
116
- def _ensure_schema(conn: sqlite3.Connection) -> None:
117
- conn.executescript(
118
- """
119
- CREATE TABLE IF NOT EXISTS job_metadata (
120
- key TEXT PRIMARY KEY,
121
- value TEXT NOT NULL
122
- );
123
-
124
- CREATE TABLE IF NOT EXISTS jobs (
125
- job_id TEXT PRIMARY KEY,
126
- name TEXT NOT NULL,
127
- status TEXT NOT NULL,
128
- phase TEXT NOT NULL,
129
- project_name TEXT NOT NULL,
130
- project_id TEXT,
131
- project_root TEXT NOT NULL,
132
- target_json TEXT NOT NULL,
133
- params_json TEXT NOT NULL,
134
- plan_json TEXT NOT NULL,
135
- total_clips INTEGER NOT NULL DEFAULT 0,
136
- pending_clips INTEGER NOT NULL DEFAULT 0,
137
- running_clips INTEGER NOT NULL DEFAULT 0,
138
- succeeded_clips INTEGER NOT NULL DEFAULT 0,
139
- failed_clips INTEGER NOT NULL DEFAULT 0,
140
- skipped_clips INTEGER NOT NULL DEFAULT 0,
141
- last_error TEXT,
142
- created_at TEXT NOT NULL,
143
- updated_at TEXT NOT NULL,
144
- started_at TEXT,
145
- completed_at TEXT,
146
- canceled_at TEXT
147
- );
148
-
149
- CREATE TABLE IF NOT EXISTS job_clips (
150
- id INTEGER PRIMARY KEY AUTOINCREMENT,
151
- job_id TEXT NOT NULL,
152
- position INTEGER NOT NULL,
153
- clip_key TEXT NOT NULL,
154
- status TEXT NOT NULL,
155
- record_json TEXT NOT NULL,
156
- clip_plan_json TEXT NOT NULL,
157
- report_path TEXT,
158
- marker_plan_path TEXT,
159
- cache_status TEXT,
160
- error TEXT,
161
- attempts INTEGER NOT NULL DEFAULT 0,
162
- started_at TEXT,
163
- completed_at TEXT,
164
- updated_at TEXT NOT NULL,
165
- UNIQUE(job_id, position),
166
- FOREIGN KEY (job_id) REFERENCES jobs(job_id) ON DELETE CASCADE
167
- );
168
-
169
- CREATE INDEX IF NOT EXISTS idx_job_clips_job_status ON job_clips(job_id, status, position);
170
- CREATE INDEX IF NOT EXISTS idx_job_clips_clip_key ON job_clips(clip_key);
171
-
172
- CREATE TABLE IF NOT EXISTS job_events (
173
- id INTEGER PRIMARY KEY AUTOINCREMENT,
174
- job_id TEXT NOT NULL,
175
- event_time TEXT NOT NULL,
176
- level TEXT NOT NULL,
177
- message TEXT NOT NULL,
178
- payload_json TEXT,
179
- FOREIGN KEY (job_id) REFERENCES jobs(job_id) ON DELETE CASCADE
180
- );
181
-
182
- CREATE INDEX IF NOT EXISTS idx_job_events_job_id ON job_events(job_id, id);
183
- """
184
- )
185
- conn.execute(
186
- "INSERT OR REPLACE INTO job_metadata (key, value) VALUES (?, ?)",
187
- ("schema_version", str(JOB_SCHEMA_VERSION)),
188
- )
189
- conn.execute(
190
- "INSERT OR REPLACE INTO job_metadata (key, value) VALUES (?, ?)",
191
- ("analysis_version", ANALYSIS_VERSION),
192
- )
193
- conn.commit()
194
-
195
-
196
- def _event(conn: sqlite3.Connection, job_id: str, level: str, message: str, payload: Optional[Dict[str, Any]] = None) -> None:
197
- conn.execute(
198
- "INSERT INTO job_events (job_id, event_time, level, message, payload_json) VALUES (?, ?, ?, ?, ?)",
199
- (job_id, _utc_now(), level, message, _stable_json(payload or {}) if payload else None),
200
- )
201
-
202
-
203
- def _sync_job_counts(conn: sqlite3.Connection, job_id: str) -> Dict[str, int]:
204
- rows = conn.execute(
205
- "SELECT status, COUNT(*) AS count FROM job_clips WHERE job_id = ? GROUP BY status",
206
- (job_id,),
207
- ).fetchall()
208
- counts = {row["status"]: int(row["count"]) for row in rows}
209
- payload = {
210
- "pending_clips": counts.get("pending", 0),
211
- "running_clips": counts.get("running", 0),
212
- "succeeded_clips": counts.get("succeeded", 0),
213
- "failed_clips": counts.get("failed", 0),
214
- "skipped_clips": counts.get("skipped", 0),
215
- }
216
- conn.execute(
217
- """
218
- UPDATE jobs
219
- SET pending_clips = ?, running_clips = ?, succeeded_clips = ?,
220
- failed_clips = ?, skipped_clips = ?, updated_at = ?
221
- WHERE job_id = ?
222
- """,
223
- (
224
- payload["pending_clips"],
225
- payload["running_clips"],
226
- payload["succeeded_clips"],
227
- payload["failed_clips"],
228
- payload["skipped_clips"],
229
- _utc_now(),
230
- job_id,
231
- ),
232
- )
233
- return payload
234
-
235
-
236
- def _row_dict(row: sqlite3.Row) -> Dict[str, Any]:
237
- return {key: row[key] for key in row.keys()}
238
-
239
-
240
- def _job_paths(project_root: str, job_id: str) -> Dict[str, str]:
241
- job_dir = os.path.join(normalize_path(project_root), JOB_DIR_NAME, job_id)
242
- return {
243
- "job_dir": job_dir,
244
- "progress_json": os.path.join(job_dir, "progress.json"),
245
- "events_jsonl": os.path.join(job_dir, "events.jsonl"),
246
- }
247
-
248
-
249
- def _write_job_sidecars(conn: sqlite3.Connection, project_root: str, job_id: str) -> None:
250
- paths = _job_paths(project_root, job_id)
251
- os.makedirs(paths["job_dir"], exist_ok=True)
252
- status = batch_job_status(project_root, job_id)
253
- tmp_progress = f"{paths['progress_json']}.tmp"
254
- with open(tmp_progress, "w", encoding="utf-8") as handle:
255
- json.dump(status, handle, indent=2, ensure_ascii=False)
256
- handle.write("\n")
257
- os.replace(tmp_progress, paths["progress_json"])
258
-
259
- rows = conn.execute(
260
- """
261
- SELECT event_time, level, message, payload_json
262
- FROM job_events
263
- WHERE job_id = ?
264
- ORDER BY id
265
- """,
266
- (job_id,),
267
- ).fetchall()
268
- tmp_events = f"{paths['events_jsonl']}.tmp"
269
- with open(tmp_events, "w", encoding="utf-8") as handle:
270
- for row in rows:
271
- payload = {
272
- "time": row["event_time"],
273
- "level": row["level"],
274
- "message": row["message"],
275
- }
276
- if row["payload_json"]:
277
- payload["payload"] = _read_json(row["payload_json"])
278
- handle.write(json.dumps(payload, ensure_ascii=False, sort_keys=True))
279
- handle.write("\n")
280
- os.replace(tmp_events, paths["events_jsonl"])
281
-
282
-
283
- def _job_summary_from_row(row: sqlite3.Row) -> Dict[str, Any]:
284
- payload = _row_dict(row)
285
- total = int(payload.get("total_clips") or 0)
286
- done = int(payload.get("succeeded_clips") or 0) + int(payload.get("failed_clips") or 0) + int(payload.get("skipped_clips") or 0)
287
- payload["progress"] = {
288
- "done_clips": done,
289
- "total_clips": total,
290
- "percent": round((done / total) * 100, 2) if total else 0.0,
291
- }
292
- for json_key in ("target_json", "params_json"):
293
- target_key = json_key.replace("_json", "")
294
- payload[target_key] = _read_json(str(payload.pop(json_key, "{}")))
295
- payload.pop("plan_json", None)
296
- return payload
297
-
298
-
299
- def records_from_paths(paths: Iterable[Any], *, recursive: bool = True) -> Tuple[List[Dict[str, Any]], List[str]]:
300
- records: List[Dict[str, Any]] = []
301
- warnings: List[str] = []
302
- seen = set()
303
- for raw_path in paths:
304
- if raw_path in (None, ""):
305
- continue
306
- path = normalize_path(raw_path)
307
- candidates: List[str] = []
308
- if os.path.isdir(path):
309
- walker = os.walk(path)
310
- for dirpath, dirnames, filenames in walker:
311
- if not recursive:
312
- dirnames[:] = []
313
- for filename in sorted(filenames):
314
- candidate = os.path.join(dirpath, filename)
315
- if Path(candidate).suffix.lower() in MEDIA_EXTENSIONS:
316
- candidates.append(candidate)
317
- elif os.path.isfile(path):
318
- if Path(path).suffix.lower() in MEDIA_EXTENSIONS:
319
- candidates.append(path)
320
- else:
321
- warnings.append(f"Skipping unsupported file extension: {path}")
322
- else:
323
- warnings.append(f"Path not found: {path}")
324
- for candidate in sorted(candidates):
325
- normalized = normalize_path(candidate)
326
- if normalized in seen:
327
- continue
328
- seen.add(normalized)
329
- records.append(
330
- {
331
- "clip_id": None,
332
- "clip_name": os.path.basename(normalized),
333
- "bin_path": None,
334
- "file_path": normalized,
335
- "media_id": None,
336
- "duration": None,
337
- "fps": None,
338
- "resolution": None,
339
- "media_type": "file",
340
- }
341
- )
342
- return records, warnings
343
-
344
-
345
- def create_batch_job(
346
- *,
347
- project_name: Any,
348
- project_id: Any = None,
349
- records: List[Dict[str, Any]],
350
- target: Dict[str, Any],
351
- params: Optional[Dict[str, Any]] = None,
352
- capabilities: Optional[Dict[str, Any]] = None,
353
- name: Optional[str] = None,
354
- ) -> Dict[str, Any]:
355
- params = dict(params or {})
356
- params["dry_run"] = False
357
- params["session_only"] = False
358
- params["persist"] = True
359
- params.setdefault("cleanup_frames", True)
360
- params.setdefault("reuse_existing", True)
361
- params.setdefault("auto_build_index", True)
362
-
363
- plan = build_plan(
364
- project_name=project_name,
365
- project_id=project_id,
366
- records=records,
367
- target=target,
368
- params=params,
369
- capabilities=capabilities or detect_capabilities(),
370
- )
371
- if not plan.get("success"):
372
- return plan
373
- if plan.get("capability_gaps") and plan_requires_capabilities(plan):
374
- return {
375
- "success": False,
376
- "status": "missing_required_capabilities",
377
- "error": "Cannot create batch job because required local analysis tools are missing.",
378
- "plan": plan,
379
- "capability_gaps": plan.get("capability_gaps"),
380
- "install_guidance": plan.get("install_guidance"),
381
- "next_step": (
382
- "Install or configure the missing tools, then start the batch again. "
383
- "The MCP reports guidance only and does not install packages automatically."
384
- ),
385
- }
386
-
387
- project_root = plan["output_root"]["project_root"]
388
- os.makedirs(project_root, exist_ok=True)
389
- created_at = _utc_now()
390
- job_name = str(name or params.get("job_name") or params.get("jobName") or f"{target.get('type', 'analysis')} analysis")
391
- job_id = _job_id(job_name, target, f"{created_at}-{time.time_ns()}")
392
- conn = _connect_jobs(project_root)
393
- try:
394
- conn.execute(
395
- """
396
- INSERT INTO jobs (
397
- job_id, name, status, phase, project_name, project_id, project_root,
398
- target_json, params_json, plan_json, total_clips, pending_clips,
399
- created_at, updated_at
400
- ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
401
- """,
402
- (
403
- job_id,
404
- job_name,
405
- "queued",
406
- "created",
407
- str(project_name or "Project"),
408
- str(project_id) if project_id is not None else None,
409
- project_root,
410
- _stable_json(target),
411
- _stable_json(params),
412
- _stable_json(plan),
413
- len(plan.get("clips") or []),
414
- len(plan.get("clips") or []),
415
- created_at,
416
- created_at,
417
- ),
418
- )
419
- for position, clip_plan in enumerate(plan.get("clips") or []):
420
- record = clip_plan.get("record") or {}
421
- conn.execute(
422
- """
423
- INSERT INTO job_clips (
424
- job_id, position, clip_key, status, record_json, clip_plan_json,
425
- cache_status, updated_at
426
- ) VALUES (?, ?, ?, ?, ?, ?, ?, ?)
427
- """,
428
- (
429
- job_id,
430
- position,
431
- stable_clip_directory(record),
432
- "pending",
433
- _stable_json(record),
434
- _stable_json(clip_plan),
435
- clip_plan.get("cache_status"),
436
- created_at,
437
- ),
438
- )
439
- _event(conn, job_id, "info", "Batch job created", {"clip_count": len(plan.get("clips") or [])})
440
- conn.commit()
441
- _write_job_sidecars(conn, project_root, job_id)
442
- finally:
443
- conn.close()
444
-
445
- return {
446
- "success": True,
447
- "job": batch_job_status(project_root, job_id),
448
- "plan": {
449
- "depth": plan.get("depth"),
450
- "clip_count": plan.get("clip_count"),
451
- "estimated_seconds_after_reuse": plan.get("estimated_seconds_after_reuse"),
452
- "analysis_keyframe_budget_per_clip": plan.get("analysis_keyframe_budget_per_clip"),
453
- "output_root": plan.get("output_root"),
454
- "reusable_clip_count": plan.get("reusable_clip_count"),
455
- },
456
- }
457
-
458
-
459
- def create_batch_job_from_paths(
460
- *,
461
- project_name: Any,
462
- project_id: Any = None,
463
- paths: Iterable[Any],
464
- analysis_root: Any = None,
465
- recursive: bool = True,
466
- params: Optional[Dict[str, Any]] = None,
467
- name: Optional[str] = None,
468
- ) -> Dict[str, Any]:
469
- records, warnings = records_from_paths(paths, recursive=recursive)
470
- if not records:
471
- return {"success": False, "error": "No analyzable media files found", "warnings": warnings}
472
- params = dict(params or {})
473
- if analysis_root:
474
- params["analysis_root"] = analysis_root
475
- target = {
476
- "type": "paths",
477
- "paths": [record["file_path"] for record in records],
478
- "recursive": recursive,
479
- }
480
- result = create_batch_job(
481
- project_name=project_name,
482
- project_id=project_id,
483
- records=records,
484
- target=target,
485
- params=params,
486
- name=name,
487
- )
488
- if warnings:
489
- result.setdefault("warnings", warnings)
490
- return result
491
-
492
-
493
- def list_batch_jobs(project_root: str, *, limit: Any = 50) -> Dict[str, Any]:
494
- root = normalize_path(project_root)
495
- db_path, err = job_db_path(root)
496
- if err:
497
- return {"success": False, "error": err}
498
- if not db_path or not os.path.isfile(db_path):
499
- return {"success": True, "project_root": root, "jobs": [], "count": 0}
500
- try:
501
- max_jobs = max(1, min(int(limit), 200))
502
- except (TypeError, ValueError):
503
- max_jobs = 50
504
- conn = _connect_jobs(root)
505
- try:
506
- rows = conn.execute(
507
- """
508
- SELECT *
509
- FROM jobs
510
- ORDER BY created_at DESC
511
- LIMIT ?
512
- """,
513
- (max_jobs,),
514
- ).fetchall()
515
- finally:
516
- conn.close()
517
- jobs = [_job_summary_from_row(row) for row in rows]
518
- return {"success": True, "project_root": root, "jobs": jobs, "count": len(jobs)}
519
-
520
-
521
- def batch_job_status(project_root: str, job_id: str, *, include_clips: bool = True, include_events: bool = True) -> Dict[str, Any]:
522
- root = normalize_path(project_root)
523
- conn = _connect_jobs(root)
524
- try:
525
- job = conn.execute("SELECT * FROM jobs WHERE job_id = ?", (job_id,)).fetchone()
526
- if not job:
527
- return {"success": False, "error": f"Batch job not found: {job_id}"}
528
- _sync_job_counts(conn, job_id)
529
- conn.commit()
530
- job = conn.execute("SELECT * FROM jobs WHERE job_id = ?", (job_id,)).fetchone()
531
- payload = _job_summary_from_row(job)
532
- payload["success"] = True
533
- payload["paths"] = _job_paths(root, job_id)
534
- if include_clips:
535
- payload["clips"] = [
536
- _row_dict(row)
537
- for row in conn.execute(
538
- """
539
- SELECT position, clip_key, status, report_path, marker_plan_path,
540
- cache_status, error, attempts, started_at, completed_at
541
- FROM job_clips
542
- WHERE job_id = ?
543
- ORDER BY position
544
- """,
545
- (job_id,),
546
- ).fetchall()
547
- ]
548
- if include_events:
549
- payload["events"] = [
550
- {
551
- **{key: row[key] for key in ("event_time", "level", "message")},
552
- "payload": _read_json(row["payload_json"]) if row["payload_json"] else None,
553
- }
554
- for row in conn.execute(
555
- """
556
- SELECT event_time, level, message, payload_json
557
- FROM job_events
558
- WHERE job_id = ?
559
- ORDER BY id DESC
560
- LIMIT 50
561
- """,
562
- (job_id,),
563
- ).fetchall()
564
- ]
565
- finally:
566
- conn.close()
567
- return payload
568
-
569
-
570
- def _set_job_status(conn: sqlite3.Connection, job_id: str, status: str, phase: str, **extra: Any) -> None:
571
- fields = ["status = ?", "phase = ?", "updated_at = ?"]
572
- values: List[Any] = [status, phase, _utc_now()]
573
- for key, value in extra.items():
574
- fields.append(f"{key} = ?")
575
- values.append(value)
576
- values.append(job_id)
577
- conn.execute(f"UPDATE jobs SET {', '.join(fields)} WHERE job_id = ?", values)
578
-
579
-
580
- def cancel_batch_job(project_root: str, job_id: str) -> Dict[str, Any]:
581
- root = normalize_path(project_root)
582
- conn = _connect_jobs(root)
583
- try:
584
- if not conn.execute("SELECT 1 FROM jobs WHERE job_id = ?", (job_id,)).fetchone():
585
- return {"success": False, "error": f"Batch job not found: {job_id}"}
586
- conn.execute(
587
- "UPDATE job_clips SET status = 'pending', updated_at = ? WHERE job_id = ? AND status = 'running'",
588
- (_utc_now(), job_id),
589
- )
590
- _set_job_status(conn, job_id, "canceled", "canceled", canceled_at=_utc_now())
591
- _event(conn, job_id, "warning", "Batch job canceled")
592
- _sync_job_counts(conn, job_id)
593
- conn.commit()
594
- _write_job_sidecars(conn, root, job_id)
595
- finally:
596
- conn.close()
597
- return batch_job_status(root, job_id)
598
-
599
-
600
- def resume_batch_job(project_root: str, job_id: str) -> Dict[str, Any]:
601
- root = normalize_path(project_root)
602
- conn = _connect_jobs(root)
603
- try:
604
- if not conn.execute("SELECT 1 FROM jobs WHERE job_id = ?", (job_id,)).fetchone():
605
- return {"success": False, "error": f"Batch job not found: {job_id}"}
606
- conn.execute(
607
- "UPDATE job_clips SET status = 'pending', updated_at = ? WHERE job_id = ? AND status = 'running'",
608
- (_utc_now(), job_id),
609
- )
610
- _set_job_status(conn, job_id, "queued", "resumed", canceled_at=None)
611
- _event(conn, job_id, "info", "Batch job resumed")
612
- _sync_job_counts(conn, job_id)
613
- conn.commit()
614
- _write_job_sidecars(conn, root, job_id)
615
- finally:
616
- conn.close()
617
- return batch_job_status(root, job_id)
618
-
619
-
620
- def _auto_build_index(conn: sqlite3.Connection, root: str, job_id: str, message: str) -> Dict[str, Any]:
621
- index = build_analysis_index(root)
622
- _event(conn, job_id, "info" if index.get("success") else "error", message, index)
623
- conn.commit()
624
- return index
625
-
626
-
627
- def _finish_job_if_complete(
628
- conn: sqlite3.Connection,
629
- root: str,
630
- job_id: str,
631
- params: Dict[str, Any],
632
- *,
633
- index_already_refreshed: bool = False,
634
- ) -> None:
635
- counts = _sync_job_counts(conn, job_id)
636
- if counts["pending_clips"] or counts["running_clips"]:
637
- return
638
- status = "completed_with_errors" if counts["failed_clips"] else "completed"
639
- _set_job_status(conn, job_id, status, "complete", completed_at=_utc_now())
640
- _event(conn, job_id, "info", "Batch job completed", counts)
641
- conn.commit()
642
- summarize_reports(root)
643
- if params.get("auto_build_index", True) and not index_already_refreshed:
644
- _auto_build_index(conn, root, job_id, "Analysis index rebuilt")
645
-
646
-
647
- def run_batch_job_slice(
648
- project_root: str,
649
- job_id: str,
650
- *,
651
- max_clips: Any = 1,
652
- max_seconds: Optional[Any] = None,
653
- capabilities: Optional[Dict[str, Any]] = None,
654
- ) -> Dict[str, Any]:
655
- root = normalize_path(project_root)
656
- try:
657
- clip_limit = max(1, min(int(max_clips), 25))
658
- except (TypeError, ValueError):
659
- clip_limit = 1
660
- deadline = None
661
- if max_seconds not in (None, ""):
662
- try:
663
- deadline = time.monotonic() + max(1.0, float(max_seconds))
664
- except (TypeError, ValueError):
665
- deadline = None
666
-
667
- conn = _connect_jobs(root)
668
- processed: List[Dict[str, Any]] = []
669
- try:
670
- job = conn.execute("SELECT * FROM jobs WHERE job_id = ?", (job_id,)).fetchone()
671
- if not job:
672
- return {"success": False, "error": f"Batch job not found: {job_id}"}
673
- if job["status"] == "canceled":
674
- return {"success": False, "error": f"Batch job is canceled: {job_id}", "job": batch_job_status(root, job_id)}
675
- params = _read_json(job["params_json"])
676
- plan = _read_json(job["plan_json"])
677
- now = _utc_now()
678
- if job["started_at"] is None:
679
- conn.execute("UPDATE jobs SET started_at = ? WHERE job_id = ?", (now, job_id))
680
- _set_job_status(conn, job_id, "running", "analyzing")
681
- _event(conn, job_id, "info", "Running batch job slice", {"max_clips": clip_limit, "max_seconds": max_seconds})
682
- conn.commit()
683
-
684
- rows = conn.execute(
685
- """
686
- SELECT *
687
- FROM job_clips
688
- WHERE job_id = ? AND status = 'pending'
689
- ORDER BY position
690
- LIMIT ?
691
- """,
692
- (job_id, clip_limit),
693
- ).fetchall()
694
-
695
- for row in rows:
696
- if deadline is not None and time.monotonic() >= deadline:
697
- break
698
- clip_plan = _read_json(row["clip_plan_json"])
699
- mini_plan = copy.deepcopy(plan)
700
- mini_plan["clips"] = [clip_plan]
701
- mini_plan["clip_count"] = 1
702
- mini_plan["dry_run"] = False
703
- started_at = _utc_now()
704
- conn.execute(
705
- """
706
- UPDATE job_clips
707
- SET status = 'running', attempts = attempts + 1, started_at = ?,
708
- updated_at = ?, error = NULL
709
- WHERE id = ?
710
- """,
711
- (started_at, started_at, row["id"]),
712
- )
713
- _sync_job_counts(conn, job_id)
714
- conn.commit()
715
- try:
716
- slice_params = copy.deepcopy(params)
717
- slice_params["auto_build_index"] = False
718
- # C6 caps integration: thread the job_id so per-job usage
719
- # rollups populate (otherwise JOB scope stays at zero).
720
- slice_params["job_id"] = job_id
721
- manifest = execute_plan(mini_plan, params=slice_params, capabilities=capabilities or detect_capabilities())
722
- clip_result = (manifest.get("clips") or [{}])[0] if isinstance(manifest, dict) else {}
723
- completed_at = _utc_now()
724
- if manifest.get("success") and clip_result.get("success"):
725
- status = "skipped" if clip_result.get("reused") else "succeeded"
726
- conn.execute(
727
- """
728
- UPDATE job_clips
729
- SET status = ?, report_path = ?, marker_plan_path = ?,
730
- cache_status = ?, completed_at = ?, updated_at = ?
731
- WHERE id = ?
732
- """,
733
- (
734
- status,
735
- clip_result.get("analysis_json"),
736
- clip_result.get("marker_plan_json"),
737
- clip_result.get("cache_status"),
738
- completed_at,
739
- completed_at,
740
- row["id"],
741
- ),
742
- )
743
- processed.append({"position": row["position"], "status": status, "report_path": clip_result.get("analysis_json")})
744
- _event(conn, job_id, "info", f"Clip {row['position'] + 1} {status}", {"clip": clip_result.get("record")})
745
- else:
746
- error = clip_result.get("error") or manifest.get("error") or "Clip analysis failed"
747
- conn.execute(
748
- """
749
- UPDATE job_clips
750
- SET status = 'failed', error = ?, completed_at = ?, updated_at = ?
751
- WHERE id = ?
752
- """,
753
- (error, completed_at, completed_at, row["id"]),
754
- )
755
- processed.append({"position": row["position"], "status": "failed", "error": error})
756
- _event(conn, job_id, "error", f"Clip {row['position'] + 1} failed", {"error": error})
757
- except Exception as exc: # pragma: no cover - defensive for arbitrary media/tool failures
758
- completed_at = _utc_now()
759
- conn.execute(
760
- """
761
- UPDATE job_clips
762
- SET status = 'failed', error = ?, completed_at = ?, updated_at = ?
763
- WHERE id = ?
764
- """,
765
- (str(exc), completed_at, completed_at, row["id"]),
766
- )
767
- processed.append({"position": row["position"], "status": "failed", "error": str(exc)})
768
- _event(conn, job_id, "error", f"Clip {row['position'] + 1} raised an exception", {"error": str(exc)})
769
- _sync_job_counts(conn, job_id)
770
- conn.commit()
771
-
772
- index_refreshed = False
773
- if params.get("auto_build_index", True) and any(
774
- row.get("status") in {"succeeded", "skipped"} for row in processed
775
- ):
776
- _auto_build_index(conn, root, job_id, "Analysis index refreshed after batch slice")
777
- index_refreshed = True
778
-
779
- _finish_job_if_complete(conn, root, job_id, params, index_already_refreshed=index_refreshed)
780
- _sync_job_counts(conn, job_id)
781
- conn.commit()
782
- current = conn.execute("SELECT status FROM jobs WHERE job_id = ?", (job_id,)).fetchone()
783
- if current and current["status"] == "running":
784
- _set_job_status(conn, job_id, "queued", "waiting_for_next_slice")
785
- conn.commit()
786
- _write_job_sidecars(conn, root, job_id)
787
- finally:
788
- conn.close()
789
-
790
- return {
791
- "success": True,
792
- "processed_count": len(processed),
793
- "processed": processed,
794
- "job": batch_job_status(root, job_id),
795
- }
796
-
797
-
798
- def project_root_for_dashboard(project_name: Any, project_id: Any = None, analysis_root: Any = None, source_paths: Optional[Iterable[Any]] = None) -> Dict[str, Any]:
799
- return resolve_output_root(
800
- project_name=project_name,
801
- project_id=project_id,
802
- analysis_root=analysis_root,
803
- source_paths=source_paths or [],
804
- create=True,
805
- )