@ironbee-ai/cli 0.6.2 → 0.7.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 (362) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/README.md +167 -39
  3. package/dist/analysis/code-changes.js.map +1 -1
  4. package/dist/analysis/cross-session.js.map +1 -1
  5. package/dist/analysis/fix-effectiveness.js.map +1 -1
  6. package/dist/analysis/time-analysis.js.map +1 -1
  7. package/dist/analysis/verdict-details.js.map +1 -1
  8. package/dist/analysis/verification-quality.js.map +1 -1
  9. package/dist/analytics/classifier.d.ts +99 -0
  10. package/dist/analytics/classifier.d.ts.map +1 -0
  11. package/dist/analytics/classifier.js +380 -0
  12. package/dist/analytics/classifier.js.map +1 -0
  13. package/dist/analytics/emit.d.ts +67 -0
  14. package/dist/analytics/emit.d.ts.map +1 -0
  15. package/dist/analytics/emit.js +901 -0
  16. package/dist/analytics/emit.js.map +1 -0
  17. package/dist/analytics/errors.d.ts +33 -0
  18. package/dist/analytics/errors.d.ts.map +1 -0
  19. package/dist/analytics/errors.js +93 -0
  20. package/dist/analytics/errors.js.map +1 -0
  21. package/dist/analytics/hook-trigger.d.ts +39 -0
  22. package/dist/analytics/hook-trigger.d.ts.map +1 -0
  23. package/dist/analytics/hook-trigger.js +127 -0
  24. package/dist/analytics/hook-trigger.js.map +1 -0
  25. package/dist/analytics/log.d.ts +44 -0
  26. package/dist/analytics/log.d.ts.map +1 -0
  27. package/dist/analytics/log.js +158 -0
  28. package/dist/analytics/log.js.map +1 -0
  29. package/dist/analytics/merge.d.ts +40 -0
  30. package/dist/analytics/merge.d.ts.map +1 -0
  31. package/dist/analytics/merge.js +527 -0
  32. package/dist/analytics/merge.js.map +1 -0
  33. package/dist/analytics/pricing.d.ts +149 -0
  34. package/dist/analytics/pricing.d.ts.map +1 -0
  35. package/dist/analytics/pricing.js +179 -0
  36. package/dist/analytics/pricing.js.map +1 -0
  37. package/dist/analytics/projection.d.ts +356 -0
  38. package/dist/analytics/projection.d.ts.map +1 -0
  39. package/dist/analytics/projection.js +2281 -0
  40. package/dist/analytics/projection.js.map +1 -0
  41. package/dist/analytics/spawn.d.ts +28 -0
  42. package/dist/analytics/spawn.d.ts.map +1 -0
  43. package/dist/analytics/spawn.js +57 -0
  44. package/dist/analytics/spawn.js.map +1 -0
  45. package/dist/analytics/state.d.ts +58 -0
  46. package/dist/analytics/state.d.ts.map +1 -0
  47. package/dist/analytics/state.js +329 -0
  48. package/dist/analytics/state.js.map +1 -0
  49. package/dist/analytics/transcript.d.ts +150 -0
  50. package/dist/analytics/transcript.d.ts.map +1 -0
  51. package/dist/analytics/transcript.js +276 -0
  52. package/dist/analytics/transcript.js.map +1 -0
  53. package/dist/analytics/types.d.ts +875 -0
  54. package/dist/analytics/types.d.ts.map +1 -0
  55. package/dist/analytics/types.js +31 -0
  56. package/dist/analytics/types.js.map +1 -0
  57. package/dist/clients/base.d.ts +21 -2
  58. package/dist/clients/base.d.ts.map +1 -1
  59. package/dist/clients/claude/commands/ironbee-verify.md +15 -7
  60. package/dist/clients/claude/fragments/command-verify.node.md +33 -0
  61. package/dist/clients/claude/fragments/rule.node.md +29 -0
  62. package/dist/clients/claude/fragments/skill.node.md +77 -0
  63. package/dist/clients/claude/hooks/activity-end.d.ts +13 -0
  64. package/dist/clients/claude/hooks/activity-end.d.ts.map +1 -0
  65. package/dist/clients/claude/hooks/activity-end.js +42 -0
  66. package/dist/clients/claude/hooks/activity-end.js.map +1 -0
  67. package/dist/clients/claude/hooks/require-verdict.d.ts +3 -2
  68. package/dist/clients/claude/hooks/require-verdict.d.ts.map +1 -1
  69. package/dist/clients/claude/hooks/require-verdict.js +6 -5
  70. package/dist/clients/claude/hooks/require-verdict.js.map +1 -1
  71. package/dist/clients/claude/hooks/require-verification.d.ts +7 -4
  72. package/dist/clients/claude/hooks/require-verification.d.ts.map +1 -1
  73. package/dist/clients/claude/hooks/require-verification.js +44 -22
  74. package/dist/clients/claude/hooks/require-verification.js.map +1 -1
  75. package/dist/clients/claude/hooks/session-end.d.ts.map +1 -1
  76. package/dist/clients/claude/hooks/session-end.js +17 -2
  77. package/dist/clients/claude/hooks/session-end.js.map +1 -1
  78. package/dist/clients/claude/hooks/session-start.d.ts.map +1 -1
  79. package/dist/clients/claude/hooks/session-start.js +2 -1
  80. package/dist/clients/claude/hooks/session-start.js.map +1 -1
  81. package/dist/clients/claude/hooks/track-action-monitor.d.ts +27 -0
  82. package/dist/clients/claude/hooks/track-action-monitor.d.ts.map +1 -0
  83. package/dist/clients/claude/hooks/track-action-monitor.js +126 -0
  84. package/dist/clients/claude/hooks/track-action-monitor.js.map +1 -0
  85. package/dist/clients/claude/hooks/track-action.d.ts.map +1 -1
  86. package/dist/clients/claude/hooks/track-action.js +29 -20
  87. package/dist/clients/claude/hooks/track-action.js.map +1 -1
  88. package/dist/clients/claude/hooks/verify-gate.d.ts.map +1 -1
  89. package/dist/clients/claude/hooks/verify-gate.js +18 -1
  90. package/dist/clients/claude/hooks/verify-gate.js.map +1 -1
  91. package/dist/clients/claude/index.d.ts +4 -1
  92. package/dist/clients/claude/index.d.ts.map +1 -1
  93. package/dist/clients/claude/index.js +171 -94
  94. package/dist/clients/claude/index.js.map +1 -1
  95. package/dist/clients/claude/rules/ironbee-verification.md +41 -33
  96. package/dist/clients/claude/skills/ironbee-verification.md +93 -76
  97. package/dist/clients/cursor/commands/ironbee-verify/SKILL.md +18 -10
  98. package/dist/clients/cursor/fragments/command-verify.node.md +33 -0
  99. package/dist/clients/cursor/fragments/rule.node.md +29 -0
  100. package/dist/clients/cursor/fragments/skill.node.md +77 -0
  101. package/dist/clients/cursor/hooks/activity-end.d.ts +14 -0
  102. package/dist/clients/cursor/hooks/activity-end.d.ts.map +1 -0
  103. package/dist/clients/cursor/hooks/activity-end.js +45 -0
  104. package/dist/clients/cursor/hooks/activity-end.js.map +1 -0
  105. package/dist/clients/cursor/hooks/require-verdict.d.ts +1 -1
  106. package/dist/clients/cursor/hooks/require-verdict.js +4 -4
  107. package/dist/clients/cursor/hooks/require-verification.d.ts.map +1 -1
  108. package/dist/clients/cursor/hooks/require-verification.js +42 -16
  109. package/dist/clients/cursor/hooks/require-verification.js.map +1 -1
  110. package/dist/clients/cursor/hooks/session-end.d.ts.map +1 -1
  111. package/dist/clients/cursor/hooks/session-end.js +18 -2
  112. package/dist/clients/cursor/hooks/session-end.js.map +1 -1
  113. package/dist/clients/cursor/hooks/session-start.d.ts.map +1 -1
  114. package/dist/clients/cursor/hooks/session-start.js +2 -1
  115. package/dist/clients/cursor/hooks/session-start.js.map +1 -1
  116. package/dist/clients/cursor/hooks/track-action-monitor.d.ts +27 -0
  117. package/dist/clients/cursor/hooks/track-action-monitor.d.ts.map +1 -0
  118. package/dist/clients/cursor/hooks/track-action-monitor.js +133 -0
  119. package/dist/clients/cursor/hooks/track-action-monitor.js.map +1 -0
  120. package/dist/clients/cursor/hooks/track-action.d.ts.map +1 -1
  121. package/dist/clients/cursor/hooks/track-action.js +51 -23
  122. package/dist/clients/cursor/hooks/track-action.js.map +1 -1
  123. package/dist/clients/cursor/hooks/verify-gate.d.ts.map +1 -1
  124. package/dist/clients/cursor/hooks/verify-gate.js +14 -1
  125. package/dist/clients/cursor/hooks/verify-gate.js.map +1 -1
  126. package/dist/clients/cursor/index.d.ts +4 -1
  127. package/dist/clients/cursor/index.d.ts.map +1 -1
  128. package/dist/clients/cursor/index.js +117 -71
  129. package/dist/clients/cursor/index.js.map +1 -1
  130. package/dist/clients/cursor/rules/ironbee-verification.mdc +37 -29
  131. package/dist/clients/cursor/skills/ironbee-verification.md +93 -76
  132. package/dist/clients/registry.d.ts +14 -0
  133. package/dist/clients/registry.d.ts.map +1 -1
  134. package/dist/clients/registry.js +34 -0
  135. package/dist/clients/registry.js.map +1 -1
  136. package/dist/commands/analyze.d.ts.map +1 -1
  137. package/dist/commands/analyze.js +40 -0
  138. package/dist/commands/analyze.js.map +1 -1
  139. package/dist/commands/backend-toggle.d.ts +45 -0
  140. package/dist/commands/backend-toggle.d.ts.map +1 -0
  141. package/dist/commands/backend-toggle.js +192 -0
  142. package/dist/commands/backend-toggle.js.map +1 -0
  143. package/dist/commands/disable-backend.d.ts +14 -0
  144. package/dist/commands/disable-backend.d.ts.map +1 -0
  145. package/dist/commands/disable-backend.js +34 -0
  146. package/dist/commands/disable-backend.js.map +1 -0
  147. package/dist/commands/disable-verification.d.ts +16 -0
  148. package/dist/commands/disable-verification.d.ts.map +1 -0
  149. package/dist/commands/disable-verification.js +36 -0
  150. package/dist/commands/disable-verification.js.map +1 -0
  151. package/dist/commands/enable-backend.d.ts +15 -0
  152. package/dist/commands/enable-backend.d.ts.map +1 -0
  153. package/dist/commands/enable-backend.js +35 -0
  154. package/dist/commands/enable-backend.js.map +1 -0
  155. package/dist/commands/enable-verification.d.ts +14 -0
  156. package/dist/commands/enable-verification.d.ts.map +1 -0
  157. package/dist/commands/enable-verification.js +34 -0
  158. package/dist/commands/enable-verification.js.map +1 -0
  159. package/dist/commands/hook.d.ts.map +1 -1
  160. package/dist/commands/hook.js +60 -0
  161. package/dist/commands/hook.js.map +1 -1
  162. package/dist/commands/import.d.ts +39 -0
  163. package/dist/commands/import.d.ts.map +1 -0
  164. package/dist/commands/import.js +369 -0
  165. package/dist/commands/import.js.map +1 -0
  166. package/dist/commands/install.d.ts.map +1 -1
  167. package/dist/commands/install.js +15 -20
  168. package/dist/commands/install.js.map +1 -1
  169. package/dist/commands/process-analytics.d.ts +18 -0
  170. package/dist/commands/process-analytics.d.ts.map +1 -0
  171. package/dist/commands/process-analytics.js +57 -0
  172. package/dist/commands/process-analytics.js.map +1 -0
  173. package/dist/commands/queue.d.ts +2 -3
  174. package/dist/commands/queue.d.ts.map +1 -1
  175. package/dist/commands/queue.js +2 -3
  176. package/dist/commands/queue.js.map +1 -1
  177. package/dist/commands/status.d.ts.map +1 -1
  178. package/dist/commands/status.js +29 -1
  179. package/dist/commands/status.js.map +1 -1
  180. package/dist/commands/verification-toggle.d.ts +47 -0
  181. package/dist/commands/verification-toggle.d.ts.map +1 -0
  182. package/dist/commands/verification-toggle.js +122 -0
  183. package/dist/commands/verification-toggle.js.map +1 -0
  184. package/dist/commands/verify.d.ts.map +1 -1
  185. package/dist/commands/verify.js +28 -0
  186. package/dist/commands/verify.js.map +1 -1
  187. package/dist/hooks/core/actions.d.ts +72 -70
  188. package/dist/hooks/core/actions.d.ts.map +1 -1
  189. package/dist/hooks/core/actions.js +191 -28
  190. package/dist/hooks/core/actions.js.map +1 -1
  191. package/dist/hooks/core/activity-end.d.ts +20 -0
  192. package/dist/hooks/core/activity-end.d.ts.map +1 -0
  193. package/dist/hooks/core/activity-end.js +23 -0
  194. package/dist/hooks/core/activity-end.js.map +1 -0
  195. package/dist/hooks/core/required-tools.d.ts +30 -0
  196. package/dist/hooks/core/required-tools.d.ts.map +1 -0
  197. package/dist/hooks/core/required-tools.js +70 -0
  198. package/dist/hooks/core/required-tools.js.map +1 -0
  199. package/dist/hooks/core/session-state.d.ts +12 -3
  200. package/dist/hooks/core/session-state.d.ts.map +1 -1
  201. package/dist/hooks/core/session-state.js +59 -0
  202. package/dist/hooks/core/session-state.js.map +1 -1
  203. package/dist/hooks/core/submit-verdict.d.ts.map +1 -1
  204. package/dist/hooks/core/submit-verdict.js +16 -12
  205. package/dist/hooks/core/submit-verdict.js.map +1 -1
  206. package/dist/hooks/core/verify-gate.d.ts +17 -3
  207. package/dist/hooks/core/verify-gate.d.ts.map +1 -1
  208. package/dist/hooks/core/verify-gate.js +312 -116
  209. package/dist/hooks/core/verify-gate.js.map +1 -1
  210. package/dist/import/claude/analytics-runner.d.ts +42 -0
  211. package/dist/import/claude/analytics-runner.d.ts.map +1 -0
  212. package/dist/import/claude/analytics-runner.js +213 -0
  213. package/dist/import/claude/analytics-runner.js.map +1 -0
  214. package/dist/import/claude/discovery.d.ts +22 -0
  215. package/dist/import/claude/discovery.d.ts.map +1 -0
  216. package/dist/import/claude/discovery.js +197 -0
  217. package/dist/import/claude/discovery.js.map +1 -0
  218. package/dist/import/claude/encoding.d.ts +50 -0
  219. package/dist/import/claude/encoding.d.ts.map +1 -0
  220. package/dist/import/claude/encoding.js +110 -0
  221. package/dist/import/claude/encoding.js.map +1 -0
  222. package/dist/import/claude/events/file-change.d.ts +28 -0
  223. package/dist/import/claude/events/file-change.d.ts.map +1 -0
  224. package/dist/import/claude/events/file-change.js +112 -0
  225. package/dist/import/claude/events/file-change.js.map +1 -0
  226. package/dist/import/claude/events/tool-call.d.ts +61 -0
  227. package/dist/import/claude/events/tool-call.d.ts.map +1 -0
  228. package/dist/import/claude/events/tool-call.js +119 -0
  229. package/dist/import/claude/events/tool-call.js.map +1 -0
  230. package/dist/import/claude/runner.d.ts +31 -0
  231. package/dist/import/claude/runner.d.ts.map +1 -0
  232. package/dist/import/claude/runner.js +280 -0
  233. package/dist/import/claude/runner.js.map +1 -0
  234. package/dist/import/claude/summary.d.ts +23 -0
  235. package/dist/import/claude/summary.d.ts.map +1 -0
  236. package/dist/import/claude/summary.js +186 -0
  237. package/dist/import/claude/summary.js.map +1 -0
  238. package/dist/import/claude/transcript-walk.d.ts +52 -0
  239. package/dist/import/claude/transcript-walk.d.ts.map +1 -0
  240. package/dist/import/claude/transcript-walk.js +187 -0
  241. package/dist/import/claude/transcript-walk.js.map +1 -0
  242. package/dist/import/concurrent-pool.d.ts +45 -0
  243. package/dist/import/concurrent-pool.d.ts.map +1 -0
  244. package/dist/import/concurrent-pool.js +95 -0
  245. package/dist/import/concurrent-pool.js.map +1 -0
  246. package/dist/import/emitter.d.ts +29 -0
  247. package/dist/import/emitter.d.ts.map +1 -0
  248. package/dist/import/emitter.js +66 -0
  249. package/dist/import/emitter.js.map +1 -0
  250. package/dist/import/events/activity.d.ts +23 -0
  251. package/dist/import/events/activity.d.ts.map +1 -0
  252. package/dist/import/events/activity.js +45 -0
  253. package/dist/import/events/activity.js.map +1 -0
  254. package/dist/import/events/session.d.ts +24 -0
  255. package/dist/import/events/session.d.ts.map +1 -0
  256. package/dist/import/events/session.js +47 -0
  257. package/dist/import/events/session.js.map +1 -0
  258. package/dist/import/filter.d.ts +47 -0
  259. package/dist/import/filter.d.ts.map +1 -0
  260. package/dist/import/filter.js +90 -0
  261. package/dist/import/filter.js.map +1 -0
  262. package/dist/import/ids.d.ts +56 -0
  263. package/dist/import/ids.d.ts.map +1 -0
  264. package/dist/import/ids.js +87 -0
  265. package/dist/import/ids.js.map +1 -0
  266. package/dist/import/index.d.ts +29 -0
  267. package/dist/import/index.d.ts.map +1 -0
  268. package/dist/import/index.js +52 -0
  269. package/dist/import/index.js.map +1 -0
  270. package/dist/import/marker.d.ts +20 -0
  271. package/dist/import/marker.d.ts.map +1 -0
  272. package/dist/import/marker.js +71 -0
  273. package/dist/import/marker.js.map +1 -0
  274. package/dist/import/pipeline.d.ts +41 -0
  275. package/dist/import/pipeline.d.ts.map +1 -0
  276. package/dist/import/pipeline.js +47 -0
  277. package/dist/import/pipeline.js.map +1 -0
  278. package/dist/import/progress.d.ts +20 -0
  279. package/dist/import/progress.d.ts.map +1 -0
  280. package/dist/import/progress.js +69 -0
  281. package/dist/import/progress.js.map +1 -0
  282. package/dist/import/skip.d.ts +13 -0
  283. package/dist/import/skip.d.ts.map +1 -0
  284. package/dist/import/skip.js +24 -0
  285. package/dist/import/skip.js.map +1 -0
  286. package/dist/import/types.d.ts +125 -0
  287. package/dist/import/types.d.ts.map +1 -0
  288. package/dist/import/types.js +28 -0
  289. package/dist/import/types.js.map +1 -0
  290. package/dist/index.js +21 -2
  291. package/dist/index.js.map +1 -1
  292. package/dist/lib/collector.d.ts +29 -3
  293. package/dist/lib/collector.d.ts.map +1 -1
  294. package/dist/lib/collector.js +118 -8
  295. package/dist/lib/collector.js.map +1 -1
  296. package/dist/lib/config.d.ts +240 -83
  297. package/dist/lib/config.d.ts.map +1 -1
  298. package/dist/lib/config.js +482 -89
  299. package/dist/lib/config.js.map +1 -1
  300. package/dist/lib/event.d.ts +72 -0
  301. package/dist/lib/event.d.ts.map +1 -0
  302. package/dist/lib/event.js +42 -0
  303. package/dist/lib/event.js.map +1 -0
  304. package/dist/lib/gitignore.d.ts +21 -0
  305. package/dist/lib/gitignore.d.ts.map +1 -0
  306. package/dist/lib/gitignore.js +54 -0
  307. package/dist/lib/gitignore.js.map +1 -0
  308. package/dist/lib/runtime-section.d.ts +118 -0
  309. package/dist/lib/runtime-section.d.ts.map +1 -0
  310. package/dist/lib/runtime-section.js +256 -0
  311. package/dist/lib/runtime-section.js.map +1 -0
  312. package/dist/lib/telemetry.d.ts +1 -1
  313. package/dist/lib/telemetry.d.ts.map +1 -1
  314. package/dist/lib/telemetry.js +4 -1
  315. package/dist/lib/telemetry.js.map +1 -1
  316. package/dist/queue/dead-letter.d.ts +5 -1
  317. package/dist/queue/dead-letter.d.ts.map +1 -1
  318. package/dist/queue/dead-letter.js +5 -1
  319. package/dist/queue/dead-letter.js.map +1 -1
  320. package/dist/queue/drain.d.ts +3 -2
  321. package/dist/queue/drain.d.ts.map +1 -1
  322. package/dist/queue/drain.js +3 -2
  323. package/dist/queue/drain.js.map +1 -1
  324. package/dist/queue/flush.d.ts +28 -12
  325. package/dist/queue/flush.d.ts.map +1 -1
  326. package/dist/queue/flush.js +43 -18
  327. package/dist/queue/flush.js.map +1 -1
  328. package/dist/queue/handlers/send-event.d.ts.map +1 -1
  329. package/dist/queue/handlers/send-event.js.map +1 -1
  330. package/dist/queue/index.d.ts +1 -2
  331. package/dist/queue/index.d.ts.map +1 -1
  332. package/dist/queue/index.js +2 -2
  333. package/dist/queue/index.js.map +1 -1
  334. package/dist/queue/paths.d.ts +4 -2
  335. package/dist/queue/paths.d.ts.map +1 -1
  336. package/dist/queue/paths.js +4 -2
  337. package/dist/queue/paths.js.map +1 -1
  338. package/dist/queue/process-file.d.ts +5 -1
  339. package/dist/queue/process-file.d.ts.map +1 -1
  340. package/dist/queue/process-file.js +5 -1
  341. package/dist/queue/process-file.js.map +1 -1
  342. package/dist/queue/snapshot.d.ts +4 -1
  343. package/dist/queue/snapshot.d.ts.map +1 -1
  344. package/dist/queue/snapshot.js +4 -1
  345. package/dist/queue/snapshot.js.map +1 -1
  346. package/dist/queue/spawn.d.ts +1 -3
  347. package/dist/queue/spawn.d.ts.map +1 -1
  348. package/dist/queue/spawn.js +1 -3
  349. package/dist/queue/spawn.js.map +1 -1
  350. package/dist/queue/submit.d.ts +6 -1
  351. package/dist/queue/submit.d.ts.map +1 -1
  352. package/dist/queue/submit.js +6 -1
  353. package/dist/queue/submit.js.map +1 -1
  354. package/dist/queue/types.d.ts +5 -1
  355. package/dist/queue/types.d.ts.map +1 -1
  356. package/dist/queue/types.js +5 -1
  357. package/dist/queue/types.js.map +1 -1
  358. package/dist/queue/worker-log.d.ts +3 -1
  359. package/dist/queue/worker-log.d.ts.map +1 -1
  360. package/dist/queue/worker-log.js +3 -1
  361. package/dist/queue/worker-log.js.map +1 -1
  362. package/package.json +1 -1
@@ -6,20 +6,45 @@
6
6
  * 1. Global: ~/.ironbee/config.json
7
7
  * 2. Project: <projectDir>/.ironbee/config.json
8
8
  *
9
- * Project config overrides global config (shallow merge per key).
9
+ * Project config overrides global config (deep merge for nested sections,
10
+ * shallow for primitives — see deepMerge).
11
+ *
12
+ * The pre-split layout (top-level `verifyPatterns` / `additionalVerifyPatterns`)
13
+ * is no longer supported. Configs carrying those fields throw at load time —
14
+ * patterns must move under `browser.*` (or the appropriate runtime under
15
+ * `backend.<runtime>.*`).
10
16
  */
11
17
  Object.defineProperty(exports, "__esModule", { value: true });
18
+ exports.DEFAULT_BACKEND_NODE_EVIDENCE_PATHS = exports.DEFAULT_BACKEND_NODE_ALWAYS_REQUIRED = exports.DEFAULT_BROWSER_ALWAYS_REQUIRED = exports.DEFAULT_BROWSER_VERIFY_PATTERNS = exports.RUNTIME_TO_SERVER = exports.BACKEND_SERVERS = void 0;
12
19
  exports.loadConfig = loadConfig;
20
+ exports.getActiveCycles = getActiveCycles;
13
21
  exports.requiresVerification = requiresVerification;
22
+ exports.getRequiredToolsConfig = getRequiredToolsConfig;
14
23
  exports.getMcpServerEntry = getMcpServerEntry;
24
+ exports.getNodeDevToolsMcpEntry = getNodeDevToolsMcpEntry;
15
25
  exports.getMaxRetries = getMaxRetries;
26
+ exports.isCollectorConfigured = isCollectorConfigured;
16
27
  exports.isJobQueueEnabled = isJobQueueEnabled;
17
28
  exports.isRecordingEnabled = isRecordingEnabled;
29
+ exports.getVerificationEnabled = getVerificationEnabled;
30
+ exports.isAnalyticsEnabled = isAnalyticsEnabled;
31
+ exports.isAnalyticsEmitOnStopEnabled = isAnalyticsEmitOnStopEnabled;
32
+ exports.getAnalyticsEmitOnStopMinIntervalSeconds = getAnalyticsEmitOnStopMinIntervalSeconds;
33
+ exports.isAnalyticsTurnEventsEnabled = isAnalyticsTurnEventsEnabled;
34
+ exports.isAnalyticsStepEventsEnabled = isAnalyticsStepEventsEnabled;
35
+ exports.isAnalyticsApiRequestEventsEnabled = isAnalyticsApiRequestEventsEnabled;
18
36
  const fs_1 = require("fs");
19
37
  const path_1 = require("path");
20
38
  const os_1 = require("os");
21
39
  const logger_1 = require("./logger");
22
- const DEFAULT_VERIFY_PATTERNS = [
40
+ /** Backend MCP servers in the runtime registry. Code change to extend. */
41
+ exports.BACKEND_SERVERS = ["node-devtools"];
42
+ /** Map runtime key (`node`) → backend server name (`node-devtools`). */
43
+ exports.RUNTIME_TO_SERVER = {
44
+ node: "node-devtools",
45
+ };
46
+ /** Browser default verify patterns — preserves the pre-split list as-is. */
47
+ exports.DEFAULT_BROWSER_VERIFY_PATTERNS = [
23
48
  "*.ts", "*.tsx",
24
49
  "*.js", "*.jsx", "*.mjs", "*.cjs",
25
50
  "*.vue", "*.svelte",
@@ -46,6 +71,36 @@ const DEFAULT_VERIFY_PATTERNS = [
46
71
  "*.hbs", "*.ejs", "*.pug", "*.jade",
47
72
  "*.astro",
48
73
  ];
74
+ /** Browser-cycle required tools — strict all-of, no alternative paths. */
75
+ exports.DEFAULT_BROWSER_ALWAYS_REQUIRED = [
76
+ "bdt_navigation_go-to",
77
+ "bdt_content_take-screenshot",
78
+ "bdt_a11y_take-aria-snapshot",
79
+ "bdt_o11y_get-console-messages",
80
+ ];
81
+ /** Node-cycle required tools — connect always; then probe path or log path. */
82
+ exports.DEFAULT_BACKEND_NODE_ALWAYS_REQUIRED = [
83
+ "ndt_debug_connect",
84
+ ];
85
+ exports.DEFAULT_BACKEND_NODE_EVIDENCE_PATHS = [
86
+ {
87
+ name: "probe",
88
+ allOf: [
89
+ {
90
+ anyOf: [
91
+ "ndt_debug_put-tracepoint",
92
+ "ndt_debug_put-logpoint",
93
+ "ndt_debug_put-exceptionpoint",
94
+ ],
95
+ },
96
+ "ndt_debug_get-probe-snapshots",
97
+ ],
98
+ },
99
+ {
100
+ name: "log",
101
+ allOf: ["ndt_debug_get-logs"],
102
+ },
103
+ ];
49
104
  const DEFAULT_MAX_RETRIES = 3;
50
105
  function loadJsonFile(filePath) {
51
106
  if (!(0, fs_1.existsSync)(filePath)) {
@@ -59,116 +114,282 @@ function loadJsonFile(filePath) {
59
114
  return {};
60
115
  }
61
116
  }
117
+ /** Detect pre-split flat config. Loud failure per design D6. */
118
+ function assertNoLegacyShape(config, sourcePath) {
119
+ const legacy = [];
120
+ if (Object.prototype.hasOwnProperty.call(config, "verifyPatterns")) {
121
+ legacy.push("'verifyPatterns'");
122
+ }
123
+ if (Object.prototype.hasOwnProperty.call(config, "additionalVerifyPatterns")) {
124
+ legacy.push("'additionalVerifyPatterns'");
125
+ }
126
+ if (legacy.length === 0) {
127
+ return;
128
+ }
129
+ throw new Error(`Legacy IronBee config detected in ${sourcePath}: ${legacy.join(", ")} at the top level is no longer supported. ` +
130
+ `Move them under 'browser.*' (or the appropriate runtime under 'backend.<runtime>.*').`);
131
+ }
132
+ function assertVerificationShape(config, sourcePath) {
133
+ if (!Object.prototype.hasOwnProperty.call(config, "verification")) {
134
+ return;
135
+ }
136
+ const v = config.verification;
137
+ if (v === null || typeof v !== "object" || Array.isArray(v)) {
138
+ throw new Error(`Invalid IronBee config in ${sourcePath}: 'verification' must be an object. ` +
139
+ `Expected shape: { "enable": boolean }.`);
140
+ }
141
+ const block = v;
142
+ if (Object.prototype.hasOwnProperty.call(block, "enable") && typeof block.enable !== "boolean") {
143
+ throw new Error(`Invalid IronBee config in ${sourcePath}: 'verification.enable' must be boolean. ` +
144
+ `Got ${typeof block.enable}.`);
145
+ }
146
+ }
147
+ /** Deep merge for the nested `browser` / `backend.<runtime>` blocks. */
148
+ function mergeCycleConfig(base, override) {
149
+ if (base === undefined && override === undefined) {
150
+ return undefined;
151
+ }
152
+ return { ...(base ?? {}), ...(override ?? {}) };
153
+ }
154
+ function mergeBackend(base, override) {
155
+ if (base === undefined && override === undefined) {
156
+ return undefined;
157
+ }
158
+ const merged = {};
159
+ const keys = new Set([
160
+ ...Object.keys(base ?? {}),
161
+ ...Object.keys(override ?? {}),
162
+ ]);
163
+ for (const key of keys) {
164
+ merged[key] = mergeCycleConfig(base?.[key], override?.[key]);
165
+ }
166
+ return merged;
167
+ }
62
168
  function loadConfig(projectDir) {
63
169
  const globalPath = (0, path_1.join)((0, os_1.homedir)(), ".ironbee", "config.json");
64
170
  const globalConfig = loadJsonFile(globalPath);
171
+ if ((0, fs_1.existsSync)(globalPath)) {
172
+ assertNoLegacyShape(globalConfig, globalPath);
173
+ assertVerificationShape(globalConfig, globalPath);
174
+ }
65
175
  let projectConfig = {};
176
+ let projectPath = "";
66
177
  if (projectDir) {
67
- const projectPath = (0, path_1.join)(projectDir, ".ironbee", "config.json");
178
+ projectPath = (0, path_1.join)(projectDir, ".ironbee", "config.json");
68
179
  projectConfig = loadJsonFile(projectPath);
180
+ if ((0, fs_1.existsSync)(projectPath)) {
181
+ assertNoLegacyShape(projectConfig, projectPath);
182
+ assertVerificationShape(projectConfig, projectPath);
183
+ }
69
184
  }
70
- // merge: defaults < global < project
71
185
  const merged = {
72
186
  ...globalConfig,
73
187
  ...projectConfig,
74
188
  };
189
+ merged.browser = mergeCycleConfig(globalConfig.browser, projectConfig.browser);
190
+ merged.backend = mergeBackend(globalConfig.backend, projectConfig.backend);
75
191
  return merged;
76
192
  }
77
- /**
78
- * Converts a glob pattern to a RegExp.
79
- * Supports: *, **, ?
80
- * Examples:
81
- * "*.ts" → matches "foo.ts"
82
- * "src/components/**" → matches "src/components/Button/index.tsx"
83
- * "*.test.*" → matches "foo.test.ts"
84
- */
193
+ // Glob → RegExp. Supports *, **, ** + slash (zero-or-more segments), ?, and
194
+ // brace expansion {a,b,c} (a|b|c). The trailing-slash form of ** matches
195
+ // the empty path, so "server/**/*.ts" correctly matches both "server/api.ts"
196
+ // (depth 0) and "server/sub/api.ts" (depth 1+).
85
197
  function globToRegExp(pattern) {
86
- let regexStr = pattern
87
- .replace(/[.+^${}()|[\]\\]/g, "\\$&") // escape regex special chars (except * and ?)
88
- .replace(/\*\*/g, "{{GLOBSTAR}}") // placeholder for **
89
- .replace(/\*/g, "[^/]*") // * = anything except /
90
- .replace(/\?/g, "[^/]") // ? = single char except /
91
- .replace(/\{\{GLOBSTAR\}\}/g, ".*"); // ** = anything including /
92
- // if pattern has no path separators, match against basename only
93
- if (!pattern.includes("/")) {
94
- regexStr = `(^|/)${regexStr}$`;
95
- }
96
- else {
97
- regexStr = `(^|/)${regexStr}$`;
98
- }
99
- return new RegExp(regexStr);
198
+ // 1. Brace expansion `{a,b,c}` → `(a|b|c)` before any escaping.
199
+ let p = pattern.replace(/\{([^}]+)\}/g, (_match, group) => {
200
+ return `(${group.split(",").map((s) => s.trim()).join("|")})`;
201
+ });
202
+ // 2. Tokenize globs to placeholders so the escape pass leaves them alone.
203
+ p = p.replace(/\*\*\//g, "\x00DSS\x00") // **/
204
+ .replace(/\*\*/g, "\x00DS\x00") // **
205
+ .replace(/\*/g, "\x00SS\x00") // *
206
+ .replace(/\?/g, "\x00QM\x00"); // ?
207
+ // 3. Escape regex specials (parens / pipe stay literal — they came from brace expansion).
208
+ p = p.replace(/[.+^$\\[\]]/g, "\\$&");
209
+ // 4. Restore globs as regex equivalents.
210
+ p = p.replace(/\x00DSS\x00/g, "(?:.*/)?")
211
+ .replace(/\x00DS\x00/g, ".*")
212
+ .replace(/\x00SS\x00/g, "[^/]*")
213
+ .replace(/\x00QM\x00/g, "[^/]");
214
+ return new RegExp(`(^|/)${p}$`);
100
215
  }
101
- /**
102
- * Tests if a file path matches any of the given glob patterns.
103
- */
104
216
  function matchesAny(filePath, patterns) {
105
217
  for (const pattern of patterns) {
106
- const regex = globToRegExp(pattern);
107
- if (regex.test(filePath)) {
218
+ if (globToRegExp(pattern).test(filePath)) {
108
219
  return true;
109
220
  }
110
221
  }
111
222
  return false;
112
223
  }
224
+ /** Returns the effective pattern set for the browser cycle. */
225
+ function getBrowserPatterns(config) {
226
+ const block = config.browser;
227
+ const base = block?.verifyPatterns ?? exports.DEFAULT_BROWSER_VERIFY_PATTERNS;
228
+ const additional = block?.additionalVerifyPatterns ?? [];
229
+ return [...base, ...additional];
230
+ }
231
+ /** Returns the effective pattern set for `backend.<runtime>`. Empty by default. */
232
+ function getBackendRuntimePatterns(config, runtime) {
233
+ const block = config.backend?.[runtime];
234
+ const base = block?.verifyPatterns ?? [];
235
+ const additional = block?.additionalVerifyPatterns ?? [];
236
+ return [...base, ...additional];
237
+ }
113
238
  /**
114
- * Determines whether a file requires browser verification.
239
+ * Returns the names of cycles whose pattern set matches `filePath`.
240
+ * Used by verify-gate to determine which cycles are active for a Stop hook.
115
241
  *
116
- * Check order:
117
- * 1. ignoredVerifyPatterns if matched, skip (return false)
118
- * 2. verifyPatterns (+ additionalVerifyPatterns) → if matched, verify (return true)
119
- * 3. No match → skip (return false)
242
+ * Order: browser first, then each registered backend runtime. `ignoredVerifyPatterns`
243
+ * applies globally a file matched there activates no cycles.
120
244
  */
121
- function requiresVerification(filePath, config) {
122
- // 1. check ignored patterns first
245
+ function getActiveCycles(filePath, config) {
123
246
  const ignored = config.ignoredVerifyPatterns ?? [];
124
247
  if (ignored.length > 0 && matchesAny(filePath, ignored)) {
125
- return false;
248
+ return [];
249
+ }
250
+ const active = [];
251
+ if (matchesAny(filePath, getBrowserPatterns(config))) {
252
+ active.push("browser");
126
253
  }
127
- // 2. build verify patterns: base + additional
128
- const base = config.verifyPatterns ?? DEFAULT_VERIFY_PATTERNS;
129
- const additional = config.additionalVerifyPatterns ?? [];
130
- const patterns = [...base, ...additional];
131
- return matchesAny(filePath, patterns);
254
+ for (const server of exports.BACKEND_SERVERS) {
255
+ const runtime = Object.keys(exports.RUNTIME_TO_SERVER).find((k) => exports.RUNTIME_TO_SERVER[k] === server);
256
+ if (runtime === undefined) {
257
+ continue;
258
+ }
259
+ const patterns = getBackendRuntimePatterns(config, runtime);
260
+ if (patterns.length > 0 && matchesAny(filePath, patterns)) {
261
+ active.push(runtime);
262
+ }
263
+ }
264
+ return active;
132
265
  }
133
- const IRONBEE_ENV = {
266
+ /**
267
+ * Returns true if a file is under verification by *any* cycle. Used by
268
+ * `clear-verdict` and `require-verdict` hooks where the question is just
269
+ * "should this edit be tracked / gated?", regardless of which cycle.
270
+ */
271
+ function requiresVerification(filePath, config) {
272
+ return getActiveCycles(filePath, config).length > 0;
273
+ }
274
+ /**
275
+ * Per-cycle resolved required-tools spec. Falls back to defaults at this layer
276
+ * so consumers always get a complete `{ alwaysRequired, evidencePaths }` shape.
277
+ *
278
+ * Validity rule: at least one of `alwaysRequired` or `evidencePaths` must be
279
+ * non-empty. Both empty → config error (caught here, surfaced to caller).
280
+ */
281
+ function getRequiredToolsConfig(config, cycle) {
282
+ let alwaysRequired;
283
+ let evidencePaths;
284
+ if (cycle === "browser") {
285
+ alwaysRequired = config.browser?.alwaysRequired ?? exports.DEFAULT_BROWSER_ALWAYS_REQUIRED;
286
+ evidencePaths = config.browser?.evidencePaths ?? [];
287
+ }
288
+ else if (cycle === "node") {
289
+ alwaysRequired = config.backend?.node?.alwaysRequired ?? exports.DEFAULT_BACKEND_NODE_ALWAYS_REQUIRED;
290
+ evidencePaths = config.backend?.node?.evidencePaths ?? exports.DEFAULT_BACKEND_NODE_EVIDENCE_PATHS;
291
+ }
292
+ else {
293
+ const block = config.backend?.[cycle];
294
+ alwaysRequired = block?.alwaysRequired ?? [];
295
+ evidencePaths = block?.evidencePaths ?? [];
296
+ }
297
+ if (alwaysRequired.length === 0 && evidencePaths.length === 0) {
298
+ throw new Error(`Invalid required-tools config for cycle '${cycle}': both 'alwaysRequired' and 'evidencePaths' are empty. ` +
299
+ `At least one must specify required tools.`);
300
+ }
301
+ return { alwaysRequired, evidencePaths };
302
+ }
303
+ const DEFAULT_MCP_COMMAND = "npx";
304
+ const DEFAULT_MCP_ARGS = ["-y", "browser-devtools-mcp"];
305
+ const BROWSER_IRONBEE_ENV = {
134
306
  TOOL_NAME_PREFIX: "bdt_",
135
307
  TOOL_INPUT_METADATA_ENABLE: "true",
136
308
  };
137
- const DEFAULT_MCP_ENV = {
309
+ const NODE_IRONBEE_ENV = {
310
+ PLATFORM: "node",
311
+ TOOL_NAME_PREFIX: "ndt_",
312
+ TOOL_INPUT_METADATA_ENABLE: "true",
313
+ };
314
+ const BROWSER_DEFAULT_MCP_ENV = {
138
315
  BROWSER_DEVTOOLS_INSTALL_CHROMIUM: "true",
139
316
  };
140
- const DEFAULT_MCP_COMMAND = "npx";
141
- const DEFAULT_MCP_ARGS = ["-y", "browser-devtools-mcp"];
317
+ const NODE_DEFAULT_MCP_ENV = {};
142
318
  /**
143
- * Returns the MCP server config for browser-devtools.
319
+ * Auto-derive OTEL exporter env for the devtools MCP server when the
320
+ * IronBee Collector is configured + enabled. Returns `{}` when the
321
+ * collector is absent / disabled / missing a URL — in that case the MCP
322
+ * server runs without telemetry, same as before.
144
323
  *
145
- * Config is read from `browserDevTools` key in IronBee config:
146
- * - `browserDevTools.mcp` full MCP config (used as-is if provided)
147
- * - `browserDevTools.env` — env vars for the MCP server (used only when mcp is not provided)
324
+ * Precedence in the final env block (lowest highest):
325
+ * default static env < auto-injected OTEL env < user override env < ironbee env
148
326
  *
149
- * Priority:
150
- * 1. If `browserDevTools.mcp` exists use as-is (env from mcp config, not from browserDevTools.env)
151
- * 2. Otherwise build default config (npx -y browser-devtools-mcp) with browserDevTools.env merged
327
+ * So operators can still override individual keys via `browserDevTools.env`
328
+ * (e.g. point OTEL at a separate observability endpoint), or fully opt out
329
+ * by setting `OTEL_ENABLE: "false"` in their override.
152
330
  *
153
- * IronBee's own env vars (TOOL_NAME_PREFIX, TOOL_INPUT_METADATA_ENABLE) are always applied last,
154
- * overriding any user-provided values.
331
+ * `runtime` toggles browser-only vars (USER_INTERACTION_EVENTS,
332
+ * BROWSER_HEADLESS_ENABLE) node-devtools doesn't honor those.
155
333
  */
156
- function getMcpServerEntry(projectDir) {
157
- const config = loadConfig(projectDir);
158
- const browserDevTools = config.browserDevTools;
159
- if (browserDevTools && typeof browserDevTools === "object" && !Array.isArray(browserDevTools)) {
160
- const bdtConfig = browserDevTools;
161
- // if mcp is explicitly provided, use as-is + ironbee env overrides
162
- if (bdtConfig.mcp && typeof bdtConfig.mcp === "object" && !Array.isArray(bdtConfig.mcp)) {
163
- const mcp = { ...bdtConfig.mcp };
164
- const mcpEnv = { ...(mcp.env ?? {}), ...IRONBEE_ENV };
334
+ function buildOtelEnv(config, runtime) {
335
+ if (!isCollectorConfigured(config)) {
336
+ return {};
337
+ }
338
+ // Cast: `isCollectorConfigured` already guaranteed url + apiKey are
339
+ // non-empty strings.
340
+ const section = config.collector;
341
+ const env = {
342
+ OTEL_ENABLE: "true",
343
+ OTEL_EXPORTER_HTTP_URL: section.url,
344
+ OTEL_EXPORTER_HTTP_HEADERS: `X-API-Key=${section.apiKey}`,
345
+ OTEL_EXPORTER_TYPE: "otlp/http-protobuf",
346
+ };
347
+ if (runtime === "browser") {
348
+ env.OTEL_INSTRUMENTATION_USER_INTERACTION_EVENTS = "change,input,click";
349
+ env.BROWSER_HEADLESS_ENABLE = "true";
350
+ }
351
+ return env;
352
+ }
353
+ /**
354
+ * Builds an MCP server entry for the given runtime ("browser" or "node").
355
+ *
356
+ * Reads override config from `config.<runtime>DevTools`:
357
+ * - `<...>.mcp` — full MCP config (used as-is + ironbee env always wins)
358
+ * - `<...>.env` — env vars merged into the default (npx -y) config
359
+ *
360
+ * Env precedence (lowest → highest):
361
+ * 1. Static `defaultEnv` (e.g. `BROWSER_DEVTOOLS_INSTALL_CHROMIUM`)
362
+ * 2. Auto-injected OTEL exporter env (when `collector` is configured + enabled)
363
+ * 3. User override env from `<runtime>DevTools.env`
364
+ * 4. IronBee invariant env (`TOOL_NAME_PREFIX`, `TOOL_INPUT_METADATA_ENABLE`, `PLATFORM`)
365
+ *
366
+ * Operators can override individual OTEL vars or opt out entirely
367
+ * (`<runtime>DevTools.env.OTEL_ENABLE: "false"`). IronBee env always wins
368
+ * so the wire contract (tool-name prefix, metadata-injection toggle) cannot
369
+ * be broken from outside.
370
+ */
371
+ function buildMcpEntry(config, overrideKey, ironbeeEnv, defaultEnv, runtime) {
372
+ const otelEnv = buildOtelEnv(config, runtime);
373
+ const override = config[overrideKey];
374
+ if (override && typeof override === "object" && !Array.isArray(override)) {
375
+ const block = override;
376
+ if (block.mcp && typeof block.mcp === "object" && !Array.isArray(block.mcp)) {
377
+ // Full-replacement MCP override. We still inject OTEL into the
378
+ // env (auto-derived from collector config) and let ironbee env
379
+ // win last. Operator's own `mcp.env` keys take precedence over
380
+ // OTEL but lose to ironbee env.
381
+ const mcp = { ...block.mcp };
382
+ const mcpEnv = {
383
+ ...otelEnv,
384
+ ...(mcp.env ?? {}),
385
+ ...ironbeeEnv,
386
+ };
165
387
  mcp.env = mcpEnv;
166
388
  return mcp;
167
389
  }
168
- // no mcp provided — build default with browserDevTools.env merged
169
390
  const userEnv = {};
170
- if (bdtConfig.env && typeof bdtConfig.env === "object" && !Array.isArray(bdtConfig.env)) {
171
- const envObj = bdtConfig.env;
391
+ if (block.env && typeof block.env === "object" && !Array.isArray(block.env)) {
392
+ const envObj = block.env;
172
393
  for (const key of Object.keys(envObj)) {
173
394
  if (typeof envObj[key] === "string") {
174
395
  userEnv[key] = envObj[key];
@@ -178,57 +399,229 @@ function getMcpServerEntry(projectDir) {
178
399
  return {
179
400
  command: DEFAULT_MCP_COMMAND,
180
401
  args: [...DEFAULT_MCP_ARGS],
181
- env: { ...DEFAULT_MCP_ENV, ...userEnv, ...IRONBEE_ENV },
402
+ env: { ...defaultEnv, ...otelEnv, ...userEnv, ...ironbeeEnv },
182
403
  };
183
404
  }
184
- // no browserDevTools config — full defaults
185
405
  return {
186
406
  command: DEFAULT_MCP_COMMAND,
187
407
  args: [...DEFAULT_MCP_ARGS],
188
- env: { ...DEFAULT_MCP_ENV, ...IRONBEE_ENV },
408
+ env: { ...defaultEnv, ...otelEnv, ...ironbeeEnv },
189
409
  };
190
410
  }
411
+ /** Returns the MCP server entry for `browser-devtools` (PLATFORM=browser, bdt_). */
412
+ function getMcpServerEntry(projectDir) {
413
+ const config = loadConfig(projectDir);
414
+ return buildMcpEntry(config, "browserDevTools", BROWSER_IRONBEE_ENV, BROWSER_DEFAULT_MCP_ENV, "browser");
415
+ }
416
+ /** Returns the MCP server entry for `node-devtools` (PLATFORM=node, ndt_). */
417
+ function getNodeDevToolsMcpEntry(projectDir) {
418
+ const config = loadConfig(projectDir);
419
+ return buildMcpEntry(config, "nodeDevTools", NODE_IRONBEE_ENV, NODE_DEFAULT_MCP_ENV, "node");
420
+ }
191
421
  function getMaxRetries(config) {
192
422
  return (typeof config.maxRetries === "number" && config.maxRetries > 0)
193
423
  ? config.maxRetries
194
424
  : DEFAULT_MAX_RETRIES;
195
425
  }
196
426
  /**
197
- * Returns true when the job queue layer is enabled for this project.
427
+ * Returns true when the IronBee Collector is configured + active for this
428
+ * config — i.e. the collector section is present, `enable !== false`, the
429
+ * `url` is non-empty, and the `IRONBEE_COLLECTOR=false` env override isn't
430
+ * set. Pure check over an already-loaded `IronBeeConfig` so callers that
431
+ * have a config in hand don't pay an extra `loadConfig` round-trip.
198
432
  *
199
- * Rule:
200
- * - No `jobQueue` section in config disabled (default-off).
201
- * - `jobQueue` section present enabled, unless `enable: false` is set
202
- * explicitly. Adding any sub-key (e.g. `autoFlushSizeBytes`) implicitly
203
- * opts in.
433
+ * Used by the auto-enable behavior in `isJobQueueEnabled` /
434
+ * `isRecordingEnabled` / `isAnalyticsEnabled`: when the operator has
435
+ * configured a collector, we treat all three feature switches as
436
+ * implicitly opted in. Rationale: setting up a collector is a strong
437
+ * signal that the operator wants events to flow there, and these three
438
+ * sections are exactly what makes events flow (queue dispatch, recording
439
+ * enforcement, analytics derivation). Explicit `enable: false` on any of
440
+ * them still wins — the auto-enable only fires for "section absent".
441
+ */
442
+ function isCollectorConfigured(config) {
443
+ if (process.env.IRONBEE_COLLECTOR === "false") {
444
+ return false;
445
+ }
446
+ const section = config.collector;
447
+ if (!section) {
448
+ return false;
449
+ }
450
+ if (section.enable === false) {
451
+ return false;
452
+ }
453
+ if (typeof section.url !== "string" || section.url.length === 0) {
454
+ return false;
455
+ }
456
+ // apiKey is required — a collector running without auth is a developer
457
+ // convenience that ironbee no longer supports as the "configured" state.
458
+ // Production deployments always have an api key; for local testing,
459
+ // operators can either set a placeholder key or use IRONBEE_COLLECTOR=false
460
+ // to fully disable.
461
+ if (typeof section.apiKey !== "string" || section.apiKey.length === 0) {
462
+ return false;
463
+ }
464
+ return true;
465
+ }
466
+ /**
467
+ * Returns true when the job queue layer is enabled for this project.
204
468
  *
205
- * Used as both a producer-side gate (track-action skips wire construction
206
- * when off) and a defense-in-depth guard inside `submit()` itself.
469
+ * Resolution:
470
+ * 1. Section explicitly disabled (`enable: false`) `false`.
471
+ * 2. Section present (any other shape) → `true` (presence-as-opt-in).
472
+ * 3. Collector configured + valid → `true` (auto-enable: a collector is
473
+ * pointless without a queue feeding it tool_call events).
474
+ * 4. Otherwise → `false`.
207
475
  */
208
476
  function isJobQueueEnabled(projectDir) {
209
477
  const config = loadConfig(projectDir);
210
- return isPresenceEnabled(config.jobQueue);
478
+ return isFeatureEnabledWithCollectorAutoEnable(config, config.jobQueue);
211
479
  }
212
480
  /**
213
481
  * Returns true when recording enforcement is enabled for this project.
214
- * Same presence-as-opt-in semantics as `isJobQueueEnabled`: the `recording`
215
- * section being present in config.json turns enforcement on; set
216
- * `enable: false` explicitly to suspend without removing the section.
482
+ * Same resolution as `isJobQueueEnabled` (presence-as-opt-in + collector
483
+ * auto-enable + explicit-disable wins).
217
484
  */
218
485
  function isRecordingEnabled(projectDir) {
219
486
  const config = loadConfig(projectDir);
220
- return isPresenceEnabled(config.recording);
487
+ return isFeatureEnabledWithCollectorAutoEnable(config, config.recording);
221
488
  }
222
489
  /**
223
- * Shared "section present + not explicitly disabled" check used by the
224
- * three master-switch helpers (`jobQueue`, `recording`, and inline by
225
- * `getCollectorTarget`). Defensive against malformed config values
226
- * (null / non-object / array) those are treated as disabled.
490
+ * Shared resolution for sections that follow the "presence-as-opt-in +
491
+ * auto-enable when collector is configured" pattern. `jobQueue`, `recording`,
492
+ * `analytics` all use this. `collector` itself uses `isCollectorConfigured`
493
+ * directly (it's the one that drives the auto-enable, not subject to it).
227
494
  */
495
+ function isFeatureEnabledWithCollectorAutoEnable(config, section) {
496
+ // Explicit opt-out always wins, regardless of collector config.
497
+ if (section !== undefined && section !== null && typeof section === "object" && !Array.isArray(section)) {
498
+ if (section.enable === false) {
499
+ return false;
500
+ }
501
+ // Presence (with non-false `enable`) opts in.
502
+ return true;
503
+ }
504
+ // Section absent → fall back to collector-driven auto-enable.
505
+ return isCollectorConfigured(config);
506
+ }
228
507
  function isPresenceEnabled(section) {
229
508
  if (section === undefined || section === null || typeof section !== "object" || Array.isArray(section)) {
230
509
  return false;
231
510
  }
232
511
  return section.enable !== false;
233
512
  }
513
+ /**
514
+ * Returns true when verification enforcement is enabled for this config.
515
+ *
516
+ * **Inverse semantics from `isJobQueueEnabled` / `isRecordingEnabled`**:
517
+ * verification is the core feature, opt-out by `enable: false`. Section absent,
518
+ * empty section, or `enable: true` all return true. Only `enable: false`
519
+ * returns false.
520
+ */
521
+ function getVerificationEnabled(config) {
522
+ const section = config.verification;
523
+ if (section === undefined || section === null) {
524
+ return true;
525
+ }
526
+ if (typeof section !== "object" || Array.isArray(section)) {
527
+ return true;
528
+ }
529
+ return section.enable !== false;
530
+ }
531
+ /**
532
+ * Returns true when analytics collection is enabled for this project.
533
+ * Same resolution as `isJobQueueEnabled` / `isRecordingEnabled`:
534
+ * explicit-disable wins, presence opts in, and collector configured +
535
+ * section absent auto-enables.
536
+ */
537
+ function isAnalyticsEnabled(projectDir) {
538
+ const config = loadConfig(projectDir);
539
+ return isFeatureEnabledWithCollectorAutoEnable(config, config.analytics);
540
+ }
541
+ /** Whether the Stop hook should project + emit analytics. Default `true` when analytics is enabled. */
542
+ function isAnalyticsEmitOnStopEnabled(projectDir) {
543
+ const config = loadConfig(projectDir);
544
+ const section = config.analytics;
545
+ if (section === null || typeof section !== "object" || Array.isArray(section)) {
546
+ return true;
547
+ }
548
+ const v = section.emitOnStop;
549
+ return v !== false;
550
+ }
551
+ /**
552
+ * Throttle interval (seconds) for Stop-hook projection. Default 0 (disabled).
553
+ *
554
+ * The CLI used to default to 30s here as a "don't hammer the backend on
555
+ * hot Stop loops" guard. That was a CLI-side judgment about cadence — backends
556
+ * can rate-limit / dedupe / aggregate however they want, and a 30s default
557
+ * silently dropped per-Stop visibility (especially painful for verify-gate
558
+ * retry loops where each Stop carries fresh signal). Default is now 0;
559
+ * operators who DO want CLI-side throttling can set the config explicitly.
560
+ * `throttleState` in `hook-trigger.ts` returns null when interval ≤ 0, so
561
+ * 0 = no-op fast path.
562
+ */
563
+ function getAnalyticsEmitOnStopMinIntervalSeconds(projectDir) {
564
+ const config = loadConfig(projectDir);
565
+ const section = config.analytics;
566
+ if (section === null || typeof section !== "object" || Array.isArray(section)) {
567
+ return 0;
568
+ }
569
+ const v = section.emitOnStopMinIntervalSeconds;
570
+ return typeof v === "number" && v >= 0 ? v : 0;
571
+ }
572
+ /**
573
+ * Returns true when the per-turn `session_turn_analytics` wire records should
574
+ * be emitted alongside `session_analytics`. **Default `false` (opt-in)** —
575
+ * inverse of the `analytics` master switch's presence-as-opt-in.
576
+ *
577
+ * Rationale: turn-grain records are high-volume secondary signal; backends
578
+ * that only want session aggregates shouldn't pay for them by default. The
579
+ * base `session_analytics` record always ships when analytics is enabled.
580
+ */
581
+ function isAnalyticsTurnEventsEnabled(projectDir) {
582
+ const config = loadConfig(projectDir);
583
+ const section = config.analytics;
584
+ if (section === null || typeof section !== "object" || Array.isArray(section)) {
585
+ return false;
586
+ }
587
+ return section.emitTurnEvents === true;
588
+ }
589
+ /**
590
+ * Returns true when the per-step `session_turn_step_analytics` wire records
591
+ * should be emitted. **Default `false` (opt-in)** — same rationale as
592
+ * {@link isAnalyticsTurnEventsEnabled}, with one extra dimension: step-grain
593
+ * records are an order of magnitude higher volume than turn-grain.
594
+ *
595
+ * Independent of `emitTurnEvents`. Backend can reconstruct turn from steps,
596
+ * so step-only / turn-only / both / neither are all valid combinations.
597
+ */
598
+ function isAnalyticsStepEventsEnabled(projectDir) {
599
+ const config = loadConfig(projectDir);
600
+ const section = config.analytics;
601
+ if (section === null || typeof section !== "object" || Array.isArray(section)) {
602
+ return false;
603
+ }
604
+ return section.emitStepEvents === true;
605
+ }
606
+ /**
607
+ * Returns true when the per-API-request `api_request` wire records should be
608
+ * emitted. **Default `true` (opt-out)** — INVERSE of the turn / step event
609
+ * gating, mirroring the `verification.enable` opt-out semantics. Per-request
610
+ * audit (cost attribution, failure tracking, request-id timeline) is the
611
+ * primary value of the analytics pipeline for backend cost accounting, so
612
+ * it ships by default. Operators can opt out via `emitApiRequestEvents: false`
613
+ * if backend doesn't consume them.
614
+ *
615
+ * Independent of `emitTurnEvents` / `emitStepEvents`. The `analytics` master
616
+ * switch (section presence) still gates everything below it — without
617
+ * `analytics` in config, no api_request events ship regardless of this flag.
618
+ */
619
+ function isAnalyticsApiRequestEventsEnabled(projectDir) {
620
+ const config = loadConfig(projectDir);
621
+ const section = config.analytics;
622
+ if (section === null || typeof section !== "object" || Array.isArray(section)) {
623
+ return false;
624
+ }
625
+ return section.emitApiRequestEvents !== false;
626
+ }
234
627
  //# sourceMappingURL=config.js.map