bosun 0.42.5 → 0.43.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 (473) hide show
  1. package/.env.example +36 -51
  2. package/README.md +19 -3
  3. package/agent/agent-custom-tools.mjs +138 -26
  4. package/agent/agent-endpoint.mjs +1 -2
  5. package/agent/agent-event-bus.mjs +33 -2
  6. package/agent/agent-hooks.mjs +1 -1
  7. package/agent/agent-launcher.mjs +6210 -0
  8. package/agent/agent-pool.mjs +7 -4018
  9. package/agent/agent-prompt-catalog.mjs +5 -6
  10. package/agent/agent-prompts.mjs +62 -6
  11. package/agent/agent-sdk.mjs +130 -0
  12. package/agent/agent-supervisor.mjs +30 -6
  13. package/agent/auth/_shared.mjs +129 -0
  14. package/agent/auth/anthropic-api-key.mjs +13 -0
  15. package/agent/auth/azure-openai.mjs +17 -0
  16. package/agent/auth/cerebras.mjs +14 -0
  17. package/agent/auth/chatgpt-codex-subscription.mjs +15 -0
  18. package/agent/auth/claude-subscription.mjs +15 -0
  19. package/agent/auth/copilot-oauth.mjs +13 -0
  20. package/agent/auth/deepinfra.mjs +14 -0
  21. package/agent/auth/fireworks.mjs +14 -0
  22. package/agent/auth/gemini-api-key.mjs +14 -0
  23. package/agent/auth/groq.mjs +14 -0
  24. package/agent/auth/index.mjs +85 -0
  25. package/agent/auth/nebius.mjs +14 -0
  26. package/agent/auth/ollama.mjs +14 -0
  27. package/agent/auth/openai-api-key.mjs +13 -0
  28. package/agent/auth/openai-compatible.mjs +15 -0
  29. package/agent/auth/openrouter.mjs +14 -0
  30. package/agent/auth/perplexity.mjs +14 -0
  31. package/agent/auth/sambanova.mjs +14 -0
  32. package/agent/auth/together.mjs +14 -0
  33. package/agent/auth/xai.mjs +14 -0
  34. package/agent/autofix-prompts.mjs +2 -2
  35. package/agent/autofix.mjs +2 -2
  36. package/agent/bosun-skills.mjs +215 -86
  37. package/agent/fleet-coordinator.mjs +161 -32
  38. package/agent/harness/agent-loop.mjs +26 -0
  39. package/agent/harness/event-contract.mjs +125 -0
  40. package/agent/harness/followup-queue.mjs +33 -0
  41. package/agent/harness/message-normalizer.mjs +43 -0
  42. package/agent/harness/module-boundaries.md +73 -0
  43. package/agent/harness/run-contract.mjs +122 -0
  44. package/agent/harness/runtime-config.mjs +132 -0
  45. package/agent/harness/session-state.mjs +80 -0
  46. package/agent/harness/steering-queue.mjs +35 -0
  47. package/agent/harness/tool-runner.mjs +95 -0
  48. package/agent/harness/turn-runner.mjs +135 -0
  49. package/agent/harness-agent-service.mjs +852 -0
  50. package/agent/harness-executor-config.mjs +384 -0
  51. package/agent/hook-library.mjs +141 -2
  52. package/agent/hook-profiles.mjs +15 -2
  53. package/agent/internal-harness-control-plane.mjs +672 -0
  54. package/agent/internal-harness-profile.mjs +519 -0
  55. package/agent/internal-harness-runtime.mjs +1219 -0
  56. package/agent/lineage-graph.mjs +141 -0
  57. package/agent/primary-agent.mjs +602 -706
  58. package/agent/provider-auth-manager.mjs +830 -0
  59. package/agent/provider-auth-state.mjs +440 -0
  60. package/agent/provider-capabilities.mjs +116 -0
  61. package/agent/provider-kernel.mjs +596 -0
  62. package/agent/provider-message-transform.mjs +583 -0
  63. package/agent/provider-model-catalog.mjs +163 -0
  64. package/agent/provider-registry.mjs +657 -0
  65. package/agent/provider-runtime-discovery.mjs +147 -0
  66. package/agent/provider-session.mjs +767 -0
  67. package/agent/providers/_shared.mjs +397 -0
  68. package/agent/providers/anthropic-messages.mjs +64 -0
  69. package/agent/providers/azure-openai-responses.mjs +69 -0
  70. package/agent/providers/cerebras.mjs +66 -0
  71. package/agent/providers/claude-subscription-shim.mjs +68 -0
  72. package/agent/providers/copilot-oauth.mjs +66 -0
  73. package/agent/providers/deepinfra.mjs +66 -0
  74. package/agent/providers/fireworks.mjs +66 -0
  75. package/agent/providers/gemini-generate-content.mjs +66 -0
  76. package/agent/providers/groq.mjs +66 -0
  77. package/agent/providers/index.mjs +208 -0
  78. package/agent/providers/nebius.mjs +66 -0
  79. package/agent/providers/ollama.mjs +66 -0
  80. package/agent/providers/openai-codex-subscription.mjs +75 -0
  81. package/agent/providers/openai-compatible.mjs +65 -0
  82. package/agent/providers/openai-responses.mjs +67 -0
  83. package/agent/providers/openrouter.mjs +66 -0
  84. package/agent/providers/perplexity.mjs +66 -0
  85. package/agent/providers/provider-contract.mjs +138 -0
  86. package/agent/providers/provider-errors.mjs +63 -0
  87. package/agent/providers/provider-model-pricing.mjs +246 -0
  88. package/agent/providers/provider-stream-normalizer.mjs +7 -0
  89. package/agent/providers/provider-usage-normalizer.mjs +48 -0
  90. package/agent/providers/sambanova.mjs +66 -0
  91. package/agent/providers/together.mjs +66 -0
  92. package/agent/providers/xai.mjs +66 -0
  93. package/agent/query-engine.mjs +260 -0
  94. package/agent/retry-queue.mjs +1 -0
  95. package/agent/review-agent.mjs +1 -1
  96. package/agent/session-contract.mjs +127 -0
  97. package/agent/session-manager.mjs +1859 -0
  98. package/agent/session-replay.mjs +617 -0
  99. package/agent/session-snapshot-store.mjs +379 -0
  100. package/agent/skills/agent-coordination.md +6 -0
  101. package/agent/skills/background-task-execution.md +6 -0
  102. package/agent/skills/bosun-agent-api.md +6 -0
  103. package/agent/skills/code-quality-anti-patterns.md +7 -0
  104. package/agent/skills/commit-conventions.md +6 -0
  105. package/agent/skills/custom-tool-creation.md +6 -0
  106. package/agent/skills/error-recovery.md +6 -0
  107. package/agent/skills/pr-workflow.md +6 -0
  108. package/agent/skills/tdd-pattern.md +6 -0
  109. package/agent/subagent-contract.mjs +104 -0
  110. package/agent/subagent-control.mjs +633 -0
  111. package/agent/subagent-pool.mjs +260 -0
  112. package/agent/thread-contract.mjs +88 -0
  113. package/agent/thread-registry.mjs +552 -0
  114. package/agent/tool-approval-manager.mjs +259 -0
  115. package/agent/tool-builtin-catalog.mjs +855 -0
  116. package/agent/tool-contract.mjs +101 -0
  117. package/agent/tool-event-contract.mjs +99 -0
  118. package/agent/tool-execution-ledger.mjs +32 -0
  119. package/agent/tool-network-policy.mjs +86 -0
  120. package/agent/tool-orchestrator.mjs +382 -0
  121. package/agent/tool-output-truncation.mjs +70 -0
  122. package/agent/tool-registry.mjs +200 -0
  123. package/agent/tool-retry-policy.mjs +57 -0
  124. package/agent/tool-runtime-context.mjs +220 -0
  125. package/bench/harness-load-bench.mjs +281 -0
  126. package/bench/harness-parity-bench.mjs +214 -0
  127. package/bench/swebench/bosun-swebench.mjs +21 -6
  128. package/bosun-tui.mjs +59 -13
  129. package/bosun.config.example.json +55 -2
  130. package/bosun.schema.json +598 -5
  131. package/cli.mjs +656 -160
  132. package/config/config-doctor.mjs +80 -26
  133. package/config/config-editor.mjs +417 -0
  134. package/config/config.mjs +489 -144
  135. package/config/repo-config.mjs +125 -49
  136. package/config/repo-root.mjs +33 -1
  137. package/desktop/main.mjs +554 -115
  138. package/desktop/package.json +1 -1
  139. package/git/diff-stats.mjs +7 -5
  140. package/git/git-editor-fix.mjs +2 -42
  141. package/github/github-app-auth.mjs +6 -0
  142. package/github/github-oauth-portal.mjs +20 -0
  143. package/infra/anomaly-detector.mjs +122 -22
  144. package/infra/approval-projection-store.mjs +75 -0
  145. package/infra/config-reload-bus.mjs +33 -0
  146. package/infra/container-runner.mjs +37 -4
  147. package/infra/error-detector.mjs +110 -35
  148. package/infra/event-schema.mjs +353 -0
  149. package/infra/guardrails.mjs +383 -0
  150. package/infra/heartbeat-monitor.mjs +432 -0
  151. package/infra/library-manager.mjs +367 -19
  152. package/infra/live-event-projector.mjs +197 -0
  153. package/infra/maintenance.mjs +202 -51
  154. package/infra/monitor.mjs +1749 -2027
  155. package/infra/preflight.mjs +107 -6
  156. package/infra/presence.mjs +33 -9
  157. package/infra/projection-contract.mjs +27 -0
  158. package/infra/provider-usage-ledger.mjs +73 -0
  159. package/infra/replay-reader.mjs +140 -0
  160. package/infra/runtime-accumulator.mjs +303 -8
  161. package/infra/runtime-metrics.mjs +156 -0
  162. package/infra/session-projection-store.mjs +169 -0
  163. package/infra/session-telemetry-runtime.mjs +580 -0
  164. package/infra/session-telemetry.mjs +338 -0
  165. package/infra/session-tracker.mjs +1613 -228
  166. package/infra/startup-service.mjs +0 -2
  167. package/infra/storage-janitor.mjs +1046 -0
  168. package/infra/subagent-projection-store.mjs +89 -0
  169. package/infra/test-runtime.mjs +53 -20
  170. package/infra/trace-export.mjs +103 -0
  171. package/infra/tui-bridge.mjs +607 -5
  172. package/infra/update-check.mjs +7 -8
  173. package/infra/windows-hidden-child-processes.mjs +99 -0
  174. package/infra/worktree-recovery-state.mjs +20 -7
  175. package/kanban/kanban-adapter.mjs +702 -310
  176. package/kanban/repo-mirror-projection-store.mjs +871 -0
  177. package/lib/agent-configuration-guide.mjs +280 -0
  178. package/lib/hot-path-runtime.mjs +1061 -0
  179. package/lib/integrations-registry.mjs +294 -0
  180. package/lib/log-tail.mjs +101 -0
  181. package/lib/logger.mjs +21 -25
  182. package/lib/mojibake-repair.mjs +40 -0
  183. package/lib/repo-map.mjs +137 -24
  184. package/lib/request-json-api.mjs +59 -0
  185. package/lib/safe-box.mjs +56 -0
  186. package/lib/session-insights.mjs +3 -1
  187. package/lib/skill-markdown-safety.mjs +394 -0
  188. package/lib/state-ledger-sqlite.mjs +4462 -0
  189. package/lib/vault-keychain.mjs +259 -0
  190. package/lib/vault.mjs +374 -0
  191. package/lib/workflow-flowchart-utils.mjs +326 -0
  192. package/monitor-tail-sanitizer.mjs +1 -2
  193. package/native/bosun-telemetry/Cargo.toml +9 -0
  194. package/native/bosun-telemetry/src/export.rs +151 -0
  195. package/native/bosun-telemetry/src/main.rs +76 -0
  196. package/native/bosun-telemetry/src/metrics.rs +114 -0
  197. package/native/bosun-telemetry/src/session_telemetry.rs +178 -0
  198. package/native/bosun-unified-exec/Cargo.lock +107 -0
  199. package/native/bosun-unified-exec/Cargo.toml +8 -0
  200. package/native/bosun-unified-exec/src/async_watcher.rs +145 -0
  201. package/native/bosun-unified-exec/src/head_tail_buffer.rs +241 -0
  202. package/native/bosun-unified-exec/src/main.rs +86 -0
  203. package/native/bosun-unified-exec/src/process_manager.rs +308 -0
  204. package/native/bosun-unified-exec/src/tool_orchestrator.rs +187 -0
  205. package/package.json +230 -59
  206. package/postinstall.mjs +182 -13
  207. package/server/bosun-mcp-server.mjs +348 -10
  208. package/server/routes/harness-agent-bridge.mjs +128 -0
  209. package/server/routes/harness-approvals.mjs +290 -0
  210. package/server/routes/harness-events.mjs +469 -0
  211. package/server/routes/harness-providers.mjs +385 -0
  212. package/server/routes/harness-sessions.mjs +2230 -0
  213. package/server/routes/harness-subagents.mjs +138 -0
  214. package/server/routes/harness-surface-payload.mjs +74 -0
  215. package/server/setup-web-server.mjs +468 -39
  216. package/server/ui-server.mjs +13041 -4418
  217. package/setup.mjs +206 -298
  218. package/shared-workspaces.json +1 -1
  219. package/shell/anthropic-native-adapter.mjs +1218 -0
  220. package/shell/auth-resolver.mjs +247 -0
  221. package/shell/claude-shell.mjs +85 -2
  222. package/shell/codex-config-file.mjs +9 -0
  223. package/shell/codex-config.mjs +192 -249
  224. package/shell/codex-model-profiles.mjs +76 -12
  225. package/shell/codex-sdk-import.mjs +7 -0
  226. package/shell/codex-shell.mjs +708 -170
  227. package/shell/context-compaction.mjs +898 -0
  228. package/shell/copilot-shell.mjs +359 -109
  229. package/shell/gemini-native-adapter.mjs +411 -0
  230. package/shell/gemini-shell.mjs +121 -13
  231. package/shell/mcp-client.mjs +401 -0
  232. package/shell/mcp-registry.mjs +72 -0
  233. package/shell/message-pruner.mjs +248 -0
  234. package/shell/openai-native-adapter.mjs +1975 -0
  235. package/shell/opencode-providers.mjs +16 -531
  236. package/shell/opencode-shell.mjs +180 -9
  237. package/shell/provider-transform.mjs +386 -0
  238. package/shell/pwsh-runtime.mjs +9 -2
  239. package/shell/retry-fetch.mjs +244 -0
  240. package/shell/session-resume.mjs +97 -0
  241. package/shell/session-store.mjs +215 -0
  242. package/shell/shell-adapter-registry.mjs +346 -0
  243. package/shell/shell-session-compat.mjs +442 -0
  244. package/shell/smooth-stream.mjs +233 -0
  245. package/shell/stop-condition.mjs +238 -0
  246. package/shell/tool-call-repair.mjs +345 -0
  247. package/shell/tool-executor.mjs +571 -0
  248. package/task/pipeline.mjs +3 -1
  249. package/task/task-assessment.mjs +312 -6
  250. package/task/task-claims.mjs +312 -48
  251. package/task/task-cli.mjs +90 -14
  252. package/task/task-complexity.mjs +6 -6
  253. package/task/task-context.mjs +37 -0
  254. package/task/task-debt-ledger.mjs +110 -0
  255. package/task/task-executor.mjs +1104 -119
  256. package/task/task-replanner.mjs +553 -0
  257. package/task/task-simulate-cli.mjs +1481 -0
  258. package/task/task-store.mjs +996 -68
  259. package/telegram/executor-health-region-cache.mjs +75 -0
  260. package/telegram/get-telegram-chat-id.mjs +0 -0
  261. package/telegram/harness-api-client.mjs +124 -0
  262. package/telegram/sticky-menu-state.mjs +384 -0
  263. package/telegram/telegram-bot.mjs +548 -865
  264. package/telegram/telegram-sentinel.mjs +25 -14
  265. package/telegram/telegram-surface-runtime.mjs +53 -0
  266. package/tools/generate-demo-defaults.mjs +23 -4
  267. package/tools/harness-hotpath-bench.mjs +246 -0
  268. package/tools/import-check.mjs +279 -234
  269. package/tools/install-git-hooks.mjs +96 -20
  270. package/tools/native-rust.mjs +124 -0
  271. package/tools/packed-cli-smoke.mjs +147 -0
  272. package/tools/prepublish-check.mjs +53 -1
  273. package/tools/run-workflow-guaranteed-suite.mjs +56 -0
  274. package/tools/site-serve.mjs +112 -0
  275. package/tools/sync-demo-ui.mjs +188 -0
  276. package/tools/syntax-check.mjs +32 -28
  277. package/tools/test-kanban-enhancement.mjs +7 -7
  278. package/tools/test-shared-state-integration.mjs +5 -19
  279. package/tools/vite-windows-realpath-shim.mjs +274 -0
  280. package/tools/vitest-esbuild-shim.mjs +45 -0
  281. package/tools/vitest-full-suite.mjs +310 -0
  282. package/tools/vitest-runner.mjs +505 -11
  283. package/tools/workflow-orphan-worktree-recovery.mjs +24 -7
  284. package/tui/CommandPalette.js +87 -0
  285. package/tui/app.mjs +463 -37
  286. package/tui/components/status-header.mjs +43 -1
  287. package/tui/lib/command-palette.mjs +191 -0
  288. package/tui/lib/connection-target.mjs +577 -0
  289. package/tui/lib/header-config.mjs +0 -2
  290. package/tui/lib/navigation.mjs +8 -3
  291. package/tui/lib/ws-bridge.mjs +141 -51
  292. package/tui/screens/agents-screen-helpers.mjs +87 -3
  293. package/tui/screens/agents.mjs +1074 -202
  294. package/tui/screens/connection-setup.mjs +363 -0
  295. package/tui/screens/harness-approvals.mjs +7 -0
  296. package/tui/screens/harness-sessions.mjs +109 -0
  297. package/tui/screens/harness-subagents.mjs +18 -0
  298. package/tui/screens/harness-telemetry.mjs +67 -0
  299. package/tui/screens/logs.mjs +325 -0
  300. package/tui/screens/settings-screen-helpers.mjs +75 -0
  301. package/tui/screens/settings.mjs +397 -0
  302. package/tui/screens/status.mjs +130 -5
  303. package/tui/screens/telemetry-screen-helpers.mjs +158 -0
  304. package/tui/screens/telemetry.mjs +246 -0
  305. package/tui/screens/workflows.mjs +984 -0
  306. package/ui/app.js +746 -189
  307. package/ui/app.monolith.js +2 -3
  308. package/ui/assets/toastui-editor-all.min.js +24 -0
  309. package/ui/components/agent-selector.js +706 -49
  310. package/ui/components/charts.js +16 -12
  311. package/ui/components/chat-view.js +536 -35
  312. package/ui/components/commit-graph.js +648 -0
  313. package/ui/components/context-menu.js +89 -0
  314. package/ui/components/diff-viewer.js +169 -53
  315. package/ui/components/forms.js +13 -2
  316. package/ui/components/kanban-board.js +541 -92
  317. package/ui/components/session-list.js +303 -66
  318. package/ui/components/shared.js +9 -1
  319. package/ui/components/task-markdown.js +272 -0
  320. package/ui/components/workspace-executor-settings.js +142 -0
  321. package/ui/components/workspace-switcher.js +35 -79
  322. package/ui/demo-defaults.js +17278 -7297
  323. package/ui/demo.html +7058 -5338
  324. package/ui/index.html +189 -112
  325. package/ui/modules/agent-events.js +309 -36
  326. package/ui/modules/api.js +236 -13
  327. package/ui/modules/chat-turn-groups.js +101 -0
  328. package/ui/modules/harness-client.js +56 -0
  329. package/ui/modules/icon-utils.js +9 -1
  330. package/ui/modules/icons.js +26 -2
  331. package/ui/modules/repo-area-contention.js +97 -0
  332. package/ui/modules/router.js +2 -0
  333. package/ui/modules/session-api.js +158 -14
  334. package/ui/modules/session-insights-worker.js +28 -0
  335. package/ui/modules/session-insights.js +173 -4
  336. package/ui/modules/session-surface.js +221 -0
  337. package/ui/modules/settings-schema.js +148 -27
  338. package/ui/modules/state.js +122 -13
  339. package/ui/modules/streaming.js +196 -60
  340. package/ui/modules/structured-values.js +47 -0
  341. package/ui/modules/task-hierarchy.js +374 -0
  342. package/ui/modules/worktree-recovery.js +10 -1
  343. package/ui/setup.html +3327 -2538
  344. package/ui/styles/components.css +983 -99
  345. package/ui/styles/kanban.css +229 -0
  346. package/ui/styles/layout.css +578 -106
  347. package/ui/styles/toastui-editor-dark.css +1 -0
  348. package/ui/styles/toastui-editor-viewer.css +6 -0
  349. package/ui/styles/toastui-editor.css +6 -0
  350. package/ui/styles/variables.css +14 -2
  351. package/ui/styles/workspace-switcher.css +22 -0
  352. package/ui/styles.css +20 -5
  353. package/ui/tabs/agents.js +1222 -86
  354. package/ui/tabs/chat.js +588 -146
  355. package/ui/tabs/context-compression-lab.js +962 -0
  356. package/ui/tabs/control.js +347 -61
  357. package/ui/tabs/dashboard.js +372 -105
  358. package/ui/tabs/guardrails.js +1140 -0
  359. package/ui/tabs/infra.js +91 -12
  360. package/ui/tabs/integrations.js +388 -0
  361. package/ui/tabs/library.js +161 -21
  362. package/ui/tabs/logs.js +410 -52
  363. package/ui/tabs/manual-flows.js +268 -52
  364. package/ui/tabs/settings.js +2117 -105
  365. package/ui/tabs/tasks.js +2851 -303
  366. package/ui/tabs/telemetry.js +246 -8
  367. package/ui/tabs/workflow-canvas-utils.mjs +172 -15
  368. package/ui/tabs/workflows.js +2632 -348
  369. package/ui/tui/App.js +127 -119
  370. package/ui/tui/HelpScreen.js +201 -0
  371. package/ui/tui/SettingsScreen.js +388 -0
  372. package/ui/tui/TasksScreen.js +30 -7
  373. package/ui/tui/TelemetryScreen.js +155 -0
  374. package/ui/tui/WorkflowsScreen.js +350 -0
  375. package/ui/tui/config-events.js +13 -0
  376. package/ui/tui/constants.js +1 -1
  377. package/ui/tui/logs-screen-helpers.js +292 -0
  378. package/ui/tui/tasks-screen-helpers.js +52 -0
  379. package/ui/tui/telemetry-helpers.js +158 -0
  380. package/ui/tui/useWorkflows.js +126 -6
  381. package/ui/tui/workflows-screen-helpers.js +220 -0
  382. package/ui/vendor/preact-jsx-runtime.js +5 -0
  383. package/utils.mjs +2 -2
  384. package/voice/vision-session-state.mjs +257 -0
  385. package/voice/voice-action-dispatcher.mjs +57 -12
  386. package/voice/voice-agents-sdk.mjs +1 -1
  387. package/voice/voice-auth-manager.mjs +102 -123
  388. package/voice/voice-tool-definitions.mjs +7 -7
  389. package/voice/voice-tools.mjs +837 -101
  390. package/workflow/action-approval.mjs +415 -0
  391. package/workflow/approval-queue.mjs +1254 -0
  392. package/workflow/credential-store.mjs +553 -0
  393. package/workflow/cron-scheduler.mjs +512 -0
  394. package/workflow/declarative-workflows.mjs +21 -2
  395. package/workflow/delegation-runtime.mjs +557 -0
  396. package/workflow/execution-ledger.mjs +1317 -33
  397. package/workflow/harness-approval-node.mjs +237 -0
  398. package/workflow/harness-output-contract.mjs +86 -0
  399. package/workflow/harness-session-node.mjs +160 -0
  400. package/workflow/harness-subagent-node.mjs +483 -0
  401. package/workflow/harness-tool-node.mjs +176 -0
  402. package/workflow/heavy-runner-pool.mjs +546 -0
  403. package/workflow/manual-flows.mjs +969 -28
  404. package/workflow/mcp-discovery-proxy.mjs +363 -143
  405. package/workflow/mcp-registry.mjs +507 -65
  406. package/workflow/meeting-workflow-service.mjs +24 -12
  407. package/workflow/pipeline-workflows.mjs +44 -2
  408. package/workflow/pipeline.mjs +72 -28
  409. package/workflow/project-detection.mjs +31 -6
  410. package/workflow/research-evidence-sidecar.mjs +1246 -0
  411. package/workflow/run-evaluator.mjs +2155 -0
  412. package/workflow/workflow-cli.mjs +229 -2
  413. package/workflow/workflow-contract.mjs +130 -2
  414. package/workflow/workflow-engine.mjs +5636 -331
  415. package/workflow/workflow-migration.mjs +0 -1
  416. package/workflow/workflow-nodes/actions.mjs +15526 -0
  417. package/workflow/workflow-nodes/agent.mjs +1863 -0
  418. package/workflow/workflow-nodes/conditions.mjs +307 -0
  419. package/workflow/workflow-nodes/definitions.mjs +210 -29
  420. package/workflow/workflow-nodes/flow.mjs +749 -0
  421. package/workflow/workflow-nodes/loop.mjs +449 -0
  422. package/workflow/workflow-nodes/meetings.mjs +456 -0
  423. package/workflow/workflow-nodes/notifications.mjs +169 -0
  424. package/workflow/workflow-nodes/transforms.mjs +83 -30
  425. package/workflow/workflow-nodes/triggers.mjs +1405 -0
  426. package/workflow/workflow-nodes/validation.mjs +722 -0
  427. package/workflow/workflow-nodes.mjs +43 -14965
  428. package/workflow/workflow-serializer.mjs +294 -0
  429. package/workflow/workflow-templates.mjs +304 -9
  430. package/workflow-templates/_helpers.mjs +1 -3
  431. package/workflow-templates/agents.mjs +235 -27
  432. package/workflow-templates/bosun-native.mjs +3 -1
  433. package/workflow-templates/code-quality.mjs +1 -1
  434. package/workflow-templates/continuation-loop.mjs +21 -5
  435. package/workflow-templates/coverage.mjs +6 -2
  436. package/workflow-templates/github.mjs +2565 -177
  437. package/workflow-templates/reliability.mjs +463 -35
  438. package/workflow-templates/research-evidence.mjs +389 -0
  439. package/workflow-templates/security.mjs +75 -62
  440. package/workflow-templates/sub-workflows.mjs +11 -3
  441. package/workflow-templates/task-batch.mjs +102 -20
  442. package/workflow-templates/task-lifecycle.mjs +333 -298
  443. package/workspace/command-diagnostics.mjs +111 -0
  444. package/workspace/context-cache.mjs +1308 -124
  445. package/workspace/context-indexer.mjs +915 -9
  446. package/workspace/context-injector.mjs +144 -0
  447. package/workspace/execution-journal.mjs +255 -0
  448. package/workspace/scope-locks.mjs +481 -0
  449. package/workspace/shared-knowledge.mjs +496 -91
  450. package/workspace/shared-state-manager.mjs +344 -1
  451. package/workspace/shared-workspace-cli.mjs +0 -0
  452. package/workspace/shared-workspace-registry.mjs +2 -2
  453. package/workspace/skillbook-store.mjs +681 -0
  454. package/workspace/workspace-manager.mjs +14 -8
  455. package/workspace/workspace-monitor.mjs +4 -4
  456. package/workspace/worktree-manager.mjs +146 -55
  457. package/workspace/worktree-setup.mjs +822 -0
  458. package/agent/rotate-agent-logs.sh +0 -134
  459. package/git/sdk-conflict-resolver.mjs +0 -971
  460. package/infra/sync-engine.mjs +0 -1160
  461. package/kanban/ve-kanban.mjs +0 -664
  462. package/kanban/ve-kanban.ps1 +0 -1365
  463. package/kanban/ve-kanban.sh +0 -18
  464. package/kanban/ve-orchestrator.mjs +0 -340
  465. package/kanban/ve-orchestrator.ps1 +0 -6762
  466. package/kanban/ve-orchestrator.sh +0 -18
  467. package/kanban/vibe-kanban-wrapper.mjs +0 -41
  468. package/kanban/vk-error-resolver.mjs +0 -474
  469. package/kanban/vk-log-stream.mjs +0 -932
  470. package/task/task-archiver.mjs +0 -813
  471. package/tools/publish.mjs +0 -239
  472. package/ui/components/chat-view.js.bak +0 -1
  473. package/ui/tabs/infra.js.bak +0 -1
@@ -1,234 +1,279 @@
1
- /**
2
- * import-check.mjs — ESM named-export validation gate.
3
- *
4
- * Uses vm.SourceTextModule.link() to validate that every named import
5
- * from a local module actually exists as an export in the target module.
6
- * This catches the class of errors where a named import is added but the
7
- * corresponding export doesn't exist (e.g., partial merges, abandoned WIP,
8
- * renames that missed a call-site).
9
- *
10
- * External dependencies (node: builtins, npm packages) are dynamically
11
- * imported and mirrored as SyntheticModules so that local module linking
12
- * succeeds without requiring the full dependency graph.
13
- */
14
-
15
- import vm from "node:vm";
16
- import { readFileSync, existsSync } from "node:fs";
17
- import { resolve, dirname, relative, extname } from "node:path";
18
- import { execSync } from "node:child_process";
19
- import { pathToFileURL, fileURLToPath } from "node:url";
20
-
21
- const JS_EXTENSIONS = new Set([".mjs", ".js", ".cjs"]);
22
-
23
- /**
24
- * Discover all .mjs source modules via git, excluding test/bench/site/desktop.
25
- */
26
- function discoverModules(rootDir) {
27
- try {
28
- const output = execSync("git ls-files --cached", {
29
- encoding: "utf8",
30
- cwd: rootDir,
31
- stdio: ["pipe", "pipe", "pipe"],
32
- });
33
- return output
34
- .split("\n")
35
- .map((f) => f.trim())
36
- .filter(
37
- (f) =>
38
- f.endsWith(".mjs") &&
39
- !f.startsWith("tests/") &&
40
- !f.startsWith("bench/") &&
41
- !f.startsWith("site/") &&
42
- !f.startsWith("desktop/") &&
43
- !f.startsWith("tools/"),
44
- )
45
- .sort((a, b) => a.localeCompare(b));
46
- } catch {
47
- return [];
48
- }
49
- }
50
-
51
- /**
52
- * Validate all ESM named imports by linking modules with vm.SourceTextModule.
53
- *
54
- * @param {object} [opts]
55
- * @param {string} [opts.rootDir] — project root (default: cwd)
56
- * @param {string[]} [opts.files] — explicit list of relative .mjs paths to check
57
- * @returns {{ errors: Array<{file: string, error: string}>, moduleCount: number }}
58
- */
59
- export async function validateImports({ rootDir, files } = {}) {
60
- rootDir = rootDir ?? process.cwd();
61
-
62
- if (typeof vm.SourceTextModule !== "function") {
63
- throw new Error(
64
- "vm.SourceTextModule is unavailable. Run with --experimental-vm-modules.",
65
- );
66
- }
67
-
68
- const context = vm.createContext({});
69
- const moduleCache = new Map(); // absolute path SourceTextModule | SyntheticModule
70
- const externalCache = new Map(); // specifier → SyntheticModule
71
- const errors = [];
72
-
73
- const moduleFiles = files ?? discoverModules(rootDir);
74
-
75
- // Phase 1: Parse all source modules into SourceTextModules.
76
- for (const file of moduleFiles) {
77
- const absPath = resolve(rootDir, file);
78
- if (!existsSync(absPath)) continue;
79
- try {
80
- const source = readFileSync(absPath, "utf8");
81
- const mod = new vm.SourceTextModule(source, {
82
- identifier: absPath,
83
- context,
84
- });
85
- moduleCache.set(absPath, mod);
86
- } catch {
87
- // Syntax errors are caught by syntax-check.mjs — skip silently.
88
- }
89
- }
90
-
91
- /**
92
- * Create a SyntheticModule stub for an external dependency.
93
- * Dynamically imports the real module to mirror its export names.
94
- */
95
- async function stubExternal(specifier) {
96
- if (externalCache.has(specifier)) return externalCache.get(specifier);
97
-
98
- let exportNames = ["default"];
99
- try {
100
- const real = await import(specifier);
101
- exportNames = Object.keys(real);
102
- if (!exportNames.includes("default")) exportNames.push("default");
103
- } catch {
104
- // Cannot import (optional dep, missing, etc.) — stub with default only.
105
- }
106
-
107
- const synth = new vm.SyntheticModule(
108
- exportNames,
109
- function () {
110
- for (const name of exportNames) this.setExport(name, undefined);
111
- },
112
- { identifier: `external:${specifier}`, context },
113
- );
114
- // SyntheticModules have no dependencies, so linker is never called.
115
- await synth.link(() => {});
116
- externalCache.set(specifier, synth);
117
- return synth;
118
- }
119
-
120
- /**
121
- * Create a SyntheticModule stub for a non-JS file (e.g., .json, .node).
122
- */
123
- async function stubNonJs(absPath) {
124
- if (moduleCache.has(absPath)) return moduleCache.get(absPath);
125
-
126
- const synth = new vm.SyntheticModule(
127
- ["default"],
128
- function () {
129
- this.setExport("default", undefined);
130
- },
131
- { identifier: absPath, context },
132
- );
133
- await synth.link(() => {});
134
- moduleCache.set(absPath, synth);
135
- return synth;
136
- }
137
-
138
- /**
139
- * Linker callback for vm.SourceTextModule.link().
140
- * Resolves specifiers to cached modules or creates stubs.
141
- */
142
- async function linker(specifier, referencingModule) {
143
- // ── External dependency (node: builtin or npm package) ──
144
- if (!specifier.startsWith(".") && !specifier.startsWith("/")) {
145
- return stubExternal(specifier);
146
- }
147
-
148
- // ── Local import — resolve relative to referencing module ──
149
- const refDir = dirname(referencingModule.identifier);
150
- const resolved = resolve(refDir, specifier);
151
-
152
- // Already parsed / stubbed
153
- if (moduleCache.has(resolved)) return moduleCache.get(resolved);
154
-
155
- // Non-JS file (.json, .node, .wasm, etc.)
156
- if (!JS_EXTENSIONS.has(extname(resolved))) {
157
- return stubNonJs(resolved);
158
- }
159
-
160
- // JS file outside our initial scan (e.g., vendor/, tools/ dependency)
161
- if (existsSync(resolved)) {
162
- try {
163
- const source = readFileSync(resolved, "utf8");
164
- const mod = new vm.SourceTextModule(source, {
165
- identifier: resolved,
166
- context,
167
- });
168
- moduleCache.set(resolved, mod);
169
- return mod;
170
- } catch {
171
- // Parse error — syntax-check.mjs will report it.
172
- return stubNonJs(resolved);
173
- }
174
- }
175
-
176
- // File doesn't exist — report clearly.
177
- throw new Error(
178
- `Cannot find module '${specifier}' imported from ${relative(rootDir, referencingModule.identifier)}`,
179
- );
180
- }
181
-
182
- // Phase 2: Link all modules — this validates named export bindings.
183
- for (const [absPath, mod] of moduleCache) {
184
- // Already linked (as a transitive dependency of a previously linked module).
185
- if (mod.status !== "unlinked") continue;
186
- try {
187
- await mod.link(linker);
188
- } catch (err) {
189
- const rel = relative(rootDir, absPath);
190
- errors.push({ file: rel, error: err.message });
191
- }
192
- }
193
-
194
- return { errors, moduleCount: moduleCache.size };
195
- }
196
-
197
- // ── CLI entrypoint ──────────────────────────────────────────────────────────
198
-
199
- async function main() {
200
- const args = process.argv.slice(2);
201
-
202
- let rootDir = process.cwd();
203
- let files = undefined;
204
-
205
- for (let i = 0; i < args.length; i++) {
206
- if (args[i] === "--root" && args[i + 1]) {
207
- rootDir = resolve(args[++i]);
208
- } else if (args[i] === "--files" && args[i + 1]) {
209
- files = args[++i].split(",").filter(Boolean);
210
- }
211
- }
212
-
213
- const { errors, moduleCount } = await validateImports({ rootDir, files });
214
-
215
- if (errors.length > 0) {
216
- console.error("Import validation failed:\n");
217
- for (const { file, error } of errors) {
218
- console.error(` \u2717 ${file}`);
219
- console.error(` ${error}\n`);
220
- }
221
- process.exit(1);
222
- }
223
-
224
- console.log(
225
- `Imports OK: ${moduleCount} modules linked, 0 broken imports`,
226
- );
227
- }
228
-
229
- if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
230
- main().catch((err) => {
231
- console.error(err.stack || err.message);
232
- process.exit(1);
233
- });
234
- }
1
+ /**
2
+ * import-check.mjs — ESM named-export validation gate.
3
+ *
4
+ * Uses vm.SourceTextModule.link() to validate that every named import
5
+ * from a local module actually exists as an export in the target module.
6
+ * This catches the class of errors where a named import is added but the
7
+ * corresponding export doesn't exist (e.g., partial merges, abandoned WIP,
8
+ * renames that missed a call-site).
9
+ *
10
+ * External dependencies (node: builtins, npm packages) are dynamically
11
+ * imported and mirrored as SyntheticModules so that local module linking
12
+ * succeeds without requiring the full dependency graph.
13
+ */
14
+
15
+ import vm from "node:vm";
16
+ import { readFileSync, existsSync } from "node:fs";
17
+ import { resolve, dirname, relative, extname } from "node:path";
18
+ import { execSync } from "node:child_process";
19
+ import { pathToFileURL, fileURLToPath } from "node:url";
20
+
21
+ const JS_EXTENSIONS = new Set([".mjs", ".js", ".cjs"]);
22
+
23
+ function toErrorMessage(error) {
24
+ return error instanceof Error ? error.message : String(error);
25
+ }
26
+
27
+ function buildSyntaxError(rootDir, absPath, error) {
28
+ const moduleFile = relative(rootDir, absPath);
29
+ const syntaxError = new Error(`Syntax error in ${moduleFile}: ${toErrorMessage(error)}`);
30
+ syntaxError.code = "module_syntax_error";
31
+ syntaxError.moduleFile = moduleFile;
32
+ return syntaxError;
33
+ }
34
+
35
+ /**
36
+ * Discover all .mjs source modules via git, excluding test/bench/site/desktop.
37
+ */
38
+ export function discoverSourceModules(rootDir) {
39
+ try {
40
+ const output = execSync("git ls-files --cached", {
41
+ encoding: "utf8",
42
+ cwd: rootDir,
43
+ stdio: ["pipe", "pipe", "pipe"],
44
+ });
45
+ return output
46
+ .split("\n")
47
+ .map((f) => f.trim())
48
+ .filter(
49
+ (f) =>
50
+ f.endsWith(".mjs") &&
51
+ !f.startsWith("tests/") &&
52
+ !f.startsWith("bench/") &&
53
+ !f.startsWith("site/") &&
54
+ !f.startsWith("desktop/") &&
55
+ !f.startsWith("tools/"),
56
+ )
57
+ .sort((a, b) => a.localeCompare(b));
58
+ } catch {
59
+ return [];
60
+ }
61
+ }
62
+
63
+ /**
64
+ * Validate all ESM named imports by linking modules with vm.SourceTextModule.
65
+ *
66
+ * @param {object} [opts]
67
+ * @param {string} [opts.rootDir] — project root (default: cwd)
68
+ * @param {string[]} [opts.files] — explicit list of relative .mjs paths to check
69
+ * @returns {{ errors: Array<{file: string, error: string}>, moduleCount: number }}
70
+ */
71
+ export async function validateImports({ rootDir, files } = {}) {
72
+ rootDir = rootDir ?? process.cwd();
73
+
74
+ if (typeof vm.SourceTextModule !== "function") {
75
+ throw new Error(
76
+ "vm.SourceTextModule is unavailable. Run with --experimental-vm-modules.",
77
+ );
78
+ }
79
+
80
+ const context = vm.createContext({});
81
+ const moduleCache = new Map(); // absolute path → SourceTextModule | SyntheticModule
82
+ const externalCache = new Map(); // specifier → SyntheticModule
83
+ const unresolvedExternals = new Set(); // specifiers that couldn't be dynamically imported
84
+ const errors = [];
85
+ const parseErrors = new Map(); // absolute path → Error
86
+
87
+ const moduleFiles = files ?? discoverSourceModules(rootDir);
88
+ const seenErrors = new Set();
89
+
90
+ function recordError(file, error) {
91
+ const message = toErrorMessage(error);
92
+ const key = `${file}\n${message}`;
93
+ if (seenErrors.has(key)) return;
94
+ seenErrors.add(key);
95
+ errors.push({ file, error: message });
96
+ }
97
+
98
+ // Phase 1: Parse all source modules into SourceTextModules.
99
+ for (const file of moduleFiles) {
100
+ const absPath = resolve(rootDir, file);
101
+ if (!existsSync(absPath)) continue;
102
+ try {
103
+ const source = readFileSync(absPath, "utf8");
104
+ const mod = new vm.SourceTextModule(source, {
105
+ identifier: absPath,
106
+ context,
107
+ });
108
+ moduleCache.set(absPath, mod);
109
+ } catch (error) {
110
+ parseErrors.set(absPath, buildSyntaxError(rootDir, absPath, error));
111
+ }
112
+ }
113
+
114
+ /**
115
+ * Create a SyntheticModule stub for an external dependency.
116
+ * Dynamically imports the real module to mirror its export names.
117
+ */
118
+ async function stubExternal(specifier) {
119
+ if (externalCache.has(specifier)) return externalCache.get(specifier);
120
+
121
+ let exportNames = ["default"];
122
+ try {
123
+ const real = await import(specifier);
124
+ exportNames = Object.keys(real);
125
+ if (!exportNames.includes("default")) exportNames.push("default");
126
+ } catch {
127
+ // Cannot import (optional dep, missing from node_modules, etc.).
128
+ // Track as unresolved so we can suppress false-positive named-export
129
+ // errors that occur when running from a worktree without node_modules.
130
+ unresolvedExternals.add(specifier);
131
+ }
132
+
133
+ const synth = new vm.SyntheticModule(
134
+ exportNames,
135
+ function () {
136
+ for (const name of exportNames) this.setExport(name, undefined);
137
+ },
138
+ { identifier: `external:${specifier}`, context },
139
+ );
140
+ // SyntheticModules have no dependencies, so linker is never called.
141
+ await synth.link(() => {});
142
+ externalCache.set(specifier, synth);
143
+ return synth;
144
+ }
145
+
146
+ /**
147
+ * Create a SyntheticModule stub for a non-JS file (e.g., .json, .node).
148
+ */
149
+ async function stubNonJs(absPath) {
150
+ if (moduleCache.has(absPath)) return moduleCache.get(absPath);
151
+
152
+ const synth = new vm.SyntheticModule(
153
+ ["default"],
154
+ function () {
155
+ this.setExport("default", undefined);
156
+ },
157
+ { identifier: absPath, context },
158
+ );
159
+ await synth.link(() => {});
160
+ moduleCache.set(absPath, synth);
161
+ return synth;
162
+ }
163
+
164
+ /**
165
+ * Linker callback for vm.SourceTextModule.link().
166
+ * Resolves specifiers to cached modules or creates stubs.
167
+ */
168
+ async function linker(specifier, referencingModule) {
169
+ // ── External dependency (node: builtin or npm package) ──
170
+ if (!specifier.startsWith(".") && !specifier.startsWith("/")) {
171
+ return stubExternal(specifier);
172
+ }
173
+
174
+ // ── Local import — resolve relative to referencing module ──
175
+ const refDir = dirname(referencingModule.identifier);
176
+ const resolved = resolve(refDir, specifier);
177
+
178
+ // Already parsed / stubbed
179
+ if (moduleCache.has(resolved)) return moduleCache.get(resolved);
180
+ if (parseErrors.has(resolved)) throw parseErrors.get(resolved);
181
+
182
+ // Non-JS file (.json, .node, .wasm, etc.)
183
+ if (!JS_EXTENSIONS.has(extname(resolved))) {
184
+ return stubNonJs(resolved);
185
+ }
186
+
187
+ // JS file outside our initial scan (e.g., vendor/, tools/ dependency)
188
+ if (existsSync(resolved)) {
189
+ try {
190
+ const source = readFileSync(resolved, "utf8");
191
+ const mod = new vm.SourceTextModule(source, {
192
+ identifier: resolved,
193
+ context,
194
+ });
195
+ moduleCache.set(resolved, mod);
196
+ return mod;
197
+ } catch (error) {
198
+ const syntaxError = buildSyntaxError(rootDir, resolved, error);
199
+ parseErrors.set(resolved, syntaxError);
200
+ throw syntaxError;
201
+ }
202
+ }
203
+
204
+ // File doesn't exist — report clearly.
205
+ throw new Error(
206
+ `Cannot find module '${specifier}' imported from ${relative(rootDir, referencingModule.identifier)}`,
207
+ );
208
+ }
209
+
210
+ // Phase 2: Link all modules — this validates named export bindings.
211
+ for (const [absPath, syntaxError] of parseErrors) {
212
+ recordError(relative(rootDir, absPath), syntaxError);
213
+ }
214
+
215
+ for (const [absPath, mod] of moduleCache) {
216
+ // Already linked (as a transitive dependency of a previously linked module).
217
+ if (mod.status !== "unlinked") continue;
218
+ try {
219
+ await mod.link(linker);
220
+ } catch (err) {
221
+ const rel = typeof err?.moduleFile === "string" && err.moduleFile
222
+ ? err.moduleFile
223
+ : relative(rootDir, absPath);
224
+ recordError(rel, err);
225
+ }
226
+ }
227
+
228
+ // Suppress errors caused by unresolvable external packages (e.g. missing
229
+ // node_modules when running from a git worktree). The pattern emitted by
230
+ // vm.SourceTextModule is: "The requested module '<spec>' does not provide
231
+ // an export named '<name>'".
232
+ const filteredErrors = errors.filter(({ error }) => {
233
+ for (const spec of unresolvedExternals) {
234
+ if (error.includes(`'${spec}'`)) return false;
235
+ }
236
+ return true;
237
+ });
238
+
239
+ return { errors: filteredErrors, moduleCount: moduleCache.size };
240
+ }
241
+
242
+ // ── CLI entrypoint ──────────────────────────────────────────────────────────
243
+
244
+ async function main() {
245
+ const args = process.argv.slice(2);
246
+
247
+ let rootDir = process.cwd();
248
+ let files = undefined;
249
+
250
+ for (let i = 0; i < args.length; i++) {
251
+ if (args[i] === "--root" && args[i + 1]) {
252
+ rootDir = resolve(args[++i]);
253
+ } else if (args[i] === "--files" && args[i + 1]) {
254
+ files = args[++i].split(",").filter(Boolean);
255
+ }
256
+ }
257
+
258
+ const { errors, moduleCount } = await validateImports({ rootDir, files });
259
+
260
+ if (errors.length > 0) {
261
+ console.error("Import validation failed:\n");
262
+ for (const { file, error } of errors) {
263
+ console.error(` \u2717 ${file}`);
264
+ console.error(` ${error}\n`);
265
+ }
266
+ process.exit(1);
267
+ }
268
+
269
+ console.log(
270
+ `Imports OK: ${moduleCount} modules linked, 0 broken imports`,
271
+ );
272
+ }
273
+
274
+ if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
275
+ main().catch((err) => {
276
+ console.error(err.stack || err.message);
277
+ process.exit(1);
278
+ });
279
+ }
@@ -1,38 +1,114 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- import { execSync } from "node:child_process";
3
+ import { spawnSync } from "node:child_process";
4
4
  import { existsSync } from "node:fs";
5
5
  import { resolve } from "node:path";
6
+ import { fileURLToPath } from "node:url";
7
+ import { sanitizeGitEnv } from "../git/git-safety.mjs";
6
8
  import { isEnvFlagEnabled, shouldAutoInstallGitHooks } from "../task/task-context.mjs";
7
9
 
8
- function run(cmd) {
9
- execSync(cmd, { stdio: "inherit" });
10
+ function runGit(args, cwd) {
11
+ return spawnSync("git", args, {
12
+ cwd,
13
+ encoding: "utf8",
14
+ timeout: 15_000,
15
+ windowsHide: true,
16
+ env: sanitizeGitEnv(),
17
+ });
10
18
  }
11
19
 
12
- function main() {
13
- if (isEnvFlagEnabled(process.env.BOSUN_SKIP_GIT_HOOKS, false)) {
14
- return;
15
- }
16
- if (!shouldAutoInstallGitHooks()) {
17
- return;
18
- }
20
+ function normalizeHooksPath(value) {
21
+ return String(value || "")
22
+ .trim()
23
+ .replace(/\\/g, "/")
24
+ .replace(/\/+$/, "");
25
+ }
26
+
27
+ function isExpectedHooksPath(value) {
28
+ const normalized = normalizeHooksPath(value);
29
+ return normalized === ".githooks" || normalized.endsWith("/.githooks");
30
+ }
31
+
32
+ function getGitConfigValue(cwd, key) {
33
+ const result = runGit(["config", "--get", key], cwd);
34
+ if (result.status !== 0) return "";
35
+ return String(result.stdout || "").trim();
36
+ }
37
+
38
+ function getRepoRoot(cwd) {
39
+ const result = runGit(["rev-parse", "--show-toplevel"], cwd);
40
+ if (result.status !== 0) return "";
41
+ return String(result.stdout || "").trim();
42
+ }
43
+
44
+ export function installGitHooks(options = {}) {
45
+ const cwd = resolve(options.cwd || process.cwd());
46
+ const env = options.env || process.env;
47
+ const silent = options.silent === true;
48
+ const force = options.force === true;
19
49
 
20
- let root = "";
21
- try {
22
- root = execSync("git rev-parse --show-toplevel", { encoding: "utf8" }).trim();
23
- } catch {
24
- return;
50
+ if (!force && isEnvFlagEnabled(env.BOSUN_SKIP_GIT_HOOKS, false)) {
51
+ return { ok: true, skipped: true, reason: "env-skip" };
52
+ }
53
+ if (!force && !shouldAutoInstallGitHooks({ env })) {
54
+ return { ok: true, skipped: true, reason: "auto-install-disabled" };
25
55
  }
26
56
 
27
- if (!root) return;
57
+ const root = getRepoRoot(cwd);
58
+ if (!root) return { ok: true, skipped: true, reason: "not-a-git-repo" };
28
59
 
29
60
  const hooksDir = resolve(root, ".githooks");
30
61
  if (!existsSync(hooksDir)) {
31
- return;
62
+ return { ok: true, skipped: true, reason: "missing-hooks-dir", root, hooksDir };
63
+ }
64
+
65
+ const previousHooksPath = getGitConfigValue(root, "core.hooksPath");
66
+ if (isExpectedHooksPath(previousHooksPath)) {
67
+ if (!silent) {
68
+ console.log(`[hooks] installed (core.hooksPath=${previousHooksPath || ".githooks"})`);
69
+ }
70
+ return {
71
+ ok: true,
72
+ changed: false,
73
+ repaired: false,
74
+ root,
75
+ hooksDir,
76
+ hooksPath: previousHooksPath || ".githooks",
77
+ previousHooksPath,
78
+ };
79
+ }
80
+
81
+ const result = runGit(["config", "core.hooksPath", ".githooks"], root);
82
+ const installedHooksPath = result.status === 0
83
+ ? (getGitConfigValue(root, "core.hooksPath") || ".githooks")
84
+ : previousHooksPath;
85
+ const ok = result.status === 0 && isExpectedHooksPath(installedHooksPath);
86
+
87
+ if (ok && !silent) {
88
+ const action = previousHooksPath ? "repaired" : "installed";
89
+ console.log(`[hooks] ${action} (core.hooksPath=${installedHooksPath})`);
32
90
  }
33
91
 
34
- run(`git -C "${root}" config core.hooksPath .githooks`);
35
- console.log(`[hooks] installed (core.hooksPath=.githooks)`);
92
+ return {
93
+ ok,
94
+ changed: result.status === 0,
95
+ repaired: Boolean(previousHooksPath),
96
+ root,
97
+ hooksDir,
98
+ hooksPath: installedHooksPath,
99
+ previousHooksPath,
100
+ error: ok ? "" : String(result.stderr || result.stdout || "").trim(),
101
+ };
36
102
  }
37
103
 
38
- main();
104
+ function main() {
105
+ const result = installGitHooks();
106
+ if (!result.ok && !result.skipped) {
107
+ console.error(result.error || "[hooks] failed to install .githooks");
108
+ process.exitCode = 1;
109
+ }
110
+ }
111
+
112
+ if (process.argv[1] && resolve(process.argv[1]) === fileURLToPath(import.meta.url)) {
113
+ main();
114
+ }