@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,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
- )